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..353d839f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,18 +3,12 @@ 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 +i2pd.exe # Autotools @@ -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..d2bfe7bf 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,1012 +1,6 @@ # 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 -- RedDSA key blinding -- .b32.i2p addresses in jump links -- ntcp2.addressv6 parameter -### Changed -- Allow HTTP headers without value -- Set data directory from external storage path for Android -- addresshelper support is configurable per tunnel -- gradlew script for android build -### Fixed -- Deletion of expired encrypted LeaseSet2 on floodfills -- ipv6 fallback address -- SSU incoming packets routing - -## [2.24.0] - 2019-03-21 -### Added -- Support of transient keys for LeaseSet2 -- Support of encrypted LeaseSet2 -- Recognize signature type 11 (RedDSA) -- Support websocket connections over HTTP proxy -- Ability to disable full addressbook persist -### Changed -- Don't load peer profiles if non-persistant -- REUSE_ADDR for ipv6 acceptors -- Reset eTags if addressbook can't be loaded -### Fixed -- Build with boost 1.70 -- Filter out unspecified addresses from RouterInfo -- Check floodfill status change -- 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 -- Ability to adjust timestamps through the NTP -- Ability to disable peer profile persist -- Request permission for android >= 6 -- Initial addressbook to android assets -- Cancel graceful shutdown for android -- Russian translation for android -### Changed -- Chacha20 and Poly1305 implementation -- Eliminate extra copy of NTCP2 send buffers -- Extract content of tunnel.d from assets on android -- Removed name resolvers from transports -- Update reseed certificates -### Fixed -- LeaseSet published content verification -- Exclude invalid LeaseSets from the list on a floodfill -- Build for OpenWrt with openssl 1.1.1 - -## [2.22.0] - 2018-11-09 -### Added -- Multiple tunnel config files from tunnels.d folder -### Changed -- Fetch own RouterInfo upon SessionRequest for NTCP2 -- Faster XOR between AES blocks for non AVX capable CPUs -### Fixed -- Fixed NTCP2 termination send - -## [2.21.1] - 2018-10-22 -### Changed -- cost=13 for unpublished NTCP2 address -### Fixed -- Handle I2NP messages longer than 32K - -## [2.21.0] - 2018-10-04 -### Added -- EdDSA, x25519 and SipHash from openssl 1.1.1 -- NTCP2 ipv6 incoming connections -- Show total number of destination's outgoing tags in the web console -### Changed -- Android build with openssl 1.1.1/boost 1.64 -- Bandwidth classes 'P' and 'X' don't add 'O' anymore -### Fixed -- Update own RouterInfo if no SSU -- Recognize 'P' and 'X' routers as high bandwidth without 'O' -- NTCP address doesn't disappear if NTCP2 enabled -- Android with api 26+ - -## [2.20.0] - 2018-08-23 -### Added -- Full implementation of NTCP2 -- Assets for android -### Changed -- armeabi-v7a and x86 in one apk for android -- 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 - -## [2.19.0] - 2018-06-26 -### Added -- ECIES support for RouterInfo -- HTTP outproxy authorization -- AVX/AESNI runtime detection -- Initial implementation of NTCP2 -- I2CP session reconfigure -- I2CP method ClientServicesInfo -- Datagrams to websocks -### Changed -- RouterInfo uses EdDSA signature by default -- Remove stream bans -- Android build system changed to gradle -- Multiple changes in QT GUI -- Dockerfile -### Fixed -- zero tunnelID issue -- tunnels reload -- headers in webconsole -- XSS in webconsole from SAM session name -- build for gcc 8 -- cmake build scripts -- systemd service files -- some netbsd issues - ## [2.18.0] - 2018-01-30 ### Added - Show tunnel nicknames for I2CP destination in WebUI @@ -1049,7 +43,7 @@ - NTCP soft and hard descriptors limits - Support full timestamps in logs ### Changed -- Faster implementation of GOST R 34.11 hash +- Faster implmentation of GOST R 34.11 hash - Reject routers with RSA signtures - Reload config and shudown from Windows GUI - Update tunnels address(destination) without restart @@ -1144,12 +138,12 @@ ### 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 ### Changed -- Reduced file descriptors usage +- Reduced file descriptiors usage - Strict reseed checks enabled by default ## Fixed diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..4931a709 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,54 @@ +FROM alpine:latest + +MAINTAINER Mikal Villa + +ENV GIT_BRANCH="master" +ENV I2PD_PREFIX="/opt/i2pd-${GIT_BRANCH}" +ENV PATH=${I2PD_PREFIX}/bin:$PATH + +ENV GOSU_VERSION=1.7 +ENV GOSU_SHASUM="34049cfc713e8b74b90d6de49690fa601dc040021980812b2f1f691534be8a50 /usr/local/bin/gosu" + +RUN mkdir /user && adduser -S -h /user i2pd && chown -R i2pd:nobody /user + + +# +# Each RUN is a layer, adding the dependencies and building i2pd in one layer takes around 8-900Mb, so to keep the +# image under 20mb we need to remove all the build dependencies in the same "RUN" / layer. +# + +# 1. install deps, clone and build. +# 2. strip binaries. +# 3. Purge all dependencies and other unrelated packages, including build directory. +RUN apk --no-cache --virtual build-dependendencies add make gcc g++ libtool boost-dev build-base openssl-dev openssl git \ + && mkdir -p /tmp/build \ + && cd /tmp/build && git clone -b ${GIT_BRANCH} https://github.com/PurpleI2P/i2pd.git \ + && cd i2pd \ + && make -j4 \ + && mkdir -p ${I2PD_PREFIX}/bin \ + && mv i2pd ${I2PD_PREFIX}/bin/ \ + && cd ${I2PD_PREFIX}/bin \ + && strip i2pd \ + && rm -fr /tmp/build && apk --purge del build-dependendencies build-base fortify-headers boost-dev zlib-dev openssl-dev \ + boost-python3 python3 gdbm boost-unit_test_framework boost-python linux-headers boost-prg_exec_monitor \ + boost-serialization boost-signals boost-wave boost-wserialization boost-math boost-graph boost-regex git pcre \ + libtool g++ gcc pkgconfig + +# 2. Adding required libraries to run i2pd to ensure it will run. +RUN apk --no-cache add boost-filesystem boost-system boost-program_options boost-date_time boost-thread boost-iostreams openssl musl-utils libstdc++ + +# Gosu is a replacement for su/sudo in docker and not a backdoor :) See https://github.com/tianon/gosu +RUN wget -O /usr/local/bin/gosu https://github.com/tianon/gosu/releases/download/${GOSU_VERSION}/gosu-amd64 \ + && echo "${GOSU_SHASUM}" | sha256sum -c && chmod +x /usr/local/bin/gosu + +COPY entrypoint.sh /entrypoint.sh + +RUN chmod a+x /entrypoint.sh +RUN echo "export PATH=${PATH}" >> /etc/profile + +VOLUME [ "/var/lib/i2pd" ] + +EXPOSE 7070 4444 4447 7656 2827 7654 7650 + +ENTRYPOINT [ "/entrypoint.sh" ] + 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..460b16e4 100644 --- a/Makefile +++ b/Makefile @@ -1,53 +1,26 @@ -.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) +USE_AESNI := yes +USE_AVX := yes +USE_STATIC := no +USE_MESHNET := no +USE_UPNP := no -# 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) - -ifeq ($(DEBUG),yes) - CXX_DEBUG = -g -else - CXX_DEBUG = -Os - LD_DEBUG = -s -endif - -ifneq (, $(DESTDIR)) - PREFIX = $(DESTDIR) +ifeq ($(WEBSOCKETS),1) + NEEDED_CXXFLAGS += -DWITH_EVENTS endif ifneq (, $(findstring darwin, $(SYS))) @@ -57,57 +30,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))) +else ifneq (, $(findstring linux, $(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 + include Makefile.linux +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) +$(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 +68,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 +119,9 @@ 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..f2293540 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 - +CXXFLAGS ?= -g -Wall -Wextra -Wno-unused-parameter -pedantic -Wno-misleading-indentation ## 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 = -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..b3105c56 100644 --- a/Makefile.homebrew +++ b/Makefile.homebrew @@ -1,49 +1,45 @@ # root directory holding homebrew -BREWROOT = /opt/homebrew +BREWROOT = /usr/local BOOSTROOT = ${BREWROOT}/opt/boost -SSLROOT = ${BREWROOT}/opt/openssl@1.1 +SSLROOT = ${BREWROOT}/opt/libressl UPNPROOT = ${BREWROOT}/opt/miniupnpc +CXXFLAGS = -g -Wall -std=c++11 -DMAC_OSX -Wno-overloaded-virtual +INCFLAGS = -I${SSLROOT}/include -I${BOOSTROOT}/include -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 + LDFLAGS = -L${SSLROOT}/lib -L${BOOSTROOT}/lib + 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 -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 +# 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 +# note from psi: 2009 macbook does not have aesni +#ifeq ($(USE_AESNI),yes) +# CXXFLAGS += -maes -DAESNI +#endif + +# Disabled, since it will be the default make rule. I think its better +# to define the default rule in Makefile and not Makefile. - torkel +#install: all +# test -d ${PREFIX} || mkdir -p ${PREFIX}/ +# cp -r i2p ${PREFIX}/ diff --git a/Makefile.linux b/Makefile.linux index 4ea39e22..4a82591a 100644 --- a/Makefile.linux +++ b/Makefile.linux @@ -1,25 +1,28 @@ # set defaults instead redefine -CXXFLAGS ?= ${CXX_DEBUG} -Wall -Wextra -Wno-unused-parameter -pedantic -Wno-psabi -LDFLAGS ?= ${LD_DEBUG} +CXXFLAGS ?= -g -Wall -Wextra -Wno-unused-parameter -pedantic -Wno-misleading-indentation +INCFLAGS ?= ## NOTE: The NEEDED_CXXFLAGS are 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 FDLAGS to work at build-time. +## custom FLAGS 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-7]\.[0-9]"),3) # gcc >= 5.0 + NEEDED_CXXFLAGS += -std=c++11 +else ifeq ($(shell expr match ${CXXVER} "7"),1) # gcc 7 ubuntu + NEEDED_CXXFLAGS += -std=c++11 else # not supported $(error Compiler too old) endif @@ -30,41 +33,40 @@ 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) + CPU_FLAGS += -maes -DAESNI +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..b40d0ada 100644 --- a/Makefile.mingw +++ b/Makefile.mingw @@ -1,50 +1,54 @@ -# Build application with GUI (tray, main window) -USE_WIN32_APP := yes - -WINDRES = windres - -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 - -# UPNP Support -ifeq ($(USE_UPNP),yes) - DEFINES += -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 \ - -lwsock32 \ - -lws2_32 \ - -liphlpapi \ - -lcrypt32 \ - -lgdi32 \ - -lole32 \ - -luuid \ - -lpthread - -ifeq ($(USE_WIN32_APP), yes) - DEFINES += -DWIN32_APP - LDFLAGS += -mwindows - DAEMON_RC += Win32/Resource.rc - DAEMON_OBJS += $(patsubst %.rc,obj/%.o,$(DAEMON_RC)) -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 $@ +USE_WIN32_APP=yes +CXX = g++ +WINDRES = windres +CXXFLAGS = -Os -D_MT -DWIN32 -D_WINDOWS -DWIN32_LEAN_AND_MEAN +NEEDED_CXXFLAGS = -std=c++11 +BOOST_SUFFIX = -mt +INCFLAGS = -Idaemon -I. +LDFLAGS = -s -Wl,-rpath,/usr/local/lib -Wl,-Bstatic -static-libgcc -static-libstdc++ + +# UPNP Support +ifeq ($(USE_UPNP),yes) + CXXFLAGS += -DUSE_UPNP -DMINIUPNP_STATICLIB + LDLIBS = -lminiupnpc +endif + +LDLIBS += \ + -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 + +ifeq ($(USE_WIN32_APP), yes) + 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 -DAESNI +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 + $(WINDRES) -i $< -o $@ diff --git a/Makefile.osx b/Makefile.osx index 52282307..8bbf37f0 100644 --- a/Makefile.osx +++ b/Makefile.osx @@ -1,20 +1,18 @@ CXX = clang++ -CXXFLAGS := ${CXX_DEBUG} -Wall -std=c++17 +CXXFLAGS = -Os -Wall -std=c++11 -DMAC_OSX +#CXXFLAGS = -g -O2 -Wall -std=c++11 INCFLAGS = -I/usr/local/include -DEFINES := -DMAC_OSX -LDFLAGS := -Wl,-rpath,/usr/local/lib -L/usr/local/lib -LDFLAGS += -Wl,-dead_strip -LDFLAGS += -Wl,-dead_strip_dylibs +LDFLAGS = -Wl,-rpath,/usr/local/lib -L/usr/local/lib 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 +20,18 @@ ifeq ($(USE_UPNP),yes) endif endif -OSARCH = $(shell uname -p) - -ifneq ($(OSARCH),powerpc) +ifeq ($(USE_AESNI),1) + CXXFLAGS += -maes -DAESNI +else CXXFLAGS += -msse endif + +ifeq ($(USE_AVX),1) + CXXFLAGS += -mavx +endif + +# Disabled, since it will be the default make rule. I think its better +# to define the default rule in Makefile and not Makefile. - torkel +#install: all +# test -d ${PREFIX} || mkdir -p ${PREFIX}/ +# cp -r i2p ${PREFIX}/ 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..40948b34 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,3 @@ -[![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* - i2pd ==== @@ -47,17 +38,11 @@ Resources Installing ---------- -The easiest way to install i2pd is by using precompiled packages and binaries. -You can fetch most of them on [release](https://github.com/PurpleI2P/i2pd/releases/latest) page. -Please see [documentation](https://i2pd.readthedocs.io/en/latest/user-guide/install/) for more info. - -Building --------- +The easiest way to install i2pd is by using +[precompiled binaries](https://github.com/PurpleI2P/i2pd/releases/latest). 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 +54,11 @@ 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 x86/x64 - [![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) +* FreeBSD +* Android * iOS Using i2pd @@ -86,36 +67,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..3f9226d1 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,45 @@ namespace util setlocale(LC_CTYPE, ""); SetConsoleCP(1251); SetConsoleOutputCP(1251); - //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 ); - } - ); + setlocale(LC_ALL, "Russian"); 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); + I2PService service(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 +67,12 @@ namespace util setlocale(LC_CTYPE, ""); SetConsoleCP(1251); SetConsoleOutputCP(1251); - //setlocale(LC_ALL, "Russian"); - setlocale(LC_TIME, "C"); + setlocale(LC_ALL, "Russian"); #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..f58b4615 100644 --- a/Win32/Win32App.cpp +++ b/Win32/Win32App.cpp @@ -1,502 +1,394 @@ -/* -* 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_TRAY_ICON 2050 +#define WM_TRAYICON (WM_USER + 1) + +#define IDT_GRACEFUL_SHUTDOWN_TIMER 2100 +#define FRAME_UPDATE_TIMER 2101 + +namespace i2p +{ +namespace win32 +{ + 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); + 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 ()); + 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) + { + switch (uMsg) + { + case WM_CREATE: + { + AddTrayIcon (hWnd); + break; + } + case WM_CLOSE: + { + RemoveTrayIcon (hWnd); + KillTimer (hWnd, FRAME_UPDATE_TIMER); + KillTimer (hWnd, IDT_GRACEFUL_SHUTDOWN_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_GRACEFUL_SHUTDOWN: + { + i2p::context.SetAcceptsTunnels (false); + SetTimer (hWnd, IDT_GRACEFUL_SHUTDOWN_TIMER, 10*60*1000, nullptr); // 10 minutes + i2p::util::DaemonWin32::Instance ().isGraceful = true; + return 0; + } + case ID_STOP_GRACEFUL_SHUTDOWN: + { + i2p::context.SetAcceptsTunnels (true); + KillTimer (hWnd, IDT_GRACEFUL_SHUTDOWN_TIMER); + 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: + { + if (wParam == IDT_GRACEFUL_SHUTDOWN_TIMER) + { + PostMessage (hWnd, WM_CLOSE, 0, 0); // exit + return 0; + } + if (wParam == FRAME_UPDATE_TIMER) + { + InvalidateRect(hWnd, NULL, TRUE); + } + 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; + } + } + 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..4a0058ac 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,124 @@ 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; + } + strncat(szPath, " --daemon", MAX_PATH); + + // 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 60% rename from build/win_installer.iss rename to Win32/installer.iss index a4b67ad2..15ce4372 100644 --- a/build/win_installer.iss +++ b/Win32/installer.iss @@ -1,50 +1,37 @@ #define I2Pd_AppName "i2pd" +#define I2Pd_ver "2.18.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..d9fa5a57 --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,8 @@ +gen +tests +.idea +ant.properties +local.properties +build.sh +bin +log* \ No newline at end of file diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml new file mode 100755 index 00000000..3ae952b3 --- /dev/null +++ b/android/AndroidManifest.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 00000000..a88403fd --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,47 @@ +buildscript { + repositories { + mavenCentral() + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:2.1.2' + } +} + +apply plugin: 'com.android.application' + +android { + compileSdkVersion 25 + buildToolsVersion "25.0.2" + defaultConfig { + applicationId "org.purplei2p.i2pd" + targetSdkVersion 25 + minSdkVersion 14 + versionCode 1 + versionName "2.17.1" + } + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src'] + res.srcDirs = ['res'] + jniLibs.srcDirs = ['libs'] + } + } + signingConfigs { + orignal { + storeFile file("i2pdapk.jks") + storePassword "android" + keyAlias "i2pdapk" + keyPassword "android" + } + } + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.orignal + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt' + } + } +} + 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/jni/Android.mk b/android/jni/Android.mk new file mode 100755 index 00000000..d5a8f05f --- /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_62_0/$(TARGET_ARCH_ABI)/lib/libboost_system.a +LOCAL_EXPORT_C_INCLUDES := $(BOOST_PATH)/boost_1_62_0/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_62_0/$(TARGET_ARCH_ABI)/lib/libboost_date_time.a +LOCAL_EXPORT_C_INCLUDES := $(BOOST_PATH)/boost_1_62_0/include +include $(PREBUILT_STATIC_LIBRARY) + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) +LOCAL_MODULE := boost_filesystem +LOCAL_SRC_FILES := $(BOOST_PATH)/boost_1_62_0/$(TARGET_ARCH_ABI)/lib/libboost_filesystem.a +LOCAL_EXPORT_C_INCLUDES := $(BOOST_PATH)/boost_1_62_0/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_62_0/$(TARGET_ARCH_ABI)/lib/libboost_program_options.a +LOCAL_EXPORT_C_INCLUDES := $(BOOST_PATH)/boost_1_62_0/include +include $(PREBUILT_STATIC_LIBRARY) + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) +LOCAL_MODULE := crypto +LOCAL_SRC_FILES := $(OPENSSL_PATH)/openssl-1.1.0e/$(TARGET_ARCH_ABI)/lib/libcrypto.a +LOCAL_EXPORT_C_INCLUDES := $(OPENSSL_PATH)/openssl-1.1.0e/include +include $(PREBUILT_STATIC_LIBRARY) + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) +LOCAL_MODULE := ssl +LOCAL_SRC_FILES := $(OPENSSL_PATH)/openssl-1.1.0e/$(TARGET_ARCH_ABI)/lib/libssl.a +LOCAL_EXPORT_C_INCLUDES := $(OPENSSL_PATH)/openssl-1.1.0e/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)/miniupnp-2.0/$(TARGET_ARCH_ABI)/lib/libminiupnpc.a +LOCAL_EXPORT_C_INCLUDES := $(MINIUPNP_PATH)/miniupnp-2.0/include +include $(PREBUILT_STATIC_LIBRARY) diff --git a/android/jni/Application.mk b/android/jni/Application.mk new file mode 100755 index 00000000..0fa116c2 --- /dev/null +++ b/android/jni/Application.mk @@ -0,0 +1,40 @@ +#APP_ABI := all +#APP_ABI := armeabi-v7a x86 +#APP_ABI := x86 +#APP_ABI := x86_64 +APP_ABI := armeabi-v7a +#can be android-3 but will fail for x86 since arch-x86 is not present at ndkroot/platforms/android-3/ . libz is taken from there. +APP_PLATFORM := android-14 + +# http://stackoverflow.com/a/21386866/529442 http://stackoverflow.com/a/15616255/529442 to enable c++11 support in Eclipse +NDK_TOOLCHAIN_VERSION := 4.9 +# APP_STL := stlport_shared --> does not seem to contain C++11 features +APP_STL := gnustl_shared + +# Enable c++11 extensions in source code +APP_CPPFLAGS += -std=c++11 + +APP_CPPFLAGS += -DANDROID -D__ANDROID__ -DUSE_UPNP +ifeq ($(TARGET_ARCH_ABI),armeabi-v7a) +APP_CPPFLAGS += -DANDROID_ARM7A +endif + +APP_OPTIM := debug + +# 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..75584740 --- /dev/null +++ b/android/jni/DaemonAndroid.cpp @@ -0,0 +1,193 @@ +#include "DaemonAndroid.h" +#include "Daemon.h" +#include +#include +#include +#include +//#include "mainwindow.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."); + } + } +*/ + 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; + 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(); + } +} +} diff --git a/android/jni/DaemonAndroid.h b/android/jni/DaemonAndroid.h new file mode 100644 index 00000000..9cc8219b --- /dev/null +++ b/android/jni/DaemonAndroid.h @@ -0,0 +1,87 @@ +#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(); + + /* + 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..8791c90b --- /dev/null +++ b/android/jni/i2pd_android.cpp @@ -0,0 +1,66 @@ + +//#include +#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_onNetworkStateChanged + (JNIEnv * env, jclass clazz, jboolean isConnected) +{ + bool isConnectedBool = (bool) isConnected; + i2p::transport::transports.SetOnline (isConnectedBool); +} 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..04923d22 --- /dev/null +++ b/android/jni/org_purplei2p_i2pd_I2PD_JNI.h @@ -0,0 +1,33 @@ +/* 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_onNetworkStateChanged + (JNIEnv * env, jclass clazz, jboolean isConnected); + +#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..f72b9716 --- /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-25 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/menu/options_main.xml b/android/res/menu/options_main.xml new file mode 100644 index 00000000..f66caa2a --- /dev/null +++ b/android/res/menu/options_main.xml @@ -0,0 +1,16 @@ + + + + diff --git a/android/res/values/strings.xml b/android/res/values/strings.xml new file mode 100755 index 00000000..0b8bef38 --- /dev/null +++ b/android/res/values/strings.xml @@ -0,0 +1,11 @@ + + + i2pd + i2pd started + i2pd service started + i2pd service stopped + Quit + Graceful Quit + Graceful quit is already in progress + Graceful quit is in progress + diff --git a/android/src/org/purplei2p/i2pd/DaemonSingleton.java b/android/src/org/purplei2p/i2pd/DaemonSingleton.java new file mode 100644 index 00000000..65afd0f5 --- /dev/null +++ b/android/src/org/purplei2p/i2pd/DaemonSingleton.java @@ -0,0 +1,126 @@ +package org.purplei2p.i2pd; + +import java.util.HashSet; +import java.util.Set; + +import android.util.Log; + +public class DaemonSingleton { + private static final String TAG="i2pd"; + private static final DaemonSingleton instance = new DaemonSingleton(); + public static interface StateUpdateListener { void daemonStateUpdate(); } + private final Set stateUpdateListeners = new HashSet(); + + public static DaemonSingleton getInstance() { + return instance; + } + + public synchronized void addStateChangeListener(StateUpdateListener listener) { stateUpdateListeners.add(listener); } + public synchronized void removeStateChangeListener(StateUpdateListener listener) { stateUpdateListeners.remove(listener); } + + public synchronized void stopAcceptingTunnels() { + if(isStartedOkay()){ + state=State.gracefulShutdownInProgress; + fireStateUpdate(); + I2PD_JNI.stopAcceptingTunnels(); + } + } + + public void onNetworkStateChange(boolean isConnected) { + I2PD_JNI.onNetworkStateChanged(isConnected); + } + + private boolean startedOkay; + + public static enum State {uninitialized,starting,jniLibraryLoaded,startedOkay,startFailed,gracefulShutdownInProgress}; + + private State state = State.uninitialized; + + public State getState() { return state; } + + public synchronized void start() { + if(state != State.uninitialized)return; + state = State.starting; + fireStateUpdate(); + new Thread(new Runnable(){ + + @Override + public void run() { + try { + I2PD_JNI.loadLibraries(); + synchronized (DaemonSingleton.this) { + state = State.jniLibraryLoaded; + fireStateUpdate(); + } + } catch (Throwable tr) { + lastThrowable=tr; + synchronized (DaemonSingleton.this) { + state = State.startFailed; + fireStateUpdate(); + } + return; + } + try { + synchronized (DaemonSingleton.this) { + daemonStartResult = I2PD_JNI.startDaemon(); + if("ok".equals(daemonStartResult)){ + state=State.startedOkay; + setStartedOkay(true); + }else state=State.startFailed; + fireStateUpdate(); + } + } catch (Throwable tr) { + lastThrowable=tr; + synchronized (DaemonSingleton.this) { + state = State.startFailed; + fireStateUpdate(); + } + return; + } + } + + }, "i2pdDaemonStart").start(); + } + private Throwable lastThrowable; + private String daemonStartResult="N/A"; + + private synchronized void fireStateUpdate() { + Log.i(TAG, "daemon state change: "+state); + for(StateUpdateListener listener : stateUpdateListeners) { + try { + listener.daemonStateUpdate(); + } catch (Throwable tr) { + Log.e(TAG, "exception in listener ignored", tr); + } + } + } + + public Throwable getLastThrowable() { + return lastThrowable; + } + + public String getDaemonStartResult() { + return daemonStartResult; + } + + private final Object startedOkayLock = new Object(); + + public boolean isStartedOkay() { + synchronized (startedOkayLock) { + return startedOkay; + } + } + + private void setStartedOkay(boolean startedOkay) { + synchronized (startedOkayLock) { + this.startedOkay = startedOkay; + } + } + + public synchronized void stopDaemon() { + if(isStartedOkay()){ + try {I2PD_JNI.stopDaemon();}catch(Throwable tr){Log.e(TAG, "", tr);} + setStartedOkay(false); + } + } +} diff --git a/android/src/org/purplei2p/i2pd/ForegroundService.java b/android/src/org/purplei2p/i2pd/ForegroundService.java new file mode 100644 index 00000000..bfd650c8 --- /dev/null +++ b/android/src/org/purplei2p/i2pd/ForegroundService.java @@ -0,0 +1,97 @@ +package org.purplei2p.i2pd; + +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.Service; +import android.content.Intent; +import android.os.Binder; +import android.os.IBinder; +import android.util.Log; +import android.widget.Toast; + +public class ForegroundService extends Service { + private NotificationManager notificationManager; + + // Unique Identification Number for the Notification. + // We use it on Notification start, and to cancel it. + private int NOTIFICATION = R.string.i2pd_started; + + /** + * Class for clients to access. Because we know this service always + * runs in the same process as its clients, we don't need to deal with + * IPC. + */ + public class LocalBinder extends Binder { + ForegroundService getService() { + return ForegroundService.this; + } + } + + @Override + public void onCreate() { + notificationManager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); + + // Display a notification about us starting. We put an icon in the status bar. + showNotification(); + daemon.start(); + // Tell the user we started. + Toast.makeText(this, R.string.i2pd_service_started, Toast.LENGTH_SHORT).show(); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + Log.i("ForegroundService", "Received start id " + startId + ": " + intent); + daemon.start(); + return START_STICKY; + } + + @Override + public void onDestroy() { + // Cancel the persistent notification. + notificationManager.cancel(NOTIFICATION); + + stopForeground(true); + + // Tell the user we stopped. + Toast.makeText(this, R.string.i2pd_service_stopped, Toast.LENGTH_SHORT).show(); + } + + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } + + // This is the object that receives interactions from clients. See + // RemoteService for a more complete example. + private final IBinder mBinder = new LocalBinder(); + + /** + * Show a notification while this service is running. + */ + private void showNotification() { + // In this sample, we'll use the same text for the ticker and the expanded notification + CharSequence text = getText(R.string.i2pd_started); + + // The PendingIntent to launch our activity if the user selects this notification + PendingIntent contentIntent = PendingIntent.getActivity(this, 0, + new Intent(this, I2PD.class), 0); + + // Set the info for the views that show in the notification panel. + Notification notification = new Notification.Builder(this) + .setSmallIcon(R.drawable.itoopie_notification_icon) // the status icon + .setTicker(text) // the status text + .setWhen(System.currentTimeMillis()) // the time stamp + .setContentTitle(getText(R.string.app_name)) // the label of the entry + .setContentText(text) // the contents of the entry + .setContentIntent(contentIntent) // The intent to send when the entry is clicked + .build(); + + // Send the notification. + //mNM.notify(NOTIFICATION, notification); + startForeground(NOTIFICATION, notification); + } + + private final DaemonSingleton daemon = DaemonSingleton.getInstance(); +} + diff --git a/android/src/org/purplei2p/i2pd/I2PD.java b/android/src/org/purplei2p/i2pd/I2PD.java new file mode 100755 index 00000000..a2494b2b --- /dev/null +++ b/android/src/org/purplei2p/i2pd/I2PD.java @@ -0,0 +1,245 @@ +package org.purplei2p.i2pd; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Timer; +import java.util.TimerTask; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Build; +import android.os.Bundle; +import android.os.IBinder; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; +import android.widget.TextView; +import android.widget.Toast; + +public class I2PD extends Activity { + private static final String TAG = "i2pd"; + + private TextView textView; + + private final DaemonSingleton daemon = DaemonSingleton.getInstance(); + + private DaemonSingleton.StateUpdateListener daemonStateUpdatedListener = + new DaemonSingleton.StateUpdateListener() { + + @Override + public void daemonStateUpdate() { + runOnUiThread(new Runnable(){ + + @Override + public void run() { + try { + if(textView==null)return; + Throwable tr = daemon.getLastThrowable(); + if(tr!=null) { + textView.setText(throwableToString(tr)); + return; + } + DaemonSingleton.State state = daemon.getState(); + textView.setText(String.valueOf(state)+ + (DaemonSingleton.State.startFailed.equals(state)?": "+daemon.getDaemonStartResult():"")); + } catch (Throwable tr) { + Log.e(TAG,"error ignored",tr); + } + } + }); + } + }; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + textView = new TextView(this); + setContentView(textView); + DaemonSingleton.getInstance().addStateChangeListener(daemonStateUpdatedListener); + daemonStateUpdatedListener.daemonStateUpdate(); + + //set the app be foreground + doBindService(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + localDestroy(); + } + + private void localDestroy() { + textView = null; + DaemonSingleton.getInstance().removeStateChangeListener(daemonStateUpdatedListener); + Timer gracefulQuitTimer = getGracefulQuitTimer(); + if(gracefulQuitTimer!=null) { + gracefulQuitTimer.cancel(); + setGracefulQuitTimer(null); + } + try{ + doUnbindService(); + }catch(Throwable tr){ + Log.e(TAG, "", tr); + } + } + + private CharSequence throwableToString(Throwable tr) { + StringWriter sw = new StringWriter(8192); + PrintWriter pw = new PrintWriter(sw); + tr.printStackTrace(pw); + pw.close(); + return sw.toString(); + } + +// private LocalService mBoundService; + + private ServiceConnection mConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + // This is called when the connection with the service has been + // established, giving us the service object we can use to + // interact with the service. Because we have bound to a explicit + // service that we know is running in our own process, we can + // cast its IBinder to a concrete class and directly access it. +// mBoundService = ((LocalService.LocalBinder)service).getService(); + + // Tell the user about this for our demo. +// Toast.makeText(Binding.this, R.string.local_service_connected, +// Toast.LENGTH_SHORT).show(); + } + + public void onServiceDisconnected(ComponentName className) { + // This is called when the connection with the service has been + // unexpectedly disconnected -- that is, its process crashed. + // Because it is running in our same process, we should never + // see this happen. +// mBoundService = null; +// Toast.makeText(Binding.this, R.string.local_service_disconnected, +// Toast.LENGTH_SHORT).show(); + } + }; + + + private boolean mIsBound; + + private void doBindService() { + // Establish a connection with the service. We use an explicit + // class name because we want a specific service implementation that + // we know will be running in our own process (and thus won't be + // supporting component replacement by other applications). + bindService(new Intent(this, + ForegroundService.class), mConnection, Context.BIND_AUTO_CREATE); + mIsBound = true; + } + + private void doUnbindService() { + if (mIsBound) { + // Detach our existing connection. + unbindService(mConnection); + mIsBound = false; + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.options_main, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle action bar item clicks here. The action bar will + // automatically handle clicks on the Home/Up button, so long + // as you specify a parent activity in AndroidManifest.xml. + int id = item.getItemId(); + + switch(id){ + case R.id.action_quit: + quit(); + return true; + case R.id.action_graceful_quit: + gracefulQuit(); + return true; + } + + return super.onOptionsItemSelected(item); + } + + @SuppressLint("NewApi") + private void quit() { + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + finishAndRemoveTask(); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + finishAffinity(); + } else { + //moveTaskToBack(true); + finish(); + } + }catch (Throwable tr) { + Log.e(TAG, "", tr); + } + try{ + daemon.stopDaemon(); + }catch (Throwable tr) { + Log.e(TAG, "", tr); + } + System.exit(0); + } + + private Timer gracefulQuitTimer; + private final Object gracefulQuitTimerLock = new Object(); + private void gracefulQuit() { + if(getGracefulQuitTimer()!=null){ + Toast.makeText(this, R.string.graceful_quit_is_already_in_progress, + Toast.LENGTH_SHORT).show(); + return; + } + Toast.makeText(this, R.string.graceful_quit_is_in_progress, + Toast.LENGTH_SHORT).show(); + new Thread(new Runnable(){ + + @Override + public void run() { + try{ + Log.d(TAG, "grac stopping"); + if(daemon.isStartedOkay()) { + daemon.stopAcceptingTunnels(); + Timer gracefulQuitTimer = new Timer(true); + setGracefulQuitTimer(gracefulQuitTimer); + gracefulQuitTimer.schedule(new TimerTask(){ + + @Override + public void run() { + quit(); + } + + }, 10*60*1000/*milliseconds*/); + }else{ + quit(); + } + } catch(Throwable tr) { + Log.e(TAG,"",tr); + } + } + + },"gracQuitInit").start(); + } + + private Timer getGracefulQuitTimer() { + synchronized (gracefulQuitTimerLock) { + return gracefulQuitTimer; + } + } + + private void setGracefulQuitTimer(Timer gracefulQuitTimer) { + synchronized (gracefulQuitTimerLock) { + this.gracefulQuitTimer = gracefulQuitTimer; + } + } +} diff --git a/android/src/org/purplei2p/i2pd/I2PD_JNI.java b/android/src/org/purplei2p/i2pd/I2PD_JNI.java new file mode 100644 index 00000000..f965d471 --- /dev/null +++ b/android/src/org/purplei2p/i2pd/I2PD_JNI.java @@ -0,0 +1,21 @@ +package org.purplei2p.i2pd; + +public class I2PD_JNI { + public static native String getABICompiledWith(); + /** + * returns error info if failed + * returns "ok" if daemon initialized and started okay + */ + public static native String startDaemon(); + //should only be called after startDaemon() success + public static native void stopDaemon(); + + public static native void stopAcceptingTunnels(); + + public static native void onNetworkStateChanged(boolean isConnected); + + public static void loadLibraries() { + System.loadLibrary("gnustl_shared"); + System.loadLibrary("i2pd"); + } +} diff --git a/android/src/org/purplei2p/i2pd/NetworkStateChangeReceiver.java b/android/src/org/purplei2p/i2pd/NetworkStateChangeReceiver.java new file mode 100644 index 00000000..e2f284b0 --- /dev/null +++ b/android/src/org/purplei2p/i2pd/NetworkStateChangeReceiver.java @@ -0,0 +1,30 @@ +package org.purplei2p.i2pd; + +import android.util.Log; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; + +public class NetworkStateChangeReceiver extends BroadcastReceiver { + + private static final String TAG = "i2pd"; + + //api level 1 + @Override + public void onReceive(final Context context, final Intent intent) { + Log.d(TAG,"Network state change"); + try { + ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo activeNetworkInfo = cm.getActiveNetworkInfo(); + boolean isConnected = activeNetworkInfo!=null && activeNetworkInfo.isConnected(); + // https://developer.android.com/training/monitoring-device-state/connectivity-monitoring.html?hl=ru + // boolean isWiFi = activeNetworkInfo!=null && (activeNetworkInfo.getType() == ConnectivityManager.TYPE_WIFI); + + I2PD_JNI.onNetworkStateChanged(isConnected); + } catch (Throwable tr) { + Log.d(TAG,"",tr); + } + } +} diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 00000000..ac632141 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,49 @@ +version: 2.18.{build} +pull_requests: + do_not_increment_build_number: true +branches: + only: + - openssl +skip_tags: true +os: Visual Studio 2015 +shallow_clone: true +clone_depth: 1 + +environment: + MSYS2_PATH_TYPE: inherit + CHERE_INVOKING: enabled_from_arguments + matrix: + - MSYSTEM: MINGW64 + - MSYSTEM: MINGW32 + +install: +- c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Rns gcc-fortran gcc" +- c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Syuu" + +- c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Syuu" + +- if "%MSYSTEM%" == "MINGW64" ( + c:\msys64\usr\bin\bash -lc "pacman --noconfirm -S mingw-w64-x86_64-boost mingw-w64-x86_64-miniupnpc" + ) else ( + c:\msys64\usr\bin\bash -lc "pacman --noconfirm -S mingw-w64-i686-boost mingw-w64-i686-miniupnpc" + ) + +- if "%MSYSTEM%" == "MINGW64" ( + set "bitness=64" + ) else ( + set "bitness=32" + ) + +build_script: +- cmd: >- + cd \projects\i2pd + + echo MSYSTEM = %MSYSTEM%, bitness = %bitness% + +- c:\msys64\usr\bin\bash -lc "make USE_UPNP=yes -j2" +- 7z a -tzip -mx9 -mmt i2pd-mingw-win%bitness%.zip i2pd.exe + +test: off + +artifacts: +- path: i2pd-mingw-win*.zip diff --git a/build/.gitignore b/build/.gitignore index 39b8094c..b595141b 100644 --- a/build/.gitignore +++ b/build/.gitignore @@ -1,27 +1,14 @@ # Various generated files /CMakeFiles/ -/Testing/ -/tests/ -/.ninja_* -/arch.c -/build.ninja /i2pd -/i2pd.exe -/i2pd.exe.debug /libi2pd.a /libi2pdclient.a -/libi2pdlang.a /cmake_install.cmake /CMakeCache.txt /CPackConfig.cmake /CPackSourceConfig.cmake -/CTestTestfile.cmake /install_manifest.txt -/Makefile +/arch.c # windows build script i2pd*.zip -build*.log -# MVS project files -*.vcxproj -*.vcxproj.filters -*.sln +build*.log \ No newline at end of file diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index bc936e18..bc80423a 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -1,318 +1,389 @@ -cmake_minimum_required(VERSION 3.7) - -if(${CMAKE_VERSION} VERSION_LESS 3.22) - cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) -else() - cmake_policy(VERSION 3.22) -endif() +cmake_minimum_required ( VERSION 2.8.12 ) +# this addresses CMP0059 with CMake > 3.3 for PCH flags +cmake_policy( VERSION 2.8.12 ) +project ( "i2pd" ) # for debugging #set(CMAKE_VERBOSE_MAKEFILE on) +# configurale options +option(WITH_AESNI "Use AES-NI instructions set" OFF) +option(WITH_AVX "Use AVX instructions" OFF) +option(WITH_HARDENING "Use hardening compiler flags" OFF) +option(WITH_LIBRARY "Build library" ON) +option(WITH_BINARY "Build binary" ON) +option(WITH_STATIC "Static build" OFF) +option(WITH_UPNP "Include support for UPnP client" OFF) +option(WITH_PCH "Use precompiled header" OFF) +option(WITH_GUI "Include GUI (currently MS Windows only)" ON) +option(WITH_MESHNET "Build for cjdns test network" OFF) +option(WITH_ADDRSANITIZER "Build with address sanitizer unix only" OFF) +option(WITH_THREADSANITIZER "Build with thread sanitizer unix only" OFF) +option(WITH_I2LUA "Build for i2lua" OFF) +option(WITH_WEBSOCKETS "Build with websocket ui" OFF) + # paths -set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules") -set(CMAKE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/..") +set ( CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules" ) +set ( CMAKE_SOURCE_DIR ".." ) -set(LIBI2PD_SRC_DIR ${CMAKE_SOURCE_DIR}/libi2pd) -set(LIBI2PD_CLIENT_SRC_DIR ${CMAKE_SOURCE_DIR}/libi2pd_client) -set(LANG_SRC_DIR ${CMAKE_SOURCE_DIR}/i18n) -set(DAEMON_SRC_DIR ${CMAKE_SOURCE_DIR}/daemon) - -include(Version) -set_version("${LIBI2PD_SRC_DIR}/version.h" PROJECT_VERSION) - -project( - i2pd - VERSION ${PROJECT_VERSION} - HOMEPAGE_URL "https://i2pd.website/" - LANGUAGES C CXX -) - -# configurable options -option(WITH_HARDENING "Use hardening compiler flags" OFF) -option(WITH_LIBRARY "Build library" ON) -option(WITH_BINARY "Build binary" ON) -option(WITH_STATIC "Static build" OFF) -option(WITH_UPNP "Include support for UPnP client" OFF) -option(WITH_GIT_VERSION "Use git commit info as version" OFF) -option(WITH_ADDRSANITIZER "Build with address sanitizer unix only" OFF) -option(WITH_THREADSANITIZER "Build with thread sanitizer unix only" OFF) -option(BUILD_TESTING "Build tests" OFF) - -IF(BUILD_TESTING) - enable_testing() -ENDIF() - -# Handle paths nicely -include(GNUInstallDirs) - -# Architecture +# architecture include(TargetArch) target_architecture(ARCHITECTURE) -include(CheckAtomic) - -if(WITH_STATIC) - if(MSVC) - set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") - endif() -endif() +set(LIBI2PD_SRC_DIR ../libi2pd) +set(LIBI2PD_CLIENT_SRC_DIR ../libi2pd_client) include_directories(${LIBI2PD_SRC_DIR}) -FILE(GLOB LIBI2PD_SRC ${LIBI2PD_SRC_DIR}/*.cpp) +include_directories(${LIBI2PD_CLIENT_SRC_DIR}) + +set (LIBI2PD_SRC + "${LIBI2PD_SRC_DIR}/BloomFilter.cpp" + "${LIBI2PD_SRC_DIR}/Config.cpp" + "${LIBI2PD_SRC_DIR}/Crypto.cpp" + "${LIBI2PD_SRC_DIR}/CryptoKey.cpp" + "${LIBI2PD_SRC_DIR}/Garlic.cpp" + "${LIBI2PD_SRC_DIR}/Gzip.cpp" + "${LIBI2PD_SRC_DIR}/HTTP.cpp" + "${LIBI2PD_SRC_DIR}/I2NPProtocol.cpp" + "${LIBI2PD_SRC_DIR}/Identity.cpp" + "${LIBI2PD_SRC_DIR}/LeaseSet.cpp" + "${LIBI2PD_SRC_DIR}/FS.cpp" + "${LIBI2PD_SRC_DIR}/Log.cpp" + "${LIBI2PD_SRC_DIR}/NTCPSession.cpp" + "${LIBI2PD_SRC_DIR}/NetDbRequests.cpp" + "${LIBI2PD_SRC_DIR}/NetDb.cpp" + "${LIBI2PD_SRC_DIR}/Profiling.cpp" + "${LIBI2PD_SRC_DIR}/Reseed.cpp" + "${LIBI2PD_SRC_DIR}/RouterContext.cpp" + "${LIBI2PD_SRC_DIR}/RouterInfo.cpp" + "${LIBI2PD_SRC_DIR}/SSU.cpp" + "${LIBI2PD_SRC_DIR}/SSUData.cpp" + "${LIBI2PD_SRC_DIR}/SSUSession.cpp" + "${LIBI2PD_SRC_DIR}/Streaming.cpp" + "${LIBI2PD_SRC_DIR}/Destination.cpp" + "${LIBI2PD_SRC_DIR}/TransitTunnel.cpp" + "${LIBI2PD_SRC_DIR}/Tunnel.cpp" + "${LIBI2PD_SRC_DIR}/TunnelGateway.cpp" + "${LIBI2PD_SRC_DIR}/Transports.cpp" + "${LIBI2PD_SRC_DIR}/TunnelEndpoint.cpp" + "${LIBI2PD_SRC_DIR}/TunnelPool.cpp" + "${LIBI2PD_SRC_DIR}/Base.cpp" + "${LIBI2PD_SRC_DIR}/util.cpp" + "${LIBI2PD_SRC_DIR}/Datagram.cpp" + "${LIBI2PD_SRC_DIR}/Family.cpp" + "${LIBI2PD_SRC_DIR}/Signature.cpp" + "${LIBI2PD_SRC_DIR}/Timestamp.cpp" + "${LIBI2PD_SRC_DIR}/api.cpp" + "${LIBI2PD_SRC_DIR}/Event.cpp" + "${LIBI2PD_SRC_DIR}/Gost.cpp" +) + +if (WITH_WEBSOCKETS) + add_definitions(-DWITH_EVENTS) + find_package(websocketpp REQUIRED) +endif () + +if (WIN32 OR MSYS) + list (APPEND LIBI2PD_SRC "${CMAKE_SOURCE_DIR}/I2PEndian.cpp") +endif () + +if (WITH_I2LUA) + add_definitions(-DI2LUA) +endif() + add_library(libi2pd ${LIBI2PD_SRC}) set_target_properties(libi2pd PROPERTIES PREFIX "") +install(TARGETS libi2pd + EXPORT libi2pd + ARCHIVE DESTINATION lib + COMPONENT Libraries) +# TODO Make libi2pd available to 3rd party projects via CMake as imported target +# FIXME This pulls stdafx +# install(EXPORT libi2pd DESTINATION ${CMAKE_INSTALL_LIBDIR}) -if(WITH_LIBRARY) - install(TARGETS libi2pd - EXPORT libi2pd - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - COMPONENT Libraries) -endif() +set (CLIENT_SRC + "${LIBI2PD_CLIENT_SRC_DIR}/AddressBook.cpp" + "${LIBI2PD_CLIENT_SRC_DIR}/BOB.cpp" + "${LIBI2PD_CLIENT_SRC_DIR}/ClientContext.cpp" + "${LIBI2PD_CLIENT_SRC_DIR}/MatchedDestination.cpp" + "${LIBI2PD_CLIENT_SRC_DIR}/I2PTunnel.cpp" + "${LIBI2PD_CLIENT_SRC_DIR}/I2PService.cpp" + "${LIBI2PD_CLIENT_SRC_DIR}/SAM.cpp" + "${LIBI2PD_CLIENT_SRC_DIR}/SOCKS.cpp" + "${LIBI2PD_CLIENT_SRC_DIR}/HTTPProxy.cpp" + "${LIBI2PD_CLIENT_SRC_DIR}/I2CP.cpp" + "${LIBI2PD_CLIENT_SRC_DIR}/WebSocks.cpp" +) -include_directories(${LIBI2PD_CLIENT_SRC_DIR}) -FILE(GLOB CLIENT_SRC ${LIBI2PD_CLIENT_SRC_DIR}/*.cpp) -add_library(libi2pdclient ${CLIENT_SRC}) -set_target_properties(libi2pdclient PROPERTIES PREFIX "") +if(WITH_WEBSOCKETS) + list (APPEND CLIENT_SRC "${LIBI2PD_CLIENT_SRC_DIR}/Websocket.cpp") +endif () +add_library(i2pdclient ${CLIENT_SRC}) -if(WITH_LIBRARY) - install(TARGETS libi2pdclient - EXPORT libi2pdclient - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - COMPONENT Libraries) -endif() +set(DAEMON_SRC_DIR ../daemon) -include_directories(${LANG_SRC_DIR}) -FILE(GLOB LANG_SRC ${LANG_SRC_DIR}/*.cpp) -add_library(libi2pdlang ${LANG_SRC}) -set_target_properties(libi2pdlang PROPERTIES PREFIX "") - -if(WITH_LIBRARY) - install(TARGETS libi2pdlang - EXPORT libi2pdlang - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - COMPONENT Libraries) -endif() - -include_directories(${DAEMON_SRC_DIR}) - -set(DAEMON_SRC +set (DAEMON_SRC "${DAEMON_SRC_DIR}/Daemon.cpp" "${DAEMON_SRC_DIR}/HTTPServer.cpp" "${DAEMON_SRC_DIR}/I2PControl.cpp" - "${DAEMON_SRC_DIR}/I2PControlHandlers.cpp" "${DAEMON_SRC_DIR}/i2pd.cpp" "${DAEMON_SRC_DIR}/UPnP.cpp" ) -if(WIN32) - set(WIN32_SRC_DIR ${CMAKE_SOURCE_DIR}/Win32) - include_directories(${WIN32_SRC_DIR}) +if (WITH_MESHNET) + add_definitions(-DMESHNET) +endif () - list(APPEND DAEMON_SRC - "${WIN32_SRC_DIR}/DaemonWin32.cpp" - "${WIN32_SRC_DIR}/Win32App.cpp" - "${WIN32_SRC_DIR}/Win32Service.cpp" - "${WIN32_SRC_DIR}/Win32NetState.cpp" - ) - - file(GLOB WIN32_RC ${WIN32_SRC_DIR}/*.rc) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DWIN32_APP -DWIN32_LEAN_AND_MEAN -DNOMINMAX") - -endif() - -if(WITH_UPNP) +if (WITH_UPNP) add_definitions(-DUSE_UPNP) -endif() + if (NOT MSVC AND NOT MSYS) + set(DL_LIB ${CMAKE_DL_LIBS}) + endif () +endif () -if(WITH_GIT_VERSION) - include(GetGitRevisionDescription) - git_describe(GIT_VERSION) - add_definitions(-DGITVER=${GIT_VERSION}) -endif() - -if(APPLE) - add_definitions(-DMAC_OSX) -endif() - -if(HAIKU) - add_definitions(-D_DEFAULT_SOURCE -D_GNU_SOURCE) -endif() - -if(MSVC) - add_definitions(-DWINVER=0x0600) - add_definitions(-D_WIN32_WINNT=0x0600) +# compiler flags customization (by vendor) +if (MSVC) + add_definitions( -DWIN32_LEAN_AND_MEAN -DNOMINMAX ) + # TODO Check & report to Boost dev, there should be no need for these two + add_definitions( -DBOOST_THREAD_NO_LIB -DBOOST_CHRONO_NO_LIB ) + set( CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /GL" ) + set( CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /INCREMENTAL:NO /LTCG" ) + set( CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELEASE} /GL" ) + set( CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO} /INCREMENTAL:NO /LTCG" ) else() - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Winvalid-pch -Wno-unused-parameter -Wno-uninitialized") - set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -pedantic") + if (MSYS OR MINGW) + add_definitions( -DWIN32_LEAN_AND_MEAN ) + endif () + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Winvalid-pch -Wno-unused-parameter" ) + set( CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -pedantic" ) # TODO: The following is incompatible with static build and enabled hardening for OpenWRT. - # Multiple definitions of __stack_chk_fail(libssp & libc) - if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang") - set(CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS_MINSIZEREL} -flto -s") - endif() - set(CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS_MINSIZEREL} -ffunction-sections -fdata-sections") - set(CMAKE_EXE_LINKER_FLAGS_MINSIZEREL "-Wl,--gc-sections") # -flto is added from above -endif() + # Multiple definitions of __stack_chk_fail (libssp & libc) + set( CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS_MINSIZEREL} -flto -s -ffunction-sections -fdata-sections" ) + set( CMAKE_EXE_LINKER_FLAGS_MINSIZEREL "-Wl,--gc-sections" ) # -flto is added from above +endif () -if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pipe") - if(WITH_HARDENING) - add_definitions("-D_FORTIFY_SOURCE=2") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wformat -Wformat-security -Werror=format-security") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fstack-protector --param ssp-buffer-size=4") - endif() -elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") +# check for c++11 support +include(CheckCXXCompilerFlag) +CHECK_CXX_COMPILER_FLAG("-std=c++11" CXX11_SUPPORTED) +CHECK_CXX_COMPILER_FLAG("-std=c++0x" CXX0X_SUPPORTED) +if (CXX11_SUPPORTED) + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11" ) +elseif (CXX0X_SUPPORTED) # gcc 4.6 + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x" ) +elseif (NOT MSVC) + message(SEND_ERROR "C++11 standart not seems to be supported by compiler. Too old version?") +endif () + +if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pipe") + if (WITH_HARDENING) + add_definitions( "-D_FORTIFY_SOURCE=2" ) + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wformat -Wformat-security -Werror=format-security" ) + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fstack-protector --param ssp-buffer-size=4" ) + endif () +elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") # more tweaks - if(LINUX) - set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -stdlib=libstdc++") # required for - list(APPEND CMAKE_REQUIRED_LIBRARIES "stdc++") # required to link with -stdlib=libstdc++ + if (NOT (MSVC OR MSYS OR APPLE)) + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-const-variable -Wno-overloaded-virtual -Wno-c99-extensions" ) endif() - if(NOT APPLE) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-const-variable -Wno-overloaded-virtual -Wno-c99-extensions") - endif() -endif() +endif () -# compiler flags customization(by system) -if(UNIX) - list(APPEND DAEMON_SRC "${DAEMON_SRC_DIR}/UnixDaemon.cpp") - if(NOT(CMAKE_SYSTEM_NAME STREQUAL "OpenBSD" OR APPLE)) +if (WITH_HARDENING AND MSVC) + # Most security options like dynamic base, buffer & stack checks are ON by default + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /guard:cf" ) +endif () + +# compiler flags customization (by system) +if (UNIX) + list (APPEND DAEMON_SRC "${DAEMON_SRC_DIR}/UnixDaemon.cpp") + if (NOT (CMAKE_SYSTEM_NAME STREQUAL "OpenBSD" OR APPLE)) # "'sleep_for' is not a member of 'std::this_thread'" in gcc 4.7/4.8 - add_definitions("-D_GLIBCXX_USE_NANOSLEEP=1") + add_definitions( "-D_GLIBCXX_USE_NANOSLEEP=1" ) + endif () +elseif (WIN32 OR MSYS) + list (APPEND DAEMON_SRC "${CMAKE_SOURCE_DIR}/Win32/DaemonWin32.cpp") + if (WITH_GUI) + list (APPEND DAEMON_SRC "${CMAKE_SOURCE_DIR}/Win32/Win32App.cpp") + set_source_files_properties("${CMAKE_SOURCE_DIR}/Win32/DaemonWin32.cpp" + PROPERTIES COMPILE_DEFINITIONS WIN32_APP) + endif () + list (APPEND DAEMON_SRC "${CMAKE_SOURCE_DIR}/Win32/Win32Service.cpp") + list (APPEND DAEMON_SRC "${CMAKE_SOURCE_DIR}/Win32/Resource.rc") +endif () + +if (WITH_AESNI) + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -maes" ) + add_definitions ( -DAESNI ) +endif() + +if (WITH_AVX) + set ( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mavx" ) +endif() + +if (WITH_ADDRSANITIZER) + if (NOT MSVC) + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer" ) + set( CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address" ) + else () + message( SEND_ERROR "MSVC does not support address sanitizer option") endif() endif() -if(WITH_ADDRSANITIZER) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address") -endif() - -if(WITH_THREADSANITIZER) - if(WITH_ADDRSANITIZER) - message(FATAL_ERROR "thread sanitizer option cannot be combined with address sanitizer") - else() - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=thread") +if (WITH_THREADSANITIZER) + if (WITH_ADDRSANITIZER) + message( FATAL_ERROR "thread sanitizer option cannot be combined with address sanitizer") + elseif (NOT MSVC) + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread" ) + set( CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=thread" ) + else () + message( SEND_ERROR "MSVC does not support address sanitizer option") endif() endif() -if (CMAKE_COMPILER_IS_GNUCC AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 10.0 AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 8.0) # gcc 8-9 - list(APPEND CMAKE_REQUIRED_LIBRARIES "stdc++fs") -endif() - -# Use std::atomic instead of GCC builtins on macOS PowerPC: -# For more information refer to: https://github.com/PurpleI2P/i2pd/issues/1726#issuecomment-1306335111 -# This has been fixed in Boost 1.81, nevertheless we retain the setting for the sake of compatibility. -if(APPLE AND CMAKE_OSX_ARCHITECTURES MATCHES "ppc") - add_definitions(-DBOOST_SP_USE_STD_ATOMIC) -endif() # libraries +# TODO: once CMake 3.1+ becomes mainstream, see e.g. http://stackoverflow.com/a/29871891/673826 +# use imported Threads::Threads instead set(THREADS_PREFER_PTHREAD_FLAG ON) -find_package(Threads REQUIRED) - -if(WITH_STATIC) - if(NOT MSVC) - set(CMAKE_FIND_LIBRARY_SUFFIXES ".a") - endif() - - set(Boost_USE_STATIC_LIBS ON) - if(MSVC) - set(Boost_USE_STATIC_RUNTIME ON) - else() - set(Boost_USE_STATIC_RUNTIME OFF) - endif() - - if(MSVC) - set(OPENSSL_MSVC_STATIC_RT ON) - endif() - set(OPENSSL_USE_STATIC_LIBS ON) - - set(ZLIB_USE_STATIC_LIBS ON) - if(MSVC) - set(ZLIB_NAMES zlibstatic zlibstat) - else() - set(ZLIB_NAMES libz zlibstatic zlibstat zlib z) - endif() - - if(WITH_UPNP) - set(MINIUPNPC_USE_STATIC_LIBS ON) - add_definitions(-DMINIUPNP_STATICLIB) - endif() - - set(BUILD_SHARED_LIBS OFF) - - if(${CMAKE_CXX_COMPILER} MATCHES ".*-openwrt-.*") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread") - # set(CMAKE_THREAD_LIBS_INIT "gcc_eh -Wl,--whole-archive -lpthread -Wl,--no-whole-archive") - set(CMAKE_THREAD_LIBS_INIT "gcc_eh -Wl,-u,pthread_create,-u,pthread_once,-u,pthread_mutex_lock,-u,pthread_mutex_unlock,-u,pthread_join,-u,pthread_equal,-u,pthread_detach,-u,pthread_cond_wait,-u,pthread_cond_signal,-u,pthread_cond_destroy,-u,pthread_cond_broadcast,-u,pthread_cancel") - endif() +if (IOS) + set(CMAKE_THREAD_LIBS_INIT "-lpthread") + set(CMAKE_HAVE_THREADS_LIBRARY 1) + set(CMAKE_USE_WIN32_THREADS_INIT 0) + set(CMAKE_USE_PTHREADS_INIT 1) else() - # TODO: Consider separate compilation for LIBI2PD_SRC for library. - # No need in -fPIC overhead for binary if not interested in library - # HINT: revert c266cff CMakeLists.txt: compilation speed up - if(NOT MSVC) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC") - endif() - add_definitions(-DBOOST_ATOMIC_DYN_LINK -DBOOST_SYSTEM_DYN_LINK -DBOOST_FILESYSTEM_DYN_LINK -DBOOST_PROGRAM_OPTIONS_DYN_LINK) - if(WIN32) - set(Boost_USE_STATIC_LIBS OFF) - set(Boost_USE_STATIC_RUNTIME OFF) - endif() + find_package ( Threads REQUIRED ) +endif() +if(THREADS_HAVE_PTHREAD_ARG) # compile time flag + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread") endif() -find_package(Boost REQUIRED COMPONENTS system filesystem program_options) -if(NOT DEFINED Boost_FOUND) - message(SEND_ERROR "Boost is not found, or your boost version was below 1.46. Please download Boost!") +if (WITH_STATIC) + set(Boost_USE_STATIC_LIBS ON) + set(Boost_USE_STATIC_RUNTIME ON) + if (WIN32 AND NOT MSYS AND NOT MINGW) + # http://www.cmake.org/Wiki/CMake_FAQ#Dynamic_Replace + foreach(flag_var + CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE + CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO) + if(${flag_var} MATCHES "/MD") + string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}") + endif(${flag_var} MATCHES "/MD") + endforeach(flag_var) + else () + set(CMAKE_FIND_LIBRARY_SUFFIXES .a) + endif () + set(BUILD_SHARED_LIBS OFF) + if (${CMAKE_CXX_COMPILER} MATCHES ".*-openwrt-.*") + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread" ) + # set( CMAKE_THREAD_LIBS_INIT "gcc_eh -Wl,--whole-archive -lpthread -Wl,--no-whole-archive" ) + set( CMAKE_THREAD_LIBS_INIT "gcc_eh -Wl,-u,pthread_create,-u,pthread_once,-u,pthread_mutex_lock,-u,pthread_mutex_unlock,-u,pthread_join,-u,pthread_equal,-u,pthread_detach,-u,pthread_cond_wait,-u,pthread_cond_signal,-u,pthread_cond_destroy,-u,pthread_cond_broadcast,-u,pthread_cancel" ) + endif () +else() + if (NOT WIN32 AND NOT MSYS) + # TODO: Consider separate compilation for LIBI2PD_SRC for library. + # No need in -fPIC overhead for binary if not interested in library + # HINT: revert c266cff CMakeLists.txt: compilation speed up + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC" ) + endif () + add_definitions(-DBOOST_SYSTEM_DYN_LINK -DBOOST_FILESYSTEM_DYN_LINK -DBOOST_PROGRAM_OPTIONS_DYN_LINK -DBOOST_DATE_TIME_DYN_LINK -DBOOST_REGEX_DYN_LINK) +endif () + +if (WITH_PCH) + include_directories(BEFORE ${CMAKE_BINARY_DIR}) + add_library(stdafx STATIC "${LIBI2PD_SRC_DIR}/stdafx.cpp") + if(MSVC) + target_compile_options(stdafx PRIVATE /Ycstdafx.h /Zm155) + add_custom_command(TARGET stdafx POST_BUILD + COMMAND xcopy /y stdafx.dir\\$\\*.pdb libi2pd.dir\\$\\ + COMMAND xcopy /y stdafx.dir\\$\\*.pdb i2pdclient.dir\\$\\ + COMMAND xcopy /y stdafx.dir\\$\\*.pdb i2pd.dir\\$\\ + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + ) + target_compile_options(libi2pd PRIVATE /FIstdafx.h /Yustdafx.h /Zm155 "/Fp${CMAKE_BINARY_DIR}/stdafx.dir/$/stdafx.pch") + target_compile_options(i2pdclient PRIVATE /FIstdafx.h /Yustdafx.h /Zm155 "/Fp${CMAKE_BINARY_DIR}/stdafx.dir/$/stdafx.pch") + else() + string(TOUPPER ${CMAKE_BUILD_TYPE} BTU) + get_directory_property(DEFS DEFINITIONS) + string(REPLACE " " ";" FLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_${BTU}} ${DEFS}") + add_custom_command(TARGET stdafx PRE_BUILD + COMMAND ${CMAKE_CXX_COMPILER} ${FLAGS} -c ${CMAKE_CURRENT_SOURCE_DIR}/../libi2pd/stdafx.h -o ${CMAKE_BINARY_DIR}/stdafx.h.gch + ) + target_compile_options(libi2pd PRIVATE -include libi2pd/stdafx.h) + target_compile_options(i2pdclient PRIVATE -include libi2pd/stdafx.h) + endif() + target_link_libraries(libi2pd stdafx) endif() -find_package(OpenSSL REQUIRED) -if(NOT DEFINED OPENSSL_FOUND) +target_link_libraries(i2pdclient libi2pd) + +find_package ( Boost COMPONENTS system filesystem program_options date_time REQUIRED ) +if(NOT DEFINED Boost_INCLUDE_DIRS) + message(SEND_ERROR "Boost is not found, or your boost version was bellow 1.46. Please download Boost!") +endif() + +find_package ( OpenSSL REQUIRED ) +if(NOT DEFINED OPENSSL_INCLUDE_DIR) message(SEND_ERROR "Could not find OpenSSL. Please download and install it first!") -endif() - -if(OPENSSL_VERSION VERSION_GREATER_EQUAL "3.0.0") - add_definitions(-DOPENSSL_SUPPRESS_DEPRECATED) -endif() - -if(WITH_UPNP) - find_package(MiniUPnPc REQUIRED) - if(NOT MINIUPNPC_FOUND) - message(SEND_ERROR "Could not find MiniUPnPc. Please download and install it first!") - else() - include_directories(SYSTEM ${MINIUPNPC_INCLUDE_DIR}) +else() + if(NOT (OPENSSL_VERSION VERSION_LESS 1.1)) + message(WARNING "Your OpenSSL version ${OPENSSL_VERSION} >=1.1 is experimental: build with v1.0 when possible.") endif() endif() -find_package(ZLIB) -if(ZLIB_FOUND) +if (WITH_UPNP) + find_package ( MiniUPnPc REQUIRED ) + include_directories( SYSTEM ${MINIUPNPC_INCLUDE_DIR} ) +endif() + +find_package ( ZLIB ) +if (NOT ZLIB_FOUND ) + # We are probably on Windows + find_program( PATCH patch C:/Program Files/Git/usr/bin C:/msys64/usr/bin C:/msys32/usr/bin C:/Strawberry/c/bin ) + include( ExternalProject ) + if( CMAKE_SIZEOF_VOID_P EQUAL 8 ) + set( ZLIB_EXTRA -DAMD64=ON ) + else() + set( ZLIB_EXTRA -DASM686=ON "-DCMAKE_ASM_MASM_FLAGS=/W0 /safeseh" ) + endif() + ExternalProject_Add(zlib-project + URL http://zlib.net/zlib-1.2.8.tar.gz + URL_MD5 44d667c142d7cda120332623eab69f40 + PREFIX ${CMAKE_CURRENT_BINARY_DIR}/zlib + PATCH_COMMAND "${PATCH}" -p0 < ${CMAKE_CURRENT_SOURCE_DIR}/cmake-zlib-static.patch + && "${PATCH}" -p0 < ${CMAKE_CURRENT_SOURCE_DIR}/cmake-zlib-amd64.patch + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH= + -DWITH_STATIC=${WITH_STATIC} ${ZLIB_EXTRA} + ) + if (WITH_PCH) + add_dependencies( stdafx zlib-project ) + else () + add_dependencies( libi2pd zlib-project ) + endif () + # ExternalProject_Get_Property(zlib-project install_dir) + set ( ZLIB_INCLUDE_DIR "${CMAKE_CURRENT_BINARY_DIR}/zlib/include" CACHE FILEPATH "zlib include dir" FORCE) + if (NOT WITH_STATIC) + set ( ZLIB_LIBRARY debug zlibd optimized zlib CACHE STRING "zlib libraries" FORCE) + endif () + link_directories(${CMAKE_CURRENT_BINARY_DIR}/zlib/lib) +else() link_directories(${ZLIB_ROOT}/lib) -endif() - -# C++ standard to use, based on compiler and version of boost -if(NOT MSVC) -# check for c++20 & c++17 support - include(CheckCXXCompilerFlag) - - if(Boost_VERSION VERSION_GREATER_EQUAL "1.83") # min boost version for c++20 - CHECK_CXX_COMPILER_FLAG("-std=c++20" CXX20_SUPPORTED) - endif() - CHECK_CXX_COMPILER_FLAG("-std=c++17" CXX17_SUPPORTED) - - - if(CXX20_SUPPORTED) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++20") - elseif(CXX17_SUPPORTED) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17") - else() - message(SEND_ERROR "C++20 nor C++17 standard not seems to be supported by compiler. Too old version?") - endif() -endif() +endif () +if (WITH_STATIC AND (MSVC OR MSYS)) + set ( ZLIB_LIBRARY debug zlibstaticd optimized zlibstatic CACHE STRING "zlib libraries" FORCE) +endif () # load includes -include_directories(SYSTEM ${Boost_INCLUDE_DIRS} ${OPENSSL_INCLUDE_DIR} ${ZLIB_INCLUDE_DIR}) +include_directories( SYSTEM ${Boost_INCLUDE_DIRS} ${OPENSSL_INCLUDE_DIR} ${ZLIB_INCLUDE_DIR} ) + + +# warn if for meshnet +if (WITH_MESHNET) + message(STATUS "Building for testnet") + message(WARNING "This build will NOT work on mainline i2p") +endif() + +include(CheckAtomic) + # show summary message(STATUS "---------------------------------------") @@ -321,47 +392,52 @@ message(STATUS "Compiler vendor : ${CMAKE_CXX_COMPILER_ID}") message(STATUS "Compiler version : ${CMAKE_CXX_COMPILER_VERSION}") message(STATUS "Compiler path : ${CMAKE_CXX_COMPILER}") message(STATUS "Architecture : ${ARCHITECTURE}") -message(STATUS "Compiler flags : ${CMAKE_CXX_FLAGS}") message(STATUS "Install prefix: : ${CMAKE_INSTALL_PREFIX}") message(STATUS "Options:") +message(STATUS " AESNI : ${WITH_AESNI}") +message(STATUS " AVX : ${WITH_AVX}") message(STATUS " HARDENING : ${WITH_HARDENING}") message(STATUS " LIBRARY : ${WITH_LIBRARY}") message(STATUS " BINARY : ${WITH_BINARY}") message(STATUS " STATIC BUILD : ${WITH_STATIC}") message(STATUS " UPnP : ${WITH_UPNP}") -if(WITH_GIT_VERSION) -message(STATUS " GIT VERSION : ${WITH_GIT_VERSION} (${GIT_VERSION})") -else() -message(STATUS " GIT VERSION : ${WITH_GIT_VERSION}") -endif() +message(STATUS " PCH : ${WITH_PCH}") +message(STATUS " MESHNET : ${WITH_MESHNET}") message(STATUS " ADDRSANITIZER : ${WITH_ADDRSANITIZER}") message(STATUS " THREADSANITIZER : ${WITH_THREADSANITIZER}") +message(STATUS " I2LUA : ${WITH_I2LUA}") +message(STATUS " WEBSOCKETS : ${WITH_WEBSOCKETS}") message(STATUS "---------------------------------------") -if(WITH_BINARY) - if(WIN32) - add_executable("${PROJECT_NAME}" WIN32 ${DAEMON_SRC} ${WIN32_RC}) - else() - add_executable("${PROJECT_NAME}" ${DAEMON_SRC}) +#Handle paths nicely +include(GNUInstallDirs) + +if (WITH_BINARY) + add_executable ( "${PROJECT_NAME}" ${DAEMON_SRC} ) + if (WIN32 AND WITH_GUI) + set_target_properties("${PROJECT_NAME}" PROPERTIES WIN32_EXECUTABLE TRUE ) + endif() + if(NOT MSVC) + if (WITH_STATIC) + set_target_properties("${PROJECT_NAME}" PROPERTIES LINK_FLAGS "-static" ) + endif () endif() - if(WIN32) - list(APPEND MINGW_EXTRA "wsock32" "ws2_32" "iphlpapi") - # OpenSSL may require Crypt32 library on MSVC build, which is not added by CMake lesser than 3.21 - if(MSVC AND ${CMAKE_VERSION} VERSION_LESS 3.21) - list(APPEND MINGW_EXTRA "crypt32") + if (WITH_PCH) + if (MSVC) + target_compile_options("${PROJECT_NAME}" PRIVATE /FIstdafx.h /Yustdafx.h /Zm155 "/Fp${CMAKE_BINARY_DIR}/stdafx.dir/$/stdafx.pch") + else() + target_compile_options("${PROJECT_NAME}" PRIVATE -include libi2pd/stdafx.h) endif() endif() - if(WITH_STATIC) - if(NOT MSVC) - set_target_properties("${PROJECT_NAME}" PROPERTIES LINK_FLAGS "-static") - endif() - endif() + if (WITH_HARDENING AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND NOT MSYS AND NOT MINGW) + set_target_properties("${PROJECT_NAME}" PROPERTIES LINK_FLAGS "-z relro -z now" ) + endif () - if(WITH_HARDENING AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - set_target_properties("${PROJECT_NAME}" PROPERTIES LINK_FLAGS "-z relro -z now") - endif() + if (WITH_UPNP) + target_link_libraries("${PROJECT_NAME}" "${MINIUPNPC_LIBRARY}") + endif () # FindBoost pulls pthread for thread which is broken for static linking at least on Ubuntu 15.04 list(GET Boost_LIBRARIES -1 LAST_Boost_LIBRARIES) @@ -369,24 +445,127 @@ if(WITH_BINARY) list(REMOVE_AT Boost_LIBRARIES -1) endif() - # synchronization library is incompatible with Windows 7 - if(WIN32) - get_target_property(BOOSTFSLIBS Boost::filesystem INTERFACE_LINK_LIBRARIES) - list(REMOVE_ITEM BOOSTFSLIBS synchronization) - set_target_properties(Boost::filesystem PROPERTIES INTERFACE_LINK_LIBRARIES "${BOOSTFSLIBS}") - endif() - - if(WITH_STATIC) + if (MSYS OR MINGW) + set (MINGW_EXTRA -lws2_32 -lmswsock -liphlpapi ) + endif () + if (WITH_STATIC) set(DL_LIB ${CMAKE_DL_LIBS}) endif() - - target_link_libraries("${PROJECT_NAME}" libi2pd libi2pdclient libi2pdlang ${Boost_LIBRARIES} OpenSSL::SSL OpenSSL::Crypto ${MINIUPNPC_LIBRARY} ZLIB::ZLIB Threads::Threads ${MINGW_EXTRA} ${DL_LIB} ${CMAKE_REQUIRED_LIBRARIES}) + target_link_libraries( "${PROJECT_NAME}" libi2pd i2pdclient ${DL_LIB} ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} ${MINGW_EXTRA} ${DL_LIB} ${CMAKE_REQUIRED_LIBRARIES}) install(TARGETS "${PROJECT_NAME}" RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT Runtime) - set(APPS "\${CMAKE_INSTALL_PREFIX}/bin/${PROJECT_NAME}${CMAKE_EXECUTABLE_SUFFIX}") - set(DIRS "${Boost_LIBRARY_DIR};${OPENSSL_INCLUDE_DIR}/../bin;${ZLIB_INCLUDE_DIR}/../bin;/mingw32/bin") -endif() + set (APPS "\${CMAKE_INSTALL_PREFIX}/bin/${PROJECT_NAME}${CMAKE_EXECUTABLE_SUFFIX}") + set (DIRS "${Boost_LIBRARY_DIR};${OPENSSL_INCLUDE_DIR}/../bin;${ZLIB_INCLUDE_DIR}/../bin;/mingw32/bin") + if (MSVC) + install(FILES $ DESTINATION ${CMAKE_INSTALL_BINDIR} CONFIGURATIONS DEBUG RELWITHDEBINFO COMPONENT Symbols) + # TODO Somehow this picks lots of unrelevant stuff with MSYS. OS X testing needed. + INSTALL(CODE " + include(BundleUtilities) + fixup_bundle(\"${APPS}\" \"\" \"${DIRS}\") + " COMPONENT Runtime) + endif () +endif () -if(BUILD_TESTING) - add_subdirectory(${CMAKE_SOURCE_DIR}/tests ${CMAKE_CURRENT_BINARY_DIR}/tests) +install(FILES ../LICENSE + DESTINATION . + COMPONENT Runtime + ) +# Take a copy on Appveyor +install(FILES "C:/projects/openssl-$ENV{OPENSSL}/LICENSE" + DESTINATION . + COMPONENT Runtime + RENAME LICENSE_OPENSSL + OPTIONAL # for local builds only! + ) + +file(GLOB_RECURSE I2PD_SOURCES "../libi2pd/*.cpp" "../libi2pd_client/*.cpp" "../daemon/*.cpp" "../build" "../Win32" "../Makefile*") +install(FILES ${I2PD_SOURCES} DESTINATION src/ COMPONENT Source) +# install(DIRECTORY ../ DESTINATION src/ +# # OPTIONAL +# COMPONENT Source FILES_MATCHING +# PATTERN .git EXCLUDE +# PATTERN "*.cpp" +# ) + +file(GLOB I2PD_HEADERS "../libi2pd/*.h" "../libi2pd_client/*.h" "../daemon/*.h") +install(FILES ${I2PD_HEADERS} DESTINATION src/ COMPONENT Headers) +# install(DIRECTORY ../ DESTINATION src/ +# # OPTIONAL +# COMPONENT Headers FILES_MATCHING +# PATTERN .git EXCLUDE +# PATTERN "*.h" +# ) + +set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Purple I2P, a C++ I2P daemon") +set(CPACK_PACKAGE_VENDOR "Purple I2P") +set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/../README.md") +set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/../LICENSE") +file(READ ../libi2pd/version.h version_h) +string(REGEX REPLACE ".*I2PD_VERSION_MAJOR ([0-9]+).*" "\\1" CPACK_PACKAGE_VERSION_MAJOR "${version_h}") +string(REGEX REPLACE ".*I2PD_VERSION_MINOR ([0-9]+).*" "\\1" CPACK_PACKAGE_VERSION_MINOR "${version_h}") +string(REGEX REPLACE ".*I2PD_VERSION_MICRO ([0-9]+).*" "\\1" CPACK_PACKAGE_VERSION_MICRO "${version_h}") +string(REGEX REPLACE ".*I2PD_VERSION_PATCH ([0-9]+).*" "\\1" CPACK_PACKAGE_VERSION_PATCH "${version_h}") +set(CPACK_PACKAGE_INSTALL_DIRECTORY "Purple I2P")# ${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}") +include(CPackComponent) +cpack_add_component(Runtime + DESCRIPTION "Main files" + REQUIRED INSTALL_TYPES minimal) +cpack_add_component(Symbols + DISPLAY_NAME "Debug symbols" + DESCRIPTION "Debug symbols for use with WinDbg or Visual Studio" + INSTALL_TYPES recommended full + ) +cpack_add_component(Libraries + DESCRIPTION "Binary libraries for development" + INSTALL_TYPES full dev3rd + ) +cpack_add_component(Source + DISPLAY_NAME "Source code" + DESCRIPTION "I2pd source code" + INSTALL_TYPES full + ) +cpack_add_component(Headers + DISPLAY_NAME "Header files" + DESCRIPTION "I2pd header files for development" + INSTALL_TYPES full dev3rd + ) +install(FILES ${MINIUPNPC_INCLUDE_DIR}/miniupnpc/miniupnpc.dll + DESTINATION bin + COMPONENT MiniUPnPc + OPTIONAL + ) +install(FILES ${MINIUPNPC_INCLUDE_DIR}/miniupnpc/LICENSE + DESTINATION . + COMPONENT MiniUPnPc + RENAME LICENSE_MINIUPNPC + OPTIONAL + ) +cpack_add_component(MiniUPnPc + INSTALL_TYPES full recommended + # DOWNLOADED + # ARCHIVE_FILE miniupnpc-win32.zip + ) +cpack_add_install_type(recommended DISPLAY_NAME Recommended) +cpack_add_install_type(dev3rd DISPLAY_NAME "Third party development") +cpack_add_install_type(full DISPLAY_NAME Full) +cpack_add_install_type(minimal DISPLAY_NAME Minimal) +if((WIN32 OR MSYS) AND NOT UNIX) + # There is a bug in NSI that does not handle full unix paths properly. Make + # sure there is at least one set of four (4) backlasshes. + set(CPACK_NSIS_DEFINES "RequestExecutionLevel user") + set(CPACK_PACKAGE_ICON "${CMAKE_CURRENT_SOURCE_DIR}/../Win32\\\\mask.bmp") + set(CPACK_NSIS_INSTALLED_ICON_NAME "bin/i2pd.exe") + SET(CPACK_NSIS_DISPLAY_NAME "${CPACK_PACKAGE_DESCRIPTION_SUMMARY}") + set(CPACK_NSIS_HELP_LINK "https:\\\\\\\\github.com\\\\PurpleI2P\\\\i2pd\\\\issues") + set(CPACK_NSIS_URL_INFO_ABOUT "https:\\\\\\\\github.com\\\\PurpleI2P\\\\i2pd") + set(CPACK_NSIS_CREATE_ICONS_EXTRA "CreateShortCut '$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\Install i2pd as windows service.lnk' '$INSTDIR\\\\bin\\\\i2pd.exe' '--service=install' +CreateShortCut '$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\Remove i2pd windows service.lnk' '$INSTDIR\\\\bin\\\\i2pd.exe' '--service=remove'") + set(CPACK_NSIS_DELETE_ICONS_EXTRA "Delete '$SMPROGRAMS\\\\$START_MENU\\\\Install i2pd as windows service.lnk' +Delete '$SMPROGRAMS\\\\$START_MENU\\\\Remove i2pd windows service.lnk'") +else() + set(CPACK_STRIP_FILES "bin/i2pd") + set(CPACK_SOURCE_STRIP_FILES "") endif() +set(CPACK_PACKAGE_EXECUTABLES "i2pd" "C++ I2P daemon") +set(CPACK_SOURCE_GENERATOR "TGZ") +include(CPack) diff --git a/build/build_mingw.cmd b/build/build_mingw.cmd index e8bb2b84..cc6a15fa 100644 --- a/build/build_mingw.cmd +++ b/build/build_mingw.cmd @@ -2,105 +2,56 @@ setlocal enableextensions enabledelayedexpansion title Building i2pd -REM Copyright (c) 2013-2022, The PurpleI2P Project +REM Copyright (c) 2013-2017, The PurpleI2P Project REM This file is part of Purple i2pd project and licensed under BSD3 REM See full license text in LICENSE file at top of project tree REM To use that script, you must have installed in your MSYS installation these packages: REM Base: git make zip -REM UCRT64: mingw-w64-ucrt-x86_64-boost mingw-w64-ucrt-x86_64-openssl mingw-w64-ucrt-x86_64-gcc -REM MINGW32: mingw-w64-i686-boost mingw-w64-i686-openssl mingw-w64-i686-gcc +REM x86_64: mingw-w64-x86_64-boost mingw-w64-x86_64-openssl mingw-w64-x86_64-gcc +REM i686: mingw-w64-i686-boost mingw-w64-i686-openssl mingw-w64-i686-gcc REM setting up variables for MSYS -REM Note: if you installed MSYS64 to different path, edit WD variable (only C:\msys64 needed to edit) +REM Note: if you installed MSYS64 to different path, edit WD variable (only C:\msys64 needed to edit)! +set "WD=C:\msys64\usr\bin\" set MSYS2_PATH_TYPE=inherit set CHERE_INVOKING=enabled_from_arguments -set MSYSTEM=MINGW32 +set MSYSTEM=MSYS -set "WD=C:\msys64\usr\bin\" set "xSH=%WD%bash -lc" -set "FILELIST=i2pd.exe README.txt contrib/i2pd.conf contrib/tunnels.conf contrib/certificates contrib/tunnels.d contrib/webconsole" - -REM detecting number of processors -set /a threads=%NUMBER_OF_PROCESSORS% +REM detecting number of processors and subtract 1. +set /a threads=%NUMBER_OF_PROCESSORS%-1 REM we must work in root of repo cd .. REM deleting old log files -del /S build_*.log >> nul 2>&1 +del /S build_*.log >> nul echo Receiving latest commit and cleaning up... -%xSH% "git checkout contrib/* && git pull && make clean" > build\build.log 2>&1 +%xSH% "git pull && make clean" > build/build_git.log 2>&1 +echo. REM set to variable current commit hash -for /F "usebackq" %%a in (`%xSH% "git describe --tags"`) DO ( +FOR /F "usebackq" %%a IN (`%xSH% 'git describe --tags'`) DO ( set tag=%%a ) -REM set to variable latest released tag -for /F "usebackq" %%b in (`%xSH% "git describe --abbrev=0"`) DO ( - set reltag=%%b -) - -echo Preparing configuration files and README for packaging... - %xSH% "echo To use configs and certificates, move all files and certificates folder from contrib directory here. > README.txt" >> nul -REM converting configuration files to DOS format (make usable in Windows Notepad) -%xSH% "unix2dos contrib/i2pd.conf contrib/tunnels.conf contrib/tunnels.d/* contrib/webconsole/style.css" >> build\build.log 2>&1 - -REM Prepare binary signing command if signing key and password provided -if defined SIGN ( - echo Signing enabled - - for %%X in (signtool.exe) do (set xSIGNTOOL=%%~$PATH:X) - if not defined xSIGNTOOL ( - if not defined SIGNTOOL ( - echo Error: Can't find signtool. Please provide path to binary using SIGNTOOL variable. - exit /b 1 - ) else ( - set "xSIGNTOOL=%SIGNTOOL%" - ) - ) - - if defined SIGNKEY ( - set "xSIGNKEYOPTS=/f ^"%SIGNKEY%^"" - ) - - if defined SIGNPASS ( - set "xSIGNPASSOPTS=/p ^"%SIGNPASS%^"" - ) - - set "xSIGNOPTS=sign /tr http://timestamp.digicert.com /td sha256 /fd sha256 %xSIGNKEYOPTS% %xSIGNPASSOPTS%" -) - REM starting building set MSYSTEM=MINGW32 set bitness=32 call :BUILDING - -set MSYSTEM=UCRT64 -set bitness=64 -call :BUILDING - -REM build for Windows XP -if exist C:\msys64-xp\ ( call :BUILDING_XP ) - echo. -REM compile installer -echo Building installer... -C:\PROGRA~2\INNOSE~1\ISCC.exe /dI2Pd_TextVer="%tag%" /dI2Pd_Ver="%reltag%.0" build\win_installer.iss >> build\build.log 2>&1 +set MSYSTEM=MINGW64 +set bitness=64 +call :BUILDING +echo. -REM Sign binary -if defined xSIGNOPTS ( - "%xSIGNTOOL%" %xSIGNOPTS% build\setup_i2pd_v%tag%.exe -) - -%xSH% "git checkout contrib/*" >> build\build.log 2>&1 -del README.txt i2pd_x32.exe i2pd_x64.exe i2pd_xp.exe >> nul +del README.txt >> nul echo Build complete... pause @@ -108,42 +59,14 @@ exit /b 0 :BUILDING %xSH% "make clean" >> nul -echo Building i2pd %tag% for win%bitness%... -REM Build i2pd -%xSH% "make DEBUG=no USE_UPNP=yes -j%threads%" > build\build_win%bitness%_%tag%.log 2>&1 +echo Building i2pd %tag% for win%bitness%: +echo Build AVX+AESNI... +%xSH% "make USE_UPNP=yes USE_AVX=1 USE_AESNI=1 -j%threads% && zip -r9 build/i2pd_%tag%_win%bitness%_mingw_avx_aesni.zip i2pd.exe README.txt contrib/i2pd.conf contrib/tunnels.conf contrib/certificates && make clean" > build/build_win%bitness%_avx_aesni.log 2>&1 +echo Build AVX... +%xSH% "make USE_UPNP=yes USE_AVX=1 -j%threads% && zip -r9 build/i2pd_%tag%_win%bitness%_mingw_avx.zip i2pd.exe README.txt contrib/i2pd.conf contrib/tunnels.conf contrib/certificates && make clean" > build/build_win%bitness%_avx.log 2>&1 +echo Build AESNI... +%xSH% "make USE_UPNP=yes USE_AESNI=1 -j%threads% && zip -r9 build/i2pd_%tag%_win%bitness%_mingw_aesni.zip i2pd.exe README.txt contrib/i2pd.conf contrib/tunnels.conf contrib/certificates && make clean" > build/build_win%bitness%_aesni.log 2>&1 +echo Build without extensions... +%xSH% "make USE_UPNP=yes -j%threads% && zip -r9 build/i2pd_%tag%_win%bitness%_mingw.zip i2pd.exe README.txt contrib/i2pd.conf contrib/tunnels.conf contrib/certificates && make clean" > build/build_win%bitness%.log 2>&1 -REM Sign binary -if defined xSIGNOPTS ( - "%xSIGNTOOL%" %xSIGNOPTS% i2pd.exe -) - -REM Copy binary for installer and create distribution archive -%xSH% "cp i2pd.exe i2pd_x%bitness%.exe && zip -r9 build/i2pd_%tag%_win%bitness%_mingw.zip %FILELIST%" >> build\build_win%bitness%_%tag%.log 2>&1 - -REM Clean work directory -%xSH% "make clean" >> build\build_win%bitness%_%tag%.log 2>&1 -goto EOF - -:BUILDING_XP -set MSYSTEM=MINGW32 -set bitness=32 -set "WD=C:\msys64-xp\usr\bin\" -set "xSH=%WD%bash -lc" - -%xSH% "make clean" >> nul -echo Building i2pd %tag% for winxp... -%xSH% "make DEBUG=no USE_UPNP=yes USE_WINXP_FLAGS=yes -j%threads%" > build\build_winxp_%tag%.log 2>&1 - -REM Sign binary -if defined xSIGNOPTS ( - "%xSIGNTOOL%" %xSIGNOPTS% i2pd.exe -) - -REM Copy binary for installer and create distribution archive -%xSH% "cp i2pd.exe i2pd_xp.exe && zip -r9 build/i2pd_%tag%_winxp_mingw.zip %FILELIST%" >> build\build_winxp_%tag%.log 2>&1 - -REM Clean work directory -%xSH% "make clean" >> build\build_winxp_%tag%.log 2>&1 -goto EOF - -:EOF +:EOF \ No newline at end of file diff --git a/build/cmake-zlib-amd64.patch b/build/cmake-zlib-amd64.patch new file mode 100644 index 00000000..7ea1d9fe --- /dev/null +++ b/build/cmake-zlib-amd64.patch @@ -0,0 +1,10 @@ +--- CMakeLists.txt.orig 2015-12-07 14:19:36.447689600 -0600 ++++ CMakeLists.txt 2015-12-07 14:18:23.004419900 -0600 +@@ -165,6 +165,7 @@ + ENABLE_LANGUAGE(ASM_MASM) + set(ZLIB_ASMS + contrib/masmx64/gvmat64.asm ++ contrib/masmx64/inffas8664.c + contrib/masmx64/inffasx64.asm + ) + endif() diff --git a/build/cmake-zlib-static.patch b/build/cmake-zlib-static.patch new file mode 100644 index 00000000..68f1400e --- /dev/null +++ b/build/cmake-zlib-static.patch @@ -0,0 +1,28 @@ +--- CMakeLists.txt.orig 2013-04-28 17:57:10.000000000 -0500 ++++ CMakeLists.txt 2015-12-03 12:53:52.371087900 -0600 +@@ -7,6 +7,7 @@ + + option(ASM686 "Enable building i686 assembly implementation") + option(AMD64 "Enable building amd64 assembly implementation") ++option(WITH_STATIC "Static runtime on Windows" OFF) + + set(INSTALL_BIN_DIR "${CMAKE_INSTALL_PREFIX}/bin" CACHE PATH "Installation directory for executables") + set(INSTALL_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib" CACHE PATH "Installation directory for libraries") +@@ -66,6 +67,17 @@ + include_directories(${CMAKE_CURRENT_SOURCE_DIR}) + endif() + ++if(WITH_STATIC AND (MSVC OR MSYS)) ++ # http://www.cmake.org/Wiki/CMake_FAQ#Dynamic_Replace ++ foreach(flag_var ++ CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE ++ CMAKE_C_FLAGS_MINSIZEREL CMAKE_C_FLAGS_RELWITHDEBINFO) ++ if(${flag_var} MATCHES "/MD") ++ string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}") ++ endif(${flag_var} MATCHES "/MD") ++ endforeach(flag_var) ++endif() ++ + if(NOT CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR) + # If we're doing an out of source build and the user has a zconf.h + # in their source tree... diff --git a/build/cmake_modules/CheckAtomic.cmake b/build/cmake_modules/CheckAtomic.cmake index 4954e3e5..b8296a1c 100644 --- a/build/cmake_modules/CheckAtomic.cmake +++ b/build/cmake_modules/CheckAtomic.cmake @@ -1,23 +1,18 @@ # atomic builtins are required for threading support. INCLUDE(CheckCXXSourceCompiles) -INCLUDE(CheckLibraryExists) # Sometimes linking against libatomic is required for atomic ops, if # the platform doesn't support lock-free atomics. function(check_working_cxx_atomics varname) set(OLD_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS}) - set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -std=c++17") + set(CMAKE_REQUIRED_FLAGS "-std=c++11") CHECK_CXX_SOURCE_COMPILES(" #include std::atomic x; -std::atomic y; -std::atomic z; int main() { - ++z; - ++y; - return ++x; + return x; } " ${varname}) set(CMAKE_REQUIRED_FLAGS ${OLD_CMAKE_REQUIRED_FLAGS}) @@ -25,14 +20,13 @@ endfunction(check_working_cxx_atomics) function(check_working_cxx_atomics64 varname) set(OLD_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS}) - set(CMAKE_REQUIRED_FLAGS "-std=c++17 ${CMAKE_REQUIRED_FLAGS}") + set(CMAKE_REQUIRED_FLAGS "-std=c++11 ${CMAKE_REQUIRED_FLAGS}") CHECK_CXX_SOURCE_COMPILES(" #include #include std::atomic x (0); int main() { uint64_t i = x.load(std::memory_order_relaxed); - (void)i; return 0; } " ${varname}) @@ -40,16 +34,15 @@ int main() { endfunction(check_working_cxx_atomics64) -# Check for (non-64-bit) atomic operations. -if(MSVC) - set(HAVE_CXX_ATOMICS_WITHOUT_LIB True) -else() +# This isn't necessary on MSVC, so avoid command-line switch annoyance +# by only running on GCC-like hosts. +if (LLVM_COMPILER_IS_GCC_COMPATIBLE) # First check if atomics work without the library. check_working_cxx_atomics(HAVE_CXX_ATOMICS_WITHOUT_LIB) # If not, check if the library exists, and atomics work with it. if(NOT HAVE_CXX_ATOMICS_WITHOUT_LIB) check_library_exists(atomic __atomic_fetch_add_4 "" HAVE_LIBATOMIC) - if(HAVE_LIBATOMIC) + if( HAVE_LIBATOMIC ) list(APPEND CMAKE_REQUIRED_LIBRARIES "atomic") check_working_cxx_atomics(HAVE_CXX_ATOMICS_WITH_LIB) if (NOT HAVE_CXX_ATOMICS_WITH_LIB) @@ -65,20 +58,20 @@ endif() if(MSVC) set(HAVE_CXX_ATOMICS64_WITHOUT_LIB True) else() - # First check if atomics work without the library. check_working_cxx_atomics64(HAVE_CXX_ATOMICS64_WITHOUT_LIB) - # If not, check if the library exists, and atomics work with it. - if(NOT HAVE_CXX_ATOMICS64_WITHOUT_LIB) - check_library_exists(atomic __atomic_load_8 "" HAVE_CXX_LIBATOMICS64) - if(HAVE_CXX_LIBATOMICS64) - list(APPEND CMAKE_REQUIRED_LIBRARIES "atomic") - check_working_cxx_atomics64(HAVE_CXX_ATOMICS64_WITH_LIB) - if (NOT HAVE_CXX_ATOMICS64_WITH_LIB) - message(FATAL_ERROR "Host compiler must support 64-bit std::atomic!") - endif() - else() - message(FATAL_ERROR "Host compiler appears to require libatomic for 64-bit operations, but cannot find it.") +endif() + +# If not, check if the library exists, and atomics work with it. +if(NOT HAVE_CXX_ATOMICS64_WITHOUT_LIB) + check_library_exists(atomic __atomic_load_8 "" HAVE_CXX_LIBATOMICS64) + if(HAVE_CXX_LIBATOMICS64) + list(APPEND CMAKE_REQUIRED_LIBRARIES "atomic") + check_working_cxx_atomics64(HAVE_CXX_ATOMICS64_WITH_LIB) + if (NOT HAVE_CXX_ATOMICS64_WITH_LIB) + message(FATAL_ERROR "Host compiler must support std::atomic!") endif() + else() + message(FATAL_ERROR "Host compiler appears to require libatomic, but cannot find it.") endif() endif() @@ -87,6 +80,7 @@ endif() ## assumes C++11 works. CHECK_CXX_SOURCE_COMPILES(" #ifdef _MSC_VER +#include /* Workaround for PR19898. */ #include #endif int main() { diff --git a/build/cmake_modules/FindCheck.cmake b/build/cmake_modules/FindCheck.cmake deleted file mode 100644 index 8ad818f4..00000000 --- a/build/cmake_modules/FindCheck.cmake +++ /dev/null @@ -1,55 +0,0 @@ -# - Try to find the CHECK libraries -# Once done this will define -# -# CHECK_FOUND - system has check -# CHECK_INCLUDE_DIRS - the check include directory -# CHECK_LIBRARIES - check library -# -# Copyright (c) 2007 Daniel Gollub -# Copyright (c) 2007-2009 Bjoern Ricks -# -# Redistribution and use is allowed according to the terms of the New -# BSD license. -# For details see the accompanying COPYING-CMAKE-SCRIPTS file. - - -INCLUDE( FindPkgConfig ) - -IF ( Check_FIND_REQUIRED ) - SET( _pkgconfig_REQUIRED "REQUIRED" ) -ELSE( Check_FIND_REQUIRED ) - SET( _pkgconfig_REQUIRED "" ) -ENDIF ( Check_FIND_REQUIRED ) - -IF ( CHECK_MIN_VERSION ) - PKG_SEARCH_MODULE( CHECK ${_pkgconfig_REQUIRED} check>=${CHECK_MIN_VERSION} ) -ELSE ( CHECK_MIN_VERSION ) - PKG_SEARCH_MODULE( CHECK ${_pkgconfig_REQUIRED} check ) -ENDIF ( CHECK_MIN_VERSION ) - -# Look for CHECK include dir and libraries -IF( NOT CHECK_FOUND AND NOT PKG_CONFIG_FOUND ) - - FIND_PATH( CHECK_INCLUDE_DIRS check.h ) - - FIND_LIBRARY( CHECK_LIBRARIES NAMES check ) - - IF ( CHECK_INCLUDE_DIRS AND CHECK_LIBRARIES ) - SET( CHECK_FOUND 1 ) - IF ( NOT Check_FIND_QUIETLY ) - MESSAGE ( STATUS "Found CHECK: ${CHECK_LIBRARIES}" ) - ENDIF ( NOT Check_FIND_QUIETLY ) - ELSE ( CHECK_INCLUDE_DIRS AND CHECK_LIBRARIES ) - IF ( Check_FIND_REQUIRED ) - MESSAGE( FATAL_ERROR "Could NOT find CHECK" ) - ELSE ( Check_FIND_REQUIRED ) - IF ( NOT Check_FIND_QUIETLY ) - MESSAGE( STATUS "Could NOT find CHECK" ) - ENDIF ( NOT Check_FIND_QUIETLY ) - ENDIF ( Check_FIND_REQUIRED ) - ENDIF ( CHECK_INCLUDE_DIRS AND CHECK_LIBRARIES ) -ENDIF( NOT CHECK_FOUND AND NOT PKG_CONFIG_FOUND ) - -# Hide advanced variables from CMake GUIs -MARK_AS_ADVANCED( CHECK_INCLUDE_DIRS CHECK_LIBRARIES ) - diff --git a/build/cmake_modules/GetGitRevisionDescription.cmake b/build/cmake_modules/GetGitRevisionDescription.cmake deleted file mode 100644 index a08895c6..00000000 --- a/build/cmake_modules/GetGitRevisionDescription.cmake +++ /dev/null @@ -1,284 +0,0 @@ -# - Returns a version string from Git -# -# These functions force a re-configure on each git commit so that you can -# trust the values of the variables in your build system. -# -# get_git_head_revision( [ALLOW_LOOKING_ABOVE_CMAKE_SOURCE_DIR]) -# -# Returns the refspec and sha hash of the current head revision -# -# git_describe( [ ...]) -# -# Returns the results of git describe on the source tree, and adjusting -# the output so that it tests false if an error occurs. -# -# git_describe_working_tree( [ ...]) -# -# Returns the results of git describe on the working tree (--dirty option), -# and adjusting the output so that it tests false if an error occurs. -# -# git_get_exact_tag( [ ...]) -# -# Returns the results of git describe --exact-match on the source tree, -# and adjusting the output so that it tests false if there was no exact -# matching tag. -# -# git_local_changes() -# -# Returns either "CLEAN" or "DIRTY" with respect to uncommitted changes. -# Uses the return code of "git diff-index --quiet HEAD --". -# Does not regard untracked files. -# -# Requires CMake 2.6 or newer (uses the 'function' command) -# -# Original Author: -# 2009-2020 Ryan Pavlik -# http://academic.cleardefinition.com -# -# Copyright 2009-2013, Iowa State University. -# Copyright 2013-2020, Ryan Pavlik -# Copyright 2013-2020, Contributors -# SPDX-License-Identifier: BSL-1.0 -# Distributed under the Boost Software License, Version 1.0. -# (See accompanying file LICENSE_1_0.txt or copy at -# http://www.boost.org/LICENSE_1_0.txt) - -if(__get_git_revision_description) - return() -endif() -set(__get_git_revision_description YES) - -# We must run the following at "include" time, not at function call time, -# to find the path to this module rather than the path to a calling list file -get_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH) - -# Function _git_find_closest_git_dir finds the next closest .git directory -# that is part of any directory in the path defined by _start_dir. -# The result is returned in the parent scope variable whose name is passed -# as variable _git_dir_var. If no .git directory can be found, the -# function returns an empty string via _git_dir_var. -# -# Example: Given a path C:/bla/foo/bar and assuming C:/bla/.git exists and -# neither foo nor bar contain a file/directory .git. This will return -# C:/bla/.git -# -function(_git_find_closest_git_dir _start_dir _git_dir_var) - set(cur_dir "${_start_dir}") - set(git_dir "${_start_dir}/.git") - while(NOT EXISTS "${git_dir}") - # .git dir not found, search parent directories - set(git_previous_parent "${cur_dir}") - get_filename_component(cur_dir "${cur_dir}" DIRECTORY) - if(cur_dir STREQUAL git_previous_parent) - # We have reached the root directory, we are not in git - set(${_git_dir_var} - "" - PARENT_SCOPE) - return() - endif() - set(git_dir "${cur_dir}/.git") - endwhile() - set(${_git_dir_var} - "${git_dir}" - PARENT_SCOPE) -endfunction() - -function(get_git_head_revision _refspecvar _hashvar) - _git_find_closest_git_dir("${CMAKE_CURRENT_SOURCE_DIR}" GIT_DIR) - - if("${ARGN}" STREQUAL "ALLOW_LOOKING_ABOVE_CMAKE_SOURCE_DIR") - set(ALLOW_LOOKING_ABOVE_CMAKE_SOURCE_DIR TRUE) - else() - set(ALLOW_LOOKING_ABOVE_CMAKE_SOURCE_DIR FALSE) - endif() - if(NOT "${GIT_DIR}" STREQUAL "") - file(RELATIVE_PATH _relative_to_source_dir "${CMAKE_SOURCE_DIR}" - "${GIT_DIR}") - if("${_relative_to_source_dir}" MATCHES "[.][.]" AND NOT ALLOW_LOOKING_ABOVE_CMAKE_SOURCE_DIR) - # We've gone above the CMake root dir. - set(GIT_DIR "") - endif() - endif() - if("${GIT_DIR}" STREQUAL "") - set(${_refspecvar} - "GITDIR-NOTFOUND" - PARENT_SCOPE) - set(${_hashvar} - "GITDIR-NOTFOUND" - PARENT_SCOPE) - return() - endif() - - # Check if the current source dir is a git submodule or a worktree. - # In both cases .git is a file instead of a directory. - # - if(NOT IS_DIRECTORY ${GIT_DIR}) - # The following git command will return a non empty string that - # points to the super project working tree if the current - # source dir is inside a git submodule. - # Otherwise the command will return an empty string. - # - execute_process( - COMMAND "${GIT_EXECUTABLE}" rev-parse - --show-superproject-working-tree - WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" - OUTPUT_VARIABLE out - ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) - if(NOT "${out}" STREQUAL "") - # If out is empty, GIT_DIR/CMAKE_CURRENT_SOURCE_DIR is in a submodule - file(READ ${GIT_DIR} submodule) - string(REGEX REPLACE "gitdir: (.*)$" "\\1" GIT_DIR_RELATIVE - ${submodule}) - string(STRIP ${GIT_DIR_RELATIVE} GIT_DIR_RELATIVE) - get_filename_component(SUBMODULE_DIR ${GIT_DIR} PATH) - get_filename_component(GIT_DIR ${SUBMODULE_DIR}/${GIT_DIR_RELATIVE} - ABSOLUTE) - set(HEAD_SOURCE_FILE "${GIT_DIR}/HEAD") - else() - # GIT_DIR/CMAKE_CURRENT_SOURCE_DIR is in a worktree - file(READ ${GIT_DIR} worktree_ref) - # The .git directory contains a path to the worktree information directory - # inside the parent git repo of the worktree. - # - string(REGEX REPLACE "gitdir: (.*)$" "\\1" git_worktree_dir - ${worktree_ref}) - string(STRIP ${git_worktree_dir} git_worktree_dir) - _git_find_closest_git_dir("${git_worktree_dir}" GIT_DIR) - set(HEAD_SOURCE_FILE "${git_worktree_dir}/HEAD") - endif() - else() - set(HEAD_SOURCE_FILE "${GIT_DIR}/HEAD") - endif() - set(GIT_DATA "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/git-data") - if(NOT EXISTS "${GIT_DATA}") - file(MAKE_DIRECTORY "${GIT_DATA}") - endif() - - if(NOT EXISTS "${HEAD_SOURCE_FILE}") - return() - endif() - set(HEAD_FILE "${GIT_DATA}/HEAD") - configure_file("${HEAD_SOURCE_FILE}" "${HEAD_FILE}" COPYONLY) - - configure_file("${_gitdescmoddir}/GetGitRevisionDescription.cmake.in" - "${GIT_DATA}/grabRef.cmake" @ONLY) - include("${GIT_DATA}/grabRef.cmake") - - set(${_refspecvar} - "${HEAD_REF}" - PARENT_SCOPE) - set(${_hashvar} - "${HEAD_HASH}" - PARENT_SCOPE) -endfunction() - -function(git_describe _var) - if(NOT GIT_FOUND) - find_package(Git QUIET) - endif() - get_git_head_revision(refspec hash) - if(NOT GIT_FOUND) - set(${_var} - "GIT-NOTFOUND" - PARENT_SCOPE) - return() - endif() - if(NOT hash) - set(${_var} - "HEAD-HASH-NOTFOUND" - PARENT_SCOPE) - return() - endif() - - # TODO sanitize - #if((${ARGN}" MATCHES "&&") OR - # (ARGN MATCHES "||") OR - # (ARGN MATCHES "\\;")) - # message("Please report the following error to the project!") - # message(FATAL_ERROR "Looks like someone's doing something nefarious with git_describe! Passed arguments ${ARGN}") - #endif() - - #message(STATUS "Arguments to execute_process: ${ARGN}") - - execute_process( - COMMAND "${GIT_EXECUTABLE}" describe --tags --always ${hash} ${ARGN} - WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" - RESULT_VARIABLE res - OUTPUT_VARIABLE out - ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) - if(NOT res EQUAL 0) - set(out "${out}-${res}-NOTFOUND") - endif() - - set(${_var} - "${out}" - PARENT_SCOPE) -endfunction() - -function(git_describe_working_tree _var) - if(NOT GIT_FOUND) - find_package(Git QUIET) - endif() - if(NOT GIT_FOUND) - set(${_var} - "GIT-NOTFOUND" - PARENT_SCOPE) - return() - endif() - - execute_process( - COMMAND "${GIT_EXECUTABLE}" describe --dirty ${ARGN} - WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" - RESULT_VARIABLE res - OUTPUT_VARIABLE out - ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) - if(NOT res EQUAL 0) - set(out "${out}-${res}-NOTFOUND") - endif() - - set(${_var} - "${out}" - PARENT_SCOPE) -endfunction() - -function(git_get_exact_tag _var) - git_describe(out --exact-match ${ARGN}) - set(${_var} - "${out}" - PARENT_SCOPE) -endfunction() - -function(git_local_changes _var) - if(NOT GIT_FOUND) - find_package(Git QUIET) - endif() - get_git_head_revision(refspec hash) - if(NOT GIT_FOUND) - set(${_var} - "GIT-NOTFOUND" - PARENT_SCOPE) - return() - endif() - if(NOT hash) - set(${_var} - "HEAD-HASH-NOTFOUND" - PARENT_SCOPE) - return() - endif() - - execute_process( - COMMAND "${GIT_EXECUTABLE}" diff-index --quiet HEAD -- - WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" - RESULT_VARIABLE res - OUTPUT_VARIABLE out - ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) - if(res EQUAL 0) - set(${_var} - "CLEAN" - PARENT_SCOPE) - else() - set(${_var} - "DIRTY" - PARENT_SCOPE) - endif() -endfunction() diff --git a/build/cmake_modules/GetGitRevisionDescription.cmake.in b/build/cmake_modules/GetGitRevisionDescription.cmake.in deleted file mode 100644 index 116efc4e..00000000 --- a/build/cmake_modules/GetGitRevisionDescription.cmake.in +++ /dev/null @@ -1,43 +0,0 @@ -# -# Internal file for GetGitRevisionDescription.cmake -# -# Requires CMake 2.6 or newer (uses the 'function' command) -# -# Original Author: -# 2009-2010 Ryan Pavlik -# http://academic.cleardefinition.com -# Iowa State University HCI Graduate Program/VRAC -# -# Copyright 2009-2012, Iowa State University -# Copyright 2011-2015, Contributors -# Distributed under the Boost Software License, Version 1.0. -# (See accompanying file LICENSE_1_0.txt or copy at -# http://www.boost.org/LICENSE_1_0.txt) -# SPDX-License-Identifier: BSL-1.0 - -set(HEAD_HASH) - -file(READ "@HEAD_FILE@" HEAD_CONTENTS LIMIT 1024) - -string(STRIP "${HEAD_CONTENTS}" HEAD_CONTENTS) -if(HEAD_CONTENTS MATCHES "ref") - # named branch - string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}") - if(EXISTS "@GIT_DIR@/${HEAD_REF}") - configure_file("@GIT_DIR@/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY) - else() - configure_file("@GIT_DIR@/packed-refs" "@GIT_DATA@/packed-refs" COPYONLY) - file(READ "@GIT_DATA@/packed-refs" PACKED_REFS) - if(${PACKED_REFS} MATCHES "([0-9a-z]*) ${HEAD_REF}") - set(HEAD_HASH "${CMAKE_MATCH_1}") - endif() - endif() -else() - # detached HEAD - configure_file("@GIT_DIR@/HEAD" "@GIT_DATA@/head-ref" COPYONLY) -endif() - -if(NOT HEAD_HASH) - file(READ "@GIT_DATA@/head-ref" HEAD_HASH LIMIT 1024) - string(STRIP "${HEAD_HASH}" HEAD_HASH) -endif() diff --git a/build/cmake_modules/TargetArch.cmake b/build/cmake_modules/TargetArch.cmake index e611c4f7..3761e4df 100644 --- a/build/cmake_modules/TargetArch.cmake +++ b/build/cmake_modules/TargetArch.cmake @@ -1,30 +1,16 @@ -# Copyright (c) 2017-2023, The PurpleI2P Project -# This file is part of Purple i2pd project and licensed under BSD3 -# See full license text in LICENSE file at top of project tree - # Based on the Qt 5 processor detection code, so should be very accurate -# https://github.com/qt/qtbase/blob/dev/src/corelib/global/qprocessordetection.h -# Currently handles arm (v5, v6, v7, v8), x86 (32/64), ia64, mips (32/64, mipsel, mips64el) and ppc (32/64) +# https://qt.gitorious.org/qt/qtbase/blobs/master/src/corelib/global/qprocessordetection.h +# Currently handles arm (v5, v6, v7), x86 (32/64), ia64, and ppc (32/64) # Regarding POWER/PowerPC, just as is noted in the Qt source, # "There are many more known variants/revisions that we do not handle/detect." set(archdetect_c_code " -#if defined(__arm__) || defined(__TARGET_ARCH_ARM)|| defined(_M_ARM) || defined(_M_ARM64) || defined(__aarch64__) || defined(__ARM64__) - #if defined(__ARM64_ARCH_8__) \\ - || defined(__aarch64__) \\ - || defined(__ARMv8__) \\ - || defined(__ARMv8_A__) \\ - || defined(_M_ARM64) \\ - || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM-0 >= 8) - #error cmake_ARCH arm64 - #elif defined(__ARM_ARCH_7__) \\ +#if defined(__arm__) || defined(__TARGET_ARCH_ARM) + #if defined(__ARM_ARCH_7__) \\ || defined(__ARM_ARCH_7A__) \\ || defined(__ARM_ARCH_7R__) \\ || defined(__ARM_ARCH_7M__) \\ - || defined(__ARM_ARCH_7S__) \\ - || defined(_ARM_ARCH_7) \\ - || defined(__CORE_CORTEXA__) \\ || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM-0 >= 7) #error cmake_ARCH armv7 #elif defined(__ARM_ARCH_6__) \\ @@ -37,7 +23,6 @@ set(archdetect_c_code " || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM-0 >= 6) #error cmake_ARCH armv6 #elif defined(__ARM_ARCH_5TEJ__) \\ - || defined(__ARM_ARCH_5TE__) \\ || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM-0 >= 5) #error cmake_ARCH armv5 #else @@ -49,19 +34,7 @@ set(archdetect_c_code " #error cmake_ARCH x86_64 #elif defined(__ia64) || defined(__ia64__) || defined(_M_IA64) #error cmake_ARCH ia64 -#elif defined(__mips) || defined(__mips__) || defined(_M_MRX000) - #if defined(_MIPS_ARCH_MIPS64) || defined(__mips64) - #if defined(__MIPSEL__) - #error cmake_ARCH mips64el - #else - #error cmake_ARCH mips64 - #endif - #elif defined(__MIPSEL__) - #error cmake_ARCH mipsel - #else - #error cmake_ARCH mips - #endif -#elif defined(__ppc__) || defined(__ppc) || defined(__powerpc__) || defined(__POWERPC__) \\ +#elif defined(__ppc__) || defined(__ppc) || defined(__powerpc__) \\ || defined(_ARCH_COM) || defined(_ARCH_PWR) || defined(_ARCH_PPC) \\ || defined(_M_MPPC) || defined(_M_PPC) #if defined(__ppc64__) || defined(__powerpc64__) || defined(__64BIT__) @@ -74,7 +47,7 @@ set(archdetect_c_code " #error cmake_ARCH unknown ") -# Set ppc_support to TRUE before including this file on ppc and ppc64 +# Set ppc_support to TRUE before including this file or ppc and ppc64 # will be treated as invalid architectures since they are no longer supported by Apple function(target_architecture output_var) @@ -83,25 +56,23 @@ function(target_architecture output_var) # First let's normalize the order of the values # Note that it's not possible to compile PowerPC applications if you are using - # the OS X SDK version 10.7 or later - you'll need 10.4/10.5/10.6 for that, so we - # disable it by default. Also, ppc64 is not supported in 10.6. + # the OS X SDK version 10.6 or later - you'll need 10.4/10.5 for that, so we + # disable it by default # See this page for more information: # http://stackoverflow.com/questions/5333490/how-can-we-restore-ppc-ppc64-as-well-as-full-10-4-10-5-sdk-support-to-xcode-4 # Architecture defaults to i386 or ppc on OS X 10.5 and earlier, depending on the CPU type detected at runtime. - # On OS X 10.6+ the default is x86_64 if the CPU supports it, i386 otherwise; 10.6 also supports ppc. + # On OS X 10.6+ the default is x86_64 if the CPU supports it, i386 otherwise. foreach(osx_arch ${CMAKE_OSX_ARCHITECTURES}) if("${osx_arch}" STREQUAL "ppc" AND ppc_support) set(osx_arch_ppc TRUE) - elseif("${osx_arch}" STREQUAL "ppc64" AND ppc_support) - set(osx_arch_ppc64 TRUE) elseif("${osx_arch}" STREQUAL "i386") set(osx_arch_i386 TRUE) elseif("${osx_arch}" STREQUAL "x86_64") set(osx_arch_x86_64 TRUE) - elseif("${osx_arch}" STREQUAL "arm64") - set(osx_arch_arm64 TRUE) + elseif("${osx_arch}" STREQUAL "ppc64" AND ppc_support) + set(osx_arch_ppc64 TRUE) else() message(FATAL_ERROR "Invalid OS X arch name: ${osx_arch}") endif() @@ -112,10 +83,6 @@ function(target_architecture output_var) list(APPEND ARCH ppc) endif() - if(osx_arch_ppc64) - list(APPEND ARCH ppc64) - endif() - if(osx_arch_i386) list(APPEND ARCH i386) endif() @@ -124,8 +91,8 @@ function(target_architecture output_var) list(APPEND ARCH x86_64) endif() - if(osx_arch_arm64) - list(APPEND ARCH arm64) + if(osx_arch_ppc64) + list(APPEND ARCH ppc64) endif() else() file(WRITE "${CMAKE_BINARY_DIR}/arch.c" "${archdetect_c_code}") @@ -133,11 +100,11 @@ function(target_architecture output_var) enable_language(C) # Detect the architecture in a rather creative way... - # This compiles a small C program which is a series of ifdefs that selects - # a particular #error preprocessor directive whose message string contains - # the target architecture. The program will always fail to compile (both because - # file is not a valid C program, and obviously because of the presence of - # the #error preprocessor directives... but by exploiting the preprocessor in this + # This compiles a small C program which is a series of ifdefs that selects a + # particular #error preprocessor directive whose message string contains the + # target architecture. The program will always fail to compile (both because + # file is not a valid C program, and obviously because of the presence of the + # #error preprocessor directives... but by exploiting the preprocessor in this # way, we can detect the correct target architecture even when cross-compiling, # since the program itself never needs to be run (only the compiler/preprocessor) try_run( diff --git a/build/cmake_modules/Version.cmake b/build/cmake_modules/Version.cmake deleted file mode 100644 index cb9551db..00000000 --- a/build/cmake_modules/Version.cmake +++ /dev/null @@ -1,16 +0,0 @@ -# read version - -function(set_version version_file output_var) - file(READ "${version_file}" version_data) - - string(REGEX MATCH "I2PD_VERSION_MAJOR ([0-9]*)" _ ${version_data}) - set(version_major ${CMAKE_MATCH_1}) - - string(REGEX MATCH "I2PD_VERSION_MINOR ([0-9]*)" _ ${version_data}) - set(version_minor ${CMAKE_MATCH_1}) - - string(REGEX MATCH "I2PD_VERSION_MICRO ([0-9]*)" _ ${version_data}) - set(version_micro ${CMAKE_MATCH_1}) - - set(${output_var} "${version_major}.${version_minor}.${version_micro}" PARENT_SCOPE) -endfunction() diff --git a/build/docker/README.md b/build/docker/README.md new file mode 100644 index 00000000..877a9ce5 --- /dev/null +++ b/build/docker/README.md @@ -0,0 +1,34 @@ +Howto build & run +================== + +**Build** + +Assuming you're in the root directory of the anoncoin source code. + +$ `cd build/docker` +$ `docker -t meeh/i2pd:latest .` + +**Run** + +To run either the local build, or if not found - fetched prebuild from hub.docker.io, run the following command. + +$ `docker run --name anonnode -v /path/to/i2pd/datadir/on/host:/var/lib/i2pd -p 7070:7070 -p 4444:4444 -p 4447:4447 -p 7656:7656 -p 2827:2827 -p 7654:7654 -p 7650:7650 -d meeh/i2pd` + +All the ports ( -p HOSTPORT:DOCKERPORT ) is optional. However the command above enable all features (Webconsole, HTTP Proxy, BOB, SAM, i2cp, etc) + +The volume ( -v HOSTDIR:DOCKERDIR ) is also optional, but if you don't use it, your config, routerid and private keys will die along with the container. + +**Options** + +Options are set via docker environment variables. This can be set at run with -e parameters. + +* **ENABLE_IPV6** - Enable IPv6 support. Any value can be used - it triggers as long as it's not empty. +* **LOGLEVEL** - Set the loglevel. +* **ENABLE_AUTH** - Enable auth for the webconsole. Username and password needs to be set manually in i2pd.conf cause security reasons. + +**Logging** + +Logging happens to STDOUT as the best practise with docker containers, since infrastructure systems like kubernetes with ELK integration can automatically forward the log to say, kibana or greylog without manual setup. :) + + + diff --git a/build/docker/old-ubuntu-based/Dockerfile b/build/docker/old-ubuntu-based/Dockerfile new file mode 100644 index 00000000..3faddf2e --- /dev/null +++ b/build/docker/old-ubuntu-based/Dockerfile @@ -0,0 +1,11 @@ +FROM ubuntu + +RUN apt-get update && apt-get install -y libboost-dev libboost-filesystem-dev \ + libboost-program-options-dev libboost-date-time-dev \ + libssl-dev git build-essential + +RUN git clone https://github.com/PurpleI2P/i2pd.git +WORKDIR /i2pd +RUN make + +CMD ./i2pd diff --git a/build/fig.yml b/build/fig.yml new file mode 100644 index 00000000..372ef350 --- /dev/null +++ b/build/fig.yml @@ -0,0 +1,2 @@ +i2pd: + build: . diff --git a/contrib/apparmor/docker-i2pd b/contrib/apparmor/docker-i2pd deleted file mode 100644 index 8e34298a..00000000 --- a/contrib/apparmor/docker-i2pd +++ /dev/null @@ -1,42 +0,0 @@ -# _________________________________________ -# / Copy this file to the right location \ -# | then load with: | -# | | -# | apparmor_parser -r -W | -# | /etc/apparmor.d/docker-i2pd | -# | | -# | docker run --security-opt | -# | "apparmor=docker-i2pd" ... | -# | purplei2p/i2pd | -# | | -# \ And "aa-status" to verify it's loaded. / -# ----------------------------------------- -# \ ^__^ -# \ (oo)\_______ -# (__)\ )\/\ -# ||----w | -# || || - -#include - -profile docker-i2pd flags=(attach_disconnected,mediate_deleted) { - #include - #include - #include - - /bin/busybox ix, - /usr/local/bin/i2pd ix, - /entrypoint.sh ixr, - - /i2pd_certificates/** r, - - /home/i2pd/data/** rw, - - /home/i2pd/data/i2pd.pid k, - - deny /home/i2pd/data/i2pd.conf w, - deny /home/i2pd/data/tunnels.conf w, - deny /home/i2pd/data/tunnels.d/** w, - deny /home/i2pd/data/certificates/** w, - deny /home/i2pd/data/i2pd.log r, -} diff --git a/contrib/apparmor/usr.bin.i2pd b/contrib/apparmor/usr.sbin.i2pd similarity index 57% rename from contrib/apparmor/usr.bin.i2pd rename to contrib/apparmor/usr.sbin.i2pd index 4d370f3c..4349e07b 100644 --- a/contrib/apparmor/usr.bin.i2pd +++ b/contrib/apparmor/usr.sbin.i2pd @@ -4,22 +4,31 @@ # #include -profile i2pd /{usr/,}bin/i2pd { +/usr/sbin/i2pd { #include - #include - #include + + network inet dgram, + network inet stream, + network inet6 dgram, + network inet6 stream, + network netlink raw, + + /etc/gai.conf r, + /etc/host.conf r, + /etc/hosts r, + /etc/nsswitch.conf r, + /run/resolvconf/resolv.conf r, # path specific (feel free to modify if you have another paths) /etc/i2pd/** r, + /run/i2pd/i2pd.pid rw, /var/lib/i2pd/** rw, /var/log/i2pd/i2pd.log w, - /{var/,}run/i2pd/i2pd.pid rwk, - /{usr/,}bin/i2pd mr, - @{system_share_dirs}/i2pd/** r, + /var/run/i2pd/i2pd.pid rw, + /usr/sbin/i2pd mr, + /usr/share/i2pd/** r, # user homedir (if started not by init.d or systemd) owner @{HOME}/.i2pd/ rw, owner @{HOME}/.i2pd/** rwk, - - #include if exists } diff --git a/contrib/certificates/family/stormycloud.crt b/contrib/certificates/family/stormycloud.crt deleted file mode 100644 index 4ec4765a..00000000 --- a/contrib/certificates/family/stormycloud.crt +++ /dev/null @@ -1,14 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICKDCCAc6gAwIBAgIUcPHZXtYSqGNRCD6z8gp79WUFtI0wCgYIKoZIzj0EAwIw -gZMxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEPMA0GA1UEBwwGQXVzdGlu -MRgwFgYDVQQKDA9TdG9ybXlDbG91ZCBJbmMxIzAhBgNVBAMMGnN0b3JteWNsb3Vk -LmZhbWlseS5pMnAubmV0MSQwIgYJKoZIhvcNAQkBFhVhZG1pbkBzdG9ybXljbG91 -ZC5vcmcwHhcNMjIwMzE5MTU1MjU2WhcNMzIwMzE2MTU1MjU2WjCBkzELMAkGA1UE -BhMCVVMxDjAMBgNVBAgMBVRleGFzMQ8wDQYDVQQHDAZBdXN0aW4xGDAWBgNVBAoM -D1N0b3JteUNsb3VkIEluYzEjMCEGA1UEAwwac3Rvcm15Y2xvdWQuZmFtaWx5Lmky -cC5uZXQxJDAiBgkqhkiG9w0BCQEWFWFkbWluQHN0b3JteWNsb3VkLm9yZzBZMBMG -ByqGSM49AgEGCCqGSM49AwEHA0IABFUli0hvJEmowNjJVjbKEIWBJhqe973S4VdL -cJuA5yY3dC4Y998abWEox7/Y1BhnBbpJuiodA341bXKkLMXQy/kwCgYIKoZIzj0E -AwIDSAAwRQIgD12F/TfY3iV1/WDF7BSKgbD5g2MfELUIy1dtUlJQuJUCIQD69mZw -V1Z9j2x0ZsuirS3i6AMfVyTDj0RFS3U1jeHzIQ== ------END CERTIFICATE----- diff --git a/contrib/certificates/reseed/acetone_at_mail.i2p.crt b/contrib/certificates/reseed/acetone_at_mail.i2p.crt deleted file mode 100644 index 13f9f17d..00000000 --- a/contrib/certificates/reseed/acetone_at_mail.i2p.crt +++ /dev/null @@ -1,32 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIFfzCCA2egAwIBAgIEctG1gDANBgkqhkiG9w0BAQ0FADBwMQswCQYDVQQGEwJY -WDELMAkGA1UECAwCWFgxCzAJBgNVBAcMAlhYMR4wHAYDVQQKDBVJMlAgQW5vbnlt -b3VzIE5ldHdvcmsxDDAKBgNVBAsMA0kyUDEZMBcGA1UEAwwQYWNldG9uZUBtYWls -LmkycDAeFw0yMTAxMjUxMDMyMjBaFw0zMTAxMjMxMDMyMjBaMHAxCzAJBgNVBAYT -AlhYMQswCQYDVQQIDAJYWDELMAkGA1UEBwwCWFgxHjAcBgNVBAoMFUkyUCBBbm9u -eW1vdXMgTmV0d29yazEMMAoGA1UECwwDSTJQMRkwFwYDVQQDDBBhY2V0b25lQG1h -aWwuaTJwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAwqF/BRRmvZ54 -5XArgxbytDi7m7MDjFE/whUADruHj/9jXGCxE8DDiiKTt3yhfakV0SNo5xk7AMD+ -wqiSNC5JCHTm18gd2M4cQLIaOVRqucLLge4XVgk2WPX6OT98wfxh7mqA3wlSdEpj -dY3Txtkf7VfZLicG76/RBtLFW3aBdsn63hZaQqZE4x/5MJyPVZx59+lys5RmMi0o -LpXJy4HOu1/Gl1iKDJoI/ARFG3y7uP/B+ZtZBitJetTs0HcqycnNJq0tVZf2HiGF -JNy67AL4foxNYPXP6QsvXvp6LRpGANaBCkFCBlriSF+x1zO2H3uAkRnuLYXuKIfB -HudejTp4R57VgZGiHYoawHaF17FVAApue9G8O82XYECjhET35B9yFoOBHTvaMxLU -CKrmayH8KMQon95mfe1qpoO3/YDa8DCxkjAfjdtytat7nt2pGZMH6/cLJxcFiofh -RtRVvb+omv/X12j/6iCFrwP4NvBnAZsa736igbjpyee5n+CSyYxd9cJkRX1vQVk7 -WFSqL58Pz+g6CKJmdMPvqNOfUQ6mieBeejmx35B4pLzLcoNxw8R3O1+I2l4dg042 -dEydKRQNwdzOec4jYwnKR40iwIyZxpchXWGRbBdyF5RQCbIIo60QBJlfXMJ2svan -q5lYIeWeY3mlODXu4KH4K09y10KT8FsCAwEAAaMhMB8wHQYDVR0OBBYEFMh+DoIL -APNiu2o+6I9A49joNYQuMA0GCSqGSIb3DQEBDQUAA4ICAQBFeOJi0rmkqN5/E3IB -nE2x4mUeLI82tUcN2D3Yu8J81vy4DnH+oMRQFDtYEHW5pfirRmgSZ7MQwYQnqWLp -iTE7SyCxlqGrmVsYp7PzfS1pUT2QeWPtsNYUDdraG0Zr9BkIGB60VMhjMSa9WUrj -lbchzr6E/j/EsEOE7IK08JxIDKCDZM2LLwis4tAM6tmiylkMf2RlUBIRBs1TCO+q -x3yByttNE2P4nQyQVQpjc1qsaOMvJvbxun37dwo+oTQy+hwkA86BWTDRYdN3xwOk -OfAOtlX6zM/wCKMN0ZRnjZoh59ZCn4JXokt3IjZ4n8qJOuJFRKeKGmGeKA8uaGW8 -ih5tdB99Gu5Z8LOT1FxAJKwQBn5My0JijPoMit4B0WKNC8hy2zc2YvNfflu1ZRj5 -wF4E5ktbtT/LWFSoRPas/GFS8wSXk/kbSB0ArDcRRszb3JHqbALmSQxngz3rfwb3 -SHwQIIg956gjMDueEX5CrGrMqigiK53b9fqtpghUrHDsqtEXqeImpAY65PX1asqo -metDNuETHF7XrAjP7TGJfnrYQyeK90iS7j1G68ScBGkKY2nsTnFoXkSk5s5D338E -SUzPaOlh91spmkVY6gQTVQ7BakADBHw+zBgDA1gBN/4JPvgN36hquj63+aG1cKy3 -3ZUnv2ipo2fpr69NtuBnutK6gw== ------END CERTIFICATE----- diff --git a/contrib/certificates/reseed/admin_at_stormycloud.org.crt b/contrib/certificates/reseed/admin_at_stormycloud.org.crt deleted file mode 100644 index ae44521b..00000000 --- a/contrib/certificates/reseed/admin_at_stormycloud.org.crt +++ /dev/null @@ -1,34 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIF1zCCA7+gAwIBAgIRAMDqFR09Xuj8ZUu+oetSvAEwDQYJKoZIhvcNAQELBQAw -dTELMAkGA1UEBhMCWFgxCzAJBgNVBAcTAlhYMQswCQYDVQQJEwJYWDEeMBwGA1UE -ChMVSTJQIEFub255bW91cyBOZXR3b3JrMQwwCgYDVQQLEwNJMlAxHjAcBgNVBAMM -FWFkbWluQHN0b3JteWNsb3VkLm9yZzAeFw0yNDAxMjUxNDE1MzBaFw0zNDAxMjUx -NDE1MzBaMHUxCzAJBgNVBAYTAlhYMQswCQYDVQQHEwJYWDELMAkGA1UECRMCWFgx -HjAcBgNVBAoTFUkyUCBBbm9ueW1vdXMgTmV0d29yazEMMAoGA1UECxMDSTJQMR4w -HAYDVQQDDBVhZG1pbkBzdG9ybXljbG91ZC5vcmcwggIiMA0GCSqGSIb3DQEBAQUA -A4ICDwAwggIKAoICAQDbGX+GikPzQXr9zvkrhfO9g0l49KHLNQhUKYqd6T+PfnGo -Fm0d3ZZVVQZ045vWgroOXDGGZZWxUIlb2inRaR2DF1TxN3pPYt59RgY9ZQ9+TL7o -isY91krCRygY8EcAmHIjlfZQ9dBVcL7CfyT0MYZA5Efee9+NDHSewTfQP9T2faIE -83Fcyd93a2mIHYjKUbJnojng/wgsy8srbsEuuTok4MIQmDj+B5nz+za2FgI0/ydh -srlMt4aGJF4/DIem9z9d0zBCOkwrmtFIzjNF1mOSA8ES4m5YnKA/y9rZlRidLPGu -prbXhPVnqHeOnHMz2QCw1wbVo504kl0bMqyEz2tVWsO9ep7iZoQs2xkFAEaegYNT -QLUpwVGlyuq3wXXwopFRffOSimGSazICwWI6j+K0pOtgefNJaWrqKYvtkj1SbK2L -LBNUIENz6VnB7KPRckuX6zxC8PpOiBK9BcftfO+xAz/wC6qq3riBPw30KKSym0nC -Zp5KciDn4Phtw9PGq8Bkl8SyWl0jtFnfTB1tzJkisf2qKcNHaFTEe2JW763YLbh/ -AU+8X8evFu40qLgvOgKoyy5DLy6i8zetX+3t9K0Fxt9+Vzzq6lm5V/RS8iIPPn+M -q1/3Z5kD0KQBG9h/Gl8BH+lB71ZxPAOZ3SMu8DJZcxBLVmDWqQPCr5CKnoz0swID -AQABo2IwYDAOBgNVHQ8BAf8EBAMCAoQwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsG -AQUFBwMBMA8GA1UdEwEB/wQFMAMBAf8wHgYDVR0OBBcEFWFkbWluQHN0b3JteWNs -b3VkLm9yZzANBgkqhkiG9w0BAQsFAAOCAgEARWOJ69vTHMneSXYscha+4Ytjg0RM -faewJNEGj8qy/Qvh9si2bWYNPRK6BlbHFS7pRYBLAnhaeLBGVv1CCR6GUMMe74zQ -UuMeAoWU6qMDmB3GfYoZJh8sIxpwHqyJeTdeccRbZ4sX4F6u3IHPXYiU/AgbYqH7 -pYXQg2lCjXZYaDFAlEf5SlYUDOhhXe5kR8Edhlrsu32/JzA1DQK0JjxKCBp+DQmA -ltdOpQtAg03fHP4ssdj7VvjIDl28iIlATwBvHrdNm7T0tYWn6TWhvxbRqvfTxfaH -MvxnPdIJwNP4/9TyQkwjwHb1h+ucho3CnxI/AxspdOvT1ElMhP6Ce6rcS9pk11Rl -x0ChsqpWwDg7KYpg0qZFSKCTBp4zBq9xoMJ6BQcgMfyl736WbsCzFTEyfifp8beg -NxUa/Qk7w7cuSPGyMIKNOmOR7FLlFbtocy8sXVsUQdqnp/edelufdNe39U9uNtY6 -yoXI9//Tc6NgOwy2Oyia0slZ5qHRkB7e4USXMRzJ3p4q9eCVKjAJs81Utp7O2U+9 -vhbhwWP8CAnNTT1E5WS6EKtfrdqF7wjkV+noPGLDGmrXi01J1fSMAjMfVO+7/LOL -UN+G4ybKWnEhhOO27yidN8Xx6UrCS23DBlPPQAeA74dTsTExiOxf1o1EXzcQiMyO -LAj3/Ojbi1xkWhI= ------END CERTIFICATE----- diff --git a/contrib/certificates/reseed/arnavbhatt288_at_mail.i2p.crt b/contrib/certificates/reseed/arnavbhatt288_at_mail.i2p.crt deleted file mode 100644 index 9068afb9..00000000 --- a/contrib/certificates/reseed/arnavbhatt288_at_mail.i2p.crt +++ /dev/null @@ -1,34 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIF2TCCA8GgAwIBAgIQIHQPtSoFU+cUpYD8PZaWZjANBgkqhkiG9w0BAQsFADB2 -MQswCQYDVQQGEwJYWDELMAkGA1UEBxMCWFgxCzAJBgNVBAkTAlhYMR4wHAYDVQQK -ExVJMlAgQW5vbnltb3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEfMB0GA1UEAwwW -YXJuYXZiaGF0dDI4OEBtYWlsLmkycDAeFw0yMzAxMjUxODUzNDFaFw0zMzAxMjUx -ODUzNDFaMHYxCzAJBgNVBAYTAlhYMQswCQYDVQQHEwJYWDELMAkGA1UECRMCWFgx -HjAcBgNVBAoTFUkyUCBBbm9ueW1vdXMgTmV0d29yazEMMAoGA1UECxMDSTJQMR8w -HQYDVQQDDBZhcm5hdmJoYXR0Mjg4QG1haWwuaTJwMIICIjANBgkqhkiG9w0BAQEF -AAOCAg8AMIICCgKCAgEAtwG73sC0jYd3fgEzZh0SveAdUd5yD35nINJRrdPSrSwY -n3i1qGe3fNLj877PvUDU+qiHH0fFZfyFkXTaq3TUp1u4YkmvaoPHy6FZlojB08lK -FBm+iJ1hifQ7MFmvIKUGv+cjlN6xSoQ0U6B2QOy6iZnBgFZ/7jbRY4iZOIj7VJtY -aodeHfy0bWe447VJovbkUi7NJPFZQS65LMcAIWcWTxrC0Gj8SmdxL3a5+hxpmmg0 -+KCQvWQDdxAQjsc16sgUCdUc6cWYO4yw9H6fgdq9GJX+LnXR9OB58GsAjjlLlFoI -CZxdARDpoqcIj6AoKIanALf8yfbIyrqqJE47cuaqV9bht5MWKnXbwHplEkT4ZNkh -PnRDia7B5HY3uwbt39CBm264PEWXvWG2sozTWKQqBjmMN2cj/NFDUEqKv6BggMY1 -HcqxWFKRcgKCtRvrmTmfp5l0/ou+OtUaFUg0a6Qhtb93Hj10vK6wZzidBqj0ggzB -eJDI95b89u8JgzRoOBriuMKTc91WTkOvBLkB3dgUbUpx2p8KHjvf/pppBH9u0oxp -qJFFK840DbnJydEvjKezeVe5Ax6YRSRxyEdKzRoWdvKVxb3qBBKMdCKTYEPxHPBu -JMEQVUCXJMti++1KEiQGhcfWvLyT7OewbcIZNk9XWNrxlKcGrTp9AOwaaNC5m1kC -AwEAAaNjMGEwDgYDVR0PAQH/BAQDAgKEMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggr -BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB8GA1UdDgQYBBZhcm5hdmJoYXR0Mjg4 -QG1haWwuaTJwMA0GCSqGSIb3DQEBCwUAA4ICAQAHiK0ld/1PF9DIhutD660/bzBg -mF2Z76hcBqDZ8tnQai/u/RXYrH9wso9BYyrVsvk3fr6tpGT49Ian0MVpPOxMoTU2 -oBEmQlYrfclQLFsOLmA0y2r1ggXzIrt69jB710Vhwdnz09oOE8rS4E2T5oDD8Wvy -Kony+AarRceqtkOlzyquc42KjzdrbHsosF7G2iGhNI6t+T3BfWJ+Q+d5sj3OIh6e -gSfvHL44E4vZt6dtofRN3MAZ60kNLF5YWyaUo3Snv9Lso1IwIz3AVr5ehv+8sFL/ -KxaXdkZ5Yn2YUX7p1t4VQd+eXVPYjf1befg4PvrwSkylu3Jpee3fllZSKXeSVx9x -jpJiq5vIakqk22pnWb1Vn7xzSW1vtEG7QLjobOr1WrcGiwdv+HKiWcXJXDzKoWXs -h3VEfr51Kap8cIJv+D6lJIG9IcIhiQ6CXWBmtjWJvbdVwFBy1/3Fhaou9liHi+gK -4Yh5a5OGCzc7xjtpGaTmoLEz7NzDNOdd/r840qRDOh70izzmFZd5Gwq4hoVcPJcS -EAySwtgqK0/4d0zDd2Wg9ASJV9DnDf8QuSmHZgZ9Efs47XcWz9TvkWUS1E66AJsN -mmI1NDQ3mv3dv5+WPq+dqqYFsnx3xWL1g5Z3buk0opeuXMzoHwM7UfN8h7Q1M5+t -+XBgkaYA4iEwYKqlCQ== ------END CERTIFICATE----- diff --git a/contrib/certificates/reseed/atomike_at_mail.i2p.crt b/contrib/certificates/reseed/atomike_at_mail.i2p.crt new file mode 100644 index 00000000..1e724f00 --- /dev/null +++ b/contrib/certificates/reseed/atomike_at_mail.i2p.crt @@ -0,0 +1,34 @@ +-----BEGIN CERTIFICATE----- +MIIF5TCCA82gAwIBAgIRANFIiHpTaRY2Z30TQOiuqFcwDQYJKoZIhvcNAQELBQAw +cDELMAkGA1UEBhMCWFgxCzAJBgNVBAcTAlhYMQswCQYDVQQJEwJYWDEeMBwGA1UE +ChMVSTJQIEFub255bW91cyBOZXR3b3JrMQwwCgYDVQQLEwNJMlAxGTAXBgNVBAMM +EGF0b21pa2VAbWFpbC5pMnAwHhcNMTYwODAyMTQyNDEyWhcNMjYwODAyMTQyNDEy +WjBwMQswCQYDVQQGEwJYWDELMAkGA1UEBxMCWFgxCzAJBgNVBAkTAlhYMR4wHAYD +VQQKExVJMlAgQW5vbnltb3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEZMBcGA1UE +AwwQYXRvbWlrZUBtYWlsLmkycDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC +ggIBAMLRmxclaAvm405JLHNNiniUi0aZaBoLJ+afwn2LGfTDUhTD5Y8lW6V9o90n +eTNOCaiid7bWpVBkA1M4gZ9TdUnP0POa99jXZbj4PHFRl1l8k4Ap12PUO3hgwtH7 +7j7j+UPaIuE2y+U7hJbmyQ0v7r8yjGWSTtSqs+exNhyr4Mh7DvacZySZ+oqQdXYA +vnfDpBX1dKlN1Nb4XloG0uE1OK1YfJoC+p+v8qXjKagIdZgThdmsWcQ82EGI+Q9u +VfrE4m3CNwJy0X86wMNYqHej88wBHnJMmTm+cZtFLVmZsRqnuLAQL1wrfCbGSltR +zhVQHTysLwMz9+llTXtzMf+R2kcEAYWiPc5IRVU+LvkN/610r5fuHW+OcQ9ZgRVn +PMqlv5PDG2ZxdIOAQQsOd7fH0r5q3MhqlVstVE45Rl33uA+M7wjJK2cvnOoSioxp +szn2GIZliXQXo4dJczgfN2U4PLBGRBGmrB1R2S1YsG6CrSJuMCX14VKJP69Nfm8a +EDA5GKNke+ZpXCszPLaNMB70LVFQc9FmMhsOgLIIoJBgd61uMgokMJJMLaWN0RaK +w1ZduxYGUmg2T2pi/clIkVzZmlcHKViUn0sMcKD+ibEPOvQIB/3HPEEt6iIkanc/ +da5IFzikkaykt/Tu6o8rreeEu65HkIxFaCHegSXLHSyxj00BAgMBAAGjejB4MA4G +A1UdDwEB/wQEAwIChDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwDwYD +VR0TAQH/BAUwAwEB/zAZBgNVHQ4EEgQQYXRvbWlrZUBtYWlsLmkycDAbBgNVHSME +FDASgBBhdG9taWtlQG1haWwuaTJwMA0GCSqGSIb3DQEBCwUAA4ICAQAA0MdWfN/N +1q5CdJqDyw4JQwzdYkA27Wr02qIcmwnqjcCEDPl4uDTyqN9gbEpJ48AcsdXRa6GE +lLh/qJ67I6YDe63LuhndzRULNgxGHVMGS8kBJIssQehb2rOFnbUTp0gMR+0QpXXe +omase4kL90c9uuYX1vXaO/ADssY2/QX49prwJO+UY/jGhcX4YheFI/teA85u6Qko +ero437Shqhl0kbdK+eBkOFf9a7mGxpMT73KE1jFS6433W4fFOkybQ1dcS0qStaUM +3qKC0EQCbAl1seAp3AGuG46swHZB0rZ1WCKVAr5yqCWSWMYO+fL6FosNg9z/VDVh +g6FFfoGrv19yaVFa9AvQsk1ATZ+bwtHProNx2Xet9pnAI30dT16+C5wCctoR6RVf +iOHl6CGqadjOycbMDVvOfJhypNDgWW3gBaCfXiAocJTLpR7hKNZ2bnvcP2xyXH1j +Qz/kiMJoZ3+TV1yC/x/maAHsUIQHqqd6ZRj7x5MgJq0UBdITo2ZQVfXYI0ZGIeNm +fMu+P5448+NdpASa9QoqS8kPFeUaHJMzMFHBKhrr8lTJeZ82hKBXt5jD3Tbef5Ck +n5auKu2D0IjvrzsdIpNMQAhuBPT06TW/LzN/MvardZcaLcBmcutefw6Z7RsedHvj +cGpnw4a2u9sHZIUNHzoGq32+7UWXsBI5Ow== +-----END CERTIFICATE----- diff --git a/contrib/certificates/reseed/backup_at_mail.i2p.crt b/contrib/certificates/reseed/backup_at_mail.i2p.crt new file mode 100644 index 00000000..73b08eaa --- /dev/null +++ b/contrib/certificates/reseed/backup_at_mail.i2p.crt @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFfTCCA2WgAwIBAgIEOprmhjANBgkqhkiG9w0BAQ0FADBvMQswCQYDVQQGEwJY +WDELMAkGA1UECBMCWFgxCzAJBgNVBAcTAlhYMR4wHAYDVQQKExVJMlAgQW5vbnlt +b3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEYMBYGA1UEAwwPYmFja3VwQG1haWwu +aTJwMB4XDTEzMTAxMzEzNDQ1NVoXDTIzMTAxMzEzNDQ1NVowbzELMAkGA1UEBhMC +WFgxCzAJBgNVBAgTAlhYMQswCQYDVQQHEwJYWDEeMBwGA1UEChMVSTJQIEFub255 +bW91cyBOZXR3b3JrMQwwCgYDVQQLEwNJMlAxGDAWBgNVBAMMD2JhY2t1cEBtYWls +LmkycDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAIoAkobXwk/Enf1d +roHyqCyvcJfZJVTwb/LgYWAvCBMCr+RGqlSgtk3g69Y3I0xU08fD2kGt3r5Pwsbr +omXIbJAcccyLqmQ5QX6QgL+X9VpMDp9C4h2RogCrqLBAWw4cuZ4RS9VCpP1Yis7H +uejYqENP86p7BsRnuW/4cYnfunAdMpss4LpRGQXt1nTX+kfgCYgnKFbFqwAHt7yV +Ds+Pe6FuBHPlp+sc1amKRcUnSvhXLsv43VicnT7xYL/kUsN83wrtHA3B4aGDx3aA +3/EzuRmIXQB0BlTZILMEyYwG/nc4OsW82QYrvEZ9BIg9A4lF/wS/KZCICPxLF2zo +dGjnmlgkiA4s8eO+va/ElHyELjckVXqmG1eXHhSkEsDvOQJy01IUuwLinvq7cUbJ +HfJBZJllEg+sLDCv3FkEqN+XjBNFfQN4oNew4w6IPY6YH1INVB9LL0Cmdu4DudLv +TY8OcI8eSfez3hmm+pYQ23PJRYYnvRDnRECyIWBegkckWRh8U/WvZUYUvETK6EDl +/0KpTtfzX6MqHA5D6bTAB8Y3ijGMLrZ/B5vj5yCoZbLiGme9X2moR2k1LEhdhtzV +exsqezCpg6dn48FTX7mHjvR5/r4kz2jqBGmdPUWIIxnjFUzDUK3llVQiHihleHpe +jL4LqnhBGKWFRTaVwaIkBG4zAfIzAgMBAAGjITAfMB0GA1UdDgQWBBQNkfW7bSMl +1/4KDbgwrkf9x1Zu/TANBgkqhkiG9w0BAQ0FAAOCAgEAGg3a3rTf0EznQocmio0T +5gCoL0n8h6yKW/PyPAIELrd9wiYjhJFcWvMTcJJJnVqmAL5vpvhaAFVtAfx70MGa +0DZ7FvytK5hEfF4IqOFDyEEVGJR5rIpVK4MeI1nmwEsxdbW+FhODjtRzgYO8XBME +Xj4aY1FWg9vxc3reUj6PSFsZtsB0aLiRgL9JDovJIiRw0Uqr1v2wXBte5yVCxDge +vTREZtpK4cKetoOa68pwSXI32JwKE18j6bfdKVBCcYQKlKP/3gHGduaDrQv3w32S +DRym5s6MREeTUOtAw4wq46KpdOX8yyAqJPrCfMwS6ORd3t+egqOw0PUnsqb97w4O +lUtrRYvb2cOj60SmRx4vJvItyuHbKqIK7o2e1RcUZPXYoAVx2ww4XB2Wk4D7LSAs +cS7nLj8yAqzJ2qqtBzxu+zILJtkVa12dKF0xmS0BxBp4sCYiBtmAVE8AWQqEuSHA +FrMWqoXcjcfdvvyX487FFWWUE7ZBIn0hee2sK9J9+SPtqczJaN7TF3K3nzo65WJG +1epltmq2Ugjb67Gz7v4y7H23DJ/qhm8yLtCHTj69HTta5I08j6Kut924WLZaiMO/ +4YoEL5AE63X0sxYibKFQiq7FW5nUJA280GRlY3xSMFzlB2ggazrUV3YAWVDhfdnI +flpzWXkFM2D36OUaubfe9YY= +-----END CERTIFICATE----- diff --git a/contrib/certificates/reseed/bugme_at_mail.i2p.crt b/contrib/certificates/reseed/bugme_at_mail.i2p.crt new file mode 100644 index 00000000..2b6acac0 --- /dev/null +++ b/contrib/certificates/reseed/bugme_at_mail.i2p.crt @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFezCCA2OgAwIBAgIEUQYyQjANBgkqhkiG9w0BAQ0FADBuMQswCQYDVQQGEwJY +WDELMAkGA1UECBMCWFgxCzAJBgNVBAcTAlhYMR4wHAYDVQQKExVJMlAgQW5vbnlt +b3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEXMBUGA1UEAwwOYnVnbWVAbWFpbC5p +MnAwHhcNMTQxMTA2MDkxMTE0WhcNMjQxMTA1MDkxMTE0WjBuMQswCQYDVQQGEwJY +WDELMAkGA1UECBMCWFgxCzAJBgNVBAcTAlhYMR4wHAYDVQQKExVJMlAgQW5vbnlt +b3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEXMBUGA1UEAwwOYnVnbWVAbWFpbC5p +MnAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCrThOH0eSDT0VnCSBC +sqYmAydWH+O8eNttDXr2mSvZLhvAW+6/xHTkKhaWvkIvvS0Vh8hujMnD90Cgp4Fk +TKCxMj9K527o5xIZwWW05OevbjlBwIpVLO1PjmsfsoD1nIX14eEzJSEoAulKsv7V +jGUC/6hC11mmVvH9buQLSRv6sCjuAcMszmw3TAD+XYBIs+z57KuwYXtX3+OA543c +l1/ZKLYkkwY8cwzZqWDVWqTKP5TfVae58t40HhJk3bOsr21FZsaOjlmao3GO+d/3 +exKuUGJRcolSqskL3sZ1ovFqko81obvvx0upI0YA0iMr/NRGl3VPuf/LJvRppYGc +LsJHgy9TIgtHvaXRi5Nt4CbKl9sZh/7WkkTTI5YGvevu00btlabAN+DSAZZqdsB3 +wY8HhM1MHiA9SWsqwU65TwErcRrjNna2FiDHEu0xk5+/iAGl6CSKHZBmNcYKXSv8 +cwShB0jjmciK0a05nC638RPgj0fng7KRrSglyzfjXRrljmZ40LSBL/GGMZMWpOM7 +mEsBH5UZJ/2BEmjc9X9257zBdx8BK8y1TXpAligpNBsERcTw1WP1PJ35einZvlXW +qI3GwMf0sl26sn+evcK0gDl27jVDZ45MtNQEq64M4NV3Tn9zq0eg/39YvjVeqrI5 +l7sxmYqYGR6BuSncwdc4x+t6swIDAQABoyEwHzAdBgNVHQ4EFgQU/REZ7NMbVZHr +Xkao6Q8Ccqv2kAMwDQYJKoZIhvcNAQENBQADggIBACc2YjLVNbl1kJUdg2klCLJt +5LjNTiIZa2Cha5GStlC/lyoRRge6+q/y9TN3tTptlzLPS9pI9EE1GfIQaE+HAk+e +/bC3KUOAHgVuETvsNAbfpaVsPCdWpFuXmp/4b9iDN7qZy4afTKUPA/Ir/cLfNp14 +JULfP4z2yFOsCQZ5viNFAs1u99FrwobV2LBzUSIJQewsksuOwj96zIyau0Y629oJ +k+og88Tifd9EH3MVZNGhdpojQDDdwHQSITnCDgfRP5yER1WIA4jg6l+mM90QkvLY +5NjWTna5kJ3X6UizvgCk365yzT2sbN3R9UGXfCJa9GBcnnviJtJF3+/gC0abwY2f +NtVYp32Xky45NY/NdRhDg0bjHP3psxmX+Sc0M9NuQcDQ+fUR+CzM0IGeiszkzXOs +RG+bOou2cZ81G4oxWdAALHIRrn7VvLGlkFMxiIZyhYcTGQZzsTPT6n18dY99+DAV +yQWZfIRdm8DOnt0G+cwfeohc/9ZwDmj4jJAAi0aeTXdY6NEGIVydk6MAycEhg2Hx +9EV96kRwZNIW0AGY8CozECFL3Eyo2ClQVV4Q35SsBibsitDjM03usc2DJ/qjynXA +C8HoOSWgbddiBvqZueqK8GdhykOy3J3ysr+MNN/lbG48LqkQr1OWxev9rGGQ6RJT +wpBgPyAFAwouPy1whmnx +-----END CERTIFICATE----- diff --git a/contrib/certificates/reseed/echelon3_at_mail.i2p.crt b/contrib/certificates/reseed/echelon3_at_mail.i2p.crt deleted file mode 100644 index 7560a01e..00000000 --- a/contrib/certificates/reseed/echelon3_at_mail.i2p.crt +++ /dev/null @@ -1,33 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIFyzCCA7OgAwIBAgIRALWNWsnQ0Vmn/99iCNT7cdQwDQYJKoZIhvcNAQELBQAw -cTELMAkGA1UEBhMCWFgxCzAJBgNVBAcTAlhYMQswCQYDVQQJEwJYWDEeMBwGA1UE -ChMVSTJQIEFub255bW91cyBOZXR3b3JrMQwwCgYDVQQLEwNJMlAxGjAYBgNVBAMM -EWVjaGVsb24zQG1haWwuaTJwMB4XDTIxMTEyOTE5MzU1OVoXDTMxMTEyOTE5MzU1 -OVowcTELMAkGA1UEBhMCWFgxCzAJBgNVBAcTAlhYMQswCQYDVQQJEwJYWDEeMBwG -A1UEChMVSTJQIEFub255bW91cyBOZXR3b3JrMQwwCgYDVQQLEwNJMlAxGjAYBgNV -BAMMEWVjaGVsb24zQG1haWwuaTJwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC -CgKCAgEA3pccNiQWJUS1t3QHK7rBCNKAsM2dz4szN3+3SrDy1w+rOrK8Vt5aypPU -QYUQwG+odjEPacuoRtO/W14KJl5yAI3eQS+X/cYDXmxvfm4zx5JRumYptXwJD57G -rlPHnFvk8R+Hvh+/UyqgSAZ9ZaKjEzYK4AtbYEXtopaM4U2VYN8xKjvKyWlhPdxo -kI3//qcTlSqGHHeHrkItLG1LubM1EnPu+9zI2WN2zBBRcm8ZtWqHoqFJ1zgJr/49 -nMK8Lnb3I54ctva8x5+gsSk4dbG/mMsOIZekFqYJJs3+u9w5fmOYI7v9GlQr7UhE -G3MwjJ5Cj1LmLVlz/4LApZrDSd2JvwIUdGL3UW8+blaTeCPKIRvmsTeRxo1gORMF -ZH0dg39722lK7ScwOlOUX9ggzRUlYCmvnjQJZGJEUoP68QxjlQfkXZyffmMfvm6K -V6mcZ5aHMGO1lYAl40kWNJ0jGpmxJqTDhNFDEKr0TlRGVxXGWzObEOrcJ8ysRMc1 -x6oXQhh79HXZcKwhZaXLx23ZvVoTfhRm4JH0SSP6XqQm35j4NI1SllEsDns29wU3 -Re4wOWJCCYlPG3CtY32CinwQRoVgtiJk18W8+Pxw7sBFq8sL5L0Z+5bB6nTkBfV6 -7OrZGWL0i344zQE0e3yIsLih+5Wyqw6RSSMysenl3alnUB9EvE0CAwEAAaNeMFww -DgYDVR0PAQH/BAQDAgKEMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAP -BgNVHRMBAf8EBTADAQH/MBoGA1UdDgQTBBFlY2hlbG9uM0BtYWlsLmkycDANBgkq -hkiG9w0BAQsFAAOCAgEAaUMnMYtNFBl9dFON6e4EjYo53Wknj61uIVO11dvLqjnh -7X6guPML+GgNZsPQGLu7Bqw4hVgy/cV5AlFc7SXOhzpaYo1ycpjg3Ws1VK2wrk7+ -4bvUThNcS1KZVFDdRE62549rYNfYNfPxXvccOTW9meTCC1kLHerh65ySDr9J02O6 -o5Mf685PgBasBH6dlosOLTtee2gRLNFcAluQYKerawS1gDys5239UNHPCqTgO+Od -FiKfl48OIOzPGLKEf4lXC+lkwZElewShrHhzd8aGueedTi0UHOtQuY7ocsofqXc8 -OnyT/y2X6wn/YkzviKgfxYDSI7FJiUgXCPcT0jUNmuwR168yL5BfzoQmrCvlOOQg -P7ibdBJ6UkL8pRpv/SYpvaX/kf4agYtwh5IL9FzNCwNu54ZC6JilLUhYAU38Eolq -OZ/cGiMoSFQIeBPvB3cdsqEud9W4P+MqN5A76fMzdVV77lGsIS1eCGMceR3CjOiF -6SdAskcBZWhFiRNQweC0iv57/nPCeTCuNAqbZSHd7zC1AKhNmmsKSJUJQCGijcce -P8Gl0AFfZneN2bVEFvJ/zd71pD8ll1Gkju16bfdWn0V4NRaxFiXNr2bL+ah9blud -EXOomE3R6ow1QZk+Gnpy3wh9jfwlrJuFoANvHnv4WREbdjwr//71XjBri5p1wPE= ------END CERTIFICATE----- diff --git a/contrib/certificates/reseed/echelon_at_mail.i2p.crt b/contrib/certificates/reseed/echelon_at_mail.i2p.crt new file mode 100644 index 00000000..ad10e3ac --- /dev/null +++ b/contrib/certificates/reseed/echelon_at_mail.i2p.crt @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFfzCCA2egAwIBAgIESg3kkzANBgkqhkiG9w0BAQ0FADBwMQswCQYDVQQGEwJY +WDELMAkGA1UECBMCWFgxCzAJBgNVBAcTAlhYMR4wHAYDVQQKExVJMlAgQW5vbnlt +b3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEZMBcGA1UEAwwQZWNoZWxvbkBtYWls +LmkycDAeFw0xNDA3MzExNjQ3MDJaFw0yNDA3MzAxNjQ3MDJaMHAxCzAJBgNVBAYT +AlhYMQswCQYDVQQIEwJYWDELMAkGA1UEBxMCWFgxHjAcBgNVBAoTFUkyUCBBbm9u +eW1vdXMgTmV0d29yazEMMAoGA1UECxMDSTJQMRkwFwYDVQQDDBBlY2hlbG9uQG1h +aWwuaTJwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAmcEgLwwhzLNe +XLOMSrhwB8hWpOhfjo4s6S/wjBtjjUc8nI3D0hSn3HY26p0rvcvNEWexPUpPULmC +exGkU463nu7PiFONiORI1eJAiUFHibRiaA7Wboyo38pO73KirwjG07Y+Ua0jp+HS ++4FQ/I/9H/bPplReTOU/6hmRbgQ69U8nE68HzZHQxP68yVJ2rPHSXMPhF4R1h0G1 +1mCAT+TgTsnwHNGF77XHJnY4/M4e2cgycEZjZow36C3t2mNDVkMgF19QQeb9WmLR +zREn3nq9BJqHpUkn9yWw0kKXTZSds+7UxESfzf3BzK0+hky2fh5H+qbYAo2lz4yj +81MXTAu+4RRkg4DBLlF+2dkclhwQLxxzvkRC6tPkn5i33Yltg7EfzA9IoQ05potJ +I+iOcF+aStfFgFj9u3B5UkcF4P0cH1QD3c6BK4hIezQYqRoPly1gHqg+XdwjG/dr +4as7HA9FTz3p2E8nClpIC1x3hfgwAdfd29aeBxO1WW/z99iMF7TBAF+u5T86XEW1 +WpknqCbTli36yJ8a5fPWxZHrryBRJT5yLxejjFeadtutBSwljiVFq+Y38VqwFivq +VLiBt7IxAsZ8iilgfnnnAvBH6chWfSKb4H7kB4TJvDiV96QmmvoEaWYNHZozMhyK +tO3b5w+xqbJXyCLA3Q75jD0km76hjcECAwEAAaMhMB8wHQYDVR0OBBYEFAHQcAam +QRS/EUhuCSr9pB4Ux0rYMA0GCSqGSIb3DQEBDQUAA4ICAQBq1+1QLmgLAjrTg3tb +4XKgAVICQRoBDNUEobQg3pYeUX9eFNya2RxNljuvYpwT80ilGMPOXcjddmr5ngiK +dbGRcuuJk9MPEHtPaPT3+JJlvKQ3B3g2wva2Wz2OAyLZUGQs389K4nTbwh4QF0n2 +aHFL8BHiD62hiKnCoNaW4ZovUNNvOxo9lMyAiaFU2gqQNcdad8hP9EAllbvbxDx9 +Tjww2UbwQUIHS9rna4Tlu+f0hDXTWIutc2A51W2fJCb7L3+lYO7Wv55ND/WtryLZ +XpMp27+MpuEnN3kQmz/l9R0hIJsWc/x9GQkjm5wEaIZEyTtenqwRKGmVCtAj0Pgv +jn1L3/lWmrNq+OZHb/QeyfKtA3nXfQKVmT98ewQiK/S5i1xIAXCJPytOD887b/o1 +cdurTmCiZMwgiQ+HLJqCg3MDa5mvKqRkRdZXfE6aQWEcSbpAhpV15R17q7L+Fg0W +shLSNucxyGNU8PjiC/nOmqfqUiPiMltJjPmscxBLim8foyxjakC4+6N6m+Jzgznj +PocBehFAfKYj66XEwzIBN7Z2uuXoYH9YptkocFjTzvchcryVulDWZ4FWxreUMhpM +4oyjjhSB4tB9clXlwMqg577q3D6Ms0zLTqsztyPN3zr6jGev3jpVq7Q1GOlciHPv +JNJOWTH/Vas1W6XlwGcOOAARTQ== +-----END CERTIFICATE----- diff --git a/contrib/certificates/reseed/hankhill19580_at_gmail.com.crt b/contrib/certificates/reseed/hankhill19580_at_gmail.com.crt deleted file mode 100644 index 2f177ee1..00000000 --- a/contrib/certificates/reseed/hankhill19580_at_gmail.com.crt +++ /dev/null @@ -1,34 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIF3TCCA8WgAwIBAgIRAKye34BRrKyQN6kMVPHddykwDQYJKoZIhvcNAQELBQAw -dzELMAkGA1UEBhMCWFgxCzAJBgNVBAcTAlhYMQswCQYDVQQJEwJYWDEeMBwGA1UE -ChMVSTJQIEFub255bW91cyBOZXR3b3JrMQwwCgYDVQQLEwNJMlAxIDAeBgNVBAMM -F2hhbmtoaWxsMTk1ODBAZ21haWwuY29tMB4XDTIwMDUwNzA1MDkxMFoXDTMwMDUw -NzA1MDkxMFowdzELMAkGA1UEBhMCWFgxCzAJBgNVBAcTAlhYMQswCQYDVQQJEwJY -WDEeMBwGA1UEChMVSTJQIEFub255bW91cyBOZXR3b3JrMQwwCgYDVQQLEwNJMlAx -IDAeBgNVBAMMF2hhbmtoaWxsMTk1ODBAZ21haWwuY29tMIICIjANBgkqhkiG9w0B -AQEFAAOCAg8AMIICCgKCAgEA5Vt7c0SeUdVkcXXEYe3M9LmCTUyiCv/PHF2Puys6 -8luLH8lO0U/pQ4j703kFKK7s4rV65jVpGNncjHWbfSCNevvs6VcbAFoo7oJX7Yjt -5+Z4oU1g7JG86feTwU6pzfFjAs0RO2lNq2L8AyLYKWOnPsVrmuGYl2c6N5WDzTxA -Et66IudfGsppTv7oZkgX6VNUMioV8tCjBTLaPCkSfyYKBX7r6ByHY86PflhFgYES -zIB92Ma75YFtCB0ktCM+o6d7wmnt10Iy4I6craZ+z7szCDRF73jhf3Vk7vGzb2cN -aCfr2riwlRJBaKrLJP5m0dGf5RdhviMgxc6JAgkN7Ius5lkxO/p3OSy5co0DrMJ7 -lvwdZ2hu0dnO75unTt6ImR4RQ90Sqj7MUdorKR/8FcYEo+twBV8cV3s9kjuO5jxV -g976Q+GD3zDoixiege3W5UT4ff/Anm4mJpE5PKbNuO+KUjk6WA4B1PeudkEcxkO4 -tQYy0aBzfjeyENee9otd4TgN1epY4wlHIORCa3HUFmFZd9VZMQcxwv7c47wl2kc9 -Cv1L6Nae78wRzRu2CHD8zWhq+tv5q7Md2eRd3mFPI09ljsOgG2TQv6300WvHvI5M -enNdjYjLqOTRCzUJ2Jst4BZsvDxjWYkHsSZc1UORzm2LQmh2bJvbhC3m81qANGw6 -ZhcCAwEAAaNkMGIwDgYDVR0PAQH/BAQDAgKEMB0GA1UdJQQWMBQGCCsGAQUFBwMC -BggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCAGA1UdDgQZBBdoYW5raGlsbDE5 -NTgwQGdtYWlsLmNvbTANBgkqhkiG9w0BAQsFAAOCAgEAVtMF7lrgkDLTNXlavI7h -HJqFxFHjmxPk3iu2Qrgwk302Gowqg5NjVVamT20cXeuJaUa6maTTHzDyyCai3+3e -roaosGxZQRpRf5/RBz2yhdEPLZBV9IqxGgIxvCWNqNIYB1SNk00rwC4q5heW1me0 -EsOK4Mw5IbS2jUjbi9E5th781QDj91elwltghxwtDvpE2vzAJwmxwwBhjySGsKfq -w8SBZOxN+Ih5/IIpDnYGNoN1LSkJnBVGSkjY6OpstuJRIPYWl5zX5tJtYdaxiD+8 -qNbFHBIZ5WrktMopJ3QJJxHdERyK6BFYYSzX/a1gO7woOFCkx8qMCsVzfcE/z1pp -JxJvshT32hnrKZ6MbZMd9JpTFclQ62RV5tNs3FPP3sbDsFtKBUtj87SW7XsimHbZ -OrWlPacSnQDbOoV5TfDDCqWi4PW2EqzDsDcg+Lc8EnBRIquWcAox2+4zmcQI29wO -C1TUpMT5o/wGyL/i9pf6GuTbH0D+aYukULropgSrK57EALbuvqnN3vh5l2QlX/rM -+7lCKsGCNLiJFXb0m6l/B9CC1947XVEbpMEAC/80Shwxl/UB+mKFpJxcNLFtPXzv -FYv2ixarBPbJx/FclOO8G91QC4ZhAKbsVZn5HPMSgtZe+xWM1r0/UJVChsMTafpd -CCOJyu3XtyzFf+tAeixOnuQ= ------END CERTIFICATE----- diff --git a/contrib/certificates/reseed/i2p-reseed_at_mk16.de.crt b/contrib/certificates/reseed/i2p-reseed_at_mk16.de.crt deleted file mode 100644 index 3d1c4526..00000000 --- a/contrib/certificates/reseed/i2p-reseed_at_mk16.de.crt +++ /dev/null @@ -1,34 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIFzTCCA7WgAwIBAgIQeUqFi0fHNQopg6BZlBLhVzANBgkqhkiG9w0BAQsFADBy -MQswCQYDVQQGEwJYWDELMAkGA1UEBxMCWFgxCzAJBgNVBAkTAlhYMR4wHAYDVQQK -ExVJMlAgQW5vbnltb3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEbMBkGA1UEAwwS -aTJwLXJlc2VlZEBtazE2LmRlMB4XDTIyMDIwNTE3MzkzM1oXDTMyMDIwNTE3Mzkz -M1owcjELMAkGA1UEBhMCWFgxCzAJBgNVBAcTAlhYMQswCQYDVQQJEwJYWDEeMBwG -A1UEChMVSTJQIEFub255bW91cyBOZXR3b3JrMQwwCgYDVQQLEwNJMlAxGzAZBgNV -BAMMEmkycC1yZXNlZWRAbWsxNi5kZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC -AgoCggIBAMYxs2D2xpN/8blGawvAlU9DemHIxApOEwaLNfh8aAvqEdB41NTqcx4U -H8VchSormCfkCvezuMHO+K2HX7ihEZ1v6tbr6aX6hY9UZUyDDYsKmJoB1oKEhddv -5UYfcWPE2eSykdFsWgTQD6Z+cRQWHEoCzb7qc+Jrw6KcnHMD0VrmBrEQPzTBxMHW -4HC97PVkSLJTDArnS6ZiX4IbWRPw/mbpJT6EoVZo8J/it0pdn/X4KodEXDcnEMSe -VRulfZH/nSmOOvKhoHPckmgz/u66BlnuSYXEIB0KfDIcAlSYiPDxGnAemTozJYXA -UVMeFMs+YE5wiPgzzu+vpC31xtZLq0gyaCfgEi1P9j2ES/8pH3Gw6W2OH4kBx+jO -TBsfI+ph6qFZ3WWT23MRVyl3ATuI/GHdczTxD9JaOn74lLI+Hnu8wXnyztVWkTMB -4sAnzjdeHkvNDyQ10vSaN0HnGfg6zuAuUSqFQujFF8Vg8ZCcsh8GouWfzYDvi9mj -9pfxx8v6UCC719I4J9CgFjWnn2Hqez3fO8fFulY61VPyCCZp4gKWbI2SIQP/n5gz -ecYJRrJoem+rYfEQ/fwxROsvm3fCO4D6dt7ILRuX286GDIw2qSvP1zZVAioMwSj3 -9CAjKLwD/BhTRiMOlpaVv6IWqjtevbiaIKvbHTnoxvkGsDqe3gJhAgMBAAGjXzBd -MA4GA1UdDwEB/wQEAwIChDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEw -DwYDVR0TAQH/BAUwAwEB/zAbBgNVHQ4EFAQSaTJwLXJlc2VlZEBtazE2LmRlMA0G -CSqGSIb3DQEBCwUAA4ICAQAb+x6XpJdjpVYw2bvWIUbatQJwq0YaEW5W61xGLgIG -a37oll3YZbSY9Vk+N1cE0f61L3ya4Ioz6zlH/MO2zUG/dEk8vqdgIPUYJvyF7wwF -w3/G4VMaDKOJx4bAZNmaiRFGYNhCOhCnZx6uZGrLNIJ2Dc+mflrGmGwYphtXVV3e -Iv+ki3gSRgfXuMfKi4B5bLPnz7XDe4TSmwZZSRac4ly4KqmZUyntqbilRxaGTej3 -VYJ1tac8yppyk5N3VopMQNmBarNZG16wSOTD7CtKgn382jgRW8cR7BMeqhORivp0 -ZnPJFhzh4uthdlPdXXo6lxfvZjfiwlDPytvEu2QBz3urTgopGqRLcTBnLucWg9li -OSy9z7hNEnIN3iIJJAwI1wBdDa7K0h3PFBbIUa7X2ybn81VeNSfO25Lo8YTZEKsc -wcThJrNV6qOQv8rM/7aXugi6+VzPlCR+18iKRbebCnlqGR2dT1zFtj3negtOkrjo -LH4H6VUr3q2Ie56IubS2hUKiUkDm0ckP3Vum35GGntyEAzl6uyog0hJFOJb3aq30 -YQLzyVEOz8NnA+32oMRzJJdDxQ7pqG5fgq7EF4d++YSgEfdVXxvfgXQ6m3jAyC7Z -p/gX4rlxNsjeGU3Ds51wkmhH4IB1aSQr52PE6RaBhhh3SmADEv6S/3eGvE4F4MN5 -2Q== ------END CERTIFICATE----- diff --git a/contrib/certificates/reseed/igor_at_novg.net.crt b/contrib/certificates/reseed/igor_at_novg.net.crt index c6a05e8b..12ce7a61 100644 --- a/contrib/certificates/reseed/igor_at_novg.net.crt +++ b/contrib/certificates/reseed/igor_at_novg.net.crt @@ -1,33 +1,33 @@ -----BEGIN CERTIFICATE----- -MIIFvjCCA6agAwIBAgIQBnsUOmOu2oZZIwHBmQc1BDANBgkqhkiG9w0BAQsFADBt +MIIFvjCCA6agAwIBAgIQIDtv8tGMh0FyB2w5XjfZxTANBgkqhkiG9w0BAQsFADBt MQswCQYDVQQGEwJYWDELMAkGA1UEBxMCWFgxCzAJBgNVBAkTAlhYMR4wHAYDVQQK ExVJMlAgQW5vbnltb3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEWMBQGA1UEAwwN -aWdvckBub3ZnLm5ldDAeFw0yMzAxMjgxNDM4MzFaFw0zMzAxMjgxNDM4MzFaMG0x +aWdvckBub3ZnLm5ldDAeFw0xNzA3MjQxODI4NThaFw0yNzA3MjQxODI4NThaMG0x CzAJBgNVBAYTAlhYMQswCQYDVQQHEwJYWDELMAkGA1UECRMCWFgxHjAcBgNVBAoT FUkyUCBBbm9ueW1vdXMgTmV0d29yazEMMAoGA1UECxMDSTJQMRYwFAYDVQQDDA1p -Z29yQG5vdmcubmV0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvLkf -bM3uiYfp9m0vgdoftyXtk2/9bHf3u5iaM0WfoJIsw1iizo/mxJl+Iy7SxLC16nV0 -v5FpncVv+Z8x9dgoAYVuLq9zKfsAbpj6kuxAqw6vJMlD1TiIL3nSODV9BJLk47X5 -tmvoOSj9BgvemYThTE3nj+DbuJRW5q90KyBV/LdLrQJX3k5R3FFL5tTad2LKFNZ4 -vEOcYwwx6mvrkJ2lly6bAQUCtfc648Jyq+NO3Rba1fmn7gcP9zXXc5KYsj/ovyY2 -OaocSF5wMhzBuPxO+M2HqbYLMAkc6/GesGds8Rm8wofuhJoI5YtqJuLKZm6nQXSc -fx6PKgbKcTIUWNFMsxyfghz9hpbg0rkvC7PtfAjtV0yaDtUum1eZeNEx1HbRWN2n -TQNCVuv0yaKC41qxqzhEybkdjL9JlgUh7VuskaCelB0lz+kgYjGu8ezOa0ua2iKq -4FC/1MbPulxN8NOt4pmbGqqoxmCdShp38wdnOBM3DsAS9f0JaQZd4CDyY4DCSfVn -xPdWk31+VXVt3Ixh1EUqZWYTRSsZApkCyYzkiZ/qPGG6FR9Hq2SuhC5o4P44k7eo -6wwBWD8a5RjsZhvr05E5yBrKXh/PjLwmtG73QC+ouR54/5xtedvdTwNS94FnNctX -FT6QGZnRwCkhPaRe1oQMzP+88pGoCfO33GBAuwUCAwEAAaNaMFgwDgYDVR0PAQH/ +Z29yQG5vdmcubmV0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxst4 +cam3YibBtQHGPCPX13uRQti56U3XZytSZntaKrUFmJxjt41Q/mOy3KYo+lBvhfDF +x3tWKjgP9LJOJ28zvddFhZVNxqZRjcnAoPuSOVCw88g01D9OAasKF11hCfdxZP6h +vGm8WCnjD8KPcYFxJC4HJUiFeProAwuTzEAESTRk4CAQe3Ie91JspuqoLUc5Qxlm +w5QpjnjfZY4kaVHmZDKGIZDgNIt5v85bu4pWwZ6O+o90xQqjxvjyz/xccIec3sHw +MHJ8h8ZKMokCKEJTaRWBvdeNXki7nf3gUy/3GjYQlzo0Nxk/Hw4svPcA+eL0AYiy +Jn83bIB5VToW2zYUdV4u3qHeAhEg8Y7HI0kKcSUGm9AQXzbzP8YCHxi0sbb0GAJy +f1Xf3XzoPfT64giD8ReUHhwKpyMB6uvG/NfWSZAzeAO/NT7DAwXpKIVQdkVdqy8b +mvHvjf9/kWKOirA2Nygf3r79Vbg2mqbYC/b63XI9hheU689+O7qyhTEhNz+11X0d +Zax7UPrLrwOeB9TNfEnztsmrHNdv2n+KcOO2o11Wvz2nHP9g+dgwoZSD1ZEpFzWP +0sD5knKLwAL/64qLlAQ1feqW7hMr80IADcKjLSODkIDIIGm0ksXqEzTjz1JzbRDq +jUjq7EAlkw3G69rv1gHxIntllJRQidAqecyWHOMCAwEAAaNaMFgwDgYDVR0PAQH/ BAQDAgKEMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8E BTADAQH/MBYGA1UdDgQPBA1pZ29yQG5vdmcubmV0MA0GCSqGSIb3DQEBCwUAA4IC -AQCteAb5/bqhHr/i5CJbDzlofprXFC826c19GxQ/9Hw0kA52l0J9Q8Vz8Vy7VQyP -QNa8MCv6FeNy8a/wXp6cafyFsBtvehVQO8lFlpCgMEl2Bma43+GaCwkrM6bFNXeW -iQ9h4e1KjsUZ8cQDNEcamiJ80+xbMhBrj5bAZwKmZs8MoGEMyXKEZmcmwA+/fy1c -cx4izsOsmRXmEHXsvB9ydJHZZeKW8+r0DAtgPslwXuXHG6MuBQo7dKCqn+iMxHXV -Jxriq3yvNffdGx4maSLJrjQ1ealt/UMzql7huVSItnVFWoYf7GAELXNJ/PmqVyaK -q11LQ8W/Aud6s/bblaJrFJnK8PbPpaw4RvHoWVLYaZYmQnV2msWs5EuESBlEADbv -UklQXLMc2f9HKWPA5678nvYPrmu8IL5pMkAxgGRqmd+7vCz4lU9M5z3HObU+WRBt -qEMYyXywV8o3tbmnlDS5S5Xxf+tLZn1cxz3ZrmcHPHDbLBNdvszF3CTJH/R2sQvD -bizvYJM+p5F+GWM5mt6w0HrOut5MRlpOws/NRrkbijuVA/A45nzTtKplIFYE3qe8 -q5SAbwYLc8cJcZCN3PxtWwbEv81V33abMt5QcjnWGLH5t2+1Z2KLCgKLSCQTxM8s -zBPHtUe8qtSQaElnNLILYbtJ1w67dPnGYTphHihC+CXjBg== +AQADyPaec28qc1HQtAV5dscJr47k92RTfvan+GEgIwyQDHZQm38eyTb05xipQCdk +5ruUDFXLB5qXXFJKUbQM6IpaktmWDJqk4Zn+1nGbtFEbKgrF55pd63+NQer5QW9o +3+dGj0eZJa3HX5EBkd2r7j2LFuB6uxv3r/xiTeHaaflCnsmyDLfb7axvYhyEzHQS +AUi1bR+ln+dXewdtuojqc1+YmVGDgzWZK2T0oOz2E21CpZUDiP3wv9QfMaotLEal +zECnbhS++q889inN3GB4kIoN6WpPpeYtTV+/r7FLv9+KUOV1s2z6mxIqC5wBFhZs +0Sr1kVo8hB/EW/YYhDp99LoAOjIO6nn1h+qttfzBYr6C16j+8lGK2A12REJ4LiUQ +cQI/0zTjt2C8Ns6ueNzMLQN1Mvmlg1Z8wIB7Az7jsIbY2zFJ0M5qR5VJveTj33K4 +4WSbC/zMWOBYHTVBvGmc6JGhu5ZUTZ+mWP7QfimGu+tdhvtrybFjE9ROIE/4yFr6 +GkxEyt0UY87TeKXJ/3KygvkMwdvqGWiZhItb807iy99+cySujtbGfF2ZXYGjBXVW +dJOVRbyGQkHh6lrWHQM4ntBv4x+5QA+OAan5PBF3tcDx1vefPx+asYslbOXpzII5 +qhvoQxuRs6j5jsVFG6RdsKNeQAt87Mb2u2zK2ZakMdyD1w== -----END CERTIFICATE----- diff --git a/contrib/certificates/reseed/meeh_at_mail.i2p.crt b/contrib/certificates/reseed/meeh_at_mail.i2p.crt new file mode 100644 index 00000000..6014c96f --- /dev/null +++ b/contrib/certificates/reseed/meeh_at_mail.i2p.crt @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFeTCCA2GgAwIBAgIEZZozujANBgkqhkiG9w0BAQ0FADBtMQswCQYDVQQGEwJY +WDELMAkGA1UECBMCWFgxCzAJBgNVBAcTAlhYMR4wHAYDVQQKExVJMlAgQW5vbnlt +b3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEWMBQGA1UEAwwNbWVlaEBtYWlsLmky +cDAeFw0xNDA2MjgyMjQ5MDlaFw0yNDA2MjcyMjQ5MDlaMG0xCzAJBgNVBAYTAlhY +MQswCQYDVQQIEwJYWDELMAkGA1UEBxMCWFgxHjAcBgNVBAoTFUkyUCBBbm9ueW1v +dXMgTmV0d29yazEMMAoGA1UECxMDSTJQMRYwFAYDVQQDDA1tZWVoQG1haWwuaTJw +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAnVnmPE4uUvCky0yCnnVH +cJEDqzwDPupx0zr0YDlhZk5VOPPecx5haayJ/V6nXPc1aVVWn+CHfedcF2aBgN4K +5aBueS/l6l5WHcv02DofAqlTmyAws3oQeR1qoTuW24cKRtLR7h5bxv63f6bgp6e+ +RihFNez6UxErnRPuJOJEO2Im6EgVp6fz7tQ7R35zxAUeES2YILPySvzy2vYm/EEG +jXX7Ap2A5svVo90xCMOeUZ/55vLsjyIshN+tV87U4xwvAkUmwsmWVHm3BQpHkI6z +zMJie6epB8Bqm0GYm0EcElJH4OCxGTvDLoghpswbuUO7iy3JSfoL7ZCnoiQdK9K4 +yVVChj8lG+r7KaTowK96iZep+sZefjOt5VFGuW2Fi/WBv3ldiLlJAo/ZfrUM4+vG +fyNBXbl6bX87uTCGOT1p3dazo+zJMsAZ+Y93DlM/mDEWFa1kKNrs74syzaWEqF4L +KQE6VoYn80OOzafSigTVQgSwUtQtB0XGhMzJhyxU2XHWe1LFIy7Pta0B+lDiZj7c +I8nXxYjsDfEu/Elj/Ra9N6bH0awmgB5JDa+Tbir+oEM5SyDfpSaCGuatdGxjweGI +kVmFU0SqCZV/8TXbIu6MUVzTZMZVT94edifFSRad4fqw7eZbSXlPu++3d1/btn6h +ibM04nkv0mm+FxCKB/wdAkECAwEAAaMhMB8wHQYDVR0OBBYEFO7jIkSRkoXyJcho +9/Q0gDOINa5EMA0GCSqGSIb3DQEBDQUAA4ICAQBzfWO7+8HWOKLaYWToJ6XZbpNF +3wXv1yC4W/HRR80m4JSsq9r0d7838Nvd7vLVP6MY6MaVb/JnV76FdQ5WQ6ticD0Y +o3zmpqqbKVSspN0lrkig4surT88AjfVQz/vEIzKNQEbpzc3hC2LCiE2u+cK/ix4j +b9RohnaPvwLnew5RNQRpcmk+XejaNITISr2yQIwXL7TEYy8HdGCfzFSSFhKe9vkb +GsWS5ASrUzRoprswmlgRe8gEHI+d51Z7mWgna0/5mBz9bH/3QXtpxlLWm3bVV+kt +pZjQDTHE0GqG2YsD1Gmp4LU/JFhCojMTtiPCXmr9KFtpiVlx06DuKm5PC8Ak+5w+ +m/DQYYfv9z+AA5Y430bjnzwg67bhqVyyek4wcDQinFswv3h4bIB7CJujDcEqXXza +lhG1ufPPCUTMrVjh7AShohZraqlSlyQPY9vEppLwD4W1d+MqDHM7ljOH7gQYaUPi +wE30AdXEOxLZcT3aRKxkKf2esNofSuUC/+NXQvPjpuI4UJKO3eegi+M9dbnKoNWs +MPPLPpycecWPheFYM5K6Ao63cjlUY2wYwCfDTFgjA5q8i/Rp7i6Z6fLE3YWJ4VdR +WOFB7hlluQ//jMW6M1qz6IYXmlUjcXl81VEvlOH/QBNrPvX3I3SYXYgVRnVGUudB +o3eNsanvTU+TIFBh2Q== +-----END CERTIFICATE----- diff --git a/contrib/certificates/reseed/orignal_at_mail.i2p.crt b/contrib/certificates/reseed/orignal_at_mail.i2p.crt deleted file mode 100644 index 799b601b..00000000 --- a/contrib/certificates/reseed/orignal_at_mail.i2p.crt +++ /dev/null @@ -1,32 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIFfzCCA2egAwIBAgIEbNbRPjANBgkqhkiG9w0BAQ0FADBwMQswCQYDVQQGEwJY -WDELMAkGA1UECAwCWFgxCzAJBgNVBAcMAlhYMR4wHAYDVQQKDBVJMlAgQW5vbnlt -b3VzIE5ldHdvcmsxDDAKBgNVBAsMA0kyUDEZMBcGA1UEAwwQb3JpZ25hbEBtYWls -LmkycDAeFw0yMTA3MDYyMjExMDFaFw0zMTA3MDQyMjExMDFaMHAxCzAJBgNVBAYT -AlhYMQswCQYDVQQIDAJYWDELMAkGA1UEBwwCWFgxHjAcBgNVBAoMFUkyUCBBbm9u -eW1vdXMgTmV0d29yazEMMAoGA1UECwwDSTJQMRkwFwYDVQQDDBBvcmlnbmFsQG1h -aWwuaTJwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvNJz2KGuAkHP -tGFobfLvpybtxB50fkcEsTc9opmiy7wBKK9rSI01VS616IhABkWKZVfK2A9NqpGv -v/CyhTKoaeSNeXY7+zORUWgWK/zA9fA4GRZFqlW8j4tbompDwcLYNqRBCsn1C0OY -YA5JhXPBixMcnXl8N8x4sXhQ4l9R3+QrydhUHRvgDc8dOxRyIX7zuQAyf8tmA2Xo -xZLdvDcCJdLBIbFwxhIceIhgcOwaOx7oRkZDZdYcLJd3zjyPbu8JtOM2ZkwH7r+0 -ro5PktuDp2LAS6SII5yYNcwcrvPZGPqhLdifIw1BrdTIb/rIkQZ5iXOOdyPmT7e8 -IwAJcPFlfvrS4Vbi9oDqyx3aDUBoubgmFnO1TirL56ck83R/ubcKtdnyzAn5dp+f -ZNYW6/foSBpDDOCViylbFAR5H0HJEbBns7PZx6mGEEI4tUAJdNYl7Ly7Df60a9Rz -cD/gz08U9UwFXYKoT6roEjToADGAzb5MI4cVlAb2AmQaMNXNe04HcDL1bU50mkNU -amqPv8nxf72fBQCEmZz2G57T6QiYTtcCwiWS1QdWsuaOtCo9zO0MKcjzSdUxuxEc -dXhjQdNegsgg/Xk7bJ8lKOsACqMpFftdPmuyeZU2t+3RPuBpV/0j2qUfg/y6kb0z -CxAOYmlcL4kqw4VT+5V/EeZLIG0h9I0CAwEAAaMhMB8wHQYDVR0OBBYEFD/wJObg -CCDuhMJCVWTSTj+B3rsUMA0GCSqGSIb3DQEBDQUAA4ICAQC0PjsTSPWlGbLNeeI8 -F0B5xAwXYJzZ7/LRxh8u42HDUqVIDjqkuls1l3v9D7htty2Gr3Ws2dcvcOr2KcOy -mEWg+jdP/N3vt9IkZeVS4YQoPgq6orn7lVkk00bcKb24f7ZnoQnnVV0/m42Y5P4j -LLh+8MBxsez9azXyZbDVEkgsMUAkdVO6KNz6scqz7wb8egV2GAMAp7cwChC6lanK -gv9ZyJhG/HdTv6VyuMZhJy6rX4geM97tm1iHu1VLsQcIzBKAdEvWJv8ofMeiyINe -hqAP9NYaeowKi975NOrmf+XZwxd0niApIohV684RCVUfL8H7HSPbdXhBJ/WslyDP -cTGhA2BLqEXZBn/nLQknlnl0SZTQxG2n4fEgD1E5YS/aoBrig/uXtWm2Zdf8U3mM -+bNXhbi9s7LneN2ye8LlNJBSRklNn/bNo8OmzLII1RQwf1+vaHT96lASbTVepMZ/ -Y9VcC8fAmho/zfQEKueLEB03K+gr2dGD+1crmMtUBjWJ9vPjtooZArtkDbh+kVYA -cx4N4NXULRwxVWZe5wTQOqcZ3qSS1ClMwaziwychGaj8xRAirHMZnlPOZO1UK4+5 -8F4RMJktyZjNgSLP76XPS4rJK5fobuPqFeA4OpDFn/5+/XeQFF6i6wntx1tzztzH -zc+BrVZOdcYPqu9iLXyRQ9JwwA== ------END CERTIFICATE----- diff --git a/contrib/certificates/reseed/rambler_at_mail.i2p.crt b/contrib/certificates/reseed/rambler_at_mail.i2p.crt deleted file mode 100644 index 40a5c9fe..00000000 --- a/contrib/certificates/reseed/rambler_at_mail.i2p.crt +++ /dev/null @@ -1,33 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIFxzCCA6+gAwIBAgIQfKAV7rmoWA8jWpLfMtDQqzANBgkqhkiG9w0BAQsFADBw -MQswCQYDVQQGEwJYWDELMAkGA1UEBxMCWFgxCzAJBgNVBAkTAlhYMR4wHAYDVQQK -ExVJMlAgQW5vbnltb3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEZMBcGA1UEAwwQ -cmFtYmxlckBtYWlsLmkycDAeFw0yMTExMDYwNzEwMzJaFw0zMTExMDYwNzEwMzJa -MHAxCzAJBgNVBAYTAlhYMQswCQYDVQQHEwJYWDELMAkGA1UECRMCWFgxHjAcBgNV -BAoTFUkyUCBBbm9ueW1vdXMgTmV0d29yazEMMAoGA1UECxMDSTJQMRkwFwYDVQQD -DBByYW1ibGVyQG1haWwuaTJwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC -AgEAz4vQlIdjY56uqkFKWld9Oy3E8+06Ag9fUzBVleS2bdJfaFtmEa8xz6Pep7Bb -zJK0Q9t2CW7/xqIWuspWlYn5EYAS7BFiNOX70KX4PMpltj3C4Dpxpjll9LdydU2k -FquCflXNJESnBDdd0qDRMboMf4c9lTz0mTLwAtzInLwHGDrbxEiQ/YqPgPJreOXQ -anhjkpxJcgpLR+9od8EdLNKbShVWEeSBnYp0FcjnZKOb9KC2gjqP0sWdzlw3i1hh -CB38A7a03Q4yUcmxCw4ktM60d/2jCZ+G7KHwcbkfxDjl85r0UgEzgfF7LuIuxxmA -MNLH1eAACnLTl42O72EHdtD9VWWwZF2NuFgAzT3MEFnMKDk+OqZOeZQOEgkIfrNP -O5XYMYxHSWCf/dmSq36ZJwhC40k2S9ArS8BQNY8NvwZG5CSGDU52FKaHzFn6EwLE -4CpsrptUX2itXLaFUiNMw6I+eSgTO7x+gpahZVqpdRSQXmpE0xA5jP/DwPyt3ZVe -/4q4kn3imcSCxBP5NQHWfVszsruRkh9np4R0xVlT8UCwJmY8Yg8zwJG5UddTAck5 -JavDsaXgWMwcZ/qQboZKlH/iAdQnbkte8Yd5GL5nmTeS+vwuluwmA/y9kUzSUhk+ -86kA0eRJ1+e2HdA1/UOTRmyIoIeQ5/fhELMXzhksLcpMGTUCAwEAAaNdMFswDgYD -VR0PAQH/BAQDAgKEMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAPBgNV -HRMBAf8EBTADAQH/MBkGA1UdDgQSBBByYW1ibGVyQG1haWwuaTJwMA0GCSqGSIb3 -DQEBCwUAA4ICAQAxRdSTZGEblnNeVuRoEQq/a/6q4egFaOkzXCPKEnDzB5yvm83g -35ImquGFZkgaoc5qUAHVeBwOQrWgUI4xHPofnbM2VsgEUMz6h3ovobPNkN3+lRT5 -30krd0y+A/Q895EHDu0lyf3BHMmtCWiKWQBttuc0dnmoLCRsQxgy+kYJCS/81jCM -4KNnyrtc6a/czqSq758CncjP2nErVucendsguQoA5JUw53YJ4FYHG/f9tYEkhm9C -D6u7L3vTUcMRUrRxSiJyNixH36nEwpM6DNHiPNc+CFKZ/Zx449R1GjcpDhTrXnWP -2H1r3cyKEM8a76VUEs2GQCaaglOR4N1goyqgYEjScf+/4VmARL3VUzfP8Oub70rM -t1fip5QD/4VDQuA/9C9g5Rr2nJ3K2jVnpSSKnBYFYf5z9RZdTOVXjXaEi72lWxpk -mjgK6c5EFOJxYoCaTbKX9Kz9ZIWVOVMrgHWwA/wDW+Qk5zgP9Ysau65xIp9P1RdB -qHgR5BcIrNky9RD8cIzxzMPCSMVgnf0eLFuHmG8uUl/xHHVRprf0pd7DYkQ44HWN -Z/g/gg3DaJdH7vvkShzgjt4iZrmOCHQIKkSGFRYZf0/Mpn6mgK9+grtO9osVgAQr -LBO+5LIxV/S5bcrzWQLOiMABTd2X/0PTOjuXpfinZ3rDSUiNFPq5kLLSlA== ------END CERTIFICATE----- diff --git a/contrib/certificates/reseed/reseed_at_diva.exchange.crt b/contrib/certificates/reseed/reseed_at_diva.exchange.crt deleted file mode 100644 index 04b1524b..00000000 --- a/contrib/certificates/reseed/reseed_at_diva.exchange.crt +++ /dev/null @@ -1,34 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIF0zCCA7ugAwIBAgIQWjHyC+NRh3emuuAwcEnKSjANBgkqhkiG9w0BAQsFADB0 -MQswCQYDVQQGEwJYWDELMAkGA1UEBxMCWFgxCzAJBgNVBAkTAlhYMR4wHAYDVQQK -ExVJMlAgQW5vbnltb3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEdMBsGA1UEAwwU -cmVzZWVkQGRpdmEuZXhjaGFuZ2UwHhcNMjAwNjA5MDUzNjQ1WhcNMzAwNjA5MDUz -NjQ1WjB0MQswCQYDVQQGEwJYWDELMAkGA1UEBxMCWFgxCzAJBgNVBAkTAlhYMR4w -HAYDVQQKExVJMlAgQW5vbnltb3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEdMBsG -A1UEAwwUcmVzZWVkQGRpdmEuZXhjaGFuZ2UwggIiMA0GCSqGSIb3DQEBAQUAA4IC -DwAwggIKAoICAQC6BJGeMEgoXk9dlzKVfmwHrT2VpwTT+wRJvh3eAM746u4uDT2y -NPHXhdGcQ9dRRZ63T98IshWCwOmWSlm1kdWkmKkVVb93GUoMQ3gziCi0apLJMAau -gEu/sPCbORS2dPsQeAPW2eIsJO7dSjTRiQAuquW//NcIXG4gnxDA52lgke1BvpKr -83SJlCrqECAy6OKtZ49yn75CqmPPWFn0b/E8bxruN5ffeipTTospvdEtT41gXUqk -hOz3k8ang+QTWiP//jOjk31KXZ2dbh0LOlNJOvRxCqQmBZafNxxCR4DH8RewfPlL -qOiOJVzbLSP9RjqPLwnny5BOjbLWXcaybN5Qv2Pyd4mKtN3EpqBwRu7VnzXpsuuG -gRbxNmfKJ/vBEGrZAHAxi0NkHHEEne3B7pPDc2dVZHOfTfCu31m9uDHZ4eHEsNOJ -SJRiGjq74l0chCSlBGLrD1Y9LPyqadjdwuB9bzM0tMFC1wPflanQCflhhnEzAfbN -BaU2GRXo/I1UCDW/dH1FIkqEe61eMW1Lwqr5tdlrUpdr5VIddTyNJRBJogbZ+HZE -8mcoJW2lXRAkYi7KEm4b4EQNe7sbRNTF0j+fAJ+3ZOZ3O3SMHss6ignlSa+giVim -VvL+Joc6wpSzxpeNPf6m82cEO/UvifFYeOC9TpiRriSt+vvgQVzQtfQ+fQIDAQAB -o2EwXzAOBgNVHQ8BAf8EBAMCAoQwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUF -BwMBMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFHJlc2VlZEBkaXZhLmV4Y2hh -bmdlMA0GCSqGSIb3DQEBCwUAA4ICAQCFGOb1dHlwjmgFHEER6oMiGWl1mI3Hb7GX -NNI6QUhZQ+iEWGYtsOTk3Q8xejL8t6AG/ZLXfZviLIJXZc5XZfPXk0ezDSC2cYxQ -ZAyYPw2dRP14brI86sCSqNAFIax/U5SM3zXhCbBiTfaEoBPfDpvKjx+VliaITUnc -sHTRn+C5ID5M8cZIqUSGECPEMU/bDtuRNJLTKYaJ98yXtYuS2CWsMEM4o0GGcnYQ -5HOZT/lbbwfq1Ks7IyJpeIpRaS5qckGcfgkxFY4eGujDuaFeWC+HCIh9RzBJrqZR -73Aly4Pyu7Jjg8xCCf9MswDjtqAjEHgWCmRLWL7p3H6cPipFKNMY6yomYZl5urE7 -q6DUAZFKwPqlZpyeaY4/SVvaHTxuPp7484s3db4kPhdmuQS/DOB/7d+cn/S580Vy -ALqlFQjtjLEaT16upceAV0gYktDInE6Rtym/OsqilrtYks/Sc0GROSz8lJhDDWbr -W3t92muSXDh0rYrEUYWl+xl1gSTpbIP75zzU+cUr1E/qlRY9qZn66FsJpOuN0I0q -UXsQS/bPDcA+IW48Hd9LfO9gtTWZslwFTimjEvQ2nJAnUlUQP6OfuPUKHoYX/CwY -2LCN8+pv2bKPDVHvp0lf6xrbbZNvFtzfR0G3AprZjYpuu2XgjVB5nJnwmbH74b9w -LD8d2z2Lgg== ------END CERTIFICATE----- diff --git a/contrib/certificates/reseed/unixeno_at_cubicchaos.net.crt b/contrib/certificates/reseed/unixeno_at_cubicchaos.net.crt deleted file mode 100644 index c94d319e..00000000 --- a/contrib/certificates/reseed/unixeno_at_cubicchaos.net.crt +++ /dev/null @@ -1,34 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIF2TCCA8GgAwIBAgIQVpTNnJZlUTDqmZiHRU4wCjANBgkqhkiG9w0BAQsFADB2 -MQswCQYDVQQGEwJYWDELMAkGA1UEBxMCWFgxCzAJBgNVBAkTAlhYMR4wHAYDVQQK -ExVJMlAgQW5vbnltb3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEfMB0GA1UEAwwW -dW5peGVub0BjdWJpY2NoYW9zLm5ldDAeFw0yNTAzMDQxODU5NDZaFw0zNTAzMDQx -ODU5NDZaMHYxCzAJBgNVBAYTAlhYMQswCQYDVQQHEwJYWDELMAkGA1UECRMCWFgx -HjAcBgNVBAoTFUkyUCBBbm9ueW1vdXMgTmV0d29yazEMMAoGA1UECxMDSTJQMR8w -HQYDVQQDDBZ1bml4ZW5vQGN1YmljY2hhb3MubmV0MIICIjANBgkqhkiG9w0BAQEF -AAOCAg8AMIICCgKCAgEAr/JoAzLDtHXoAc7QcP4IxO+xNTeiYs78Wlg/Sl/sa6qz -gJoGaKH/X++z4Xe9lBSZalXCamnO4QMTgsWOIeoMy6XVbGzNTXPl8JUcblTIXwkP -pv848b1nxLfgLHzPRz1mJMpMikBugJ3Iz1sQzDVlUdye2fgbGChWliz9P4ClEODv -A/4+7i6uvJgEZ7A+jx3vBCXhiJppE3wTuz5D9BQqG8NuEwwjwBTBByoCC4oxOe0h -Qu1k7kEr+n4qpSEg/1eJ/RYSm+I8BftK1RUfykTwxlfmyEmKsfLBQWczE8Ca9nUB -5V34UH2bRy1cvavJYcNW3EPsGNf4naRs+Gy8XIFrb315GgWC1Z6+tzk+QFli9YeF -0DgtYEZciqu/407o8ZEURTnPjB7GhLDDp1LAQ7CQRhzaraXjHj0hyO+6rFpFdD0D -mXhvI/Eph3QIldsgnQc7nPhU2csN8Vi6bNDgm0HZ8cdmIBpI2Uxn/acZX/9G40oj -czrhsCBEecu/BluLJsfaWCYg90rvONP8Fc4edHAMonzYZR4r0q4hbv7AM8GmDRDN -J9/DZFi+Qs9NAe06jJC3jSsj7IdIs8TMhw8FX3xWOlXwjmVETAgY/dta/MpLJ6tJ -i+E+TH/Ndntj/D6WUwdQq+LyCR6gqHUWR6rl6EDQz+08DWb7j/72JSLb/DaXrDUC -AwEAAaNjMGEwDgYDVR0PAQH/BAQDAgKEMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggr -BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB8GA1UdDgQYBBZ1bml4ZW5vQGN1Ymlj -Y2hhb3MubmV0MA0GCSqGSIb3DQEBCwUAA4ICAQBBVoPeuOkmJDUdzIrzmxTmjMyz -gpfrZnjirTQKWhbv53sAWNeJ3kZ9l9m+0YpqEtFDrZPL5LTBXcSci5gGuxPkp+i/ -f/axsdcFMKbI9B/M53fyXLLJY0EM4mdhNAWtph1kTowFPhhReefCdqxYIy9uk2pL -gfb6NYJf+w9//fKYFZXb9SsiRchfv81+lbN+PIprnCpV3cTZWmpLRi2hN86pMW20 -3rh7rqJ4dPnA/NoyM+JstL10IU/4StqInweEvoo4W44+cC0zYGvfkkrKL4LB8w5S -6DKebtk7NQDtzuw2QnA9Ms4bmqWQpbL6/7uGaauS0+nmF+2gkqi9hcv0W5ZoBb/D -IVRauySnCtp9PbYM7pIJP9a1U6naLj7L1VixqsJGfPQ8V9TCOOi5bDc3RTetI/DX -bXHhAqHYzboakptylCp+Ao5h2hu0+w4rqnG63HwsHDJWcETbdVFQfzlzUmbx53yV -GnBsUxDgMOiHTZdKLkEnH4Q/XI76uc0ntTRlK9ktKWZPSISUlHrFnFl6I5UdeBMy -6vpB9sJO5L5RPRi4945K5Xdennywdi508mNXtMMmNCqrk1SMYbwaY6ZtIvXEGam9 -uHQTiTEX9LED/VXzFGqzdyDbG43HgS0PksgzedelHWfVAEnc06U3JX2lqUyihYHa -N4jAXWQ7s5p4GYaf4Q== ------END CERTIFICATE----- diff --git a/contrib/certificates/reseed/zmx_at_mail.i2p.crt b/contrib/certificates/reseed/zmx_at_mail.i2p.crt new file mode 100644 index 00000000..41f4cc75 --- /dev/null +++ b/contrib/certificates/reseed/zmx_at_mail.i2p.crt @@ -0,0 +1,34 @@ +-----BEGIN CERTIFICATE----- +MIIF1TCCA72gAwIBAgIRAJBHySZnvNg3lU00//fwny4wDQYJKoZIhvcNAQELBQAw +bDELMAkGA1UEBhMCWFgxHjAcBgNVBAoTFUkyUCBBbm9ueW1vdXMgTmV0d29yazEM +MAoGA1UECxMDSTJQMQswCQYDVQQHEwJYWDELMAkGA1UECRMCWFgxFTATBgNVBAMM +DHpteEBtYWlsLmkycDAeFw0xNjAxMDExNzE5MTlaFw0yNjAxMDExNzE5MTlaMGwx +CzAJBgNVBAYTAlhYMR4wHAYDVQQKExVJMlAgQW5vbnltb3VzIE5ldHdvcmsxDDAK +BgNVBAsTA0kyUDELMAkGA1UEBxMCWFgxCzAJBgNVBAkTAlhYMRUwEwYDVQQDDAx6 +bXhAbWFpbC5pMnAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCnDGVU +iC6pNJ3mfqZRQYACUbQ6SQI05yh3PawHqQrmiW3rD05SXBCF+6b2EpA4U0ThFhtm +cGyUObtBL749x03SUYcWhknZNq+zrvb9AypaKFpIx2DjFT8vQadn0l71cNaiwxX1 +Wzk1Au6mh9SFPvH5gDF9SQol7dYYKnn9L61V7hvH9fDiZyoi9Cz3ifE3SAWoM2PJ +lBzbu16tyQE94HvIdZhp8cE/6/kiW1wjSqvT9dfZ4gMuZHOF5E8lkq/bg8tPa/oj +rglY7ozT/9/IWtJ7ERcDyepmKjq7+Xx4sNXTvc+B7D4XfMjhaxFLtV/kLQ9mqx8R +UPvPy+atw7mlfUf822YFSft2jBAxNJwCPdhXuuFkTUTIk9YXcChUCSPyv17gej/P +A++/hdhYI/kIs8AVsaJjytTqwU3A2Pt1QogM8VLsSJ2NY7gSzj868nzIZ4OuoWbz +KzpnS/3bQkYHrqMtDIjRr1bOudxbu2/ben5v8Qg9wE9uV/8YNhhaKAcfJOV6OXfF +MYec9DOEVVvECOfYUX35Vtn/w7E6SSL7Gu6QEWviA4Bf2XBh1YFX0ZpBUMY9awNz +7PDf+z+YGkrQ6ifvLPW9vHW3lmouRWzo5NgJIIvLYBJKmxkf08p94s8YailjiGzA +dJWXg3HDWgwMe7BY7AJQbU/o35Vv+0CroUsR3wIDAQABo3IwcDAOBgNVHQ8BAf8E +BAMCAoQwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMA8GA1UdEwEB/wQF +MAMBAf8wFQYDVR0OBA4EDHpteEBtYWlsLmkycDAXBgNVHSMEEDAOgAx6bXhAbWFp +bC5pMnAwDQYJKoZIhvcNAQELBQADggIBAATXH/PNdF40DjD9DcF4W5Ot7CWGskDY +cR4ywtvU2EcDNEwv4q0FPEpxy5LPaUmTKQ6fsRXUZizjaPLpgCLbv9qYc5xRLrSi +yk9mrAbJ1iEU+DfHHBcS1VQWtc7+9LA0W3ZIA+pygjPjTxwQqQAcjn4BdfaIQpVa +VJ2kl5JtbTuYHL80GAQFYnzCCa5GKM7zgcLsyO1mQwnpDvFeSlKJJ6rx1QjhlJu+ +90Ig8IOBCIgokfUv9OdYBl6rmDq9i9pvqJU+H4VepqE1jnDAO+YqQ4laZj7LVVM8 +I9uia+8RKntUOBkUkLB3ouGdVJUmp3kGrkExxUdDHYP9VNJG6ZMwyKO8HXGtoTsR +TFWIEIbq/biBL9obM/d8fRV5xpfZNbPi6cRzw8REY9UIKECKr7B2B6PnDVVQIQw0 +7SCVjmSYWexOqoJPZ1L7/AZDP/tFvx32cWwCszj5jqUaPo9ZNPb6DxQJDdNaZrFH +3CA+PbiaeEz9IH0yBY/6wQgO0k3qOyFQrlkC+YRoYUQNc+6xS38l5ZnYUtBAy8ms +N43eODQ/OhsLzy6PwwXdzvR/0g18SrQyTLfbn2b/kwvbC8Qe40QFfkOf5lPXjdnP +Ii/lcMuvDMlMhoWGFwWm5bkkXE81TKnFXu2/IMsW6HYb3oiTjkaCap22fCr9l0jj +fNr8P7NIRyZ8 +-----END CERTIFICATE----- diff --git a/contrib/certificates/router/orignal_at_mail.i2p.crt b/contrib/certificates/router/orignal_at_mail.i2p.crt new file mode 100644 index 00000000..c1229f3b --- /dev/null +++ b/contrib/certificates/router/orignal_at_mail.i2p.crt @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFVDCCAzwCCQC2r1XWYtqtAzANBgkqhkiG9w0BAQsFADBsMQswCQYDVQQGEwJY +WDELMAkGA1UECAwCWFgxCzAJBgNVBAcMAlhYMRMwEQYDVQQKDApQdXJwbGUgSTJQ +MQ0wCwYDVQQLDARJMlBEMR8wHQYJKoZIhvcNAQkBFhBvcmlnbmFsQG1haWwuaTJw +MB4XDTE1MDIyMjEzNTgxOFoXDTI1MDIxOTEzNTgxOFowbDELMAkGA1UEBhMCWFgx +CzAJBgNVBAgMAlhYMQswCQYDVQQHDAJYWDETMBEGA1UECgwKUHVycGxlIEkyUDEN +MAsGA1UECwwESTJQRDEfMB0GCSqGSIb3DQEJARYQb3JpZ25hbEBtYWlsLmkycDCC +AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALp3D/gdvFjrMm+IE8tHZCWE +hQ6Pp0CCgCGDBC3WQFLqR98bqVPl4UwRG/MKY/LY7Woai06JNmGcpfw0LMoNnHxT +bvKtDRe/8kQdhdLHhgIkWKSbMvTAl7uUdV6FzsPgDR0x7scoFVWEhkF0wfmzGF2V +yr/WCBQejFPu69z03m5tRQ8Xjp2txWV45RawUmFu50bgbZvLCSLfTkIvxmfJzgPN +pJ3sPa/g7TBZl2uEiAu4uaEKvTuuzStOWCGgFaHYFVlTfFXTvmhFMqHfaidtzrlu +H35WGrmIWTDl6uGPC5QkSppvkj73rDj5aEyPzWMz5DN3YeECoVSchN+OJJCM6m7+ +rLFYXghVEp2h+T9O1GBRfcHlQ2E3CrWWvxhmK8dfteJmd501dyNX2paeuIg/aPFO +54/8m2r11uyF29hgY8VWLdXtqvwhKuK36PCzofEwDp9QQX8GRsEV4pZTrn4bDhGo +kb9BF7TZTqtL3uyiRmIyBXrNNiYlA1Xm4fyKRtxl0mrPaUXdgdnCt3KxOAJ8WM2B +7L/kk9U8C/nexHbMxIZfTap49XcUg5dxSO9kOBosIOcCUms8sAzBPDV2tWAByhYF +jI/Tutbd3F0+fvcmTcIFOlGbOxKgO2SfwXjv/44g/3LMK6IAMFB9UOc8KhnnJP0f +uAHvMXn1ahRs4pM1VizLAgMBAAEwDQYJKoZIhvcNAQELBQADggIBAIOxdaXT+wfu +nv/+1hy5T4TlRMNNsuj79ROcy6Mp+JwMG50HjTc0qTlXh8C7nHybDJn4v7DA+Nyn +RxT0J5I+Gqn+Na9TaC9mLeX/lwe8/KomyhBWxjrsyWj1V6v/cLO924S2rtcfzMDm +l3SFh9YHM1KF/R9N1XYBwtMzr3bupWDnE1yycYp1F4sMLr5SMzMQ0svQpQEM2/y5 +kly8+eUzryhm+ag9x1686uEG5gxhQ1eHQoZEaClHUOsV+28+d5If7cqcYx9Hf5Tt +CiVjJQzdxBF+6GeiJtKxnLtevqlkbyIJt6Cm9/7YIy/ovRGF2AKSYN6oCwmZQ6i1 +8nRnFq5zE7O94m+GXconWZxy0wVqA6472HThMi7S+Tk/eLYen2ilGY+KCb9a0FH5 +5MOuWSoJZ8/HfW2VeQmL8EjhWm5F2ybg28wgXK4BOGR3jQi03Fsc+AFidnWxSKo0 +aiJoPgOsfyu8/fnCcAi07kSmjzUKIWskApgcpGQLNXHFK9mtg7+VA8esRnfLlKtP +tJf+nNAPY1sqHfGBzh7WWGWal5RGHF5nEm3ta3oiFF5sMKCJ6C87zVwFkEcRytGC +xOGmiG1O1RPrO5NG7rZUaQ4y1OKl2Y1H+nGONzZ3mvoAOvxEq6JtUnU2kZscpPlk +fpeOSDoGBYJGbIpzDreBDhxaZrwGq36k +-----END CERTIFICATE----- diff --git a/contrib/debian/README b/contrib/debian/README index 7dc3a61f..cccbc4de 100644 --- a/contrib/debian/README +++ b/contrib/debian/README @@ -1,5 +1,2 @@ -This forder contain files required for building debian packages. - -The trunk repository is contains the packaging files for the latest stable version of Debian (if we not forgot to update them). - -Files in subdirectories contains fixes to make possible to build package on specific versions of Debian/Ubuntu. They are used when building the release package. +This forder contain systemd unit files. +To use systemd daemon control, place files from this directory to debian folder before building package. diff --git a/contrib/debian/bionic/compat b/contrib/debian/bionic/compat deleted file mode 100644 index b4de3947..00000000 --- a/contrib/debian/bionic/compat +++ /dev/null @@ -1 +0,0 @@ -11 diff --git a/contrib/debian/bionic/control b/contrib/debian/bionic/control deleted file mode 100644 index d872881c..00000000 --- a/contrib/debian/bionic/control +++ /dev/null @@ -1,18 +0,0 @@ -Source: i2pd -Section: net -Priority: optional -Maintainer: r4sas -Build-Depends: debhelper (>= 11~), libboost-system-dev (>= 1.46), libboost-date-time-dev (>= 1.46), libboost-filesystem-dev (>= 1.46), libboost-program-options-dev (>= 1.46), libminiupnpc-dev, libssl-dev, zlib1g-dev -Standards-Version: 4.2.0 -Homepage: http://i2pd.website/ -Vcs-Git: git://github.com/PurpleI2P/i2pd.git -Vcs-Browser: https://github.com/PurpleI2P/i2pd - -Package: i2pd -Architecture: any -Pre-Depends: ${misc:Pre-Depends}, adduser -Depends: ${shlibs:Depends}, ${misc:Depends}, lsb-base, -Description: Full-featured C++ implementation of I2P client. - I2P (Invisible Internet Protocol) is a universal anonymous network layer. All - communications over I2P are anonymous and end-to-end encrypted, participants - don't reveal their real IP addresses. diff --git a/contrib/debian/i2pd.service b/contrib/debian/i2pd.service deleted file mode 120000 index ca477e3b..00000000 --- a/contrib/debian/i2pd.service +++ /dev/null @@ -1 +0,0 @@ -../i2pd.service \ No newline at end of file diff --git a/contrib/debian/i2pd.service b/contrib/debian/i2pd.service new file mode 100644 index 00000000..37f26f14 --- /dev/null +++ b/contrib/debian/i2pd.service @@ -0,0 +1,27 @@ +[Unit] +Description=I2P Router written in C++ +After=network.target + +[Service] +User=i2pd +Group=i2pd +RuntimeDirectory=i2pd +RuntimeDirectoryMode=0700 +Type=simple +ExecStart=/usr/sbin/i2pd --conf=/etc/i2pd/i2pd.conf --tunconf=/etc/i2pd/tunnels.conf --pidfile=/var/run/i2pd/i2pd.pid --logfile=/var/log/i2pd/i2pd.log --daemon --service +ExecReload=/bin/kill -HUP $MAINPID +PIDFile=/var/run/i2pd/i2pd.pid +### Uncomment, if auto restart needed +#Restart=on-failure + +### Use SIGINT for graceful stop daemon. +# i2pd stops accepting new tunnels and waits ~10 min while old ones do not die. +KillSignal=SIGINT +TimeoutStopSec=10m + +# If you have problems with hanging i2pd, you can try enable this +#LimitNOFILE=4096 +PrivateDevices=yes + +[Install] +WantedBy=multi-user.target diff --git a/contrib/debian/i2pd.tmpfile b/contrib/debian/i2pd.tmpfile index e9bfebc6..6cd19112 100644 --- a/contrib/debian/i2pd.tmpfile +++ b/contrib/debian/i2pd.tmpfile @@ -1,2 +1,2 @@ -d /run/i2pd 0755 i2pd i2pd - - +d /var/run/i2pd 0755 i2pd i2pd - - d /var/log/i2pd 0755 i2pd i2pd - - diff --git a/contrib/debian/trusty/compat b/contrib/debian/trusty/compat deleted file mode 100644 index ec635144..00000000 --- a/contrib/debian/trusty/compat +++ /dev/null @@ -1 +0,0 @@ -9 diff --git a/contrib/debian/trusty/control b/contrib/debian/trusty/control deleted file mode 100644 index fc618ddc..00000000 --- a/contrib/debian/trusty/control +++ /dev/null @@ -1,18 +0,0 @@ -Source: i2pd -Section: net -Priority: optional -Maintainer: r4sas -Build-Depends: debhelper (>= 9), libboost-system-dev (>= 1.46), libboost-date-time-dev (>= 1.46), libboost-filesystem-dev (>= 1.46), libboost-program-options-dev (>= 1.46), libminiupnpc-dev, libssl-dev, zlib1g-dev -Standards-Version: 3.9.8 -Homepage: http://i2pd.website/ -Vcs-Git: git://github.com/PurpleI2P/i2pd.git -Vcs-Browser: https://github.com/PurpleI2P/i2pd - -Package: i2pd -Architecture: any -Pre-Depends: ${misc:Pre-Depends}, adduser -Depends: ${shlibs:Depends}, ${misc:Depends}, lsb-base, -Description: Full-featured C++ implementation of I2P client. - I2P (Invisible Internet Protocol) is a universal anonymous network layer. All - communications over I2P are anonymous and end-to-end encrypted, participants - don't reveal their real IP addresses. diff --git a/contrib/debian/trusty/patches/01-upnp.patch b/contrib/debian/trusty/patches/01-upnp.patch deleted file mode 100644 index 74d36c06..00000000 --- a/contrib/debian/trusty/patches/01-upnp.patch +++ /dev/null @@ -1,17 +0,0 @@ -Description: Enable UPnP usage in package -Author: r4sas - -Reviewed-By: r4sas -Last-Update: 2024-12-30 - ---- i2pd.orig/Makefile -+++ i2pd/Makefile -@@ -31,7 +31,7 @@ # import source files lists - include filelist.mk - - USE_STATIC := $(or $(USE_STATIC),no) --USE_UPNP := $(or $(USE_UPNP),no) -+USE_UPNP := $(or $(USE_UPNP),yes) - DEBUG := $(or $(DEBUG),yes) - - # for debugging purposes only, when commit hash needed in trunk builds in i2pd version string diff --git a/contrib/debian/trusty/patches/02-service.patch b/contrib/debian/trusty/patches/02-service.patch deleted file mode 100644 index 546e252f..00000000 --- a/contrib/debian/trusty/patches/02-service.patch +++ /dev/null @@ -1,19 +0,0 @@ -Description: Disable LogsDirectory and LogsDirectoryMode options in service -Author: r4sas - -Reviewed-By: r4sas -Last-Update: 2024-07-19 - ---- a/contrib/i2pd.service -+++ b/contrib/i2pd.service -@@ -8,8 +8,8 @@ User=i2pd - Group=i2pd - RuntimeDirectory=i2pd - RuntimeDirectoryMode=0700 --LogsDirectory=i2pd --LogsDirectoryMode=0700 -+#LogsDirectory=i2pd -+#LogsDirectoryMode=0700 - Type=forking - ExecStart=/usr/bin/i2pd --conf=/etc/i2pd/i2pd.conf --tunconf=/etc/i2pd/tunnels.conf --tunnelsdir=/etc/i2pd/tunnels.conf.d --pidfile=/run/i2pd/i2pd.pid --logfile=/var/log/i2pd/i2pd.log --daemon --service - ExecReload=/bin/sh -c "kill -HUP $MAINPID" diff --git a/contrib/debian/trusty/patches/series b/contrib/debian/trusty/patches/series deleted file mode 100644 index d8caec41..00000000 --- a/contrib/debian/trusty/patches/series +++ /dev/null @@ -1,2 +0,0 @@ -01-upnp.patch -02-service.patch diff --git a/contrib/debian/trusty/rules b/contrib/debian/trusty/rules deleted file mode 100755 index 97a4e008..00000000 --- a/contrib/debian/trusty/rules +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/make -f -#export DH_VERBOSE=1 - -export DEB_BUILD_MAINT_OPTIONS=hardening=+all - -include /usr/share/dpkg/architecture.mk - -ifeq ($(DEB_HOST_ARCH),i386) - export DEB_BUILD_OPTIONS=parallel=1 -endif - -export DEB_CXXFLAGS_MAINT_APPEND=-Wall -pedantic -export DEB_LDFLAGS_MAINT_APPEND= - -%: - dh $@ --parallel - -override_dh_auto_install: diff --git a/contrib/debian/xenial/compat b/contrib/debian/xenial/compat deleted file mode 100644 index ec635144..00000000 --- a/contrib/debian/xenial/compat +++ /dev/null @@ -1 +0,0 @@ -9 diff --git a/contrib/debian/xenial/control b/contrib/debian/xenial/control deleted file mode 100644 index fc618ddc..00000000 --- a/contrib/debian/xenial/control +++ /dev/null @@ -1,18 +0,0 @@ -Source: i2pd -Section: net -Priority: optional -Maintainer: r4sas -Build-Depends: debhelper (>= 9), libboost-system-dev (>= 1.46), libboost-date-time-dev (>= 1.46), libboost-filesystem-dev (>= 1.46), libboost-program-options-dev (>= 1.46), libminiupnpc-dev, libssl-dev, zlib1g-dev -Standards-Version: 3.9.8 -Homepage: http://i2pd.website/ -Vcs-Git: git://github.com/PurpleI2P/i2pd.git -Vcs-Browser: https://github.com/PurpleI2P/i2pd - -Package: i2pd -Architecture: any -Pre-Depends: ${misc:Pre-Depends}, adduser -Depends: ${shlibs:Depends}, ${misc:Depends}, lsb-base, -Description: Full-featured C++ implementation of I2P client. - I2P (Invisible Internet Protocol) is a universal anonymous network layer. All - communications over I2P are anonymous and end-to-end encrypted, participants - don't reveal their real IP addresses. diff --git a/contrib/debian/xenial/patches/01-upnp.patch b/contrib/debian/xenial/patches/01-upnp.patch deleted file mode 100644 index 74d36c06..00000000 --- a/contrib/debian/xenial/patches/01-upnp.patch +++ /dev/null @@ -1,17 +0,0 @@ -Description: Enable UPnP usage in package -Author: r4sas - -Reviewed-By: r4sas -Last-Update: 2024-12-30 - ---- i2pd.orig/Makefile -+++ i2pd/Makefile -@@ -31,7 +31,7 @@ # import source files lists - include filelist.mk - - USE_STATIC := $(or $(USE_STATIC),no) --USE_UPNP := $(or $(USE_UPNP),no) -+USE_UPNP := $(or $(USE_UPNP),yes) - DEBUG := $(or $(DEBUG),yes) - - # for debugging purposes only, when commit hash needed in trunk builds in i2pd version string diff --git a/contrib/debian/xenial/patches/02-service.patch b/contrib/debian/xenial/patches/02-service.patch deleted file mode 100644 index 546e252f..00000000 --- a/contrib/debian/xenial/patches/02-service.patch +++ /dev/null @@ -1,19 +0,0 @@ -Description: Disable LogsDirectory and LogsDirectoryMode options in service -Author: r4sas - -Reviewed-By: r4sas -Last-Update: 2024-07-19 - ---- a/contrib/i2pd.service -+++ b/contrib/i2pd.service -@@ -8,8 +8,8 @@ User=i2pd - Group=i2pd - RuntimeDirectory=i2pd - RuntimeDirectoryMode=0700 --LogsDirectory=i2pd --LogsDirectoryMode=0700 -+#LogsDirectory=i2pd -+#LogsDirectoryMode=0700 - Type=forking - ExecStart=/usr/bin/i2pd --conf=/etc/i2pd/i2pd.conf --tunconf=/etc/i2pd/tunnels.conf --tunnelsdir=/etc/i2pd/tunnels.conf.d --pidfile=/run/i2pd/i2pd.pid --logfile=/var/log/i2pd/i2pd.log --daemon --service - ExecReload=/bin/sh -c "kill -HUP $MAINPID" diff --git a/contrib/debian/xenial/patches/series b/contrib/debian/xenial/patches/series deleted file mode 100644 index d8caec41..00000000 --- a/contrib/debian/xenial/patches/series +++ /dev/null @@ -1,2 +0,0 @@ -01-upnp.patch -02-service.patch diff --git a/contrib/debian/xenial/rules b/contrib/debian/xenial/rules deleted file mode 100755 index 11791d9b..00000000 --- a/contrib/debian/xenial/rules +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/make -f -#export DH_VERBOSE=1 -export DEB_BUILD_MAINT_OPTIONS = hardening=+all - -include /usr/share/dpkg/architecture.mk - -export DEB_CXXFLAGS_MAINT_APPEND = -Wall -pedantic -export DEB_LDFLAGS_MAINT_APPEND = - -%: - dh $@ --parallel - -override_dh_auto_install: diff --git a/contrib/dinit/i2pd b/contrib/dinit/i2pd deleted file mode 100644 index 63d877c1..00000000 --- a/contrib/dinit/i2pd +++ /dev/null @@ -1,8 +0,0 @@ -type = bgprocess -run-as = i2pd -command = /usr/bin/i2pd --conf=/var/lib/i2pd/i2pd.conf --pidfile=/var/lib/i2pd/i2pd.pid --daemon --service -smooth-recovery = true -depends-on = ntpd -# uncomment if you want to use i2pd with yggdrasil -# depends-on = yggdrasil -pid-file = /var/lib/i2pd/i2pd.pid diff --git a/contrib/docker/Dockerfile b/contrib/docker/Dockerfile index ead21f10..e7e60f37 100644 --- a/contrib/docker/Dockerfile +++ b/contrib/docker/Dockerfile @@ -1,21 +1,8 @@ -# -# Copyright (c) 2017-2022, The PurpleI2P Project -# -# This file is part of Purple i2pd project and licensed under BSD3 -# -# See full license text in LICENSE file at top of project tree -# - FROM alpine:latest -LABEL authors="Mikal Villa , Darknet Villain " -LABEL maintainer="R4SAS " - -LABEL org.opencontainers.image.source=https://github.com/PurpleI2P/i2pd -LABEL org.opencontainers.image.documentation=https://i2pd.readthedocs.io/en/latest/ -LABEL org.opencontainers.image.licenses=BSD3 +LABEL authors "Mikal Villa , Darknet Villain " # Expose git branch, tag and URL variables as arguments -ARG GIT_BRANCH="openssl" +ARG GIT_BRANCH="master" ENV GIT_BRANCH=${GIT_BRANCH} ARG GIT_TAG="" ENV GIT_TAG=${GIT_TAG} @@ -24,44 +11,38 @@ ENV REPO_URL=${REPO_URL} ENV I2PD_HOME="/home/i2pd" ENV DATA_DIR="${I2PD_HOME}/data" -ENV DEFAULT_ARGS=" --datadir=$DATA_DIR" RUN mkdir -p "$I2PD_HOME" "$DATA_DIR" \ && adduser -S -h "$I2PD_HOME" i2pd \ && chown -R i2pd:nobody "$I2PD_HOME" - -# 1. Building binary -# Each RUN is a layer, adding the dependencies and building i2pd in one layer takes around 8-900Mb, so to keep the -# image under 20mb we need to remove all the build dependencies in the same "RUN" / layer. # -# 1. install deps, clone and build. -# 2. strip binaries. -# 3. Purge all dependencies and other unrelated packages, including build directory. +# Each RUN is a layer, adding the dependencies and building i2pd in one layer takes around 8-900Mb, so to keep the +# image under 20mb we need to remove all the build dependencies in the same "RUN" / layer. +# -RUN apk update \ - && apk --no-cache --virtual build-dependendencies add make gcc g++ libtool zlib-dev boost-dev build-base openssl-dev openssl miniupnpc-dev git \ +# 1. install deps, clone and build. +# 2. strip binaries. +# 3. Purge all dependencies and other unrelated packages, including build directory. +RUN apk --no-cache --virtual build-dependendencies add make gcc g++ libtool boost-dev build-base openssl-dev openssl git \ && mkdir -p /tmp/build \ && cd /tmp/build && git clone -b ${GIT_BRANCH} ${REPO_URL} \ && cd i2pd \ && if [ -n "${GIT_TAG}" ]; then git checkout tags/${GIT_TAG}; fi \ - && make -j$(nproc) USE_UPNP=yes \ + && make \ && cp -R contrib/certificates /i2pd_certificates \ && mkdir -p /usr/local/bin \ && mv i2pd /usr/local/bin \ && cd /usr/local/bin \ && strip i2pd \ && rm -fr /tmp/build && apk --no-cache --purge del build-dependendencies build-base fortify-headers boost-dev zlib-dev openssl-dev \ - miniupnpc-dev boost-python3 python3 gdbm boost-unit_test_framework linux-headers boost-prg_exec_monitor \ - boost-serialization boost-wave boost-wserialization boost-math boost-graph boost-regex git pcre2 \ - libtool g++ gcc + boost-python3 python3 gdbm boost-unit_test_framework boost-python linux-headers boost-prg_exec_monitor \ + boost-serialization boost-signals boost-wave boost-wserialization boost-math boost-graph boost-regex git pcre \ + libtool g++ gcc pkgconfig # 2. Adding required libraries to run i2pd to ensure it will run. -RUN apk --no-cache add boost-filesystem boost-system boost-program_options boost-date_time boost-thread boost-iostreams openssl miniupnpc musl-utils libstdc++ +RUN apk --no-cache add boost-filesystem boost-system boost-program_options boost-date_time boost-thread boost-iostreams openssl musl-utils libstdc++ -# 3. Copy preconfigured config file and entrypoint -COPY i2pd-docker.conf "$DATA_DIR/i2pd.conf" -RUN chown i2pd:nobody "$DATA_DIR/i2pd.conf" COPY entrypoint.sh /entrypoint.sh RUN chmod a+x /entrypoint.sh diff --git a/contrib/docker/entrypoint.sh b/contrib/docker/entrypoint.sh index ce897e3c..ca9dddd4 100644 --- a/contrib/docker/entrypoint.sh +++ b/contrib/docker/entrypoint.sh @@ -2,6 +2,7 @@ COMMAND=/usr/local/bin/i2pd # To make ports exposeable # Note: $DATA_DIR is defined in /etc/profile +DEFAULT_ARGS=" --datadir=$DATA_DIR --reseed.verify=true --upnp.enabled=false --http.enabled=true --http.address=0.0.0.0 --httpproxy.enabled=true --httpproxy.address=0.0.0.0 --socksproxy.enabled=true --socksproxy.address=0.0.0.0 --sam.enabled=true --sam.address=0.0.0.0" if [ "$1" = "--help" ]; then set -- $COMMAND --help diff --git a/contrib/docker/i2pd-docker.conf b/contrib/docker/i2pd-docker.conf deleted file mode 100644 index 3b91a235..00000000 --- a/contrib/docker/i2pd-docker.conf +++ /dev/null @@ -1,52 +0,0 @@ -## Preconfigured i2pd configuration file for a Docker container -## See https://i2pd.readthedocs.io/en/latest/user-guide/configuration/ -## for more options you can use in this file. - -## Note that for exposing ports outside of container you need to bind all services to 0.0.0.0 - -log = file -loglevel = none - -ipv4 = true -ipv6 = false - -# bandwidth = L -# notransit = false -# floodfill = false - -[ntcp2] -enabled = true -published = true - -[ssu2] -enabled = true -published = true - -[http] -enabled = true -address = 0.0.0.0 -port = 7070 - -[httpproxy] -enabled = true -address = 0.0.0.0 -port = 4444 - -[socksproxy] -enabled = true -address = 0.0.0.0 -port = 4447 - -[sam] -enabled = true -address = 0.0.0.0 -port = 7656 - -[upnp] -enabled = false - -[reseed] -verify = true - -[limits] -# transittunnels = 2500 diff --git a/contrib/i18n/English.po b/contrib/i18n/English.po deleted file mode 100644 index 7782187f..00000000 --- a/contrib/i18n/English.po +++ /dev/null @@ -1,784 +0,0 @@ -# i2pd -# Copyright (C) 2021-2023 PurpleI2P team -# This file is distributed under the same license as the i2pd package. -# R4SAS , 2021-2023. -# -msgid "" -msgstr "" -"Project-Id-Version: i2pd\n" -"Report-Msgid-Bugs-To: https://github.com/PurpleI2P/i2pd/issues\n" -"POT-Creation-Date: 2023-06-10 01:25\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"X-Generator: Poedit 3.0\n" -"X-Poedit-SourceCharset: UTF-8\n" -"X-Poedit-Basepath: .\n" -"X-Poedit-KeywordsList: ;tr\n" -"X-Poedit-SearchPath-0: daemon/HTTPServer.cpp\n" -"X-Poedit-SearchPath-1: libi2pd_client/HTTPProxy.cpp\n" - -#: daemon/HTTPServer.cpp:107 -#, c-format -msgid "%d day" -msgid_plural "%d days" -msgstr[0] "" -msgstr[1] "" - -#: daemon/HTTPServer.cpp:111 -#, c-format -msgid "%d hour" -msgid_plural "%d hours" -msgstr[0] "" -msgstr[1] "" - -#: daemon/HTTPServer.cpp:115 -#, c-format -msgid "%d minute" -msgid_plural "%d minutes" -msgstr[0] "" -msgstr[1] "" - -#: daemon/HTTPServer.cpp:118 -#, c-format -msgid "%d second" -msgid_plural "%d seconds" -msgstr[0] "" -msgstr[1] "" - -#. tr: Kibibyte -#: daemon/HTTPServer.cpp:126 -#, c-format -msgid "%.2f KiB" -msgstr "" - -#. tr: Mebibyte -#: daemon/HTTPServer.cpp:128 -#, c-format -msgid "%.2f MiB" -msgstr "" - -#. tr: Gibibyte -#: daemon/HTTPServer.cpp:130 -#, c-format -msgid "%.2f GiB" -msgstr "" - -#: daemon/HTTPServer.cpp:147 -msgid "building" -msgstr "" - -#: daemon/HTTPServer.cpp:148 -msgid "failed" -msgstr "" - -#: daemon/HTTPServer.cpp:149 -msgid "expiring" -msgstr "" - -#: daemon/HTTPServer.cpp:150 -msgid "established" -msgstr "" - -#: daemon/HTTPServer.cpp:151 -msgid "unknown" -msgstr "" - -#: daemon/HTTPServer.cpp:153 -msgid "exploratory" -msgstr "" - -#. tr: Webconsole page title -#: daemon/HTTPServer.cpp:185 -msgid "Purple I2P Webconsole" -msgstr "" - -#: daemon/HTTPServer.cpp:190 -msgid "i2pd webconsole" -msgstr "" - -#: daemon/HTTPServer.cpp:193 -msgid "Main page" -msgstr "" - -#: daemon/HTTPServer.cpp:194 daemon/HTTPServer.cpp:742 -msgid "Router commands" -msgstr "" - -#: daemon/HTTPServer.cpp:195 daemon/HTTPServer.cpp:395 -#: daemon/HTTPServer.cpp:407 -msgid "Local Destinations" -msgstr "" - -#: daemon/HTTPServer.cpp:197 daemon/HTTPServer.cpp:365 -#: daemon/HTTPServer.cpp:454 daemon/HTTPServer.cpp:474 -#: daemon/HTTPServer.cpp:636 daemon/HTTPServer.cpp:682 -#: daemon/HTTPServer.cpp:686 -msgid "LeaseSets" -msgstr "" - -#: daemon/HTTPServer.cpp:199 daemon/HTTPServer.cpp:692 -msgid "Tunnels" -msgstr "" - -#: daemon/HTTPServer.cpp:201 daemon/HTTPServer.cpp:372 -#: daemon/HTTPServer.cpp:813 daemon/HTTPServer.cpp:830 -msgid "Transit Tunnels" -msgstr "" - -#: daemon/HTTPServer.cpp:203 daemon/HTTPServer.cpp:898 -msgid "Transports" -msgstr "" - -#: daemon/HTTPServer.cpp:204 -msgid "I2P tunnels" -msgstr "" - -#: daemon/HTTPServer.cpp:206 daemon/HTTPServer.cpp:927 -#: daemon/HTTPServer.cpp:937 -msgid "SAM sessions" -msgstr "" - -#: daemon/HTTPServer.cpp:222 daemon/HTTPServer.cpp:1329 -#: daemon/HTTPServer.cpp:1332 daemon/HTTPServer.cpp:1335 -#: daemon/HTTPServer.cpp:1362 daemon/HTTPServer.cpp:1365 -#: daemon/HTTPServer.cpp:1379 daemon/HTTPServer.cpp:1424 -#: daemon/HTTPServer.cpp:1427 daemon/HTTPServer.cpp:1430 -msgid "ERROR" -msgstr "" - -#: daemon/HTTPServer.cpp:229 -msgid "OK" -msgstr "" - -#: daemon/HTTPServer.cpp:230 -msgid "Testing" -msgstr "" - -#: daemon/HTTPServer.cpp:231 -msgid "Firewalled" -msgstr "" - -#: daemon/HTTPServer.cpp:232 daemon/HTTPServer.cpp:235 -#: daemon/HTTPServer.cpp:336 -msgid "Unknown" -msgstr "" - -#: daemon/HTTPServer.cpp:233 daemon/HTTPServer.cpp:382 -#: daemon/HTTPServer.cpp:383 daemon/HTTPServer.cpp:1003 -#: daemon/HTTPServer.cpp:1011 -msgid "Proxy" -msgstr "" - -#: daemon/HTTPServer.cpp:234 -msgid "Mesh" -msgstr "" - -#: daemon/HTTPServer.cpp:242 -msgid "Clock skew" -msgstr "" - -#: daemon/HTTPServer.cpp:245 -msgid "Offline" -msgstr "" - -#: daemon/HTTPServer.cpp:248 -msgid "Symmetric NAT" -msgstr "" - -#: daemon/HTTPServer.cpp:251 -msgid "Full cone NAT" -msgstr "" - -#: daemon/HTTPServer.cpp:254 -msgid "No Descriptors" -msgstr "" - -#: daemon/HTTPServer.cpp:263 -msgid "Uptime" -msgstr "" - -#: daemon/HTTPServer.cpp:266 -msgid "Network status" -msgstr "" - -#: daemon/HTTPServer.cpp:271 -msgid "Network status v6" -msgstr "" - -#: daemon/HTTPServer.cpp:277 daemon/HTTPServer.cpp:284 -msgid "Stopping in" -msgstr "" - -#: daemon/HTTPServer.cpp:291 -msgid "Family" -msgstr "" - -#: daemon/HTTPServer.cpp:292 -msgid "Tunnel creation success rate" -msgstr "" - -#: daemon/HTTPServer.cpp:296 -msgid "Total tunnel creation success rate" -msgstr "" - -#: daemon/HTTPServer.cpp:298 -msgid "Received" -msgstr "" - -#. tr: Kibibyte/s -#: daemon/HTTPServer.cpp:300 daemon/HTTPServer.cpp:303 -#: daemon/HTTPServer.cpp:306 -#, c-format -msgid "%.2f KiB/s" -msgstr "" - -#: daemon/HTTPServer.cpp:301 -msgid "Sent" -msgstr "" - -#: daemon/HTTPServer.cpp:304 -msgid "Transit" -msgstr "" - -#: daemon/HTTPServer.cpp:307 -msgid "Data path" -msgstr "" - -#: daemon/HTTPServer.cpp:310 -msgid "Hidden content. Press on text to see." -msgstr "" - -#: daemon/HTTPServer.cpp:314 -msgid "Router Ident" -msgstr "" - -#: daemon/HTTPServer.cpp:316 -msgid "Router Family" -msgstr "" - -#: daemon/HTTPServer.cpp:317 -msgid "Router Caps" -msgstr "" - -#: daemon/HTTPServer.cpp:318 -msgid "Version" -msgstr "" - -#: daemon/HTTPServer.cpp:319 -msgid "Our external address" -msgstr "" - -#. tr: Shown when router doesn't publish itself and have "Firewalled" state -#: daemon/HTTPServer.cpp:349 -msgid "supported" -msgstr "" - -#: daemon/HTTPServer.cpp:363 -msgid "Routers" -msgstr "" - -#: daemon/HTTPServer.cpp:364 -msgid "Floodfills" -msgstr "" - -#: daemon/HTTPServer.cpp:371 daemon/HTTPServer.cpp:987 -msgid "Client Tunnels" -msgstr "" - -#: daemon/HTTPServer.cpp:381 -msgid "Services" -msgstr "" - -#: daemon/HTTPServer.cpp:382 daemon/HTTPServer.cpp:383 -#: daemon/HTTPServer.cpp:384 daemon/HTTPServer.cpp:385 -#: daemon/HTTPServer.cpp:386 daemon/HTTPServer.cpp:387 -msgid "Enabled" -msgstr "" - -#: daemon/HTTPServer.cpp:382 daemon/HTTPServer.cpp:383 -#: daemon/HTTPServer.cpp:384 daemon/HTTPServer.cpp:385 -#: daemon/HTTPServer.cpp:386 daemon/HTTPServer.cpp:387 -msgid "Disabled" -msgstr "" - -#: daemon/HTTPServer.cpp:434 -msgid "Encrypted B33 address" -msgstr "" - -#: daemon/HTTPServer.cpp:442 -msgid "Address registration line" -msgstr "" - -#: daemon/HTTPServer.cpp:447 -msgid "Domain" -msgstr "" - -#: daemon/HTTPServer.cpp:448 -msgid "Generate" -msgstr "" - -#: daemon/HTTPServer.cpp:449 -msgid "" -"Note: result string can be used only for registering 2LD domains " -"(example.i2p). For registering subdomains please use i2pd-tools." -msgstr "" - -#: daemon/HTTPServer.cpp:457 -msgid "Address" -msgstr "" - -#: daemon/HTTPServer.cpp:459 -msgid "Type" -msgstr "" - -#: daemon/HTTPServer.cpp:460 -msgid "EncType" -msgstr "" - -#: daemon/HTTPServer.cpp:467 -msgid "Expire LeaseSet" -msgstr "" - -#: daemon/HTTPServer.cpp:479 daemon/HTTPServer.cpp:697 -msgid "Inbound tunnels" -msgstr "" - -#. tr: Milliseconds -#: daemon/HTTPServer.cpp:494 daemon/HTTPServer.cpp:514 -#: daemon/HTTPServer.cpp:711 daemon/HTTPServer.cpp:731 -#, c-format -msgid "%dms" -msgstr "" - -#: daemon/HTTPServer.cpp:499 daemon/HTTPServer.cpp:716 -msgid "Outbound tunnels" -msgstr "" - -#: daemon/HTTPServer.cpp:521 -msgid "Tags" -msgstr "" - -#: daemon/HTTPServer.cpp:522 -msgid "Incoming" -msgstr "" - -#: daemon/HTTPServer.cpp:529 daemon/HTTPServer.cpp:535 -msgid "Outgoing" -msgstr "" - -#: daemon/HTTPServer.cpp:532 daemon/HTTPServer.cpp:551 -msgid "Destination" -msgstr "" - -#: daemon/HTTPServer.cpp:532 daemon/HTTPServer.cpp:814 -msgid "Amount" -msgstr "" - -#: daemon/HTTPServer.cpp:540 -msgid "Incoming Tags" -msgstr "" - -#: daemon/HTTPServer.cpp:548 daemon/HTTPServer.cpp:554 -msgid "Tags sessions" -msgstr "" - -#: daemon/HTTPServer.cpp:551 -msgid "Status" -msgstr "" - -#: daemon/HTTPServer.cpp:561 daemon/HTTPServer.cpp:621 -msgid "Local Destination" -msgstr "" - -#: daemon/HTTPServer.cpp:572 daemon/HTTPServer.cpp:960 -msgid "Streams" -msgstr "" - -#: daemon/HTTPServer.cpp:595 -msgid "Close stream" -msgstr "" - -#: daemon/HTTPServer.cpp:613 daemon/HTTPServer.cpp:1430 -msgid "Such destination is not found" -msgstr "" - -#: daemon/HTTPServer.cpp:626 -msgid "I2CP session not found" -msgstr "" - -#: daemon/HTTPServer.cpp:629 -msgid "I2CP is not enabled" -msgstr "" - -#: daemon/HTTPServer.cpp:658 -msgid "Invalid" -msgstr "" - -#: daemon/HTTPServer.cpp:661 -msgid "Store type" -msgstr "" - -#: daemon/HTTPServer.cpp:662 -msgid "Expires" -msgstr "" - -#: daemon/HTTPServer.cpp:667 -msgid "Non Expired Leases" -msgstr "" - -#: daemon/HTTPServer.cpp:670 -msgid "Gateway" -msgstr "" - -#: daemon/HTTPServer.cpp:671 -msgid "TunnelID" -msgstr "" - -#: daemon/HTTPServer.cpp:672 -msgid "EndDate" -msgstr "" - -#: daemon/HTTPServer.cpp:682 -msgid "floodfill mode is disabled" -msgstr "" - -#: daemon/HTTPServer.cpp:693 -msgid "Queue size" -msgstr "" - -#: daemon/HTTPServer.cpp:743 -msgid "Run peer test" -msgstr "" - -#: daemon/HTTPServer.cpp:744 -msgid "Reload tunnels configuration" -msgstr "" - -#: daemon/HTTPServer.cpp:747 -msgid "Decline transit tunnels" -msgstr "" - -#: daemon/HTTPServer.cpp:749 -msgid "Accept transit tunnels" -msgstr "" - -#: daemon/HTTPServer.cpp:753 daemon/HTTPServer.cpp:758 -msgid "Cancel graceful shutdown" -msgstr "" - -#: daemon/HTTPServer.cpp:755 daemon/HTTPServer.cpp:760 -msgid "Start graceful shutdown" -msgstr "" - -#: daemon/HTTPServer.cpp:763 -msgid "Force shutdown" -msgstr "" - -#: daemon/HTTPServer.cpp:764 -msgid "Reload external CSS styles" -msgstr "" - -#: daemon/HTTPServer.cpp:767 -msgid "" -"Note: any action done here are not persistent and not changes your " -"config files." -msgstr "" - -#: daemon/HTTPServer.cpp:770 -msgid "Logging level" -msgstr "" - -#: daemon/HTTPServer.cpp:779 -msgid "Transit tunnels limit" -msgstr "" - -#: daemon/HTTPServer.cpp:784 daemon/HTTPServer.cpp:803 -msgid "Change" -msgstr "" - -#: daemon/HTTPServer.cpp:791 -msgid "Change language" -msgstr "" - -#: daemon/HTTPServer.cpp:830 -msgid "no transit tunnels currently built" -msgstr "" - -#: daemon/HTTPServer.cpp:921 daemon/HTTPServer.cpp:944 -msgid "SAM disabled" -msgstr "" - -#: daemon/HTTPServer.cpp:937 -msgid "no sessions currently running" -msgstr "" - -#: daemon/HTTPServer.cpp:950 -msgid "SAM session not found" -msgstr "" - -#: daemon/HTTPServer.cpp:955 -msgid "SAM Session" -msgstr "" - -#: daemon/HTTPServer.cpp:1020 -msgid "Server Tunnels" -msgstr "" - -#: daemon/HTTPServer.cpp:1036 -msgid "Client Forwards" -msgstr "" - -#: daemon/HTTPServer.cpp:1050 -msgid "Server Forwards" -msgstr "" - -#: daemon/HTTPServer.cpp:1250 -msgid "Unknown page" -msgstr "" - -#: daemon/HTTPServer.cpp:1269 -msgid "Invalid token" -msgstr "" - -#: daemon/HTTPServer.cpp:1327 daemon/HTTPServer.cpp:1359 -#: daemon/HTTPServer.cpp:1414 daemon/HTTPServer.cpp:1454 -msgid "SUCCESS" -msgstr "" - -#: daemon/HTTPServer.cpp:1327 -msgid "Stream closed" -msgstr "" - -#: daemon/HTTPServer.cpp:1329 -msgid "Stream not found or already was closed" -msgstr "" - -#: daemon/HTTPServer.cpp:1332 daemon/HTTPServer.cpp:1365 -msgid "Destination not found" -msgstr "" - -#: daemon/HTTPServer.cpp:1335 -msgid "StreamID can't be null" -msgstr "" - -#: daemon/HTTPServer.cpp:1337 daemon/HTTPServer.cpp:1367 -#: daemon/HTTPServer.cpp:1432 -msgid "Return to destination page" -msgstr "" - -#: daemon/HTTPServer.cpp:1338 daemon/HTTPServer.cpp:1368 -#: daemon/HTTPServer.cpp:1381 daemon/HTTPServer.cpp:1456 -#, c-format -msgid "You will be redirected in %d seconds" -msgstr "" - -#: daemon/HTTPServer.cpp:1359 -msgid "LeaseSet expiration time updated" -msgstr "" - -#: daemon/HTTPServer.cpp:1362 -msgid "LeaseSet is not found or already expired" -msgstr "" - -#: daemon/HTTPServer.cpp:1379 -#, c-format -msgid "Transit tunnels count must not exceed %d" -msgstr "" - -#: daemon/HTTPServer.cpp:1380 daemon/HTTPServer.cpp:1455 -msgid "Back to commands list" -msgstr "" - -#: daemon/HTTPServer.cpp:1416 -msgid "Register at reg.i2p" -msgstr "" - -#: daemon/HTTPServer.cpp:1417 -msgid "Description" -msgstr "" - -#: daemon/HTTPServer.cpp:1417 -msgid "A bit information about service on domain" -msgstr "" - -#: daemon/HTTPServer.cpp:1418 -msgid "Submit" -msgstr "" - -#: daemon/HTTPServer.cpp:1424 -msgid "Domain can't end with .b32.i2p" -msgstr "" - -#: daemon/HTTPServer.cpp:1427 -msgid "Domain must end with .i2p" -msgstr "" - -#: daemon/HTTPServer.cpp:1450 -msgid "Unknown command" -msgstr "" - -#: daemon/HTTPServer.cpp:1454 -msgid "Command accepted" -msgstr "" - -#: libi2pd_client/HTTPProxy.cpp:166 -msgid "Proxy error" -msgstr "" - -#: libi2pd_client/HTTPProxy.cpp:174 -msgid "Proxy info" -msgstr "" - -#: libi2pd_client/HTTPProxy.cpp:182 -msgid "Proxy error: Host not found" -msgstr "" - -#: libi2pd_client/HTTPProxy.cpp:183 -msgid "Remote host not found in router's addressbook" -msgstr "" - -#: libi2pd_client/HTTPProxy.cpp:184 -msgid "You may try to find this host on jump services below" -msgstr "" - -#: libi2pd_client/HTTPProxy.cpp:333 libi2pd_client/HTTPProxy.cpp:348 -#: libi2pd_client/HTTPProxy.cpp:417 libi2pd_client/HTTPProxy.cpp:460 -msgid "Invalid request" -msgstr "" - -#: libi2pd_client/HTTPProxy.cpp:333 -msgid "Proxy unable to parse your request" -msgstr "" - -#: libi2pd_client/HTTPProxy.cpp:348 -msgid "Addresshelper is not supported" -msgstr "" - -#: libi2pd_client/HTTPProxy.cpp:373 -#, c-format -msgid "" -"Host %s is already in router's addressbook. Be " -"careful: source of this URL may be harmful! Click here to update record: " -"Continue." -msgstr "" - -#: libi2pd_client/HTTPProxy.cpp:375 -msgid "Addresshelper forced update rejected" -msgstr "" - -#: libi2pd_client/HTTPProxy.cpp:382 -#, c-format -msgid "" -"To add host %s in router's addressbook, click here: Continue." -msgstr "" - -#: libi2pd_client/HTTPProxy.cpp:384 -msgid "Addresshelper request" -msgstr "" - -#: libi2pd_client/HTTPProxy.cpp:393 -#, c-format -msgid "" -"Host %s added to router's addressbook from helper. Click here to proceed: Continue." -msgstr "" - -#: libi2pd_client/HTTPProxy.cpp:395 -msgid "Addresshelper adding" -msgstr "" - -#: libi2pd_client/HTTPProxy.cpp:402 -#, c-format -msgid "" -"Host %s is already in router's addressbook. Click " -"here to update record: Continue." -msgstr "" - -#: libi2pd_client/HTTPProxy.cpp:404 -msgid "Addresshelper update" -msgstr "" - -#: libi2pd_client/HTTPProxy.cpp:417 -msgid "Invalid request URI" -msgstr "" - -#: libi2pd_client/HTTPProxy.cpp:460 -msgid "Can't detect destination host from request" -msgstr "" - -#: libi2pd_client/HTTPProxy.cpp:477 libi2pd_client/HTTPProxy.cpp:481 -msgid "Outproxy failure" -msgstr "" - -#: libi2pd_client/HTTPProxy.cpp:477 -msgid "Bad outproxy settings" -msgstr "" - -#: libi2pd_client/HTTPProxy.cpp:480 -#, c-format -msgid "Host %s is not inside I2P network, but outproxy is not enabled" -msgstr "" - -#: libi2pd_client/HTTPProxy.cpp:569 -msgid "Unknown outproxy URL" -msgstr "" - -#: libi2pd_client/HTTPProxy.cpp:575 -msgid "Cannot resolve upstream proxy" -msgstr "" - -#: libi2pd_client/HTTPProxy.cpp:583 -msgid "Hostname is too long" -msgstr "" - -#: libi2pd_client/HTTPProxy.cpp:610 -msgid "Cannot connect to upstream SOCKS proxy" -msgstr "" - -#: libi2pd_client/HTTPProxy.cpp:616 -msgid "Cannot negotiate with SOCKS proxy" -msgstr "" - -#: libi2pd_client/HTTPProxy.cpp:658 -msgid "CONNECT error" -msgstr "" - -#: libi2pd_client/HTTPProxy.cpp:658 -msgid "Failed to connect" -msgstr "" - -#: libi2pd_client/HTTPProxy.cpp:669 libi2pd_client/HTTPProxy.cpp:695 -msgid "SOCKS proxy error" -msgstr "" - -#: libi2pd_client/HTTPProxy.cpp:677 -msgid "Failed to send request to upstream" -msgstr "" - -#: libi2pd_client/HTTPProxy.cpp:698 -msgid "No reply from SOCKS proxy" -msgstr "" - -#: libi2pd_client/HTTPProxy.cpp:705 -msgid "Cannot connect" -msgstr "" - -#: libi2pd_client/HTTPProxy.cpp:705 -msgid "HTTP out proxy not implemented" -msgstr "" - -#: libi2pd_client/HTTPProxy.cpp:706 -msgid "Cannot connect to upstream HTTP proxy" -msgstr "" - -#: libi2pd_client/HTTPProxy.cpp:739 -msgid "Host is down" -msgstr "" - -#: libi2pd_client/HTTPProxy.cpp:739 -msgid "" -"Can't create connection to requested host, it may be down. Please try again " -"later." -msgstr "" diff --git a/contrib/i18n/README.md b/contrib/i18n/README.md deleted file mode 100644 index ce775ecb..00000000 --- a/contrib/i18n/README.md +++ /dev/null @@ -1,30 +0,0 @@ -`xgettext` command for extracting translation ---- - -``` -xgettext --omit-header -ctr: -ktr -kntr:1,2 daemon/HTTPServer.cpp libi2pd_client/HTTPProxy.cpp -``` - -Regex for transforming gettext translations to our format: ---- - -``` -in: ^(\"|#[:.,]|msgctxt)(.*)$\n -out: -``` - -``` -in: msgid\ \"(.*)\"\nmsgid_plural\ \"(.*)\"\nmsgstr\[0\]\ \"(.*)\"\n(msgstr\[1\]\ \"(.*)\"\n)?(msgstr\[2\]\ \"(.*)\"\n)?(msgstr\[3\]\ \"(.*)\"\n)?(msgstr\[4\]\ \"(.*)\"\n)?(msgstr\[5\]\ \"(.*)\"\n)? -out: #{"$2", {"$3", "$5", "$7", "$9", "$11"}},\n -``` - -``` -in: msgid\ \"(.*)\"\nmsgstr\ \"(.*)\"\n -out: {"$1", "$2"},\n -``` - - -``` -in: \n\n -out: \n -``` diff --git a/contrib/i2pd.conf b/contrib/i2pd.conf index c26f8af0..c87a2c0b 100644 --- a/contrib/i2pd.conf +++ b/contrib/i2pd.conf @@ -1,5 +1,5 @@ ## Configuration file for a typical i2pd user -## See https://i2pd.readthedocs.io/en/latest/user-guide/configuration/ +## See https://i2pd.readthedocs.org/en/latest/configuration.html ## for more options you can use in this file. ## Lines that begin with "## " try to explain what's going on. Lines @@ -10,60 +10,37 @@ ## Default: ~/.i2pd/tunnels.conf or /var/lib/i2pd/tunnels.conf # tunconf = /var/lib/i2pd/tunnels.conf -## Tunnels config files path -## Use that path to store separated tunnels in different config files. -## Default: ~/.i2pd/tunnels.d or /var/lib/i2pd/tunnels.d -# tunnelsdir = /var/lib/i2pd/tunnels.d - -## Path to certificates used for verifying .su3, families -## Default: ~/.i2pd/certificates or /var/lib/i2pd/certificates -# certsdir = /var/lib/i2pd/certificates - -## Where to write pidfile (default: /run/i2pd.pid, not used in Windows) -# pidfile = /run/i2pd.pid +## Where to write pidfile (don't write by default) +# pidfile = /var/run/i2pd.pid ## Logging configuration section ## By default logs go to stdout with level 'info' and higher -## For Windows OS by default logs go to file with level 'warn' and higher ## ## Logs destination (valid values: stdout, file, syslog) ## * stdout - print log entries to stdout ## * file - log entries to a file ## * syslog - use syslog, see man 3 syslog # log = file -## Path to logfile (default: autodetect) -# logfile = /var/log/i2pd/i2pd.log -## Log messages above this level (debug, info, *warn, error, critical, none) +## Path to logfile (default - autodetect) +# logfile = /var/log/i2pd.log +## Log messages above this level (debug, *info, warn, error, none) ## If you set it to none, logging will be disabled -# loglevel = warn -## Write full CLF-formatted date and time to log (default: write only time) -# logclftime = true +# loglevel = info -## Daemon mode. Router will go to background after start. Ignored on Windows -## (default: true) +## Path to storage of i2pd data (RI, keys, peer profiles, ...) +## Default: ~/.i2pd or /var/lib/i2pd +# datadir = /var/lib/i2pd + +## Daemon mode. Router will go to background after start # daemon = true +## Run as a service. Router will use system folders like ‘/var/lib/i2pd’ +# service = true ## Specify a family, router belongs to (default - none) # family = -## Network interface to bind to -## Updates address4/6 options if they are not set -# ifname = -## You can specify different interfaces for IPv4 and IPv6 -# ifname4 = -# ifname6 = - -## Local address to bind transport sockets to -## Overrides host option if: -## For ipv4: if ipv4 = true and nat = false -## For ipv6: if 'host' is not set or ipv4 = true -# address4 = -# address6 = - -## External IPv4 or IPv6 address to listen for connections +## External IP address to listen for connections ## By default i2pd sets IP automatically -## Sets published NTCP2v4/SSUv4 address to 'host' value if nat = true -## Sets published NTCP2v6/SSUv6 address to 'host' value if ipv4 = false # host = 1.2.3.4 ## Port to listen for connections @@ -71,133 +48,38 @@ ## don't just uncomment this # port = 4567 -## Enable communication through ipv4 (default: true) +## Enable communication through ipv4 ipv4 = true -## Enable communication through ipv6 (default: false) +## Enable communication through ipv6 ipv6 = false +## Network interface to bind to +# ifname = + +## Enable NTCP transport (default = true) +# ntcp = true +## Enable SSU transport (default = true) +# ssu = true + +## Should we assume we are behind NAT? (false only in MeshNet) +# nat = true + ## Bandwidth configuration -## L limit bandwidth to 32 KB/sec, O - to 256 KB/sec, P - to 2048 KB/sec, +## L limit bandwidth to 32KBs/sec, O - to 256KBs/sec, P - to 2048KBs/sec, ## X - unlimited -## Default is L (regular node) and X if floodfill mode enabled. -## If you want to share more bandwidth without floodfill mode, uncomment -## that line and adjust value to your possibilities. Value can be set to -## integer in kilobytes, it will apply that limit and flag will be used -## from next upper limit (example: if you set 4096 flag will be X, but real -## limit will be 4096 KB/s). Same can be done when floodfill mode is used, -## but keep in mind that low values may be negatively evaluated by Java -## router algorithms. +## Default is X for floodfill, L for regular node # bandwidth = L -## Max % of bandwidth limit for transit. 0-100 (default: 100) -# share = 100 ## Router will not accept transit tunnels, disabling transit traffic completely -## (default: false) +## (default = false) # notransit = true -## Router will be floodfill (default: false) -## Note: that mode uses much more network connections and CPU! +## Router will be floodfill # floodfill = true -[ntcp2] -## Enable NTCP2 transport (default: true) -# enabled = true -## Publish address in RouterInfo (default: true) -# published = true -## Port for incoming connections (default is global port option value) -# port = 4567 - -[ssu2] -## Enable SSU2 transport (default: true) -# enabled = true -## Publish address in RouterInfo (default: true) -# published = true -## Port for incoming connections (default is global port option value) -# port = 4567 - -[http] -## Web Console settings -## Enable the Web Console (default: true) -# enabled = true -## Address and port service will listen on (default: 127.0.0.1:7070) -# address = 127.0.0.1 -# port = 7070 -## Path to web console (default: /) -# webroot = / -## Enable Web Console authentication (default: false) -## You should not use Web Console via public networks without additional encryption. -## HTTP authentication is not encryption layer! -# auth = true -# user = i2pd -# pass = changeme -## Select webconsole language -## Currently supported english (default), afrikaans, armenian, chinese, czech, french, -## german, italian, polish, portuguese, russian, spanish, turkish, turkmen, ukrainian -## and uzbek languages -# lang = english - -[httpproxy] -## Enable the HTTP proxy (default: true) -# enabled = true -## Address and port service will listen on (default: 127.0.0.1:4444) -# address = 127.0.0.1 -# port = 4444 -## Optional keys file for proxy local destination (default: http-proxy-keys.dat) -# keys = http-proxy-keys.dat -## Enable address helper for adding .i2p domains with "jump URLs" (default: true) -## You should disable this feature if your i2pd HTTP Proxy is public, -## because anyone could spoof the short domain via addresshelper and forward other users to phishing links -# addresshelper = true -## Address of a proxy server inside I2P, which is used to visit regular Internet -# outproxy = http://false.i2p -## httpproxy section also accepts I2CP parameters, like "inbound.length" etc. - -[socksproxy] -## Enable the SOCKS proxy (default: true) -# enabled = true -## Address and port service will listen on (default: 127.0.0.1:4447) -# address = 127.0.0.1 -# port = 4447 -## Optional keys file for proxy local destination (default: socks-proxy-keys.dat) -# keys = socks-proxy-keys.dat -## Socks outproxy. Example below is set to use Tor for all connections except i2p -## Enable using of SOCKS outproxy (works only with SOCKS4, default: false) -# outproxy.enabled = false -## Address and port of outproxy -# outproxy = 127.0.0.1 -# outproxyport = 9050 -## socksproxy section also accepts I2CP parameters, like "inbound.length" etc. - -[sam] -## Enable the SAM bridge (default: true) -# enabled = false -## Address and ports service will listen on (default: 127.0.0.1:7656, udp: 7655) -# address = 127.0.0.1 -# port = 7656 -# portudp = 7655 - -[bob] -## Enable the BOB command channel (default: false) -# enabled = false -## Address and port service will listen on (default: 127.0.0.1:2827) -# address = 127.0.0.1 -# port = 2827 - -[i2cp] -## Enable the I2CP protocol (default: false) -# enabled = false -## Address and port service will listen on (default: 127.0.0.1:7654) -# address = 127.0.0.1 -# port = 7654 - -[i2pcontrol] -## Enable the I2PControl protocol (default: false) -# enabled = false -## Address and port service will listen on (default: 127.0.0.1:7650) -# address = 127.0.0.1 -# port = 7650 -## Authentication password (default: itoopie) -# password = itoopie +[limits] +## Maximum active transit sessions (default:2500) +# transittunnels = 2500 [precomputation] ## Enable or disable elgamal precomputation table @@ -207,73 +89,84 @@ ipv6 = false [upnp] ## Enable or disable UPnP: automatic port forwarding (enabled by default in WINDOWS, ANDROID) # enabled = false -## Name i2pd appears in UPnP forwardings list (default: I2Pd) + +## Name i2pd appears in UPnP forwardings list (default = I2Pd) # name = I2Pd -[meshnets] -## Enable connectivity over the Yggdrasil network (default: false) -# yggdrasil = false -## You can bind address from your Yggdrasil subnet 300::/64 -## The address must first be added to the network interface -# yggaddress = - [reseed] -## Options for bootstrapping into I2P network, aka reseeding -## Enable reseed data verification (default: true) +## Enable or disable reseed data verification. verify = true ## URLs to request reseed data from, separated by comma ## Default: "mainline" I2P Network reseeds # urls = https://reseed.i2p-projekt.de/,https://i2p.mooo.com/netDb/,https://netdb.i2p2.no/ -## Reseed URLs through the Yggdrasil, separated by comma -# yggurls = http://[324:71e:281a:9ed3::ace]:7070/ ## 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 (default: 25) -# threshold = 25 [addressbook] ## AddressBook subscription URL for initial setup -## Default: reg.i2p at "mainline" I2P Network -# defaulturl = http://shx5vqsw7usdaunyzr2qmes2fq37oumybpudrd4jjj4e4vk4uusa.b32.i2p/hosts.txt +## Default: inr.i2p at "mainline" I2P Network +# defaulturl = http://joajgazyztfssty4w2on5oaqksz6tqoxbduy553y34mf4byv6gpq.b32.i2p/export/alive-hosts.txt ## Optional subscriptions URLs, separated by comma -# subscriptions = http://reg.i2p/hosts.txt,http://identiguy.i2p/hosts.txt,http://stats.i2p/cgi-bin/newhosts.txt +# subscriptions = http://inr.i2p/export/alive-hosts.txt,http://stats.i2p/cgi-bin/newhosts.txt,http://rus.i2p/hosts.txt -[limits] -## Maximum active transit sessions (default: 5000) -## This value is doubled if floodfill mode is enabled! -# transittunnels = 5000 -## Limit number of open file descriptors (0 - use system limit) -# openfiles = 0 -## Maximum size of corefile in Kb (0 - use system limit) -# coresize = 0 - -[trust] -## Enable explicit trust options. (default: false) +[http] +## Uncomment and set to 'false' to disable Web Console # enabled = true -## Make direct I2P connections only to routers in specified Family. -# family = MyFamily -## Make direct I2P connections only to routers specified here. Comma separated list of base64 identities. -# routers = -## Should we hide our router from other routers? (default: false) -# hidden = true +## Address and port service will listen on +address = 127.0.0.1 +port = 7070 -[exploratory] -## Exploratory tunnels settings with default values -# inbound.length = 2 -# inbound.quantity = 3 -# outbound.length = 2 -# outbound.quantity = 3 +[httpproxy] +## Uncomment and set to 'false' to disable HTTP Proxy +# enabled = true +## Address and port service will listen on +address = 127.0.0.1 +port = 4444 +## Optional keys file for proxy local destination +# keys = http-proxy-keys.dat -[persist] -## Save peer profiles on disk (default: true) -# profiles = true -## Save full addresses on disk (default: true) -# addressbook = true +[socksproxy] +## Uncomment and set to 'false' to disable SOCKS Proxy +# enabled = true +## Address and port service will listen on +address = 127.0.0.1 +port = 4447 +## Optional keys file for proxy local destination +# keys = socks-proxy-keys.dat +## Socks outproxy. Example below is set to use Tor for all connections except i2p +## Uncomment and set to 'true' to enable using of SOCKS outproxy +# outproxy.enabled = false +## Address and port of outproxy +# outproxy = 127.0.0.1 +# outproxyport = 9050 + +[sam] +## Uncomment and set to 'true' to enable SAM Bridge +enabled = true +## Address and port service will listen on +# address = 127.0.0.1 +# port = 7656 + +[bob] +## Uncomment and set to 'true' to enable BOB command channel +# enabled = false +## Address and port service will listen on +# address = 127.0.0.1 +# port = 2827 + +[i2cp] +## Uncomment and set to 'true' to enable I2CP protocol +# enabled = false +## Address and port service will listen on +# address = 127.0.0.1 +# port = 7654 + +[i2pcontrol] +## Uncomment and set to 'true' to enable I2PControl protocol +# enabled = false +## Address and port service will listen on +# address = 127.0.0.1 +# port = 7650 diff --git a/contrib/i2pd.logrotate b/contrib/i2pd.logrotate deleted file mode 100644 index d0ca70ad..00000000 --- a/contrib/i2pd.logrotate +++ /dev/null @@ -1,9 +0,0 @@ -"/var/log/i2pd/*.log" { - copytruncate - daily - rotate 5 - compress - delaycompress - missingok - notifempty -} diff --git a/contrib/i2pd.service b/contrib/i2pd.service deleted file mode 100644 index 1ab46979..00000000 --- a/contrib/i2pd.service +++ /dev/null @@ -1,38 +0,0 @@ -[Unit] -Description=I2P Router written in C++ -Documentation=man:i2pd(1) https://i2pd.readthedocs.io/en/latest/ -Wants=network.target -After=network.target network-online.target - -[Service] -User=i2pd -Group=i2pd -RuntimeDirectory=i2pd -RuntimeDirectoryMode=0700 -LogsDirectory=i2pd -LogsDirectoryMode=0700 -Type=forking -ExecStart=/usr/bin/i2pd --conf=/etc/i2pd/i2pd.conf --tunconf=/etc/i2pd/tunnels.conf --tunnelsdir=/etc/i2pd/tunnels.conf.d --pidfile=/run/i2pd/i2pd.pid --logfile=/var/log/i2pd/i2pd.log --daemon --service -ExecReload=/bin/sh -c "kill -HUP $MAINPID" -PIDFile=/run/i2pd/i2pd.pid -### Uncomment, if auto restart needed -#Restart=on-failure - -# Use SIGTERM to stop i2pd immediately. -# Some cleanup processes can delay stopping, so we set 30 seconds timeout and then SIGKILL i2pd. -KillSignal=SIGTERM -TimeoutStopSec=30s -SendSIGKILL=yes - -# If you have the patience waiting 10 min on restarting/stopping it, uncomment this. -# i2pd stops accepting new tunnels and waits ~10 min while old ones do not die. -#KillSignal=SIGINT -#TimeoutStopSec=10m - -# If you have problems with hanging i2pd, you can try increase this -LimitNOFILE=8192 -# To enable write of coredump uncomment this -#LimitCORE=infinity - -[Install] -WantedBy=multi-user.target diff --git a/contrib/rpm/i2pd-git.spec b/contrib/rpm/i2pd-git.spec deleted file mode 100644 index 2083ba18..00000000 --- a/contrib/rpm/i2pd-git.spec +++ /dev/null @@ -1,287 +0,0 @@ -%define git_hash %(git rev-parse HEAD | cut -c -7) - -Name: i2pd-git -Version: 2.56.0 -Release: git%{git_hash}%{?dist} -Summary: I2P router written in C++ -Conflicts: i2pd - -License: BSD -URL: https://github.com/PurpleI2P/i2pd -Source0: https://github.com/PurpleI2P/i2pd/archive/openssl/i2pd-openssl.tar.gz - -%if 0%{?rhel} == 7 -BuildRequires: cmake3 -%else -BuildRequires: cmake -%endif - -BuildRequires: chrpath -BuildRequires: gcc-c++ -BuildRequires: zlib-devel -BuildRequires: boost-devel -BuildRequires: openssl-devel -BuildRequires: miniupnpc-devel -BuildRequires: systemd-units - -%if 0%{?fedora} == 41 -BuildRequires: openssl-devel-engine -%endif - -Requires: logrotate -Requires: systemd -Requires(pre): %{_sbindir}/useradd %{_sbindir}/groupadd - - -%description -C++ implementation of I2P. - - -%prep -%setup -q -n i2pd-openssl - - -%build -cd build -%if 0%{?rhel} == 7 - %cmake3 \ - -DWITH_LIBRARY=OFF \ - -DWITH_UPNP=ON \ - -DWITH_HARDENING=ON \ - -DBUILD_SHARED_LIBS:BOOL=OFF -%else - %cmake \ - -DWITH_LIBRARY=OFF \ - -DWITH_UPNP=ON \ - -DWITH_HARDENING=ON \ - %if 0%{?fedora} > 29 - -DBUILD_SHARED_LIBS:BOOL=OFF \ - . - %else - -DBUILD_SHARED_LIBS:BOOL=OFF - %endif -%endif - -%if 0%{?rhel} == 9 || 0%{?fedora} >= 35 || 0%{?eln} - pushd redhat-linux-build -%else - %if 0%{?fedora} >= 33 - pushd %{_target_platform} - %endif - - %if 0%{?mageia} > 7 - pushd build - %endif -%endif - -make %{?_smp_mflags} - -%if 0%{?rhel} == 9 || 0%{?fedora} >= 33 || 0%{?mageia} > 7 - popd -%endif - - -%install -pushd build - -%if 0%{?rhel} == 9 || 0%{?fedora} >= 35 || 0%{?eln} - pushd redhat-linux-build -%else - %if 0%{?fedora} >= 33 - pushd %{_target_platform} - %endif - - %if 0%{?mageia} - pushd build - %endif -%endif - -chrpath -d i2pd -%{__install} -D -m 755 i2pd %{buildroot}%{_bindir}/i2pd -%{__install} -d -m 755 %{buildroot}%{_datadir}/i2pd -%{__install} -d -m 700 %{buildroot}%{_sharedstatedir}/i2pd -%{__install} -d -m 700 %{buildroot}%{_localstatedir}/log/i2pd -%{__install} -D -m 644 %{_builddir}/i2pd-openssl/contrib/i2pd.conf %{buildroot}%{_sysconfdir}/i2pd/i2pd.conf -%{__install} -D -m 644 %{_builddir}/i2pd-openssl/contrib/subscriptions.txt %{buildroot}%{_sysconfdir}/i2pd/subscriptions.txt -%{__install} -D -m 644 %{_builddir}/i2pd-openssl/contrib/tunnels.conf %{buildroot}%{_sysconfdir}/i2pd/tunnels.conf -%{__install} -D -m 644 %{_builddir}/i2pd-openssl/contrib/i2pd.logrotate %{buildroot}%{_sysconfdir}/logrotate.d/i2pd -%{__install} -D -m 644 %{_builddir}/i2pd-openssl/contrib/i2pd.service %{buildroot}%{_unitdir}/i2pd.service -%{__install} -D -m 644 %{_builddir}/i2pd-openssl/debian/i2pd.1 %{buildroot}%{_mandir}/man1/i2pd.1 -%{__cp} -r %{_builddir}/i2pd-openssl/contrib/certificates/ %{buildroot}%{_datadir}/i2pd/certificates -%{__cp} -r %{_builddir}/i2pd-openssl/contrib/tunnels.d/ %{buildroot}%{_sysconfdir}/i2pd/tunnels.conf.d -ln -s %{_datadir}/%{name}/certificates %{buildroot}%{_sharedstatedir}/i2pd/certificates - - -%pre -getent group i2pd >/dev/null || %{_sbindir}/groupadd -r i2pd -getent passwd i2pd >/dev/null || \ - %{_sbindir}/useradd -r -g i2pd -s %{_sbindir}/nologin \ - -d %{_sharedstatedir}/i2pd -c 'I2P Service' i2pd - - -%post -%systemd_post i2pd.service - - -%preun -%systemd_preun i2pd.service - - -%postun -%systemd_postun_with_restart i2pd.service - - -%files -%doc LICENSE README.md contrib/i2pd.conf contrib/subscriptions.txt contrib/tunnels.conf contrib/tunnels.d -%{_bindir}/i2pd -%config(noreplace) %{_sysconfdir}/i2pd/*.conf -%config(noreplace) %{_sysconfdir}/i2pd/tunnels.conf.d/*.conf -%config %{_sysconfdir}/i2pd/subscriptions.txt -%doc %{_sysconfdir}/i2pd/tunnels.conf.d/README -%{_sysconfdir}/logrotate.d/i2pd -%{_unitdir}/i2pd.service -%{_mandir}/man1/i2pd.1* -%dir %attr(0700,i2pd,i2pd) %{_sharedstatedir}/i2pd -%dir %attr(0700,i2pd,i2pd) %{_localstatedir}/log/i2pd -%{_datadir}/i2pd/certificates -%{_sharedstatedir}/i2pd/certificates - - -%changelog -* Tue Feb 11 2025 orignal - 2.56.0 -- update to 2.56.0 - -* Mon Dec 30 2024 orignal - 2.55.0 -- update to 2.55.0 - -* Sun Oct 6 2024 orignal - 2.54.0 -- update to 2.54.0 - -* Tue Jul 30 2024 orignal - 2.53.1 -- update to 2.53.1 - -* Fri Jul 19 2024 orignal - 2.53.0 -- update to 2.53.0 - -* Sun May 12 2024 orignal - 2.52.0 -- update to 2.52.0 - -* Sat Apr 06 2024 orignal - 2.51.0 -- update to 2.51.0 - -* Sat Jan 06 2024 orignal - 2.50.2 -- update to 2.50.2 - -* Sat Dec 23 2023 r4sas - 2.50.1 -- update to 2.50.1 - -* Mon Dec 18 2023 orignal - 2.50.0 -- update to 2.50.0 - -* Mon Sep 18 2023 orignal - 2.49.0 -- update to 2.49.0 - -* Mon Jun 12 2023 orignal - 2.48.0 -- update to 2.48.0 - -* Sat Mar 11 2023 orignal - 2.47.0 -- update to 2.47.0 - -* Mon Feb 20 2023 r4sas - 2.46.1 -- update to 2.46.1 - -* Wed Feb 15 2023 orignal - 2.46.0 -- update to 2.46.0 - -* Wed Jan 11 2023 orignal - 2.45.1 -- update to 2.45.1 - -* Tue Jan 3 2023 orignal - 2.45.0 -- update to 2.45.0 - -* Sun Nov 20 2022 orignal - 2.44.0 -- update to 2.44.0 - -* Mon Aug 22 2022 orignal - 2.43.0 -- update to 2.43.0 - -* Tue May 24 2022 r4sas - 2.42.1 -- update to 2.42.1 - -* Sun May 22 2022 orignal - 2.42.0 -- update to 2.42.0 - -* Sun Feb 20 2022 r4sas - 2.41.0 -- update to 2.41.0 -- fixed build on Fedora Copr over openssl trunk code - -* Mon Nov 29 2021 orignal - 2.40.0 -- update to 2.40.0 - -* Tue Aug 24 2021 r4sas - 2.39.0-2 -- changed if statements to cover fedora 35 - -* Mon Aug 23 2021 orignal - 2.39.0 -- update to 2.39.0 -- fixed build on fedora 36 - -* Mon May 17 2021 orignal - 2.38.0 -- update to 2.38.0 - -* Mon Mar 15 2021 orignal - 2.37.0 -- update to 2.37.0 - -* Mon Feb 15 2021 orignal - 2.36.0 -- update to 2.36.0 - -* Mon Nov 30 2020 orignal - 2.35.0 -- update to 2.35.0 - -* Tue Oct 27 2020 orignal - 2.34.0 -- update to 2.34.0 - -* Mon Aug 24 2020 orignal - 2.33.0 -- update to 2.33.0 - -* Tue Jun 02 2020 r4sas - 2.32.1 -- update to 2.32.1 - -* Mon May 25 2020 r4sas - 2.32.0 -- update to 2.32.0 -- updated systemd service file (#1394) - -* Thu May 7 2020 Anatolii Vorona - 2.31.0-3 -- added RPM logrotate config - -* Fri Apr 10 2020 orignal - 2.31.0 -- update to 2.31.0 - -* Tue Feb 25 2020 orignal - 2.30.0 -- update to 2.30.0 - -* Mon Oct 21 2019 orignal - 2.29.0 -- update to 2.29.0 - -* Tue Aug 27 2019 orignal - 2.28.0 -- update to 2.28.0 - -* Wed Jul 3 2019 orignal - 2.27.0 -- update to 2.27.0 - -* Fri Jun 7 2019 orignal - 2.26.0 -- update to 2.26.0 - -* Thu May 9 2019 orignal - 2.25.0 -- update to 2.25.0 - -* Thu Mar 21 2019 orignal - 2.24.0 -- update to 2.24.0 - -* Mon Jan 21 2019 orignal - 2.23.0 -- update to 2.23.0 - -* Fri Nov 09 2018 r4sas - 2.22.0 -- add support of tunnelsdir option - -* Thu Feb 01 2018 r4sas - 2.18.0 -- Initial i2pd-git based on i2pd 2.18.0-1 spec diff --git a/contrib/rpm/i2pd.service b/contrib/rpm/i2pd.service new file mode 100644 index 00000000..ec5949ff --- /dev/null +++ b/contrib/rpm/i2pd.service @@ -0,0 +1,27 @@ +[Unit] +Description=I2P Router written in C++ +After=network.target + +[Service] +User=i2pd +Group=i2pd +RuntimeDirectory=i2pd +RuntimeDirectoryMode=0700 +Type=simple +ExecStart=/usr/sbin/i2pd --conf=/etc/i2pd/i2pd.conf --tunconf=/etc/i2pd/tunnels.conf --pidfile=/var/run/i2pd/i2pd.pid --logfile=/var/log/i2pd/i2pd.log --daemon --service +ExecReload=/bin/kill -HUP $MAINPID +PIDFile=/var/run/i2pd/i2pd.pid +### Uncomment, if auto restart needed +#Restart=on-failure + +### Use SIGINT for graceful stop daemon. +# i2pd stops accepting new tunnels and waits ~10 min while old ones do not die. +KillSignal=SIGINT +TimeoutStopSec=10m + +# If you have problems with hunging i2pd, you can try enable this +#LimitNOFILE=4096 +PrivateDevices=yes + +[Install] +WantedBy=multi-user.target diff --git a/contrib/rpm/i2pd.spec b/contrib/rpm/i2pd.spec index 4eb558ba..316be961 100644 --- a/contrib/rpm/i2pd.spec +++ b/contrib/rpm/i2pd.spec @@ -1,40 +1,35 @@ -Name: i2pd -Version: 2.56.0 -Release: 1%{?dist} -Summary: I2P router written in C++ -Conflicts: i2pd-git +%define build_timestamp %(date +"%Y%m%d") -License: BSD -URL: https://github.com/PurpleI2P/i2pd -Source0: https://github.com/PurpleI2P/i2pd/archive/%{version}/%name-%version.tar.gz +Name: i2pd +Version: 2.18.0 +Release: %{build_timestamp}git%{?dist} +Summary: I2P router written in C++ +Obsoletes: %{name}-systemd -%if 0%{?rhel} == 7 -BuildRequires: cmake3 +License: BSD +URL: https://github.com/PurpleI2P/i2pd +Source0: https://github.com/PurpleI2P/i2pd/archive/%{version}/%name-%version.tar.gz + +%if 0%{?rhel} == 7 +BuildRequires: cmake3 %else -BuildRequires: cmake +BuildRequires: cmake %endif -BuildRequires: chrpath -BuildRequires: gcc-c++ -BuildRequires: zlib-devel -BuildRequires: boost-devel -BuildRequires: openssl-devel -BuildRequires: miniupnpc-devel -BuildRequires: systemd-units - -%if 0%{?fedora} == 41 -BuildRequires: openssl-devel-engine -%endif - -Requires: logrotate -Requires: systemd -Requires(pre): %{_sbindir}/useradd %{_sbindir}/groupadd +BuildRequires: chrpath +BuildRequires: gcc-c++ +BuildRequires: zlib-devel +BuildRequires: boost-devel +BuildRequires: openssl-devel +BuildRequires: miniupnpc-devel +BuildRequires: systemd-units +Requires: systemd +Requires(pre): %{_sbindir}/useradd %{_sbindir}/groupadd %description C++ implementation of I2P. - %prep %setup -q @@ -42,72 +37,34 @@ C++ implementation of I2P. %build cd build %if 0%{?rhel} == 7 - %cmake3 \ +%cmake3 \ -DWITH_LIBRARY=OFF \ -DWITH_UPNP=ON \ -DWITH_HARDENING=ON \ -DBUILD_SHARED_LIBS:BOOL=OFF %else - %cmake \ +%cmake \ -DWITH_LIBRARY=OFF \ -DWITH_UPNP=ON \ -DWITH_HARDENING=ON \ - %if 0%{?fedora} > 29 - -DBUILD_SHARED_LIBS:BOOL=OFF \ - . - %else -DBUILD_SHARED_LIBS:BOOL=OFF - %endif -%endif - -%if 0%{?rhel} == 9 || 0%{?fedora} >= 35 || 0%{?eln} - pushd redhat-linux-build -%else - %if 0%{?fedora} >= 33 - pushd %{_target_platform} - %endif - - %if 0%{?mageia} > 7 - pushd build - %endif %endif make %{?_smp_mflags} -%if 0%{?rhel} == 9 || 0%{?fedora} >= 33 || 0%{?mageia} > 7 - popd -%endif - %install -pushd build - -%if 0%{?rhel} == 9 || 0%{?fedora} >= 35 || 0%{?eln} - pushd redhat-linux-build -%else - %if 0%{?fedora} >= 33 - pushd %{_target_platform} - %endif - - %if 0%{?mageia} - pushd build - %endif -%endif - +cd build chrpath -d i2pd -%{__install} -D -m 755 i2pd %{buildroot}%{_bindir}/i2pd -%{__install} -d -m 755 %{buildroot}%{_datadir}/i2pd -%{__install} -d -m 700 %{buildroot}%{_sharedstatedir}/i2pd -%{__install} -d -m 700 %{buildroot}%{_localstatedir}/log/i2pd -%{__install} -D -m 644 %{_builddir}/%{name}-%{version}/contrib/i2pd.conf %{buildroot}%{_sysconfdir}/i2pd/i2pd.conf -%{__install} -D -m 644 %{_builddir}/%{name}-%{version}/contrib/subscriptions.txt %{buildroot}%{_sysconfdir}/i2pd/subscriptions.txt -%{__install} -D -m 644 %{_builddir}/%{name}-%{version}/contrib/tunnels.conf %{buildroot}%{_sysconfdir}/i2pd/tunnels.conf -%{__install} -D -m 644 %{_builddir}/%{name}-%{version}/contrib/i2pd.logrotate %{buildroot}%{_sysconfdir}/logrotate.d/i2pd -%{__install} -D -m 644 %{_builddir}/%{name}-%{version}/contrib/i2pd.service %{buildroot}%{_unitdir}/i2pd.service -%{__install} -D -m 644 %{_builddir}/%{name}-%{version}/debian/i2pd.1 %{buildroot}%{_mandir}/man1/i2pd.1 +install -D -m 755 i2pd %{buildroot}%{_sbindir}/i2pd +install -D -m 755 %{_builddir}/%{name}-%{version}/contrib/i2pd.conf %{buildroot}%{_sysconfdir}/i2pd/i2pd.conf +install -D -m 755 %{_builddir}/%{name}-%{version}/contrib/tunnels.conf %{buildroot}%{_sysconfdir}/i2pd/tunnels.conf +install -d -m 755 %{buildroot}/%{_datadir}/i2pd %{__cp} -r %{_builddir}/%{name}-%{version}/contrib/certificates/ %{buildroot}%{_datadir}/i2pd/certificates -%{__cp} -r %{_builddir}/%{name}-%{version}/contrib/tunnels.d/ %{buildroot}%{_sysconfdir}/i2pd/tunnels.conf.d -ln -s %{_datadir}/%{name}/certificates %{buildroot}%{_sharedstatedir}/i2pd/certificates +install -D -m 644 %{_builddir}/%{name}-%{version}/contrib/rpm/i2pd.service %{buildroot}/%{_unitdir}/i2pd.service +install -d -m 700 %{buildroot}/%{_sharedstatedir}/i2pd +install -d -m 700 %{buildroot}/%{_localstatedir}/log/i2pd +ln -s %{_datadir}/%{name}/certificates %{buildroot}%{_sharedstatedir}/%{name}/certificates %pre @@ -130,178 +87,18 @@ getent passwd i2pd >/dev/null || \ %files -%doc LICENSE README.md contrib/i2pd.conf contrib/subscriptions.txt contrib/tunnels.conf contrib/tunnels.d -%{_bindir}/i2pd -%config(noreplace) %{_sysconfdir}/i2pd/*.conf -%config(noreplace) %{_sysconfdir}/i2pd/tunnels.conf.d/*.conf -%config %{_sysconfdir}/i2pd/subscriptions.txt -%doc %{_sysconfdir}/i2pd/tunnels.conf.d/README -%{_sysconfdir}/logrotate.d/i2pd -%{_unitdir}/i2pd.service -%{_mandir}/man1/i2pd.1* -%dir %attr(0700,i2pd,i2pd) %{_sharedstatedir}/i2pd -%dir %attr(0700,i2pd,i2pd) %{_localstatedir}/log/i2pd +%doc LICENSE README.md +%{_sbindir}/i2pd %{_datadir}/i2pd/certificates +%config(noreplace) %{_sysconfdir}/i2pd/* +/%{_unitdir}/i2pd.service +%dir %attr(0700,i2pd,i2pd) %{_localstatedir}/log/i2pd +%dir %attr(0700,i2pd,i2pd) %{_sharedstatedir}/i2pd %{_sharedstatedir}/i2pd/certificates %changelog -* Tue Feb 11 2025 orignal - 2.56.0 -- update to 2.56.0 - -* Mon Dec 30 2024 orignal - 2.55.0 -- update to 2.55.0 - -* Sun Oct 6 2024 orignal - 2.54.0 -- update to 2.54.0 - -* Tue Jul 30 2024 orignal - 2.53.1 -- update to 2.53.1 - -* Fri Jul 19 2024 orignal - 2.53.0 -- update to 2.53.0 - -* Sun May 12 2024 orignal - 2.52.0 -- update to 2.52.0 - -* Sat Apr 06 2024 orignal - 2.51.0 -- update to 2.51.0 - -* Sat Jan 06 2024 orignal - 2.50.2 -- update to 2.50.2 - -* Sat Dec 23 2023 r4sas - 2.50.1 -- update to 2.50.1 - -* Mon Dec 18 2023 orignal - 2.50.0 -- update to 2.50.0 - -* Mon Sep 18 2023 orignal - 2.49.0 -- update to 2.49.0 - -* Mon Jun 12 2023 orignal - 2.48.0 -- update to 2.48.0 - -* Sat Mar 11 2023 orignal - 2.47.0 -- update to 2.47.0 - -* Mon Feb 20 2023 r4sas - 2.46.1 -- update to 2.46.1 - -* Wed Feb 15 2023 orignal - 2.46.0 -- update to 2.46.0 - -* Wed Jan 11 2023 orignal - 2.45.1 -- update to 2.45.1 - -* Tue Jan 3 2023 orignal - 2.45.0 -- update to 2.45.0 - -* Sun Nov 20 2022 orignal - 2.44.0 -- update to 2.44.0 - -* Mon Aug 22 2022 orignal - 2.43.0 -- update to 2.43.0 - -* Tue May 24 2022 r4sas - 2.42.1 -- update to 2.42.1 - -* Sun May 22 2022 orignal - 2.42.0 -- update to 2.42.0 - -* Sun Feb 20 2022 r4sas - 2.41.0 -- update to 2.41.0 - -* Mon Nov 29 2021 orignal - 2.40.0 -- update to 2.40.0 - -* Tue Aug 24 2021 r4sas - 2.39.0-2 -- changed if statements to cover fedora 35 - -* Mon Aug 23 2021 orignal - 2.39.0 -- update to 2.39.0 -- fixed build on fedora 36 - -* Mon May 17 2021 orignal - 2.38.0 -- update to 2.38.0 - -* Mon Mar 15 2021 orignal - 2.37.0 -- update to 2.37.0 - -* Mon Feb 15 2021 orignal - 2.36.0 -- update to 2.36.0 - -* Mon Nov 30 2020 orignal - 2.35.0 -- update to 2.35.0 - -* Tue Oct 27 2020 orignal - 2.34.0 -- update to 2.34.0 - -* Mon Aug 24 2020 orignal - 2.33.0 -- update to 2.33.0 - -* Tue Jun 02 2020 r4sas - 2.32.1 -- update to 2.32.1 - -* Mon May 25 2020 r4sas - 2.32.0 -- update to 2.32.0 -- updated systemd service file (#1394) - -* Thu May 7 2020 Anatolii Vorona - 2.31.0-3 -- added RPM logrotate config - -* Fri Apr 10 2020 orignal - 2.31.0 -- update to 2.31.0 - -* Tue Feb 25 2020 orignal - 2.30.0 -- update to 2.30.0 - -* Mon Oct 21 2019 orignal - 2.29.0 -- update to 2.29.0 - -* Tue Aug 27 2019 orignal - 2.28.0 -- update to 2.28.0 - -* Wed Jul 3 2019 orignal - 2.27.0 -- update to 2.27.0 - -* Fri Jun 7 2019 orignal - 2.26.0 -- update to 2.26.0 - -* Thu May 9 2019 orignal - 2.25.0 -- update to 2.25.0 - -* Thu Mar 21 2019 orignal - 2.24.0 -- update to 2.24.0 - -* Mon Jan 21 2019 orignal - 2.23.0 -- update to 2.23.0 - -* Fri Nov 09 2018 r4sas - 2.22.0 -- update to 2.22.0 -- add support of tunnelsdir option - -* Mon Oct 22 2018 orignal - 2.21.1 -- update to 2.21.1 - -* Thu Oct 4 2018 orignal - 2.21.0 -- update to 2.21.0 - -* Thu Aug 23 2018 orignal - 2.20.0 -- update to 2.20.0 - -* Tue Jun 26 2018 orignal - 2.19.0 -- update to 2.19.0 - -* Mon Feb 05 2018 r4sas - 2.18.0-2 -- Fixed blocking system shutdown for 10 minutes (#1089) - -* Thu Feb 01 2018 r4sas - 2.18.0-1 -- Added to conflicts i2pd-git package -- Fixed release versioning -- Fixed paths with double slashes - -* Tue Jan 30 2018 orignal - 2.18.0 +* Tue Jan 30 2018 orignal > - 2.18.0 - update to 2.18.0 * Sat Jan 27 2018 l-n-s - 2.17.0-1 diff --git a/contrib/subscriptions.txt b/contrib/subscriptions.txt index 76d73993..8f4afb03 100644 --- a/contrib/subscriptions.txt +++ b/contrib/subscriptions.txt @@ -1,4 +1,3 @@ -http://reg.i2p/hosts.txt -http://identiguy.i2p/hosts.txt +http://inr.i2p/export/alive-hosts.txt http://stats.i2p/cgi-bin/newhosts.txt http://i2p-projekt.i2p/hosts.txt diff --git a/contrib/tunnels.conf b/contrib/tunnels.conf index fc455e79..be8681dc 100644 --- a/contrib/tunnels.conf +++ b/contrib/tunnels.conf @@ -1,17 +1,16 @@ -[IRC-ILITA] +[IRC-IRC2P] type = client address = 127.0.0.1 port = 6668 -destination = irc.ilita.i2p +destination = irc.postman.i2p destinationport = 6667 keys = irc-keys.dat -i2p.streaming.profile=2 -#[IRC-IRC2P] +#[IRC-ILITA] #type = client #address = 127.0.0.1 #port = 6669 -#destination = irc.postman.i2p +#destination = irc.ilita.i2p #destinationport = 6667 #keys = irc-keys.dat @@ -31,4 +30,4 @@ i2p.streaming.profile=2 #destinationport = 110 #keys = pop3-keys.dat -# see more examples at https://i2pd.readthedocs.io/en/latest/user-guide/tunnels/ +# see more examples in /usr/share/doc/i2pd/configuration.md.gz diff --git a/contrib/tunnels.d/IRC-Ilita.conf b/contrib/tunnels.d/IRC-Ilita.conf deleted file mode 100644 index 74d836aa..00000000 --- a/contrib/tunnels.d/IRC-Ilita.conf +++ /dev/null @@ -1,7 +0,0 @@ -#[IRC-ILITA] -#type = client -#address = 127.0.0.1 -#port = 6669 -#destination = irc.ilita.i2p -#destinationport = 6667 -#keys = irc-keys.dat diff --git a/contrib/tunnels.d/IRC-Irc2P.conf b/contrib/tunnels.d/IRC-Irc2P.conf deleted file mode 100644 index 97da71dc..00000000 --- a/contrib/tunnels.d/IRC-Irc2P.conf +++ /dev/null @@ -1,7 +0,0 @@ -#[IRC-IRC2P] -#type = client -#address = 127.0.0.1 -#port = 6668 -#destination = irc.postman.i2p -#destinationport = 6667 -#keys = irc-keys.dat diff --git a/contrib/tunnels.d/README b/contrib/tunnels.d/README deleted file mode 100644 index 7b07c4be..00000000 --- a/contrib/tunnels.d/README +++ /dev/null @@ -1,4 +0,0 @@ -# In that directory you can store separated config files for every tunnel. -# Please read documentation for more info. -# -# You can find examples in /usr/share/doc/i2pd/tunnels.d directory diff --git a/contrib/webconsole/style.css b/contrib/webconsole/style.css deleted file mode 100644 index 89021539..00000000 --- a/contrib/webconsole/style.css +++ /dev/null @@ -1,293 +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 - * - ****************************************************************** - * - * This is style sheet for webconsole, with @media selectors for adaptive - * view on desktop and mobile devices, respecting preferred user's color - * scheme used in system/browser. - * - * Minified copy of that style sheet is bundled inside i2pd sources. -*/ - -:root { - --main-bg-color: #fafafa; - --main-text-color: #103456; - --main-link-color: #894c84; - --main-link-hover-color: #fafafa; -} - -@media (prefers-color-scheme: dark) { - :root { - --main-bg-color: #242424; - --main-text-color: #17ab5c; - --main-link-color: #bf64b7; - --main-link-hover-color: #000000; - } -} - -body { - font: 100%/1.5em sans-serif; - margin: 0; - padding: 1.5em; - background: var(--main-bg-color); - color: var(--main-text-color); -} - -a, .slide label { - text-decoration: none; - color: var(--main-link-color); -} - -a:hover, a.button.selected, .slide label:hover, button[type=submit]:hover { - color: var(--main-link-hover-color); - background: var(--main-link-color); -} - -a.button { - appearance: button; - text-decoration: none; - padding: 0 5px; - border: 1px solid var(--main-link-color); -} - -.header { - font-size: 2.5em; - text-align: center; - margin: 1em 0; - color: var(--main-link-color); -} - -.wrapper { - margin: 0 auto; - padding: 1em; - max-width: 64em; -} - -.menu { - display: block; - float: left; - overflow: hidden; - padding: 4px; - max-width: 12em; - white-space: nowrap; - text-overflow: ellipsis; -} - -.listitem { - display: block; - font-family: monospace; - font-size: 1.2em; - white-space: nowrap; -} - -.tableitem { - font-family: monospace; - font-size: 1.2em; - white-space: nowrap; -} - -.content { - float: left; - font-size: 1em; - margin-left: 2em; - padding: 4px; - max-width: 50em; - overflow: auto; -} - -.tunnel.established { - color: #56B734; -} - -.tunnel.expiring { - color: #D3AE3F; -} - -.tunnel.failed { - color: #D33F3F; -} - -.tunnel.building { - color: #434343; -} - -caption { - font-size: 1.5em; - text-align: center; - color: var(--main-link-color); -} - -table { - display: table; - border-collapse: collapse; - text-align: center; -} - -table.extaddr { - text-align: left; -} - -table.services { - width: 100%; -} - -textarea { - background-color: var(--main-bg-color); - color: var(--main-text-color); - word-break: break-all; -} - -.streamdest { - width: 120px; - max-width: 240px; - overflow: hidden; - text-overflow: ellipsis; -} - -.slide div.slidecontent, .slide [type="checkbox"] { - display: none; -} - -.slide [type="checkbox"]:checked ~ div.slidecontent { - display: block; - margin-top: 0; - padding: 0; -} - -.disabled { - color: #D33F3F; -} - -.enabled { - color: #56B734; -} - -button[type=submit] { - background-color: transparent; - color: var(--main-link-color); - text-decoration: none; - padding: 5px; - border: 1px solid var(--main-link-color); - font-size: 14px; -} - -input, select, select option { - background-color: var(--main-bg-color); - color: var(--main-link-color); - padding: 5px; - border: 1px solid var(--main-link-color); - font-size: 14px; -} - -input:focus, select:focus, select option:focus { - outline: none; -} - -input[type=number]::-webkit-inner-spin-button { - -webkit-appearance: none; -} - -@media screen and (max-width: 1150px) { /* adaptive style */ - .wrapper { - max-width: 58em; - } - - .content { - max-width: 40em; - } -} - -@media screen and (max-width: 980px) { - body { - font: 100%/1.2em sans-serif; - padding: 1.2em 0 0 0; - } - - .menu { - width: 100%; - max-width: unset; - display: block; - float: none; - position: unset; - font-size: 16px; - text-align: center; - } - - .menu a, .commands a { - display: inline-block; - padding: 4px; - } - - .content { - float: none; - margin-left: unset; - margin-top: 16px; - max-width: 100%; - width: 100%; - text-align: center; - } - - a, .slide label { - display: block; - } - - .header { - margin: unset; - font-size: 1.5em; - } - - small { - display: block - } - - a.button { - appearance: button; - text-decoration: none; - margin-top: 10px; - padding: 6px; - border: 2px solid var(--main-link-color); - border-radius: 5px; - width: -webkit-fill-available; - } - - input, select { - width: 35%; - text-align: center; - padding: 5px; - border: 2px solid var(--main-link-color); - border-radius: 5px; - font-size: 18px; - } - - table.extaddr { - margin: auto; - text-align: unset; - } - - textarea { - width: -webkit-fill-available; - height: auto; - padding: 5px; - border: 2px solid var(--main-link-color); - border-radius: 5px; - font-size: 12px; - } - - button[type=submit] { - padding: 5px 15px; - background: transparent; - border: 2px solid var(--main-link-color); - cursor: pointer; - -webkit-border-radius: 5px; - border-radius: 5px; - position: relative; - height: 36px; - display: -webkit-inline-box; - margin-top: 10px; - } -} diff --git a/daemon/Daemon.cpp b/daemon/Daemon.cpp index e2fdf2d4..54146d5d 100644 --- a/daemon/Daemon.cpp +++ b/daemon/Daemon.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 @@ -17,6 +9,7 @@ #include "Base.h" #include "version.h" #include "Transports.h" +#include "NTCPSession.h" #include "RouterInfo.h" #include "RouterContext.h" #include "Tunnel.h" @@ -30,379 +23,350 @@ #include "ClientContext.h" #include "Crypto.h" #include "UPnP.h" -#include "Timestamp.h" -#include "I18N.h" +#include "util.h" + +#include "Event.h" +#include "Websocket.h" namespace i2p { -namespace util -{ - class Daemon_Singleton::Daemon_Singleton_Private + namespace util { - public: - Daemon_Singleton_Private() {}; - ~Daemon_Singleton_Private() {}; - - std::unique_ptr httpServer; - std::unique_ptr m_I2PControlService; - std::unique_ptr UPnP; - std::unique_ptr m_NTPSync; - }; - - Daemon_Singleton::Daemon_Singleton() : isDaemon(false), running(true), d(*new Daemon_Singleton_Private()) {} - Daemon_Singleton::~Daemon_Singleton() { - delete &d; - } - - bool Daemon_Singleton::IsService () const - { - bool service = false; - i2p::config::GetOption("service", service); - return service; - } - - void Daemon_Singleton::setDataDir(std::string path) - { - if (path != "") - DaemonDataDir = path; - } - - bool Daemon_Singleton::init(int argc, char* argv[]) { - return init(argc, argv, nullptr); - } - - bool Daemon_Singleton::init(int argc, char* argv[], std::shared_ptr logstream) - { - i2p::config::Init(); - i2p::config::ParseCmdline(argc, argv); - - std::string config; i2p::config::GetOption("conf", config); - std::string datadir; - if(DaemonDataDir != "") { - datadir = DaemonDataDir; - } else { - i2p::config::GetOption("datadir", datadir); - } - - i2p::fs::DetectDataDir(datadir, IsService()); - i2p::fs::Init(); - - datadir = i2p::fs::GetDataDir(); - - if (config == "") + class Daemon_Singleton::Daemon_Singleton_Private { - config = i2p::fs::DataDirPath("i2pd.conf"); - if (!i2p::fs::Exists (config)) { - // use i2pd.conf only if exists - config = ""; /* reset */ - } + public: + Daemon_Singleton_Private() {}; + ~Daemon_Singleton_Private() {}; + + std::unique_ptr httpServer; + std::unique_ptr m_I2PControlService; + std::unique_ptr UPnP; +#ifdef WITH_EVENTS + std::unique_ptr m_WebsocketServer; +#endif + }; + + Daemon_Singleton::Daemon_Singleton() : isDaemon(false), running(true), d(*new Daemon_Singleton_Private()) {} + Daemon_Singleton::~Daemon_Singleton() { + delete &d; } - i2p::config::ParseConfig(config); - i2p::config::Finalize(); - - i2p::config::GetOption("daemon", isDaemon); - - std::string certsdir; i2p::config::GetOption("certsdir", certsdir); - i2p::fs::SetCertsDir(certsdir); - - certsdir = i2p::fs::GetCertsDir(); - - std::string logs = ""; i2p::config::GetOption("log", logs); - std::string logfile = ""; i2p::config::GetOption("logfile", logfile); - std::string loglevel = ""; i2p::config::GetOption("loglevel", loglevel); - bool logclftime; i2p::config::GetOption("logclftime", logclftime); - - /* setup logging */ - if (logclftime) - i2p::log::Logger().SetTimeFormat ("[%d/%b/%Y:%H:%M:%S %z]"); - -#ifdef WIN32_APP - // Win32 app with GUI supports only logging to file - logs = "file"; -#else - if (isDaemon && (logs == "" || logs == "stdout")) - logs = "file"; -#endif - - i2p::log::Logger().SetLogLevel(loglevel); - if (logstream) { - LogPrint(eLogInfo, "Log: Sending messages to std::ostream"); - i2p::log::Logger().SendTo (logstream); - } else if (logs == "file") { - if (logfile == "") - logfile = i2p::fs::DataDirPath("i2pd.log"); - LogPrint(eLogInfo, "Log: Sending messages to ", logfile); - i2p::log::Logger().SendTo (logfile); + bool Daemon_Singleton::IsService () const + { + bool service = false; #ifndef _WIN32 - } else if (logs == "syslog") { - LogPrint(eLogInfo, "Log: Sending messages to syslog"); - i2p::log::Logger().SendTo("i2pd", LOG_DAEMON); + i2p::config::GetOption("service", service); #endif - } else { - // use stdout -- default + return service; } - LogPrint(eLogNone, "i2pd v", VERSION, " (", I2P_VERSION, ") starting..."); - LogPrint(eLogDebug, "FS: Main config file: ", config); - LogPrint(eLogDebug, "FS: Data directory: ", datadir); - LogPrint(eLogDebug, "FS: Certificates directory: ", certsdir); - - bool precomputation; i2p::config::GetOption("precomputation.elgamal", precomputation); - bool ssu; i2p::config::GetOption("ssu", ssu); - if (!ssu && i2p::config::IsDefault ("precomputation.elgamal")) - precomputation = false; // we don't elgamal table if no ssu, unless it's specified explicitly - i2p::crypto::InitCrypto (precomputation); - - i2p::transport::InitAddressFromIface (); // get address4/6 from interfaces - - int netID; i2p::config::GetOption("netid", netID); - i2p::context.SetNetID (netID); - - bool checkReserved; i2p::config::GetOption("reservedrange", checkReserved); - i2p::transport::transports.SetCheckReserved(checkReserved); - - i2p::context.Init (); - - i2p::transport::InitTransports (); - - bool isFloodfill; i2p::config::GetOption("floodfill", isFloodfill); - if (isFloodfill) + bool Daemon_Singleton::init(int argc, char* argv[]) { - LogPrint(eLogInfo, "Daemon: Router configured as floodfill"); - i2p::context.SetFloodfill (true); - } - else - i2p::context.SetFloodfill (false); + i2p::config::Init(); + i2p::config::ParseCmdline(argc, argv); - bool transit; i2p::config::GetOption("notransit", transit); - i2p::context.SetAcceptsTunnels (!transit); - uint32_t transitTunnels; i2p::config::GetOption("limits.transittunnels", transitTunnels); - if (isFloodfill && i2p::config::IsDefault ("limits.transittunnels")) - transitTunnels *= 2; // double default number of transit tunnels for floodfill - i2p::tunnel::tunnels.SetMaxNumTransitTunnels (transitTunnels); + std::string config; i2p::config::GetOption("conf", config); + std::string datadir; i2p::config::GetOption("datadir", datadir); + i2p::fs::DetectDataDir(datadir, IsService()); + i2p::fs::Init(); - /* this section also honors 'floodfill' flag, if set above */ - std::string bandwidth; i2p::config::GetOption("bandwidth", bandwidth); - if (bandwidth.length () > 0) - { - if (bandwidth.length () == 1 && ((bandwidth[0] >= 'K' && bandwidth[0] <= 'P') || bandwidth[0] == 'X' )) + datadir = i2p::fs::GetDataDir(); + // TODO: drop old name detection in v2.8.0 + if (config == "") { - i2p::context.SetBandwidth (bandwidth[0]); - LogPrint(eLogInfo, "Daemon: Bandwidth set to ", i2p::context.GetBandwidthLimit (), "KBps"); + 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)) { + // use i2pd.conf only if exists + config = ""; /* reset */ + } + } } - else + + i2p::config::ParseConfig(config); + i2p::config::Finalize(); + + i2p::config::GetOption("daemon", isDaemon); + + std::string logs = ""; i2p::config::GetOption("log", logs); + std::string logfile = ""; i2p::config::GetOption("logfile", logfile); + std::string loglevel = ""; i2p::config::GetOption("loglevel", loglevel); + bool logclftime; i2p::config::GetOption("logclftime", logclftime); + + /* setup logging */ + if (logclftime) + i2p::log::Logger().SetTimeFormat ("[%d/%b/%Y:%H:%M:%S %z]"); + + if (isDaemon && (logs == "" || logs == "stdout")) + logs = "file"; + + i2p::log::Logger().SetLogLevel(loglevel); + if (logs == "file") { + if (logfile == "") + logfile = i2p::fs::DataDirPath("i2pd.log"); + LogPrint(eLogInfo, "Log: will send messages to ", logfile); + i2p::log::Logger().SendTo (logfile); +#ifndef _WIN32 + } else if (logs == "syslog") { + LogPrint(eLogInfo, "Log: will send messages to syslog"); + i2p::log::Logger().SendTo("i2pd", LOG_DAEMON); +#endif + } else { + // use stdout -- default + } + + LogPrint(eLogInfo, "i2pd v", VERSION, " starting"); +#ifdef AESNI + LogPrint(eLogInfo, "AESNI enabled"); +#endif +#if defined(__AVX__) + LogPrint(eLogInfo, "AVX enabled"); +#endif + LogPrint(eLogDebug, "FS: main config file: ", config); + LogPrint(eLogDebug, "FS: data directory: ", datadir); + + bool precomputation; i2p::config::GetOption("precomputation.elgamal", precomputation); + i2p::crypto::InitCrypto (precomputation); + + int netID; i2p::config::GetOption("netid", netID); + i2p::context.SetNetID (netID); + i2p::context.Init (); + + bool ipv6; i2p::config::GetOption("ipv6", ipv6); + bool ipv4; i2p::config::GetOption("ipv4", ipv4); +#ifdef MESHNET + // manual override for meshnet + ipv4 = false; + ipv6 = true; +#endif + uint16_t port; i2p::config::GetOption("port", port); + if (!i2p::config::IsDefault("port")) { - auto value = std::atoi(bandwidth.c_str()); - if (value > 0) + LogPrint(eLogInfo, "Daemon: accepting incoming connections at port ", port); + i2p::context.UpdatePort (port); + } + i2p::context.SetSupportsV6 (ipv6); + i2p::context.SetSupportsV4 (ipv4); + + bool transit; i2p::config::GetOption("notransit", transit); + i2p::context.SetAcceptsTunnels (!transit); + uint16_t transitTunnels; i2p::config::GetOption("limits.transittunnels", transitTunnels); + SetMaxNumTransitTunnels (transitTunnels); + + bool isFloodfill; i2p::config::GetOption("floodfill", isFloodfill); + if (isFloodfill) { + LogPrint(eLogInfo, "Daemon: router will be floodfill"); + i2p::context.SetFloodfill (true); + } else { + i2p::context.SetFloodfill (false); + } + + /* this section also honors 'floodfill' flag, if set above */ + std::string bandwidth; i2p::config::GetOption("bandwidth", bandwidth); + if (bandwidth.length () > 0) + { + if (bandwidth[0] >= 'K' && bandwidth[0] <= 'X') { - i2p::context.SetBandwidth (value); - LogPrint(eLogInfo, "Daemon: Bandwidth set to ", i2p::context.GetBandwidthLimit (), " KBps"); + i2p::context.SetBandwidth (bandwidth[0]); + LogPrint(eLogInfo, "Daemon: bandwidth set to ", i2p::context.GetBandwidthLimit (), "KBps"); } else { - LogPrint(eLogInfo, "Daemon: Unexpected bandwidth ", bandwidth, ". Set to 'low'"); - i2p::context.SetBandwidth (i2p::data::CAPS_FLAG_LOW_BANDWIDTH2); + auto value = std::atoi(bandwidth.c_str()); + if (value > 0) + { + i2p::context.SetBandwidth (value); + LogPrint(eLogInfo, "Daemon: bandwidth set to ", i2p::context.GetBandwidthLimit (), " KBps"); + } + else + { + LogPrint(eLogInfo, "Daemon: unexpected bandwidth ", bandwidth, ". Set to 'low'"); + i2p::context.SetBandwidth (i2p::data::CAPS_FLAG_LOW_BANDWIDTH2); + } } } - } - else if (isFloodfill) - { - LogPrint(eLogInfo, "Daemon: Floodfill bandwidth set to 'extra'"); - i2p::context.SetBandwidth (i2p::data::CAPS_FLAG_EXTRA_BANDWIDTH2); - } - else - { - LogPrint(eLogInfo, "Daemon: bandwidth set to 'low'"); - i2p::context.SetBandwidth (i2p::data::CAPS_FLAG_LOW_BANDWIDTH2); - } - - int shareRatio; i2p::config::GetOption("share", shareRatio); - i2p::context.SetShareRatio (shareRatio); - - std::string family; i2p::config::GetOption("family", family); - i2p::context.SetFamily (family); - if (family.length () > 0) - LogPrint(eLogInfo, "Daemon: Router family set to ", family); - - bool trust; i2p::config::GetOption("trust.enabled", trust); - if (trust) - { - LogPrint(eLogInfo, "Daemon: Explicit trust enabled"); - std::string fam; i2p::config::GetOption("trust.family", fam); - std::string routers; i2p::config::GetOption("trust.routers", routers); - bool restricted = false; - if (fam.length() > 0) + else if (isFloodfill) { - std::set fams; - size_t pos = 0, comma; - do - { - comma = fam.find (',', pos); - fams.insert (fam.substr (pos, comma != std::string::npos ? comma - pos : std::string::npos)); - pos = comma + 1; - } - while (comma != std::string::npos); - i2p::transport::transports.RestrictRoutesToFamilies(fams); - restricted = fams.size() > 0; + LogPrint(eLogInfo, "Daemon: floodfill bandwidth set to 'extra'"); + i2p::context.SetBandwidth (i2p::data::CAPS_FLAG_EXTRA_BANDWIDTH1); } - if (routers.length() > 0) { - std::set idents; - size_t pos = 0, comma; - do - { - comma = routers.find (',', pos); - i2p::data::IdentHash ident; - ident.FromBase64 (routers.substr (pos, comma != std::string::npos ? comma - pos : std::string::npos)); - idents.insert (ident); - pos = comma + 1; - } - while (comma != std::string::npos); - LogPrint(eLogInfo, "Daemon: Setting restricted routes to use ", idents.size(), " trusted routers"); - i2p::transport::transports.RestrictRoutesToRouters(idents); - restricted = idents.size() > 0; - } - if(!restricted) - LogPrint(eLogError, "Daemon: No trusted routers of families specified"); - } - - bool hidden; i2p::config::GetOption("trust.hidden", hidden); - if (hidden) - { - LogPrint(eLogInfo, "Daemon: Hidden mode enabled"); - i2p::context.SetHidden(true); - } - - std::string httpLang; i2p::config::GetOption("http.lang", httpLang); - i2p::i18n::SetLanguage(httpLang); - - return true; - } - - bool Daemon_Singleton::start() - { - i2p::log::Logger().Start(); - LogPrint(eLogInfo, "Daemon: Starting NetDB"); - i2p::data::netdb.Start(); - - bool upnp; i2p::config::GetOption("upnp.enabled", upnp); - if (upnp) { - d.UPnP = std::unique_ptr(new i2p::transport::UPnP); - d.UPnP->Start (); - } - - bool nettime; i2p::config::GetOption("nettime.enabled", nettime); - if (nettime) - { - d.m_NTPSync = std::unique_ptr(new i2p::util::NTPTimeSync); - d.m_NTPSync->Start (); - } - - bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); - bool ssu2; i2p::config::GetOption("ssu2.enabled", ssu2); - LogPrint(eLogInfo, "Daemon: Starting Transports"); - if(!ssu2) LogPrint(eLogInfo, "Daemon: SSU2 disabled"); - if(!ntcp2) LogPrint(eLogInfo, "Daemon: NTCP2 disabled"); - - i2p::transport::transports.Start(ntcp2, ssu2); - if (i2p::transport::transports.IsBoundSSU2() || i2p::transport::transports.IsBoundNTCP2()) - LogPrint(eLogInfo, "Daemon: Transports started"); - else - { - LogPrint(eLogCritical, "Daemon: Failed to start Transports"); - /** shut down netdb right away */ - i2p::transport::transports.Stop(); - i2p::data::netdb.Stop(); - return false; - } - - bool http; i2p::config::GetOption("http.enabled", http); - if (http) { - std::string httpAddr; i2p::config::GetOption("http.address", httpAddr); - uint16_t httpPort; i2p::config::GetOption("http.port", httpPort); - LogPrint(eLogInfo, "Daemon: Starting Webconsole at ", httpAddr, ":", httpPort); - try + else { + LogPrint(eLogInfo, "Daemon: bandwidth set to 'low'"); + i2p::context.SetBandwidth (i2p::data::CAPS_FLAG_LOW_BANDWIDTH2); + } + + int shareRatio; i2p::config::GetOption("share", shareRatio); + i2p::context.SetShareRatio (shareRatio); + + std::string family; i2p::config::GetOption("family", family); + i2p::context.SetFamily (family); + if (family.length () > 0) + LogPrint(eLogInfo, "Daemon: family set to ", family); + + bool trust; i2p::config::GetOption("trust.enabled", trust); + if (trust) + { + LogPrint(eLogInfo, "Daemon: explicit trust enabled"); + std::string fam; i2p::config::GetOption("trust.family", fam); + std::string routers; i2p::config::GetOption("trust.routers", routers); + bool restricted = false; + if (fam.length() > 0) + { + std::set fams; + size_t pos = 0, comma; + do + { + comma = fam.find (',', pos); + fams.insert (fam.substr (pos, comma != std::string::npos ? comma - pos : std::string::npos)); + pos = comma + 1; + } + while (comma != std::string::npos); + i2p::transport::transports.RestrictRoutesToFamilies(fams); + restricted = fams.size() > 0; + } + if (routers.length() > 0) { + std::set idents; + size_t pos = 0, comma; + do + { + comma = routers.find (',', pos); + i2p::data::IdentHash ident; + ident.FromBase64 (routers.substr (pos, comma != std::string::npos ? comma - pos : std::string::npos)); + idents.insert (ident); + pos = comma + 1; + } + while (comma != std::string::npos); + LogPrint(eLogInfo, "Daemon: setting restricted routes to use ", idents.size(), " trusted routesrs"); + i2p::transport::transports.RestrictRoutesToRouters(idents); + restricted = idents.size() > 0; + } + if(!restricted) + LogPrint(eLogError, "Daemon: no trusted routers of families specififed"); + } + bool hidden; i2p::config::GetOption("trust.hidden", hidden); + if (hidden) + { + LogPrint(eLogInfo, "Daemon: using hidden mode"); + i2p::data::netdb.SetHidden(true); + } + return true; + } + + bool Daemon_Singleton::start() + { + i2p::log::Logger().Start(); + LogPrint(eLogInfo, "Daemon: starting NetDB"); + i2p::data::netdb.Start(); + + bool upnp; i2p::config::GetOption("upnp.enabled", upnp); + if (upnp) { + d.UPnP = std::unique_ptr(new i2p::transport::UPnP); + d.UPnP->Start (); + } + + bool ntcp; i2p::config::GetOption("ntcp", ntcp); + bool ssu; i2p::config::GetOption("ssu", ssu); + LogPrint(eLogInfo, "Daemon: starting Transports"); + if(!ssu) LogPrint(eLogInfo, "Daemon: ssu disabled"); + if(!ntcp) LogPrint(eLogInfo, "Daemon: ntcp disabled"); + + i2p::transport::transports.Start(ntcp, ssu); + if (i2p::transport::transports.IsBoundNTCP() || i2p::transport::transports.IsBoundSSU()) { + LogPrint(eLogInfo, "Daemon: Transports started"); + } else { + LogPrint(eLogError, "Daemon: failed to start Transports"); + /** shut down netdb right away */ + i2p::transport::transports.Stop(); + i2p::data::netdb.Stop(); + return false; + } + + bool http; i2p::config::GetOption("http.enabled", http); + if (http) { + std::string httpAddr; i2p::config::GetOption("http.address", httpAddr); + uint16_t httpPort; i2p::config::GetOption("http.port", httpPort); + LogPrint(eLogInfo, "Daemon: starting HTTP Server at ", httpAddr, ":", httpPort); d.httpServer = std::unique_ptr(new i2p::http::HTTPServer(httpAddr, httpPort)); d.httpServer->Start(); } - catch (std::exception& ex) - { - LogPrint (eLogCritical, "Daemon: Failed to start Webconsole: ", ex.what ()); - ThrowFatal ("Unable to start webconsole at ", httpAddr, ":", httpPort, ": ", ex.what ()); - } - } - LogPrint(eLogInfo, "Daemon: Starting Tunnels"); - i2p::tunnel::tunnels.Start(); - LogPrint(eLogInfo, "Daemon: Starting Router context"); - i2p::context.Start(); + LogPrint(eLogInfo, "Daemon: starting Tunnels"); + i2p::tunnel::tunnels.Start(); - LogPrint(eLogInfo, "Daemon: Starting Client"); - i2p::client::context.Start (); + LogPrint(eLogInfo, "Daemon: starting Client"); + i2p::client::context.Start (); - // I2P Control Protocol - bool i2pcontrol; i2p::config::GetOption("i2pcontrol.enabled", i2pcontrol); - if (i2pcontrol) { - std::string i2pcpAddr; i2p::config::GetOption("i2pcontrol.address", i2pcpAddr); - uint16_t i2pcpPort; i2p::config::GetOption("i2pcontrol.port", i2pcpPort); - LogPrint(eLogInfo, "Daemon: Starting I2PControl at ", i2pcpAddr, ":", i2pcpPort); - try - { + // I2P Control Protocol + bool i2pcontrol; i2p::config::GetOption("i2pcontrol.enabled", i2pcontrol); + if (i2pcontrol) { + std::string i2pcpAddr; i2p::config::GetOption("i2pcontrol.address", i2pcpAddr); + uint16_t i2pcpPort; i2p::config::GetOption("i2pcontrol.port", i2pcpPort); + LogPrint(eLogInfo, "Daemon: starting I2PControl at ", i2pcpAddr, ":", i2pcpPort); d.m_I2PControlService = std::unique_ptr(new i2p::client::I2PControlService (i2pcpAddr, i2pcpPort)); d.m_I2PControlService->Start (); } - catch (std::exception& ex) - { - LogPrint (eLogCritical, "Daemon: Failed to start I2PControl: ", ex.what ()); - ThrowFatal ("Unable to start I2PControl service at ", i2pcpAddr, ":", i2pcpPort, ": ", ex.what ()); +#ifdef WITH_EVENTS + + bool websocket; i2p::config::GetOption("websockets.enabled", websocket); + if(websocket) { + std::string websocketAddr; i2p::config::GetOption("websockets.address", websocketAddr); + uint16_t websocketPort; i2p::config::GetOption("websockets.port", websocketPort); + LogPrint(eLogInfo, "Daemon: starting Websocket server at ", websocketAddr, ":", websocketPort); + d.m_WebsocketServer = std::unique_ptr(new i2p::event::WebsocketServer (websocketAddr, websocketPort)); + d.m_WebsocketServer->Start(); + i2p::event::core.SetListener(d.m_WebsocketServer->ToListener()); } +#endif + return true; } - return true; - } - bool Daemon_Singleton::stop() - { - LogPrint(eLogInfo, "Daemon: Shutting down"); - LogPrint(eLogInfo, "Daemon: Stopping Client"); - i2p::client::context.Stop(); - LogPrint(eLogInfo, "Daemon: Stopping Router context"); - i2p::context.Stop(); - LogPrint(eLogInfo, "Daemon: Stopping Tunnels"); - i2p::tunnel::tunnels.Stop(); - - if (d.UPnP) + bool Daemon_Singleton::stop() { - d.UPnP->Stop (); - d.UPnP = nullptr; - } +#ifdef WITH_EVENTS + i2p::event::core.SetListener(nullptr); +#endif + LogPrint(eLogInfo, "Daemon: shutting down"); + LogPrint(eLogInfo, "Daemon: stopping Client"); + i2p::client::context.Stop(); + LogPrint(eLogInfo, "Daemon: stopping Tunnels"); + i2p::tunnel::tunnels.Stop(); - if (d.m_NTPSync) - { - d.m_NTPSync->Stop (); - d.m_NTPSync = nullptr; - } + if (d.UPnP) { + d.UPnP->Stop (); + d.UPnP = nullptr; + } - LogPrint(eLogInfo, "Daemon: Stopping Transports"); - i2p::transport::transports.Stop(); - LogPrint(eLogInfo, "Daemon: Stopping NetDB"); - i2p::data::netdb.Stop(); - if (d.httpServer) { - LogPrint(eLogInfo, "Daemon: Stopping HTTP Server"); - d.httpServer->Stop(); - d.httpServer = nullptr; - } - if (d.m_I2PControlService) - { - LogPrint(eLogInfo, "Daemon: Stopping I2PControl"); - d.m_I2PControlService->Stop (); - d.m_I2PControlService = nullptr; - } - i2p::crypto::TerminateCrypto (); - i2p::log::Logger().Stop(); + LogPrint(eLogInfo, "Daemon: stopping Transports"); + i2p::transport::transports.Stop(); + LogPrint(eLogInfo, "Daemon: stopping NetDB"); + i2p::data::netdb.Stop(); + if (d.httpServer) { + LogPrint(eLogInfo, "Daemon: stopping HTTP Server"); + d.httpServer->Stop(); + d.httpServer = nullptr; + } + if (d.m_I2PControlService) + { + LogPrint(eLogInfo, "Daemon: stopping I2PControl"); + d.m_I2PControlService->Stop (); + d.m_I2PControlService = nullptr; + } +#ifdef WITH_EVENTS + if (d.m_WebsocketServer) { + LogPrint(eLogInfo, "Daemon: stopping Websocket server"); + d.m_WebsocketServer->Stop(); + d.m_WebsocketServer = nullptr; + } +#endif + i2p::crypto::TerminateCrypto (); + i2p::log::Logger().Stop(); - return true; - } + return true; + } } } diff --git a/daemon/Daemon.h b/daemon/Daemon.h index 26d4a047..48301e73 100644 --- a/daemon/Daemon.h +++ b/daemon/Daemon.h @@ -1,17 +1,8 @@ -/* -* 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 DAEMON_H__ #define DAEMON_H__ #include #include -#include namespace i2p { @@ -20,33 +11,24 @@ namespace util class Daemon_Singleton_Private; class Daemon_Singleton { - public: + public: + virtual bool init(int argc, char* argv[]); + virtual bool start(); + virtual bool stop(); + virtual void run () {}; - virtual bool init (int argc, char* argv[], std::shared_ptr logstream); - virtual bool init (int argc, char* argv[]); - virtual bool start (); - virtual bool stop (); - virtual void run () {}; + bool isDaemon; + bool running; - virtual void setDataDir (std::string path); + protected: + Daemon_Singleton(); + virtual ~Daemon_Singleton(); - bool isDaemon; - bool running; + bool IsService () const; - protected: - - Daemon_Singleton (); - virtual ~Daemon_Singleton (); - - bool IsService () const; - - // d-pointer for httpServer, httpProxy, etc. - class Daemon_Singleton_Private; - Daemon_Singleton_Private &d; - - private: - - std::string DaemonDataDir; + // d-pointer for httpServer, httpProxy, etc. + class Daemon_Singleton_Private; + Daemon_Singleton_Private &d; }; #if defined(QT_GUI_LIB) // check if QT @@ -55,7 +37,6 @@ namespace util class DaemonQT: public i2p::util::Daemon_Singleton { public: - static DaemonQT& Instance() { static DaemonQT instance; @@ -63,12 +44,24 @@ namespace util } }; +#elif defined(ANDROID) +#define Daemon i2p::util::DaemonAndroid::Instance() + // dummy, invoked from android/jni/DaemonAndroid.* + class DaemonAndroid: public i2p::util::Daemon_Singleton + { + public: + static DaemonAndroid& Instance() + { + static DaemonAndroid instance; + return instance; + } + }; + #elif defined(_WIN32) #define Daemon i2p::util::DaemonWin32::Instance() class DaemonWin32 : public Daemon_Singleton { public: - static DaemonWin32& Instance() { static DaemonWin32 instance; @@ -84,25 +77,12 @@ namespace util DaemonWin32 ():isGraceful(false) {} }; -#elif (defined(ANDROID) && !defined(ANDROID_BINARY)) -#define Daemon i2p::util::DaemonAndroid::Instance() - // dummy, invoked from android/jni/DaemonAndroid.* - class DaemonAndroid: public i2p::util::Daemon_Singleton - { - public: - static DaemonAndroid& Instance() - { - static DaemonAndroid instance; - return instance; - } - }; #else #define Daemon i2p::util::DaemonLinux::Instance() class DaemonLinux : public Daemon_Singleton { public: - static DaemonLinux& Instance() { static DaemonLinux instance; @@ -114,12 +94,10 @@ namespace util void run (); private: - std::string pidfile; int pidFH; public: - int gracefulShutdownInterval; // in seconds }; #endif diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index dca545fe..017bc8a4 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.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 #include -#include +#include #include "Base.h" #include "FS.h" @@ -29,39 +21,53 @@ #include "HTTPServer.h" #include "Daemon.h" #include "util.h" -#include "ECIESX25519AEADRatchetSession.h" -#include "I18N.h" - #ifdef WIN32_APP -#include "Win32App.h" +#include "Win32/Win32App.h" #endif -// For image, style and info +// For image and info #include "version.h" -#include "HTTPServerResources.h" namespace i2p { namespace http { - static void LoadExtCSS () - { - std::stringstream s; - std::string styleFile = i2p::fs::DataDirPath ("webconsole/style.css"); - if (i2p::fs::Exists(styleFile)) { - std::ifstream f(styleFile, std::ifstream::binary); - s << f.rdbuf(); - externalCSS = s.str(); - } else if (externalCSS.length() != 0) { // clean up external style if file was removed - externalCSS = ""; - } - } + const char *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=="; - static void GetStyles (std::stringstream& s) - { - if (externalCSS.length() != 0) - s << "\r\n"; - else - s << internalCSS; - } + const char *cssStyles = + "\r\n"; const char HTTP_PAGE_TUNNELS[] = "tunnels"; const char HTTP_PAGE_TRANSIT_TUNNELS[] = "transit_tunnels"; @@ -80,42 +86,28 @@ namespace http { const char HTTP_COMMAND_SHUTDOWN_CANCEL[] = "shutdown_cancel"; const char HTTP_COMMAND_SHUTDOWN_NOW[] = "terminate"; const char HTTP_COMMAND_RUN_PEER_TEST[] = "run_peer_test"; - const char HTTP_COMMAND_RELOAD_TUNNELS_CONFIG[] = "reload_tunnels_config"; + const char HTTP_COMMAND_RELOAD_CONFIG[] = "reload_config"; const char HTTP_COMMAND_LOGLEVEL[] = "set_loglevel"; - const char HTTP_COMMAND_KILLSTREAM[] = "closestream"; - const char HTTP_COMMAND_LIMITTRANSIT[] = "limittransit"; - const char HTTP_COMMAND_GET_REG_STRING[] = "get_reg_string"; - const char HTTP_COMMAND_SETLANGUAGE[] = "setlanguage"; - const char HTTP_COMMAND_RELOAD_CSS[] = "reload_css"; - const char HTTP_COMMAND_EXPIRELEASE[] = "expirelease"; - - static std::string ConvertTime (uint64_t time) - { - lldiv_t divTime = lldiv(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.%03lld", tm->tm_mday, tm->tm_mon + 1, tm->tm_year + 1900, tm->tm_hour, tm->tm_min, tm->tm_sec, divTime.rem); - return date; - } + const char HTTP_PARAM_SAM_SESSION_ID[] = "id"; + const char HTTP_PARAM_ADDRESS[] = "address"; static void ShowUptime (std::stringstream& s, int seconds) { int num; if ((num = seconds / 86400) > 0) { - s << ntr("%d day", "%d days", num, num) << ", "; + s << num << " days, "; seconds -= num * 86400; } if ((num = seconds / 3600) > 0) { - s << ntr("%d hour", "%d hours", num, num) << ", "; + s << num << " hours, "; seconds -= num * 3600; } if ((num = seconds / 60) > 0) { - s << ntr("%d minute", "%d minutes", num, num) << ", "; + s << num << " min, "; seconds -= num * 60; } - s << ntr("%d second", "%d seconds", seconds, seconds); + s << seconds << " seconds"; } static void ShowTraffic (std::stringstream& s, uint64_t bytes) @@ -123,41 +115,36 @@ namespace http { s << std::fixed << std::setprecision(2); auto numKBytes = (double) bytes / 1024; if (numKBytes < 1024) - s << tr(/* tr: Kibibyte */ "%.2f KiB", numKBytes); + s << numKBytes << " KiB"; else if (numKBytes < 1024 * 1024) - s << tr(/* tr: Mebibyte */ "%.2f MiB", numKBytes / 1024); + s << numKBytes / 1024 << " MiB"; else - s << tr(/* tr: Gibibyte */ "%.2f GiB", numKBytes / 1024 / 1024); + s << numKBytes / 1024 / 1024 << " GiB"; } static void ShowTunnelDetails (std::stringstream& s, enum i2p::tunnel::TunnelState eState, bool explr, int bytes) { std::string state; - std::string_view stateText; - switch (eState) - { + switch (eState) { case i2p::tunnel::eTunnelStateBuildReplyReceived : - case i2p::tunnel::eTunnelStatePending : state = "building"; break; - case i2p::tunnel::eTunnelStateBuildFailed : state = "failed"; stateText = "declined"; break; - case i2p::tunnel::eTunnelStateTestFailed : state = "failed"; stateText = "test failed"; break; - case i2p::tunnel::eTunnelStateFailed : state = "failed"; break; - case i2p::tunnel::eTunnelStateExpiring : state = "expiring"; break; + case i2p::tunnel::eTunnelStatePending : state = "building"; break; + case i2p::tunnel::eTunnelStateBuildFailed : + case i2p::tunnel::eTunnelStateTestFailed : + case i2p::tunnel::eTunnelStateFailed : state = "failed"; break; + case i2p::tunnel::eTunnelStateExpiring : state = "expiring"; break; case i2p::tunnel::eTunnelStateEstablished : state = "established"; break; default: state = "unknown"; break; } - if (stateText.empty ()) stateText = tr(state); - - s << " " << stateText << ((explr) ? " (" + std::string(tr("exploratory")) + ")" : "") << ", "; // TODO: - ShowTraffic(s, bytes); - s << "\r\n"; + s << " " << state << ((explr) ? " (exploratory)" : "") << ", "; + s << " " << (int) (bytes / 1024) << " KiB
\r\n"; } static void SetLogLevel (const std::string& level) { - if (level == "none" || level == "critical" || level == "error" || level == "warn" || level == "info" || level == "debug") + if (level == "none" || level == "error" || level == "warn" || level == "info" || level == "debug") i2p::log::Logger().SetLogLevel(level); else { - LogPrint(eLogError, "HTTPServer: Unknown loglevel set attempted"); + LogPrint(eLogError, "HTTPServer: unknown loglevel set attempted"); return; } i2p::log::Logger().Reopen (); @@ -165,244 +152,164 @@ namespace http { static void ShowPageHead (std::stringstream& s) { - std::string webroot; i2p::config::GetOption("http.webroot", webroot); - - // Page language - std::string currLang = i2p::client::context.GetLanguage ()->GetLanguage(); // get current used language - auto it = i2p::i18n::languages.find(currLang); - std::string langCode = it->second.ShortCode; - s << "\r\n" - "\r\n" + "\r\n" /* TODO: Add support for locale */ " \r\n" /* TODO: Find something to parse html/template system. This is horrible. */ +#if (!defined(WIN32)) " \r\n" - " \r\n" +#else + " \r\n" +#endif " \r\n" - " " << tr(/* tr: Webconsole page title */ "Purple I2P Webconsole") << "\r\n"; - GetStyles(s); + " Purple I2P " VERSION " Webconsole\r\n" + << cssStyles << + "\r\n"; s << - "\r\n" "\r\n" - "
" << tr("i2pd webconsole") << "
\r\n" - "
\r\n" - "
\r\n" - " " << tr("Main page") << "

\r\n" - " " << tr("Router commands") << "
\r\n" - " " << tr("Local Destinations") << "
\r\n"; - if (i2p::context.IsFloodfill ()) - s << " " << tr("LeaseSets") << "
\r\n"; - s << - " " << tr("Tunnels") << "
\r\n"; - if (i2p::context.AcceptsTunnels () || i2p::tunnel::tunnels.CountTransitTunnels()) - s << " " << tr("Transit Tunnels") << "
\r\n"; - s << - " " << tr("Transports") << "
\r\n" - " " << tr("I2P tunnels") << "
\r\n"; + "
i2pd webconsole
\r\n" + "
\r\n" + "
\r\n" + " Main page
\r\n
\r\n" + " Router commands
\r\n" + " Local destinations
\r\n" + " LeaseSets
\r\n" + " Tunnels
\r\n" + " Transit tunnels
\r\n" + " Transports
\r\n" + " I2P tunnels
\r\n"; if (i2p::client::context.GetSAMBridge ()) - s << " " << tr("SAM sessions") << "
\r\n"; + s << " SAM sessions
\r\n"; s << "
\r\n" - "
"; + "
"; } static void ShowPageTail (std::stringstream& s) { s << - "
\r\n
\r\n" + "
\r\n" "\r\n" "\r\n"; } - static void ShowError(std::stringstream& s, std::string_view string) + static void ShowError(std::stringstream& s, const std::string& string) { - s << "" << tr("ERROR") << ": " << string << "
\r\n"; + s << "ERROR: " << string << "
\r\n"; } - static void ShowNetworkStatus (std::stringstream& s, RouterStatus status, bool testing, RouterError error) + void ShowStatus (std::stringstream& s, bool includeHiddenContent) { - switch (status) - { - case eRouterStatusOK: s << tr("OK"); break; - case eRouterStatusFirewalled: s << tr("Firewalled"); break; - case eRouterStatusUnknown: s << tr("Unknown"); break; - case eRouterStatusProxy: s << tr("Proxy"); break; - case eRouterStatusMesh: s << tr("Mesh"); break; - default: s << tr("Unknown"); - } - if (testing) - s << " (" << tr("Testing") << ")"; - if (error != eRouterErrorNone) - { - switch (error) - { - case eRouterErrorClockSkew: - s << " - " << tr("Clock skew"); - break; - case eRouterErrorOffline: - s << " - " << tr("Offline"); - break; - case eRouterErrorSymmetricNAT: - s << " - " << tr("Symmetric NAT"); - break; - case eRouterErrorFullConeNAT: - s << " - " << tr("Full cone NAT"); - break; - case eRouterErrorNoDescriptors: - s << " - " << tr("No Descriptors"); - break; - default: ; - } - } - } - - void ShowStatus (std::stringstream& s, bool includeHiddenContent, i2p::http::OutputFormatEnum outputFormat) - { - s << "" << tr("Uptime") << ": "; + s << "Uptime: "; ShowUptime(s, i2p::context.GetUptime ()); s << "
\r\n"; - s << "" << tr("Network status") << ": "; - ShowNetworkStatus (s, i2p::context.GetStatus (), i2p::context.GetTesting(), i2p::context.GetError ()); - s << "
\r\n"; - if (i2p::context.SupportsV6 ()) + s << "Network status: "; + switch (i2p::context.GetStatus ()) { - s << "" << tr("Network status v6") << ": "; - ShowNetworkStatus (s, i2p::context.GetStatusV6 (), i2p::context.GetTestingV6(), i2p::context.GetErrorV6 ()); - s << "
\r\n"; + case eRouterStatusOK: s << "OK"; break; + case eRouterStatusTesting: s << "Testing"; break; + case eRouterStatusFirewalled: s << "Firewalled"; break; + case eRouterStatusError: + { + s << "Error"; + switch (i2p::context.GetError ()) + { + case eRouterErrorClockSkew: + s << "
Clock skew"; + break; + default: ; + } + break; + } + default: s << "Unknown"; } -#if ((!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) || defined(ANDROID_BINARY)) + s << "
\r\n"; +#if (!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) if (auto remains = Daemon.gracefulShutdownInterval) { - s << "" << tr("Stopping in") << ": "; - ShowUptime(s, remains); - s << "
\r\n"; - } -#elif defined(WIN32_APP) - if (i2p::win32::g_GracefulShutdownEndtime != 0) { - uint16_t remains = (i2p::win32::g_GracefulShutdownEndtime - GetTickCount()) / 1000; - s << "" << tr("Stopping in") << ": "; - ShowUptime(s, remains); + s << "Stopping in: "; + s << remains << " seconds"; s << "
\r\n"; } #endif auto family = i2p::context.GetFamily (); if (family.length () > 0) - s << ""<< tr("Family") << ": " << family << "
\r\n"; - s << "" << tr("Tunnel creation success rate") << ": " << i2p::tunnel::tunnels.GetTunnelCreationSuccessRate () << "%
\r\n"; - bool isTotalTCSR; - i2p::config::GetOption("http.showTotalTCSR", isTotalTCSR); - if (isTotalTCSR) { - s << "" << tr("Total tunnel creation success rate") << ": " << i2p::tunnel::tunnels.GetTotalTunnelCreationSuccessRate() << "%
\r\n"; - } - s << "" << tr("Received") << ": "; + s << "Family: " << family << "
\r\n"; + s << "Tunnel creation success rate: " << i2p::tunnel::tunnels.GetTunnelCreationSuccessRate () << "%
\r\n"; + s << "Received: "; ShowTraffic (s, i2p::transport::transports.GetTotalReceivedBytes ()); - s << " (" << tr(/* tr: Kibibyte/s */ "%.2f KiB/s", (double) i2p::transport::transports.GetInBandwidth15s () / 1024) << ")
\r\n"; - s << "" << tr("Sent") << ": "; + s << " (" << (double) i2p::transport::transports.GetInBandwidth () / 1024 << " KiB/s)
\r\n"; + s << "Sent: "; ShowTraffic (s, i2p::transport::transports.GetTotalSentBytes ()); - s << " (" << tr(/* tr: Kibibyte/s */ "%.2f KiB/s", (double) i2p::transport::transports.GetOutBandwidth15s () / 1024) << ")
\r\n"; - s << "" << tr("Transit") << ": "; + s << " (" << (double) i2p::transport::transports.GetOutBandwidth () / 1024 << " KiB/s)
\r\n"; + s << "Transit: "; ShowTraffic (s, i2p::transport::transports.GetTotalTransitTransmittedBytes ()); - s << " (" << tr(/* tr: Kibibyte/s */ "%.2f KiB/s", (double) i2p::transport::transports.GetTransitBandwidth15s () / 1024) << ")
\r\n"; - s << "" << tr("Data path") << ": " << i2p::fs::GetUTF8DataDir() << "
\r\n"; - s << "
"; - if ((outputFormat == OutputFormatEnum::forWebConsole) || !includeHiddenContent) { - s << "\r\n\r\n
\r\n"; - } - if (includeHiddenContent) - { - s << "" << tr("Router Ident") << ": " << i2p::context.GetRouterInfo().GetIdentHashBase64() << "
\r\n"; - if (!i2p::context.GetRouterInfo().GetProperty("family").empty()) - s << "" << tr("Router Family") << ": " << i2p::context.GetRouterInfo().GetProperty("family") << "
\r\n"; - s << "" << tr("Router Caps") << ": " << i2p::context.GetRouterInfo().GetProperty("caps") << "
\r\n"; - s << "" << tr("Version") << ": " VERSION "
\r\n"; - s << ""<< tr("Our external address") << ":" << "
\r\n\r\n"; - auto addresses = i2p::context.GetRouterInfo().GetAddresses (); - if (addresses) + s << " (" << (double) i2p::transport::transports.GetTransitBandwidth () / 1024 << " KiB/s)
\r\n"; + s << "Data path: " << i2p::fs::GetDataDir() << "
\r\n"; + s << "
\r\n\r\n

\r\n"; + if(includeHiddenContent) { + s << "Router Ident: " << i2p::context.GetRouterInfo().GetIdentHashBase64() << "
\r\n"; + s << "Router Family: " << i2p::context.GetRouterInfo().GetProperty("family") << "
\r\n"; + s << "Router Caps: " << i2p::context.GetRouterInfo().GetProperty("caps") << "
\r\n"; + s << "Our external address:" << "
\r\n" ; + for (const auto& address : i2p::context.GetRouterInfo().GetAddresses()) { - for (const auto& address : *addresses) + switch (address->transportStyle) { - if (!address) continue; - s << "

\r\n\r\n"; - if (address->published) - s << "\r\n"; - else - { - s << "\r\n"; - } - s << "\r\n"; + case i2p::data::RouterInfo::eTransportNTCP: + if (address->host.is_v6 ()) + s << "NTCP6  "; + else + s << "NTCP  "; + break; + case i2p::data::RouterInfo::eTransportSSU: + if (address->host.is_v6 ()) + s << "SSU6     "; + else + s << "SSU     "; + break; + default: + s << "Unknown  "; } + s << address->host.to_string() << ":" << address->port << "
\r\n"; } - s << "
"; - switch (address->transportStyle) - { - case i2p::data::RouterInfo::eTransportNTCP2: - s << "NTCP2"; - break; - case i2p::data::RouterInfo::eTransportSSU2: - s << "SSU2"; - break; - default: - s << tr("Unknown"); - } - bool v6 = address->IsV6 (); - if (v6) - { - if (address->IsV4 ()) s << "v4"; - s << "v6"; - } - s << "" << (v6 ? "[" : "") << address->host.to_string() << (v6 ? "]:" : ":") << address->port << "" << tr(/* tr: Shown when router doesn't publish itself and have "Firewalled" state */ "supported"); - if (address->port) - s << " :" << address->port; - s << "
\r\n"; } - s << "
\r\n
\r\n"; - if (outputFormat == OutputFormatEnum::forQtUi) { - s << "
"; - } - s << "" << tr("Routers") << ": " << i2p::data::netdb.GetNumRouters () << " "; - s << "" << tr("Floodfills") << ": " << i2p::data::netdb.GetNumFloodfills () << " "; - s << "" << tr("LeaseSets") << ": " << i2p::data::netdb.GetNumLeaseSets () << "
\r\n"; + s << "

\r\n
\r\n"; + s << "Routers: " << i2p::data::netdb.GetNumRouters () << " "; + s << "Floodfills: " << i2p::data::netdb.GetNumFloodfills () << " "; + s << "LeaseSets: " << i2p::data::netdb.GetNumLeaseSets () << "
\r\n"; size_t clientTunnelCount = i2p::tunnel::tunnels.CountOutboundTunnels(); clientTunnelCount += i2p::tunnel::tunnels.CountInboundTunnels(); size_t transitTunnelCount = i2p::tunnel::tunnels.CountTransitTunnels(); - s << "" << tr("Client Tunnels") << ": " << std::to_string(clientTunnelCount) << " "; - s << "" << tr("Transit Tunnels") << ": " << std::to_string(transitTunnelCount) << "
\r\n
\r\n"; + s << "Client Tunnels: " << std::to_string(clientTunnelCount) << " "; + s << "Transit Tunnels: " << std::to_string(transitTunnelCount) << "
\r\n
\r\n"; - if (outputFormat==OutputFormatEnum::forWebConsole) { - bool httpproxy = i2p::client::context.GetHttpProxy () ? true : false; - bool socksproxy = i2p::client::context.GetSocksProxy () ? true : false; - bool bob = i2p::client::context.GetBOBCommandChannel () ? true : false; - bool sam = i2p::client::context.GetSAMBridge () ? true : false; - bool i2cp = i2p::client::context.GetI2CPServer () ? true : false; - bool i2pcontrol; i2p::config::GetOption("i2pcontrol.enabled", i2pcontrol); - s << "\r\n"; - s << "\r\n"; - s << "\r\n"; - s << "\r\n"; - s << "\r\n"; - s << "\r\n"; - s << "\r\n"; - s << "
" << tr("Services") << "
" << "HTTP " << tr("Proxy") << "" << (httpproxy ? tr("Enabled") : tr("Disabled")) << "
" << "SOCKS " << tr("Proxy") << "" << (socksproxy ? tr("Enabled") : tr("Disabled")) << "
" << "BOB" << "" << (bob ? tr("Enabled") : tr("Disabled")) << "
" << "SAM" << "" << (sam ? tr("Enabled") : tr("Disabled")) << "
" << "I2CP" << "" << (i2cp ? tr("Enabled") : tr("Disabled")) << "
" << "I2PControl" << "" << (i2pcontrol ? tr("Enabled") : tr("Disabled")) << "
\r\n"; - } + s << "\r\n"; + s << "\r\n"; + s << "\r\n"; + s << "\r\n"; + s << "\r\n"; + s << "\r\n"; + bool i2pcontrol; i2p::config::GetOption("i2pcontrol.enabled", i2pcontrol); + s << "\r\n"; + s << "
Services
ServiceState
" << "HTTP Proxy" << "
" << "SOCKS Proxy" << "
" << "BOB" << "
" << "SAM" << "
" << "I2CP" << "
" << "I2PControl" << "
\r\n"; } void ShowLocalDestinations (std::stringstream& s) { - std::string webroot; i2p::config::GetOption("http.webroot", webroot); - s << "" << tr("Local Destinations") << ":
\r\n
\r\n"; + s << "Local Destinations:
\r\n
\r\n"; for (auto& it: i2p::client::context.GetDestinations ()) { auto ident = it.second->GetIdentHash (); - s << "\r\n" << std::endl; + s << ""; + s << i2p::client::context.GetAddressBook ().ToAddress(ident) << "
\r\n" << std::endl; } - s << "
\r\n"; auto i2cpServer = i2p::client::context.GetI2CPServer (); if (i2cpServer && !(i2cpServer->GetSessions ().empty ())) { - s << "
I2CP "<< tr("Local Destinations") << ":
\r\n
\r\n"; + s << "
I2CP Local Destinations:
\r\n
\r\n"; for (auto& it: i2cpServer->GetSessions ()) { auto dest = it.second->GetDestination (); @@ -410,204 +317,84 @@ namespace http { { auto ident = dest->GetIdentHash (); auto& name = dest->GetNickname (); - s << "
[ "; - s << name << " ] ⇔ " << i2p::client::context.GetAddressBook ().ToAddress(ident) <<"
\r\n" << std::endl; + s << "[ "; + s << name << " ] ⇔ " << i2p::client::context.GetAddressBook ().ToAddress(ident) <<"
\r\n" << std::endl; } } - s << "
\r\n"; } } - static void ShowHop(std::stringstream& s, const i2p::data::IdentityEx& ident) + static void ShowLeaseSetDestination (std::stringstream& s, std::shared_ptr dest) { - auto identHash = ident.GetIdentHash(); - auto router = i2p::data::netdb.FindRouter(identHash); - s << i2p::data::GetIdentHashAbbreviation(identHash); - if (router) - s << " " << router->GetBandwidthCap() << ""; - } - - static void ShowLeaseSetDestination (std::stringstream& s, std::shared_ptr dest, uint32_t token) - { - s << "Base32:
\r\n
\r\n
\r\n"; - - s << "Base64:
\r\n
\r\n
\r\n"; - - if (dest->IsEncryptedLeaseSet ()) + if(dest->GetNumRemoteLeaseSets()) { - i2p::data::BlindedPublicKey blinded (dest->GetIdentity (), dest->IsPerClientAuth ()); - s << "
\r\n\r\n
\r\n"; - s << blinded.ToB33 () << ".b32.i2p
\r\n"; - s << "
\r\n
\r\n"; - } - - if (dest->IsPublic() && token && !dest->IsEncryptedLeaseSet ()) - { - std::string webroot; i2p::config::GetOption("http.webroot", webroot); - s << "
\r\n\r\n
\r\n" - "
\r\n" - " \r\n" - " \r\n" - " GetIdentHash ().ToBase32 () << "\">\r\n" - " " << tr("Domain") << ":\r\n\r\n" - " \r\n" - "
\r\n" << tr("Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.") << "\r\n
\r\n
\r\n
\r\n"; - } - - 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.second->GetIdentHash ().ToBase32 () << "
\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 (); - } - 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"; + for (const auto& it: dest->GetSessions ()) + s << i2p::client::context.GetAddressBook ().ToAddress(it.first) << " " << it.second->GetNumOutgoingTags () << "
\r\n"; + s << "

\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,466 +405,336 @@ 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) + 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 + i2p::data::LeaseSet ls(leaseSet->GetBuffer(), leaseSet->GetBufferLen()); + s << "
\r\n"; + if (!ls.IsValid()) + s << "
!! Invalid !!
\r\n"; + s << "
\r\n"; + s << "\r\n

\r\n"; + s << "Expires: " << ls.GetExpirationTime() << "
\r\n"; + auto leases = ls.GetNonExpiredLeases(); + s << "Non Expired Leases: " << leases.size() << "
\r\n"; + for ( auto & l : leases ) { - // 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)); - ls->Update (leaseSet->GetBuffer(), leaseSet->GetBufferLen(), false); - } - 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"; + s << "Gateway: " << l->tunnelGateway.ToBase64() << "
\r\n"; + s << "TunnelID: " << l->tunnelID << "
\r\n"; + s << "EndDate: " << l->endDate << "
\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"; - -#if ((!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) || defined(ANDROID_BINARY)) + s << " Accept transit tunnels
\r\n"; +#if (!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) 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"; - } - } - - template - static void ShowTransportSessions (std::stringstream& s, const Sessions& sessions, const std::string name) - { - auto comp = [](typename Sessions::mapped_type a, typename Sessions::mapped_type b) - { return a->GetRemoteEndpoint() < b->GetRemoteEndpoint(); }; - std::set sortedSessions(comp); - for (const auto& it : sessions) - { - auto ret = sortedSessions.insert(it.second); - if (!ret.second) - LogPrint(eLogError, "HTTPServer: Duplicate remote endpoint detected: ", (*ret.first)->GetRemoteEndpoint()); - } - std::stringstream tmp_s, tmp_s6; uint16_t cnt = 0, cnt6 = 0; - for (const auto& it: sortedSessions) - { - auto endpoint = it->GetRemoteEndpoint (); - if (it && it->IsEstablished () && endpoint.address ().is_v4 ()) - { - 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; - cnt++; - } - if (it && it->IsEstablished () && 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; - cnt6++; - } - } - if (!tmp_s.str ().empty ()) - { - s << "
\r\n\r\n
" - << tmp_s.str () << "
\r\n
\r\n"; - } - if (!tmp_s6.str ().empty ()) - { - s << "
\r\n\r\n
" - << tmp_s6.str () << "
\r\n
\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"; } } void ShowTransports (std::stringstream& s) { - s << "" << tr("Transports") << ":
\r\n"; - auto ntcp2Server = i2p::transport::transports.GetNTCP2Server (); - if (ntcp2Server) + s << "Transports:
\r\n
\r\n"; + auto ntcpServer = i2p::transport::transports.GetNTCPServer (); + if (ntcpServer) { - auto sessions = ntcp2Server->GetNTCP2Sessions (); + auto sessions = ntcpServer->GetNTCPSessions (); if (!sessions.empty ()) - ShowTransportSessions (s, sessions, "NTCP2"); + { + std::stringstream tmp_s, tmp_s6; uint16_t cnt = 0, cnt6 = 0; + for (const auto& it: sessions ) + { + if (it.second && it.second->IsEstablished () && !it.second->GetSocket ().remote_endpoint ().address ().is_v6 ()) + { + // 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.second && it.second->IsEstablished () && it.second->GetSocket ().remote_endpoint ().address ().is_v6 ()) + { + 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

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

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

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

\r\n
\r\n"; + } + } } - 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"; + } } } void ShowSAMSessions (std::stringstream& s) - { - std::string webroot; i2p::config::GetOption("http.webroot", webroot); - auto sam = i2p::client::context.GetSAMBridge (); - if (!sam) - { - ShowError(s, tr("SAM disabled")); - return; - } - - if (sam->GetSessions ().size ()) - { - 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"; - } - 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) { auto sam = i2p::client::context.GetSAMBridge (); if (!sam) { - ShowError(s, tr("SAM disabled")); + ShowError(s, "SAM disabled"); return; } + s << "SAM Sessions:
\r\n
\r\n"; + for (auto& it: sam->GetSessions ()) + { + auto& name = it.second->localDestination->GetNickname (); + s << ""; + s << name << " (" << it.first << ")
\r\n" << std::endl; + } + } + static void ShowSAMSession (std::stringstream& s, const std::string& id) + { + s << "SAM Session:
\r\n
\r\n"; + auto sam = i2p::client::context.GetSAMBridge (); + if (!sam) { + 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"; - for (const auto& it: sam->ListSockets(id)) + s << "Streams:
\r\n"; + for (const auto& it: session->ListSockets()) { - 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"; } } - HTTPConnection::HTTPConnection (std::string hostname, std::shared_ptr socket): - m_Socket (socket), m_BufferLen (0), expected_host(hostname) + HTTPConnection::HTTPConnection (std::shared_ptr socket): + m_Socket (socket), m_Timer (socket->get_io_service ()), m_BufferLen (0) { /* cache options */ i2p::config::GetOption("http.auth", needAuth); @@ -1088,8 +745,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 +786,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; @@ -1141,11 +797,25 @@ namespace http { auto provided = req.GetHeader ("Authorization"); if (provided.length () > 0) { - std::string expected = "Basic " + i2p::data::ToBase64Standard (user + ":" + pass); - if (expected == provided) return true; + bool result = false; + + std::string expected = user + ":" + pass; + size_t b64_sz = i2p::data::Base64EncodingBufferSize(expected.length()) + 1; + char * b64_creds = new char[b64_sz]; + std::size_t len = 0; + len = i2p::data::ByteStreamToBase64((unsigned char *)expected.c_str(), expected.length(), b64_creds, b64_sz); + /* if we decoded properly then check credentials */ + if(len) { + b64_creds[len] = '\0'; + expected = "Basic "; + expected += b64_creds; + result = expected == provided; + } + delete [] b64_creds; + return result; } - 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 +825,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; @@ -1164,37 +834,14 @@ namespace http { return; } - bool strictheaders; - i2p::config::GetOption("http.strictheaders", strictheaders); - if (strictheaders) - { - std::string http_hostname; - i2p::config::GetOption("http.hostname", http_hostname); - std::string host = req.GetHeader("Host"); - auto idx = host.find(':'); - /* strip out port so it's just host */ - if (idx != std::string::npos && idx > 0) - { - host = host.substr(0, idx); - } - if (!(host == expected_host || host == http_hostname)) - { - /* deny request as it's from a non whitelisted hostname */ - res.code = 403; - content = "host mismatch"; - SendReply(res, content); - 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); res.add_header("Refresh", "10"); } ShowPageTail (s); @@ -1205,24 +852,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 +868,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 +887,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 +900,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,217 +913,70 @@ 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)) +#if (!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) 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)) 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) << ""; - res.add_header("Refresh", redirect.c_str()); + 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", "5; url=/?page=commands"); } 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_Hostname(address) + 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)) { } @@ -1512,27 +1002,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 +1024,6 @@ namespace http { void HTTPServer::Run () { - i2p::util::SetThreadName("Webconsole"); - while (m_IsRunning) { try @@ -1553,7 +1032,7 @@ namespace http { } catch (std::exception& ex) { - LogPrint (eLogError, "HTTPServer: Runtime exception: ", ex.what ()); + LogPrint (eLogError, "HTTPServer: runtime exception: ", ex.what ()); } } } @@ -1561,26 +1040,28 @@ 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 (); } void HTTPServer::CreateConnection(std::shared_ptr newSocket) { - auto conn = std::make_shared (m_Hostname, newSocket); + auto conn = std::make_shared (newSocket); conn->Receive (); } } // http diff --git a/daemon/HTTPServer.h b/daemon/HTTPServer.h index 38b790d4..f1ca10fc 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,14 +16,12 @@ 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 { public: - HTTPConnection (std::string serverhost, std::shared_ptr socket); + HTTPConnection (std::shared_ptr socket); void Receive (); private: @@ -45,18 +35,17 @@ 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: std::shared_ptr m_Socket; + boost::asio::deadline_timer m_Timer; char m_Buffer[HTTP_CONNECTION_BUFFER_SIZE + 1]; size_t m_BufferLen; std::string m_SendBuffer; bool needAuth; std::string user; std::string pass; - std::string expected_host; static std::map m_Tokens; // token->timestamp in seconds }; @@ -83,25 +72,21 @@ 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 + void ShowStatus (std::stringstream& s, bool includeHiddenContent); + 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..494ea026 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,50 @@ 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; // 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; } I2PControlService::~I2PControlService () @@ -110,7 +105,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 +114,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 +127,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 +182,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 +215,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 +226,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 +246,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 +265,30 @@ 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::SendResponse (std::shared_ptr socket, std::shared_ptr buf, std::ostringstream& response, bool isHtml) { @@ -310,12 +298,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 +311,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 +369,93 @@ 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 +463,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 +481,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 +495,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 +507,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) { @@ -447,23 +552,23 @@ namespace client EVP_PKEY_assign_RSA (pkey, rsa); X509 * x509 = X509_new (); ASN1_INTEGER_set (X509_get_serialNumber (x509), 1); - X509_gmtime_adj (X509_getm_notBefore (x509), 0); - X509_gmtime_adj (X509_getm_notAfter (x509), I2P_CONTROL_CERTIFICATE_VALIDITY*24*60*60); // expiration + X509_gmtime_adj (X509_get_notBefore (x509), 0); + X509_gmtime_adj (X509_get_notAfter (x509), I2P_CONTROL_CERTIFICATE_VALIDITY*24*60*60); // expiration X509_set_pubkey (x509, pkey); // public key X509_NAME * name = X509_get_subject_name (x509); X509_NAME_add_entry_by_txt (name, "C", MBSTRING_ASC, (unsigned char *)"A1", -1, -1, 0); // country (Anonymous proxy) X509_NAME_add_entry_by_txt (name, "O", MBSTRING_ASC, (unsigned char *)I2P_CONTROL_CERTIFICATE_ORGANIZATION, -1, -1, 0); // organization X509_NAME_add_entry_by_txt (name, "CN", MBSTRING_ASC, (unsigned char *)I2P_CONTROL_CERTIFICATE_COMMON_NAME, -1, -1, 0); // common name X509_set_issuer_name (x509, name); // set issuer to ourselves - X509_sign (x509, pkey, EVP_sha1 ()); // sign, last param must be NULL for EdDSA + 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,12 +577,12 @@ 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); } diff --git a/daemon/I2PControl.h b/daemon/I2PControl.h index 83dd6549..a7ed1eab 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,82 @@ 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; + // 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); // 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); + 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; }; } } 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..ba7b408c 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) - TryPortMapping (address); + if (!address->host.is_v6 ()) + 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 ()) + 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..a9c48fee 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,25 @@ 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; } - 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 +164,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 +172,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 +194,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/.gitignore b/debian/.gitignore index b03f9e24..6bc6bcf2 100644 --- a/debian/.gitignore +++ b/debian/.gitignore @@ -1,9 +1,9 @@ -debhelper-build-stamp files i2pd-dbg.substvars +i2pd-dbg/ i2pd.postinst.debhelper i2pd.postrm.debhelper i2pd.prerm.debhelper i2pd.substvars i2pd/ -i2pd-dbg/ + 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..afddc797 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,304 +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 - - -- orignal Thu, 9 May 2019 16:00:00 +0000 - -i2pd (2.24.0-1) unstable; urgency=medium - - * updated to version 2.24.0/0.9.39 - - -- orignal Thu, 21 Mar 2019 16:00:00 +0000 - -i2pd (2.23.0-1) unstable; urgency=medium - - * updated to version 2.23.0/0.9.38 - * update docs, dirs, install, links files - - -- orignal Mon, 21 Jan 2019 16:00:00 +0000 - -i2pd (2.22.0-1) unstable; urgency=medium - - * updated to version 2.22.0/0.9.37 - * update manpage (1) - * update links, install files to support tunnelsdir option - * renamed and updated patch (#1210) - - -- r4sas Fri, 09 Nov 2018 02:00:00 +0000 - -i2pd (2.21.1-1) unstable; urgency=medium - - * updated to version 2.21.1 - - -- orignal Mon, 22 Oct 2018 16:00:00 +0000 - -i2pd (2.21.0-1) unstable; urgency=medium - - * updated to version 2.21.0/0.9.37 - - -- orignal Thu, 4 Oct 2018 16:00:00 +0000 - -i2pd (2.20.0-1) unstable; urgency=medium - - * updated to version 2.20.0/0.9.36 - - -- orignal Thu, 23 Aug 2018 16:00:00 +0000 - -i2pd (2.19.0-1) unstable; urgency=medium - - * updated to version 2.19.0/0.9.35 - * update manpage (1) - * update docfiles - * update build rules - * fixes in systemd unit (#1089, #1142, #1154, #1155) - * package now building with systemd support - - -- R4SAS Tue, 26 Jun 2018 16:27:45 +0000 - i2pd (2.18.0-1) unstable; urgency=low * updated to version 2.18.0/0.9.33 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..7bd18ebb 100644 --- a/debian/control +++ b/debian/control @@ -1,18 +1,34 @@ 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 +Maintainer: R4SAS +Build-Depends: debhelper (>= 9), dpkg-dev (>= 1.16.1~), 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, dh-apparmor +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 -Depends: ${shlibs:Depends}, ${misc:Depends}, lsb-base, -Description: Full-featured C++ implementation of I2P client. +Pre-Depends: adduser +Depends: ${shlibs:Depends}, ${misc:Depends} +Suggests: tor, privoxy, apparmor +Description: 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. + . + 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} +Suggests: gdb +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/i2pd.1 b/debian/i2pd.1 index 42c282d9..e1390891 100644 --- a/debian/i2pd.1 +++ b/debian/i2pd.1 @@ -1,19 +1,22 @@ -.TH "I2PD" "1" "June 20, 2018" +.TH I2PD "1" "March 31, 2015" -.SH "NAME" -i2pd \- Full-featured C++ implementation of I2P client. -.SH "SYNOPSIS" +.SH NAME +i2pd \- Load-balanced unspoofable packet switching network + +.SH SYNOPSIS .B i2pd [\fIOPTION1\fR] [\fIOPTION2\fR]... -.SH "DESCRIPTION" + +.SH DESCRIPTION i2pd is a C++ implementation of the router for the I2P anonymizing network, offering a simple layer that identity-sensitive applications can use to securely communicate. All data is wrapped with several layers of encryption, and the network is both distributed and dynamic, with no trusted parties. + .PP Any of the configuration options below can be used in the \fBDAEMON_ARGS\fR variable in \fI/etc/default/i2pd\fR. -.SH "OPTIONS" +.BR .TP \fB\-\-help\fR Show available options. @@ -33,79 +36,50 @@ Where to write pidfile (don\'t write by default) \fB\-\-log=\fR Logs destination: \fIstdout\fR, \fIfile\fR, \fIsyslog\fR (\fIstdout\fR if not set, \fIfile\fR - otherwise, for compatibility) .TP -\fB\-\-logfile=\fR +\fB\-\-logfile\fR Path to logfile (default - autodetect) .TP \fB\-\-loglevel=\fR -Log messages above this level (\fIdebug\fR, \fBinfo\fR, \fIwarn\fR, \fIerror\fR, \fInone\fR) -.TP -\fB\-\-logclftime\fR -Log messages with full CLF-formatted date and time (\fIdisabled\fR by default) +Log messages above this level (\fIdebug\fR, \fBinfo\fR, \fIwarn\fR, \fIerror\fR) .TP \fB\-\-datadir=\fR Path to storage of i2pd data (RI, keys, peer profiles, ...) .TP -\fB\-\-tunnelsdir=\fR -Path to tunnels configuration files (default: \fI~/.i2pd/tunnels.d\fR or \fI/var/lib/i2pd/tunnels.d\fR) -.TP \fB\-\-host=\fR The external IP address .TP \fB\-\-port=\fR The port to listen on for incoming connections .TP -\fB\-\-ifname=\fR -The network interface to bind to -.TP -\fB\-\-ifname4=\fR -The network interface to bind to for IPv4 connections -.TP -\fB\-\-ifname6=\fR -The network interface to bind to for IPv6 connections -.TP -\fB\-\-ipv4=\fR -Enable communication through ipv4 (\fIenabled\fR by default) -.TP -\fB\-\-ipv6\fR -Enable communication through ipv6 (\fIdisabled\fR by default) -.TP -\fB\-\-ntcp=\fR -Enable usage of NTCP transport (\fIenabled\fR by default) -.TP -\fB\-\-ntcpproxy=\fR -Set proxy URL for NTCP transport -.TP -\fB\-\-ssu=\fR -Enable usage of SSU transport (\fIenabled\fR by default) -.TP -\fB\-\-notransit\fR -Router will not accept transit tunnels at startup (\fIdisabled\fR by default) -.TP -\fB\-\-floodfill\fR -Router will be floodfill (\fIdisabled\fR by default) -.TP -\fB\-\-bandwidth=\fR -Bandwidth limit: integer in KBps or letter aliases: \fBL (32KBps)\fR, \fIO (256)\fR, \fIP (2048)\fR, \fIX (>9000)\fR -.TP -\fB\-\-share=\fR -Limit of transit traffic from max bandwidth in percents. (default: 100) -.TP \fB\-\-daemon\fR -Router will go to background after start (\fIdisabled\fR by default) +Router will go to background after start .TP \fB\-\-service\fR -Router will use system folders like \fI/var/lib/i2pd\fR (\fIdisabled\fR by default) +Router will use system folders like \fI/var/lib/i2pd\fR +.TP +\fB\-\-ipv6\fR +Enable communication through ipv6. false by default +.TP +\fB\-\-notransit\fR +Router will not accept transit tunnels at startup +.TP +\fB\-\-floodfill\fR +Router will be floodfill +.TP +\fB\-\-bandwidth=\fR +Bandwidth limit: integer in KBps or letter aliases: \fIL (32KBps)\fR, O (256), P (2048), X (>9000) .TP \fB\-\-family=\fR Name of a family, router belongs to. .PP -Switches, which enabled by default (like \fB\-\-ssu\fR, \fB\-\-ntcp\fR, etc.), can be disabled in config file. -.RE -See service-specific parameters in example config file \fI/usr/share/doc/i2pd/i2pd.conf.gz\fR -.SH "FILES" +See service-specific parameters in example config file \fIcontrib/i2pd.conf\fR + +.SH FILES +.PP /etc/i2pd/i2pd.conf, /etc/i2pd/tunnels.conf, /etc/default/i2pd .RS 4 i2pd configuration files (when running as a system service) + .RE .PP /var/lib/i2pd/ @@ -116,15 +90,16 @@ i2pd profile directory (when running as a system service, see \fB\-\-service\fR $HOME/.i2pd/ .RS 4 i2pd profile directory (when running as a normal user) -.SH "SEE ALSO" -Documentation at Read the Docs: \m[blue]\fBhttps://i2pd\&.readthedocs\&.io/en/latest/\fR\m[] -.SH "AUTHOR" -This manual page was written by kytv <\m[blue]\fBkillyourtv@i2pmail\&.org\fR\m[]> for the Debian system (but may be used by others). .RE -Updated by hagen <\m[blue]\fBhagen@i2pmail\&.org\fR\m[]> in 2016. -.RE -Updated by R4SAS <\m[blue]\fBr4sas@i2pmail\&.org\fR\m[]> in 2018. .PP -Permission is granted to copy, distribute and/or modify this document under the terms of the GNU General Public License, Version 2 or any later version published by the Free Software Foundation. -.RE +/usr/share/doc/i2pd/examples/hosts.txt.gz +.RS 4 +default I2P hosts file +.SH AUTHOR +This manual page was written by kytv for the Debian system (but may be used by others). +.PP +Updated by hagen in 2016. +.PP +Permission is granted to copy, distribute and/or modify this document under the terms of the GNU General Public License, Version 2 or any later version published by the Free Software Foundation +.BR On Debian systems, the complete text of the GNU General Public License can be found in \fI/usr/share/common-licenses/GPL\fR 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..e4ed01e1 100644 --- a/debian/i2pd.init +++ b/debian/i2pd.init @@ -13,12 +13,11 @@ 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 TUNCONF=/etc/$NAME/tunnels.conf -TUNDIR=/etc/$NAME/tunnels.conf.d LOGFILE=/var/log/$NAME/$NAME.log USER="i2pd" @@ -54,7 +53,7 @@ do_start() || return 1 start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --chuid "$USER" -- \ --service --daemon --log=file --logfile=$LOGFILE --conf=$I2PCONF --tunconf=$TUNCONF \ - --tunnelsdir=$TUNDIR --pidfile=$PIDFILE $DAEMON_OPTS > /dev/null 2>&1 \ + --pidfile=$PIDFILE $DAEMON_OPTS > /dev/null 2>&1 \ || return 2 return $? } diff --git a/debian/i2pd.install b/debian/i2pd.install index bde52854..d81101fc 100644 --- a/debian/i2pd.install +++ b/debian/i2pd.install @@ -1,6 +1,6 @@ -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..e8e2473b 100644 --- a/debian/i2pd.links +++ b/debian/i2pd.links @@ -1,4 +1,4 @@ -etc/i2pd/i2pd.conf var/lib/i2pd/i2pd.conf +etc/i2pd/i2pd.conf var/lib/i2pd/i2pd.conf etc/i2pd/tunnels.conf var/lib/i2pd/tunnels.conf -etc/i2pd/tunnels.conf.d var/lib/i2pd/tunnels.d +etc/i2pd/subscriptions.txt var/lib/i2pd/subscriptions.txt 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 85% rename from contrib/openrc/i2pd.openrc rename to debian/i2pd.openrc index 0233eed8..2ad10bc5 100644 --- a/contrib/openrc/i2pd.openrc +++ b/debian/i2pd.openrc @@ -4,11 +4,10 @@ pidfile="/var/run/i2pd/i2pd.pid" logfile="/var/log/i2pd/i2pd.log" mainconf="/etc/i2pd/i2pd.conf" tunconf="/etc/i2pd/tunnels.conf" -tundir="/etc/i2pd/tunnels.conf.d" name="i2pd" -command="/usr/bin/i2pd" -command_args="--service --daemon --log=file --logfile=$logfile --conf=$mainconf --tunconf=$tunconf --tunnelsdir=$tundir --pidfile=$pidfile" +command="/usr/sbin/i2pd" +command_args="--service --daemon --log=file --logfile=$logfile --conf=$mainconf --tunconf=$tunconf --pidfile=$pidfile" description="i2p router written in C++" required_dirs="/var/lib/i2pd" required_files="$mainconf" diff --git a/debian/i2pd.service b/debian/i2pd.service deleted file mode 120000 index 57d6b4da..00000000 --- a/debian/i2pd.service +++ /dev/null @@ -1 +0,0 @@ -../contrib/debian/i2pd.service \ No newline at end of file diff --git a/debian/i2pd.tmpfile b/debian/i2pd.tmpfile deleted file mode 120000 index 22dfb4cf..00000000 --- a/debian/i2pd.tmpfile +++ /dev/null @@ -1 +0,0 @@ -../contrib/debian/i2pd.tmpfile \ No newline at end of file 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..2dff9e09 --- /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 + + ifeq ($(WEBSOCKETS),1) 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/series b/debian/patches/series index f97fdf65..972d2a10 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -1 +1 @@ -01-upnp.patch +01-tune-build-opts.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..4654ae6c 100755 --- a/debian/rules +++ b/debian/rules @@ -1,13 +1,21 @@ #!/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 + +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/docs/Doxyfile b/docs/Doxyfile index 275d668e..c434d2dc 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -1094,7 +1094,7 @@ HTML_STYLESHEET = # cascading style sheets that are included after the standard style sheets # created by doxygen. Using this option one can overrule certain style aspects. # This is preferred over using HTML_STYLESHEET since it does not replace the -# standard style sheet and is therefore more robust against future updates. +# standard style sheet and is therefor more robust against future updates. # Doxygen will copy the style sheet files to the output directory. # Note: The order of the extra stylesheet files is of importance (e.g. the last # stylesheet in the list overrules the setting of the previous ones in the @@ -1637,7 +1637,7 @@ EXTRA_PACKAGES = # Note: Only use a user-defined header if you know what you are doing! The # following commands have a special meaning inside the header: $title, # $datetime, $date, $doxygenversion, $projectname, $projectnumber, -# $projectbrief, $projectlogo. Doxygen will replace $title with the empty string, +# $projectbrief, $projectlogo. Doxygen will replace $title with the empy string, # for the replacement values of the other commands the user is referred to # HTML_HEADER. # This tag requires that the tag GENERATE_LATEX is set to YES. diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100644 index 00000000..24a415aa --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +ARGS="" +if [ "${ENABLE_IPV6}" != "" ]; then + ARGS="${ARGS} –ipv6" +fi + +if [ "${LOGLEVEL}" != "" ]; then + ARGS="${ARGS} –loglevel=${LOGLEVEL}" +fi + +if [ "${ENABLE_AUTH}" != "" ]; then + ARGS="${ARGS} –http.auth" +fi + + +# To make ports exposeable +DEFAULT_ARGS=" –http.address=0.0.0.0 –httpproxy.address=0.0.0.0 -socksproxy.address=0.0.0.0 –sam.address=0.0.0.0 –bob.address=0.0.0.0 –i2cp.address=0.0.0.0 –i2pcontrol.port=0.0.0.0 –upnp.enabled=false -service " + +mkdir -p /var/lib/i2pd && chown -R i2pd:nobody /var/lib/i2pd && chmod u+rw /var/lib/i2pd + +gosu i2pd i2pd $DEFAULT_ARGS $ARGS + + 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..09f04c05 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,148 @@ 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 ()); - // replace '-' by '+' and '~' by '/' - for (auto& ch: str) - if (ch == '-') - ch = '+'; - else if (ch == '~') - ch = '/'; - return str; + auto d = div (input_size, 3); + if (d.rem) d.quot++; + return 4*d.quot; } /* @@ -224,19 +221,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 +254,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 +280,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..bc92376f 100644 --- a/libi2pd/Base.h +++ b/libi2pd/Base.h @@ -1,52 +1,24 @@ -/* -* 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); } // 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.h b/libi2pd/CPU.h deleted file mode 100644 index 3fc38d47..00000000 --- a/libi2pd/CPU.h +++ /dev/null @@ -1,24 +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 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 - -#endif diff --git a/libi2pd/Config.cpp b/libi2pd/Config.cpp index 939cd9ff..636e4986 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; @@ -33,43 +32,37 @@ namespace config { options_description general("General options"); general.add_options() ("help", "Show this message") - ("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") ("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)") - ("logclftime", bool_switch()->default_value(false), "Write full CLF-formatted date and time to log (default: disabled, write only time)") + ("loglevel", value()->default_value("info"), "Set the minimal level of log messages (debug, info, warn, error)") + ("logclftime", value()->default_value(false), "Write full CLF-formatted date and time to log (default: 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?") ("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") - ("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)") + ("ipv4", value()->default_value(true), "Enable communication through ipv4") + ("ipv6", value()->zero_tokens()->default_value(false), "Enable communication through ipv6") ("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)") - ("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") + ("daemon", value()->zero_tokens()->default_value(false), "Router will go to background after start") + ("service", value()->zero_tokens()->default_value(false), "Router will use system folders like '/var/lib/i2pd'") + ("notransit", value()->zero_tokens()->default_value(false), "Router will not accept transit tunnels at startup") + ("floodfill", value()->zero_tokens()->default_value(false), "Router will be floodfill") + ("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", value()->default_value(true), "Enable NTCP transport") + ("ssu", value()->default_value(true), "Enable SSU transport") + ("ntcpproxy", value()->default_value(""), "Proxy URL for NTCP transport") #ifdef _WIN32 - ("svcctl", value()->default_value(""), "Ignored") - ("insomnia", bool_switch()->default_value(false), "Prevent system from sleeping (default: disabled)") - ("close", value()->default_value("ask"), "Action on close: minimize, exit, ask") + ("svcctl", value()->default_value(""), "Windows service management ('install' or 'remove')") + ("insomnia", value()->zero_tokens()->default_value(false), "Prevent system from sleeping") + ("close", value()->default_value("ask"), "Action on close: minimize, exit, ask") // TODO: add custom validator or something #endif ; @@ -77,26 +70,19 @@ 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 probabalistic backoff with ntcp sessions (default: use system limit)") + ("limits.ntcphard", value()->default_value(0), "Maximum number of ntcp sessions (default: use system limit)") ; options_description httpserver("HTTP Server options"); httpserver.add_options() - ("http.enabled", value()->default_value(true), "Enable or disable webconsole") - ("http.address", value()->default_value("127.0.0.1"), "Webconsole listen address") - ("http.port", value()->default_value(7070), "Webconsole listen port") - ("http.auth", value()->default_value(false), "Enable Basic HTTP auth for webconsole") - ("http.user", value()->default_value("i2pd"), "Username for basic auth") - ("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.enabled", value()->default_value(true), "Enable or disable webconsole") + ("http.address", value()->default_value("127.0.0.1"), "Webconsole listen address") + ("http.port", value()->default_value(7070), "Webconsole listen port") + ("http.auth", value()->default_value(false), "Enable Basic HTTP auth for webconsole") + ("http.user", value()->default_value("i2pd"), "Username for basic auth") + ("http.pass", value()->default_value(""), "Password for basic auth (default: random, see logs)") ; options_description httpproxy("HTTP Proxy options"); @@ -104,27 +90,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 +107,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 +139,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"); @@ -197,8 +147,8 @@ namespace config { ("i2pcontrol.address", value()->default_value("127.0.0.1"), "I2PCP listen address") ("i2pcontrol.port", value()->default_value(7650), "I2PCP listen port") ("i2pcontrol.password", value()->default_value("itoopie"), "I2PCP access password") - ("i2pcontrol.cert", value()->default_value("i2pcontrol.crt.pem"), "I2PCP connection certificate") - ("i2pcontrol.key", value()->default_value("i2pcontrol.key.pem"), "I2PCP connection certificate key") + ("i2pcontrol.cert", value()->default_value("i2pcontrol.crt.pem"), "I2PCP connection cerificate") + ("i2pcontrol.key", value()->default_value("i2pcontrol.key.pem"), "I2PCP connection cerificate key") ; bool upnp_default = false; @@ -208,13 +158,13 @@ namespace config { options_description upnp("UPnP options"); upnp.add_options() ("upnp.enabled", value()->default_value(upnp_default), "Enable or disable UPnP: automatic port forwarding") - ("upnp.name", value()->default_value("I2Pd"), "Name i2pd appears in UPnP forwarding list") + ("upnp.name", value()->default_value("I2Pd"), "Name i2pd appears in UPnP forwardings list") ; 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 +176,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-fr.i2pd.xyz/," + "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://i2p-0.manas.ca:8443/," + "https://download.xxlspeed.com/," + "https://reseed-ru.lngserv.ru/," + "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"); @@ -288,71 +228,12 @@ namespace config { ("exploratory.outbound.quantity", value()->default_value(3), "Exploratory outbound tunnels quantity") ; - 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") - ; - - options_description nettime("Time sync options"); - nettime.add_options() - ("nettime.enabled", value()->default_value(false), "Enable NTP time sync (default: disabled)") - ("nettime.ntpservers", value()->default_value( - "0.pool.ntp.org," - "1.pool.ntp.org," - "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)") - ; - - 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)") - ; - - 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 +243,8 @@ 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 +253,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 +262,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); } @@ -401,23 +272,6 @@ namespace config { 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 " - << BOOST_VERSION / 100000 << "." // maj. version - << BOOST_VERSION / 100 % 1000 << "." // min. version - << BOOST_VERSION % 100 // patch version - << std::endl; -#if defined(OPENSSL_VERSION_TEXT) - std::cout << OPENSSL_VERSION_TEXT << std::endl; -#endif -#if defined(LIBRESSL_VERSION_TEXT) - std::cout << LIBRESSL_VERSION_TEXT << std::endl; -#endif - - exit(EXIT_SUCCESS); - } } void ParseConfig(const std::string& path) @@ -428,7 +282,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 +292,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); }; @@ -475,3 +327,4 @@ namespace config { } // namespace config } // namespace i2p + diff --git a/libi2pd/Config.h b/libi2pd/Config.h index 79463e65..0bbcd5b1 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 it's list with description + * 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 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 othervise + */ + bool IsDefault(const char *name); } } diff --git a/libi2pd/Crypto.cpp b/libi2pd/Crypto.cpp index c41b4c10..6109529d 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 @@ -16,24 +8,14 @@ #include #include "TunnelBase.h" #include -#if OPENSSL_HKDF -#include -#endif -#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 -#include -#include -#endif -#include "CPU.h" -#include "Crypto.h" -#include "Ed25519.h" -#include "I2PEndian.h" #include "Log.h" +#include "Crypto.h" 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 +35,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 +49,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 +67,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 +102,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 +132,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 +139,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,83 +222,58 @@ namespace crypto static BIGNUM * (* g_ElggTable)[255] = nullptr; -// x25519 - X25519Keys::X25519Keys () +// DH + + DHKeys::DHKeys () { - m_Ctx = EVP_PKEY_CTX_new_id (NID_X25519, NULL); - m_Pkey = nullptr; + m_DH = DH_new (); + DH_set0_pqg (m_DH, BN_dup (elgp), NULL, BN_dup (elgg)); + DH_set0_key (m_DH, NULL, NULL); } - X25519Keys::X25519Keys (const uint8_t * priv, const uint8_t * pub) + DHKeys::~DHKeys () { - 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 + 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 { - size_t len = 32; - EVP_PKEY_get_raw_public_key (m_Pkey, m_PublicKey, &len); + 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); } - X25519Keys::~X25519Keys () + void DHKeys::Agree (const uint8_t * pub, uint8_t * shared) { - EVP_PKEY_CTX_free (m_Ctx); - if (m_Pkey) EVP_PKEY_free (m_Pkey); - } - - void X25519Keys::GenerateKeys () - { - if (m_Pkey) - { - EVP_PKEY_free (m_Pkey); - m_Pkey = nullptr; - } - EVP_PKEY_keygen_init (m_Ctx); - EVP_PKEY_keygen (m_Ctx, &m_Pkey); - EVP_PKEY_CTX_free (m_Ctx); - 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); - } - - bool X25519Keys::Agree (const uint8_t * pub, uint8_t * shared) - { - if (!pub || (pub[31] & 0x80)) return false; // not x25519 key - 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; - } - - void X25519Keys::GetPrivateKey (uint8_t * priv) const - { - 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); - } + BIGNUM * pk = BN_bin2bn (pub, 256, NULL); + DH_compute_key (shared, pk, m_DH); + BN_free (pk); } // 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 +281,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 +309,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 +353,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 +373,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) { - BN_CTX * ctx = BN_CTX_new (); BN_CTX_start (ctx); BIGNUM * q = BN_CTX_get (ctx); EC_GROUP_get_order(curve, q, ctx); @@ -463,7 +390,7 @@ namespace crypto bn2buf (x, encrypted + 1, len); bn2buf (y, encrypted + 1 + len, len); RAND_bytes (encrypted + 1 + 2*len, 256 - 2*len); - // encryption key and iv + // 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]; @@ -476,19 +403,18 @@ namespace crypto memcpy (m+33, data, 222); SHA256 (m+33, 222, m+1); // encrypt + encrypted[257] = 0; CBCEncryption encryption; encryption.SetKey (shared); - encrypted[257] = 0; - encryption.Encrypt (m, 256, iv, encrypted + 258); + encryption.SetIV (iv); + encryption.Encrypt (m, 256, encrypted + 258); 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 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); @@ -512,7 +438,8 @@ namespace crypto uint8_t m[256]; CBCDecryption decryption; decryption.SetKey (shared); - decryption.Decrypt (encrypted + 258, 256, iv, m); + decryption.SetIV (iv); + decryption.Decrypt (encrypted + 258, 256, m); // verify and copy uint8_t hash[32]; SHA256 (m + 33, 222, hash); @@ -532,7 +459,6 @@ namespace crypto EC_POINT_free (p); BN_CTX_end (ctx); - BN_CTX_free (ctx); return ret; } @@ -549,405 +475,421 @@ namespace crypto BN_CTX_free (ctx); } -// AES - ECBEncryption::ECBEncryption () - { - m_Ctx = EVP_CIPHER_CTX_new (); - } - - ECBEncryption::~ECBEncryption () - { - if (m_Ctx) - EVP_CIPHER_CTX_free (m_Ctx); - } - - void ECBEncryption::Encrypt (const uint8_t * in, uint8_t * out) - { - EVP_EncryptInit_ex (m_Ctx, EVP_aes_256_ecb(), NULL, m_Key, NULL); - EVP_CIPHER_CTX_set_padding (m_Ctx, 0); - int len; - EVP_EncryptUpdate (m_Ctx, out, &len, in, 16); - EVP_EncryptFinal_ex (m_Ctx, out + len, &len); - } +// HMAC + const uint64_t IPAD = 0x3636363636363636; + const uint64_t OPAD = 0x5C5C5C5C5C5C5C5C; - ECBDecryption::ECBDecryption () - { - m_Ctx = EVP_CIPHER_CTX_new (); - } - - ECBDecryption::~ECBDecryption () - { - if (m_Ctx) - EVP_CIPHER_CTX_free (m_Ctx); - } - - void ECBDecryption::Decrypt (const uint8_t * in, uint8_t * out) - { - EVP_DecryptInit_ex (m_Ctx, EVP_aes_256_ecb(), NULL, m_Key, NULL); - EVP_CIPHER_CTX_set_padding (m_Ctx, 0); - int len; - EVP_DecryptUpdate (m_Ctx, out, &len, in, 16); - EVP_DecryptFinal_ex (m_Ctx, out + len, &len); - } +#if defined(__AVX__) + static const uint64_t ipads[] = { IPAD, IPAD, IPAD, IPAD }; + static const uint64_t opads[] = { OPAD, OPAD, OPAD, OPAD }; +#endif - - CBCEncryption::CBCEncryption () - { - m_Ctx = EVP_CIPHER_CTX_new (); - } - - CBCEncryption::~CBCEncryption () + 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 { - if (m_Ctx) - EVP_CIPHER_CTX_free (m_Ctx); - } - - void CBCEncryption::Encrypt (const uint8_t * in, size_t len, const uint8_t * iv, uint8_t * out) - { - // len/16 - 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); - } - - CBCDecryption::CBCDecryption () - { - m_Ctx = EVP_CIPHER_CTX_new (); - } - - CBCDecryption::~CBCDecryption () - { - 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) - { - // len/16 - EVP_DecryptInit_ex (m_Ctx, EVP_aes_256_cbc(), NULL, m_Key, iv); - EVP_CIPHER_CTX_set_padding (m_Ctx, 0); - int l; - EVP_DecryptUpdate (m_Ctx, out, &l, in, len); - EVP_DecryptFinal_ex (m_Ctx, out + l, &l); - } - - void TunnelEncryption::Encrypt (const uint8_t * in, uint8_t * out) - { - uint8_t iv[16]; - m_IVEncryption.Encrypt (in, iv); // iv - m_LayerEncryption.Encrypt (in + 16, i2p::tunnel::TUNNEL_DATA_ENCRYPTED_SIZE, iv, out + 16); // data - m_IVEncryption.Encrypt (iv, out); // double iv - } - - void TunnelDecryption::Decrypt (const uint8_t * in, uint8_t * out) - { - uint8_t iv[16]; - m_IVDecryption.Decrypt (in, iv); // iv - m_LayerDecryption.Decrypt (in + 16, i2p::tunnel::TUNNEL_DATA_ENCRYPTED_SIZE, iv, out + 16); // data - m_IVDecryption.Decrypt (iv, out); // double iv - } - -// AEAD/ChaCha20/Poly1305 - - static bool AEADChaCha20Poly1305 (EVP_CIPHER_CTX * ctx, const uint8_t * msg, size_t msgLen, - const uint8_t * ad, size_t adLen, const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len, bool encrypt) - { - if (!ctx || len < msgLen) return false; - if (encrypt && len < msgLen + 16) return false; - bool ret = true; - int outlen = 0; - if (encrypt) - { - 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); - EVP_EncryptUpdate(ctx, NULL, &outlen, ad, adLen); - EVP_EncryptUpdate(ctx, buf, &outlen, msg, msgLen); - EVP_EncryptFinal_ex(ctx, buf + outlen, &outlen); - EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, 16, buf + msgLen); - } - else - { -#if defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x4000000fL - std::vector m(msgLen + 16); - if (msg == buf) - { - // we have to use different buffers otherwise verification fails - memcpy (m.data (), msg, msgLen + 16); - msg = m.data (); - } -#endif - EVP_DecryptInit_ex(ctx, EVP_chacha20_poly1305(), 0, 0, 0); - EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, 12, 0); - EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, 16, (uint8_t *)(msg + msgLen)); - EVP_DecryptInit_ex(ctx, NULL, NULL, key, nonce); - EVP_DecryptUpdate(ctx, NULL, &outlen, ad, adLen); - 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); - 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) - { - if (bufs.empty ()) return; - int outlen = 0; - EVP_EncryptInit_ex(m_Ctx, EVP_chacha20_poly1305(), 0, 0, 0); - EVP_CIPHER_CTX_ctrl(m_Ctx, EVP_CTRL_AEAD_SET_IVLEN, 12, 0); - EVP_EncryptInit_ex(m_Ctx, NULL, NULL, key, nonce); - for (const auto& it: bufs) - EVP_EncryptUpdate(m_Ctx, it.first, &outlen, it.first, it.second); - EVP_EncryptFinal_ex(m_Ctx, NULL, &outlen); - EVP_CIPHER_CTX_ctrl(m_Ctx, EVP_CTRL_AEAD_GET_TAG, 16, mac); - } - - AEADChaCha20Poly1305Decryptor::AEADChaCha20Poly1305Decryptor () - { - m_Ctx = EVP_CIPHER_CTX_new (); - } - - AEADChaCha20Poly1305Decryptor::~AEADChaCha20Poly1305Decryptor () - { - if (m_Ctx) - EVP_CIPHER_CTX_free (m_Ctx); - } - - bool AEADChaCha20Poly1305Decryptor::Decrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen, - const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len) - { - return AEADChaCha20Poly1305 (m_Ctx, msg, msgLen, ad, adLen, key, nonce, buf, len, false); - } - - static void ChaCha20 (EVP_CIPHER_CTX *ctx, const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out) - { - 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); + uint64_t buf[256]; + uint64_t hash[12]; // 96 bytes +#if defined(__AVX__) // for 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 - 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 + // 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); +#endif + + // concatenate with msg + memcpy (buf + 8, msg, len); + // calculate first hash + MD5((uint8_t *)buf, len + 64, (uint8_t *)(hash + 8)); // 16 bytes + + // calculate digest + MD5((uint8_t *)hash, 96, digest); + } + +// AES + #ifdef AESNI + + #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" + + void ECBCryptoAESNI::ExpandKey (const AESKey& key) + { + __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 + ); + } + + #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" + + void ECBEncryptionAESNI::Encrypt (const ChipherBlock * in, ChipherBlock * out) + { + __asm__ + ( + "movups (%[in]), %%xmm0 \n" + EncryptAES256(sched) + "movups %%xmm0, (%[out]) \n" + : : [sched]"r"(GetKeySchedule ()), [in]"r"(in), [out]"r"(out) : "%xmm0", "memory" + ); + } + + #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" + + void ECBDecryptionAESNI::Decrypt (const ChipherBlock * in, ChipherBlock * out) + { + __asm__ + ( + "movups (%[in]), %%xmm0 \n" + DecryptAES256(sched) + "movups %%xmm0, (%[out]) \n" + : : [sched]"r"(GetKeySchedule ()), [in]"r"(in), [out]"r"(out) : "%xmm0", "memory" + ); + } + + #define CallAESIMC(offset) \ + "movaps "#offset"(%[shed]), %%xmm0 \n" \ + "aesimc %%xmm0, %%xmm0 \n" \ + "movaps %%xmm0, "#offset"(%[shed]) \n" + + void ECBDecryptionAESNI::SetKey (const AESKey& key) + { + 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" + ); + } + +#endif + + + void CBCEncryption::Encrypt (int numBlocks, const ChipherBlock * in, ChipherBlock * out) + { +#ifdef 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 + for (int i = 0; i < numBlocks; i++) { - memcpy (out + 32, info.c_str (), l); out[l + 32] = 0x02; - HMAC(EVP_sha256(), prk, 32, out, l + 33, out + 32, &len); + *m_LastBlock.GetChipherBlock () ^= in[i]; + m_ECBEncryption.Encrypt (m_LastBlock.GetChipherBlock (), m_LastBlock.GetChipherBlock ()); + out[i] = *m_LastBlock.GetChipherBlock (); } #endif } -// Noise - - void NoiseSymmetricState::Init (const uint8_t * ck, const uint8_t * hh, const uint8_t * pub) + void CBCEncryption::Encrypt (const uint8_t * in, std::size_t len, 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); + // len/16 + int numBlocks = len >> 4; + if (numBlocks > 0) + Encrypt (numBlocks, (const ChipherBlock *)in, (ChipherBlock *)out); } - void NoiseSymmetricState::MixHash (const std::vector >& bufs) + void CBCEncryption::Encrypt (const uint8_t * in, uint8_t * out) { - 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); +#ifdef 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 + Encrypt (1, (const ChipherBlock *)in, (ChipherBlock *)out); +#endif } - void NoiseSymmetricState::MixKey (const uint8_t * sharedSecret) + void CBCDecryption::Decrypt (int numBlocks, const ChipherBlock * in, ChipherBlock * out) { - 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); +#ifdef 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 + 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; } - 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 +#endif } - void InitNoiseXKState (NoiseSymmetricState& state, const uint8_t * pub) + void CBCDecryption::Decrypt (const uint8_t * in, std::size_t len, uint8_t * out) { - 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); + int numBlocks = len >> 4; + if (numBlocks > 0) + Decrypt (numBlocks, (const ChipherBlock *)in, (ChipherBlock *)out); } - void InitNoiseXKState1 (NoiseSymmetricState& state, const uint8_t * pub) + void CBCDecryption::Decrypt (const uint8_t * in, uint8_t * out) { - 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); +#ifdef 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 + Decrypt (1, (const ChipherBlock *)in, (ChipherBlock *)out); +#endif } - void InitNoiseIKState (NoiseSymmetricState& state, const uint8_t * pub) + void TunnelEncryption::Encrypt (const uint8_t * in, uint8_t * out) { - 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); +#ifdef 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.GetKeySchedule ()), + [in]"r"(in), [out]"r"(out), [num]"r"(63) // 63 blocks = 1008 bytes + : "%xmm0", "%xmm1", "cc", "memory" + ); +#else + 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 +#endif } - -// init and terminate -/* std::vector > m_OpenSSLMutexes; + void TunnelDecryption::Decrypt (const uint8_t * in, uint8_t * out) + { +#ifdef 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.GetKeySchedule ()), + [in]"r"(in), [out]"r"(out), [num]"r"(63) // 63 blocks = 1008 bytes + : "%xmm0", "%xmm1", "%xmm2", "cc", "memory" + ); +#else + 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 +#endif + } + +/* 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 +901,17 @@ namespace crypto } }*/ + void InitCrypto (bool precomputation) { + SSL_library_init (); /* 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 +926,7 @@ namespace crypto if (g_ElggTable) { DestroyElggTable (g_ElggTable, -#if IS_X86_64 +#if defined(__x86_64__) ELGAMAL_FULL_EXPONENT_NUM_BYTES #else ELGAMAL_SHORT_EXPONENT_NUM_BYTES diff --git a/libi2pd/Crypto.h b/libi2pd/Crypto.h index 125a217c..e495d2e6 100644 --- a/libi2pd/Crypto.h +++ b/libi2pd/Crypto.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_H__ #define CRYPTO_H__ #include #include -#include #include #include #include @@ -21,23 +12,11 @@ #include #include #include -#include +#include #include "Base.h" #include "Tag.h" -// recognize openssl version and features -#if (OPENSSL_VERSION_NUMBER >= 0x010101000) // 1.1.1 -# define OPENSSL_HKDF 1 -# define OPENSSL_EDDSA 1 -# if (!defined(LIBRESSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER != 0x030000000)) // 3.0.0, regression in SipHash, not implemented in LibreSSL -# define OPENSSL_SIPHASH 1 -# endif -# if (OPENSSL_VERSION_NUMBER >= 0x030500000) // 3.5.0 -# define OPENSSL_PQ 1 -# endif -#endif - namespace i2p { namespace crypto @@ -45,116 +24,220 @@ 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 (); - // x25519 - class X25519Keys + // DH + class DHKeys { public: - X25519Keys (); - X25519Keys (const uint8_t * priv, const uint8_t * pub); // if pub is null, derive from priv - ~X25519Keys (); + DHKeys (); + ~DHKeys (); 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]; - EVP_PKEY_CTX * m_Ctx; - EVP_PKEY * m_Pkey; - bool m_IsElligatorIneligible = false; // true if definitely ineligible + DH * m_DH; + uint8_t m_PublicKey[256]; }; // 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); // 222 bytes data, 514 bytes encrypted + bool ECIESDecrypt (const EC_GROUP * curve, const BIGNUM * key, const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx); void GenerateECIESKeyPair (const EC_GROUP * curve, BIGNUM *& priv, EC_POINT *& pub); + // HMAC + typedef i2p::data::Tag<32> MACKey; + void HMACMD5Digest (uint8_t * msg, size_t len, const MACKey& key, uint8_t * digest); + // AES + struct ChipherBlock + { + uint8_t buf[16]; + + void operator^=(const ChipherBlock& other) // XOR + { +#if defined(__AVX__) // AVX + __asm__ + ( + "vmovups (%[buf]), %%xmm0 \n" + "vmovups (%[other]), %%xmm1 \n" + "vxorps %%xmm0, %%xmm1, %%xmm0 \n" + "vmovups %%xmm0, (%[buf]) \n" + : + : [buf]"r"(buf), [other]"r"(other.buf) + : "%xmm0", "%xmm1", "memory" + ); +#elif defined(__SSE__) // SSE + __asm__ + ( + "movups (%[buf]), %%xmm0 \n" + "movups (%[other]), %%xmm1 \n" + "pxor %%xmm1, %%xmm0 \n" + "movups %%xmm0, (%[buf]) \n" + : + : [buf]"r"(buf), [other]"r"(other.buf) + : "%xmm0", "%xmm1", "memory" + ); +#else + // TODO: implement it better + for (int i = 0; i < 16; i++) + buf[i] ^= other.buf[i]; +#endif + } + }; + typedef i2p::data::Tag<32> AESKey; - + + template + class AESAlignedBuffer // 16 bytes alignment + { + public: + + AESAlignedBuffer () + { + m_Buf = m_UnalignedBuffer; + uint8_t rem = ((size_t)m_Buf) & 0x0f; + if (rem) + m_Buf += (16 - rem); + } + + operator uint8_t * () { return m_Buf; }; + operator const uint8_t * () const { return m_Buf; }; + ChipherBlock * GetChipherBlock () { return (ChipherBlock *)m_Buf; }; + const ChipherBlock * GetChipherBlock () const { return (const ChipherBlock *)m_Buf; }; + + private: + + uint8_t m_UnalignedBuffer[sz + 15]; // up to 15 bytes alignment + uint8_t * m_Buf; + }; + + +#ifdef AESNI + 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 + }; + + class ECBEncryptionAESNI: public ECBCryptoAESNI + { + public: + + void SetKey (const AESKey& key) { ExpandKey (key); }; + void Encrypt (const ChipherBlock * in, ChipherBlock * out); + }; + + class ECBDecryptionAESNI: public ECBCryptoAESNI + { + public: + + void SetKey (const AESKey& key); + void Decrypt (const ChipherBlock * in, ChipherBlock * out); + }; + + typedef ECBEncryptionAESNI ECBEncryption; + typedef ECBDecryptionAESNI ECBDecryption; + +#else // use openssl + class ECBEncryption { public: - ECBEncryption (); - ~ECBEncryption (); - - void SetKey (const uint8_t * key) { m_Key = key; }; - void Encrypt(const uint8_t * in, uint8_t * out); + void SetKey (const AESKey& key) + { + AES_set_encrypt_key (key, 256, &m_Key); + } + void Encrypt (const ChipherBlock * in, ChipherBlock * out) + { + AES_encrypt (in->buf, out->buf, &m_Key); + } private: - AESKey m_Key; - EVP_CIPHER_CTX * m_Ctx; + AES_KEY m_Key; }; class ECBDecryption { public: - ECBDecryption (); - ~ECBDecryption (); - - void SetKey (const uint8_t * key) { m_Key = key; }; - void Decrypt (const uint8_t * in, uint8_t * out); - + void SetKey (const AESKey& key) + { + AES_set_decrypt_key (key, 256, &m_Key); + } + void Decrypt (const ChipherBlock * in, ChipherBlock * out) + { + AES_decrypt (in->buf, out->buf, &m_Key); + } + private: - - AESKey m_Key; - EVP_CIPHER_CTX * m_Ctx; + + AES_KEY m_Key; }; + +#endif + 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 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 - 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 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 private: - AESKey m_Key; - EVP_CIPHER_CTX * m_Ctx; + AESAlignedBuffer<16> m_IV; + ECBDecryption m_ECBDecryption; }; class TunnelEncryption // with double IV encryption @@ -172,7 +255,11 @@ namespace crypto private: ECBEncryption m_IVEncryption; +#ifdef AESNI + ECBEncryption m_LayerEncryption; +#else CBCEncryption m_LayerEncryption; +#endif }; class TunnelDecryption // with double IV encryption @@ -190,92 +277,92 @@ namespace crypto private: ECBDecryption m_IVDecryption; +#ifdef AESNI + ECBDecryption m_LayerDecryption; +#else CBCDecryption m_LayerDecryption; +#endif }; -// AEAD/ChaCha20/Poly1305 - - class AEADChaCha20Poly1305Encryptor - { - public: - - AEADChaCha20Poly1305Encryptor (); - ~AEADChaCha20Poly1305Encryptor (); - - bool Encrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen, - const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len); // msgLen is len without tag - - void Encrypt (const std::vector >& bufs, const uint8_t * key, const uint8_t * nonce, uint8_t * mac); // encrypt multiple buffers with zero ad - - private: - - EVP_CIPHER_CTX * m_Ctx; - }; - - class AEADChaCha20Poly1305Decryptor - { - public: - - AEADChaCha20Poly1305Decryptor (); - ~AEADChaCha20Poly1305Decryptor (); - - bool Decrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen, - const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len); // msgLen is len without tag - - private: - - EVP_CIPHER_CTX * m_Ctx; - }; - - bool AEADChaCha20Poly1305 (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen, - const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len, bool encrypt); // msgLen is len without tag - -// ChaCha20 - void ChaCha20 (const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out); - - class ChaCha20Context - { - public: - - ChaCha20Context (); - ~ChaCha20Context (); - void operator ()(const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out); - - private: - - EVP_CIPHER_CTX * m_Ctx; - }; - -// HKDF - - void HKDF (const uint8_t * salt, const uint8_t * key, size_t keyLen, const std::string& info, uint8_t * out, size_t outLen = 64); // salt - 32, out - 32 or 64, info <= 32 - -// 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 version +#include +#if (OPENSSL_VERSION_NUMBER < 0x010100000) || defined(LIBRESSL_VERSION_NUMBER) // 1.1.0 or LibreSSL +// 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..4be230f7 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) { - ElGamalEncrypt (m_PublicKey, data, encrypted); + ElGamalEncrypt (m_PublicKey, data, encrypted, ctx, true); } 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) { - return ElGamalDecrypt (m_PrivateKey, encrypted, data); + return ElGamalDecrypt (m_PrivateKey, encrypted, data, ctx, true); } 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) { if (m_Curve && m_PublicKey) - ECIESEncrypt (m_Curve, m_PublicKey, data, encrypted); + ECIESEncrypt (m_Curve, m_PublicKey, data, encrypted, ctx); } 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) { if (m_Curve && m_PrivateKey) - return ECIESDecrypt (m_Curve, m_PrivateKey, encrypted, data); + return ECIESDecrypt (m_Curve, m_PrivateKey, encrypted, data, ctx); 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) { if (m_PublicKey) - ECIESEncrypt (GetGOSTR3410Curve (eGOSTR3410CryptoProA)->GetGroup (), m_PublicKey, data, encrypted); + ECIESEncrypt (GetGOSTR3410Curve (eGOSTR3410CryptoProA)->GetGroup (), m_PublicKey, data, encrypted, ctx); } 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) { if (m_PrivateKey) - return ECIESDecrypt (GetGOSTR3410Curve (eGOSTR3410CryptoProA)->GetGroup (), m_PrivateKey, encrypted, data); + return ECIESDecrypt (GetGOSTR3410Curve (eGOSTR3410CryptoProA)->GetGroup (), m_PrivateKey, encrypted, data, ctx); 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 099bdd56..ece86eb0 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) = 0; // 222 bytes data, 512 bytes encrypted }; class CryptoKeyDecryptor @@ -30,8 +21,7 @@ namespace crypto public: virtual ~CryptoKeyDecryptor () {}; - virtual bool Decrypt (const uint8_t * encrypted, uint8_t * data) = 0; - virtual size_t GetPublicKeyLen () const = 0; // we need it to set key in LS2 + virtual bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) = 0; // 512 bytes encrypted, 222 bytes data }; // ElGamal @@ -40,7 +30,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); private: @@ -52,8 +42,7 @@ 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); private: @@ -68,7 +57,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); private: @@ -83,8 +72,7 @@ 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); private: @@ -102,7 +90,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); private: @@ -116,8 +104,7 @@ 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); private: @@ -125,84 +112,8 @@ namespace crypto }; 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 256; // actual size is 32, but we use 256 for compatibility with old keys files - case i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD: return 32; - // ML-KEM hybrid - case i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD: - case i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM768_X25519_AEAD: - case i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM1024_X25519_AEAD: - return 32; - }; - return 0; - } - - constexpr size_t GetCryptoPublicKeyLen (i2p::data::CryptoKeyType type) - { - switch (type) - { - case i2p::data::CRYPTO_KEY_TYPE_ELGAMAL: return 256; - case i2p::data::CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC: return 32; - case i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD: return 32; - // ML-KEM hybrid - case i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD: - case i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM768_X25519_AEAD: - case i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM1024_X25519_AEAD: - return 32; - }; - return 0; - } - - struct LocalEncryptionKey - { - std::vector pub, priv; - i2p::data::CryptoKeyType keyType; - std::shared_ptr decryptor; - - LocalEncryptionKey (i2p::data::CryptoKeyType t); - void GenerateKeys (); - void CreateDecryptor (); - }; } } #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 b243ba0a..33eff029 100644 --- a/libi2pd/Destination.cpp +++ b/libi2pd/Destination.cpp @@ -1,44 +1,28 @@ -/* -* 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), - 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) + 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) { 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 +40,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 ()) { @@ -89,51 +64,16 @@ namespace client { it = params->find (I2CP_PARAM_OUTBOUND_NICKNAME); if (it != params->end ()) m_Nickname = it->second; - // otherwise we set default nickname in Start when we know local address + // otherwise we set deafult 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 +86,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,78 +96,80 @@ 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)); - } - - void LeaseSetDestination::Stop () - { - m_CleanupTimer.cancel (); - m_PublishConfirmationTimer.cancel (); - m_PublishVerificationTimer.cancel (); - if (m_Pool) + while (m_IsRunning) { - m_Pool->SetLocalDestination (nullptr); - i2p::tunnel::tunnels.StopTunnelPool (m_Pool); - } - SaveTags (); - CleanUp (); // GarlicDestination - } - - 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; - std::map intOpts = { - {I2CP_PARAM_INBOUND_TUNNEL_LENGTH, inLen}, - {I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH, outLen}, - {I2CP_PARAM_INBOUND_TUNNELS_QUANTITY, inQuant}, - {I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY, outQuant}, - {I2CP_PARAM_TAGS_TO_SEND, numTags}, - {I2CP_PARAM_MIN_TUNNEL_LATENCY, minLatency}, - {I2CP_PARAM_MAX_TUNNEL_LATENCY, maxLatency} - }; - - auto pool = GetTunnelPool(); - inLen = pool->GetNumInboundHops(); - outLen = pool->GetNumOutboundHops(); - inQuant = pool->GetNumInboundTunnels(); - outQuant = pool->GetNumOutboundTunnels(); - minLatency = 0; - maxLatency = 0; - - for (auto & opt : intOpts) - { - itr = params.find(opt.first); - if(itr != params.end()) + try { - opt.second = std::stoi(itr->second); + m_Service.run (); + } + catch (std::exception& ex) + { + LogPrint (eLogError, "Destination: runtime exception: ", ex.what ()); } } - pool->RequireLatency(minLatency, maxLatency); - return pool->Reconfigure(inLen, outLen, inQuant, outQuant); } - std::shared_ptr LeaseSetDestination::FindLeaseSet (const i2p::data::IdentHash& ident) + bool LeaseSetDestination::Start () + { + if (!m_IsRunning) + { + 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; + } + 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; + } + + std::shared_ptr LeaseSetDestination::FindLeaseSet (const i2p::data::IdentHash& ident) { std::shared_ptr remoteLS; { @@ -261,12 +203,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; } @@ -275,31 +228,21 @@ namespace client if (!m_Pool) return nullptr; if (!m_LeaseSet) UpdateLeaseSet (); - auto ls = GetLeaseSetMt (); - return (ls && ls->GetInnerLeaseSet ()) ? ls->GetInnerLeaseSet () : ls; // always non-encrypted - } - - std::shared_ptr LeaseSetDestination::GetLeaseSetMt () - { std::lock_guard l(m_LeaseSetMutex); return m_LeaseSet; } - void LeaseSetDestination::SetLeaseSet (std::shared_ptr newLeaseSet) + void LeaseSetDestination::SetLeaseSet (i2p::data::LocalLeaseSet * newLeaseSet) { { std::lock_guard l(m_LeaseSetMutex); - m_LeaseSet = newLeaseSet; + m_LeaseSet.reset (newLeaseSet); } i2p::garlic::GarlicDestination::SetLeaseSetUpdated (); if (m_IsPublic) { - auto s = shared_from_this (); - boost::asio::post (m_Service, [s](void) - { - s->m_PublishVerificationTimer.cancel (); - s->Publish (); - }); + m_PublishVerificationTimer.cancel (); + Publish (); } } @@ -307,11 +250,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 +262,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, bufbe16toh (buf + I2NP_HEADER_SIZE_OFFSET)); 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, bufbe16toh (buf + I2NP_HEADER_SIZE_OFFSET)); 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, bufbe16toh (buf + I2NP_HEADER_SIZE_OFFSET)); 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,136 +311,60 @@ 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]) + if (buf[DATABASE_STORE_TYPE_OFFSET] == 1) // LeaseSet { - case i2p::data::NETDB_STORE_TYPE_LEASESET: // 1 - case i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2: // 3 + LogPrint (eLogDebug, "Destination: Remote LeaseSet"); + std::lock_guard lock(m_RemoteLeaseSetsMutex); + auto it = m_RemoteLeaseSets.find (key); + if (it != m_RemoteLeaseSets.end ()) { - 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 + leaseSet = it->second; + if (leaseSet->IsNewer (buf + offset, len - offset)) { - leaseSet = it->second; - if (leaseSet->IsNewer (buf + offset, len - offset)) - { - leaseSet->Update (buf + offset, len - offset); - if (leaseSet->IsValid () && leaseSet->GetIdentHash () == key && !leaseSet->IsExpired ()) - LogPrint (eLogDebug, "Destination: Remote LeaseSet updated"); - else - { - LogPrint (eLogDebug, "Destination: Remote LeaseSet update failed"); - m_RemoteLeaseSets.erase (it); - leaseSet = nullptr; - } - } - else - LogPrint (eLogDebug, "Destination: Remote LeaseSet is older. Not updated"); - } - 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 ()) - { - if (leaseSet->GetIdentHash () != GetIdentHash ()) - { - LogPrint (eLogDebug, "Destination: New remote LeaseSet added"); - m_RemoteLeaseSets[key] = leaseSet; - } - else - LogPrint (eLogDebug, "Destination: Own remote LeaseSet dropped"); - } + leaseSet->Update (buf + offset, len - offset); + if (leaseSet->IsValid () && leaseSet->GetIdentHash () == key) + LogPrint (eLogDebug, "Destination: Remote LeaseSet updated"); else { - LogPrint (eLogError, "Destination: New remote LeaseSet failed"); + LogPrint (eLogDebug, "Destination: Remote LeaseSet update failed"); + m_RemoteLeaseSets.erase (it); leaseSet = nullptr; } } - break; + else + LogPrint (eLogDebug, "Destination: Remote LeaseSet is older. Not updated"); } - case i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2: // 5 + else { - auto it2 = m_LeaseSetRequests.find (key); - if (it2 != m_LeaseSetRequests.end ()) + leaseSet = std::make_shared (buf + offset, len - offset); + if (leaseSet->IsValid () && leaseSet->GetIdentHash () == key) { - request = it2->second; - m_LeaseSetRequests.erase (it2); - if (request->requestedBlindedKey) + if (leaseSet->GetIdentHash () != GetIdentHash ()) { - 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"); + LogPrint (eLogDebug, "Destination: New remote LeaseSet added"); + m_RemoteLeaseSets[key] = leaseSet; } 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"); - } + LogPrint (eLogDebug, "Destination: Own remote LeaseSet dropped"); } else - LogPrint (eLogWarning, "Destination: Couldn't find request for encrypted LeaseSet2"); - break; + { + LogPrint (eLogError, "Destination: New remote LeaseSet failed"); + leaseSet = nullptr; + } } - default: - LogPrint (eLogError, "Destination: Unexpected client's DatabaseStore type ", buf[DATABASE_STORE_TYPE_OFFSET], ", dropped"); } + else + 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,72 +377,63 @@ 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 () { - auto leaseSet = GetLeaseSetMt (); - if (!leaseSet || !m_Pool) + if (!m_LeaseSet || !m_Pool) { LogPrint (eLogError, "Destination: Can't publish non-existing LeaseSet"); return; @@ -655,70 +453,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 (m_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 (m_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; } @@ -728,9 +489,22 @@ namespace client { if (m_PublishReplyToken) { - LogPrint (eLogWarning, "Destination: Publish confirmation was not received in ", PUBLISH_CONFIRMATION_TIMEOUT, " milliseconds or failed. will try again"); m_PublishReplyToken = 0; - Publish (); + if (GetIdentity ()->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ELGAMAL) + { + LogPrint (eLogWarning, "Destination: Publish confirmation was not received in ", PUBLISH_CONFIRMATION_TIMEOUT, " seconds, will try again"); + Publish (); + } + else + { + LogPrint (eLogWarning, "Destination: Publish confirmation was not received in ", PUBLISH_CONFIRMATION_TIMEOUT, " seconds from Java floodfill for crypto type ", (int)GetIdentity ()->GetCryptoKeyType ()); + // Java floodfill never sends confirmantion back for unknown crypto type + // assume it successive and try to verify + m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_VERIFICATION_TIMEOUT)); + m_PublishVerificationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishVerificationTimer, + shared_from_this (), std::placeholders::_1)); + + } } } } @@ -739,31 +513,26 @@ namespace client { if (ecode != boost::asio::error::operation_aborted) { - auto ls = GetLeaseSetMt (); - if (!ls) - { - LogPrint (eLogWarning, "Destination: Couldn't verify LeaseSet for ", GetIdentHash().ToBase32()); - return; - } auto s = shared_from_this (); - RequestLeaseSet (ls->GetStoreHash (), - [s, ls](std::shared_ptr leaseSet) + RequestLeaseSet (GetIdentHash (), + // "this" added due to bug in gcc 4.7-4.8 + [s,this](std::shared_ptr leaseSet) { if (leaseSet) { - if (*ls == *leaseSet) + if (s->m_LeaseSet && *s->m_LeaseSet == *leaseSet) { // we got latest LeasetSet - LogPrint (eLogDebug, "Destination: Published LeaseSet verified for ", s->GetIdentHash().ToBase32()); + LogPrint (eLogDebug, "Destination: published LeaseSet verified for ", 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; } else - LogPrint (eLogDebug, "Destination: LeaseSet is different than just published for ", s->GetIdentHash().ToBase32()); + LogPrint (eLogDebug, "Destination: LeaseSet is different than just published for ", GetIdentHash().ToBase32()); } else - LogPrint (eLogWarning, "Destination: Couldn't find published LeaseSet for ", s->GetIdentHash().ToBase32()); + LogPrint (eLogWarning, "Destination: couldn't find published LeaseSet for ", GetIdentHash().ToBase32()); // we have to publish again s->Publish (); }); @@ -781,37 +550,17 @@ 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)); - return true; - } - - bool LeaseSetDestination::RequestDestinationWithEncryptedLeaseSet (std::shared_ptr dest, RequestComplete requestComplete) - { - if (!dest || !m_Pool || !IsReady ()) - { - if (requestComplete) - boost::asio::post (m_Service, [requestComplete](void){requestComplete (nullptr);}); - return false; - } - auto storeHash = dest->GetStoreHash (); - auto leaseSet = FindLeaseSet (storeHash); - if (leaseSet) - { - if (requestComplete) - boost::asio::post (m_Service, [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 (), dest, requestComplete)); 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 ()) @@ -823,40 +572,25 @@ namespace client }); } - void LeaseSetDestination::CancelDestinationRequestWithEncryptedLeaseSet (std::shared_ptr dest, bool notify) + void LeaseSetDestination::RequestLeaseSet (const i2p::data::IdentHash& dest, RequestComplete requestComplete) { - if (dest) - CancelDestinationRequest (dest->GetStoreHash (), notify); - } - - 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) { auto request = std::make_shared (m_Service); - 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 @@ -880,41 +614,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 { @@ -922,7 +644,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)); } @@ -939,7 +661,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); @@ -955,7 +677,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; } @@ -976,8 +698,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)); } @@ -991,7 +712,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 @@ -999,120 +720,26 @@ 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 - - // extract encryption type params for LS2 - std::set encryptionKeyTypes; - if (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 ()) + if (isPublic) + PersistTemporaryKeys (); + else + i2p::data::PrivateKeys::GenerateCryptoKeyPair(GetIdentity ()->GetCryptoKeyType (), + m_EncryptionPrivateKey, m_EncryptionPublicKey); + m_Decryptor = m_Keys.CreateDecryptor (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); } } @@ -1120,51 +747,74 @@ 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); - if(length > len - 4) - { - LogPrint(eLogError, "Destination: Data message length ", length, " exceeds buffer length ", len); - return; - } buf += 4; // we assume I2CP payload uint16_t fromPort = bufbe16toh (buf + 4), // source @@ -1174,15 +824,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"); } @@ -1194,39 +838,26 @@ 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 (); RequestDestination (dest, - [s, streamRequestComplete, port](std::shared_ptr ls) + [s, streamRequestComplete, port](std::shared_ptr ls) { if (ls) streamRequestComplete(s->CreateStream (ls, port)); @@ -1236,60 +867,7 @@ namespace client } } - void ClientDestination::CreateStream (StreamRequestComplete streamRequestComplete, std::shared_ptr dest, uint16_t port) - { - if (!streamRequestComplete) - { - LogPrint (eLogError, "Destination: Request callback is not specified in CreateStream"); - return; - } - auto s = GetSharedFromThis (); - RequestDestinationWithEncryptedLeaseSet (dest, - [s, streamRequestComplete, port](std::shared_ptr ls) - { - if (ls) - streamRequestComplete(s->CreateStream (ls, port)); - else - streamRequestComplete (nullptr); - }); - } - - 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); @@ -1297,36 +875,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) { @@ -1334,9 +883,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) @@ -1364,7 +912,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) @@ -1374,25 +922,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; } @@ -1410,111 +943,36 @@ 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 for address ", ident, ".b32.i2p"); + 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"); - } - 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 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); - leaseSet = ls2; - m_LastPublishedTimestamp = publishedTimestamp; - } + auto leaseSet = new i2p::data::LocalLeaseSet (GetIdentity (), m_EncryptionPublicKey, tunnels); + // sign + Sign (leaseSet->GetBuffer (), leaseSet->GetBufferLen () - leaseSet->GetSignatureLen (), leaseSet->GetSignature ()); // TODO SetLeaseSet (leaseSet); } @@ -1523,111 +981,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); 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..6f37e768 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,12 @@ 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 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 +62,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,14 +72,13 @@ 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; std::shared_ptr outboundTunnel; std::shared_ptr replyTunnel; - std::shared_ptr requestedBlindedKey; // for encrypted LeaseSet2 only void Complete (std::shared_ptr ls) { @@ -125,120 +87,104 @@ 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 (); - - /** i2cp reconfigure */ - virtual bool Reconfigure(std::map i2cpOpts); + virtual bool Start (); + virtual bool Stop (); + 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); + 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); 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; }; + void SetLeaseSet (i2p::data::LocalLeaseSet * newLeaseSet); 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); + void RequestLeaseSet (const i2p::data::IdentHash& dest, RequestComplete requestComplete); + 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; + 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 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 +195,60 @@ 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); + 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]; + 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 deleted file mode 100644 index 47edb755..00000000 --- a/libi2pd/Ed25519.cpp +++ /dev/null @@ -1,527 +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 -#include "Log.h" -#include "Crypto.h" -#include "Ed25519.h" - -namespace i2p -{ -namespace crypto -{ - Ed25519::Ed25519 () - { - BN_CTX * ctx = BN_CTX_new (); - BIGNUM * tmp = BN_new (); - - q = BN_new (); - // 2^255-19 - BN_set_bit (q, 255); // 2^255 - BN_sub_word (q, 19); - - l = BN_new (); - // 2^252 + 27742317777372353535851937790883648493 - BN_set_bit (l, 252); - two_252_2 = BN_dup (l); - BN_dec2bn (&tmp, "27742317777372353535851937790883648493"); - BN_add (l, l, tmp); - BN_sub_word (two_252_2, 2); // 2^252 - 2 - - // -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); - - // 2^((q-1)/4) - I = BN_new (); - BN_free (tmp); - tmp = BN_dup (q); - BN_sub_word (tmp, 1); - BN_div_word (tmp, 4); - BN_set_word (I, 2); - BN_mod_exp (I, I, tmp, q, ctx); - BN_free (tmp); - - // 4*inv(5) - BIGNUM * By = BN_new (); - BN_set_word (By, 5); - BN_mod_inverse (By, By, q, ctx); - BN_mul_word (By, 4); - BIGNUM * Bx = RecoverX (By, ctx); - BN_mod (Bx, Bx, q, ctx); // % q - BN_mod (By, By, q, ctx); // % q - - // precalculate Bi256 table - Bi256Carry = { Bx, By }; // B - for (int i = 0; i < 32; i++) - { - Bi256[i][0] = Bi256Carry; // first point - for (int j = 1; j < 128; j++) - Bi256[i][j] = Sum (Bi256[i][j-1], Bi256[i][0], ctx); // (256+j+1)^i*B - Bi256Carry = Bi256[i][127]; - for (int j = 0; j < 128; j++) // add first point 128 more times - Bi256Carry = Sum (Bi256Carry, Bi256[i][0], ctx); - } - - BN_CTX_free (ctx); - } - - Ed25519::Ed25519 (const Ed25519& other): q (BN_dup (other.q)), l (BN_dup (other.l)), - d (BN_dup (other.d)), I (BN_dup (other.I)), two_252_2 (BN_dup (other.two_252_2)), - Bi256Carry (other.Bi256Carry) - { - for (int i = 0; i < 32; i++) - for (int j = 0; j < 128; j++) - Bi256[i][j] = other.Bi256[i][j]; - } - - Ed25519::~Ed25519 () - { - BN_free (q); - BN_free (l); - BN_free (d); - BN_free (I); - BN_free (two_252_2); - } - - - EDDSAPoint Ed25519::GeneratePublicKey (const uint8_t * expandedPrivateKey, BN_CTX * ctx) const - { - return MulB (expandedPrivateKey, ctx); // left half of expanded key, considered as Little Endian - } - - EDDSAPoint Ed25519::DecodePublicKey (const uint8_t * buf, BN_CTX * ctx) const - { - return DecodePoint (buf, ctx); - } - - void Ed25519::EncodePublicKey (const EDDSAPoint& publicKey, uint8_t * buf, BN_CTX * ctx) const - { - EncodePoint (Normalize (publicKey, ctx), buf); - } - - bool Ed25519::Verify (const EDDSAPoint& publicKey, const uint8_t * digest, const uint8_t * signature) const - { - BN_CTX * ctx = BN_CTX_new (); - BIGNUM * h = DecodeBN<64> (digest); - // signature 0..31 - R, 32..63 - S - // B*S = R + PK*h => R = B*S - PK*h - // we don't decode R, but encode (B*S - PK*h) - auto Bs = MulB (signature + EDDSA25519_SIGNATURE_LENGTH/2, ctx); // B*S; - BN_mod (h, h, l, ctx); // public key is multiple of B, but B%l = 0 - auto PKh = Mul (publicKey, h, ctx); // PK*h - uint8_t diff[32]; - EncodePoint (Normalize (Sum (Bs, -PKh, ctx), ctx), diff); // Bs - PKh encoded - bool passed = !memcmp (signature, diff, 32); // R - BN_free (h); - BN_CTX_free (ctx); - if (!passed) - LogPrint (eLogError, "25519 signature verification failed"); - return passed; - } - - 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 (); - // calculate r - SHA512_CTX ctx; - SHA512_Init (&ctx); - SHA512_Update (&ctx, expandedPrivateKey + EDDSA25519_PRIVATE_KEY_LENGTH, EDDSA25519_PRIVATE_KEY_LENGTH); // right half of expanded key - SHA512_Update (&ctx, buf, len); // data - uint8_t digest[64]; - SHA512_Final (digest, &ctx); - BIGNUM * r = DecodeBN<32> (digest); // DecodeBN<64> (digest); // for test vectors - // calculate R - uint8_t R[EDDSA25519_SIGNATURE_LENGTH/2]; // we must use separate buffer because signature might be inside buf - EncodePoint (Normalize (MulB (digest, bnCtx), bnCtx), R); // EncodePoint (Mul (B, r, bnCtx), R); // for test vectors - // calculate S - SHA512_Init (&ctx); - SHA512_Update (&ctx, R, EDDSA25519_SIGNATURE_LENGTH/2); // R - SHA512_Update (&ctx, publicKeyEncoded, EDDSA25519_PUBLIC_KEY_LENGTH); // public key - SHA512_Update (&ctx, buf, len); // data - SHA512_Final (digest, &ctx); - BIGNUM * h = DecodeBN<64> (digest); - // S = (r + h*a) % l - BIGNUM * a = DecodeBN (expandedPrivateKey); // left half of expanded key - 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); - EncodeBN (h, signature + EDDSA25519_SIGNATURE_LENGTH/2, EDDSA25519_SIGNATURE_LENGTH/2); // S - BN_free (r); BN_free (h); BN_free (a); - BN_CTX_free (bnCtx); - } - - 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 (); - // T = 80 random bytes - uint8_t T[80]; - RAND_bytes (T, 80); - // calculate r = H*(T || publickey || data) - SHA512_CTX ctx; - SHA512_Init (&ctx); - SHA512_Update (&ctx, T, 80); - SHA512_Update (&ctx, publicKeyEncoded, 32); - SHA512_Update (&ctx, buf, len); // data - uint8_t digest[64]; - SHA512_Final (digest, &ctx); - 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); - // calculate S - SHA512_Init (&ctx); - SHA512_Update (&ctx, R, EDDSA25519_SIGNATURE_LENGTH/2); // R - SHA512_Update (&ctx, publicKeyEncoded, EDDSA25519_PUBLIC_KEY_LENGTH); // public key - SHA512_Update (&ctx, buf, len); // data - SHA512_Final (digest, &ctx); - BIGNUM * h = DecodeBN<64> (digest); - // S = (r + h*a) % l - 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); - EncodeBN (h, signature + EDDSA25519_SIGNATURE_LENGTH/2, EDDSA25519_SIGNATURE_LENGTH/2); // S - 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) - // y3 = (y1*y2+x1*x2)*(z1*z2+d*t1*t2) - // z3 = (z1*z2-d*t1*t2)*(z1*z2+d*t1*t2) - // t3 = (y1*y2+x1*x2)*(x1*y2+y1*x2) - BIGNUM * x3 = BN_new (), * y3 = BN_new (), * z3 = BN_new (), * t3 = BN_new (); - - BN_mul (x3, p1.x, p2.x, ctx); // A = x1*x2 - BN_mul (y3, p1.y, p2.y, ctx); // B = y1*y2 - - BN_CTX_start (ctx); - BIGNUM * t1 = p1.t, * t2 = p2.t; - 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 - - if (p1.z) - { - if (p2.z) - BN_mul (z3, p1.z, p2.z, ctx); // D = z1*z2 - else - BN_copy (z3, p1.z); // D = z1 - } - else - { - if (p2.z) - BN_copy (z3, p2.z); // D = z2 - else - BN_one (z3); // D = 1 - } - - BIGNUM * E = BN_CTX_get (ctx), * F = BN_CTX_get (ctx), * G = BN_CTX_get (ctx), * H = BN_CTX_get (ctx); - BN_add (E, p1.x, p1.y); - BN_add (F, p2.x, p2.y); - BN_mul (E, E, F, ctx); // (x1 + y1)*(x2 + y2) - BN_sub (E, E, x3); - BN_sub (E, E, y3); // E = (x1 + y1)*(x2 + y2) - A - B - BN_sub (F, z3, t3); // F = D - C - BN_add (G, z3, t3); // G = D + C - BN_add (H, y3, x3); // H = B + A - - BN_mod_mul (x3, E, F, q, ctx); // x3 = E*F - BN_mod_mul (y3, G, H, q, ctx); // y3 = G*H - BN_mod_mul (z3, F, G, q, ctx); // z3 = F*G - BN_mod_mul (t3, E, H, q, ctx); // t3 = E*H - - BN_CTX_end (ctx); - - return EDDSAPoint {x3, y3, z3, t3}; - } - - void Ed25519::Double (EDDSAPoint& p, BN_CTX * ctx) const - { - BN_CTX_start (ctx); - BIGNUM * x2 = BN_CTX_get (ctx), * y2 = BN_CTX_get (ctx), * z2 = BN_CTX_get (ctx), * t2 = BN_CTX_get (ctx); - - BN_sqr (x2, p.x, ctx); // x2 = A = x^2 - BN_sqr (y2, p.y, ctx); // y2 = B = y^2 - if (p.t) - BN_sqr (t2, p.t, ctx); // t2 = t^2 - else - { - BN_mul (t2, p.x, p.y, ctx); // t = x*y - BN_sqr (t2, t2, ctx); // t2 = 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 - BN_one (z2); // z2 = 1 - - BIGNUM * E = BN_CTX_get (ctx), * F = BN_CTX_get (ctx), * G = BN_CTX_get (ctx), * H = BN_CTX_get (ctx); - // E = (x+y)*(x+y)-A-B = x^2+y^2+2xy-A-B = 2xy - BN_mul (E, p.x, p.y, ctx); - BN_lshift1 (E, E); // E =2*x*y - BN_sub (F, z2, t2); // F = D - C - BN_add (G, z2, t2); // G = D + C - BN_add (H, y2, x2); // H = B + A - - BN_mod_mul (p.x, E, F, q, ctx); // x2 = E*F - BN_mod_mul (p.y, G, H, q, ctx); // y2 = G*H - if (!p.z) p.z = BN_new (); - BN_mod_mul (p.z, F, G, q, ctx); // z2 = F*G - if (!p.t) p.t = BN_new (); - BN_mod_mul (p.t, E, H, q, ctx); // t2 = E*H - - BN_CTX_end (ctx); - } - - EDDSAPoint Ed25519::Mul (const EDDSAPoint& p, const BIGNUM * e, BN_CTX * ctx) const - { - BIGNUM * zero = BN_new (), * one = BN_new (); - BN_zero (zero); BN_one (one); - EDDSAPoint res {zero, one}; - if (!BN_is_zero (e)) - { - int bitCount = BN_num_bits (e); - for (int i = bitCount - 1; i >= 0; i--) - { - Double (res, ctx); - if (BN_is_bit_set (e, i)) res = Sum (res, p, ctx); - } - } - return res; - } - - EDDSAPoint Ed25519::MulB (const uint8_t * e, BN_CTX * ctx) const // B*e, e is 32 bytes Little Endian - { - BIGNUM * zero = BN_new (), * one = BN_new (); - BN_zero (zero); BN_one (one); - EDDSAPoint res {zero, one}; - bool carry = false; - for (int i = 0; i < 32; i++) - { - uint8_t x = e[i]; - if (carry) - { - if (x < 255) - { - x++; - carry = false; - } - else - x = 0; - } - if (x > 0) - { - if (x <= 128) - res = Sum (res, Bi256[i][x-1], ctx); - else - { - res = Sum (res, -Bi256[i][255-x], ctx); // -Bi[256-x] - carry = true; - } - } - } - if (carry) res = Sum (res, Bi256Carry, ctx); - return res; - } - - EDDSAPoint Ed25519::Normalize (const EDDSAPoint& p, BN_CTX * ctx) const - { - if (p.z) - { - BIGNUM * x = BN_new (), * y = BN_new (); - 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}; - } - else - return EDDSAPoint{BN_dup (p.x), BN_dup (p.y)}; - } - - bool Ed25519::IsOnCurve (const EDDSAPoint& p, BN_CTX * ctx) const - { - BN_CTX_start (ctx); - BIGNUM * x2 = BN_CTX_get (ctx), * y2 = BN_CTX_get (ctx), * tmp = BN_CTX_get (ctx); - BN_sqr (x2, p.x, ctx); // x^2 - BN_sqr (y2, p.y, ctx); // y^2 - // y^2 - x^2 - 1 - d*x^2*y^2 - BN_mul (tmp, d, x2, ctx); - BN_mul (tmp, tmp, y2, ctx); - BN_sub (tmp, y2, tmp); - BN_sub (tmp, tmp, x2); - BN_sub_word (tmp, 1); - BN_mod (tmp, tmp, q, ctx); // % q - bool ret = BN_is_zero (tmp); - BN_CTX_end (ctx); - return ret; - } - - BIGNUM * Ed25519::RecoverX (const BIGNUM * y, BN_CTX * ctx) const - { - BN_CTX_start (ctx); - BIGNUM * y2 = BN_CTX_get (ctx), * xx = BN_CTX_get (ctx); - BN_sqr (y2, y, ctx); // y^2 - // xx = (y^2 -1)*inv(d*y^2 +1) - BN_mul (xx, d, y2, ctx); - BN_add_word (xx, 1); - BN_mod_inverse (xx, xx, q, ctx); - BN_sub_word (y2, 1); - BN_mul (xx, y2, xx, ctx); - // x = srqt(xx) = xx^(2^252-2) - BIGNUM * x = BN_new (); - BN_mod_exp (x, xx, two_252_2, q, ctx); - // check (x^2 -xx) % q - BN_sqr (y2, x, ctx); - BN_mod_sub (y2, y2, xx, q, ctx); - if (!BN_is_zero (y2)) - BN_mod_mul (x, x, I, q, ctx); - if (BN_is_odd (x)) - BN_sub (x, q, x); - BN_CTX_end (ctx); - return x; - } - - EDDSAPoint Ed25519::DecodePoint (const uint8_t * buf, BN_CTX * ctx) const - { - // buf is 32 bytes Little Endian, convert it to Big Endian - uint8_t buf1[EDDSA25519_PUBLIC_KEY_LENGTH]; - for (size_t i = 0; i < EDDSA25519_PUBLIC_KEY_LENGTH/2; i++) // invert bytes - { - buf1[i] = buf[EDDSA25519_PUBLIC_KEY_LENGTH -1 - i]; - buf1[EDDSA25519_PUBLIC_KEY_LENGTH -1 - i] = buf[i]; - } - bool isHighestBitSet = buf1[0] & 0x80; - if (isHighestBitSet) - buf1[0] &= 0x7f; // clear highest bit - 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) - 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 - EDDSAPoint p {x, y, z, t}; - if (!IsOnCurve (p, ctx)) - LogPrint (eLogError, "Decoded point is not on 25519"); - return p; - } - - void Ed25519::EncodePoint (const EDDSAPoint& p, uint8_t * buf) const - { - EncodeBN (p.y, buf,EDDSA25519_PUBLIC_KEY_LENGTH); - if (BN_is_bit_set (p.x, 0)) // highest bit - buf[EDDSA25519_PUBLIC_KEY_LENGTH - 1] |= 0x80; // set highest bit - } - - template - BIGNUM * Ed25519::DecodeBN (const uint8_t * buf) const - { - // buf is Little Endian convert it to Big Endian - uint8_t buf1[len]; - for (size_t i = 0; i < len/2; i++) // invert bytes - { - buf1[i] = buf[len -1 - i]; - buf1[len -1 - i] = buf[i]; - } - BIGNUM * res = BN_new (); - BN_bin2bn (buf1, len, res); - return res; - } - - void Ed25519::EncodeBN (const BIGNUM * bn, uint8_t * buf, size_t len) const - { - bn2buf (bn, buf, len); - // To Little Endian - for (size_t i = 0; i < len/2; i++) // invert bytes - { - uint8_t tmp = buf[i]; - buf[i] = buf[len -1 - i]; - buf[len -1 - i] = tmp; - } - } - - 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 - BN_mod (alpha, alpha, l, ctx); // % l - uint8_t priv[32]; - EncodeBN (alpha, priv, 32); // back to Little Endian - BN_free (alpha); - // A' = BLIND_PUBKEY(A, alpha) = A + DERIVE_PUBLIC(alpha) - auto A1 = Sum (DecodePublicKey (pub, ctx), MulB (priv, ctx), ctx); // pub + B*alpha - EncodePublicKey (A1, blinded, ctx); - BN_CTX_free (ctx); - } - - void Ed25519::BlindPrivateKey (const uint8_t * priv, const uint8_t * seed, uint8_t * blindedPriv, uint8_t * blindedPub) - { - BN_CTX * ctx = BN_CTX_new (); - // calculate alpha = seed mod l - 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 - BN_add (alpha, alpha, p); // alpha = alpha + priv - // a' = BLIND_PRIVKEY(a, alpha) = (a + alpha) mod L - BN_mod (alpha, alpha, l, ctx); // % l - EncodeBN (alpha, blindedPriv, 32); - // A' = DERIVE_PUBLIC(a') - auto A1 = MulB (blindedPriv, ctx); - EncodePublicKey (A1, blindedPub, ctx); - BN_free (alpha); BN_free (p); - BN_CTX_free (ctx); - } - - void Ed25519::ExpandPrivateKey (const uint8_t * key, uint8_t * expandedKey) - { - SHA512 (key, EDDSA25519_PRIVATE_KEY_LENGTH, expandedKey); - expandedKey[0] &= 0xF8; // drop last 3 bits - expandedKey[EDDSA25519_PRIVATE_KEY_LENGTH - 1] &= 0x3F; // drop first 2 bits - expandedKey[EDDSA25519_PRIVATE_KEY_LENGTH - 1] |= 0x40; // set second bit - } - - void Ed25519::CreateRedDSAPrivateKey (uint8_t * priv) - { - uint8_t seed[32]; - RAND_bytes (seed, 32); - BIGNUM * p = DecodeBN<32> (seed); - BN_CTX * ctx = BN_CTX_new (); - BN_mod (p, p, l, ctx); // % l - EncodeBN (p, priv, 32); - BN_CTX_free (ctx); - BN_free (p); - } - - static std::unique_ptr g_Ed25519; - std::unique_ptr& GetEd25519 () - { - if (!g_Ed25519) - { - auto c = new Ed25519(); - if (!g_Ed25519) // make sure it was not created already - g_Ed25519.reset (c); - else - delete c; - } - return g_Ed25519; - } -} -} diff --git a/libi2pd/Ed25519.h b/libi2pd/Ed25519.h deleted file mode 100644 index 9c0ad801..00000000 --- a/libi2pd/Ed25519.h +++ /dev/null @@ -1,131 +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 ED25519_H__ -#define ED25519_H__ - -#include -#include -#include "Crypto.h" - -namespace i2p -{ -namespace crypto -{ - struct EDDSAPoint - { - BIGNUM * x {nullptr}; - BIGNUM * y {nullptr}; - BIGNUM * z {nullptr}; - BIGNUM * t {nullptr}; // projective coordinates - - EDDSAPoint () {} - EDDSAPoint (const EDDSAPoint& other) { *this = other; } - EDDSAPoint (EDDSAPoint&& other) { *this = std::move (other); } - EDDSAPoint (BIGNUM * x1, BIGNUM * y1, BIGNUM * z1 = nullptr, BIGNUM * t1 = nullptr) - : x(x1) - , y(y1) - , z(z1) - , t(t1) - {} - ~EDDSAPoint () { BN_free (x); BN_free (y); BN_free(z); BN_free(t); } - - EDDSAPoint& operator=(EDDSAPoint&& other) - { - if (this != &other) - { - BN_free (x); x = other.x; other.x = nullptr; - BN_free (y); y = other.y; other.y = nullptr; - BN_free (z); z = other.z; other.z = nullptr; - BN_free (t); t = other.t; other.t = nullptr; - } - return *this; - } - - EDDSAPoint& operator=(const EDDSAPoint& other) - { - if (this != &other) - { - BN_free (x); x = other.x ? BN_dup (other.x) : nullptr; - BN_free (y); y = other.y ? BN_dup (other.y) : nullptr; - BN_free (z); z = other.z ? BN_dup (other.z) : nullptr; - BN_free (t); t = other.t ? BN_dup (other.t) : nullptr; - } - return *this; - } - - EDDSAPoint operator-() const - { - BIGNUM * x1 = NULL, * y1 = NULL, * z1 = NULL, * t1 = NULL; - if (x) { x1 = BN_dup (x); BN_set_negative (x1, !BN_is_negative (x)); }; - if (y) y1 = BN_dup (y); - if (z) z1 = BN_dup (z); - if (t) { t1 = BN_dup (t); BN_set_negative (t1, !BN_is_negative (t)); }; - return EDDSAPoint {x1, y1, z1, t1}; - } - }; - - const size_t EDDSA25519_PUBLIC_KEY_LENGTH = 32; - const size_t EDDSA25519_SIGNATURE_LENGTH = 64; - const size_t EDDSA25519_PRIVATE_KEY_LENGTH = 32; - class Ed25519 - { - public: - - Ed25519 (); - Ed25519 (const Ed25519& other); - ~Ed25519 (); - - 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; - - 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; - void Double (EDDSAPoint& p, BN_CTX * ctx) const; - 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; - 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; - - template - BIGNUM * DecodeBN (const uint8_t * buf) const; - void EncodeBN (const BIGNUM * bn, uint8_t * buf, size_t len) const; - - private: - - BIGNUM * q, * l, * d, * I; - // transient values - BIGNUM * two_252_2; // 2^252-2 - EDDSAPoint Bi256[32][128]; // per byte, Bi256[i][j] = (256+j+1)^i*B, we don't store zeroes - // 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..21fb103b 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,10 @@ */ #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,334 +18,189 @@ #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 = ""; + std::string appName = "i2pd"; + std::string dataDir = ""; #ifdef _WIN32 - std::string dirSep = "\\"; + std::string dirSep = "\\"; #else - std::string dirSep = "/"; + std::string dirSep = "/"; #endif - const std::string & GetAppName () { - return appName; - } + const std::string & GetAppName () { + return appName; + } - void SetAppName (const std::string& name) { - appName = name; - } + void SetAppName (const std::string& name) { + appName = name; + } - const std::string & GetDataDir () { - return dataDir; - } + const std::string & GetDataDir () { + 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]; - - // check executable directory first - if(!GetModuleFileNameW(NULL, localAppData, MAX_PATH)) - { -#ifdef 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!"); -#endif - exit(1); - } - 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 - - // if config file exists in .exe's folder use it - if(fs_lib::exists(execPath/"i2pd.conf")) // TODO: magic string - { - dataDir = execPath.string (); - } else // otherwise %appdata% - { - if(SHGetFolderPathW(NULL, CSIDL_APPDATA, NULL, 0, localAppData) != S_OK) - { -#ifdef 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!"); -#endif - 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 - } - } - } - return; + void DetectDataDir(const std::string & cmdline_param, bool isService) { + if (cmdline_param != "") { + dataDir = cmdline_param; + return; + } +#if defined(WIN32) || defined(_WIN32) + char localAppData[MAX_PATH]; + // check executable directory first + GetModuleFileName (NULL, localAppData, MAX_PATH); + auto execPath = boost::filesystem::path(localAppData).parent_path(); + // if config file exists in .exe's folder use it + if(boost::filesystem::exists(execPath/"i2pd.conf")) // TODO: magic string + dataDir = execPath.string (); + else + { + // otherwise %appdata% + SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, localAppData); + dataDir = std::string(localAppData) + "\\" + appName; + } + return; #elif defined(MAC_OSX) - char *home = getenv("HOME"); - 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; + char *home = getenv("HOME"); + dataDir = (home != NULL && strlen(home) > 0) ? home : ""; + dataDir += "/Library/Application Support/" + appName; + return; #else /* other unix */ #if defined(ANDROID) - const char * ext = getenv("EXTERNAL_STORAGE"); - if (!ext) ext = "/sdcard"; - if (fs_lib::exists(ext)) - { - dataDir = std::string (ext) + "/" + appName; - return; - } -#endif // ANDROID - // use /home/user/.i2pd or /tmp/i2pd - char *home = getenv("HOME"); - if (home != NULL && strlen(home) > 0) { - dataDir = std::string(home) + "/." + appName; - } else { - dataDir = "/tmp/" + appName; - } - return; -#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); - - std::string destinations = DataDirPath("destinations"); - if (!fs_lib::exists(destinations)) - fs_lib::create_directory(destinations); - - std::string tags = DataDirPath("tags"); - if (!fs_lib::exists(tags)) - fs_lib::create_directory(tags); - else - i2p::garlic::CleanUpTagsFiles (); - - return true; - } - - bool ReadDir(const std::string & path, std::vector & files) { - if (!fs_lib::exists(path)) - return false; - fs_lib::directory_iterator it(path); - fs_lib::directory_iterator end; - - for ( ; it != end; it++) { - if (!fs_lib::is_regular_file(it->status())) - continue; - files.push_back(it->path().string()); - } - - return true; - } - - bool Exists(const std::string & path) { - return fs_lib::exists(path); - } - - uint32_t GetLastUpdateTime (const std::string & path) + const char * ext = getenv("EXTERNAL_STORAGE"); + if (!ext) ext = "/sdcard"; + if (boost::filesystem::exists(ext)) { - if (!fs_lib::exists(path)) - return 0; -#if STD_FILESYSTEM - std::error_code ec; - auto t = std::filesystem::last_write_time (path, ec); - if (ec) return 0; -/*#if __cplusplus >= 202002L // C++ 20 or higher - const auto sctp = std::chrono::clock_cast(t); -#else */ // TODO: wait until implemented - const auto sctp = std::chrono::time_point_cast( - t - decltype(t)::clock::now() + std::chrono::system_clock::now()); -/*#endif */ - return std::chrono::system_clock::to_time_t(sctp); -#else - boost::system::error_code ec; - auto t = boost::filesystem::last_write_time (path, ec); - return ec ? 0 : t; + dataDir = std::string (ext) + "/" + appName; + return; + } + // otherwise use /data/files #endif - } + char *home = getenv("HOME"); + if (isService) { + dataDir = "/var/lib/" + appName; + } else if (home != NULL && strlen(home) > 0) { + dataDir = std::string(home) + "/." + appName; + } else { + dataDir = "/tmp/" + appName; + } + return; +#endif + } - bool Remove(const std::string & path) { - if (!fs_lib::exists(path)) - return false; - return fs_lib::remove(path); - } + bool Init() { + if (!boost::filesystem::exists(dataDir)) + boost::filesystem::create_directory(dataDir); + std::string destinations = DataDirPath("destinations"); + if (!boost::filesystem::exists(destinations)) + boost::filesystem::create_directory(destinations); + std::string tags = DataDirPath("tags"); + if (!boost::filesystem::exists(tags)) + boost::filesystem::create_directory(tags); + else + i2p::garlic::CleanUpTagsFiles (); + + return true; + } + + bool ReadDir(const std::string & path, std::vector & files) { + if (!boost::filesystem::exists(path)) + return false; + boost::filesystem::directory_iterator it(path); + boost::filesystem::directory_iterator end; + + for ( ; it != end; it++) { + if (!boost::filesystem::is_regular_file(it->status())) + continue; + files.push_back(it->path().string()); + } + + return true; + } + + bool Exists(const std::string & path) { + return boost::filesystem::exists(path); + } + + uint32_t GetLastUpdateTime (const std::string & path) + { + if (!boost::filesystem::exists(path)) return 0; + boost::system::error_code ec; + auto t = boost::filesystem::last_write_time (path, ec); + return ec ? 0 : t; + } + + bool Remove(const std::string & path) { + if (!boost::filesystem::exists(path)) + return false; + return boost::filesystem::remove(path); + } bool CreateDirectory (const std::string& path) { - if (fs_lib::exists(path) && fs_lib::is_directory (fs_lib::status (path))) - return true; - return fs_lib::create_directory(path); + if (boost::filesystem::exists(path) && + boost::filesystem::is_directory (boost::filesystem::status (path))) return true; + return boost::filesystem::create_directory(path); } - void HashedStorage::SetPlace(const std::string &path) { - root = path + i2p::fs::dirSep + name; - } + void HashedStorage::SetPlace(const std::string &path) { + root = path + i2p::fs::dirSep + name; + } - bool HashedStorage::Init(const char * chars, size_t count) { - if (!fs_lib::exists(root)) { - fs_lib::create_directories(root); - } + bool HashedStorage::Init(const char * chars, size_t count) { + 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)) - 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)) - continue; /* ^ throws exception on failure */ -#endif - return false; - } - return true; - } + for (size_t i = 0; i < count; i++) { + auto p = root + i2p::fs::dirSep + prefix1 + chars[i]; + if (boost::filesystem::exists(p)) + continue; + if (boost::filesystem::create_directory(p)) + continue; /* ^ throws exception on failure */ + return false; + } + return true; + } - std::string HashedStorage::Path(const std::string & ident) const { - std::string safe_ident = ident; - std::replace(safe_ident.begin(), safe_ident.end(), '/', '-'); - std::replace(safe_ident.begin(), safe_ident.end(), '\\', '-'); + std::string HashedStorage::Path(const std::string & ident) const { + std::string safe_ident = ident; + std::replace(safe_ident.begin(), safe_ident.end(), '/', '-'); + std::replace(safe_ident.begin(), safe_ident.end(), '\\', '-'); - std::stringstream t(""); - t << this->root << i2p::fs::dirSep; - t << prefix1 << safe_ident[0] << i2p::fs::dirSep; - t << prefix2 << safe_ident << "." << suffix; + std::stringstream t(""); + t << this->root << i2p::fs::dirSep; + t << prefix1 << safe_ident[0] << i2p::fs::dirSep; + t << prefix2 << safe_ident << "." << suffix; - return t.str(); - } + return t.str(); + } - void HashedStorage::Remove(const std::string & ident) { - std::string path = Path(ident); - if (!fs_lib::exists(path)) - return; - fs_lib::remove(path); - } + void HashedStorage::Remove(const std::string & ident) { + std::string path = Path(ident); + if (!boost::filesystem::exists(path)) + return; + boost::filesystem::remove(path); + } - void HashedStorage::Traverse(std::vector & files) { - Iterate([&files] (const std::string & fname) { - files.push_back(fname); - }); - } + void HashedStorage::Traverse(std::vector & files) { + Iterate([&files] (const std::string & fname) { + files.push_back(fname); + }); + } - void HashedStorage::Iterate(FilenameVisitor v) - { - fs_lib::path p(root); - fs_lib::recursive_directory_iterator it(p); - fs_lib::recursive_directory_iterator end; + void HashedStorage::Iterate(FilenameVisitor v) + { + 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() )) - continue; - const std::string & t = it->path().string(); - v(t); - } - } + for ( ; it != end; it++) { + if (!boost::filesystem::is_regular_file( it->status() )) + continue; + const std::string & t = it->path().string(); + v(t); + } + } } // fs } // i2p diff --git a/libi2pd/FS.h b/libi2pd/FS.h index 7af8f494..87364fea 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 applicaton 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..3fb5d862 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,47 @@ 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(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 +89,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 +110,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 +158,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 +178,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..59089072 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); @@ -682,8 +578,8 @@ namespace garlic tunnel = from->GetTunnelPool ()->GetNextOutboundTunnel (); 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); + if (tunnel) // we have send it through an outbound tunnel + 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..5773fe15 100644 --- a/libi2pd/Gost.cpp +++ b/libi2pd/Gost.cpp @@ -1,14 +1,5 @@ -/* -* 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 #include #include #include "I2PEndian.h" @@ -96,7 +87,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 +102,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..24e55457 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 * @@ -8,345 +8,273 @@ #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" + }; - // 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); + } - 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 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); + } + } - 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; - } + static std::pair parse_header_line(const std::string& line) + { + std::size_t pos = 0; + std::size_t len = 2; /* strlen(": ") */ + std::size_t max = line.length(); + if ((pos = line.find(": ", pos)) == std::string::npos) + return std::make_pair("", ""); + while ((pos + len) < max && isspace(line.at(pos + len))) + len++; + return std::make_pair(line.substr(0, pos), line.substr(pos + len)); + } - bool URL::parse(const char *str, std::size_t len) - { - return parse({str, len ? len : strlen(str)}); - } + void gen_rfc1123_date(std::string & out) { + std::time_t now = std::time(nullptr); + char buf[128]; + std::strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S GMT", std::gmtime(&now)); + out = buf; + } - 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; + bool URL::parse(const char *str, std::size_t len) { + std::string url(str, len ? len : strlen(str)); + return parse(url); + } - /* schema */ - pos_c = url.find("://"); - if (pos_c != std::string::npos) { - schema = url.substr(0, pos_c); - pos_p = pos_c + 3; - } + 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 != std::string::npos && delim < pos_c) { + user = url.substr(pos_p, delim - pos_p); + delim += 1; + pass = url.substr(delim, pos_c - delim); + } else { + 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; + } + } - /* 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 */ + /* 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; + } - 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; - } + /* pos_p now at start of fragment part */ + frag = url.substr(pos_p, std::string::npos); + 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); + bool URL::parse_query(std::map & params) { + std::vector tokens; + strsplit(query, tokens, '&'); - 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; - } - } + 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; + } - /* 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 +290,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 +309,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_rfc1123_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..9bb7dfd1 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/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] = 1; // LeaseSet htobe32buf (payload + DATABASE_STORE_REPLY_TOKEN_OFFSET, 0); size_t size = DATABASE_STORE_HEADER_SIZE; memcpy (payload + size, leaseSet->GetBuffer (), leaseSet->GetBufferLen ()); @@ -337,13 +274,13 @@ 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 (); uint8_t * payload = m->GetPayload (); - memcpy (payload + DATABASE_STORE_KEY_OFFSET, leaseSet->GetStoreHash (), 32); - payload[DATABASE_STORE_TYPE_OFFSET] = leaseSet->GetStoreType (); // LeaseSet or LeaseSet2 + memcpy (payload + DATABASE_STORE_KEY_OFFSET, leaseSet->GetIdentHash (), 32); + payload[DATABASE_STORE_TYPE_OFFSET] = 1; // LeaseSet htobe32buf (payload + DATABASE_STORE_REPLY_TOKEN_OFFSET, replyToken); size_t size = DATABASE_STORE_HEADER_SIZE; if (replyToken && replyTunnel) @@ -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::crypto::ElGamalDecrypt (i2p::context.GetPrivateKeys ().GetPrivateKey () , 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..f394d284 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" @@ -36,9 +26,6 @@ namespace i2p const size_t I2NP_SHORT_HEADER_EXPIRATION_OFFSET = I2NP_SHORT_HEADER_TYPEID_OFFSET + 1; 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; - // Tunnel Gateway header const size_t TUNNEL_GATEWAY_HEADER_TUNNELID_OFFSET = 0; const size_t TUNNEL_GATEWAY_HEADER_LENGTH_OFFSET = TUNNEL_GATEWAY_HEADER_TUNNELID_OFFSET + 4; @@ -49,11 +36,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,49 +44,31 @@ 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 { @@ -119,20 +83,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 +103,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_MESSAGE_SIZE = 32768; 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 +113,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 +125,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 ()); }; @@ -248,30 +194,10 @@ namespace tunnel len = offset + I2NP_SHORT_HEADER_SIZE + bufbe16toh (header + I2NP_HEADER_SIZE_OFFSET); return bufbe32toh (header + I2NP_HEADER_MSGID_OFFSET); } - // for NTCP2 only - uint8_t * GetNTCP2Header () { return GetPayload () - I2NP_NTCP2_HEADER_SIZE; }; - size_t GetNTCP2Length () const { return GetPayloadLength () + I2NP_NTCP2_HEADER_SIZE; }; - void FromNTCP2 () - { - const uint8_t * ntcp2 = GetNTCP2Header (); - memcpy (GetHeader () + I2NP_HEADER_TYPEID_OFFSET, ntcp2 + I2NP_HEADER_TYPEID_OFFSET, 5); // typeid + msgid - SetExpiration (bufbe32toh (ntcp2 + I2NP_HEADER_EXPIRATION_OFFSET)*1000LL); - SetSize (len - offset - I2NP_HEADER_SIZE); - SetChks (0); - } - void ToNTCP2 () - { - uint8_t * ntcp2 = GetNTCP2Header (); - htobe32buf (ntcp2 + I2NP_HEADER_EXPIRATION_OFFSET, bufbe64toh (GetHeader () + I2NP_HEADER_EXPIRATION_OFFSET)/1000LL); - 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 +209,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 +244,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 +252,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..e5271233 100644 --- a/libi2pd/I2PEndian.h +++ b/libi2pd/I2PEndian.h @@ -1,23 +1,14 @@ -/* -* 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 #include -#if defined(__FreeBSD__) || defined(__NetBSD__) +#if defined(__FreeBSD__) #include - -#elif defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__OpenBSD__) || defined(__GLIBC__) || defined(__HAIKU__) +#elif defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__OpenBSD__) #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 @@ -156,35 +113,7 @@ inline void htobe64buf(void *buf, uint64_t big64) htobuf64(buf, htobe64(big64)); } -inline void htole16buf(void *buf, uint16_t big16) -{ - htobuf16(buf, htole16(big16)); -} -inline void htole32buf(void *buf, uint32_t big32) -{ - htobuf32(buf, htole32(big32)); -} - -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..3fcd16ad 100644 --- a/libi2pd/Identity.cpp +++ b/libi2pd/Identity.cpp @@ -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 -*/ - +#include +#include #include "Crypto.h" #include "I2PEndian.h" #include "Log.h" -#include "Timestamp.h" -#include "CryptoKey.h" #include "Identity.h" namespace i2p @@ -20,49 +12,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 +51,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; @@ -92,16 +72,33 @@ namespace data break; } case SIGNING_KEY_TYPE_RSA_SHA256_2048: + { + memcpy (m_StandardIdentity.signingKey, signingKey, 128); + excessLen = i2p::crypto::RSASHA2562048_KEY_LENGTH - 128; // 128 = 256 - 128 + excessBuf = new uint8_t[excessLen]; + memcpy (excessBuf, signingKey + 128, excessLen); + break; + } case SIGNING_KEY_TYPE_RSA_SHA384_3072: + { + memcpy (m_StandardIdentity.signingKey, signingKey, 128); + excessLen = i2p::crypto::RSASHA3843072_KEY_LENGTH - 128; // 256 = 384 - 128 + excessBuf = new uint8_t[excessLen]; + memcpy (excessBuf, signingKey + 128, excessLen); + break; + } case SIGNING_KEY_TYPE_RSA_SHA512_4096: - LogPrint (eLogError, "Identity: RSA signing key type ", (int)type, " is not supported"); - break; + { + memcpy (m_StandardIdentity.signingKey, signingKey, 128); + excessLen = i2p::crypto::RSASHA5124096_KEY_LENGTH - 128; // 384 = 512 - 128 + excessBuf = new uint8_t[excessLen]; + memcpy (excessBuf, signingKey + 128, excessLen); + break; + } case SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519: - 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 +117,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 +125,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 +142,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 +160,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 +187,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 +207,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 +222,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 +246,13 @@ namespace data } } else + { m_ExtendedLen = 0; + m_ExtendedBuffer = nullptr; + } SHA256(buf, GetFullLen (), m_IdentHash); m_Verifier = nullptr; - CreateVerifier (); return GetFullLen (); } @@ -304,47 +262,41 @@ 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; } - const uint8_t * IdentityEx::GetSigningPublicKeyBuffer () const - { - auto keyLen = GetSigningPublicKeyLen (); - if (keyLen > 128) return nullptr; // P521 or PQ - return m_StandardIdentity.signingKey + 128 - keyLen; - } - size_t IdentityEx::GetSigningPrivateKeyLen () const { + if (!m_Verifier) CreateVerifier (); if (m_Verifier) return m_Verifier->GetPrivateKeyLen (); return GetSignatureLen ()/2; @@ -352,12 +304,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 +320,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,128 +333,147 @@ 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; } - i2p::crypto::Verifier * IdentityEx::CreateVerifier (SigningKeyType keyType) + void IdentityEx::CreateVerifier () const { + if (m_Verifier) return; // don't create again + auto keyType = GetSigningKeyType (); switch (keyType) { case SIGNING_KEY_TYPE_DSA_SHA1: - return new i2p::crypto::DSAVerifier (); - case SIGNING_KEY_TYPE_ECDSA_SHA256_P256: - return new i2p::crypto::ECDSAP256Verifier (); - case SIGNING_KEY_TYPE_ECDSA_SHA384_P384: - return new i2p::crypto::ECDSAP384Verifier (); - case SIGNING_KEY_TYPE_ECDSA_SHA512_P521: - return new i2p::crypto::ECDSAP521Verifier (); - case SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519: - return new i2p::crypto::EDDSA25519Verifier (); - case SIGNING_KEY_TYPE_GOSTR3410_CRYPTO_PRO_A_GOSTR3411_256: - return new i2p::crypto::GOSTR3410_256_Verifier (i2p::crypto::eGOSTR3410CryptoProA); - case SIGNING_KEY_TYPE_GOSTR3410_TC26_A_512_GOSTR3411_512: - 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: - LogPrint (eLogError, "Identity: RSA signing key type ", (int)keyType, " is not supported"); + UpdateVerifier (new i2p::crypto::DSAVerifier (m_StandardIdentity.signingKey)); break; + case SIGNING_KEY_TYPE_ECDSA_SHA256_P256: + { + size_t padding = 128 - i2p::crypto::ECDSAP256_KEY_LENGTH; // 64 = 128 - 64 + UpdateVerifier (new i2p::crypto::ECDSAP256Verifier (m_StandardIdentity.signingKey + padding)); + break; + } + case SIGNING_KEY_TYPE_ECDSA_SHA384_P384: + { + size_t padding = 128 - i2p::crypto::ECDSAP384_KEY_LENGTH; // 32 = 128 - 96 + UpdateVerifier (new i2p::crypto::ECDSAP384Verifier (m_StandardIdentity.signingKey + padding)); + break; + } + case SIGNING_KEY_TYPE_ECDSA_SHA512_P521: + { + uint8_t signingKey[i2p::crypto::ECDSAP521_KEY_LENGTH]; + memcpy (signingKey, m_StandardIdentity.signingKey, 128); + size_t excessLen = i2p::crypto::ECDSAP521_KEY_LENGTH - 128; // 4 = 132- 128 + memcpy (signingKey + 128, m_ExtendedBuffer + 4, excessLen); // right after signing and crypto key types + UpdateVerifier (new i2p::crypto::ECDSAP521Verifier (signingKey)); + break; + } + case SIGNING_KEY_TYPE_RSA_SHA256_2048: + { + uint8_t signingKey[i2p::crypto::RSASHA2562048_KEY_LENGTH]; + memcpy (signingKey, m_StandardIdentity.signingKey, 128); + size_t excessLen = i2p::crypto::RSASHA2562048_KEY_LENGTH - 128; // 128 = 256- 128 + memcpy (signingKey + 128, m_ExtendedBuffer + 4, excessLen); // right after signing and crypto key types + UpdateVerifier (new i2p::crypto:: RSASHA2562048Verifier (signingKey)); + break; + } + case SIGNING_KEY_TYPE_RSA_SHA384_3072: + { + uint8_t signingKey[i2p::crypto::RSASHA3843072_KEY_LENGTH]; + memcpy (signingKey, m_StandardIdentity.signingKey, 128); + size_t excessLen = i2p::crypto::RSASHA3843072_KEY_LENGTH - 128; // 256 = 384- 128 + memcpy (signingKey + 128, m_ExtendedBuffer + 4, excessLen); // right after signing and crypto key types + UpdateVerifier (new i2p::crypto:: RSASHA3843072Verifier (signingKey)); + break; + } + case SIGNING_KEY_TYPE_RSA_SHA512_4096: + { + uint8_t signingKey[i2p::crypto::RSASHA5124096_KEY_LENGTH]; + memcpy (signingKey, m_StandardIdentity.signingKey, 128); + size_t excessLen = i2p::crypto::RSASHA5124096_KEY_LENGTH - 128; // 384 = 512- 128 + memcpy (signingKey + 128, m_ExtendedBuffer + 4, excessLen); // right after signing and crypto key types + UpdateVerifier (new i2p::crypto:: RSASHA5124096Verifier (signingKey)); + break; + } + case SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519: + { + size_t padding = 128 - i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH; // 96 = 128 - 32 + UpdateVerifier (new i2p::crypto::EDDSA25519Verifier (m_StandardIdentity.signingKey + padding)); + break; + } + case SIGNING_KEY_TYPE_GOSTR3410_CRYPTO_PRO_A_GOSTR3411_256: + { + size_t padding = 128 - i2p::crypto::GOSTR3410_256_PUBLIC_KEY_LENGTH; // 64 = 128 - 64 + UpdateVerifier (new i2p::crypto::GOSTR3410_256_Verifier (i2p::crypto::eGOSTR3410CryptoProA, m_StandardIdentity.signingKey + padding)); + break; + } + case SIGNING_KEY_TYPE_GOSTR3410_TC26_A_512_GOSTR3411_512: + { + // zero padding + UpdateVerifier (new i2p::crypto::GOSTR3410_512_Verifier (i2p::crypto::eGOSTR3410TC26A512, m_StandardIdentity.signingKey)); + break; + } default: LogPrint (eLogError, "Identity: Signing key type ", (int)keyType, " is not supported"); } - return nullptr; } - 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; } - std::shared_ptr IdentityEx::CreateEncryptor (CryptoKeyType keyType, const uint8_t * key) + void IdentityEx::DropVerifier () const { - switch (keyType) - { - 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: - return std::make_shared(key); - break; - default: - LogPrint (eLogError, "Identity: Unknown crypto key type ", (int)keyType); - }; - return nullptr; + // TODO: potential race condition with Verify + m_IsVerifierCreated = false; + m_Verifier = nullptr; } std::shared_ptr IdentityEx::CreateEncryptor (const uint8_t * key) const { if (!key) key = GetEncryptionPublicKey (); // use publicKey - return CreateEncryptor (GetCryptoKeyType (), key); + switch (GetCryptoKeyType ()) + { + case CRYPTO_KEY_TYPE_ELGAMAL: + 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)GetCryptoKeyType ()); + }; + return nullptr; } - 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); - m_OfflineSignature.resize (0); - m_TransientSignatureLen = 0; - m_TransientSigningPrivateKeyLen = 0; + memcpy (m_SigningPrivateKey, keys.signingPrivateKey, m_Public->GetSigningPrivateKeyLen ()); m_Signer = nullptr; CreateSigner (); return *this; @@ -510,126 +483,60 @@ namespace data { m_Public = std::make_shared(*other.m_Public); memcpy (m_PrivateKey, other.m_PrivateKey, 256); // 256 - 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_Public->GetSigningPrivateKeyLen ()); m_Signer = nullptr; CreateSigner (); return *this; } - size_t PrivateKeys::GetFullLen () const - { - size_t ret = m_Public->GetFullLen () + GetPrivateKeyLen () + m_Public->GetSigningPrivateKeyLen (); - if (IsOfflineSignature ()) - ret += m_OfflineSignature.size () + m_TransientSigningPrivateKeyLen; - return ret; - } - size_t PrivateKeys::FromBuffer (const uint8_t * buf, size_t len) { 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) 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]) - { - allzeros = false; - break; - } - if (allzeros) - { - // 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; - } - SigningKeyType keyType = bufbe16toh (buf + ret); ret += 2; // key type - std::unique_ptr transientVerifier (IdentityEx::CreateVerifier (keyType)); - if (!transientVerifier) return 0; - auto keyLen = transientVerifier->GetPublicKeyLen (); - 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)) - { - LogPrint (eLogError, "Identity: Offline signature verification failed"); - return 0; - } - ret += m_Public->GetSignatureLen (); - m_TransientSignatureLen = transientVerifier->GetSignatureLen (); - // copy offline signature - size_t offlineInfoLen = buf + ret - offlineInfo; - m_OfflineSignature.resize (offlineInfoLen); - memcpy (m_OfflineSignature.data (), offlineInfo, offlineInfoLen); - // override signing private key - m_TransientSigningPrivateKeyLen = transientVerifier->GetPrivateKeyLen (); - if (m_TransientSigningPrivateKeyLen + ret > len) return 0; - if (m_TransientSigningPrivateKeyLen > 128) m_SigningPrivateKey.resize (m_TransientSigningPrivateKeyLen); - memcpy (m_SigningPrivateKey.data (), buf + ret, m_TransientSigningPrivateKeyLen); - ret += m_TransientSigningPrivateKeyLen; - CreateSigner (keyType); - } - else - CreateSigner (m_Public->GetSigningKeyType ()); + CreateSigner (); return ret; } 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 ()) - { - // offline signature - auto offlineSignatureLen = m_OfflineSignature.size (); - if (ret + offlineSignatureLen > len) return 0; - memcpy (buf + ret, m_OfflineSignature.data (), offlineSignatureLen); - ret += offlineSignatureLen; - // transient private key - if (ret + m_TransientSigningPrivateKeyLen > len) return 0; - memcpy (buf + ret, m_SigningPrivateKey.data (), 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 @@ -640,77 +547,43 @@ namespace data } void PrivateKeys::CreateSigner () const - { - if (IsOfflineSignature ()) - CreateSigner (bufbe16toh (m_OfflineSignature.data () + 4)); // key type - else - CreateSigner (m_Public->GetSigningKeyType ()); - } - - void PrivateKeys::CreateSigner (SigningKeyType keyType) const { 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)); - 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 - else - { - // public key is not required - auto signer = CreateSigner (keyType, m_SigningPrivateKey.data ()); - if (signer) m_Signer.reset (signer); - } - } - - i2p::crypto::Signer * PrivateKeys::CreateSigner (SigningKeyType keyType, const uint8_t * priv) - { - switch (keyType) + switch (m_Public->GetSigningKeyType ()) { + case SIGNING_KEY_TYPE_DSA_SHA1: + m_Signer.reset (new i2p::crypto::DSASigner (m_SigningPrivateKey, m_Public->GetStandardIdentity ().signingKey)); + break; case SIGNING_KEY_TYPE_ECDSA_SHA256_P256: - return new i2p::crypto::ECDSAP256Signer (priv); + m_Signer.reset (new i2p::crypto::ECDSAP256Signer (m_SigningPrivateKey)); break; case SIGNING_KEY_TYPE_ECDSA_SHA384_P384: - return new i2p::crypto::ECDSAP384Signer (priv); + m_Signer.reset (new i2p::crypto::ECDSAP384Signer (m_SigningPrivateKey)); break; case SIGNING_KEY_TYPE_ECDSA_SHA512_P521: - return new i2p::crypto::ECDSAP521Signer (priv); + m_Signer.reset (new i2p::crypto::ECDSAP521Signer (m_SigningPrivateKey)); break; case SIGNING_KEY_TYPE_RSA_SHA256_2048: + m_Signer.reset (new i2p::crypto::RSASHA2562048Signer (m_SigningPrivateKey)); + break; case SIGNING_KEY_TYPE_RSA_SHA384_3072: + m_Signer.reset (new i2p::crypto::RSASHA3843072Signer (m_SigningPrivateKey)); + break; case SIGNING_KEY_TYPE_RSA_SHA512_4096: - LogPrint (eLogError, "Identity: RSA signing key type ", (int)keyType, " is not supported"); + m_Signer.reset (new i2p::crypto::RSASHA5124096Signer (m_SigningPrivateKey)); break; case SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519: - return new i2p::crypto::EDDSA25519Signer (priv, nullptr); + m_Signer.reset (new i2p::crypto::EDDSA25519Signer (m_SigningPrivateKey, m_Public->GetStandardIdentity ().certificate - i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH)); break; case SIGNING_KEY_TYPE_GOSTR3410_CRYPTO_PRO_A_GOSTR3411_256: - return new i2p::crypto::GOSTR3410_256_Signer (i2p::crypto::eGOSTR3410CryptoProA, priv); + m_Signer.reset (new i2p::crypto::GOSTR3410_256_Signer (i2p::crypto::eGOSTR3410CryptoProA, m_SigningPrivateKey)); break; case SIGNING_KEY_TYPE_GOSTR3410_TC26_A_512_GOSTR3411_512: - return new i2p::crypto::GOSTR3410_512_Signer (i2p::crypto::eGOSTR3410TC26A512, priv); + m_Signer.reset (new i2p::crypto::GOSTR3410_512_Signer (i2p::crypto::eGOSTR3410TC26A512, m_SigningPrivateKey)); 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); - break; -#endif default: - LogPrint (eLogError, "Identity: Signing key type ", (int)keyType, " is not supported"); + LogPrint (eLogError, "Identity: Signing key type ", (int)m_Public->GetSigningKeyType (), " is not supported"); } - return nullptr; - } - - size_t PrivateKeys::GetSignatureLen () const - { - return IsOfflineSignature () ? m_TransientSignatureLen : m_Public->GetSignatureLen (); - } - - size_t PrivateKeys::GetPrivateKeyLen () const - { - return i2p::crypto::GetCryptoPrivateKeyLen (m_Public->GetCryptoKeyType ()); } uint8_t * PrivateKeys::GetPadding() @@ -735,39 +608,60 @@ 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 + switch (type) + { + case SIGNING_KEY_TYPE_ECDSA_SHA256_P256: + i2p::crypto::CreateECDSAP256RandomKeys (keys.m_SigningPrivateKey, signingPublicKey); + break; + case SIGNING_KEY_TYPE_ECDSA_SHA384_P384: + i2p::crypto::CreateECDSAP384RandomKeys (keys.m_SigningPrivateKey, signingPublicKey); + break; + case SIGNING_KEY_TYPE_ECDSA_SHA512_P521: + i2p::crypto::CreateECDSAP521RandomKeys (keys.m_SigningPrivateKey, signingPublicKey); + break; + case SIGNING_KEY_TYPE_RSA_SHA256_2048: + case SIGNING_KEY_TYPE_RSA_SHA384_3072: + case SIGNING_KEY_TYPE_RSA_SHA512_4096: + LogPrint (eLogWarning, "Identity: RSA signature type is not supported. Create EdDSA"); + // no break here + case SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519: + i2p::crypto::CreateEDDSA25519RandomKeys (keys.m_SigningPrivateKey, signingPublicKey); + break; + case SIGNING_KEY_TYPE_GOSTR3410_CRYPTO_PRO_A_GOSTR3411_256: + i2p::crypto::CreateGOSTR3410RandomKeys (i2p::crypto::eGOSTR3410CryptoProA, keys.m_SigningPrivateKey, signingPublicKey); + break; + case SIGNING_KEY_TYPE_GOSTR3410_TC26_A_512_GOSTR3411_512: + i2p::crypto::CreateGOSTR3410RandomKeys (i2p::crypto::eGOSTR3410TC26A512, keys.m_SigningPrivateKey, signingPublicKey); + break; + default: + LogPrint (eLogWarning, "Identity: Signing key type ", (int)type, " is not supported. Create DSA-SHA1"); + return PrivateKeys (i2p::data::CreateRandomKeys ()); // DSA-SHA1 + } // 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; @@ -775,48 +669,6 @@ namespace data return PrivateKeys (i2p::data::CreateRandomKeys ()); // DSA-SHA1 } - void PrivateKeys::GenerateSigningKeyPair (SigningKeyType type, uint8_t * priv, uint8_t * pub) - { - switch (type) - { - case SIGNING_KEY_TYPE_ECDSA_SHA256_P256: - i2p::crypto::CreateECDSAP256RandomKeys (priv, pub); - break; - case SIGNING_KEY_TYPE_ECDSA_SHA384_P384: - i2p::crypto::CreateECDSAP384RandomKeys (priv, pub); - break; - case SIGNING_KEY_TYPE_ECDSA_SHA512_P521: - i2p::crypto::CreateECDSAP521RandomKeys (priv, pub); - break; - case SIGNING_KEY_TYPE_RSA_SHA256_2048: - 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 - case SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519: - i2p::crypto::CreateEDDSA25519RandomKeys (priv, pub); - break; - case SIGNING_KEY_TYPE_GOSTR3410_CRYPTO_PRO_A_GOSTR3411_256: - i2p::crypto::CreateGOSTR3410RandomKeys (i2p::crypto::eGOSTR3410CryptoProA, priv, pub); - break; - case SIGNING_KEY_TYPE_GOSTR3410_TC26_A_512_GOSTR3411_512: - i2p::crypto::CreateGOSTR3410RandomKeys (i2p::crypto::eGOSTR3410TC26A512, priv, pub); - 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 - } - } - void PrivateKeys::GenerateCryptoKeyPair (CryptoKeyType type, uint8_t * priv, uint8_t * pub) { switch (type) @@ -825,41 +677,17 @@ 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"); } } - PrivateKeys PrivateKeys::CreateOfflineKeys (SigningKeyType type, uint32_t expires) const - { - PrivateKeys keys (*this); - 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 - // recreate signer - keys.m_Signer = nullptr; - keys.CreateSigner (type); - } - return keys; - } - Keys CreateRandomKeys () { Keys keys; @@ -870,14 +698,19 @@ 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)); + time_t t = time (nullptr); + struct tm tm; +#ifdef _WIN32 + gmtime_s(&tm, &t); + sprintf_s((char *)(buf + 32), 9, "%04i%02i%02i", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday); +#else + gmtime_r(&t, &tm); + sprintf((char *)(buf + 32), "%04i%02i%02i", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday); +#endif IdentHash key; SHA256(buf, 40, key); return key; @@ -886,12 +719,24 @@ namespace data XORMetric operator^(const IdentHash& key1, const IdentHash& key2) { XORMetric m; - +#if defined(__AVX__) // for 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 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]; +#endif return m; } diff --git a/libi2pd/Identity.h b/libi2pd/Identity.h index c95ce000..584e6475 100644 --- a/libi2pd/Identity.h +++ b/libi2pd/Identity.h @@ -1,30 +1,17 @@ -/* -* 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 +46,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 +54,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 +65,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 + // following signature type should never appear in netid=2 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,49 +89,43 @@ 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; }; + const Identity& GetStandardIdentity () const { return m_StandardIdentity; }; const IdentHash& GetIdentHash () const { return m_IdentHash; }; - const uint8_t * GetEncryptionPublicKey () const { return m_StandardIdentity.publicKey; }; - uint8_t * GetEncryptionPublicKeyBuffer () { return m_StandardIdentity.publicKey; }; + const uint8_t * GetEncryptionPublicKey () const { return m_StandardIdentity.publicKey; }; + uint8_t * GetEncryptionPublicKeyBuffer () { return m_StandardIdentity.publicKey; }; std::shared_ptr CreateEncryptor (const uint8_t * key) const; size_t GetFullLen () const { return m_ExtendedLen + DEFAULT_IDENTITY_SIZE; }; size_t GetSigningPublicKeyLen () const; - const uint8_t * GetSigningPublicKeyBuffer () const; // returns NULL for P521 size_t GetSigningPrivateKeyLen () const; size_t GetSignatureLen () const; bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const; SigningKeyType GetSigningKeyType () const; bool IsRSA () const; // signing key type CryptoKeyType GetCryptoKeyType () const; + void DropVerifier () const; // to save memory - bool operator == (const IdentityEx & other) const { return GetIdentHash() == other.GetIdentHash(); } - void RecalculateIdentHash(uint8_t * buff=nullptr); + bool operator == (const IdentityEx & other) const { return GetIdentHash() == other.GetIdentHash(); } - static i2p::crypto::Verifier * CreateVerifier (SigningKeyType keyType); - static std::shared_ptr CreateEncryptor (CryptoKeyType keyType, const uint8_t * key); + void RecalculateIdentHash(uint8_t * buff=nullptr); 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,47 +139,34 @@ 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 (); }; - size_t GetSignatureLen () const; // might not match identity - bool IsOfflineSignature () const { return m_TransientSignatureLen > 0; }; - uint8_t * GetPadding(); - void RecalculateIdentHash(uint8_t * buf=nullptr) { m_Public->RecalculateIdentHash(buf); } + const uint8_t * GetSigningPrivateKey () const { return m_SigningPrivateKey; }; + 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; - size_t GetFullLen () const; + size_t GetFullLen () const { return m_Public->GetFullLen () + 256 + m_Public->GetSigningPrivateKeyLen (); }; 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 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; - 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[1024]; // assume private key doesn't exceed 1024 bytes mutable std::unique_ptr m_Signer; - std::vector m_OfflineSignature; // non zero length, if applicable - size_t m_TransientSignatureLen = 0; - size_t m_TransientSigningPrivateKeyLen = 0; }; // kademlia @@ -221,10 +183,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 +194,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 +206,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..f2355930 100644 --- a/libi2pd/LeaseSet.cpp +++ b/libi2pd/LeaseSet.cpp @@ -1,34 +1,19 @@ -/* -* 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 "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_Buffer (nullptr), m_BufferLen (0) - { - } LeaseSet::LeaseSet (const uint8_t * buf, size_t len, bool storeLeases): - m_IsValid (true), m_StoreLeases (storeLeases), m_ExpirationTime (0), m_EncryptionKey (nullptr) + m_IsValid (true), m_StoreLeases (storeLeases), m_ExpirationTime (0) { m_Buffer = new uint8_t[len]; memcpy (m_Buffer, buf, len); @@ -38,7 +23,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,44 +43,34 @@ 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; } - if (m_StoreLeases) - { - if (!m_EncryptionKey) m_EncryptionKey = new uint8_t[256]; - memcpy (m_EncryptionKey, m_Buffer + size, 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 (); + // reset existing leases + if (m_StoreLeases) + for (auto& it: m_Leases) + it->isUpdated = false; + else + m_Leases.clear (); + // process leases m_ExpirationTime = 0; auto ts = i2p::util::GetMillisecondsSinceEpoch (); @@ -102,46 +84,34 @@ namespace data leases += 4; // tunnel ID lease.endDate = bufbe64toh (leases); leases += 8; // end date - UpdateLease (lease, ts); + if (ts < lease.endDate + LEASE_ENDDATE_THRESHOLD) + { + if (lease.endDate > m_ExpirationTime) + m_ExpirationTime = lease.endDate; + if (m_StoreLeases) + { + 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 "); } 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) - { - 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; - } - } - } - - void LeaseSet::UpdateLeasesBegin () - { - // reset existing leases - if (m_StoreLeases) - for (auto& it: m_Leases) - it->isUpdated = false; - else - m_Leases.clear (); - } - - void LeaseSet::UpdateLeasesEnd () - { // delete old leases if (m_StoreLeases) { @@ -156,26 +126,16 @@ namespace data ++it; } } - } - void LeaseSet::UpdateLease (const Lease& lease, uint64_t ts) - { - if (ts < lease.endDate + LEASE_ENDDATE_THRESHOLD) + // verify + if (verifySignature && !m_Identity->Verify (m_Buffer, leases - m_Buffer, leases)) { - if (lease.endDate > m_ExpirationTime) - m_ExpirationTime = lease.endDate; - if (m_StoreLeases) - { - auto ret = m_Leases.insert (i2p::data::netdb.NewLease (lease)); - if (!ret.second) (*ret.first)->endDate = lease.endDate; // update existing - (*ret.first)->isUpdated = true; - } + LogPrint (eLogWarning, "LeaseSet: verification failed"); + m_IsValid = false; } - else - 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 +160,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 +171,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,494 +208,11 @@ 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); - } - - 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]; - m_BufferLen = len; - memcpy (m_Buffer, buf, len); - } - - void LeaseSet::SetBufferLen (size_t len) - { - if (len <= m_BufferLen) m_BufferLen = len; - else - LogPrint (eLogError, "LeaseSet2: Actual buffer size ", int(len) , " exceeds full buffer size ", int(m_BufferLen)); - } - - LeaseSet2::LeaseSet2 (uint8_t storeType, const uint8_t * buf, size_t len, bool storeLeases, CryptoKeyType preferredCrypto): - LeaseSet (storeLeases), m_StoreType (storeType), m_EncryptionType (preferredCrypto) - { - SetBuffer (buf, len); - if (storeType == NETDB_STORE_TYPE_ENCRYPTED_LEASESET2) - ReadFromBufferEncrypted (buf, len, nullptr, 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) - { - ReadFromBufferEncrypted (buf, len, key, secret); - } - - 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); - // 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); - SetIdentity (identity); - } - else - identity = GetIdentity (); - size_t offset = identity->GetFullLen (); - 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 - uint16_t flags = bufbe16toh (buf + offset); offset += 2; // flags - if (flags & LEASESET2_FLAG_OFFLINE_KEYS) - { - // transient key - m_TransientVerifier = ProcessOfflineSignature (identity, buf, len, offset); - if (!m_TransientVerifier) - { - 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) - { - case NETDB_STORE_TYPE_STANDARD_LEASESET2: - s = ReadStandardLS2TypeSpecificPart (buf + offset, len - offset); - break; - case NETDB_STORE_TYPE_META_LEASESET2: - s = ReadMetaLS2TypeSpecificPart (buf + offset, len - offset); - break; - default: - LogPrint (eLogWarning, "LeaseSet2: Unexpected store type ", (int)m_StoreType); - } - 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); - } - 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 - bool LeaseSet2::VerifySignature (Verifier& verifier, const uint8_t * buf, size_t len, size_t signatureOffset) - { - 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 - uint8_t c = buf[-1]; - const_cast(buf)[-1] = m_StoreType; - bool verified = verifier->Verify (buf - 1, signatureOffset + 1, buf + signatureOffset); - const_cast(buf)[-1] = c; - if (!verified) - LogPrint (eLogWarning, "LeaseSet2: Verification failed"); - return verified; - } - - size_t LeaseSet2::ReadStandardLS2TypeSpecificPart (const uint8_t * buf, size_t len) - { - size_t offset = 0; - // properties - uint16_t propertiesLen = bufbe16toh (buf + offset); offset += 2; - offset += propertiesLen; // skip for now. TODO: implement properties - // key sections - CryptoKeyType preferredKeyType = m_EncryptionType; - m_EncryptionType = 0; - bool preferredKeyFound = false; - if (offset + 1 > len) return 0; - int numKeySections = buf[offset]; offset++; - for (int i = 0; i < numKeySections; i++) - { - if (offset + 4 > len) return 0; - uint16_t keyType = bufbe16toh (buf + offset); offset += 2; // encryption key type - uint16_t encryptionKeyLen = bufbe16toh (buf + offset); offset += 2; - if (offset + encryptionKeyLen > len) return 0; - if (IsStoreLeases () && !preferredKeyFound) // create encryptor with leases only - { - // we pick max key type if preferred not found -#if !OPENSSL_PQ - if (keyType <= i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) // skip PQ keys if not supported -#endif - { - if (keyType == preferredKeyType || !m_Encryptor || keyType > m_EncryptionType) - { - auto encryptor = i2p::data::IdentityEx::CreateEncryptor (keyType, buf + offset); - if (encryptor) - { - m_Encryptor = encryptor; // TODO: atomic - m_EncryptionType = keyType; - if (keyType == preferredKeyType) preferredKeyFound = true; - } - } - } - } - offset += encryptionKeyLen; - } - // leases - 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 (); - for (int i = 0; i < numLeases; i++) - { - if (offset + LEASE2_SIZE > len) return 0; - Lease lease; - 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); - } - - size_t LeaseSet2::ReadMetaLS2TypeSpecificPart (const uint8_t * buf, size_t len) - { - size_t offset = 0; - // properties - uint16_t propertiesLen = bufbe16toh (buf + offset); offset += 2; - offset += propertiesLen; // skip for now. TODO: implement properties - // 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; - offset += 32; // hash - offset += 3; // flags - offset += 1; // cost - offset += 4; // expires - } - // revocations - if (offset + 1 > len) return 0; - int numRevocations = buf[offset]; offset++; - for (int i = 0; i < numRevocations; i++) - { - if (offset + 32 > len) return 0; - offset += 32; // hash - } - return offset; - } - - void LeaseSet2::ReadFromBufferEncrypted (const uint8_t * buf, size_t len, std::shared_ptr key, const uint8_t * secret) - { - size_t offset = 0; - // blinded key - if (len < 2) return; - const uint8_t * stA1 = buf + offset; // stA1 = blinded signature type, 2 bytes big endian - uint16_t blindedKeyType = bufbe16toh (stA1); offset += 2; - std::unique_ptr blindedVerifier (i2p::data::IdentityEx::CreateVerifier (blindedKeyType)); - if (!blindedVerifier) return; - auto blindedKeyLen = blindedVerifier->GetPublicKeyLen (); - if (offset + blindedKeyLen >= len) return; - const uint8_t * blindedPublicKey = buf + offset; - blindedVerifier->SetPublicKey (blindedPublicKey); offset += blindedKeyLen; - // expiration - if (offset + 8 >= len) return; - const uint8_t * publishedTimestamp = buf + offset; - m_PublishedTimestamp = bufbe32toh (publishedTimestamp); offset += 4; // published timestamp (seconds) - uint16_t expires = bufbe16toh (buf + offset); offset += 2; // expires (seconds) - SetExpirationTime ((m_PublishedTimestamp + expires)*1000LL); // in milliseconds - uint16_t flags = bufbe16toh (buf + offset); offset += 2; // flags - if (flags & LEASESET2_FLAG_OFFLINE_KEYS) - { - // transient key - m_TransientVerifier = ProcessOfflineSignature (blindedVerifier, buf, len, offset); - if (!m_TransientVerifier) - { - 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; - // verify signature - bool verified = m_TransientVerifier ? VerifySignature (m_TransientVerifier, 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 ()) - { - // 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)) - { - 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]; - key->GetSubcredential (blindedPublicKey, blindedKeyLen, subcredential); - memcpy (subcredential + 32, publishedTimestamp, 4); - // outerSalt = outerCiphertext[0:32] - // keys = HKDF(outerSalt, outerInput, "ELS2_L1K", 44) - uint8_t keys[64]; // 44 bytes actual data - i2p::crypto::HKDF (outerCiphertext, subcredential, 36, "ELS2_L1K", keys); - // decrypt Layer 1 - // outerKey = keys[0:31] - // outerIV = keys[32:43] - size_t lenOuterPlaintext = lenOuterCiphertext - 32; - std::vector outerPlainText (lenOuterPlaintext); - i2p::crypto::ChaCha20 (outerCiphertext + 32, lenOuterPlaintext, keys, keys + 32, outerPlainText.data ()); - // inner key - // innerInput = authCookie || subcredential || publishedTimestamp - // 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 - // decrypt Layer 2 - // innerKey = keys[0:31] - // innerIV = keys[32:43] - size_t lenInnerPlaintext = lenOuterPlaintext - 32 - 1 - authDataLen; - std::vector innerPlainText (lenInnerPlaintext); - i2p::crypto::ChaCha20 (outerPlainText.data () + 32 + 1 + authDataLen, 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]; - 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); - } - } - - // 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 - { - auto encryptor = m_Encryptor; // TODO: atomic - if (encryptor) - encryptor->Encrypt (data, encrypted); - } - - uint64_t LeaseSet2::ExtractExpirationTimestamp (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 (m_StoreType == NETDB_STORE_TYPE_ENCRYPTED_LEASESET2) - { - // encrypted LS2 - size_t offset = 0; - 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 (); - 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; - } - else - { - auto identity = GetIdentity (); - 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; - } + encryptor->Encrypt (data, encrypted, ctx); } LocalLeaseSet::LocalLeaseSet (std::shared_ptr identity, const uint8_t * encryptionPublicKey, std::vector > tunnels): @@ -753,58 +230,34 @@ 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): m_ExpirationTime (0), m_Identity (identity) { - if (buf) - { - m_BufferLen = len; - m_Buffer = new uint8_t[m_BufferLen]; - memcpy (m_Buffer, buf, len); - } - else - { - m_Buffer = nullptr; - m_BufferLen = 0; - } + m_BufferLen = len; + m_Buffer = new uint8_t[m_BufferLen]; + memcpy (m_Buffer, buf, len); } bool LocalLeaseSet::IsExpired () const @@ -819,7 +272,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 +283,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; @@ -846,249 +299,5 @@ 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): - LocalLeaseSet (keys.GetPublic (), nullptr, 0) - { - auto identity = keys.GetPublic (); - // 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 (); - uint16_t flags = 0; - if (keys.IsOfflineSignature ()) - { - flags |= LEASESET2_FLAG_OFFLINE_KEYS; - 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; - // LS2 header - auto offset = identity->ToBuffer (m_Buffer + 1, m_BufferLen) + 1; - htobe32buf (m_Buffer + offset, publishedTimestamp); offset += 4; // published timestamp (seconds) - uint8_t * expiresBuf = m_Buffer + offset; offset += 2; // expires, fill later - htobe16buf (m_Buffer + offset, flags); offset += 2; // flags - if (keys.IsOfflineSignature ()) - { - // offline signature - const auto& offlineSignature = keys.GetOfflineSignature (); - memcpy (m_Buffer + offset, offlineSignature.data (), offlineSignature.size ()); - 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 - } - // 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 - 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 - } - // sign - keys.Sign (m_Buffer, offset, m_Buffer + offset); // LS + leading store type - } - - LocalLeaseSet2::LocalLeaseSet2 (uint8_t storeType, std::shared_ptr identity, const uint8_t * buf, size_t len): - LocalLeaseSet (identity, nullptr, 0) - { - m_BufferLen = len; - m_Buffer = new uint8_t[m_BufferLen + 1]; - memcpy (m_Buffer + 1, buf, len); - m_Buffer[0] = storeType; - } - - LocalEncryptedLeaseSet2::LocalEncryptedLeaseSet2 (std::shared_ptr ls, const i2p::data::PrivateKeys& keys, - int authType, std::shared_ptr > authKeys): - 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; - - 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[0] = NETDB_STORE_TYPE_ENCRYPTED_LEASESET2; - BlindedPublicKey blindedKey (ls->GetIdentity ()); - 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; - } - 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 - 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; - 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, lenOuterCiphertext); offset += 2; // lenOuterCiphertext - // outerChipherText - // 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) - 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 - } - // 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 (); - 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; - i2p::crypto::ChaCha20 (outerPlainText, lenOuterPlaintext, keys1, keys1 + 32, outerPlainText); // encrypt Layer 1 - // signature - blindedSigner->Sign (m_Buffer, offset, m_Buffer + offset); - // store hash - 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) - { - // fill inner LeaseSet2 - auto blindedKey = std::make_shared(identity); - i2p::data::LeaseSet2 ls (buf, len, blindedKey); // inner layer - if (ls.IsValid ()) - { - m_InnerLeaseSet = std::make_shared(ls.GetStoreType (), identity, ls.GetBuffer (), ls.GetBufferLen ()); - 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 - } - } - } } } diff --git a/libi2pd/LeaseSet.h b/libi2pd/LeaseSet.h index f5197eb5..fa24df2f 100644 --- a/libi2pd/LeaseSet.h +++ b/libi2pd/LeaseSet.h @@ -1,25 +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 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 { @@ -37,7 +25,7 @@ namespace data IdentHash tunnelGateway; uint32_t tunnelID; uint64_t endDate; // 0 means invalid - bool isUpdated; // transient + bool isUpdated; // trasient /* return true if this lease expires within t millisecond + fudge factor */ bool ExpiresWithin( const uint64_t t, const uint64_t fudge = 1000 ) const { auto expire = i2p::util::GetMillisecondsSinceEpoch (); @@ -58,33 +46,26 @@ 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 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 { public: 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; + ~LeaseSet () { delete[] m_Buffer; }; + void Update (const uint8_t * buf, size_t len, bool verifySignature = true); + 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 (); }; @@ -92,38 +73,16 @@ namespace data bool ExpiresSoon(const uint64_t dlt=1000 * 5, const uint64_t fudge = 0) const ; 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 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; }; // 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 (); - void UpdateLeasesEnd (); - void UpdateLease (const Lease& lease, uint64_t ts); - - // 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; }; - bool IsStoreLeases () const { return m_StoreLeases; }; - 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 + uint64_t ExtractTimestamp (const uint8_t * buf, size_t len) const; // returns max expiration time private: @@ -131,114 +90,38 @@ namespace data std::set, LeaseCmp> m_Leases; uint64_t m_ExpirationTime; // in milliseconds std::shared_ptr m_Identity; - uint8_t * m_EncryptionKey; + uint8_t m_EncryptionKey[256]; uint8_t * m_Buffer; size_t m_BufferLen; }; /** - * 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); - const uint8_t NETDB_STORE_TYPE_STANDARD_LEASESET2 = 3; - const uint8_t NETDB_STORE_TYPE_ENCRYPTED_LEASESET2 = 5; - 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 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 - uint8_t GetStoreType () const { return m_StoreType; }; - 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; }; - - private: - - void ReadFromBuffer (const uint8_t * buf, size_t len, bool readIdentity = true, bool verifySignature = true); - void ReadFromBufferEncrypted (const uint8_t * buf, size_t len, std::shared_ptr key, const uint8_t * secret); - size_t ReadStandardLS2TypeSpecificPart (const uint8_t * buf, size_t len); - 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 - - private: - - uint8_t m_StoreType; - uint32_t m_PublishedTimestamp = 0; // seconds - bool m_IsPublic = true, m_IsPublishedEncrypted = false; - std::shared_ptr m_TransientVerifier; - CryptoKeyType m_EncryptionType; - std::shared_ptr m_Encryptor; // for standardLS2 - }; - - // also called from Streaming.cpp - template - std::shared_ptr ProcessOfflineSignature (const Verifier& verifier, const uint8_t * buf, size_t len, size_t& offset) - { - if (offset + 6 >= len) return nullptr; - const uint8_t * signedData = buf + offset; - uint32_t expiresTimestamp = bufbe32toh (buf + offset); offset += 4; // expires timestamp - if (expiresTimestamp < i2p::util::GetSecondsSinceEpoch ()) return nullptr; - uint16_t keyType = bufbe16toh (buf + offset); offset += 2; - std::shared_ptr transientVerifier (i2p::data::IdentityEx::CreateVerifier (keyType)); - if (!transientVerifier) return nullptr; - auto keyLen = transientVerifier->GetPublicKeyLen (); - if (offset + keyLen >= len) return nullptr; - 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 (); - return transientVerifier; - } - -//------------------------------------------------------------------------------------ class LocalLeaseSet { public: LocalLeaseSet (std::shared_ptr identity, const uint8_t * encryptionPublicKey, std::vector > tunnels); LocalLeaseSet (std::shared_ptr identity, const uint8_t * buf, size_t len); - virtual ~LocalLeaseSet () { delete[] m_Buffer; }; + ~LocalLeaseSet () { delete[] m_Buffer; }; - virtual uint8_t * GetBuffer () const { return m_Buffer; }; - uint8_t * GetSignature () { return GetBuffer () + GetBufferLen () - GetSignatureLen (); }; - virtual size_t GetBufferLen () const { return m_BufferLen; }; + const uint8_t * GetBuffer () const { return m_Buffer; }; + uint8_t * GetSignature () { return m_Buffer + m_BufferLen - GetSignatureLen (); }; + size_t GetBufferLen () const { return m_BufferLen; }; size_t GetSignatureLen () const { return m_Identity->GetSignatureLen (); }; uint8_t * GetLeases () { return m_Leases; }; const IdentHash& GetIdentHash () const { return m_Identity->GetIdentHash (); }; - std::shared_ptr GetIdentity () const { return m_Identity; }; bool IsExpired () const; uint64_t GetExpirationTime () const { return m_ExpirationTime; }; void SetExpirationTime (uint64_t expirationTime) { m_ExpirationTime = expirationTime; }; bool operator== (const LeaseSet& other) const - { return GetBufferLen () == other.GetBufferLen () && !memcmp (GetBuffer (), other.GetBuffer (), GetBufferLen ()); }; + { return m_BufferLen == other.GetBufferLen () && !memcmp (other.GetBuffer (), other.GetBuffer (), m_BufferLen); }; - virtual uint8_t GetStoreType () const { return NETDB_STORE_TYPE_LEASESET; }; - virtual const IdentHash& GetStoreHash () const { return GetIdentHash (); }; // differ from ident hash for encrypted LeaseSet2 - virtual std::shared_ptr GetInnerLeaseSet () const { return nullptr; }; // non-null for encrypted LeaseSet2 private: @@ -247,65 +130,6 @@ namespace data uint8_t * m_Buffer, * m_Leases; size_t m_BufferLen; }; - - class LocalLeaseSet2: public LocalLeaseSet - { - 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 - - virtual ~LocalLeaseSet2 () { delete[] m_Buffer; }; - - uint8_t * GetBuffer () const { return m_Buffer + 1; }; - size_t GetBufferLen () const { return m_BufferLen; }; - - uint8_t GetStoreType () const { return m_Buffer[0]; }; - - protected: - - LocalLeaseSet2 (std::shared_ptr identity): LocalLeaseSet (identity, nullptr, 0), m_Buffer (nullptr), m_BufferLen(0) {}; // called from LocalEncryptedLeaseSet2 - - protected: - - uint8_t * m_Buffer; // 1 byte store type + actual buffer - 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 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; - std::shared_ptr m_InnerLeaseSet; - }; } } 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..b664a5d9 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,10 +7,6 @@ */ #include "Log.h" -#include "util.h" - -//for std::transform -#include namespace i2p { namespace log { @@ -18,14 +14,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 +28,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 +107,17 @@ 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; - } - - 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) { + 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 +155,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 +165,6 @@ namespace log { void Log::Run () { - i2p::util::SetThreadName("Logging"); - Reopen (); while (m_IsRunning) { @@ -206,6 +185,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 +196,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 +223,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 deleted file mode 100644 index bc18f5a6..00000000 --- a/libi2pd/NTCP2.cpp +++ /dev/null @@ -1,2033 +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 -#include -#include -#include "Log.h" -#include "I2PEndian.h" -#include "Crypto.h" -#include "Siphash.h" -#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) - { - } - - NTCP2Establisher::~NTCP2Establisher () - { - delete[] m_SessionConfirmedBuffer; - } - - bool NTCP2Establisher::KeyDerivationFunction1 (const uint8_t * pub, i2p::crypto::X25519Keys& priv, const uint8_t * rs, const uint8_t * epub) - { - i2p::crypto::InitNoiseXKState (*this, rs); - // h = SHA256(h || epub) - MixHash (epub, 32); - // x25519 between pub and priv - uint8_t inputKeyMaterial[32]; - if (!priv.Agree (pub, inputKeyMaterial)) return false; - MixKey (inputKeyMaterial); - return true; - } - - bool NTCP2Establisher::KDF1Alice () - { - return KeyDerivationFunction1 (m_RemoteStaticKey, *m_EphemeralKeys, m_RemoteStaticKey, GetPub ()); - } - - bool NTCP2Establisher::KDF1Bob () - { - return KeyDerivationFunction1 (GetRemotePub (), i2p::context.GetNTCP2StaticKeys (), i2p::context.GetNTCP2StaticPublicKey (), GetRemotePub ()); - } - - bool NTCP2Establisher::KeyDerivationFunction2 (const uint8_t * sessionRequest, size_t sessionRequestLen, const uint8_t * epub) - { - MixHash (sessionRequest + 32, 32); // encrypted payload - - int paddingLength = sessionRequestLen - 64; - if (paddingLength > 0) - MixHash (sessionRequest + 64, paddingLength); - MixHash (epub, 32); - - // x25519 between remote pub and ephemaral priv - uint8_t inputKeyMaterial[32]; - if (!m_EphemeralKeys->Agree (GetRemotePub (), inputKeyMaterial)) return false; - MixKey (inputKeyMaterial); - return true; - } - - bool NTCP2Establisher::KDF2Alice () - { - return KeyDerivationFunction2 (m_SessionRequestBuffer, m_SessionRequestBufferLen, GetRemotePub ()); - } - - bool NTCP2Establisher::KDF2Bob () - { - return KeyDerivationFunction2 (m_SessionRequestBuffer, m_SessionRequestBufferLen, GetPub ()); - } - - bool NTCP2Establisher::KDF3Alice () - { - uint8_t inputKeyMaterial[32]; - if (!i2p::context.GetNTCP2StaticKeys ().Agree (GetRemotePub (), inputKeyMaterial)) return false; - MixKey (inputKeyMaterial); - return true; - } - - bool NTCP2Establisher::KDF3Bob () - { - uint8_t inputKeyMaterial[32]; - if (!m_EphemeralKeys->Agree (m_RemoteStaticKey, inputKeyMaterial)) return false; - MixKey (inputKeyMaterial); - return true; - } - - void NTCP2Establisher::CreateEphemeralKey () - { - m_EphemeralKeys = i2p::transport::transports.GetNextX25519KeysPair (); - } - - bool NTCP2Establisher::CreateSessionRequestMessage (std::mt19937& rng) - { - // create buffer and fill padding - auto paddingLength = rng () % (NTCP2_SESSION_REQUEST_MAX_SIZE - 64); // message length doesn't exceed 287 bytes - m_SessionRequestBufferLen = paddingLength + 64; - RAND_bytes (m_SessionRequestBuffer + 64, paddingLength); - // encrypt X - i2p::crypto::CBCEncryption encryption; - encryption.SetKey (m_RemoteIdentHash); - encryption.Encrypt (GetPub (), 32, m_IV, m_SessionRequestBuffer); // X - memcpy (m_IV, m_SessionRequestBuffer + 16, 16); // save last block as IV for SessionCreated - // encryption key for next block - if (!KDF1Alice ()) return false; - // 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 - 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) - 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 - // 2 bytes reserved - htobe32buf (options + 8, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000); // tsA, rounded to seconds - // 4 bytes reserved - // encrypt options - if (!Encrypt (options, m_SessionRequestBuffer + 32, 16)) - { - LogPrint (eLogWarning, "NTCP2: SessionRequest failed to encrypt options"); - return false; - } - return true; - } - - bool NTCP2Establisher::CreateSessionCreatedMessage (std::mt19937& rng) - { - auto paddingLen = rng () % (NTCP2_SESSION_CREATED_MAX_SIZE - 64); - m_SessionCreatedBufferLen = paddingLen + 64; - RAND_bytes (m_SessionCreatedBuffer + 64, paddingLen); - // encrypt Y - i2p::crypto::CBCEncryption encryption; - encryption.SetKey (i2p::context.GetIdentHash ()); - encryption.Encrypt (GetPub (), 32, m_IV, m_SessionCreatedBuffer); // Y - // encryption key for next block (m_K) - if (!KDF2Bob ()) return false; - uint8_t options[16]; - memset (options, 0, 16); - htobe16buf (options + 2, paddingLen); // padLen - htobe32buf (options + 8, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000); // tsB, rounded to seconds - // encrypt options - if (!Encrypt (options, m_SessionCreatedBuffer + 32, 16)) - { - LogPrint (eLogWarning, "NTCP2: SessionCreated failed to encrypt options"); - return false; - } - return true; - } - - bool NTCP2Establisher::CreateSessionConfirmedMessagePart1 () - { - // update AD - MixHash (m_SessionCreatedBuffer + 32, 32); // encrypted payload - int paddingLength = m_SessionCreatedBufferLen - 64; - if (paddingLength > 0) - MixHash (m_SessionCreatedBuffer + 64, paddingLength); - - // part1 48 bytes, n = 1 - if (!Encrypt (i2p::context.GetNTCP2StaticPublicKey (), m_SessionConfirmedBuffer, 32)) - { - LogPrint (eLogWarning, "NTCP2: SessionConfirmed failed to encrypt part1"); - return false; - } - return true; - } - - bool NTCP2Establisher::CreateSessionConfirmedMessagePart2 () - { - // part 2 - // update AD again - MixHash (m_SessionConfirmedBuffer, 48); - // encrypt m3p2, it must be filled in SessionRequest - if (!KDF3Alice ()) return false; // MixKey, n = 0 - uint8_t * m3p2 = m_SessionConfirmedBuffer + 48; - if (!Encrypt (m3p2, m3p2, m3p2Len - 16)) - { - LogPrint (eLogWarning, "NTCP2: SessionConfirmed failed to encrypt part2"); - return false; - } - // update h again - MixHash (m3p2, m3p2Len); //h = SHA256(h || ciphertext) - return true; - } - - bool NTCP2Establisher::ProcessSessionRequestMessage (uint16_t& paddingLen, bool& clockSkew) - { - 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 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)) - { - // 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 - { - paddingLen = bufbe16toh (options + 2); - m_SessionRequestBufferLen = paddingLen + 64; - m3p2Len = bufbe16toh (options + 4); - if (m3p2Len < 16) - { - LogPrint (eLogWarning, "NTCP2: SessionRequest m3p2len=", m3p2Len, " is too short"); - return false; - } - // check timestamp - auto ts = i2p::util::GetSecondsSinceEpoch (); - 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 - } - } - else - { - LogPrint (eLogWarning, "NTCP2: SessionRequest version mismatch ", (int)options[1]); - return false; - } - } - else - { - LogPrint (eLogWarning, "NTCP2: SessionRequest AEAD verification failed "); - return false; - } - return true; - } - - bool NTCP2Establisher::ProcessSessionCreatedMessage (uint16_t& paddingLen) - { - m_SessionCreatedBufferLen = 64; - // decrypt Y - i2p::crypto::CBCDecryption decryption; - decryption.SetKey (m_RemoteIdentHash); - decryption.Decrypt (m_SessionCreatedBuffer, 32, m_IV, GetRemotePub ()); - // decryption key for next block (m_K) - if (!KDF2Alice ()) - { - LogPrint (eLogWarning, "NTCP2: SessionCreated KDF failed"); - return false; - } - // decrypt and verify MAC - uint8_t payload[16]; - if (Decrypt (m_SessionCreatedBuffer + 32, payload, 16)) - { - // options - paddingLen = bufbe16toh(payload + 2); - // check timestamp - auto ts = i2p::util::GetSecondsSinceEpoch (); - 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"); - return false; - } - } - else - { - LogPrint (eLogWarning, "NTCP2: SessionCreated AEAD verification failed "); - return false; - } - return true; - } - - bool NTCP2Establisher::ProcessSessionConfirmedMessagePart1 () - { - // 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)) - { - LogPrint (eLogWarning, "NTCP2: SessionConfirmed Part1 AEAD verification failed "); - return false; - } - return true; - } - - bool NTCP2Establisher::ProcessSessionConfirmedMessagePart2 (uint8_t * m3p2Buf) - { - // update AD again - MixHash (m_SessionConfirmedBuffer, 48); - - if (!KDF3Bob ()) // MixKey, n = 0 - { - LogPrint (eLogWarning, "NTCP2: SessionConfirmed Part2 KDF failed"); - return false; - } - if (Decrypt (m_SessionConfirmedBuffer + 48, m3p2Buf, m3p2Len - 16)) - // calculate new h again for KDF data - MixHash (m_SessionConfirmedBuffer + 48, m3p2Len); // h = SHA256(h || ciphertext) - else - { - LogPrint (eLogWarning, "NTCP2: SessionConfirmed Part2 AEAD verification failed "); - return false; - } - 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 ()), - m_IsEstablished (false), m_IsTerminated (false), - m_Establisher (new NTCP2Establisher), - m_SendKey (nullptr), m_ReceiveKey (nullptr), -#if OPENSSL_SIPHASH - m_SendMDCtx(nullptr), m_ReceiveMDCtx (nullptr), -#else - 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) - { - if (in_RemoteRouter) // Alice - { - m_Establisher->m_RemoteIdentHash = GetRemoteIdentity ()->GetIdentHash (); - 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); - } - else - LogPrint (eLogWarning, "NTCP2: Missing NTCP2 address"); - } - m_NextRouterInfoResendTime = i2p::util::GetSecondsSinceEpoch () + NTCP2_ROUTERINFO_RESEND_INTERVAL + - m_Server.GetRng ()() % NTCP2_ROUTERINFO_RESEND_INTERVAL_THRESHOLD; - } - - NTCP2Session::~NTCP2Session () - { - delete[] m_NextReceivedBuffer; - delete[] m_NextSendBuffer; -#if OPENSSL_SIPHASH - if (m_SendMDCtx) EVP_MD_CTX_destroy (m_SendMDCtx); - if (m_ReceiveMDCtx) EVP_MD_CTX_destroy (m_ReceiveMDCtx); -#endif - } - - void NTCP2Session::Terminate () - { - if (!m_IsTerminated) - { - 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"); - } - } - } - - void NTCP2Session::Close () - { - m_Socket.close (); - } - - void NTCP2Session::TerminateByTimeout () - { - SendTerminationAndTerminate (eNTCP2IdleTimeout); - } - - void NTCP2Session::Done () - { - boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ())); - } - - void NTCP2Session::Established () - { - m_IsEstablished = true; - m_Establisher.reset (nullptr); - SetTerminationTimeout (NTCP2_TERMINATION_TIMEOUT + m_Server.GetRng ()() % NTCP2_TERMINATION_TIMEOUT_VARIANCE); - SendQueue (); - transports.PeerConnected (shared_from_this ()); - } - - void NTCP2Session::CreateNonce (uint64_t seqn, uint8_t * nonce) - { - 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 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); - } - - - 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; - } - // 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)); - } - - 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 ()); - Terminate (); - } - else - { - // 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)); - } - } - - void NTCP2Session::HandleSessionRequestReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred) - { - if (ecode) - { - LogPrint (eLogWarning, "NTCP2: SessionRequest read error: ", ecode.message ()); - Terminate (); - } - else - { - m_Establisher->CreateEphemeralKey (); - boost::asio::post (m_Server.GetEstablisherService (), - [s = shared_from_this (), bytes_transferred] () - { - s->ProcessSessionRequest (bytes_transferred);; - }); - } - } - - void NTCP2Session::ProcessSessionRequest (size_t len) - { - LogPrint (eLogDebug, "NTCP2: SessionRequest received ", len); - uint16_t paddingLen = 0; - bool clockSkew = false; - if (m_Establisher->ProcessSessionRequestMessage (paddingLen, clockSkew)) - { - if (clockSkew) - { - // we don't care about padding, send SessionCreated and close session - SendSessionCreated (); - boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ())); - } - else if (paddingLen > 0) - { - if (paddingLen <= NTCP2_SESSION_REQUEST_MAX_SIZE - 64) // session request is 287 bytes max - { - boost::asio::async_read (m_Socket, boost::asio::buffer(m_Establisher->m_SessionRequestBuffer + 64, paddingLen), boost::asio::transfer_all (), - std::bind(&NTCP2Session::HandleSessionRequestPaddingReceived, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); - } - else - { - LogPrint (eLogWarning, "NTCP2: SessionRequest padding length ", (int)paddingLen, " is too long"); - boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ())); - } - } - else - SendSessionCreated (); - } - else - ReadSomethingAndTerminate (); // probing resistance - } - - void NTCP2Session::HandleSessionRequestPaddingReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred) - { - if (ecode) - { - LogPrint (eLogWarning, "NTCP2: SessionRequest padding read error: ", ecode.message ()); - Terminate (); - } - else - { - boost::asio::post (m_Server.GetEstablisherService (), - [s = shared_from_this ()] () - { - s->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 (); - 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)); - } - - void NTCP2Session::HandleSessionCreatedReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred) - { - if (ecode) - { - LogPrint (eLogWarning, "NTCP2: SessionCreated read error: ", ecode.message ()); - Terminate (); - } - else - { - m_HandshakeInterval = i2p::util::GetMillisecondsSinceEpoch () - m_HandshakeInterval; - boost::asio::post (m_Server.GetEstablisherService (), - [s = shared_from_this (), bytes_transferred] () - { - s->ProcessSessionCreated (bytes_transferred); - }); - } - } - - void NTCP2Session::ProcessSessionCreated (size_t len) - { - LogPrint (eLogDebug, "NTCP2: SessionCreated received ", len); - uint16_t paddingLen = 0; - if (m_Establisher->ProcessSessionCreatedMessage (paddingLen)) - { - if (paddingLen > 0) - { - if (paddingLen <= NTCP2_SESSION_CREATED_MAX_SIZE - 64) // session created is 287 bytes max - { - boost::asio::async_read (m_Socket, boost::asio::buffer(m_Establisher->m_SessionCreatedBuffer + 64, paddingLen), boost::asio::transfer_all (), - std::bind(&NTCP2Session::HandleSessionCreatedPaddingReceived, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); - } - else - { - LogPrint (eLogWarning, "NTCP2: SessionCreated padding length ", (int)paddingLen, " is too long"); - boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ())); - } - } - else - SendSessionConfirmed (); - } - else - { - if (GetRemoteIdentity ()) - i2p::data::netdb.SetUnreachable (GetRemoteIdentity ()->GetIdentHash (), true); // assume wrong s key - boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ())); - } - } - - void NTCP2Session::HandleSessionCreatedPaddingReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred) - { - if (ecode) - { - LogPrint (eLogWarning, "NTCP2: SessionCreated padding read error: ", ecode.message ()); - Terminate (); - } - else - { - m_Establisher->m_SessionCreatedBufferLen += bytes_transferred; - boost::asio::post (m_Server.GetEstablisherService (), - [s = shared_from_this ()] () - { - s->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; - } - // 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)); - } - - 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 (); - - // TODO: remove - // m_SendQueue.push_back (CreateDeliveryStatusMsg (1)); - // SendQueue (); - } - } - - void NTCP2Session::HandleSessionCreatedSent (const boost::system::error_code& ecode, std::size_t bytes_transferred) - { - (void) bytes_transferred; - if (ecode) - { - 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]; - 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)); - } - } - - 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 ()); - Terminate (); - } - else - { - m_HandshakeInterval = i2p::util::GetMillisecondsSinceEpoch () - m_HandshakeInterval; - boost::asio::post (m_Server.GetEstablisherService (), - [s = shared_from_this ()] () - { - s->ProcessSessionConfirmed ();; - }); - } - } - - void NTCP2Session::ProcessSessionConfirmed () - { - // run on establisher thread - LogPrint (eLogDebug, "NTCP2: SessionConfirmed received"); - // part 1 - if (m_Establisher->ProcessSessionConfirmedMessagePart1 ()) - { - // part 2 - auto buf = std::make_shared > (m_Establisher->m3p2Len - 16); // -MAC - if (m_Establisher->ProcessSessionConfirmedMessagePart2 (buf->data ())) // TODO:handle in establisher thread - { - // payload - // RI block must be first - if ((*buf)[0] != eNTCP2BlkRouterInfo) - { - LogPrint (eLogWarning, "NTCP2: Unexpected block ", (int)(*buf)[0], " in SessionConfirmed"); - boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ())); - return; - } - auto size = bufbe16toh (buf->data () + 1); - if (size > buf->size () - 3 || size > i2p::data::MAX_RI_BUFFER_SIZE + 1) - { - LogPrint (eLogError, "NTCP2: Unexpected RouterInfo size ", size, " in SessionConfirmed"); - boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ())); - return; - } - boost::asio::post (m_Server.GetService (), - [s = shared_from_this (), buf, size] () - { - s->EstablishSessionAfterSessionConfirmed (buf, size); - }); - } - else - boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ())); - } - else - boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ())); - } - - void NTCP2Session::EstablishSessionAfterSessionConfirmed (std::shared_ptr > buf, size_t size) - { - // run on main NTCP2 thread - KeyDerivationFunctionDataPhase (); - // Bob data phase keys - m_SendKey = m_Kba; - m_ReceiveKey = m_Kab; - SetSipKeys (m_Sipkeysba, m_Sipkeysab); - memcpy (m_ReceiveIV.buf, m_Sipkeysab + 16, 8); - memcpy (m_SendIV.buf, m_Sipkeysba + 16, 8); - // we need to set keys for SendTerminationAndTerminate - // TODO: check flag - i2p::data::RouterInfo ri (buf->data () + 4, size - 1); // 1 byte block type + 2 bytes size + 1 byte flag - if (ri.IsUnreachable ()) - { - LogPrint (eLogError, "NTCP2: RouterInfo verification failed in SessionConfirmed from ", GetRemoteEndpoint ()); - SendTerminationAndTerminate (eNTCP2RouterInfoSignatureVerificationFail); - return; - } - LogPrint(eLogDebug, "NTCP2: SessionConfirmed from ", GetRemoteEndpoint (), - " (", i2p::data::GetIdentHashAbbreviation (ri.GetIdentHash ()), ")"); - auto ts = i2p::util::GetMillisecondsSinceEpoch (); - if (ts > ri.GetTimestamp () + i2p::data::NETDB_MIN_EXPIRATION_TIMEOUT*1000LL) // 90 minutes - { - LogPrint (eLogError, "NTCP2: RouterInfo is too old in SessionConfirmed for ", (ts - ri.GetTimestamp ())/1000LL, " seconds"); - SendTerminationAndTerminate (eNTCP2Message3Error); - return; - } - if (ts + i2p::data::NETDB_EXPIRATION_TIMEOUT_THRESHOLD*1000LL < ri.GetTimestamp ()) // 2 minutes - { - LogPrint (eLogError, "NTCP2: RouterInfo is from future for ", (ri.GetTimestamp () - ts)/1000LL, " seconds"); - SendTerminationAndTerminate (eNTCP2Message3Error); - return; - } - // update RouterInfo in netdb - auto ri1 = i2p::data::netdb.AddRouterInfo (ri.GetBuffer (), ri.GetBufferLen ()); // ri1 points to one from netdb now - if (!ri1) - { - LogPrint (eLogError, "NTCP2: Couldn't update RouterInfo from SessionConfirmed in netdb"); - Terminate (); - return; - } - - bool isOlder = false; - if (ri.GetTimestamp () + i2p::data::NETDB_EXPIRATION_TIMEOUT_THRESHOLD*1000LL < ri1->GetTimestamp ()) - { - // received RouterInfo is older than one in netdb - isOlder = true; - if (ri1->HasProfile ()) - { - auto profile = i2p::data::GetRouterProfile (ri1->GetIdentHash ()); // retrieve profile - if (profile && profile->IsDuplicated ()) - { - SendTerminationAndTerminate (eNTCP2Banned); - return; - } - } - } - - auto addr = m_RemoteEndpoint.address ().is_v4 () ? ri1->GetNTCP2V4Address () : - (i2p::util::net::IsYggdrasilAddress (m_RemoteEndpoint.address ()) ? ri1->GetYggdrasilAddress () : ri1->GetNTCP2V6Address ()); - if (!addr || memcmp (m_Establisher->m_RemoteStaticKey, addr->s, 32)) - { - LogPrint (eLogError, "NTCP2: Wrong static key in SessionConfirmed"); - Terminate (); - return; - } - if (addr->IsPublishedNTCP2 () && m_RemoteEndpoint.address () != addr->host && - (!m_RemoteEndpoint.address ().is_v6 () || (i2p::util::net::IsYggdrasilAddress (m_RemoteEndpoint.address ()) ? - memcmp (m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data () + 1, addr->host.to_v6 ().to_bytes ().data () + 1, 7) : // from the same yggdrasil subnet - memcmp (m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data (), addr->host.to_v6 ().to_bytes ().data (), 8)))) // temporary address - { - if (isOlder) // older router? - i2p::data::UpdateRouterProfile (ri1->GetIdentHash (), - [](std::shared_ptr profile) - { - if (profile) profile->Duplicated (); // mark router as duplicated in profile - }); - else - LogPrint (eLogInfo, "NTCP2: Host mismatch between published address ", addr->host, " and actual endpoint ", m_RemoteEndpoint.address ()); - SendTerminationAndTerminate (eNTCP2Banned); - return; - } - // TODO: process options block - - // ready to communicate - SetRemoteIdentity (ri1->GetRouterIdentity ()); - if (m_Server.AddNTCP2Session (shared_from_this (), true)) - { - Established (); - ReceiveLength (); - } - else - Terminate (); - } - - void NTCP2Session::SetSipKeys (const uint8_t * sendSipKey, const uint8_t * receiveSipKey) - { -#if OPENSSL_SIPHASH - EVP_PKEY * sipKey = 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); - - sipKey = 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_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; -#endif - } - - void NTCP2Session::ClientLogin () - { - m_Establisher->CreateEphemeralKey (); - boost::asio::post (m_Server.GetEstablisherService (), - [s = shared_from_this ()] () - { - s->SendSessionRequest (); - }); - } - - void NTCP2Session::ServerLogin () - { - SetTerminationTimeout (NTCP2_ESTABLISH_TIMEOUT); - SetLastActivityTimestamp (i2p::util::GetSecondsSinceEpoch ()); - boost::asio::async_read (m_Socket, boost::asio::buffer(m_Establisher->m_SessionRequestBuffer, 64), boost::asio::transfer_all (), - std::bind(&NTCP2Session::HandleSessionRequestReceived, shared_from_this (), - std::placeholders::_1, std::placeholders::_2)); - } - - 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)); - } - - void NTCP2Session::HandleReceivedLength (const boost::system::error_code& ecode, std::size_t bytes_transferred) - { - if (ecode) - { - if (ecode != boost::asio::error::operation_aborted) - 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); -#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); - if (m_NextReceivedLen >= 16) - { - CreateNextReceivedBuffer (m_NextReceivedLen); - boost::system::error_code ec; - size_t moreBytes = m_Socket.available(ec); - if (!ec) - { - if (moreBytes >= m_NextReceivedLen) - { - // read and process message immediately if available - moreBytes = boost::asio::read (m_Socket, boost::asio::buffer(m_NextReceivedBuffer, m_NextReceivedLen), boost::asio::transfer_all (), ec); - HandleReceived (ec, moreBytes); - } - else - Receive (); - } - else - LogPrint (eLogWarning, "NTCP2: Socket error: ", ec.message ()); - } - else - { - 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)); - } - - void NTCP2Session::HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred) - { - if (ecode) - { - 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); - 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"); - ProcessNextFrame (m_NextReceivedBuffer, m_NextReceivedLen-16); - m_IsReceiving = false; - ReceiveLength (); - } - else - { - LogPrint (eLogWarning, "NTCP2: Received AEAD verification failed "); - SendTerminationAndTerminate (eNTCP2DataPhaseAEADFailure); - } - } - } - - void NTCP2Session::ProcessNextFrame (const uint8_t * frame, size_t len) - { - size_t offset = 0; - while (offset < len) - { - uint8_t blk = frame[offset]; - offset++; - auto size = bufbe16toh (frame + offset); - offset += 2; - LogPrint (eLogDebug, "NTCP2: Block type ", (int)blk, " of size ", size); - if (offset + size > len) - { - LogPrint (eLogError, "NTCP2: Unexpected block length ", size); - break; - } - 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; - } - case eNTCP2BlkOptions: - LogPrint (eLogDebug, "NTCP2: Options"); - break; - case eNTCP2BlkRouterInfo: - { - LogPrint (eLogDebug, "NTCP2: RouterInfo flag=", (int)frame[offset]); - if (size <= i2p::data::MAX_RI_BUFFER_SIZE + 1) - { - auto newRi = i2p::data::netdb.AddRouterInfo (frame + offset + 1, size - 1); - if (newRi) - { - auto remoteIdentity = GetRemoteIdentity (); - if (remoteIdentity && remoteIdentity->GetIdentHash () == newRi->GetIdentHash ()) - // peer's RouterInfo update - SetRemoteIdentity (newRi->GetIdentity ()); - } - } - else - LogPrint (eLogInfo, "NTCP2: RouterInfo block is too long ", size); - break; - } - case eNTCP2BlkI2NPMessage: - { - LogPrint (eLogDebug, "NTCP2: I2NP"); - if (size > I2NP_MAX_MESSAGE_SIZE) - { - LogPrint (eLogError, "NTCP2: I2NP block is too long ", size); - break; - } - auto nextMsg = (frame[offset] == eI2NPTunnelData) ? NewI2NPTunnelMessage (true) : NewI2NPMessage (size); - 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"); - break; - } - case eNTCP2BlkTermination: - if (size >= 9) - { - 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"); - break; - default: - LogPrint (eLogWarning, "NTCP2: Unknown block type ", (int)blk); - } - offset += size; - } - m_Handler.Flush (); - } - - void NTCP2Session::SetNextSentFrameLength (size_t frameLen, uint8_t * lengthBuf) - { -#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); -#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); - } - - void NTCP2Session::SendI2NPMsgs (std::vector >& msgs) - { - if (msgs.empty () || IsTerminated ()) return; - - size_t totalLen = 0; - std::vector > encryptBufs; - std::vector bufs; - std::shared_ptr first; - uint8_t * macBuf = nullptr; - for (auto& it: msgs) - { - it->ToNTCP2 (); - auto buf = it->GetNTCP2Header (); - auto len = it->GetNTCP2Length (); - // block header - buf -= 3; - buf[0] = eNTCP2BlkI2NPMessage; // blk - htobe16buf (buf + 1, len); // size - 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); - if (paddingLen) - { - encryptBufs.push_back ( {buf + len, paddingLen} ); - len += paddingLen; - totalLen += paddingLen; - } - macBuf = buf + len; - // allocate 16 bytes for MAC - len += 16; - } - - bufs.push_back (boost::asio::buffer (buf, len)); - } - - if (!macBuf) // last block was not enough for MAC - { - // allocate send buffer - m_NextSendBuffer = new uint8_t[287]; // can be any size > 16, we just allocate 287 frequently - // create 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} ); - bufs.push_back (boost::asio::buffer (m_NextSendBuffer, paddingLen + 16)); - macBuf = m_NextSendBuffer + paddingLen; - totalLen += paddingLen; - } - if (totalLen > NTCP2_UNENCRYPTED_FRAME_MAX_SIZE) - { - LogPrint (eLogError, "NTCP2: Frame to send is too long ", totalLen); - return; - } - uint8_t nonce[12]; - CreateNonce (m_SendSequenceNumber, nonce); m_SendSequenceNumber++; - m_Server.AEADChaCha20Poly1305Encrypt (encryptBufs, m_SendKey, nonce, macBuf); // encrypt buffers - SetNextSentFrameLength (totalLen + 16, first->GetNTCP2Header () - 5); // frame length right before first block - - // send buffers - 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 ()) - { - delete[] m_NextSendBuffer; m_NextSendBuffer = nullptr; - return; - } - if (payloadLen > NTCP2_UNENCRYPTED_FRAME_MAX_SIZE) - { - LogPrint (eLogError, "NTCP2: Buffer to send is too long ", payloadLen); - delete[] m_NextSendBuffer; m_NextSendBuffer = nullptr; - return; - } - // encrypt - uint8_t nonce[12]; - CreateNonce (m_SendSequenceNumber, nonce); m_SendSequenceNumber++; - m_Server.AEADChaCha20Poly1305Encrypt ({ {m_NextSendBuffer + 2, payloadLen} }, m_SendKey, nonce, m_NextSendBuffer + payloadLen + 2); - SetNextSentFrameLength (payloadLen + 16, m_NextSendBuffer); - // send - 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)); - } - - void NTCP2Session::HandleNextFrameSent (const boost::system::error_code& ecode, std::size_t bytes_transferred) - { - 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 (); - } - else - { - UpdateNumSentBytes (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 ()); - } - } - } - - void NTCP2Session::SendQueue () - { - if (!m_SendQueue.empty () && m_IsEstablished) - { - std::vector > msgs; - auto ts = i2p::util::GetMillisecondsSinceEpoch (); - size_t s = 0; - while (!m_SendQueue.empty ()) - { - auto msg = m_SendQueue.front (); - if (!msg || msg->IsExpired (ts)) - { - // drop null or expired message - if (msg) msg->Drop (); - m_SendQueue.pop_front (); - continue; - } - size_t len = msg->GetNTCP2Length (); - if (s + len + 3 <= NTCP2_UNENCRYPTED_FRAME_MAX_SIZE) // 3 bytes block header - { - msgs.push_back (msg); - s += (len + 3); - m_SendQueue.pop_front (); - if (s >= NTCP2_SEND_AFTER_FRAME_SIZE) - break; // send frame right a way - } - else if (len + 3 > NTCP2_UNENCRYPTED_FRAME_MAX_SIZE) - { - LogPrint (eLogError, "NTCP2: I2NP message of size ", len, " can't be sent. Dropped"); - msg->Drop (); - m_SendQueue.pop_front (); - } - else - 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; - 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 (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); - } - buf[0] = eNTCP2BlkPadding; // blk - htobe16buf (buf + 1, paddingSize); // size - 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 - 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 - // padding block - auto paddingSize = CreatePaddingBlock (payloadLen, m_NextSendBuffer + 2 + payloadLen, 64); - payloadLen += paddingSize; - // encrypt and send - EncryptAndSendNextBuffer (payloadLen); - } - - 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 - // termination block - m_NextSendBuffer[2] = eNTCP2BlkTermination; - m_NextSendBuffer[3] = 0; m_NextSendBuffer[4] = 9; // 9 bytes block size - htobe64buf (m_NextSendBuffer + 5, m_ReceiveSequenceNumber); - m_NextSendBuffer[13] = (uint8_t)reason; - // padding block - auto paddingSize = CreatePaddingBlock (12, m_NextSendBuffer + 14, 19); - // encrypt and send - EncryptAndSendNextBuffer (paddingSize + 12); - } - - void NTCP2Session::SendTerminationAndTerminate (NTCP2TerminationReason reason) - { - SendTermination (reason); - boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ())); // let termination message go - } - - void NTCP2Session::ReadSomethingAndTerminate () - { - size_t len = m_Server.GetRng ()() % NTCP2_SESSION_REQUEST_MAX_SIZE; - if (len > 0 && m_Establisher) - boost::asio::async_read (m_Socket, boost::asio::buffer(m_Establisher->m_SessionRequestBuffer, len), boost::asio::transfer_all (), - [s = shared_from_this()](const boost::system::error_code& ecode, size_t bytes_transferred) - { - s->Terminate (); - }); - else - boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ())); - } - - void NTCP2Session::SendI2NPMessages (std::list >& msgs) - { - if (m_IsTerminated || msgs.empty ()) - { - msgs.clear (); - return; - } - bool empty = false; - { - std::lock_guard l(m_IntermediateQueueMutex); - empty = m_IntermediateQueue.empty (); - m_IntermediateQueue.splice (m_IntermediateQueue.end (), msgs); - } - if (empty) - boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::PostI2NPMessages, shared_from_this ())); - } - - void NTCP2Session::PostI2NPMessages () - { - 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 (); - 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); - Terminate (); - } - SetSendQueueSize (m_SendQueue.size ()); - } - - void NTCP2Session::SendLocalRouterInfo (bool update) - { - if (update || !IsOutgoing ()) // we send it in SessionConfirmed for outgoing session - boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::SendRouterInfo, shared_from_this ())); - } - - i2p::data::RouterInfo::SupportedTransports NTCP2Session::GetTransportType () const - { - if (m_RemoteEndpoint.address ().is_v4 ()) return i2p::data::RouterInfo::eNTCP2V4; - return i2p::util::net::IsYggdrasilAddress (m_RemoteEndpoint.address ()) ? i2p::data::RouterInfo::eNTCP2V6Mesh : i2p::data::RouterInfo::eNTCP2V6; - } - - NTCP2Server::NTCP2Server (): - RunnableServiceWithWork ("NTCP2"), m_TerminationTimer (GetService ()), - m_ProxyType(eNoProxy), m_Resolver(GetService ()), - m_Rng(i2p::util::GetMonotonicMicroseconds ()%1000000LL) - { - } - - NTCP2Server::~NTCP2Server () - { - Stop (); - } - - void NTCP2Server::Start () - { - m_EstablisherService.Start (); - if (!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) - { - if (!address) continue; - if (address->IsPublishedNTCP2 () && address->port) - { - if (address->IsV4()) - { - 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 ) - { - LogPrint(eLogCritical, "NTCP2: Failed to bind to v4 port ", address->port, ex.what()); - ThrowFatal ("Unable to start IPv4 NTCP2 transport at port ", address->port, ": ", ex.what ()); - continue; - } - - LogPrint (eLogInfo, "NTCP2: Start listening v4 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 ())) - { - m_NTCP2V6Acceptor.reset (new boost::asio::ip::tcp::acceptor (GetService ())); - 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->listen (); - - 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 ()); - continue; - } - } - } - } - ScheduleTermination (); - } - } - - 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 (); - } - m_NTCP2Sessions.clear (); - - if (IsRunning ()) - { - m_TerminationTimer.cancel (); - m_ProxyEndpoint = nullptr; - } - StopIOService (); - } - - bool NTCP2Server::AddNTCP2Session (std::shared_ptr session, bool incoming) - { - if (!session) return false; - if (incoming) - m_PendingIncomingSessions.erase (session->GetRemoteEndpoint ().address ()); - if (!session->GetRemoteIdentity ()) - { - LogPrint (eLogWarning, "NTCP2: Unknown identity for ", session->GetRemoteEndpoint ()); - session->Terminate (); - return false; - } - 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; - } - } - m_NTCP2Sessions.emplace (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); - } - } - - std::shared_ptr NTCP2Server::FindNTCP2Session (const i2p::data::IdentHash& ident) - { - auto it = m_NTCP2Sessions.find (ident); - if (it != m_NTCP2Sessions.end ()) - return it->second; - return nullptr; - } - - void NTCP2Server::Connect(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]() - { - 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 (); - } - }); - // 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)); - } - else - conn->Terminate (); - }); - } - - void NTCP2Server::HandleConnect (const boost::system::error_code& ecode, std::shared_ptr conn, std::shared_ptr timer) - { - timer->cancel (); - if (ecode) - { - LogPrint (eLogInfo, "NTCP2: Connect error ", ecode.message ()); - conn->Terminate (); - } - else - { - LogPrint (eLogDebug, "NTCP2: Connected to ", conn->GetRemoteEndpoint (), - " (", i2p::data::GetIdentHashAbbreviation (conn->GetRemoteIdentity ()->GetIdentHash ()), ")"); - conn->ClientLogin (); - } - } - - void NTCP2Server::HandleAccept (std::shared_ptr conn, const boost::system::error_code& error) - { - if (!error && conn) - { - 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 (m_PendingIncomingSessions.emplace (ep.address (), conn).second) - { - conn->SetRemoteEndpoint (ep); - conn->ServerLogin (); - conn = nullptr; - } - else - LogPrint (eLogInfo, "NTCP2: Incoming session from ", ep.address (), " is already pending"); - } - else - LogPrint (eLogError, "NTCP2: Incoming connection from invalid IP ", ep.address ()); - } - else - LogPrint (eLogError, "NTCP2: Connected from error ", ec.message ()); - } - else - { - LogPrint (eLogError, "NTCP2: Accept error ", error.message ()); - if (error == boost::asio::error::no_descriptors) - { - i2p::context.SetError (eRouterErrorNoDescriptors); - return; - } - } - - if (error != boost::asio::error::operation_aborted) - { - if (!conn) // connection is used, create new one - conn = std::make_shared (*this); - else // reuse failed - conn->Close (); - m_NTCP2Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCP2Server::HandleAccept, this, - conn, std::placeholders::_1)); - } - } - - void NTCP2Server::HandleAcceptV6 (std::shared_ptr conn, const boost::system::error_code& error) - { - if (!error && conn) - { - boost::system::error_code ec; - auto ep = conn->GetSocket ().remote_endpoint(ec); - if (!ec) - { - LogPrint (eLogDebug, "NTCP2: Connected from ", ep); - if (!i2p::transport::transports.IsInReservedRange(ep.address ()) || - i2p::util::net::IsYggdrasilAddress (ep.address ())) - { - if (m_PendingIncomingSessions.emplace (ep.address (), conn).second) - { - conn->SetRemoteEndpoint (ep); - conn->ServerLogin (); - conn = nullptr; - } - else - LogPrint (eLogInfo, "NTCP2: Incoming session from ", ep.address (), " is already pending"); - } - else - LogPrint (eLogError, "NTCP2: Incoming connection from invalid IP ", ep.address ()); - } - else - LogPrint (eLogError, "NTCP2: Connected from error ", ec.message ()); - } - else - { - LogPrint (eLogError, "NTCP2: Accept ipv6 error ", error.message ()); - if (error == boost::asio::error::no_descriptors) - { - i2p::context.SetErrorV6 (eRouterErrorNoDescriptors); - return; - } - } - - if (error != boost::asio::error::operation_aborted) - { - if (!conn) // connection is used, create new one - conn = std::make_shared (*this); - else // reuse failed - conn->Close (); - m_NTCP2V6Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCP2Server::HandleAcceptV6, this, - conn, std::placeholders::_1)); - } - } - - 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.async_wait (std::bind (&NTCP2Server::HandleTerminationTimer, - this, std::placeholders::_1)); - } - - void NTCP2Server::HandleTerminationTimer (const boost::system::error_code& ecode) - { - if (ecode != boost::asio::error::operation_aborted) - { - auto ts = i2p::util::GetSecondsSinceEpoch (); - // established - for (auto& it: m_NTCP2Sessions) - if (it.second->IsTerminationTimeoutExpired (ts)) - { - auto session = it.second; - 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)) - { - it->second->Terminate (); - it = m_PendingIncomingSessions.erase (it); // established of 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 deleted file mode 100644 index b50d9087..00000000 --- a/libi2pd/NTCP2.h +++ /dev/null @@ -1,333 +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 NTCP2_H__ -#define NTCP2_H__ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "Crypto.h" -#include "util.h" -#include "RouterInfo.h" -#include "TransportSession.h" - -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 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_CLOCK_SKEW = 60; // in seconds - const int NTCP2_MAX_OUTGOING_QUEUE_SIZE = 500; // how many messages we can queue up - - enum NTCP2BlockType - { - eNTCP2BlkDateTime = 0, - eNTCP2BlkOptions, // 1 - eNTCP2BlkRouterInfo, // 2 - eNTCP2BlkI2NPMessage, // 3 - eNTCP2BlkTermination, // 4 - eNTCP2BlkPadding = 254 - }; - - enum NTCP2TerminationReason - { - eNTCP2NormalClose = 0, - eNTCP2TerminationReceived, // 1 - eNTCP2IdleTimeout, // 2 - eNTCP2RouterShutdown, // 3 - eNTCP2DataPhaseAEADFailure, // 4 - eNTCP2IncompatibleOptions, // 5 - eNTCP2IncompatibleSignatureType, // 6 - eNTCP2ClockSkew, // 7 - eNTCP2PaddingViolation, // 8 - eNTCP2AEADFramingError, // 9 - eNTCP2PayloadFormatError, // 10 - eNTCP2Message1Error, // 11 - eNTCP2Message2Error, // 12 - eNTCP2Message3Error, // 13 - eNTCP2IntraFrameReadTimeout, // 14 - eNTCP2RouterInfoSignatureVerificationFail, // 15 - eNTCP2IncorrectSParameter, // 16 - eNTCP2Banned, // 17 - }; - - // RouterInfo flags - const uint8_t NTCP2_ROUTER_INFO_FLAG_REQUEST_FLOOD = 0x01; - - struct NTCP2Establisher: private i2p::crypto::NoiseSymmetricState - { - NTCP2Establisher (); - ~NTCP2Establisher (); - - 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 * 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 (); - - bool KeyDerivationFunction1 (const uint8_t * pub, i2p::crypto::X25519Keys& priv, const uint8_t * rs, const uint8_t * epub); // for SessionRequest, (pub, priv) for DH - bool KeyDerivationFunction2 (const uint8_t * sessionRequest, size_t sessionRequestLen, const uint8_t * epub); // for SessionCreate - void CreateEphemeralKey (); - - bool CreateSessionRequestMessage (std::mt19937& rng); - bool CreateSessionCreatedMessage (std::mt19937& rng); - bool CreateSessionConfirmedMessagePart1 (); - bool CreateSessionConfirmedMessagePart2 (); - - bool ProcessSessionRequestMessage (uint16_t& paddingLen, bool& clockSkew); - bool ProcessSessionCreatedMessage (uint16_t& paddingLen); - bool ProcessSessionConfirmedMessagePart1 (); - bool ProcessSessionConfirmedMessagePart2 (uint8_t * m3p2Buf); - - std::shared_ptr m_EphemeralKeys; - uint8_t m_RemoteEphemeralPublicKey[32]; // x25519 - uint8_t m_RemoteStaticKey[32], m_IV[16]; - i2p::data::IdentHash m_RemoteIdentHash; - uint16_t m3p2Len; - - uint8_t m_SessionRequestBuffer[NTCP2_SESSION_REQUEST_MAX_SIZE], - m_SessionCreatedBuffer[NTCP2_SESSION_CREATED_MAX_SIZE], * 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 (); - void Terminate (); - void TerminateByTimeout (); - void Done () override; - void Close (); // for accept - void DeleteNextReceiveBuffer (uint64_t ts); - - boost::asio::ip::tcp::socket& GetSocket () { return m_Socket; }; - const boost::asio::ip::tcp::endpoint& GetRemoteEndpoint () { return m_RemoteEndpoint; }; - void SetRemoteEndpoint (const boost::asio::ip::tcp::endpoint& ep) { m_RemoteEndpoint = ep; }; - - bool IsEstablished () const override { return m_IsEstablished; }; - i2p::data::RouterInfo::SupportedTransports GetTransportType () const override; - bool IsTerminated () const { return m_IsTerminated; }; - - void ClientLogin (); // Alice - void ServerLogin (); // Bob - - void SendLocalRouterInfo (bool update) override; // after handshake or by update - void SendI2NPMessages (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); - - // establish - void SendSessionRequest (); - void SendSessionCreated (); - void SendSessionConfirmed (); - - 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); - void Receive (); - void HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred); - void ProcessNextFrame (const uint8_t * frame, size_t len); - - void SetNextSentFrameLength (size_t frameLen, uint8_t * lengthBuf); - void SendI2NPMsgs (std::vector >& msgs); - void HandleI2NPMsgsSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, std::vector > msgs); - void EncryptAndSendNextBuffer (size_t payloadLen); - void HandleNextFrameSent (const boost::system::error_code& ecode, std::size_t bytes_transferred); - size_t CreatePaddingBlock (size_t msgLen, uint8_t * buf, size_t len); - void SendQueue (); - void SendRouterInfo (); - void SendTermination (NTCP2TerminationReason reason); - void SendTerminationAndTerminate (NTCP2TerminationReason reason); - void ReadSomethingAndTerminate (); - void PostI2NPMessages (); - - 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]; - const uint8_t * m_SendKey, * m_ReceiveKey; -#if OPENSSL_SIPHASH - EVP_MD_CTX * m_SendMDCtx, * m_ReceiveMDCtx; -#else - const uint8_t * m_SendSipKey, * m_ReceiveSipKey; -#endif - uint16_t m_NextReceivedLen; - uint8_t * m_NextReceivedBuffer, * m_NextSendBuffer; - size_t m_NextReceivedBufferSize; - union - { - uint8_t buf[8]; - uint16_t key; - } m_ReceiveIV, m_SendIV; - uint64_t m_ReceiveSequenceNumber, m_SendSequenceNumber; - - i2p::I2NPMessagesHandler m_Handler; - - bool m_IsSending, m_IsReceiving; - std::list > m_SendQueue; - uint64_t m_NextRouterInfoResendTime; // seconds since epoch - - std::list > m_IntermediateQueue; // from transports - mutable std::mutex m_IntermediateQueueMutex; - - uint16_t m_PaddingSizes[16]; - int m_NextPaddingSize; - }; - - class NTCP2Server: private i2p::util::RunnableServiceWithWork - { - private: - - class EstablisherService: public i2p::util::RunnableServiceWithWork - { - public: - - EstablisherService (): RunnableServiceWithWork ("NTCP2e") {}; - auto& GetService () { return GetIOService (); }; - void Start () { StartIOService (); }; - void Stop () { StopIOService (); }; - }; - - public: - - enum ProxyType - { - 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); - 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); - - private: - - 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); - - // timer - void ScheduleTermination (); - void HandleTerminationTimer (const boost::system::error_code& ecode); - - private: - - boost::asio::deadline_timer m_TerminationTimer; - std::unique_ptr m_NTCP2Acceptor, m_NTCP2V6Acceptor; - std::map > m_NTCP2Sessions; - std::map > m_PendingIncomingSessions; - - ProxyType m_ProxyType; - std::string m_ProxyAddress, m_ProxyAuthorization; - uint16_t m_ProxyPort; - boost::asio::ip::tcp::resolver m_Resolver; - std::unique_ptr m_ProxyEndpoint; - - std::shared_ptr m_Address4, m_Address6, m_YggdrasilAddress; - std::mt19937 m_Rng; - EstablisherService m_EstablisherService; - i2p::crypto::AEADChaCha20Poly1305Encryptor m_Encryptor; - i2p::crypto::AEADChaCha20Poly1305Decryptor m_Decryptor; - - public: - - // for HTTP/I2PControl - const decltype(m_NTCP2Sessions)& GetNTCP2Sessions () const { return m_NTCP2Sessions; }; - }; -} -} - -#endif diff --git a/libi2pd/NTCPSession.cpp b/libi2pd/NTCPSession.cpp new file mode 100644 index 00000000..a340089c --- /dev/null +++ b/libi2pd/NTCPSession.cpp @@ -0,0 +1,1324 @@ +#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 +{ + 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 ()); + } + + 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; + } + } +#if (__GNUC__ == 4) && (__GNUC_MINOR__ <= 7) +// due the bug in gcc 4.7. std::shared_future.get() is not const + if (!m_DHKeysPair) + m_DHKeysPair = transports.GetNextDHKeysPair (); + CreateAESKey (m_Establisher->phase1.pubKey); + SendPhase2 (); +#else + // TODO: check for number of pending keys + auto s = shared_from_this (); + auto keyCreated = std::async (std::launch::async, [s] () + { + if (!s->m_DHKeysPair) + s->m_DHKeysPair = transports.GetNextDHKeysPair (); + s->CreateAESKey (s->m_Establisher->phase1.pubKey); + }).share (); + m_Server.GetService ().post ([s, keyCreated]() + { + keyCreated.get (); + s->SendPhase2 (); + }); +#endif + } + } + + void NTCPSession::SendPhase2 () + { + 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 + { +#if (__GNUC__ == 4) && (__GNUC_MINOR__ <= 7) +// due the bug in gcc 4.7. std::shared_future.get() is not const + CreateAESKey (m_Establisher->phase2.pubKey); + HandlePhase2 (); +#else + auto s = shared_from_this (); + // create AES key in separate thread + auto keyCreated = std::async (std::launch::async, [s] () + { + s->CreateAESKey (s->m_Establisher->phase2.pubKey); + }).share (); // TODO: use move capture in C++ 14 instead shared_future + // let other operations execute while a key gets created + m_Server.GetService ().post ([s, keyCreated]() + { + keyCreated.get (); // we might wait if no more pending operations + s->HandlePhase2 (); + }); +#endif + } + } + + void NTCPSession::HandlePhase2 () + { + 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 ", 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 ", 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 (): + 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) + { + } + + 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) + { + 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->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; + // Termniate 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..b64f63aa --- /dev/null +++ b/libi2pd/NTCPSession.h @@ -0,0 +1,222 @@ +#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" + +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; + }; + + 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; }; + 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 (); + 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 (); + 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: + + enum RemoteAddressType + { + eIP4Address, + eIP6Address, + eHostname + }; + + enum ProxyType + { + eNoProxy, + eSocksProxy, + eHTTPProxy + }; + + + NTCPServer (); + ~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(); } + 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; + + 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..b136dfd5 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 @@ -21,13 +12,10 @@ #include "I2NPProtocol.h" #include "Tunnel.h" #include "Transports.h" -#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 +25,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_FloodfillBootstrap(nullptr), m_HiddenMode(false) { } @@ -57,49 +43,23 @@ 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 - { + uint16_t threshold; i2p::config::GetOption("reseed.threshold", threshold); + 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,229 +69,148 @@ 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; 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 && !m_HiddenMode) // 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; + 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 == 0) { - 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? + 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_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) - { - bool updated; - return AddRouterInfo (buf, len, updated); - } - - std::shared_ptr NetDb::AddRouterInfo (const uint8_t * buf, int len, bool& updated) + bool NetDb::AddRouterInfo (const uint8_t * buf, int len) { IdentityEx identity; if (identity.FromBuffer (buf, len)) - return AddRouterInfo (identity.GetIdentHash (), buf, len, updated); - updated = false; - return nullptr; + return AddRouterInfo (identity.GetIdentHash (), buf, len); + return false; } + void NetDb::SetHidden(bool hide) { + // TODO: remove reachable addresses from router info + m_HiddenMode = hide; + } + bool NetDb::AddRouterInfo (const IdentHash& ident, const uint8_t * buf, int len) { - bool updated; - if (!AddRouterInfo (ident, buf, len, updated)) - updated = false; - return updated; - } - - std::shared_ptr NetDb::AddRouterInfo (const IdentHash& ident, const uint8_t * buf, int len, bool& updated) - { - updated = true; + bool updated = true; auto r = FindRouter (ident); if (r) { 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()); - 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); - 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 (); - } - } + r->Update (buf, len); + LogPrint (eLogInfo, "NetDb: RouterInfo updated: ", ident.ToBase64()); + // TODO: check if floodfill has been changed } 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 ()) { 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,83 +223,54 @@ namespace data updated = false; } // take care about requested destination - m_Requests->RequestComplete (ident, r); - return r; + m_Requests.RequestComplete (ident, r); + return updated; } - bool NetDb::AddLeaseSet (const IdentHash& ident, const uint8_t * buf, int len) + bool NetDb::AddLeaseSet (const IdentHash& ident, const uint8_t * buf, int len, + std::shared_ptr from) { - 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) + if (!from) // unsolicited LS must be received directly { - // we update only is existing LeaseSet is not LeaseSet2 - uint64_t expires; - if(LeaseSetBufferValidate(buf, len, expires)) + auto it = m_LeaseSets.find(ident); + if (it != m_LeaseSets.end ()) { - if(it->second->GetExpirationTime() < expires) + uint64_t expires; + if(LeaseSetBufferValidate(buf, len, expires)) { - it->second->Update (buf, len, false); // signature is verified already - if (CheckLogLevel (eLogInfo)) + if(it->second->GetExpirationTime() < expires) + { + it->second->Update (buf, len, false); // signature is verified already LogPrint (eLogInfo, "NetDb: LeaseSet updated: ", ident.ToBase32()); + updated = true; + } + else + LogPrint(eLogDebug, "NetDb: LeaseSet is older: ", ident.ToBase32()); + } + else + LogPrint(eLogError, "NetDb: LeaseSet is invalid: ", ident.ToBase32()); + } + else + { + auto leaseSet = std::make_shared (buf, len, false); // we don't need leases in netdb + if (leaseSet->IsValid ()) + { + LogPrint (eLogInfo, "NetDb: LeaseSet added: ", ident.ToBase32()); + m_LeaseSets[ident] = leaseSet; updated = true; } - else if (CheckLogLevel (eLogDebug)) - LogPrint(eLogDebug, "NetDb: LeaseSet is older: ", ident.ToBase32()); + else + LogPrint (eLogError, "NetDb: new LeaseSet validation failed: ", ident.ToBase32()); } - else - LogPrint(eLogError, "NetDb: LeaseSet is invalid: ", ident.ToBase32()); - } - else - { - 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()); - m_LeaseSets[ident] = leaseSet; - updated = true; - } - else - 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) - { - 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); - } - } - } - else - 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 +280,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; @@ -440,43 +290,15 @@ namespace data std::shared_ptr NetDb::FindRouterProfile (const IdentHash& ident) const { - if (!m_PersistProfiles) - return nullptr; - auto router = FindRouter (ident); return router ? router->GetProfile () : nullptr; } 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 +309,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 boostrap 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 +357,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,23 +382,22 @@ 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); } void NetDb::VisitStoredRouterInfos(RouterInfoVisitor v) { - m_Storage.Iterate([v] (const std::string & filename) - { - auto ri = std::make_shared(filename); + m_Storage.Iterate([v] (const std::string & filename) { + auto ri = std::make_shared(filename); v(ri); }); } 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 +409,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,308 +452,187 @@ 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++; + it->second->SaveProfile (); + it = m_RouterInfos.erase (it); + continue; } + ++it; } } - // clean up expired floodfills or not floodfills anymore + // clean up expired floodfiils { - 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 = 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); - else - LogPrint (eLogError, "NetDb: Requests is null"); - } - - void NetDb::HandleNTCP2RouterInfoMsg (std::shared_ptr m) - { - uint8_t flood = m->GetPayload ()[0] & NTCP2_ROUTER_INFO_FLAG_REQUEST_FLOOD; - bool updated; - auto ri = AddRouterInfo (m->GetPayload () + 1, m->GetPayloadLength () - 1, updated); // without flags - if (flood && updated && context.IsFloodfill () && ri) + auto dest = m_Requests.CreateRequest (destination, false, requestComplete); // non-exploratory + if (!dest) { - auto floodMsg = CreateDatabaseStoreMsg (ri, 0); // replyToken = 0 - Flood (ri->GetIdentHash (), floodMsg); + 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: ", 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::HandleDatabaseStoreMsg (std::shared_ptr m) { 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; bool updated = false; - uint8_t storeType = buf[DATABASE_STORE_TYPE_OFFSET]; - if (storeType) // LeaseSet or LeaseSet2 + if (buf[DATABASE_STORE_TYPE_OFFSET]) // type { - if (len > MAX_LS_BUFFER_SIZE + offset) - { - LogPrint (eLogError, "NetDb: Database store message is too long ", len); - return; - } - if (!context.IsFloodfill ()) - { - LogPrint (eLogInfo, "NetDb: Not Floodfill, LeaseSet store request ignored for ", ident.ToBase32()); - return; - } - else if (!m->from) // unsolicited LS must be received directly - { - if (storeType == NETDB_STORE_TYPE_LEASESET) // 1 - { - if (CheckLogLevel (eLogDebug)) - LogPrint (eLogDebug, "NetDb: Store request: LeaseSet for ", ident.ToBase32()); - updated = AddLeaseSet (ident, buf + offset, len - offset); - } - else // all others are considered as LeaseSet2 - { - if (CheckLogLevel (eLogDebug)) - LogPrint (eLogDebug, "NetDb: Store request: LeaseSet2 of type ", int(storeType), " for ", ident.ToBase32()); - updated = AddLeaseSet2 (ident, buf + offset, len - offset, storeType); - } - } + LogPrint (eLogDebug, "NetDb: store request: LeaseSet for ", ident.ToBase32()); + updated = AddLeaseSet (ident, buf + offset, len - offset, m->from); } - else // RouterInfo + else { - 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 +650,120 @@ 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); + 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); + if (floodfill) + { + 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 + break; + } } 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 detination 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 +773,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 +792,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 || - lookupType == DATABASE_LOOKUP_TYPE_NORMAL_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 +872,98 @@ 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; - excluded.insert (i2p::context.GetIdentHash ()); // don't flood to itself - excluded.insert (ident); // don't flood back - for (int i = 0; i < 3; i++) + // 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++) { - auto floodfill = GetClosestFloodfill (ident, excluded, false); // current day + 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) { - const auto& h = floodfill->GetIdentHash(); - transports.SendMessage (h, CopyI2NPMessage(floodMsg)); - excluded.insert (h); - } - else - return; // no more floodfills - } - if (andNextDay) - { - // flood to two more closest flodfills for next day - std::unordered_set excluded1; - excluded1.insert (i2p::context.GetIdentHash ()); // don't flood to itself - excluded1.insert (ident); // don't flood back - for (int i = 0; i < 2; i++) - { - auto floodfill = GetClosestFloodfill (ident, excluded1, true); // next day - if (floodfill) + if (i2p::transport::transports.IsConnected (floodfill->GetIdentHash ())) + throughTunnels = false; + if (throughTunnels) { - 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); + 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 - return; - } - } + 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 ()); + } + } } std::shared_ptr NetDb::GetRandomRouter () const @@ -1151,60 +975,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 +1018,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,114 +1045,113 @@ 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 - { - return GetRandomRouter( - [fam](std::shared_ptr router)->bool - { - return router->IsFamily(fam); - }); - } + std::shared_ptr NetDb::GetRandomRouterInFamily(const std::string & fam) const { + return GetRandomRouter( + [fam](std::shared_ptr router)->bool + { + return router->IsFamily(fam); + }); + } - 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 () @@ -1388,22 +1159,14 @@ namespace data auto ts = i2p::util::GetMillisecondsSinceEpoch (); for (auto it = m_LeaseSets.begin (); it != m_LeaseSets.end ();) { - if (!it->second->IsValid () || ts > it->second->GetExpirationTime () - LEASE_ENDDATE_THRESHOLD) + if (ts > it->second->GetExpirationTime () - LEASE_ENDDATE_THRESHOLD) { - LogPrint (eLogInfo, "NetDb: LeaseSet ", it->first.ToBase64 (), " expired or invalid"); + LogPrint (eLogInfo, "NetDb: LeaseSet ", it->second->GetIdentHash ().ToBase64 (), " expired"); it = m_LeaseSets.erase (it); } 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..18377b4f 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,38 +52,43 @@ 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); + bool AddLeaseSet (const IdentHash& ident, const uint8_t * buf, int len, std::shared_ptr from); std::shared_ptr FindRouter (const IdentHash& ident) const; 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); + 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 +101,34 @@ 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 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 +137,15 @@ 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; + /** router info we are bootstrapping from or nullptr if we are not currently doing that*/ + std::shared_ptr m_FloodfillBootstrap; - std::vector > m_ExploratorySelection; - uint64_t m_LastExploratorySelectionUpdateTime; // in monotonic seconds - std::mt19937 m_Rng; - i2p::util::MemoryPoolMt m_RouterInfoBuffersPool; - i2p::util::MemoryPoolMt m_RouterInfoAddressesPool; - i2p::util::MemoryPoolMt m_RouterInfoAddressVectorsPool; - i2p::util::MemoryPoolMt m_LeasesPool; - i2p::util::MemoryPoolMt m_IdentitiesPool; - i2p::util::MemoryPoolMt m_RouterProfilesPool; + /** 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/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..d701e4b8 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 (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..4a2f8055 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); @@ -251,7 +219,7 @@ namespace data BN_CTX * bnctx = BN_CTX_new (); BIGNUM * s = BN_new (), * n = BN_new (); BN_bin2bn (signature, signatureLength, s); - BN_bin2bn (it->second, 512, n); // RSA 4096 assumed + BN_bin2bn (it->second, i2p::crypto::RSASHA5124096_KEY_LENGTH, n); BN_mod_exp (s, s, i2p::crypto::GetRSAE (), n, bnctx); // s = s^e mod n uint8_t * enSigBuf = new uint8_t[signatureLength]; i2p::crypto::bn2buf (s, enSigBuf, signatureLength); @@ -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; @@ -332,7 +300,7 @@ namespace data s.read (localFileName, fileNameLength); localFileName[fileNameLength] = 0; s.seekg (extraFieldLength, std::ios::cur); - // take care about data descriptor if presented + // take care about data desriptor if presented if (bitFlag & ZIP_BIT_FLAG_DATA_DESCRIPTOR) { size_t pos = s.tellg (); @@ -407,7 +375,7 @@ namespace data if (end - contentPos >= contentLength) break; // we are beyond contentLength } - if (numFiles) // check if routers are not outdated + if (numFiles) // check if routers are not outdated { auto ts = i2p::util::GetMillisecondsSinceEpoch (); int numOutdated = 0; @@ -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..38fe3224 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -1,16 +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 "Config.h" #include "Crypto.h" -#include "Ed25519.h" #include "Timestamp.h" #include "I2NPProtocol.h" #include "NetDb.hpp" @@ -19,10 +9,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 +17,104 @@ 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); +#if defined(__x86_64__) || defined(__i386__) || defined(_MSC_VER) + m_Keys = i2p::data::PrivateKeys::CreateRandomKeys (i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519); +#else + m_Keys = i2p::data::PrivateKeys::CreateRandomKeys (i2p::data::SIGNING_KEY_TYPE_DSA_SHA1); +#endif 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) - { - i2p::config::GetOption("ntcp2.published", ntcp2Published); - if (ntcp2Published) - { - std::string ntcp2proxy; i2p::config::GetOption("ntcp2.proxy", ntcp2proxy); - if (!ntcp2proxy.empty ()) ntcp2Published = false; - } - } - bool ssu2Published = false; - if (ssu2) - i2p::config::GetOption("ssu2.published", ssu2Published); - uint8_t caps = 0; + if (!port) + port = rand () % (30777 - 9111) + 9111; // I2P network ports range + bool ipv4; i2p::config::GetOption("ipv4", ipv4); + bool ipv6; i2p::config::GetOption("ipv6", ipv6); + 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(); + + routerInfo.AddSSUAddress (host.c_str(), port, routerInfo.GetIdentHash ()); + 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 = "::"; + 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(); + + routerInfo.AddSSUAddress (host.c_str(), port, routerInfo.GetIdentHash ()); + 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 - { - port = rand () % (30777 - 9111) + 9111; // I2P network ports range - } - while(i2p::util::net::IsPortInReservedRange(port)); - - return port; } void RouterContext::UpdateRouterInfo () { - std::shared_ptr buffer; - { - std::lock_guard l(m_RouterInfoMutex); - m_RouterInfo.CreateBuffer (m_Keys); - buffer = m_RouterInfo.CopyBuffer (); - } - { - // update save buffer to latest - std::lock_guard l(m_SaveBufferMutex); - m_SaveBuffer = buffer; - } - bool isSaving = false; - if (m_IsSaving.compare_exchange_strong (isSaving, true)) // try to save only if not being saved - { - auto savingRouterInfo = std::async (std::launch::async, [this]() - { - std::shared_ptr buffer; - while (m_SaveBuffer) - { - { - std::lock_guard l(m_SaveBufferMutex); - buffer = m_SaveBuffer; - m_SaveBuffer = nullptr; - } - if (buffer) - i2p::data::RouterInfo::SaveToFile (i2p::fs::DataDirPath (ROUTER_INFO), buffer); - } - m_IsSaving = false; - }); - } + m_RouterInfo.CreateBuffer (m_Keys); + m_RouterInfo.SaveToFile (i2p::fs::DataDirPath (ROUTER_INFO)); m_LastUpdateTime = i2p::util::GetSecondsSinceEpoch (); } - void RouterContext::NewNTCP2Keys () - { - m_NTCP2StaticKeys.reset (new i2p::crypto::X25519Keys ()); - m_NTCP2StaticKeys->GenerateKeys (); - m_NTCP2Keys.reset (new NTCP2PrivateKeys ()); - m_NTCP2StaticKeys->GetPrivateKey (m_NTCP2Keys->staticPrivateKey); - memcpy (m_NTCP2Keys->staticPublicKey, m_NTCP2StaticKeys->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 +124,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->port != port) { address->port = port; updated = true; @@ -401,215 +137,44 @@ namespace i2p UpdateRouterInfo (); } - void RouterContext::PublishNTCP2Address (std::shared_ptr address, - int port, bool publish) const - { - if (!address) return; - if (!port && !address->port) port = SelectRandomPort (); - if (port) address->port = port; - address->published = publish; - memcpy (address->i, m_NTCP2Keys->iv, 16); - } - - void RouterContext::PublishNTCP2Address (int port, bool publish, bool v4, bool v6, bool ygg) - { - if (!m_NTCP2Keys) return; - auto addresses = m_RouterInfo.GetAddresses (); - if (!addresses) return; - bool updated = false; - if (v4) - { - 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 (); - } - bool updated = false; - for (auto& address : *addresses) - { - if (address && address->IsSSU2 () && (!address->port || address->port != port || address->published != publish) && - ((v4 && address->IsV4 ()) || (v6 && address->IsV6 ()))) - { - if (port) address->port = port; - else if (!address->port) address->port = newPort; - address->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); - updated = true; - } - } - if (updated) - UpdateRouterInfo (); - } - - void RouterContext::UpdateSSU2Keys () - { - if (!m_SSU2Keys) return; - auto addresses = m_RouterInfo.GetAddresses (); - if (!addresses) return; - for (auto& it: *addresses) - { - if (it && it->IsSSU2 ()) - { - it->s = m_SSU2Keys->staticPublicKey; - it->i = m_SSU2Keys->intro; - } - } - } - 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 +211,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 +229,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 +260,71 @@ namespace i2p return m_RouterInfo.GetCaps () & i2p::data::RouterInfo::eUnreachable; } - void RouterContext::SetUnreachable (bool v4, bool v6) + void RouterContext::SetUnreachable () { - if (v4 || (v6 && !SupportsV4 ())) + // 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 address + auto& addresses = m_RouterInfo.GetAddresses (); + for (auto it = addresses.begin (); it != addresses.end (); ++it) { - // 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); + if ((*it)->transportStyle == i2p::data::RouterInfo::eTransportNTCP && + (*it)->host.is_v4 ()) + { + addresses.erase (it); + break; + } } - uint16_t port = 0; // delete previous introducers - auto addresses = m_RouterInfo.GetAddresses (); - if (addresses) - { - for (auto& addr : *addresses) - if (addr && addr->ssu && ((v4 && addr->IsV4 ()) || (v6 && addr->IsV6 ()))) - { - addr->published = false; - addr->caps &= ~i2p::data::RouterInfo::eSSUIntroducer; // can't be introducer - addr->ssu->introducers.clear (); - port = addr->port; - } - } - // unpublish NTCP2 addreeses - bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); - if (ntcp2) - PublishNTCP2Address (port, false, v4, v6, false); + 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; + caps |= i2p::data::RouterInfo::eSSUIntroducer; + if (m_IsFloodfill) + caps |= i2p::data::RouterInfo::eFloodfill; + m_RouterInfo.SetCaps (caps); + + // insert NTCP back + auto& addresses = m_RouterInfo.GetAddresses (); + for (const auto& addr : addresses) { - // 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; - // 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) + if (addr->transportStyle == i2p::data::RouterInfo::eTransportSSU && + addr->host.is_v4 ()) { - uint16_t ntcp2Port; i2p::config::GetOption ("ntcp2.port", ntcp2Port); - if (!ntcp2Port) ntcp2Port = port; - PublishNTCP2Address (ntcp2Port, true, v4, v6, false); + // insert NTCP address with host/port from SSU + m_RouterInfo.AddNTCPAddress (addr->host.to_string ().c_str (), addr->port); + break; } } + // delete previous introducers + 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 +333,50 @@ 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); + m_RouterInfo.AddSSUAddress (host.to_string ().c_str (), port, GetIdentHash (), mtu ? mtu : 1472); // TODO + updated = true; } - else - m_RouterInfo.DisableMesh (); - UpdateRouterInfo (); - } - - void RouterContext::SetMTU (int mtu, bool v4) - { - if (mtu < 1280 || mtu > 1500) return; - auto addresses = m_RouterInfo.GetAddresses (); - if (!addresses) return; - for (auto& addr: *addresses) - { - if (addr && addr->ssu && ((v4 && addr->IsV4 ()) || (!v4 && addr->IsV6 ()))) - { - addr->ssu->mtu = mtu; - LogPrint (eLogDebug, "Router: MTU for ", v4 ? "ipv4" : "ipv6", " address ", addr->host.to_string(), " is set to ", mtu); - } - } - } - - void RouterContext::UpdateNTCP2V6Address (const boost::asio::ip::address& host) - { - auto addresses = m_RouterInfo.GetAddresses (); - if (!addresses) return; - std::shared_ptr addr; - if (i2p::util::net::IsYggdrasilAddress (host)) // yggdrasil - addr = (*addresses)[i2p::data::RouterInfo::eNTCP2V6MeshIdx]; - else if (host.is_v6 ()) - addr = (*addresses)[i2p::data::RouterInfo::eNTCP2V6Idx]; - if (addr && addr->IsPublishedNTCP2 () && addr->host != host) - { - addr->host = host; + if (updated) UpdateRouterInfo (); - } } void RouterContext::UpdateStats () @@ -1051,84 +390,39 @@ namespace i2p } } - void RouterContext::UpdateTimestamp (uint64_t ts) - { - if (ts > m_LastUpdateTime + ROUTER_INFO_UPDATE_INTERVAL) - UpdateRouterInfo (); - } - 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; } - // read NTCP2 keys if available - std::ifstream n2k (i2p::fs::DataDirPath (NTCP2_KEYS), std::ifstream::in | std::ifstream::binary); - if (n2k) + else // new keys file format { - n2k.seekg (0, std::ios::end); - size_t len = n2k.tellg(); - n2k.seekg (0, std::ios::beg); - if (len == sizeof (NTCP2PrivateKeys)) - { - m_NTCP2Keys.reset (new NTCP2PrivateKeys ()); - n2k.read ((char *)m_NTCP2Keys.get (), sizeof (NTCP2PrivateKeys)); - } - n2k.close (); + uint8_t * buf = new uint8_t[len]; + fk.read ((char *)buf, len); + m_Keys.FromBuffer (buf, len); + delete[] buf; } - // 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 +431,7 @@ namespace i2p } if (IsUnreachable ()) - SetReachable (true, true); // we assume reachable until we discover firewall through peer tests - - bool updated = false; - // create new NTCP2 keys if required - bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); - bool ygg; i2p::config::GetOption("meshnets.yggdrasil", ygg); - if ((ntcp2 || ygg) && !m_NTCP2Keys) - { - NewNTCP2Keys (); - UpdateNTCP2Keys (); - updated = true; - } - // 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 (); + SetReachable (); // we assume reachable until we discover firewall through peer tests return true; } @@ -1181,378 +452,36 @@ 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) : false; } - - bool RouterContext::DecryptTunnelBuildRecord (const uint8_t * encrypted, uint8_t * data) - { - return DecryptECIESTunnelBuildRecord (encrypted, data, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE); - } - - bool RouterContext::DecryptECIESTunnelBuildRecord (const uint8_t * encrypted, uint8_t * data, size_t clearTextSize) - { - // 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_NTCP2Keys) NewNTCP2Keys (); - auto x = new i2p::crypto::X25519Keys (m_NTCP2Keys->staticPrivateKey, m_NTCP2Keys->staticPublicKey); - if (!m_NTCP2StaticKeys) - m_NTCP2StaticKeys.reset (x); - else - delete x; - } - return *m_NTCP2StaticKeys; - } - - 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..ef23af25 100644 --- a/libi2pd/RouterContext.h +++ b/libi2pd/RouterContext.h @@ -1,165 +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 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 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 - { - 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 (); - 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); 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 UpdateAddress (const boost::asio::ip::address& host); // called from SSU or Daemon + 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,107 +78,52 @@ 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 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: void CreateNewRouter (); 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; - uint64_t m_LastUpdateTime; // in seconds + std::shared_ptr m_Decryptor; + uint64_t m_LastUpdateTime; 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::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::mutex m_GarlicMutex; }; extern RouterContext context; diff --git a/libi2pd/RouterInfo.cpp b/libi2pd/RouterInfo.cpp index 4af32f57..e9c2a384 100644 --- a/libi2pd/RouterInfo.cpp +++ b/libi2pd/RouterInfo.cpp @@ -1,97 +1,57 @@ -/* -* 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 + // verify signature since we have indentity 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,224 @@ 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)); + char transportStyle[5]; + ReadString (transportStyle, 5, s); + if (!strcmp (transportStyle, "NTCP")) + address->transportStyle = eTransportNTCP; + else if (!strcmp (transportStyle, "SSU")) { - address->transportStyle = eTransportSSU2; + 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; + if (address->transportStyle == eTransportNTCP) + { + supportedTransports |= eNTCPV4; // TODO: + address->addressString = value; + } else - // we consider such address as invalid - address->transportStyle = eTransportUnknown; + { + supportedTransports |= eSSUV4; // TODO: + address->addressString = value; + } + } + else + { + // 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 - } - else if (key == "i") // ntcp2 iv or ssu2 intro - { - if (address->IsNTCP2 ()) - { - if (Base64ToByteStream (value, address->i, 16) == 16) - address->published = true; // presence of "i" means "published" NTCP2 - else - address->transportStyle = eTransportUnknown; // invalid address - } - else if (address->IsSSU2 ()) - { - if (Base64ToByteStream (value, address->i, 32) == 32) - isIntroKey = true; - else - address->transportStyle = eTransportUnknown; // invalid address - } - } - else if (key == "v") - { - if (value == "2") - isV2 = true; - else - { - LogPrint (eLogWarning, "RouterInfo: Unexpected value ", value, " for v"); - address->transportStyle = eTransportUnknown; // invalid address - } + LogPrint (eLogWarning, "RouterInfo: Unexpected field 'key' for NTCP"); } + else if (!strcmp (key, "caps")) + ExtractCaps (value); 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 (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 (introducers) supportedTransports |= eSSUV4; // in case if host is not presented 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; + m_Caps |= Caps::eExtraBandwidth; break; case CAPS_FLAG_HIDDEN: m_Caps |= Caps::eHidden; @@ -536,866 +368,103 @@ 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' */ + caps += CAPS_FLAG_HIGH_BANDWIDTH3; // 'O' + } 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 ("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 ()) - { - 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) + WriteString ("host", properties); + properties << '='; + WriteString (address.host.to_string (), properties); + properties << ';'; + 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 +472,55 @@ 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) - { - WriteString ("port", properties); - properties << '='; - WriteString (std::to_string(address.port), properties); - properties << ';'; - } - if (address.IsNTCP2 () || address.IsSSU2 ()) - { - // publish s and v for NTCP2 or SSU2 - WriteString ("s", properties); properties << '='; - WriteString (address.s.ToBase64 (), properties); properties << ';'; - WriteString ("v", properties); properties << '='; - WriteString ("2", properties); properties << ';'; - } + WriteString ("port", properties); + properties << '='; + WriteString (boost::lexical_cast(address.port), properties); + properties << ';'; uint16_t size = htobe16 (properties.str ().size ()); s.write ((char *)&size, sizeof (size)); @@ -1463,19 +545,173 @@ 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 = 2; + 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_back(std::move(addr)); + } + + 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; + } + + 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 +719,128 @@ 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 (); - } - } - - 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 m_SupportedTransports & (eNTCPV4 | eNTCPV6); } - std::shared_ptr LocalRouterInfo::NewBuffer () const + bool RouterInfo::IsSSU (bool v4only) const { - return std::make_shared (); + if (v4only) + return m_SupportedTransports & eSSUV4; + else + return m_SupportedTransports & (eSSUV4 | eSSUV6); } - std::shared_ptr LocalRouterInfo::NewAddress () const + bool RouterInfo::IsV6 () const { - return std::make_shared
(); + return m_SupportedTransports & (eNTCPV6 | eSSUV6); } - RouterInfo::AddressesPtr LocalRouterInfo::NewAddresses () const + bool RouterInfo::IsV4 () const { - return RouterInfo::AddressesPtr(new RouterInfo::Addresses ()); + return m_SupportedTransports & (eNTCPV4 | eSSUV4); } - std::shared_ptr LocalRouterInfo::NewIdentity (const uint8_t * buf, size_t len) const + void RouterInfo::EnableV6 () { - return std::make_shared (buf, len); - } - - bool LocalRouterInfo::AddSSU2Introducer (const Introducer& introducer, bool v4) + if (!IsV6 ()) + m_SupportedTransports |= eNTCPV6 | eSSUV6; + } + + void RouterInfo::EnableV4 () { - auto addresses = GetAddresses (); - if (!addresses) return false; - auto addr = (*addresses)[v4 ? eSSU2V4Idx : eSSU2V6Idx]; - if (addr) + if (!IsV4 ()) + m_SupportedTransports |= eNTCPV4 | eSSUV4; + } + + + void RouterInfo::DisableV6 () + { + if (IsV6 ()) { - for (auto& intro: addr->ssu->introducers) - if (intro.iTag == introducer.iTag) return false; // already presented - addr->ssu->introducers.push_back (introducer); - SetReachableTransports (GetReachableTransports () | ((addr->IsV4 () ? eSSU2V4 : eSSU2V6))); - return true; + m_SupportedTransports &= ~(eNTCPV6 | eSSUV6); + 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; + } } - return false; } - bool LocalRouterInfo::RemoveSSU2Introducer (const IdentHash& h, bool v4) + void RouterInfo::DisableV4 () { - auto addresses = GetAddresses (); - if (!addresses) return false; - auto addr = (*addresses)[v4 ? eSSU2V4Idx : eSSU2V6Idx]; - if (addr) + if (IsV4 ()) { - 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; - } + m_SupportedTransports &= ~(eNTCPV4 | eSSUV4); + 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; + } } - return false; } - bool LocalRouterInfo::UpdateSSU2Introducer (const IdentHash& h, bool v4, uint32_t iTag, uint32_t iExp) + + bool RouterInfo::UsesIntroducer () const { - auto addresses = GetAddresses (); - if (!addresses) return false; - auto addr = (*addresses)[v4 ? eSSU2V4Idx : eSSU2V6Idx]; - if (addr) + return m_Caps & Caps::eUnreachable; // non-reachable + } + + std::shared_ptr RouterInfo::GetNTCPAddress (bool v4only) const + { + return GetAddress (eTransportNTCP, v4only); + } + + std::shared_ptr RouterInfo::GetSSUAddress (bool v4only) const + { + return GetAddress (eTransportSSU, v4only); + } + + std::shared_ptr RouterInfo::GetSSUV6Address () const + { + return GetAddress (eTransportSSU, false, true); + } + + std::shared_ptr RouterInfo::GetAddress (TransportStyle s, bool v4only, bool v6only) const + { +#if (BOOST_VERSION >= 105300) + auto addresses = boost::atomic_load (&m_Addresses); +#else + auto addresses = m_Addresses; +#endif + for (const auto& address : *addresses) { - for (auto& it: addr->ssu->introducers) - if (h == it.iH) - { - it.iTag = iTag; - it.iExp = iExp; - return true; - } + if (address->transportStyle == s) + { + if ((!v4only || address->host.is_v4 ()) && (!v6only || address->host.is_v6 ())) + return address; + } } - return false; - } + return nullptr; + } + + std::shared_ptr RouterInfo::GetProfile () const + { + if (!m_Profile) + m_Profile = GetRouterProfile (GetIdentHash ()); + return m_Profile; + } + + void RouterInfo::Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx) const + { + auto encryptor = m_RouterIdentity->CreateEncryptor (nullptr); + if (encryptor) + encryptor->Encrypt (data, encrypted, ctx); + } } } diff --git a/libi2pd/RouterInfo.h b/libi2pd/RouterInfo.h index cb3ae499..09e2c015 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,54 @@ 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 }; -#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,6 +84,7 @@ namespace data struct SSUExt { int mtu; + IntroKey key; // intro key for SSU std::vector introducers; }; @@ -150,261 +92,129 @@ namespace data { 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 + std::string addressString; int port; uint64_t date; - uint8_t caps; - bool published = false; + uint8_t cost; std::unique_ptr ssu; // not null for SSU 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 && - host == other.host && port == other.port; + return transportStyle == other.transportStyle && host == other.host && port == other.port; } bool operator!=(const Address& other) const { 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 ()); }; }; + 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 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; }; - 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; }; + void AddNTCPAddress (const char * host, int port); + void AddSSUAddress (const char * host, int port, const uint8_t * key, int mtu = 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 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); - 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 (); + size_t ReadString (char* str, size_t len, std::istream& s) const; 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; + void ExtractCaps (const char * value); + std::shared_ptr GetAddress (TransportStyle s, bool v4only, bool v6only = false) const; + void UpdateCapsProperty (); 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..2197552c --- /dev/null +++ b/libi2pd/SSU.cpp @@ -0,0 +1,790 @@ +#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 (); + 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; + 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; + 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..d789e70e --- /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) + { + 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); + 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..2a8cd2d5 --- /dev/null +++ b/libi2pd/SSUSession.cpp @@ -0,0 +1,1200 @@ +#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, senderEndpoint); // 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, const boost::asio::ip::udp::endpoint& senderEndpoint) + { + 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 reaquest header size ", headerSize, " exceeds packet length ", len); + return; + } + m_RemoteEndpoint = senderEndpoint; + 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) m_Data.Send (it); + } + } + + 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 it's 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..8247c420 --- /dev/null +++ b/libi2pd/SSUSession.h @@ -0,0 +1,165 @@ +#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 + + // 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 (); + 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, const boost::asio::ip::udp::endpoint& senderEndpoint); + 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; + 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..aded9bc8 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,205 +6,443 @@ namespace i2p { namespace crypto { -#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 - DSAVerifier::DSAVerifier (): - m_PublicKey (nullptr) + class Ed25519 { + public: + + Ed25519 () + { + BN_CTX * ctx = BN_CTX_new (); + BIGNUM * tmp = BN_new (); + + q = BN_new (); + // 2^255-19 + BN_set_bit (q, 255); // 2^255 + BN_sub_word (q, 19); + + l = BN_new (); + // 2^252 + 27742317777372353535851937790883648493 + BN_set_bit (l, 252); + two_252_2 = BN_dup (l); + BN_dec2bn (&tmp, "27742317777372353535851937790883648493"); + BN_add (l, l, tmp); + BN_sub_word (two_252_2, 2); // 2^252 - 2 + + // -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_mul (d, d, tmp, ctx); + + // 2^((q-1)/4) + I = BN_new (); + BN_free (tmp); + tmp = BN_dup (q); + BN_sub_word (tmp, 1); + BN_div_word (tmp, 4); + BN_set_word (I, 2); + BN_mod_exp (I, I, tmp, q, ctx); + BN_free (tmp); + + // 4*inv(5) + BIGNUM * By = BN_new (); + BN_set_word (By, 5); + BN_mod_inverse (By, By, q, ctx); + BN_mul_word (By, 4); + BIGNUM * Bx = RecoverX (By, ctx); + BN_mod (Bx, Bx, q, ctx); // % q + BN_mod (By, By, q, ctx); // % q + + // precalculate Bi256 table + Bi256Carry = { Bx, By }; // B + for (int i = 0; i < 32; i++) + { + Bi256[i][0] = Bi256Carry; // first point + for (int j = 1; j < 128; j++) + Bi256[i][j] = Sum (Bi256[i][j-1], Bi256[i][0], ctx); // (256+j+1)^i*B + Bi256Carry = Bi256[i][127]; + for (int j = 0; j < 128; j++) // add first point 128 more times + Bi256Carry = Sum (Bi256Carry, Bi256[i][0], ctx); + } + + BN_CTX_free (ctx); + } + + Ed25519 (const Ed25519& other): q (BN_dup (other.q)), l (BN_dup (other.l)), + d (BN_dup (other.d)), I (BN_dup (other.I)), two_252_2 (BN_dup (other.two_252_2)), + Bi256Carry (other.Bi256Carry) + { + for (int i = 0; i < 32; i++) + for (int j = 0; j < 128; j++) + Bi256[i][j] = other.Bi256[i][j]; + } + + ~Ed25519 () + { + BN_free (q); + BN_free (l); + BN_free (d); + BN_free (I); + BN_free (two_252_2); + } + + + EDDSAPoint GeneratePublicKey (const uint8_t * expandedPrivateKey, BN_CTX * ctx) const + { + return MulB (expandedPrivateKey, ctx); // left half of expanded key, considered as Little Endian + } + + EDDSAPoint DecodePublicKey (const uint8_t * buf, BN_CTX * ctx) const + { + return DecodePoint (buf, ctx); + } + + void EncodePublicKey (const EDDSAPoint& publicKey, uint8_t * buf, BN_CTX * ctx) const + { + EncodePoint (Normalize (publicKey, ctx), buf); + } + + bool Verify (const EDDSAPoint& publicKey, const uint8_t * digest, const uint8_t * signature) const + { + BN_CTX * ctx = BN_CTX_new (); + BIGNUM * h = DecodeBN<64> (digest); + // signature 0..31 - R, 32..63 - S + // B*S = R + PK*h => R = B*S - PK*h + // we don't decode R, but encode (B*S - PK*h) + auto Bs = MulB (signature + EDDSA25519_SIGNATURE_LENGTH/2, ctx); // B*S; + BN_mod (h, h, l, ctx); // public key is multiple of B, but B%l = 0 + auto PKh = Mul (publicKey, h, ctx); // PK*h + uint8_t diff[32]; + EncodePoint (Normalize (Sum (Bs, -PKh, ctx), ctx), diff); // Bs - PKh encoded + bool passed = !memcmp (signature, diff, 32); // R + BN_free (h); + BN_CTX_free (ctx); + if (!passed) + LogPrint (eLogError, "25519 signature verification failed"); + return passed; + } + + void 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 (); + // calculate r + SHA512_CTX ctx; + SHA512_Init (&ctx); + SHA512_Update (&ctx, expandedPrivateKey + EDDSA25519_PRIVATE_KEY_LENGTH, EDDSA25519_PRIVATE_KEY_LENGTH); // right half of expanded key + SHA512_Update (&ctx, buf, len); // data + uint8_t digest[64]; + SHA512_Final (digest, &ctx); + BIGNUM * r = DecodeBN<32> (digest); // DecodeBN<64> (digest); // for test vectors + // calculate R + uint8_t R[EDDSA25519_SIGNATURE_LENGTH/2]; // we must use separate buffer because signature might be inside buf + EncodePoint (Normalize (MulB (digest, bnCtx), bnCtx), R); // EncodePoint (Mul (B, r, bnCtx), R); // for test vectors + // calculate S + SHA512_Init (&ctx); + SHA512_Update (&ctx, R, EDDSA25519_SIGNATURE_LENGTH/2); // R + SHA512_Update (&ctx, publicKeyEncoded, EDDSA25519_PUBLIC_KEY_LENGTH); // public key + SHA512_Update (&ctx, buf, len); // data + SHA512_Final (digest, &ctx); + BIGNUM * h = DecodeBN<64> (digest); + // S = (r + h*a) % l + BIGNUM * a = DecodeBN (expandedPrivateKey); // left half of expanded key + 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); + EncodeBN (h, signature + EDDSA25519_SIGNATURE_LENGTH/2, EDDSA25519_SIGNATURE_LENGTH/2); // S + BN_free (r); BN_free (h); BN_free (a); + BN_CTX_free (bnCtx); + } + + private: + + EDDSAPoint Sum (const EDDSAPoint& p1, const EDDSAPoint& p2, BN_CTX * ctx) const + { + // x3 = (x1*y2+y1*x2)*(z1*z2-d*t1*t2) + // y3 = (y1*y2+x1*x2)*(z1*z2+d*t1*t2) + // z3 = (z1*z2-d*t1*t2)*(z1*z2+d*t1*t2) + // t3 = (y1*y2+x1*x2)*(x1*y2+y1*x2) + BIGNUM * x3 = BN_new (), * y3 = BN_new (), * z3 = BN_new (), * t3 = BN_new (); + + BN_mul (x3, p1.x, p2.x, ctx); // A = x1*x2 + BN_mul (y3, p1.y, p2.y, ctx); // B = y1*y2 + + BN_CTX_start (ctx); + BIGNUM * t1 = p1.t, * t2 = p2.t; + 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 + + if (p1.z) + { + if (p2.z) + BN_mul (z3, p1.z, p2.z, ctx); // D = z1*z2 + else + BN_copy (z3, p1.z); // D = z1 + } + else + { + if (p2.z) + BN_copy (z3, p2.z); // D = z2 + else + BN_one (z3); // D = 1 + } + + BIGNUM * E = BN_CTX_get (ctx), * F = BN_CTX_get (ctx), * G = BN_CTX_get (ctx), * H = BN_CTX_get (ctx); + BN_add (E, p1.x, p1.y); + BN_add (F, p2.x, p2.y); + BN_mul (E, E, F, ctx); // (x1 + y1)*(x2 + y2) + BN_sub (E, E, x3); + BN_sub (E, E, y3); // E = (x1 + y1)*(x2 + y2) - A - B + BN_sub (F, z3, t3); // F = D - C + BN_add (G, z3, t3); // G = D + C + BN_add (H, y3, x3); // H = B + A + + BN_mod_mul (x3, E, F, q, ctx); // x3 = E*F + BN_mod_mul (y3, G, H, q, ctx); // y3 = G*H + BN_mod_mul (z3, F, G, q, ctx); // z3 = F*G + BN_mod_mul (t3, E, H, q, ctx); // t3 = E*H + + BN_CTX_end (ctx); + + return EDDSAPoint {x3, y3, z3, t3}; + } + + void Double (EDDSAPoint& p, BN_CTX * ctx) const + { + BN_CTX_start (ctx); + BIGNUM * x2 = BN_CTX_get (ctx), * y2 = BN_CTX_get (ctx), * z2 = BN_CTX_get (ctx), * t2 = BN_CTX_get (ctx); + + BN_sqr (x2, p.x, ctx); // x2 = A = x^2 + BN_sqr (y2, p.y, ctx); // y2 = B = y^2 + if (p.t) + BN_sqr (t2, p.t, ctx); // t2 = t^2 + else + { + BN_mul (t2, p.x, p.y, ctx); // t = x*y + BN_sqr (t2, t2, ctx); // t2 = 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 + BN_one (z2); // z2 = 1 + + BIGNUM * E = BN_CTX_get (ctx), * F = BN_CTX_get (ctx), * G = BN_CTX_get (ctx), * H = BN_CTX_get (ctx); + // E = (x+y)*(x+y)-A-B = x^2+y^2+2xy-A-B = 2xy + BN_mul (E, p.x, p.y, ctx); + BN_lshift1 (E, E); // E =2*x*y + BN_sub (F, z2, t2); // F = D - C + BN_add (G, z2, t2); // G = D + C + BN_add (H, y2, x2); // H = B + A + + BN_mod_mul (p.x, E, F, q, ctx); // x2 = E*F + BN_mod_mul (p.y, G, H, q, ctx); // y2 = G*H + if (!p.z) p.z = BN_new (); + BN_mod_mul (p.z, F, G, q, ctx); // z2 = F*G + if (!p.t) p.t = BN_new (); + BN_mod_mul (p.t, E, H, q, ctx); // t2 = E*H + + BN_CTX_end (ctx); + } + + EDDSAPoint Mul (const EDDSAPoint& p, const BIGNUM * e, BN_CTX * ctx) const + { + BIGNUM * zero = BN_new (), * one = BN_new (); + BN_zero (zero); BN_one (one); + EDDSAPoint res {zero, one}; + if (!BN_is_zero (e)) + { + int bitCount = BN_num_bits (e); + for (int i = bitCount - 1; i >= 0; i--) + { + Double (res, ctx); + if (BN_is_bit_set (e, i)) res = Sum (res, p, ctx); + } + } + return res; + } + + EDDSAPoint MulB (const uint8_t * e, BN_CTX * ctx) const // B*e, e is 32 bytes Little Endian + { + BIGNUM * zero = BN_new (), * one = BN_new (); + BN_zero (zero); BN_one (one); + EDDSAPoint res {zero, one}; + bool carry = false; + for (int i = 0; i < 32; i++) + { + uint8_t x = e[i]; + if (carry) + { + if (x < 255) + { + x++; + carry = false; + } + else + x = 0; + } + if (x > 0) + { + if (x <= 128) + res = Sum (res, Bi256[i][x-1], ctx); + else + { + res = Sum (res, -Bi256[i][255-x], ctx); // -Bi[256-x] + carry = true; + } + } + } + if (carry) res = Sum (res, Bi256Carry, ctx); + return res; + } + + EDDSAPoint Normalize (const EDDSAPoint& p, BN_CTX * ctx) const + { + if (p.z) + { + BIGNUM * x = BN_new (), * y = BN_new (); + 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}; + } + else + return EDDSAPoint{BN_dup (p.x), BN_dup (p.y)}; + } + + bool IsOnCurve (const EDDSAPoint& p, BN_CTX * ctx) const + { + BN_CTX_start (ctx); + BIGNUM * x2 = BN_CTX_get (ctx), * y2 = BN_CTX_get (ctx), * tmp = BN_CTX_get (ctx); + BN_sqr (x2, p.x, ctx); // x^2 + BN_sqr (y2, p.y, ctx); // y^2 + // y^2 - x^2 - 1 - d*x^2*y^2 + BN_mul (tmp, d, x2, ctx); + BN_mul (tmp, tmp, y2, ctx); + BN_sub (tmp, y2, tmp); + BN_sub (tmp, tmp, x2); + BN_sub_word (tmp, 1); + BN_mod (tmp, tmp, q, ctx); // % q + bool ret = BN_is_zero (tmp); + BN_CTX_end (ctx); + return ret; + } + + BIGNUM * RecoverX (const BIGNUM * y, BN_CTX * ctx) const + { + BN_CTX_start (ctx); + BIGNUM * y2 = BN_CTX_get (ctx), * xx = BN_CTX_get (ctx); + BN_sqr (y2, y, ctx); // y^2 + // xx = (y^2 -1)*inv(d*y^2 +1) + BN_mul (xx, d, y2, ctx); + BN_add_word (xx, 1); + BN_mod_inverse (xx, xx, q, ctx); + BN_sub_word (y2, 1); + BN_mul (xx, y2, xx, ctx); + // x = srqt(xx) = xx^(2^252-2) + BIGNUM * x = BN_new (); + BN_mod_exp (x, xx, two_252_2, q, ctx); + // check (x^2 -xx) % q + BN_sqr (y2, x, ctx); + BN_mod_sub (y2, y2, xx, q, ctx); + if (!BN_is_zero (y2)) + BN_mod_mul (x, x, I, q, ctx); + if (BN_is_odd (x)) + BN_sub (x, q, x); + BN_CTX_end (ctx); + return x; + } + + EDDSAPoint DecodePoint (const uint8_t * buf, BN_CTX * ctx) const + { + // buf is 32 bytes Little Endian, convert it to Big Endian + uint8_t buf1[EDDSA25519_PUBLIC_KEY_LENGTH]; + for (size_t i = 0; i < EDDSA25519_PUBLIC_KEY_LENGTH/2; i++) // invert bytes + { + buf1[i] = buf[EDDSA25519_PUBLIC_KEY_LENGTH -1 - i]; + buf1[EDDSA25519_PUBLIC_KEY_LENGTH -1 - i] = buf[i]; + } + bool isHighestBitSet = buf1[0] & 0x80; + if (isHighestBitSet) + buf1[0] &= 0x7f; // clear highest bit + BIGNUM * y = BN_new (); + BN_bin2bn (buf1, EDDSA25519_PUBLIC_KEY_LENGTH, y); + BIGNUM * x = RecoverX (y, ctx); + if (BN_is_bit_set (x, 0) != isHighestBitSet) + 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 + EDDSAPoint p {x, y, z, t}; + if (!IsOnCurve (p, ctx)) + LogPrint (eLogError, "Decoded point is not on 25519"); + return p; + } + + void EncodePoint (const EDDSAPoint& p, uint8_t * buf) const + { + EncodeBN (p.y, buf,EDDSA25519_PUBLIC_KEY_LENGTH); + if (BN_is_bit_set (p.x, 0)) // highest bit + buf[EDDSA25519_PUBLIC_KEY_LENGTH - 1] |= 0x80; // set highest bit + } + + template + BIGNUM * DecodeBN (const uint8_t * buf) const + { + // buf is Little Endian convert it to Big Endian + uint8_t buf1[len]; + for (size_t i = 0; i < len/2; i++) // invert bytes + { + buf1[i] = buf[len -1 - i]; + buf1[len -1 - i] = buf[i]; + } + BIGNUM * res = BN_new (); + BN_bin2bn (buf1, len, res); + return res; + } + + void EncodeBN (const BIGNUM * bn, uint8_t * buf, size_t len) const + { + bn2buf (bn, buf, len); + // To Little Endian + for (size_t i = 0; i < len/2; i++) // invert bytes + { + uint8_t tmp = buf[i]; + buf[i] = buf[len -1 - i]; + buf[len -1 - i] = tmp; + } + } + + private: + + BIGNUM * q, * l, * d, * I; + // transient values + BIGNUM * two_252_2; // 2^252-2 + EDDSAPoint Bi256[32][128]; // per byte, Bi256[i][j] = (256+j+1)^i*B, we don't store zeroes + // if j > 128 we use 256 - j and carry 1 to next byte + // Bi256[0][0] = B, base point + EDDSAPoint Bi256Carry; // Bi256[32][0] + }; + + static std::unique_ptr g_Ed25519; + std::unique_ptr& GetEd25519 () + { + if (!g_Ed25519) + { + auto c = new Ed25519(); + if (!g_Ed25519) // make sure it was not created already + g_Ed25519.reset (c); + else + delete c; + } + return g_Ed25519; } - DSAVerifier::~DSAVerifier () - { - if (m_PublicKey) - EVP_PKEY_free (m_PublicKey); - } - void DSAVerifier::SetPublicKey (const uint8_t * signingKey) - { - if (m_PublicKey) - EVP_PKEY_free (m_PublicKey); - BIGNUM * pub = BN_bin2bn (signingKey, DSA_PUBLIC_KEY_LENGTH, NULL); - m_PublicKey = CreateDSA (pub); - BN_free (pub); - } - - bool DSAVerifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const - { - // calculate SHA1 digest - uint8_t digest[20], sign[48]; - SHA1 (buf, len, digest); - // signature - DSA_SIG * sig = DSA_SIG_new(); - DSA_SIG_set0 (sig, BN_bin2bn (signature, DSA_SIGNATURE_LENGTH/2, NULL), BN_bin2bn (signature + DSA_SIGNATURE_LENGTH/2, DSA_SIGNATURE_LENGTH/2, NULL)); - // to DER format - uint8_t * s = sign; - auto l = i2d_DSA_SIG (sig, &s); - DSA_SIG_free(sig); - // verify - auto ctx = EVP_PKEY_CTX_new (m_PublicKey, NULL); - EVP_PKEY_verify_init(ctx); - EVP_PKEY_CTX_set_signature_md(ctx, EVP_sha1()); - bool ret = EVP_PKEY_verify(ctx, sign, l, digest, 20); - EVP_PKEY_CTX_free(ctx); - return ret; - } - - DSASigner::DSASigner (const uint8_t * signingPrivateKey, const uint8_t * signingPublicKey) - { - BIGNUM * priv = BN_bin2bn (signingPrivateKey, DSA_PRIVATE_KEY_LENGTH, NULL); - m_PrivateKey = CreateDSA (nullptr, priv); - BN_free (priv); - } - - DSASigner::~DSASigner () - { - if (m_PrivateKey) - EVP_PKEY_free (m_PrivateKey); - } - - void DSASigner::Sign (const uint8_t * buf, int len, uint8_t * signature) const - { - uint8_t digest[20], sign[48]; - SHA1 (buf, len, digest); - auto ctx = EVP_PKEY_CTX_new (m_PrivateKey, NULL); - EVP_PKEY_sign_init(ctx); - EVP_PKEY_CTX_set_signature_md(ctx, EVP_sha1()); - size_t l = 48; - EVP_PKEY_sign(ctx, sign, &l, digest, 20); - const uint8_t * s1 = sign; - DSA_SIG * sig = d2i_DSA_SIG (NULL, &s1, l); - const BIGNUM * r, * s; - DSA_SIG_get0 (sig, &r, &s); - bn2buf (r, signature, DSA_SIGNATURE_LENGTH/2); - bn2buf (s, signature + DSA_SIGNATURE_LENGTH/2, DSA_SIGNATURE_LENGTH/2); - DSA_SIG_free(sig); - EVP_PKEY_CTX_free(ctx); - } - - void CreateDSARandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey) - { - EVP_PKEY * paramskey = CreateDSA(); - EVP_PKEY_CTX * ctx = EVP_PKEY_CTX_new_from_pkey(NULL, paramskey, NULL); - EVP_PKEY_keygen_init(ctx); - EVP_PKEY * pkey = nullptr; - EVP_PKEY_keygen(ctx, &pkey); - BIGNUM * pub = NULL, * priv = NULL; - EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_PUB_KEY, &pub); - bn2buf (pub, signingPublicKey, DSA_PUBLIC_KEY_LENGTH); - EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_PRIV_KEY, &priv); - bn2buf (priv, signingPrivateKey, DSA_PRIVATE_KEY_LENGTH); - BN_free (pub); BN_free (priv); - EVP_PKEY_free (pkey); - EVP_PKEY_free (paramskey); - EVP_PKEY_CTX_free (ctx); - } -#else - - DSAVerifier::DSAVerifier () - { - m_PublicKey = CreateDSA (); - } - - DSAVerifier::~DSAVerifier () - { - DSA_free (m_PublicKey); - } - - void DSAVerifier::SetPublicKey (const uint8_t * signingKey) - { - DSA_set0_key (m_PublicKey, BN_bin2bn (signingKey, DSA_PUBLIC_KEY_LENGTH, NULL), NULL); - } - - bool DSAVerifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const - { - // calculate SHA1 digest - uint8_t digest[20]; - SHA1 (buf, len, digest); - // signature - DSA_SIG * sig = DSA_SIG_new(); - DSA_SIG_set0 (sig, BN_bin2bn (signature, DSA_SIGNATURE_LENGTH/2, NULL), BN_bin2bn (signature + DSA_SIGNATURE_LENGTH/2, DSA_SIGNATURE_LENGTH/2, NULL)); - // DSA verification - int ret = DSA_do_verify (digest, 20, sig, m_PublicKey); - DSA_SIG_free(sig); - return ret; - } - - DSASigner::DSASigner (const uint8_t * signingPrivateKey, const uint8_t * signingPublicKey) - { - m_PrivateKey = CreateDSA (); - DSA_set0_key (m_PrivateKey, BN_bin2bn (signingPublicKey, DSA_PUBLIC_KEY_LENGTH, NULL), BN_bin2bn (signingPrivateKey, DSA_PRIVATE_KEY_LENGTH, NULL)); - } - - DSASigner::~DSASigner () - { - DSA_free (m_PrivateKey); - } - - void DSASigner::Sign (const uint8_t * buf, int len, uint8_t * signature) const - { - uint8_t digest[20]; - SHA1 (buf, len, digest); - DSA_SIG * sig = DSA_do_sign (digest, 20, m_PrivateKey); - const BIGNUM * r, * s; - DSA_SIG_get0 (sig, &r, &s); - bn2buf (r, signature, DSA_SIGNATURE_LENGTH/2); - bn2buf (s, signature + DSA_SIGNATURE_LENGTH/2, DSA_SIGNATURE_LENGTH/2); - DSA_SIG_free(sig); - } - - void CreateDSARandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey) - { - DSA * dsa = CreateDSA (); - DSA_generate_key (dsa); - const BIGNUM * pub_key, * priv_key; - DSA_get0_key(dsa, &pub_key, &priv_key); - bn2buf (priv_key, signingPrivateKey, DSA_PRIVATE_KEY_LENGTH); - bn2buf (pub_key, signingPublicKey, DSA_PUBLIC_KEY_LENGTH); - DSA_free (dsa); - } -#endif - -#if OPENSSL_EDDSA - EDDSA25519Verifier::EDDSA25519Verifier (): - m_Pkey (nullptr) - { - } - - EDDSA25519Verifier::~EDDSA25519Verifier () - { - EVP_PKEY_free (m_Pkey); - } - - void EDDSA25519Verifier::SetPublicKey (const uint8_t * signingKey) - { - if (m_Pkey) EVP_PKEY_free (m_Pkey); - m_Pkey = EVP_PKEY_new_raw_public_key (EVP_PKEY_ED25519, NULL, signingKey, 32); - } - - 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; - } - -#else - EDDSA25519Verifier::EDDSA25519Verifier () - { - } - - EDDSA25519Verifier::~EDDSA25519Verifier () - { - } - - void EDDSA25519Verifier::SetPublicKey (const uint8_t * signingKey) + EDDSA25519Verifier::EDDSA25519Verifier (const uint8_t * signingKey) { memcpy (m_PublicKeyEncoded, signingKey, EDDSA25519_PUBLIC_KEY_LENGTH); BN_CTX * ctx = BN_CTX_new (); @@ -236,12 +462,15 @@ namespace crypto return GetEd25519 ()->Verify (m_PublicKey, digest, signature); } -#endif - EDDSA25519SignerCompat::EDDSA25519SignerCompat (const uint8_t * signingPrivateKey, const uint8_t * signingPublicKey) + EDDSA25519Signer::EDDSA25519Signer (const uint8_t * signingPrivateKey, const uint8_t * signingPublicKey) { // expand key - Ed25519::ExpandPrivateKey (signingPrivateKey, m_ExpandedPrivateKey); + SHA512 (signingPrivateKey, EDDSA25519_PRIVATE_KEY_LENGTH, m_ExpandedPrivateKey); + m_ExpandedPrivateKey[0] &= 0xF8; // drop last 3 bits + m_ExpandedPrivateKey[EDDSA25519_PRIVATE_KEY_LENGTH - 1] &= 0x3F; // drop first 2 bits + m_ExpandedPrivateKey[EDDSA25519_PRIVATE_KEY_LENGTH - 1] |= 0x40; // set second bit + // generate and encode public key BN_CTX * ctx = BN_CTX_new (); auto publicKey = GetEd25519 ()->GeneratePublicKey (m_ExpandedPrivateKey, ctx); @@ -258,230 +487,11 @@ namespace crypto BN_CTX_free (ctx); } - EDDSA25519SignerCompat::~EDDSA25519SignerCompat () - { - } - - void EDDSA25519SignerCompat::Sign (const uint8_t * buf, int len, uint8_t * signature) const + void EDDSA25519Signer::Sign (const uint8_t * buf, int len, uint8_t * signature) const { GetEd25519 ()->Sign (m_ExpandedPrivateKey, m_PublicKeyEncoded, buf, len, signature); } - -#if OPENSSL_EDDSA - EDDSA25519Signer::EDDSA25519Signer (const uint8_t * signingPrivateKey, const uint8_t * signingPublicKey): - m_Pkey (nullptr), m_Fallback (nullptr) - { - m_Pkey = EVP_PKEY_new_raw_private_key (EVP_PKEY_ED25519, NULL, signingPrivateKey, 32); - uint8_t publicKey[EDDSA25519_PUBLIC_KEY_LENGTH]; - size_t len = EDDSA25519_PUBLIC_KEY_LENGTH; - EVP_PKEY_get_raw_public_key (m_Pkey, publicKey, &len); - 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; - } - } - - EDDSA25519Signer::~EDDSA25519Signer () - { - if (m_Fallback) delete m_Fallback; - if (m_Pkey) EVP_PKEY_free (m_Pkey); - } - - void EDDSA25519Signer::Sign (const uint8_t * buf, int len, uint8_t * signature) const - { - if (m_Fallback) - return m_Fallback->Sign (buf, len, signature); - else if (m_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); - } - else - LogPrint (eLogError, "EdDSA signing key is not set"); - } -#endif - -#if (OPENSSL_VERSION_NUMBER >= 0x030000000) - static const OSSL_PARAM EDDSA25519phParams[] = - { - OSSL_PARAM_utf8_string ("instance", (char *)"Ed25519ph", 9), - OSSL_PARAM_END - }; - - bool EDDSA25519phVerifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const - { - auto pkey = GetPkey (); - if (pkey) - { - uint8_t digest[64]; - SHA512 (buf, len, digest); - EVP_MD_CTX * ctx = EVP_MD_CTX_create (); - EVP_DigestVerifyInit_ex (ctx, NULL, NULL, NULL, NULL, pkey, EDDSA25519phParams); - auto ret = EVP_DigestVerify (ctx, signature, 64, digest, 64); - EVP_MD_CTX_destroy (ctx); - return ret; - } - else - LogPrint (eLogError, "EdDSA verification key is not set"); - return false; - } - - EDDSA25519phSigner::EDDSA25519phSigner (const uint8_t * signingPrivateKey): - EDDSA25519Signer (signingPrivateKey) - { - } - - void EDDSA25519phSigner::Sign (const uint8_t * buf, int len, uint8_t * signature) const - { - auto pkey = GetPkey (); - if (pkey) - { - uint8_t digest[64]; - SHA512 (buf, len, digest); - EVP_MD_CTX * ctx = EVP_MD_CTX_create (); - size_t l = 64; - uint8_t sig[64]; - EVP_DigestSignInit_ex (ctx, NULL, NULL, NULL, NULL, pkey, EDDSA25519phParams); - if (!EVP_DigestSign (ctx, sig, &l, digest, 64)) - LogPrint (eLogError, "EdDSA signing failed"); - memcpy (signature, sig, 64); - EVP_MD_CTX_destroy (ctx); - } - else - LogPrint (eLogError, "EdDSA signing key is not set"); - } -#endif - -#if OPENSSL_PQ - - MLDSA44Verifier::MLDSA44Verifier (): - m_Pkey (nullptr) - { - } - - MLDSA44Verifier::~MLDSA44Verifier () - { - EVP_PKEY_free (m_Pkey); - } - - void MLDSA44Verifier::SetPublicKey (const uint8_t * signingKey) - { - if (m_Pkey) - { - EVP_PKEY_free (m_Pkey); - m_Pkey = nullptr; - } - OSSL_PARAM params[] = - { - OSSL_PARAM_octet_string (OSSL_PKEY_PARAM_PUB_KEY, (uint8_t *)signingKey, GetPublicKeyLen ()), - OSSL_PARAM_END - }; - EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name (NULL, "ML-DSA-44", NULL); - if (ctx) - { - EVP_PKEY_fromdata_init (ctx); - EVP_PKEY_fromdata (ctx, &m_Pkey, OSSL_KEYMGMT_SELECT_PUBLIC_KEY, params); - EVP_PKEY_CTX_free (ctx); - } - else - LogPrint (eLogError, "MLDSA44 can't create PKEY context"); - } - - bool MLDSA44Verifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const - { - bool ret = false; - if (m_Pkey) - { - EVP_PKEY_CTX * vctx = EVP_PKEY_CTX_new_from_pkey (NULL, m_Pkey, NULL); - if (vctx) - { - EVP_SIGNATURE * sig = EVP_SIGNATURE_fetch (NULL, "ML-DSA-44", NULL); - if (sig) - { - int encode = 1; - OSSL_PARAM params[] = - { - OSSL_PARAM_int(OSSL_SIGNATURE_PARAM_MESSAGE_ENCODING, &encode), - OSSL_PARAM_END - }; - EVP_PKEY_verify_message_init (vctx, sig, params); - ret = EVP_PKEY_verify (vctx, signature, GetSignatureLen (), buf, len); - EVP_SIGNATURE_free (sig); - } - EVP_PKEY_CTX_free (vctx); - } - else - LogPrint (eLogError, "MLDSA44 can't obtain context from PKEY"); - } - else - LogPrint (eLogError, "MLDSA44 verification key is not set"); - return ret; - } - - MLDSA44Signer::MLDSA44Signer (const uint8_t * signingPrivateKey): - m_Pkey (nullptr) - { - OSSL_PARAM params[] = - { - OSSL_PARAM_octet_string (OSSL_PKEY_PARAM_PRIV_KEY, (uint8_t *)signingPrivateKey, MLDSA44_PRIVATE_KEY_LENGTH), - OSSL_PARAM_END - }; - EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name (NULL, "ML-DSA-44", NULL); - if (ctx) - { - EVP_PKEY_fromdata_init (ctx); - EVP_PKEY_fromdata (ctx, &m_Pkey, OSSL_KEYMGMT_SELECT_PRIVATE_KEY, params); - EVP_PKEY_CTX_free (ctx); - } - else - LogPrint (eLogError, "MLDSA44 can't create PKEY context"); - } - - MLDSA44Signer::~MLDSA44Signer () - { - if (m_Pkey) EVP_PKEY_free (m_Pkey); - } - - void MLDSA44Signer::Sign (const uint8_t * buf, int len, uint8_t * signature) const - { - if (m_Pkey) - { - EVP_PKEY_CTX * sctx = EVP_PKEY_CTX_new_from_pkey (NULL, m_Pkey, NULL); - if (sctx) - { - EVP_SIGNATURE * sig = EVP_SIGNATURE_fetch (NULL, "ML-DSA-44", NULL); - if (sig) - { - int encode = 1; - OSSL_PARAM params[] = - { - OSSL_PARAM_int(OSSL_SIGNATURE_PARAM_MESSAGE_ENCODING, &encode), - OSSL_PARAM_END - }; - EVP_PKEY_sign_message_init (sctx, sig, params); - size_t siglen = MLDSA44_SIGNATURE_LENGTH; - EVP_PKEY_sign (sctx, signature, &siglen, buf, len); - EVP_SIGNATURE_free (sig); - } - EVP_PKEY_CTX_free (sctx); - } - else - LogPrint (eLogError, "MLDSA44 can't obtain context from PKEY"); - } - else - LogPrint (eLogError, "MLDSA44 signing key is not set"); - } - -#endif } } + + diff --git a/libi2pd/Signature.h b/libi2pd/Signature.h index 20c7e11b..1e7db9f7 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__ @@ -14,9 +6,9 @@ #include #include #include +#include #include #include "Crypto.h" -#include "Ed25519.h" #include "Gost.h" namespace i2p @@ -32,7 +24,6 @@ namespace crypto virtual size_t GetPublicKeyLen () const = 0; virtual size_t GetSignatureLen () const = 0; virtual size_t GetPrivateKeyLen () const { return GetSignatureLen ()/2; }; - virtual void SetPublicKey (const uint8_t * signingKey) = 0; }; class Signer @@ -43,55 +34,90 @@ 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 (const uint8_t * signingKey) + { + m_PublicKey = CreateDSA (); + 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) @@ -127,13 +153,9 @@ namespace crypto { public: - ECDSAVerifier () + ECDSAVerifier (const uint8_t * signingKey) { 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); @@ -246,16 +268,160 @@ namespace crypto CreateECDSARandomKeys (NID_secp521r1, ECDSAP521_KEY_LENGTH, signingPrivateKey, signingPublicKey); } +// RSA + template + class RSAVerifier: public Verifier + { + public: + + RSAVerifier (const uint8_t * signingKey) + { + m_PublicKey = RSA_new (); + RSA_set0_key (m_PublicKey, BN_bin2bn (signingKey, keyLen, NULL) /* n */ , BN_dup (GetRSAE ()) /* d */, NULL); + } + + ~RSAVerifier () + { + RSA_free (m_PublicKey); + } + + bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const + { + uint8_t digest[Hash::hashLen]; + Hash::CalculateHash (buf, len, digest); + return RSA_verify (type, digest, Hash::hashLen, signature, GetSignatureLen (), m_PublicKey); + } + size_t GetPublicKeyLen () const { return keyLen; } + size_t GetSignatureLen () const { return keyLen; } + size_t GetPrivateKeyLen () const { return GetSignatureLen ()*2; }; + + private: + + RSA * m_PublicKey; + }; + + + template + class RSASigner: public Signer + { + public: + + RSASigner (const uint8_t * signingPrivateKey) + { + m_PrivateKey = RSA_new (); + RSA_set0_key (m_PrivateKey, BN_bin2bn (signingPrivateKey, keyLen, NULL), /* n */ + BN_dup (GetRSAE ()) /* e */, BN_bin2bn (signingPrivateKey + keyLen, keyLen, NULL) /* d */); + } + + ~RSASigner () + { + RSA_free (m_PrivateKey); + } + + void Sign (const uint8_t * buf, int len, uint8_t * signature) const + { + uint8_t digest[Hash::hashLen]; + Hash::CalculateHash (buf, len, digest); + unsigned int signatureLen = keyLen; + RSA_sign (type, digest, Hash::hashLen, signature, &signatureLen, m_PrivateKey); + } + + private: + + RSA * m_PrivateKey; + }; + + inline void CreateRSARandomKeys (size_t publicKeyLen, uint8_t * signingPrivateKey, uint8_t * signingPublicKey) + { + RSA * rsa = RSA_new (); + BIGNUM * e = BN_dup (GetRSAE ()); // make it non-const + RSA_generate_key_ex (rsa, publicKeyLen*8, e, NULL); + const BIGNUM * n, * d, * e1; + RSA_get0_key (rsa, &n, &e1, &d); + bn2buf (n, signingPrivateKey, publicKeyLen); + bn2buf (d, signingPrivateKey + publicKeyLen, publicKeyLen); + bn2buf (n, signingPublicKey, publicKeyLen); + BN_free (e); // this e is not assigned to rsa->e + RSA_free (rsa); + } + +// RSA_SHA256_2048 + const size_t RSASHA2562048_KEY_LENGTH = 256; + typedef RSAVerifier RSASHA2562048Verifier; + typedef RSASigner RSASHA2562048Signer; + +// RSA_SHA384_3072 + const size_t RSASHA3843072_KEY_LENGTH = 384; + typedef RSAVerifier RSASHA3843072Verifier; + typedef RSASigner RSASHA3843072Signer; + +// RSA_SHA512_4096 + const size_t RSASHA5124096_KEY_LENGTH = 512; + typedef RSAVerifier RSASHA5124096Verifier; + typedef RSASigner RSASHA5124096Signer; // EdDSA + struct EDDSAPoint + { + BIGNUM * x {nullptr}; + BIGNUM * y {nullptr}; + BIGNUM * z {nullptr}; + BIGNUM * t {nullptr}; // projective coordinates + + EDDSAPoint () {} + EDDSAPoint (const EDDSAPoint& other) { *this = other; } + EDDSAPoint (EDDSAPoint&& other) { *this = std::move (other); } + EDDSAPoint (BIGNUM * x1, BIGNUM * y1, BIGNUM * z1 = nullptr, BIGNUM * t1 = nullptr) + : x(x1) + , y(y1) + , z(z1) + , t(t1) + {} + ~EDDSAPoint () { BN_free (x); BN_free (y); BN_free(z); BN_free(t); } + + EDDSAPoint& operator=(EDDSAPoint&& other) + { + if (this != &other) + { + BN_free (x); x = other.x; other.x = nullptr; + BN_free (y); y = other.y; other.y = nullptr; + BN_free (z); z = other.z; other.z = nullptr; + BN_free (t); t = other.t; other.t = nullptr; + } + return *this; + } + + EDDSAPoint& operator=(const EDDSAPoint& other) + { + if (this != &other) + { + BN_free (x); x = other.x ? BN_dup (other.x) : nullptr; + BN_free (y); y = other.y ? BN_dup (other.y) : nullptr; + BN_free (z); z = other.z ? BN_dup (other.z) : nullptr; + BN_free (t); t = other.t ? BN_dup (other.t) : nullptr; + } + return *this; + } + + EDDSAPoint operator-() const + { + BIGNUM * x1 = NULL, * y1 = NULL, * z1 = NULL, * t1 = NULL; + if (x) { x1 = BN_dup (x); BN_set_negative (x1, !BN_is_negative (x)); }; + if (y) y1 = BN_dup (y); + if (z) z1 = BN_dup (z); + if (t) { t1 = BN_dup (t); BN_set_negative (t1, !BN_is_negative (t)); }; + return EDDSAPoint {x1, y1, z1, t1}; + } + }; + + const size_t EDDSA25519_PUBLIC_KEY_LENGTH = 32; + const size_t EDDSA25519_SIGNATURE_LENGTH = 64; + const size_t EDDSA25519_PRIVATE_KEY_LENGTH = 32; class EDDSA25519Verifier: public Verifier { public: - EDDSA25519Verifier (); - void SetPublicKey (const uint8_t * signingKey); - ~EDDSA25519Verifier (); - + EDDSA25519Verifier (const uint8_t * signingKey); bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const; size_t GetPublicKeyLen () const { return EDDSA25519_PUBLIC_KEY_LENGTH; }; @@ -263,38 +429,18 @@ namespace crypto private: -#if OPENSSL_EDDSA - - EVP_PKEY * m_Pkey; - - protected: - - EVP_PKEY * GetPkey () const { return m_Pkey; }; -#else EDDSAPoint m_PublicKey; uint8_t m_PublicKeyEncoded[EDDSA25519_PUBLIC_KEY_LENGTH]; -#endif }; -#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 - class EDDSA25519phVerifier: public EDDSA25519Verifier + class EDDSA25519Signer: public Signer { public: - bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const; - }; -#endif - - class EDDSA25519SignerCompat: public Signer - { - public: - - EDDSA25519SignerCompat (const uint8_t * signingPrivateKey, const uint8_t * signingPublicKey = nullptr); + EDDSA25519Signer (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 + const uint8_t * GetPublicKey () const { return m_PublicKeyEncoded; }; private: @@ -302,62 +448,11 @@ namespace crypto uint8_t m_PublicKeyEncoded[EDDSA25519_PUBLIC_KEY_LENGTH]; }; -#if OPENSSL_EDDSA - class EDDSA25519Signer: public Signer - { - public: - - 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; - EDDSA25519SignerCompat * m_Fallback; - }; -#else - - typedef EDDSA25519SignerCompat EDDSA25519Signer; - -#endif - -#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 - class EDDSA25519phSigner: public EDDSA25519Signer - { - public: - - EDDSA25519phSigner (const uint8_t * signingPrivateKey); - - void Sign (const uint8_t * buf, int len, uint8_t * signature) const; - }; - -#endif - inline void CreateEDDSA25519RandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey) { -#if OPENSSL_EDDSA - EVP_PKEY *pkey = NULL; - EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id (EVP_PKEY_ED25519, NULL); - EVP_PKEY_keygen_init (pctx); - EVP_PKEY_keygen (pctx, &pkey); - EVP_PKEY_CTX_free (pctx); - size_t len = EDDSA25519_PUBLIC_KEY_LENGTH; - 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 RAND_bytes (signingPrivateKey, EDDSA25519_PRIVATE_KEY_LENGTH); EDDSA25519Signer signer (signingPrivateKey); memcpy (signingPublicKey, signer.GetPublicKey (), EDDSA25519_PUBLIC_KEY_LENGTH); -#endif } @@ -393,22 +488,15 @@ namespace crypto enum { keyLen = Hash::hashLen }; - GOSTR3410Verifier (GOSTR3410ParamSet paramSet): - m_ParamSet (paramSet), m_PublicKey (nullptr) - { - } - - void SetPublicKey (const uint8_t * signingKey) + GOSTR3410Verifier (GOSTR3410ParamSet paramSet, const uint8_t * signingKey): + m_ParamSet (paramSet) { 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 () { EC_POINT_free (m_PublicKey); } bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const { @@ -484,94 +572,8 @@ namespace crypto typedef GOSTR3410Signer GOSTR3410_256_Signer; typedef GOSTR3410Verifier GOSTR3410_512_Verifier; typedef GOSTR3410Signer GOSTR3410_512_Signer; - - // RedDSA - typedef EDDSA25519Verifier RedDSA25519Verifier; - class RedDSA25519Signer: public Signer - { - public: - - RedDSA25519Signer (const uint8_t * signingPrivateKey) - { - memcpy (m_PrivateKey, signingPrivateKey, EDDSA25519_PRIVATE_KEY_LENGTH); - BN_CTX * ctx = BN_CTX_new (); - 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); - } - - 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]; - }; - - inline void CreateRedDSA25519RandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey) - { - GetEd25519 ()->CreateRedDSAPrivateKey (signingPrivateKey); - RedDSA25519Signer signer (signingPrivateKey); - memcpy (signingPublicKey, signer.GetPublicKey (), EDDSA25519_PUBLIC_KEY_LENGTH); - } - -#if OPENSSL_PQ -#include - - // Post-Quantum - const size_t MLDSA44_PUBLIC_KEY_LENGTH = 1312; - const size_t MLDSA44_SIGNATURE_LENGTH = 2420; - const size_t MLDSA44_PRIVATE_KEY_LENGTH = 2560; - class MLDSA44Verifier: public Verifier - { - public: - - MLDSA44Verifier (); - void SetPublicKey (const uint8_t * signingKey); - ~MLDSA44Verifier (); - - bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const; - - size_t GetPublicKeyLen () const { return MLDSA44_PUBLIC_KEY_LENGTH; }; - size_t GetSignatureLen () const { return MLDSA44_SIGNATURE_LENGTH; }; - size_t GetPrivateKeyLen () const { return MLDSA44_PRIVATE_KEY_LENGTH; }; - - private: - - EVP_PKEY * m_Pkey; - }; - - class MLDSA44Signer: public Signer - { - public: - - MLDSA44Signer (const uint8_t * signingPrivateKey); - ~MLDSA44Signer (); - - void Sign (const uint8_t * buf, int len, uint8_t * signature) const; - - private: - - EVP_PKEY * m_Pkey; - }; - - inline void CreateMLDSA44RandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey) - { - EVP_PKEY * pkey = EVP_PKEY_Q_keygen (NULL, NULL, "ML-DSA-44"); - size_t len = MLDSA44_PUBLIC_KEY_LENGTH; - EVP_PKEY_get_octet_string_param (pkey, OSSL_PKEY_PARAM_PUB_KEY, signingPublicKey, MLDSA44_PUBLIC_KEY_LENGTH, &len); - len = MLDSA44_PRIVATE_KEY_LENGTH; - EVP_PKEY_get_octet_string_param (pkey, OSSL_PKEY_PARAM_PRIV_KEY, signingPrivateKey, MLDSA44_PRIVATE_KEY_LENGTH, &len); - EVP_PKEY_free (pkey); - } -#endif } } #endif + diff --git a/libi2pd/Siphash.h b/libi2pd/Siphash.h deleted file mode 100644 index 78b22b90..00000000 --- a/libi2pd/Siphash.h +++ /dev/null @@ -1,155 +0,0 @@ -/** - * 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 - -#include -#include "Crypto.h" - -#if !OPENSSL_SIPHASH -namespace i2p -{ -namespace crypto -{ - 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 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 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; - - if(hashsz == 16) v1 ^= 0xee; - - 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; - } - - 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; - - - if(hashsz == 16) - v2 ^= 0xee; - else - v2 ^= 0xff; - - for(i = 0; i < siphash::drounds; ++i) - siphash::round(v0, v1, v2, v3); - - b = v0 ^ v1 ^ v2 ^ v3; - - siphash::u64to8le(b, h); - - if(hashsz == 8) return; - - v1 ^= 0xdd; - - for (i = 0; i < siphash::drounds; ++i) - siphash::round(v0, v1, v2, v3); - - b = v0 ^ v1 ^ v2 ^ v3; - siphash::u64to8le(b, h + 8); - } -} -} -#endif - -#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 25608cbc..91acc9d0 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)); + } } } } @@ -316,15 +217,59 @@ namespace stream void Stream::ProcessPacket (Packet * packet) { + // process flags uint32_t receivedSeqn = packet->GetSeqn (); uint16_t flags = packet->GetFlags (); LogPrint (eLogDebug, "Streaming: Process seqn=", receivedSeqn, ", flags=", flags); - if (!ProcessOptions (flags, packet)) + const uint8_t * optionData = packet->GetOptionData (); + + if (flags & PACKET_FLAG_DELAY_REQUESTED) + optionData += 2; + + if (flags & PACKET_FLAG_FROM_INCLUDED) { - m_LocalDestination.DeletePacket (packet); - Terminate (); - return; + m_RemoteIdentity = std::make_shared(optionData, packet->GetOptionSize ()); + if (m_RemoteIdentity->IsRSA ()) + { + LogPrint (eLogInfo, "Streaming: Incoming stream from RSA destination ", m_RemoteIdentity->GetIdentHash ().ToBase64 (), " Discarded"); + m_LocalDestination.DeletePacket (packet); + Terminate (); + return; + } + optionData += m_RemoteIdentity->GetFullLen (); + if (!m_RemoteLeaseSet) + LogPrint (eLogDebug, "Streaming: Incoming stream from ", m_RemoteIdentity->GetIdentHash ().ToBase64 (), ", sSID=", m_SendStreamID, ", rSID=", m_RecvStreamID); + } + + if (flags & PACKET_FLAG_MAX_PACKET_SIZE_INCLUDED) + { + uint16_t maxPacketSize = bufbe16toh (optionData); + LogPrint (eLogDebug, "Streaming: Max packet size ", maxPacketSize); + optionData += 2; + } + + if (flags & PACKET_FLAG_SIGNATURE_INCLUDED) + { + uint8_t signature[256]; + auto signatureLen = m_RemoteIdentity->GetSignatureLen (); + if(signatureLen <= sizeof(signature)) + { + memcpy (signature, optionData, signatureLen); + memset (const_cast(optionData), 0, signatureLen); + if (!m_RemoteIdentity->Verify (packet->GetBuffer (), packet->GetLength (), signature)) + { + 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"); + } } packet->offset = packet->GetPayload () - packet->buf; @@ -353,182 +298,16 @@ 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; - 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) - { - if (m_RemoteLeaseSet) m_RemoteIdentity = m_RemoteLeaseSet->GetIdentity (); - if (!m_RemoteIdentity) - m_RemoteIdentity = std::make_shared(optionData, optionSize); - if (m_RemoteIdentity->IsRSA ()) - { - LogPrint (eLogInfo, "Streaming: Incoming stream from RSA destination ", m_RemoteIdentity->GetIdentHash ().ToBase64 (), " Discarded"); - return false; - } - optionData += m_RemoteIdentity->GetFullLen (); - if (!m_RemoteLeaseSet) - LogPrint (eLogDebug, "Streaming: Incoming stream from ", m_RemoteIdentity->GetIdentHash ().ToBase64 (), ", sSID=", m_SendStreamID, ", rSID=", m_RecvStreamID); - } - - if (flags & PACKET_FLAG_MAX_PACKET_SIZE_INCLUDED) - { - uint16_t maxPacketSize = bufbe16toh (optionData); - LogPrint (eLogDebug, "Streaming: Max packet size ", maxPacketSize); - optionData += 2; - } - - if (flags & PACKET_FLAG_OFFLINE_SIGNATURE) - { - if (!m_RemoteIdentity) - { - LogPrint (eLogInfo, "Streaming: offline signature without identity"); - return false; - } - // if we have it in LeaseSet already we don't need to parse it again - if (m_RemoteLeaseSet) m_TransientVerifier = m_RemoteLeaseSet->GetTransientVerifier (); - if (m_TransientVerifier) - { - // skip option data - optionData += 6; // timestamp and key type - optionData += m_TransientVerifier->GetPublicKeyLen (); // public key - optionData += m_RemoteIdentity->GetSignatureLen (); // signature - } - else - { - // transient key - size_t offset = 0; - m_TransientVerifier = i2p::data::ProcessOfflineSignature (m_RemoteIdentity, optionData, optionSize - (optionData - packet->GetOptionData ()), offset); - optionData += offset; - if (!m_TransientVerifier) - { - LogPrint (eLogError, "Streaming: offline signature failed"); - return false; - } - } - } - - if (flags & PACKET_FLAG_SIGNATURE_INCLUDED) - { - bool verified = false; - auto signatureLen = m_TransientVerifier ? m_TransientVerifier->GetSignatureLen () : m_RemoteIdentity->GetSignatureLen (); - if (signatureLen > packet->GetLength ()) - { - LogPrint (eLogError, "Streaming: Signature too big, ", signatureLen, " bytes"); - return false; - } - if(signatureLen <= 256) - { - // standard - uint8_t signature[256]; - memcpy (signature, optionData, signatureLen); - memset (const_cast(optionData), 0, signatureLen); - verified = m_TransientVerifier ? - m_TransientVerifier->Verify (packet->GetBuffer (), packet->GetLength (), signature) : - m_RemoteIdentity->Verify (packet->GetBuffer (), packet->GetLength (), signature); - if (verified) - memcpy (const_cast(optionData), signature, signatureLen); - } - else - { - // post quantum - std::vector signature(signatureLen); - memcpy (signature.data (), optionData, signatureLen); - memset (const_cast(optionData), 0, signatureLen); - verified = m_TransientVerifier ? - m_TransientVerifier->Verify (packet->GetBuffer (), packet->GetLength (), signature.data ()) : - m_RemoteIdentity->Verify (packet->GetBuffer (), packet->GetLength (), signature.data ()); - } - if (verified) - optionData += signatureLen; - else - { - LogPrint (eLogError, "Streaming: Signature verification failed, sSID=", m_SendStreamID, ", rSID=", m_RecvStreamID); - return false; - } - } - if (immediateAckRequested) - SendQuickAck (); - return true; - } - - 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); - } - void Stream::ProcessAck (Packet * packet) { 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 +320,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 +331,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_SentPackets.empty ())) - 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 +376,97 @@ 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; + htobe16buf (packet + size, flags); + size += 2; // flags + size_t identityLen = m_LocalDestination.GetOwner ()->GetIdentity ()->GetFullLen (); + size_t signatureLen = m_LocalDestination.GetOwner ()->GetIdentity ()->GetSignatureLen (); + htobe16buf (packet + size, identityLen + signatureLen + 2); // identity + signature + packet size + 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 + uint8_t * signature = packet + size; // set it later + memset (signature, 0, signatureLen); // zeroes for now + size += signatureLen; // signature + 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 +476,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 +493,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 +516,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 +524,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 +548,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); // nof 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); @@ -1069,7 +578,9 @@ namespace stream if (m_SentPackets.empty () && m_SendBuffer.IsEmpty ()) // nothing to send { m_Status = eStreamStatusClosed; - SendClose(); + // close could be called from another thread so do SendClose from the destination thread + // this is so m_LocalDestination.NewPacket () does not trigger a race condition + m_Service.post(std::bind(&Stream::SendClose, shared_from_this())); } break; case eStreamStatusClosed: @@ -1077,7 +588,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 +603,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 +619,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 +651,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 +666,6 @@ namespace stream { if (!m_RemoteLeaseSet) { - CancelRemoteLeaseChange (); UpdateCurrentRemoteLease (); if (!m_RemoteLeaseSet) { @@ -1165,16 +673,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 +684,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 +712,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 +725,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 +746,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 +831,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,40 +843,13 @@ 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 (); - } - } - else - { - // LeaseSet updated - m_RemoteLeaseSet = remoteLeaseSet; - m_RemoteIdentity = m_RemoteLeaseSet->GetIdentity (); - m_TransientVerifier = m_RemoteLeaseSet->GetTransientVerifier (); + LogPrint (eLogWarning, "Streaming: LeaseSet ", m_RemoteIdentity->GetIdentHash ().ToBase64 (), " not found"); + m_LocalDestination.GetOwner ()->RequestDestination (m_RemoteIdentity->GetIdentHash ()); // try to request for a next attempt } } if (m_RemoteLeaseSet) @@ -1613,12 +860,7 @@ namespace stream if (leases.empty ()) { expired = false; - // time to request - if (m_RemoteLeaseSet->IsPublishedEncrypted ()) - m_LocalDestination.GetOwner ()->RequestDestinationWithEncryptedLeaseSet ( - std::make_shared(m_RemoteIdentity)); - else - m_LocalDestination.GetOwner ()->RequestDestination (m_RemoteIdentity->GetIdentHash ()); + m_LocalDestination.GetOwner ()->RequestDestination (m_RemoteIdentity->GetIdentHash ()); // time to request leases = m_RemoteLeaseSet->GetNonExpiredLeases (true); // then with threshold } if (!leases.empty ()) @@ -1636,15 +878,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 +898,16 @@ 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 ()), + m_ConnTrackTimer(m_Owner->GetService()), + m_ConnsPerMinute(DEFAULT_MAX_CONNS_PER_MIN), + m_LastBanClear(i2p::util::GetMillisecondsSinceEpoch()), + m_EnableDrop(false) { } @@ -1762,6 +923,7 @@ namespace stream void StreamingDestination::Start () { + ScheduleConnTrack(); } void StreamingDestination::Stop () @@ -1769,13 +931,14 @@ namespace stream ResetAcceptor (); m_PendingIncomingTimer.cancel (); m_PendingIncomingStreams.clear (); + m_ConnTrackTimer.cancel(); { 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; + } + { + std::unique_lock l(m_ConnsMutex); + m_Conns.clear (); } } @@ -1784,23 +947,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 +958,31 @@ 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 ()) + auto ident = incomingStream->GetRemoteIdentity(); + if(ident && m_EnableDrop) { - LogPrint (eLogWarning, "Streaming: No remote LeaseSet for incoming stream. Terminated"); - incomingStream->Terminate (); // can't send FIN anyway - return; - } + auto ih = ident->GetIdentHash(); + if(DropNewStream(ih)) + { + // drop + LogPrint(eLogWarning, "Streaming: Dropping connection, too many inbound streams from ", ih.ToBase32()); + incomingStream->Terminate(); + return; + } + } + m_LastIncomingReceiveStreamID = receiveStreamID; // handle saved packets if any { @@ -1879,13 +1020,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 +1059,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 +1076,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 +1105,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 +1128,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 +1151,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 +1169,70 @@ 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 () + void StreamingDestination::SetMaxConnsPerMinute(const uint32_t conns) { - if (m_Owner) - { - auto pool = m_Owner->GetTunnelPool (); - if (pool) - return pool->GetRng ()(); + m_EnableDrop = conns > 0; + m_ConnsPerMinute = conns; + LogPrint(eLogDebug, "Streaming: Set max conns per minute per destination to ", conns); + } + + bool StreamingDestination::DropNewStream(const i2p::data::IdentHash & ih) + { + std::lock_guard lock(m_ConnsMutex); + if (m_Banned.size() > MAX_BANNED_CONNS) return true; // overload + auto end = std::end(m_Banned); + if ( std::find(std::begin(m_Banned), end, ih) != end) return true; // already banned + auto itr = m_Conns.find(ih); + if (itr == m_Conns.end()) + m_Conns[ih] = 0; + + m_Conns[ih] += 1; + + bool ban = m_Conns[ih] >= m_ConnsPerMinute; + if (ban) + { + m_Banned.push_back(ih); + m_Conns.erase(ih); + LogPrint(eLogWarning, "Streaming: ban ", ih.ToBase32()); } - return rand (); - } + return ban; + } + + void StreamingDestination::HandleConnTrack(const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + { + { // acquire lock + std::lock_guard lock(m_ConnsMutex); + // clear conn tracking + m_Conns.clear(); + // check for ban clear + auto ts = i2p::util::GetMillisecondsSinceEpoch(); + if (ts - m_LastBanClear >= DEFAULT_BAN_INTERVAL) + { + // clear bans + m_Banned.clear(); + m_LastBanClear = ts; + } + } + // reschedule timer + ScheduleConnTrack(); + } + } + + void StreamingDestination::ScheduleConnTrack() + { + m_ConnTrackTimer.expires_from_now (boost::posix_time::seconds(60)); + m_ConnTrackTimer.async_wait ( + std::bind (&StreamingDestination::HandleConnTrack, + shared_from_this (), std::placeholders::_1)); + } } } diff --git a/libi2pd/Streaming.h b/libi2pd/Streaming.h index 570fdd1d..a114844d 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" @@ -47,51 +38,46 @@ namespace stream const uint16_t PACKET_FLAG_PROFILE_INTERACTIVE = 0x0100; const uint16_t PACKET_FLAG_ECHO = 0x0200; const uint16_t PACKET_FLAG_NO_ACK = 0x0400; - 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 + + /** i2cp option for limiting inbound stremaing connections */ + const char I2CP_PARAM_STREAMING_MAX_CONNS_PER_MIN[] = "maxconns"; + /** default maximum connections attempts per minute per destination */ + const uint32_t DEFAULT_MAX_CONNS_PER_MIN = 600; + + /** + * max banned destinations per local destination + * TODO: make configurable + */ + const uint16_t MAX_BANNED_CONNS = 9999; + /** + * length of a ban in ms + * TODO: make configurable + */ + const uint64_t DEFAULT_BAN_INTERVAL = 60 * 60 * 1000; + 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 +85,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 +93,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 +116,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 +133,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 +151,7 @@ namespace stream eStreamStatusOpen, eStreamStatusReset, eStreamStatusClosing, - eStreamStatusClosed, - eStreamStatusTerminated + eStreamStatusClosed }; class StreamingDestination; @@ -181,9 +159,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,25 +170,17 @@ 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())); }; - - /** only call close from destination thread, use Stream::AsyncClose for other threads */ void Close (); void Cancel () { m_ReceiveTimer.cancel (); }; @@ -222,7 +192,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: @@ -237,77 +208,43 @@ namespace stream void SavePacket (Packet * packet); void ProcessPacket (Packet * packet); - bool ProcessOptions (uint16_t flags, Packet * packet); void ProcessAck (Packet * packet); size_t ConcatenatePackets (uint8_t * buf, size_t len); 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,59 +253,71 @@ 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); + + /** set max connections per minute per destination */ + void SetMaxConnsPerMinute(const uint32_t conns); 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); + /** handle cleaning up connection tracking for ratelimits */ + void HandleConnTrack(const boost::system::error_code& ecode); + + bool DropNewStream(const i2p::data::IdentHash & ident); + + void ScheduleConnTrack(); + private: std::shared_ptr m_Owner; 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 + + std::mutex m_ConnsMutex; + /** how many connections per minute did each identity have */ + std::map m_Conns; + boost::asio::deadline_timer m_ConnTrackTimer; + uint32_t m_ConnsPerMinute; + /** banned identities */ + std::vector m_Banned; + uint64_t m_LastBanClear; i2p::util::MemoryPool m_PacketsPool; - i2p::util::MemoryPool > m_I2NPMsgsPool; - uint64_t m_LastCleanupTime; // in seconds - + bool m_EnableDrop; + public: i2p::data::GzipInflator m_Inflator; @@ -384,7 +333,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 +342,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 +373,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..9e9b4e63 100644 --- a/libi2pd/Timestamp.cpp +++ b/libi2pd/Timestamp.cpp @@ -1,27 +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 -*/ - -#include -#include #include #include -#include -#include #include -#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 @@ -31,70 +15,19 @@ namespace i2p { 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 (); - } - - static uint32_t GetLocalHoursSinceEpoch () - { - 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) + void SyncTimeWithNTP (const std::string& address) { - LogPrint (eLogInfo, "Timestamp: NTP request to ", address); - boost::asio::io_context service; + 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 @@ -115,165 +48,19 @@ namespace util } catch (std::exception& e) { - LogPrint (eLogError, "Timestamp: NTP error: ", e.what ()); + LogPrint (eLogError, "NTP error: ", e.what ()); } if (len >= 8) { - auto ourTs = GetLocalSecondsSinceEpoch (); + auto ourTs = GetSecondsSinceEpoch (); uint32_t ts = bufbe32toh (buf + 32); if (ts > 2208988800U) ts -= 2208988800U; // 1/1/1970 from 1/1/1900 g_TimeOffset = ts - ourTs; - LogPrint (eLogInfo, "Timestamp: ", address, " time offset from system time is ", g_TimeOffset, " seconds"); + LogPrint (eLogInfo, address, " time offset from system time is ", g_TimeOffset, " seconds"); } } - else - LogPrint (eLogError, "Timestamp: Couldn't open UDP socket"); } - else - LogPrint (eLogError, "Timestamp: Couldn't resolve address ", address); - } - - NTPTimeSync::NTPTimeSync (): m_IsRunning (false), m_Timer (m_Service) - { - i2p::config::GetOption("nettime.ntpsyncinterval", m_SyncInterval); - std::string ntpservers; i2p::config::GetOption("nettime.ntpservers", ntpservers); - boost::split (m_NTPServersList, ntpservers, boost::is_any_of(","), boost::token_compress_on); - } - - NTPTimeSync::~NTPTimeSync () - { - Stop (); - } - - void NTPTimeSync::Start() - { - if (m_NTPServersList.size () > 0) - { - m_IsRunning = true; - LogPrint(eLogInfo, "Timestamp: NTP time sync starting"); - boost::asio::post (m_Service, std::bind (&NTPTimeSync::Sync, this)); - m_Thread.reset (new std::thread (std::bind (&NTPTimeSync::Run, this))); - } - else - LogPrint (eLogWarning, "Timestamp: No NTP server found"); - } - - void NTPTimeSync::Stop () - { - if (m_IsRunning) - { - LogPrint(eLogInfo, "Timestamp: NTP time sync stopping"); - m_IsRunning = false; - m_Timer.cancel (); - m_Service.stop (); - if (m_Thread) - { - m_Thread->join (); - m_Thread.reset (nullptr); - } - } - } - - void NTPTimeSync::Run () - { - i2p::util::SetThreadName("Timesync"); - - while (m_IsRunning) - { - try - { - m_Service.run (); - } - catch (std::exception& ex) - { - LogPrint (eLogError, "Timestamp: NTP time sync exception: ", ex.what ()); - } - } - } - - void NTPTimeSync::Sync () - { - if (m_NTPServersList.size () > 0) - SyncTimeWithNTP (m_NTPServersList[rand () % m_NTPServersList.size ()]); - else - m_IsRunning = false; - - if (m_IsRunning) - { - m_Timer.expires_from_now (boost::posix_time::hours (m_SyncInterval)); - m_Timer.async_wait ([this](const boost::system::error_code& ecode) - { - if (ecode != boost::asio::error::operation_aborted) - Sync (); - }); - } - } - - uint64_t GetMillisecondsSinceEpoch () - { - 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() - { - return std::chrono::duration_cast( - std::chrono::steady_clock::now().time_since_epoch()).count(); - } - - uint64_t GetMonotonicMilliseconds() - { - return std::chrono::duration_cast( - std::chrono::steady_clock::now().time_since_epoch()).count(); - } - - uint64_t GetMonotonicSeconds () - { - return std::chrono::duration_cast( - std::chrono::steady_clock::now().time_since_epoch()).count(); - } - - void GetCurrentDate (char * date) - { - GetDateString (GetSecondsSinceEpoch (), date); - } - - void GetNextDayDate (char * date) - { - GetDateString (GetSecondsSinceEpoch () + 24*60*60, date); - } - - void GetDateString (uint64_t timestamp, char * date) - { - using clock = std::chrono::system_clock; - auto t = clock::to_time_t (clock::time_point (std::chrono::seconds(timestamp))); - struct tm tm; -#ifdef _WIN32 - gmtime_s(&tm, &t); - sprintf_s(date, 9, "%04i%02i%02i", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday); -#else - gmtime_r(&t, &tm); - 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..cddc6518 100644 --- a/libi2pd/Timestamp.h +++ b/libi2pd/Timestamp.h @@ -1,63 +1,32 @@ -/* -* 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__ #include -#include -#include -#include -#include +#include namespace i2p { namespace util { - uint64_t GetMillisecondsSinceEpoch (); - uint64_t GetSecondsSinceEpoch (); - uint32_t GetMinutesSinceEpoch (); - uint32_t GetHoursSinceEpoch (); - - uint64_t GetMonotonicMicroseconds (); - uint64_t GetMonotonicMilliseconds (); - uint64_t GetMonotonicSeconds (); - - void GetCurrentDate (char * date); // returns UTC date as YYYYMMDD string, 9 bytes - void GetNextDayDate (char * date); // returns next UTC day as YYYYMMDD string, 9 bytes - void GetDateString (uint64_t timestamp, char * date); // timestamp is seconds since epoch, returns date as YYYYMMDD string, 9 bytes - void AdjustTimeOffset (int64_t offset); // in seconds from current - - class NTPTimeSync + inline uint64_t GetMillisecondsSinceEpoch () { - public: + return std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()).count (); + } - NTPTimeSync (); - ~NTPTimeSync (); + inline uint32_t GetHoursSinceEpoch () + { + return std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()).count (); + } - void Start (); - void Stop (); - - private: - - void Run (); - void Sync (); - - private: - - bool m_IsRunning; - std::unique_ptr m_Thread; - boost::asio::io_context m_Service; - boost::asio::deadline_timer m_Timer; - int m_SyncInterval; - std::vector m_NTPServersList; - }; + inline uint64_t GetSecondsSinceEpoch () + { + return std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()).count (); + } } } #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..62bed352 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,28 @@ 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 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..6dcbe56d 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,95 +17,75 @@ 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); - m_IsRunning = false; - m_Acquired.notify_one (); - } + m_IsRunning = false; + m_Acquired.notify_one (); 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 < 20) { - CreateEphemeralKeys (num); + CreateDHKeysPairs (num); total += num; } - if (total > m_QueueSize) + if (total >= 20) { - 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; + std::unique_lock l(m_AcquiredMutex); m_Acquired.wait (l); // wait for element gets acquired } } } - 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 +95,33 @@ 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_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,198 +132,136 @@ 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; + i2p::config::GetOption("limits.ntcpsoft", softLimit); + i2p::config::GetOption("limits.ntcphard", hardLimit); + 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(); + 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 acceptors + auto& addresses = context.GetRouterInfo ().GetAddresses (); + for (const auto& address : addresses) { - m_SSU2Server = new SSU2Server (); - std::string ssu2proxy; i2p::config::GetOption("ssu2.proxy", ssu2proxy); - if (!ssu2proxy.empty()) + if (!address) continue; + if (m_NTCPServer == nullptr && enableNTCP) { - if (proxyurl.parse (ssu2proxy) && proxyurl.schema == "socks") + m_NTCPServer = new NTCPServer (); + 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) - { - m_NTCP2Server->Stop (); - delete m_NTCP2Server; - m_NTCP2Server = nullptr; - } - - m_X25519KeysPairSupplier.Stop (); + m_DHKeysPairSupplier.Stop (); m_IsRunning = false; if (m_Service) m_Service->stop (); if (m_Thread) @@ -372,13 +270,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 +282,200 @@ 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) // NTCP { - auto tr = peer->priority[peer->numAttempts]; - peer->numAttempts++; - switch (tr) + peer.numAttempts++; + auto address = peer.router->GetNTCPAddress (!context.SupportsV6 ()); + if (address && m_NTCPServer) { - case i2p::data::RouterInfo::eNTCP2V4: - case i2p::data::RouterInfo::eNTCP2V6: +#if BOOST_VERSION >= 104900 + if (!address->host.is_unspecified ()) // we have address now +#else + boost::system::error_code ecode; + address->host.to_string (ecode); + if (!ecode) +#endif { - if (!m_NTCP2Server) continue; - std::shared_ptr address = (tr == i2p::data::RouterInfo::eNTCP2V6) ? - peer->router->GetPublishedNTCP2V6Address () : peer->router->GetPublishedNTCP2V4Address (); - if (address && IsInReservedRange(address->host)) - address = nullptr; - if (address) + if (!peer.router->UsesIntroducer () && !peer.router->IsUnreachable ()) { - auto s = std::make_shared (*m_NTCP2Server, peer->router, address); - if( m_NTCP2Server->UsingProxy()) - m_NTCP2Server->ConnectWithProxy(s); - else - m_NTCP2Server->Connect (s); - return true; - } - break; - } - 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 ()) - { - if (m_SSU2Server->CreateSession (peer->router, address)) + if(!m_NTCPServer->ShouldLimit()) + { + 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_NTCPServer->Connect (address->host, address->port, s); return true; + } + else + { + LogPrint(eLogWarning, "Transports: NTCP Limit hit falling back to SSU"); + } } - break; } - case i2p::data::RouterInfo::eNTCP2V6Mesh: + else // we don't have address { - if (!m_NTCP2Server) continue; - auto address = peer->router->GetYggdrasilAddress (); - if (address) + if (address->addressString.length () > 0) // trying to resolve { - auto s = std::make_shared (*m_NTCP2Server, peer->router, address); - m_NTCP2Server->Connect (s); + if(m_NTCPServer->UsingProxy()) + { + auto s = std::make_shared (*m_NTCPServer, peer.router); + m_NTCPServer->ConnectWithProxy(address->addressString, address->port, NTCPServer::eHostname, s); + } + else + { + LogPrint (eLogDebug, "Transports: Resolving NTCP ", address->addressString); + NTCPResolve (address->addressString, ident); + } + return true; + } + } + } + else + LogPrint (eLogDebug, "Transports: NTCP address is not present for ", i2p::data::GetIdentHashAbbreviation (ident), ", trying SSU"); + } + if (peer.numAttempts == 1)// SSU + { + peer.numAttempts++; + if (m_SSUServer && peer.router->IsSSU (!context.SupportsV6 ())) + { + auto address = peer.router->GetSSUAddress (!context.SupportsV6 ()); +#if BOOST_VERSION >= 104900 + if (!address->host.is_unspecified ()) // we have address now +#else + boost::system::error_code ecode; + address->host.to_string (ecode); + if (!ecode) +#endif + { + m_SSUServer->CreateSession (peer.router, address->host, address->port); + return true; + } + else // we don't have address + { + if (address->addressString.length () > 0) // trying to resolve + { + LogPrint (eLogDebug, "Transports: Resolving SSU ", address->addressString); + SSUResolve (address->addressString, ident); return true; } - break; } - default: - LogPrint (eLogError, "Transports: Unknown transport ", (int)tr); } } - - LogPrint (eLogInfo, "Transports: No compatible addresses available"); - if (!i2p::context.IsLimitedConnectivity () && peer->router->IsReachableFrom (i2p::context.GetRouterInfo ())) - i2p::data::netdb.SetUnreachable (ident, true); // we are here because all connection attempts failed but router claimed them - peer->Done (); - std::lock_guard l(m_PeersMutex); - m_Peers.erase (ident); - return false; - } - else if (i2p::data::IsRouterBanned (ident)) - { - LogPrint (eLogWarning, "Transports: Router ", ident.ToBase64 (), " is banned. Peer dropped"); - peer->Done (); - std::lock_guard l(m_PeersMutex); + 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 +488,206 @@ 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::NTCPResolve (const std::string& addr, const i2p::data::IdentHash& ident) + { + auto resolver = std::make_shared(*m_Service); + resolver->async_resolve (boost::asio::ip::tcp::resolver::query (addr, ""), + std::bind (&Transports::HandleNTCPResolve, this, + std::placeholders::_1, std::placeholders::_2, ident, resolver)); + } + + void Transports::HandleNTCPResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it, + i2p::data::IdentHash ident, std::shared_ptr resolver) + { + auto it1 = m_Peers.find (ident); + if (it1 != m_Peers.end ()) + { + auto& peer = it1->second; + if (!ecode && peer.router) + { + while (it != boost::asio::ip::tcp::resolver::iterator()) + { + auto address = (*it).endpoint ().address (); + LogPrint (eLogDebug, "Transports: ", (*it).host_name (), " has been resolved to ", address); + if (address.is_v4 () || context.SupportsV6 ()) + { + auto addr = peer.router->GetNTCPAddress (); // TODO: take one we requested + if (addr) + { + auto s = std::make_shared (*m_NTCPServer, peer.router); + m_NTCPServer->Connect (address, addr->port, s); + return; + } + break; + } + else + LogPrint (eLogInfo, "Transports: NTCP ", address, " is not supported"); + it++; + } + } + LogPrint (eLogError, "Transports: Unable to resolve NTCP address: ", ecode.message ()); + std::unique_lock l(m_PeersMutex); + m_Peers.erase (it1); + } + } + + void Transports::SSUResolve (const std::string& addr, const i2p::data::IdentHash& ident) + { + auto resolver = std::make_shared(*m_Service); + resolver->async_resolve (boost::asio::ip::tcp::resolver::query (addr, ""), + std::bind (&Transports::HandleSSUResolve, this, + std::placeholders::_1, std::placeholders::_2, ident, resolver)); + } + + void Transports::HandleSSUResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it, + i2p::data::IdentHash ident, std::shared_ptr resolver) + { + auto it1 = m_Peers.find (ident); + if (it1 != m_Peers.end ()) + { + auto& peer = it1->second; + if (!ecode && peer.router) + { + while (it != boost::asio::ip::tcp::resolver::iterator()) + { + auto address = (*it).endpoint ().address (); + LogPrint (eLogDebug, "Transports: ", (*it).host_name (), " has been resolved to ", address); + if (address.is_v4 () || context.SupportsV6 ()) + { + auto addr = peer.router->GetSSUAddress (); // TODO: take one we requested + if (addr) + { + m_SSUServer->CreateSession (peer.router, address, addr->port); + return; + } + break; + } + else + LogPrint (eLogInfo, "Transports: SSU ", address, " is not supported"); + it++; + } + } + LogPrint (eLogError, "Transports: Unable to resolve SSU address: ", ecode.message ()); + std::unique_lock l(m_PeersMutex); + m_Peers.erase (it1); + } + } + + 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,106 +695,66 @@ 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 } if (sendDatabaseStore) - session->SendLocalRouterInfo (); + session->SendI2NPMessages ({ CreateDatabaseStoreMsg () }); 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 +763,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 +775,25 @@ 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(); + profile->Save(it->first); + } + 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 +803,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..d4410cb3 100644 --- a/libi2pd/Transports.h +++ b/libi2pd/Transports.h @@ -1,131 +1,73 @@ -/* -* 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 "NTCP2.h" +#include "NTCPSession.h" +#include "SSU.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 +75,22 @@ 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 IsBoundNTCP2() const { return m_NTCP2Server != nullptr; } + bool IsBoundNTCP() const { return m_NTCPServer != nullptr; } + bool IsBoundSSU() const { return m_SSUServer != 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 +102,87 @@ 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 NTCPResolve (const std::string& addr, const i2p::data::IdentHash& ident); + void HandleNTCPResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it, + i2p::data::IdentHash ident, std::shared_ptr resolver); + void SSUResolve (const std::string& addr, const i2p::data::IdentHash& ident); + void HandleSSUResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it, + i2p::data::IdentHash ident, std::shared_ptr resolver); + + 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; - NTCP2Server * m_NTCP2Server; + NTCPServer * m_NTCPServer; + SSUServer * m_SSUServer; 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 NTCP2Server * GetNTCP2Server () const { return m_NTCP2Server; }; - const SSU2Server * GetSSU2Server () const { return m_SSU2Server; }; + const NTCPServer * GetNTCPServer () const { return m_NTCPServer; }; + const SSUServer * GetSSUServer () const { return m_SSUServer; }; 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..e2c12b83 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 messsage 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,112 @@ 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) + { + tunnel->SetIsRecreated (); + auto pool = tunnel->GetTunnelPool (); + if (pool) + 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) + { + tunnel->SetIsRecreated (); + auto pool = tunnel->GetTunnelPool (); + if (pool) + 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 +759,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 +809,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 +825,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 +864,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 +872,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 +884,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 +936,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..38beccaa 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,45 @@ 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; }; - int GetNumHops () const { return m_Hops.size (); }; + void SetIsRecreated () { m_IsRecreated = true; }; 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 +145,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 +165,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 +188,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 +202,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 +226,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..8b7fb408 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; @@ -74,34 +59,14 @@ namespace tunnel struct TunnelCreationTimeCmp { - template - bool operator() (const std::shared_ptr & t1, const std::shared_ptr & t2) const + bool operator() (std::shared_ptr t1, std::shared_ptr t2) const { if (t1->GetCreationTime () != t2->GetCreationTime ()) return t1->GetCreationTime () > t2->GetCreationTime (); else 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..7267fc30 100644 --- a/libi2pd/TunnelConfig.h +++ b/libi2pd/TunnelConfig.h @@ -1,18 +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_CONFIG_H__ #define TUNNEL_CONFIG_H__ +#include +#include #include +#include +#include "Crypto.h" #include "Identity.h" #include "RouterContext.h" -#include "Crypto.h" +#include "Timestamp.h" namespace i2p { @@ -32,74 +28,103 @@ 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); + 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); + } - 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); + i2p::crypto::ElGamalEncrypt (ident->GetEncryptionPublicKey (), clearText, record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, ctx); + 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 +136,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 +199,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..e6bfdd84 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,19 +51,6 @@ 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) - { - CompleteCurrentTunnelDataMessage (); - CreateCurrentTunnelDataMessage (); - } - } if (fullMsgLen <= m_RemainingSize) { // message fits. First and last fragment @@ -91,6 +65,18 @@ namespace tunnel } else { + if (!messageCreated) // check if we should complete previous message + { + size_t numFollowOnFragments = fullMsgLen / TUNNEL_DATA_MAX_PAYLOAD_SIZE; + // length of bytes don'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) + { + CompleteCurrentTunnelDataMessage (); + CreateCurrentTunnelDataMessage (); + } + } if (diLen + 6 <= m_RemainingSize) { // delivery instructions fit @@ -162,7 +148,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 +171,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 +201,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..52736fa0 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,57 +66,35 @@ namespace tunnel it->SetTunnelPool (nullptr); m_OutboundTunnels.clear (); } - { - std::unique_lock l(m_TestsMutex); - m_Tests.clear (); - } - } - - bool TunnelPool::Reconfigure(int inHops, int outHops, int inQuant, int outQuant) - { - if( inHops >= 0 && outHops >= 0 && inQuant > 0 && outQuant > 0) - { - m_NumInboundHops = inHops; - m_NumOutboundHops = outHops; - m_NumInboundTunnels = inQuant; - m_NumOutboundTunnels = outQuant; - return true; - } - return false; + m_Tests.clear (); } 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 +105,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 +136,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 +180,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 +196,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 +212,8 @@ namespace tunnel } if (!tunnel) - { tunnel = GetNextOutboundTunnel (); - freshTunnel = true; - } - return std::make_pair(tunnel, freshTunnel); + return tunnel; } void TunnelPool::CreateTunnels () @@ -296,13 +224,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 +233,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 +250,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 +258,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 +269,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 +316,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 +340,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 +450,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,80 +474,47 @@ 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) - { - auto peers = tunnel->GetPeers(); - if (peers.size ()&& ValidatePeers (peers)) - config = std::make_shared(tunnel->GetPeers (), - tunnel->IsShortBuildMessage (), tunnel->GetFarEndTransports ()); - } - if (!m_NumInboundHops || config) - { - auto newTunnel = tunnels.CreateInboundTunnel (config, shared_from_this(), outboundTunnel); - if (newTunnel->IsEstablished ()) // zero hops - TunnelCreated (newTunnel); - else - newTunnel->SetRecreated (true); - } + if (m_NumInboundHops > 0) config = std::make_shared(tunnel->GetPeers ()); + auto newTunnel = tunnels.CreateInboundTunnel (config, outboundTunnel); + newTunnel->SetTunnelPool (shared_from_this()); + if (newTunnel->IsEstablished ()) // zero hops + TunnelCreated (newTunnel); } 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) @@ -801,18 +522,11 @@ namespace tunnel LogPrint (eLogDebug, "Tunnels: Re-creating destination outbound tunnel..."); std::shared_ptr config; if (m_NumOutboundHops > 0) - { - auto peers = tunnel->GetPeers(); - if (peers.size () && ValidatePeers (peers)) - config = std::make_shared(peers, inboundTunnel->GetNextTunnelID (), - inboundTunnel->GetNextIdentHash (), inboundTunnel->IsShortBuildMessage (), tunnel->GetFarEndTransports ()); - } - if (!m_NumOutboundHops || config) - { - auto newTunnel = tunnels.CreateOutboundTunnel (config, shared_from_this ()); - if (newTunnel->IsEstablished ()) // zero hops - TunnelCreated (newTunnel); - } + config = std::make_shared(tunnel->GetPeers (), inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash ()); + auto newTunnel = tunnels.CreateOutboundTunnel (config); + newTunnel->SetTunnelPool (shared_from_this ()); + if (newTunnel->IsEstablished ()) // zero hops + TunnelCreated (newTunnel); } else LogPrint (eLogDebug, "Tunnels: Can't re-create outbound tunnel, no inbound tunnels found"); @@ -821,12 +535,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 +556,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 +576,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 +587,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..07c3024e 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,68 +65,53 @@ 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 (); int GetNumInboundTunnels () const { return m_NumInboundTunnels; }; int GetNumOutboundTunnels () const { return m_NumOutboundTunnels; }; - int GetNumInboundHops() const { return m_NumInboundHops; }; - int GetNumOutboundHops() const { return m_NumOutboundHops; }; - - /** 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 +119,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..415118ff 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" @@ -36,14 +28,14 @@ namespace api i2p::fs::DetectDataDir(datadir, false); i2p::fs::Init(); - bool precomputation; i2p::config::GetOption("precomputation.elgamal", precomputation); - i2p::crypto::InitCrypto (precomputation); +#if defined(__x86_64__) + i2p::crypto::InitCrypto (false); +#else + i2p::crypto::InitCrypto (true); +#endif - 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 +52,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 +80,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 +89,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 +136,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..1395c2e6 100644 --- a/libi2pd/util.cpp +++ b/libi2pd/util.cpp @@ -1,74 +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 "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 -#include #include #include #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! -// 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(int af, const char *src, void *dst) +{ /* This function was written by Petar Korponai?. See +http://stackoverflow.com/questions/15660203/inet-pton-identifier-not-found */ struct sockaddr_storage ss; int size = sizeof (ss); char src_copy[INET6_ADDRSTRLEN + 1]; @@ -91,610 +46,262 @@ 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) - { - 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 +#ifdef WIN32 + 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; - 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) - == ERROR_BUFFER_OVERFLOW) - { - FREE(pAddresses); - pAddresses = (IP_ADAPTER_ADDRESSES*) MALLOC(outBufLen); - } + if(GetAdaptersAddresses(AF_INET, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen) + == ERROR_BUFFER_OVERFLOW) { + FREE(pAddresses); + pAddresses = (IP_ADAPTER_ADDRESSES*) MALLOC(outBufLen); + } - DWORD dwRetVal = GetAdaptersAddresses( - AF_INET, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen - ); + DWORD dwRetVal = GetAdaptersAddresses( + AF_INET, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen + ); - if (dwRetVal != NO_ERROR) - { - LogPrint(eLogError, "NetIface: GetMTU: Enclosed GetAdaptersAddresses() call has failed"); - FREE(pAddresses); - return fallback; - } + if(dwRetVal != NO_ERROR) { + LogPrint(eLogError, "NetIface: GetMTU(): enclosed GetAdaptersAddresses() call has failed"); + FREE(pAddresses); + return fallback; + } - pCurrAddresses = pAddresses; - while (pCurrAddresses) - { - pUnicast = pCurrAddresses->FirstUnicastAddress; - if (pUnicast == nullptr) - LogPrint(eLogError, "NetIface: GetMTU: Not a unicast IPv4 address, this is not supported"); + pCurrAddresses = pAddresses; + while(pCurrAddresses) { + PIP_ADAPTER_UNICAST_ADDRESS firstUnicastAddress = pCurrAddresses->FirstUnicastAddress; - while (pUnicast != nullptr) - { - LPSOCKADDR lpAddr = pUnicast->Address.lpSockaddr; - sockaddr_in* localInterfaceAddress = (sockaddr_in*) lpAddr; - if (localInterfaceAddress->sin_addr.S_un.S_addr == inputAddress.sin_addr.S_un.S_addr) - { - char addr[INET_ADDRSTRLEN]; - inetntop(AF_INET, &(((struct sockaddr_in *)localInterfaceAddress)->sin_addr), addr, INET_ADDRSTRLEN); + 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) { + auto result = pAddresses->Mtu; + FREE(pAddresses); + return result; + } + pUnicast = pUnicast->Next; + } + pCurrAddresses = pCurrAddresses->Next; + } - auto result = pCurrAddresses->Mtu; - FREE(pAddresses); - pAddresses = nullptr; - LogPrint(eLogInfo, "NetIface: GetMTU: Using ", result, " bytes for IPv4 address ", addr); - return result; - } - pUnicast = pUnicast->Next; - } - pCurrAddresses = pCurrAddresses->Next; - } + LogPrint(eLogError, "NetIface: GetMTU(): no usable unicast ipv4 addresses found"); + FREE(pAddresses); + return fallback; + } - LogPrint(eLogError, "NetIface: GetMTU: No usable unicast IPv4 addresses found"); - FREE(pAddresses); - return fallback; - } + int GetMTUWindowsIpv6(sockaddr_in6 inputAddress, int fallback) + { + ULONG outBufLen = 0; + PIP_ADAPTER_ADDRESSES pAddresses = nullptr; + PIP_ADAPTER_ADDRESSES pCurrAddresses = nullptr; + PIP_ADAPTER_UNICAST_ADDRESS pUnicast = nullptr; - 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 + if(GetAdaptersAddresses(AF_INET6, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen) + == ERROR_BUFFER_OVERFLOW) { + FREE(pAddresses); + pAddresses = (IP_ADAPTER_ADDRESSES*) MALLOC(outBufLen); + } - ULONG outBufLen = 0; - PIP_ADAPTER_ADDRESSES pAddresses = nullptr; - PIP_ADAPTER_ADDRESSES pCurrAddresses = nullptr; - PIP_ADAPTER_UNICAST_ADDRESS pUnicast = nullptr; + DWORD dwRetVal = 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); - pAddresses = (IP_ADAPTER_ADDRESSES*) MALLOC(outBufLen); - } + if(dwRetVal != NO_ERROR) { + LogPrint(eLogError, "NetIface: GetMTU(): enclosed GetAdaptersAddresses() call has failed"); + FREE(pAddresses); + return fallback; + } - DWORD dwRetVal = GetAdaptersAddresses( - AF_INET6, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen - ); + bool found_address = false; + pCurrAddresses = pAddresses; + while(pCurrAddresses) { + PIP_ADAPTER_UNICAST_ADDRESS firstUnicastAddress = pCurrAddresses->FirstUnicastAddress; + pUnicast = pCurrAddresses->FirstUnicastAddress; + if(pUnicast == nullptr) { + LogPrint(eLogError, "NetIface: GetMTU(): not a unicast ipv6 address, this is not supported"); + } + for(int i = 0; pUnicast != nullptr; ++i) { + LPSOCKADDR lpAddr = pUnicast->Address.lpSockaddr; + sockaddr_in6 *localInterfaceAddress = (sockaddr_in6*) lpAddr; - if (dwRetVal != NO_ERROR) - { - LogPrint(eLogError, "NetIface: GetMTU: Enclosed GetAdaptersAddresses() call has failed"); - FREE(pAddresses); - return fallback; - } + for (int j = 0; j != 8; ++j) { + if (localInterfaceAddress->sin6_addr.u.Word[j] != inputAddress.sin6_addr.u.Word[j]) { + break; + } else { + found_address = true; + } + } if (found_address) { + auto result = pAddresses->Mtu; + FREE(pAddresses); + pAddresses = nullptr; + return result; + } + pUnicast = pUnicast->Next; + } - bool found_address = false; - pCurrAddresses = pAddresses; - while (pCurrAddresses) - { - pUnicast = pCurrAddresses->FirstUnicastAddress; - if (pUnicast == nullptr) - LogPrint(eLogError, "NetIface: GetMTU: Not a unicast IPv6 address, this is not supported"); + pCurrAddresses = pCurrAddresses->Next; + } - while (pUnicast != nullptr) - { - LPSOCKADDR lpAddr = pUnicast->Address.lpSockaddr; - sockaddr_in6 *localInterfaceAddress = (sockaddr_in6*) lpAddr; + LogPrint(eLogError, "NetIface: GetMTU(): no usable unicast ipv6 addresses found"); + FREE(pAddresses); + return fallback; + } - for (int j = 0; j != 8; ++j) - { - if (localInterfaceAddress->sin6_addr.u.Word[j] != inputAddress.sin6_addr.u.Word[j]) - break; - else - found_address = true; - } - - if (found_address) - { - char addr[INET6_ADDRSTRLEN]; - inetntop(AF_INET6, &(((struct sockaddr_in6 *)localInterfaceAddress)->sin6_addr), addr, INET6_ADDRSTRLEN); - - auto result = pCurrAddresses->Mtu; - FREE(pAddresses); - pAddresses = nullptr; - LogPrint(eLogInfo, "NetIface: GetMTU: Using ", result, " bytes for IPv6 address ", addr); - return result; - } - pUnicast = pUnicast->Next; - } - - pCurrAddresses = pCurrAddresses->Next; - } - - 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(); - wstring localAddressUniversal(localAddress_temporary.begin(), localAddress_temporary.end()); + string localAddress_temporary = localAddress.to_string(); + wstring localAddressUniversal(localAddress_temporary.begin(), localAddress_temporary.end()); #else - std::string localAddressUniversal = localAddress.to_string(); + 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(localAddress.is_v4()) { + sockaddr_in inputAddress; + inet_pton(AF_INET, localAddressUniversal.c_str(), &(inputAddress.sin_addr)); + return GetMTUWindowsIpv4(inputAddress, fallback); + } else if(localAddress.is_v6()) { + sockaddr_in6 inputAddress; + inet_pton(AF_INET6, localAddressUniversal.c_str(), &(inputAddress.sin6_addr)); + return GetMTUWindowsIpv6(inputAddress, fallback); + } else { + LogPrint(eLogError, "NetIface: GetMTU(): address family is not supported"); + return fallback; + } - if (localAddress.is_v4()) - { - sockaddr_in inputAddress; - inetpton(AF_INET, localAddressUniversal.c_str(), &(inputAddress.sin_addr)); - return GetMTUWindowsIpv4(inputAddress, fallback); - } - else if (localAddress.is_v6()) - { - sockaddr_in6 inputAddress; - inetpton(AF_INET6, localAddressUniversal.c_str(), &(inputAddress.sin6_addr)); - return GetMTUWindowsIpv6(inputAddress, fallback); - } - 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) - { - ifaddrs* ifaddr, *ifa = nullptr; - if (getifaddrs(&ifaddr) == -1) + int GetMTUUnix(const boost::asio::ip::address& localAddress, int fallback) + { + ifaddrs* ifaddr, *ifa = nullptr; + if(getifaddrs(&ifaddr) == -1) { - LogPrint(eLogError, "NetIface: Can't call getifaddrs(): ", strerror(errno)); - return fallback; - } + LogPrint(eLogError, "NetIface: Can't call getifaddrs(): ", strerror(errno)); + return fallback; + } - int family = 0; - // look for interface matching local address - for (ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) + int family = 0; + // look for interface matching local address + for(ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) { - if (!ifa->ifa_addr) - continue; + if(!ifa->ifa_addr) + continue; - family = ifa->ifa_addr->sa_family; - if (family == AF_INET && localAddress.is_v4()) + family = ifa->ifa_addr->sa_family; + 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)) - break; // address matches - } - else if (family == AF_INET6 && localAddress.is_v6()) + sockaddr_in* sa = (sockaddr_in*) ifa->ifa_addr; + if(!memcmp(&sa->sin_addr, localAddress.to_v4().to_bytes().data(), 4)) + break; // address matches + } + 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)) - break; // address matches - } - } - int mtu = fallback; - if (ifa && family) + sockaddr_in6* sa = (sockaddr_in6*) ifa->ifa_addr; + if(!memcmp(&sa->sin6_addr, localAddress.to_v6().to_bytes().data(), 16)) + break; // address matches + } + } + int mtu = fallback; + if(ifa && family) { // interface found? - int fd = socket(family, SOCK_DGRAM, 0); - if (fd > 0) + int fd = socket(family, SOCK_DGRAM, 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) - mtu = ifr.ifr_mtu; // MTU - else - LogPrint (eLogError, "NetIface: Failed to run ioctl: ", strerror(errno)); - close(fd); - } + ifreq ifr; + 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)); + close(fd); + } else - LogPrint(eLogError, "NetIface: Failed to create datagram socket"); - } + LogPrint(eLogError, "NetIface: Failed to create datagram socket"); + } else - LogPrint(eLogWarning, "NetIface: Interface for local address", localAddress.to_string(), " not found"); - freeifaddrs(ifaddr); + LogPrint(eLogWarning, "NetIface: interface for local address", localAddress.to_string(), " not found"); + freeifaddrs(ifaddr); - return mtu; - } -#endif // _WIN32 + return mtu; + } +#endif // WIN32 - int GetMTU (const boost::asio::ip::address& localAddress) - { - int fallback = localAddress.is_v6 () ? 1280 : 620; // fallback MTU + int GetMTU(const boost::asio::ip::address& localAddress) + { + const int fallback = 576; // fallback MTU -#ifdef _WIN32 - return GetMTUWindows(localAddress, fallback); +#ifdef WIN32 + return GetMTUWindows(localAddress, fallback); #else - return GetMTUUnix(localAddress, fallback); + return GetMTUUnix(localAddress, fallback); #endif - return fallback; - } + 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"); - else - return boost::asio::ip::make_address("127.0.0.1"); +#ifdef WIN32 + LogPrint(eLogError, "NetIface: cannot get address by interface name, not implemented on WIN32"); + 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) - { - fallback = "::1"; - LogPrint(eLogWarning, "NetIface: Cannot find IPv6 address for interface ", ifname); + if(ipv6) { + fallback = "::"; + 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..e31e7b3f 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,27 +5,24 @@ #include #include #include -#include #include #include #ifdef ANDROID -#ifndef __clang__ #include namespace std { - template - std::string to_string(T value) - { - return boost::lexical_cast(value); - } - - inline int stoi(const std::string& str) - { - return boost::lexical_cast(str); - } +template +std::string to_string(T value) +{ + return boost::lexical_cast(value); +} + +inline int stoi(const std::string& str) +{ + return boost::lexical_cast(str); +} } -#endif #endif namespace i2p @@ -51,13 +40,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 +82,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 +107,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 +115,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..36e7eb6e 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 18 #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 33 #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..f29ecee3 100644 --- a/libi2pd_client/AddressBook.cpp +++ b/libi2pd_client/AddressBook.cpp @@ -1,15 +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 #include #include -#include +#include #include #include #include @@ -17,7 +9,6 @@ #include #include "Base.h" #include "util.h" -#include "Timestamp.h" #include "Identity.h" #include "FS.h" #include "Log.h" @@ -27,14 +18,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 +25,28 @@ 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; - 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; + + public: + AddressBookFilesystemStorage (): storage("addressbook", "b", "", "b32") {}; + 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); + + private: + + int LoadFromFile (const std::string& filename, std::map& addresses); // returns -1 if can't open file, otherwise number of records + }; bool AddressBookFilesystemStorage::Init() @@ -96,92 +67,50 @@ namespace client return false; } - std::shared_ptr AddressBookFilesystemStorage::GetAddress (const i2p::data::IdentHash& ident) + std::shared_ptr AddressBookFilesystemStorage::GetAddress (const i2p::data::IdentHash& ident) const { - auto ts = i2p::util::GetMonotonicSeconds (); - { - std::lock_guard l(m_FullAddressCacheMutex); - auto it = m_FullAddressCache.find (ident); - if (it != m_FullAddressCache.end ()) - { - it->second.second = ts; - return std::make_shared(it->second.first.data (), it->second.first.size ()); - } - } - - if (!m_IsPersist) - { - LogPrint(eLogDebug, "Addressbook: Persistence is disabled"); - 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; + std::string path = storage.Path( address->GetIdentHash().ToBase32() ); + std::ofstream f (path, std::ofstream::binary | std::ofstream::out); + if (!f.is_open ()) { + LogPrint (eLogError, "Addressbook: can't open file ", path); + return; } - if (m_IsPersist && !buf.empty ()) - { - std::string path = storage.Path(address->GetIdentHash().ToBase32()); - std::ofstream f (path, std::ofstream::binary | std::ofstream::out); - if (!f.is_open ()) - { - LogPrint (eLogError, "Addressbook: Can't open file ", path); - return; - } - f.write ((const char *)buf.data (), len); - } + size_t len = address->GetFullLen (); + uint8_t * buf = new uint8_t[len]; + address->ToBuffer (buf, len); + f.write ((char *)buf, len); + delete[] buf; } void AddressBookFilesystemStorage::RemoveAddress (const i2p::data::IdentHash& ident) { - { - std::lock_guard l(m_FullAddressCacheMutex); - m_FullAddressCache.erase (ident); - } - if (!m_IsPersist) return; storage.Remove( ident.ToBase32() ); } - int AddressBookFilesystemStorage::LoadFromFile (const std::string& filename, Addresses& addresses) + int AddressBookFilesystemStorage::LoadFromFile (const std::string& filename, std::map& addresses) { int num = 0; std::ifstream f (filename, std::ifstream::in); // in text mode @@ -200,14 +129,16 @@ namespace client std::string name = s.substr(0, pos++); std::string addr = s.substr(pos); - addresses[name] = std::make_shared
(addr); + i2p::data::IdentHash ident; + ident.FromBase32 (addr); + addresses[name] = ident; num++; } } return num; } - int AddressBookFilesystemStorage::Load (Addresses& addresses) + int AddressBookFilesystemStorage::Load (std::map& addresses) { int num = LoadFromFile (indexPath, addresses); if (num < 0) @@ -215,13 +146,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 +160,26 @@ 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 << "," << it.second.ToBase32 () << std::endl; + num++; + } + LogPrint (eLogInfo, "Addressbook: ", num, " addresses saved"); return num; } @@ -309,57 +205,9 @@ namespace client return true; } - void AddressBookFilesystemStorage::ResetEtags () - { - LogPrint (eLogError, "Addressbook: Resetting eTags"); - for (fs_lib::directory_iterator it (etagsPath); it != fs_lib::directory_iterator (); ++it) - { - if (!fs_lib::is_regular_file (it->status ())) - continue; - fs_lib::remove (it->path ()); - } - } - - void AddressBookFilesystemStorage::CleanUpCache () - { - auto ts = i2p::util::GetMonotonicSeconds (); - std::lock_guard l(m_FullAddressCacheMutex); - for (auto it = m_FullAddressCache.begin (); it != m_FullAddressCache.end ();) - { - if (ts > it->second.second + ADDRESS_CACHE_EXPIRATION_TIMEOUT) - it = m_FullAddressCache.erase (it); - else - it++; - } - } - //--------------------------------------------------------------------- - - Address::Address (std::string_view b32): - addressType (eAddressInvalid) - { - if (b32.length () <= B33_ADDRESS_THRESHOLD) - { - if (identHash.FromBase32 (b32) > 0) - addressType = eAddressIndentHash; - } - else - { - blindedPublicKey = std::make_shared(b32); - if (blindedPublicKey->IsValid ()) - addressType = eAddressBlindedPublicKey; - } - } - - Address::Address (const i2p::data::IdentHash& hash) - { - addressType = eAddressIndentHash; - 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 +218,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 +237,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 is 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,103 +265,71 @@ namespace client m_Subscriptions.clear (); } - std::shared_ptr AddressBook::GetAddress (std::string_view address) + bool AddressBook::GetIdentHash (const std::string& address, i2p::data::IdentHash& ident) { auto pos = address.find(".b32.i2p"); if (pos != std::string::npos) { - auto addr = std::make_shared(address.substr (0, pos)); - return addr->IsValid () ? addr : nullptr; + Base32ToByteStream (address.c_str(), pos, ident, 32); + return true; } - else -#if __cplusplus >= 202002L // C++20 - if (address.ends_with (".i2p")) -#else - if (address.find (".i2p") != std::string::npos) -#endif + else { - if (!m_IsEnabled) return nullptr; - auto addr = FindAddress (address); - if (!addr) - LookupAddress (address); // TODO: - return addr; + pos = address.find (".i2p"); + if (pos != std::string::npos) + { + auto identHash = FindAddress (address); + if (identHash) + { + ident = *identHash; + return true; + } + else + { + LookupAddress (address); // TODO: + return false; + } + } } // if not .b32 we assume full base64 address i2p::data::IdentityEx dest; if (!dest.FromBase64 (address)) - return nullptr; - return std::make_shared(dest.GetIdentHash ()); + return false; + ident = dest.GetIdentHash (); + return true; } - std::shared_ptr AddressBook::FindAddress (std::string_view address) + const i2p::data::IdentHash * AddressBook::FindAddress (const std::string& address) { auto it = m_Addresses.find (address); if (it != m_Addresses.end ()) - return it->second; + return &it->second; return nullptr; } - bool AddressBook::RecordExists (const std::string& address, const std::string& jump) + void AddressBook::InsertAddress (const std::string& address, const std::string& base64) { - 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; + auto ident = std::make_shared(); + ident->FromBase64 (base64); + m_Storage->AddAddress (ident); + m_Addresses[address] = ident->GetIdentHash (); + LogPrint (eLogInfo, "Addressbook: added ", address," -> ", ToAddress(ident->GetIdentHash ())); } - void AddressBook::InsertAddress (const std::string& address, const std::string& jump) + void AddressBook::InsertAddress (std::shared_ptr address) { - 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); - } - else - { - // assume base64 - auto ident = std::make_shared(); - if (ident->FromBase64 (jump)) - { - if (m_Storage) m_Storage->AddAddress (ident); - m_Addresses[address] = std::make_shared
(ident->GetIdentHash ()); - LogPrint (eLogInfo, "Addressbook: Added ", address," -> ", ToAddress(ident->GetIdentHash ())); - } - else - LogPrint (eLogError, "Addressbook: Malformed address ", jump); - } + m_Storage->AddAddress (address); } - void AddressBook::InsertFullAddress (std::shared_ptr address) + std::shared_ptr AddressBook::GetAddress (const std::string& address) { - if (m_Storage) m_Storage->AddAddress (address); - } - - std::shared_ptr AddressBook::GetFullAddress (const std::string& address) - { - auto addr = GetAddress (address); - if (!addr || !addr->IsIdentHash ()) return nullptr; - return m_Storage ? m_Storage->GetAddress (addr->identHash) : nullptr; + i2p::data::IdentHash ident; + if (!GetIdentHash (address, ident)) return nullptr; + return m_Storage->GetAddress (ident); } void AddressBook::LoadHosts () { - if (!m_Storage) return; if (m_Storage->Load (m_Addresses) > 0) { m_IsLoaded = true; @@ -545,9 +343,6 @@ namespace client LoadHostsFromStream (f, false); m_IsLoaded = true; } - - // reset eTags, because we don’t know how old hosts.txt is or can't load addressbook - m_Storage->ResetEtags (); } bool AddressBook::LoadHostsFromStream (std::istream& f, bool is_update) @@ -567,61 +362,36 @@ 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; } numAddresses++; auto it = m_Addresses.find (name); - if (it != m_Addresses.end ()) // already exists ? + if (it != m_Addresses.end ()) // aleady 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 != 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); + it->second = ident->GetIdentHash (); + 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.insert (std::make_pair (name, ident->GetIdentHash ())); + m_Storage->AddAddress (ident); if (is_update) - LogPrint (eLogInfo, "Addressbook: Added new host: ", name); + LogPrint (eLogInfo, "Addressbook: added new host: ", name); } } else @@ -631,7 +401,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,60 +417,58 @@ 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) { - if (!it.second->IsIdentHash ()) continue; // skip blinded for now auto dot = it.first.find ('.'); if (dot != std::string::npos) { auto domain = it.first.substr (dot + 1); - auto it1 = m_Addresses.find (domain); // find domain in our addressbook - if (it1 != m_Addresses.end () && it1->second->IsIdentHash ()) + auto it1 = m_Addresses.find (domain); // find domain in our addressbook + if (it1 != m_Addresses.end ()) { - auto dest = context.FindLocalDestination (it1->second->identHash); + auto dest = context.FindLocalDestination (it1->second); if (dest) { // address is ours std::shared_ptr resolver; - auto it2 = m_Resolvers.find (it1->second->identHash); + auto it2 = m_Resolvers.find (it1->second); if (it2 != m_Resolvers.end ()) resolver = it2->second; // resolver exists else { // create new resolver resolver = std::make_shared(dest); - m_Resolvers.insert (std::make_pair(it1->second->identHash, resolver)); + m_Resolvers.insert (std::make_pair(it1->second, resolver)); } - resolver->AddAddress (it.first, it.second->identHash); + resolver->AddAddress (it.first, it.second); } } } @@ -717,6 +485,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 +516,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 +537,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,13 +596,13 @@ namespace client } } - void AddressBook::LookupAddress (std::string_view address) + void AddressBook::LookupAddress (const std::string& address) { - std::shared_ptr addr; + const i2p::data::IdentHash * ident = nullptr; auto dot = address.find ('.'); if (dot != std::string::npos) - addr = FindAddress (address.substr (dot + 1)); - if (!addr || !addr->IsIdentHash ()) // TODO: + ident = FindAddress (address.substr (dot + 1)); + if (!ident) { LogPrint (eLogError, "Addressbook: Can't find domain for ", address); return; @@ -855,14 +620,14 @@ namespace client std::unique_lock l(m_LookupsMutex); m_Lookups[nonce] = address; } - LogPrint (eLogDebug, "Addressbook: Lookup of ", address, " to ", addr->identHash.ToBase32 (), " nonce=", nonce); + LogPrint (eLogDebug, "Addressbook: Lookup of ", address, " to ", ident->ToBase32 (), " nonce=", nonce); size_t len = address.length () + 9; uint8_t * buf = new uint8_t[len]; memset (buf, 0, 4); htobe32buf (buf + 4, nonce); buf[8] = address.length (); - memcpy (buf + 9, address.data (), address.length ()); - datagram->SendDatagramTo (buf, len, addr->identHash, ADDRESS_RESPONSE_DATAGRAM_PORT, ADDRESS_RESOLVER_DATAGRAM_PORT); + memcpy (buf + 9, address.c_str (), address.length ()); + datagram->SendDatagramTo (buf, len, *ident, ADDRESS_RESPONSE_DATAGRAM_PORT, ADDRESS_RESOLVER_DATAGRAM_PORT); delete[] buf; } } @@ -892,44 +657,19 @@ namespace client // TODO: verify from i2p::data::IdentHash hash(buf + 8); if (!hash.IsZero ()) - m_Addresses[address] = std::make_shared
(hash); + m_Addresses[address] = hash; else LogPrint (eLogInfo, "AddressBook: Lookup response: ", address, " not found"); } } - 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 +679,85 @@ 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)) - { - LogPrint(eLogError, "Addressbook: Failed to parse url: ", m_Link); + if (!url.parse(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 (!m_Book.GetIdentHash (url.host, m_Ident)) { 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; + int numAttempts = 5; 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); + if (newDataReceived.wait_for (l, std::chrono::seconds (SUBSCRIPTION_REQUEST_TIMEOUT)) == 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 +765,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 +812,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 +850,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..19257eca 100644 --- a/libi2pd_client/AddressBook.h +++ b/libi2pd_client/AddressBook.h @@ -1,29 +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 ADDRESS_BOOK_H__ #define ADDRESS_BOOK_H__ #include #include -#include #include #include #include #include -#include #include #include #include "Base.h" #include "Identity.h" #include "Log.h" #include "Destination.h" -#include "LeaseSet.h" namespace i2p { @@ -34,49 +23,29 @@ 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; - const size_t B33_ADDRESS_THRESHOLD = 52; // characters - - struct Address - { - enum { eAddressIndentHash, eAddressBlindedPublicKey, eAddressInvalid } addressType; - i2p::data::IdentHash identHash; - std::shared_ptr blindedPublicKey; - - Address (std::string_view 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"); } class AddressBookStorage // interface for storage { 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; - virtual void ResetEtags () = 0; }; class AddressBookSubscription; @@ -84,20 +53,18 @@ namespace client class AddressBook { public: - + AddressBook (); ~AddressBook (); void Start (); void StartResolvers (); void Stop (); - std::shared_ptr GetAddress (std::string_view address); - std::shared_ptr GetFullAddress (const std::string& address); - std::shared_ptr FindAddress (std::string_view address); - void LookupAddress (std::string_view address); - void InsertAddress (const std::string& address, const std::string& jump); // for jump links - void InsertFullAddress (std::shared_ptr address); - - bool RecordExists (const std::string& address, const std::string& jump); + bool GetIdentHash (const std::string& address, i2p::data::IdentHash& ident); + std::shared_ptr GetAddress (const std::string& address); + const i2p::data::IdentHash * FindAddress (const std::string& address); + void LookupAddress (const std::string& address); + void InsertAddress (const std::string& address, const std::string& base64); // for jump service + void InsertAddress (std::shared_ptr address); 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); @@ -106,8 +73,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 +89,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 +138,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..2a18c8ba 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; @@ -98,33 +72,26 @@ namespace client if (eol != receiver->buffer && eol[-1] == '\r') eol[-1] = 0; // workaround for Transmission, it sends '\r\n' terminated address receiver->data = (uint8_t *)eol + 1; receiver->dataLen = receiver->bufferOffset - (eol - receiver->buffer + 1); - auto addr = context.GetAddressBook ().GetAddress (receiver->buffer); - if (!addr) + i2p::data::IdentHash ident; + if (!context.GetAddressBook ().GetIdentHash (receiver->buffer, ident)) { - LogPrint (eLogError, "BOB: Address ", receiver->buffer, " not found"); + LogPrint (eLogError, "BOB: address ", receiver->buffer, " not found"); return; } - if (addr->IsIdentHash ()) - { - auto leaseSet = GetLocalDestination ()->FindLeaseSet (addr->identHash); - if (leaseSet) - CreateConnection (receiver, leaseSet); - else - GetLocalDestination ()->RequestDestination (addr->identHash, - std::bind (&BOBI2PInboundTunnel::HandleDestinationRequestComplete, - this, std::placeholders::_1, receiver)); - } + auto leaseSet = GetLocalDestination ()->FindLeaseSet (ident); + if (leaseSet) + CreateConnection (receiver, leaseSet); else - GetLocalDestination ()->RequestDestinationWithEncryptedLeaseSet (addr->blindedPublicKey, - std::bind (&BOBI2PInboundTunnel::HandleDestinationRequestComplete, - this, std::placeholders::_1, receiver)); + GetLocalDestination ()->RequestDestination (ident, + std::bind (&BOBI2PInboundTunnel::HandleDestinationRequestComplete, + this, std::placeholders::_1, receiver)); } else { if (receiver->bufferOffset < BOB_COMMAND_BUFFER_SIZE) ReceiveAddress (receiver); else - LogPrint (eLogError, "BOB: Missing inbound address"); + LogPrint (eLogError, "BOB: missing inbound address"); } } } @@ -145,9 +112,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 +141,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 +164,6 @@ namespace client { if (m_OutboundTunnel) m_OutboundTunnel->Start (); if (m_InboundTunnel) m_InboundTunnel->Start (); - m_IsRunning = true; } void BOBDestination::Stop () @@ -212,7 +174,6 @@ namespace client void BOBDestination::StopTunnels () { - m_IsRunning = false; if (m_OutboundTunnel) { m_OutboundTunnel->Stop (); @@ -227,18 +188,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 +206,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 +231,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 +298,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 +314,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 +370,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 +406,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 +450,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 +489,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 +540,29 @@ namespace client void BOBCommandSession::LookupCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: lookup ", operand); - if (*operand) + i2p::data::IdentHash ident; + if (!context.GetAddressBook ().GetIdentHash (operand, ident)) + { + SendReplyError ("Address Not found"); + return; + } + auto localDestination = m_CurrentDestination ? m_CurrentDestination->GetLocalDestination () : i2p::client::context.GetSharedLocalDestination (); + auto leaseSet = localDestination->FindLeaseSet (ident); + if (leaseSet) + SendReplyOK (leaseSet->GetIdentity ()->ToBase64 ().c_str ()); + else { - auto addr = context.GetAddressBook ().GetAddress (operand); - if (!addr) - { - SendReplyError ("Address Not found"); - return; - } - auto localDestination = (m_CurrentDestination && m_CurrentDestination->IsRunning ()) ? - m_CurrentDestination->GetLocalDestination () : i2p::client::context.GetSharedLocalDestination (); - if (!localDestination) - { - SendReplyError ("No local destination"); - return; - } - if (addr->IsIdentHash ()) - { - // we might have leaseset already - auto leaseSet = localDestination->FindLeaseSet (addr->identHash); - if (leaseSet) - { - SendReplyOK (leaseSet->GetIdentity ()->ToBase64 ().c_str ()); - return; - } - } - // trying to request auto s = shared_from_this (); - auto requstCallback = [s](std::shared_ptr ls) + localDestination->RequestDestination (ident, + [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); + } + ); } - 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"); } void BOBCommandSession::ClearCommandHandler (const char * operand, size_t len) @@ -751,23 +576,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 +594,7 @@ namespace client msg += operand; *(const_cast(value)) = '='; msg += " set to "; - msg += value + 1; + msg += value; SendReplyOK (msg.c_str ()); } else @@ -793,60 +604,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 +655,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 +716,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 +747,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..fb8fff97 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 @@ -47,7 +39,7 @@ namespace client if (!m_SharedLocalDestination) CreateNewSharedLocalDestination (); - // addressbook + // addressbook m_AddressBook.Start (); // HTTP proxy @@ -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,27 +206,17 @@ namespace client if (m_HttpProxy) { m_HttpProxy->Stop (); - delete m_HttpProxy; m_HttpProxy = nullptr; - } + } ReadHttpProxy (); - + // recreate SOCKS proxy 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); + } + ReadSocksProxy (); // delete unused destinations std::unique_lock l(m_DestinationsMutex); @@ -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,38 +344,26 @@ 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 - return it->second; + if (!it->second->IsRunning ()) + { + it->second->Start (); + return it->second; + } + return nullptr; } - 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, DSA m_SharedLocalDestination->Acquire (); + m_Destinations[m_SharedLocalDestination->GetIdentity ()->GetIdentHash ()] = m_SharedLocalDestination; + m_SharedLocalDestination->Start (); } std::shared_ptr ClientContext::FindLocalDestination (const i2p::data::IdentHash& destination) const @@ -440,61 +377,20 @@ 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 + void ClientContext::ReadI2CPOptions (const Section& section, std::map& options) const { - 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 - { - 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"); - 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,75 +400,41 @@ 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 () { - int numClientTunnels = 0, numServerTunnels = 0; + boost::property_tree::ptree pt; std::string tunConf; i2p::config::GetOption("tunconf", tunConf); - if (tunConf.empty ()) - 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; - if (i2p::fs::ReadDir (tunDir, files)) - { - for (auto& it: files) - { -#if __cplusplus >= 202002L // C++20 - if (!it.ends_with (".conf")) continue; -#else - if (it.substr(it.size() - 5) != ".conf") continue; // skip files which not ends with ".conf" -#endif - LogPrint(eLogDebug, "Clients: Tunnels extra config file: ", it); - ReadTunnels (it, numClientTunnels, numServerTunnels); - } + 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 (eLogInfo, "Clients: ", numClientTunnels, " I2P client tunnels created"); - 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 { + LogPrint(eLogDebug, "FS: tunnels config file: ", tunConf); + 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 + int numClientTunnels = 0, numServerTunnels = 0; for (auto& section: pt) { std::string name = section.first; @@ -580,55 +442,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); - i2p::data::SigningKeyType sigType = section.second.get (I2P_CLIENT_TUNNEL_SIGNATURE_TYPE, i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519); + 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_ECDSA_SHA256_P256); 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,75 +483,47 @@ 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 = new I2PUDPClientTunnel(name, dest, end, localDestination, destinationPort); + if(m_ClientForwards.insert(std::make_pair(end, std::unique_ptr(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; - std::shared_ptr clientTunnel; + I2PService * clientTunnel = nullptr; if (type == I2P_TUNNELS_SECTION_TYPE_SOCKS) { // socks proxy - std::string outproxy = section.second.get("outproxy", ""); - auto tun = std::make_shared(name, address, port, !outproxy.empty(), outproxy, destinationPort, localDestination); - clientTunnel = tun; - clientEndpoint = tun->GetLocalEndpoint (); + clientTunnel = new i2p::proxy::SOCKSProxy(name, address, port, false, "", destinationPort, localDestination); + clientEndpoint = ((i2p::proxy::SOCKSProxy*)clientTunnel)->GetLocalEndpoint (); } else if (type == I2P_TUNNELS_SECTION_TYPE_HTTPPROXY) { // 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); - clientTunnel = tun; - clientEndpoint = tun->GetLocalEndpoint (); + clientTunnel = new i2p::proxy::HTTPProxy(name, address, port, outproxy, localDestination); + clientEndpoint = ((i2p::proxy::HTTPProxy*)clientTunnel)->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 + clientTunnel = new WebSocks(address, port, localDestination);; + clientEndpoint = ((WebSocks*)clientTunnel)->GetLocalEndpoint(); } else { // tcp 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); - } + clientTunnel = new I2PClientTunnel (name, dest, address, port, localDestination, destinationPort); + clientEndpoint = ((I2PClientTunnel*)clientTunnel)->GetLocalEndpoint (); } - uint32_t timeout = section.second.get(I2P_CLIENT_TUNNEL_CONNECT_TIMEOUT, 0); if(timeout) { @@ -712,7 +531,7 @@ namespace client LogPrint(eLogInfo, "Clients: I2P Client tunnel connect timeout set to ", timeout); } - auto ins = m_ClientTunnels.insert (std::make_pair (clientEndpoint, clientTunnel)); + auto ins = m_ClientTunnels.insert (std::make_pair (clientEndpoint, std::unique_ptr(clientTunnel))); if (ins.second) { clientTunnel->Start (); @@ -724,129 +543,89 @@ 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); - i2p::data::SigningKeyType sigType = section.second.get (I2P_SERVER_TUNNEL_SIGNATURE_TYPE, i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519); + 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); 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); + uint32_t maxConns = section.second.get(i2p::stream::I2CP_PARAM_STREAMING_MAX_CONNS_PER_MIN, i2p::stream::DEFAULT_MAX_CONNS_PER_MIN); + 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); + I2PUDPServerTunnel * serverTunnel = new I2PUDPServerTunnel(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), + std::unique_ptr(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; + I2PServerTunnel * serverTunnel; if (type == I2P_TUNNELS_SECTION_TYPE_HTTP) - serverTunnel = std::make_shared (name, host, port, localDestination, hostOverride, inPort, gzip); + serverTunnel = new I2PServerTunnelHTTP (name, host, port, localDestination, hostOverride, inPort, gzip); else if (type == I2P_TUNNELS_SECTION_TYPE_IRC) - serverTunnel = std::make_shared (name, host, port, localDestination, webircpass, inPort, gzip); + serverTunnel = new I2PServerTunnelIRC (name, host, port, localDestination, webircpass, inPort, gzip); else // regular server tunnel by default - serverTunnel = std::make_shared (name, host, port, localDestination, inPort, gzip); + serverTunnel = new I2PServerTunnel (name, host, port, localDestination, inPort, gzip); - if (!address.empty ()) - serverTunnel->SetLocalAddress (address); - if (!isUniqueLocal) + LogPrint(eLogInfo, "Clients: Set Max Conns To ", maxConns); + serverTunnel->SetMaxConnsPerMinute(maxConns); + 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; @@ -864,7 +643,7 @@ namespace client } auto ins = m_ServerTunnels.insert (std::make_pair ( std::make_pair (localDestination->GetIdentHash (), inPort), - serverTunnel)); + std::unique_ptr(serverTunnel))); if (ins.second) { serverTunnel->Start (); @@ -876,24 +655,24 @@ 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 ()); } } + LogPrint (eLogInfo, "Clients: ", numClientTunnels, " I2P client tunnels created"); + LogPrint (eLogInfo, "Clients: ", numServerTunnels, " I2P server tunnels created"); } void ClientContext::ReadHttpProxy () @@ -902,88 +681,63 @@ 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); + 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 (); + 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, 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()); } } } - + void ClientContext::ReadSocksProxy () { std::shared_ptr localDestination; 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 (); + localDestination->Acquire (); } else - LogPrint(eLogCritical, "Clients: Failed to load SOCKS Proxy key"); + LogPrint(eLogError, "Clients: failed to load SOCKS Proxy key"); } try { @@ -993,8 +747,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 +772,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..922d7acc 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 { @@ -76,24 +65,16 @@ namespace client std::shared_ptr GetSharedLocalDestination () const { return m_SharedLocalDestination; }; std::shared_ptr CreateNewLocalDestination (bool isPublic = false, // transient - i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519, + i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_DSA_SHA1, 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, - i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519, + bool LoadPrivateKeys (i2p::data::PrivateKeys& keys, const std::string& filename, + i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256, i2p::data::CryptoKeyType cryptoType = i2p::data::CRYPTO_KEY_TYPE_ELGAMAL); AddressBook& GetAddressBook () { return m_AddressBook; }; @@ -103,33 +84,24 @@ 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 (); - void ReadTunnels (const std::string& tunConf, int& numClientTunnels, int& numServerTunnels); void ReadHttpProxy (); void ReadSocksProxy (); template std::string GetI2CPOption (const Section& section, const std::string& name, const Type& value) const; 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,13 +111,14 @@ 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::unique_ptr > m_ServerTunnels; // ->tunnel std::mutex m_ForwardsMutex; - std::map > m_ClientForwards; // local endpoint -> udp tunnel - std::map, std::shared_ptr > m_ServerForwards; // -> udp tunnel + std::map > m_ClientForwards; // local endpoint -> udp tunnel + std::map, std::unique_ptr > m_ServerForwards; // -> udp tunnel SAMBridge * m_SamBridge; BOBCommandChannel * m_BOBCommandChannel; @@ -153,19 +126,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..ac5d907d 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,83 +54,81 @@ 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 HandleHTTPConnectStreamRequestComplete(std::shared_ptr stream); + void ForwardToUpstreamProxy(); + void HandleUpstreamHTTPProxyConnect(const boost::system::error_code & ec); + void HandleUpstreamSocksProxyConnect(const boost::system::error_code & ec); + void HTTPConnect(const std::string & host, uint16_t port); + void HandleHTTPConnectStreamRequestComplete(std::shared_ptr stream); - typedef std::function ProxyResolvedHandler; + 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); - void HandleUpstreamProxyResolved(const boost::system::error_code & ecode, boost::asio::ip::tcp::resolver::results_type endpoints, ProxyResolvedHandler handler); + typedef std::function ProxyResolvedHandler; - void SocksProxySuccess(); - void HandoverToUpstreamProxy(); + void HandleUpstreamProxyResolved(const boost::system::error_code & ecode, boost::asio::ip::tcp::resolver::iterator itr, ProxyResolvedHandler handler); + + void SocksProxySuccess(); + void HandoverToUpstreamProxy(); uint8_t m_recv_chunk[8192]; std::string m_recv_buf; // from client std::string m_send_buf; // to upstream 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; - i2p::http::URL m_ProxyURL; - i2p::http::URL m_RequestURL; - int m_req_len; - i2p::http::URL m_ClientRequestURL; - i2p::http::HTTPReq m_ClientRequest; - i2p::http::HTTPRes m_ClientResponse; - std::stringstream m_ClientRequestBuffer; - + std::shared_ptr m_proxysock; + boost::asio::ip::tcp::resolver m_proxy_resolver; + std::string m_OutproxyUrl; + i2p::http::URL m_ProxyURL; + i2p::http::URL m_RequestURL; + uint8_t m_socks_buf[255+8]; // for socks request/response + ssize_t m_req_len; + i2p::http::URL m_ClientRequestURL; + i2p::http::HTTPReq m_ClientRequest; + i2p::http::HTTPRes m_ClientResponse; + std::stringstream m_ClientRequestBuffer; public: HTTPReqHandler(HTTPProxy * parent, std::shared_ptr sock) : I2PServiceHandler(parent), m_sock(sock), 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_OutproxyUrl(parent->GetOutproxyURL()) {} ~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)), - std::bind(&HTTPReqHandler::HandleSockRecv, shared_from_this(), - std::placeholders::_1, std::placeholders::_2)); + std::bind(&HTTPReqHandler::HandleSockRecv, shared_from_this(), + std::placeholders::_1, std::placeholders::_2)); } void HTTPReqHandler::Terminate() { 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 +136,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 jumpservices 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 +177,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,116 +198,28 @@ 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("Referer"); 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"); - auto x = h.find("pgrade"); - if (!(x != std::string::npos && std::tolower(h[x - 1]) == 'u')) - req.UpdateHeader("Connection", "close"); + req.AddHeader("Connection", "close"); /* keep-alive conns not supported yet */ } /** @@ -358,85 +230,50 @@ namespace proxy { */ bool HTTPReqHandler::HandleRequest() { + std::string b64; + m_req_len = m_ClientRequest.parse(m_recv_buf); if (m_req_len == 0) 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 (ExtractAddressHelper(m_RequestURL, b64, m_Confirm)) { - if (!m_Addresshelper || !i2p::client::context.GetAddressBook ().IsEnabled ()) + bool addresshelper; i2p::config::GetOption("httpproxy.addresshelper", addresshelper); + if (!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); + i2p::client::context.GetAddressBook ().InsertAddress (m_RequestURL.host, b64); + LogPrint (eLogInfo, "HTTPProxy: added b64 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 +282,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 +309,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,28 +329,29 @@ 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; } } } /* check dest_host really exists and inside I2P network */ + i2p::data::IdentHash identHash; if (str_rmatch(dest_host, ".i2p")) { - if (!i2p::client::context.GetAddressBook ().GetAddress (dest_host)) { + if (!i2p::client::context.GetAddressBook ().GetIdentHash (dest_host, identHash)) { HostNotFound(dest_host); return true; /* request processed */ } } 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 outprxy 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 +372,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,109 +380,99 @@ 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 = ""; m_ClientRequestURL.host = ""; - 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"); - m_ClientRequest.write(m_ClientRequestBuffer); m_ClientRequestBuffer << m_recv_buf.substr(m_req_len); - /* assume http if empty schema */ - if (m_ProxyURL.schema == "" || m_ProxyURL.schema == "http") - { - /* handle upstream http proxy */ + // assume http if empty schema + if (m_ProxyURL.schema == "" || m_ProxyURL.schema == "http") { + // 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 ()) - { - /* remove existing authorization if any */ - m_ClientRequest.RemoveHeader("Proxy-"); - /* add own http proxy authorization */ - m_ClientRequest.AddHeader("Proxy-Authorization", auth); - } - m_send_buf = m_ClientRequest.to_string(); - m_recv_buf.erase(0, m_req_len); - m_send_buf.append(m_recv_buf); + m_send_buf = m_recv_buf; GetOwner()->CreateStream (std::bind (&HTTPReqHandler::HandleStreamRequestComplete, shared_from_this(), std::placeholders::_1), m_ProxyURL.host, m_ProxyURL.port); } 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)); - })); - } - } - else if (m_ProxyURL.schema == "socks") - { - /* handle upstream socks proxy */ - if (!m_ProxyURL.port) m_ProxyURL.port = 9050; // default to tor default if not specified - 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::HandleUpstreamHTTPProxyConnect, this, std::placeholders::_1)); })); - } - else - { - /* unknown type, complain */ - GenericProxyError(tr("Unknown outproxy URL"), m_ProxyURL.to_string()); + } + } else if (m_ProxyURL.schema == "socks") { + // handle upstream socks proxy + if (!m_ProxyURL.port) m_ProxyURL.port = 9050; // default to tor default if not specified + boost::asio::ip::tcp::resolver::query q(m_ProxyURL.host, std::to_string(m_ProxyURL.port)); + m_proxy_resolver.async_resolve(q, std::bind(&HTTPReqHandler::HandleUpstreamProxyResolved, this, std::placeholders::_1, std::placeholders::_2, [&](boost::asio::ip::tcp::endpoint ep) { + m_proxysock->async_connect(ep, std::bind(&HTTPReqHandler::HandleUpstreamSocksProxyConnect, this, std::placeholders::_1)); + })); + } else { + // 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,12 +480,13 @@ 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); + shared_from_this(), std::placeholders::_1), host, port); else ForwardToUpstreamProxy(); } @@ -678,7 +507,7 @@ namespace proxy { } else { - GenericProxyError(tr("CONNECT error"), tr("Failed to connect")); + GenericProxyError("CONNECT error", "Failed to Connect"); } } @@ -687,37 +516,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 +585,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 +598,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): - TCPIPAcceptor (address, port, localDestination ? localDestination : i2p::client::context.GetSharedLocalDestination ()), - m_Name (name), m_OutproxyUrl (outproxy), m_Addresshelper (addresshelper), m_SendUserAgent (senduseragent) + HTTPProxy::HTTPProxy(const std::string& name, const std::string& address, int port, const std::string & outproxy, std::shared_ptr localDestination): + TCPIPAcceptor(address, port, localDestination ? localDestination : i2p::client::context.GetSharedLocalDestination ()), + m_Name (name), m_OutproxyUrl(outproxy) { } diff --git a/libi2pd_client/HTTPProxy.h b/libi2pd_client/HTTPProxy.h index 507a87e2..04a43914 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,21 @@ 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, std::shared_ptr localDestination); + HTTPProxy(const std::string& name, const std::string& address, int port, std::shared_ptr localDestination = nullptr) : + HTTPProxy(name, address, port, "", localDestination) {} ; ~HTTPProxy() {}; std::string GetOutproxyURL() const { return m_OutproxyUrl; } - bool GetHelperSupport() const { return m_Addresshelper; } - bool GetSendUserAgent () const { return m_SendUserAgent; } 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; }; } // http } // i2p diff --git a/libi2pd_client/I2CP.cpp b/libi2pd_client/I2CP.cpp index 11278e7a..371456ee 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-2016, 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,196 +23,81 @@ 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); 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); - } - - 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); + auto ls = new i2p::data::LocalLeaseSet (m_Identity, buf, len); 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 +111,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 +124,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 +156,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 +187,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 +198,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 +212,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 +225,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 +237,8 @@ namespace client else { HandleMessage (); + delete[] m_Payload; + m_Payload = nullptr; m_PayloadLen = 0; ReceiveHeader (); // next message } @@ -492,7 +250,7 @@ namespace client if (handler) (this->*handler)(m_Payload, m_PayloadLen); else - LogPrint (eLogError, "I2CP: Unknown I2CP message ", (int)m_Header[I2CP_HEADER_TYPE_OFFSET]); + LogPrint (eLogError, "I2CP: Unknown I2CP messsage ", (int)m_Header[I2CP_HEADER_TYPE_OFFSET]); } void I2CPSession::Terminate () @@ -507,100 +265,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 +326,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 +334,7 @@ namespace client break; } offset++; - mapping.emplace (param, value); + mapping.insert (std::make_pair (param, value)); } } @@ -639,26 +356,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 maformed 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,105 +381,50 @@ 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 falied"); + 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 - if(len > sizeof(uint16_t)) - { - uint16_t sessionID = bufbe16toh(buf); - if(sessionID == m_SessionID) - { - buf += sizeof(uint16_t); - const uint8_t * body = buf; - i2p::data::IdentityEx ident; - if(ident.FromBuffer(buf, len - sizeof(uint16_t))) - { - if (ident == *m_Destination->GetIdentity()) - { - size_t identsz = ident.GetFullLen(); - buf += identsz; - uint16_t optssize = bufbe16toh(buf); - if (optssize <= len - sizeof(uint16_t) - sizeof(uint64_t) - identsz - ident.GetSignatureLen() - sizeof(uint16_t)) - { - buf += sizeof(uint16_t); - std::map opts; - ExtractMapping(buf, optssize, opts); - buf += optssize; - //uint64_t date = bufbe64toh(buf); - buf += sizeof(uint64_t); - const uint8_t * sig = buf; - if(ident.Verify(body, len - sizeof(uint16_t) - ident.GetSignatureLen(), sig)) - { - if(m_Destination->Reconfigure(opts)) - { - LogPrint(eLogInfo, "I2CP: Reconfigured destination"); - status = eI2CPSessionStatusUpdated; // updated - } - else - LogPrint(eLogWarning, "I2CP: Failed to reconfigure destination"); - } - else - LogPrint(eLogError, "I2CP: Invalid reconfigure message signature"); - } - else - LogPrint(eLogError, "I2CP: Mapping size mismatch"); - } - else - LogPrint(eLogError, "I2CP: Destination mismatch"); - } - else - LogPrint(eLogError, "I2CP: Malfromed destination"); - } - else - LogPrint(eLogError, "I2CP: Session mismatch"); - } - else - LogPrint(eLogError, "I2CP: Short message"); - SendSessionStatusMessage (status); + // TODO: implement actual reconfiguration + SendSessionStatusMessage (2); // updated } - 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 +440,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,48 +458,7 @@ namespace client } } else - LogPrint (eLogError, "I2CP: Unexpected sessionID ", sessionID); - } - - void I2CPSession::CreateLeaseSet2MessageHandler (const uint8_t * buf, size_t len) - { - uint16_t sessionID = bufbe16toh (buf); - if (sessionID == m_SessionID) - { - size_t offset = 2; - if (m_Destination) - { - uint8_t storeType = buf[offset]; offset++; // store type - 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); - return; - } - offset += ls.GetBufferLen (); - // private keys - int numPrivateKeys = buf[offset]; offset++; - 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 - if (offset + keyLen > len) return; - if (keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) - m_Destination->SetECIESx25519EncryptionPrivateKey (buf + offset); - else - { - m_Destination->SetEncryptionType (keyType); - m_Destination->SetEncryptionPrivateKey (buf + offset); - } - offset += keyLen; - } - - 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 +469,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) @@ -944,20 +515,16 @@ namespace client case 1: // address { auto name = ExtractString (buf + 11, len - 11); - auto addr = i2p::client::context.GetAddressBook ().GetAddress (name); - if (!addr || !addr->IsIdentHash ()) + if (!i2p::client::context.GetAddressBook ().GetIdentHash (name, ident)) { - // TODO: handle blinded addresses - LogPrint (eLogError, "I2CP: Address ", name, " not found"); + LogPrint (eLogError, "I2CP: address ", name, " not found"); SendHostReplyMessage (requestID, nullptr); return; } - else - ident = addr->identHash; 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 +550,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 +617,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 +626,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; @@ -1110,7 +653,6 @@ namespace client m_MessagesHandlers[I2CP_DESTROY_SESSION_MESSAGE] = &I2CPSession::DestroySessionMessageHandler; m_MessagesHandlers[I2CP_RECONFIGURE_SESSION_MESSAGE] = &I2CPSession::ReconfigureSessionMessageHandler; m_MessagesHandlers[I2CP_CREATE_LEASESET_MESSAGE] = &I2CPSession::CreateLeaseSetMessageHandler; - m_MessagesHandlers[I2CP_CREATE_LEASESET2_MESSAGE] = &I2CPSession::CreateLeaseSet2MessageHandler; m_MessagesHandlers[I2CP_SEND_MESSAGE_MESSAGE] = &I2CPSession::SendMessageMessageHandler; m_MessagesHandlers[I2CP_SEND_MESSAGE_EXPIRES_MESSAGE] = &I2CPSession::SendMessageExpiresMessageHandler; m_MessagesHandlers[I2CP_HOST_LOOKUP_MESSAGE] = &I2CPSession::HostLookupMessageHandler; @@ -1120,37 +662,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 +720,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 +739,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 +749,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..657c72c1 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-2016, 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; @@ -47,7 +36,6 @@ namespace client const uint8_t I2CP_DESTROY_SESSION_MESSAGE = 3; const uint8_t I2CP_REQUEST_VARIABLE_LEASESET_MESSAGE = 37; const uint8_t I2CP_CREATE_LEASESET_MESSAGE = 4; - const uint8_t I2CP_CREATE_LEASESET2_MESSAGE = 41; const uint8_t I2CP_SEND_MESSAGE_MESSAGE = 5; const uint8_t I2CP_SEND_MESSAGE_EXPIRES_MESSAGE = 36; const uint8_t I2CP_MESSAGE_PAYLOAD_MESSAGE = 31; @@ -64,20 +52,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 +64,35 @@ 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 +100,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 +119,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); @@ -183,7 +126,6 @@ namespace client void DestroySessionMessageHandler (const uint8_t * buf, size_t len); void ReconfigureSessionMessageHandler (const uint8_t * buf, size_t len); void CreateLeaseSetMessageHandler (const uint8_t * buf, size_t len); - void CreateLeaseSet2MessageHandler (const uint8_t * buf, size_t len); void SendMessageMessageHandler (const uint8_t * buf, size_t len); void SendMessageExpiresMessageHandler (const uint8_t * buf, size_t len); void HostLookupMessageHandler (const uint8_t * buf, size_t len); @@ -200,64 +142,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 +207,4 @@ namespace client } #endif + diff --git a/libi2pd_client/I2PService.cpp b/libi2pd_client/I2PService.cpp index 4ec2648a..21e1fdfa 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" @@ -16,13 +8,12 @@ namespace i2p { namespace client { - static const i2p::data::SigningKeyType I2P_SERVICE_DEFAULT_KEY_TYPE = i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519; + static const i2p::data::SigningKeyType I2P_SERVICE_DEFAULT_KEY_TYPE = i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256; I2PService::I2PService (std::shared_ptr localDestination): m_LocalDestination (localDestination ? localDestination : i2p::client::context.CreateNewLocalDestination (false, I2P_SERVICE_DEFAULT_KEY_TYPE)), m_ReadyTimer(m_LocalDestination->GetService()), - m_ReadyTimerTriggered(false), m_ConnectTimeout(0), isUpdated (true) { @@ -56,25 +47,29 @@ namespace client void I2PService::SetConnectTimeout(uint32_t timeout) { + if(timeout && !m_ConnectTimeout) + { + TriggerReadyCheckTimer(); + } + else if (m_ConnectTimeout && !timeout) + { + m_ReadyTimer.cancel(); + } m_ConnectTimeout = timeout; } void I2PService::AddReadyCallback(ReadyCallback cb) { uint32_t now = i2p::util::GetSecondsSinceEpoch(); - uint32_t tm = (m_ConnectTimeout) ? now + m_ConnectTimeout : NEVER_TIMES_OUT; - + uint32_t tm = now + m_ConnectTimeout; LogPrint(eLogDebug, "I2PService::AddReadyCallback() ", tm, " ", now); m_ReadyCallbacks.push_back({cb, tm}); - if (!m_ReadyTimerTriggered) TriggerReadyCheckTimer(); } void I2PService::TriggerReadyCheckTimer() { m_ReadyTimer.expires_from_now(boost::posix_time::seconds (1)); - m_ReadyTimer.async_wait(std::bind(&I2PService::HandleReadyCheckTimer, shared_from_this (), std::placeholders::_1)); - m_ReadyTimerTriggered = true; - + m_ReadyTimer.async_wait(std::bind(&I2PService::HandleReadyCheckTimer, this, std::placeholders::_1)); } void I2PService::HandleReadyCheckTimer(const boost::system::error_code &ec) @@ -92,7 +87,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 >= now) { itr->first(boost::asio::error::timed_out); itr = m_ReadyCallbacks.erase(itr); @@ -101,17 +96,15 @@ namespace client ++itr; } } - if(!ec && m_ReadyCallbacks.size()) + if(!ec) TriggerReadyCheckTimer(); - else - 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) - CreateStream(streamRequestComplete, address, port); + i2p::data::IdentHash identHash; + if (i2p::client::context.GetAddressBook ().GetIdentHash (dest, identHash)) + CreateStream(streamRequestComplete, identHash, port); else { LogPrint (eLogWarning, "I2PService: Remote destination not found: ", dest); @@ -119,32 +112,217 @@ namespace client } } - void I2PService::CreateStream(StreamRequestComplete streamRequestComplete, std::shared_ptr address, uint16_t port) + void I2PService::CreateStream(StreamRequestComplete streamRequestComplete, const i2p::data::IdentHash & identHash, int port) { - if(m_ConnectTimeout && !m_LocalDestination->IsReady()) + if(m_ConnectTimeout) { - AddReadyCallback([this, streamRequestComplete, address, port] (const boost::system::error_code & ec) - { - if(ec) - { - LogPrint(eLogWarning, "I2PService::CreateStream() ", ec.message()); - streamRequestComplete(nullptr); - } - else - { - if (address->IsIdentHash ()) - this->m_LocalDestination->CreateStream(streamRequestComplete, address->identHash, port); + if(m_LocalDestination->IsReady()) + m_LocalDestination->CreateStream (streamRequestComplete, identHash, port); + else + { + AddReadyCallback([this, streamRequestComplete, identHash, port] (const boost::system::error_code & ec) { + if(ec) + { + LogPrint(eLogWarning, "I2PService::CeateStream() ", ec.message()); + streamRequestComplete(nullptr); + } else - this->m_LocalDestination->CreateStream (streamRequestComplete, address->blindedPublicKey, port); - } - }); + this->m_LocalDestination->CreateStream(streamRequestComplete, identHash, port); + }); + } + } + else + m_LocalDestination->CreateStream(streamRequestComplete, identHash, 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 (address->IsIdentHash ()) - m_LocalDestination->CreateStream (streamRequestComplete, address->identHash, port); - else - m_LocalDestination->CreateStream (streamRequestComplete, address->blindedPublicKey, port); + 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..ecfd5bc5 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__ @@ -16,7 +8,6 @@ #include #include "Destination.h" #include "Identity.h" -#include "AddressBook.h" namespace i2p { @@ -26,12 +17,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 +41,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, const i2p::data::IdentHash & ident, int port); + inline boost::asio::io_service& GetService () { return m_LocalDestination->GetService (); } virtual void Start () = 0; virtual void Stop () = 0; @@ -69,24 +58,18 @@ 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; uint32_t m_ConnectTimeout; - const size_t NEVER_TIMES_OUT = 0; - public: - bool isUpdated; // transient, used during reload only }; @@ -94,17 +77,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 +95,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 82a6ed03..975cc4ce 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,9 +21,9 @@ namespace client } I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, std::shared_ptr socket, - std::shared_ptr leaseSet, uint16_t port): + std::shared_ptr leaseSet, int port): I2PServiceHandler(owner), m_Socket (socket), m_RemoteEndpoint (socket->remote_endpoint ()), - m_IsReceiving (false) + m_IsQuiet (true) { m_Stream = GetOwner()->GetLocalDestination ()->CreateStream (leaseSet, port); } @@ -41,17 +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_IsReceiving (false) + m_RemoteEndpoint (socket->remote_endpoint ()), m_IsQuiet (true) { } I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, std::shared_ptr stream, - const boost::asio::ip::tcp::endpoint& target,std::shared_ptr sslCtx): - I2PServiceHandler(owner), m_Stream (stream), m_RemoteEndpoint (target), m_IsReceiving (false) + std::shared_ptr socket, const boost::asio::ip::tcp::endpoint& target, bool quiet): + I2PServiceHandler(owner), m_Socket (socket), m_Stream (stream), + m_RemoteEndpoint (target), m_IsQuiet (quiet) { - m_Socket = std::make_shared (owner->GetService ()); - if (sslCtx) - m_SSL = std::make_shared > (*m_Socket, *sslCtx); } I2PTunnelConnection::~I2PTunnelConnection () @@ -71,7 +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; @@ -81,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) @@ -116,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 (); @@ -150,64 +115,43 @@ namespace client void I2PTunnelConnection::Receive () { - if (m_IsReceiving) return; // already receiving - size_t bufSize = I2P_TUNNEL_CONNECTION_BUFFER_SIZE; - size_t unsentSize = m_Stream ? m_Stream->GetSendBufferSize () : 0; - if (unsentSize) - { - if (unsentSize >= I2P_TUNNEL_CONNECTION_STREAM_MAX_SEND_BUFFER_SIZE) return; // buffer is full - if (unsentSize > I2P_TUNNEL_CONNECTION_STREAM_MAX_SEND_BUFFER_SIZE - I2P_TUNNEL_CONNECTION_BUFFER_SIZE) - bufSize = I2P_TUNNEL_CONNECTION_STREAM_MAX_SEND_BUFFER_SIZE - unsentSize; - } - m_IsReceiving = true; - if (m_SSL) - m_SSL->async_read_some (boost::asio::buffer(m_Buffer, bufSize), - std::bind(&I2PTunnelConnection::HandleReceive, shared_from_this (), - std::placeholders::_1, std::placeholders::_2)); - else - m_Socket->async_read_some (boost::asio::buffer(m_Buffer, bufSize), - std::bind(&I2PTunnelConnection::HandleReceive, shared_from_this (), - std::placeholders::_1, std::placeholders::_2)); + m_Socket->async_read_some (boost::asio::buffer(m_Buffer, I2P_TUNNEL_CONNECTION_BUFFER_SIZE), + std::bind(&I2PTunnelConnection::HandleReceived, shared_from_this (), + std::placeholders::_1, std::placeholders::_2)); } - void I2PTunnelConnection::HandleReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred) + void I2PTunnelConnection::HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred) { - m_IsReceiving = false; if (ecode) { if (ecode != boost::asio::error::operation_aborted) { - LogPrint (eLogError, "I2PTunnel: Read error: ", ecode.message ()); + LogPrint (eLogError, "I2PTunnel: read error: ", ecode.message ()); Terminate (); } } else - { - WriteToStream (m_Buffer, bytes_transferred); - Receive (); // try to receive more while being sent to stream - } - } - - void I2PTunnelConnection::WriteToStream (const uint8_t * buf, size_t len) - { - if (m_Stream) { - m_Stream->AsyncSend (buf, len, - [s = shared_from_this ()](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 (); } @@ -222,15 +166,15 @@ namespace client if (m_Stream->GetStatus () == i2p::stream::eStreamStatusNew || m_Stream->GetStatus () == i2p::stream::eStreamStatusOpen) // regular { - m_Stream->AsyncReceive (boost::asio::buffer (m_StreamBuffer, I2P_TUNNEL_CONNECTION_STREAM_BUFFER_SIZE), + m_Stream->AsyncReceive (boost::asio::buffer (m_StreamBuffer, I2P_TUNNEL_CONNECTION_BUFFER_SIZE), std::bind (&I2PTunnelConnection::HandleStreamReceive, shared_from_this (), - std::placeholders::_1, std::placeholders::_2), + std::placeholders::_1, std::placeholders::_2), I2P_TUNNEL_CONNECTION_MAX_IDLE); } else // closed by peer { - // get remaining data - auto len = m_Stream->ReadSome (m_StreamBuffer, I2P_TUNNEL_CONNECTION_STREAM_BUFFER_SIZE); + // 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); else // no more data @@ -245,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 ()) @@ -262,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) @@ -328,13 +256,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"); - if (x != std::string::npos && std::tolower(line[x - 1]) == 'u') - m_OutHeader << line << "\r\n"; - else - m_OutHeader << "Connection: close\r\n"; - + m_OutHeader << "Connection: close\r\n"; m_ConnectionSent = true; } else if (!m_ProxyConnectionSent && !line.compare(0, 16, "Proxy-Connection")) @@ -343,46 +265,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) @@ -394,122 +301,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) @@ -517,19 +332,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) { } @@ -540,8 +352,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 (); @@ -572,20 +383,20 @@ 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): - I2PServiceHandler(parent), m_Address(address), + I2PClientTunnelHandler (I2PClientTunnel * parent, i2p::data::IdentHash destination, + int destinationPort, std::shared_ptr socket): + I2PServiceHandler(parent), m_DestinationIdentHash(destination), m_DestinationPort (destinationPort), m_Socket(socket) {}; void Handle(); void Terminate(); private: void HandleStreamRequestComplete (std::shared_ptr stream); - std::shared_ptr m_Address; - uint16_t m_DestinationPort; + i2p::data::IdentHash m_DestinationIdentHash; + int m_DestinationPort; std::shared_ptr m_Socket; }; @@ -593,7 +404,7 @@ namespace client { GetOwner()->CreateStream ( std::bind (&I2PClientTunnelHandler::HandleStreamRequestComplete, shared_from_this(), std::placeholders::_1), - m_Address, m_DestinationPort); + m_DestinationIdentHash, m_DestinationPort); } void I2PClientTunnelHandler::HandleStreamRequestComplete (std::shared_ptr stream) @@ -601,7 +412,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 (); @@ -626,94 +437,61 @@ 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_DestinationIdentHash (nullptr), m_DestinationPort (destinationPort) { } void I2PClientTunnel::Start () { TCPIPAcceptor::Start (); - GetAddress (); - if (m_KeepAliveInterval) - ScheduleKeepAliveTimer (); + GetIdentHash(); } 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 ())); + auto *originalIdentHash = m_DestinationIdentHash; + m_DestinationIdentHash = nullptr; + delete originalIdentHash; } /* HACK: maybe we should create a caching IdentHash provider in AddressBook */ - std::shared_ptr I2PClientTunnel::GetAddress () + const i2p::data::IdentHash * I2PClientTunnel::GetIdentHash () { - if (!m_Address) + if (!m_DestinationIdentHash) { - m_Address = i2p::client::context.GetAddressBook ().GetAddress (m_Destination); - if (!m_Address) + i2p::data::IdentHash identHash; + if (i2p::client::context.GetAddressBook ().GetIdentHash (m_Destination, identHash)) + m_DestinationIdentHash = new i2p::data::IdentHash (identHash); + else LogPrint (eLogWarning, "I2PTunnel: Remote destination ", m_Destination, " not found"); } - return m_Address; + return m_DestinationIdentHash; } std::shared_ptr I2PClientTunnel::CreateHandler(std::shared_ptr socket) { - auto address = GetAddress (); - if (address) - return std::make_shared(this, address, m_DestinationPort, socket); + const i2p::data::IdentHash *identHash = GetIdentHash(); + if (identHash) + return std::make_shared(this, *identHash, 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); @@ -722,7 +500,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)); } @@ -730,65 +508,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) @@ -797,27 +531,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) @@ -849,22 +562,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) { @@ -872,22 +582,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) { @@ -895,8 +596,275 @@ 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); + i2p::data::IdentHash * h = new i2p::data::IdentHash; + + while(!context.GetAddressBook().GetIdentHash(m_RemoteDest, *h) && !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; + } + m_RemoteIdent = h; + 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 9abae09a..1bdf8bb5 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,92 +8,71 @@ #include #include #include -#include #include "Identity.h" #include "Destination.h" +#include "Datagram.h" #include "Streaming.h" #include "I2PService.h" -#include "AddressBook.h" namespace i2p { namespace client { - constexpr size_t I2P_TUNNEL_CONNECTION_BUFFER_SIZE = 8192; - constexpr size_t I2P_TUNNEL_CONNECTION_STREAM_MAX_SEND_BUFFER_SIZE = 8*I2P_TUNNEL_CONNECTION_BUFFER_SIZE; - constexpr size_t I2P_TUNNEL_CONNECTION_STREAM_BUFFER_SIZE = 16384; + 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: - uint8_t m_Buffer[I2P_TUNNEL_CONNECTION_BUFFER_SIZE], m_StreamBuffer[I2P_TUNNEL_CONNECTION_STREAM_BUFFER_SIZE]; + 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_IsReceiving; + 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; }; @@ -109,37 +80,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; @@ -150,44 +115,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); + const i2p::data::IdentHash * GetIdentHash (); 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; + const i2p::data::IdentHash * m_DestinationIdentHash; + 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 (); @@ -197,21 +273,17 @@ 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 SetMaxConnsPerMinute(const uint32_t conns) { m_PortDestination->SetMaxConnsPerMinute(conns); } - void HandleResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::results_type endpoints, + private: + void HandleResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it, std::shared_ptr resolver); void Accept (); @@ -219,54 +291,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..68f3759e 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,93 +8,106 @@ 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); - if(addr && addr->IsIdentHash ()) + if(i2p::client::context.GetAddressBook().GetIdentHash(m_RemoteName, m_RemoteIdent)) { - m_RemoteIdent = addr->identHash; auto ls = FindLeaseSet(m_RemoteIdent); if(ls) + { HandleFoundCurrentLeaseSet(ls); + } else 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 f0df414c..f4736922 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 @@ -23,83 +15,103 @@ namespace i2p { namespace client { - SAMSocket::SAMSocket (SAMBridge& owner): - m_Owner (owner), m_Socket(owner.GetService()), m_Timer (m_Owner.GetService ()), - m_BufferOffset (0), + SAMSocket::SAMSocket (SAMBridge& owner, std::shared_ptr socket): + m_Owner (owner), m_Socket(socket), m_Timer (m_Owner.GetService ()), + m_BufferOffset (0), m_SocketType (eSAMSocketTypeUnknown), m_IsSilent (false), - m_IsAccepting (false), m_IsReceiving (false) + m_IsAccepting (false), m_Stream (nullptr) { } SAMSocket::~SAMSocket () - { - m_Stream = nullptr; - } - - void SAMSocket::Terminate (const char* reason) { if(m_Stream) { - m_Stream->AsyncClose (); - m_Stream = nullptr; + m_Stream->Close (); + m_Stream.reset (); } + 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) + if (Session) + Session->DelSocket (this); + break; + } + case eSAMSocketTypeAcceptor: + { + if (Session) { - if (m_IsAccepting && session->GetLocalDestination ()) - session->GetLocalDestination ()->StopAcceptingStreams (); + Session->DelSocket (this); + if (m_IsAccepting && Session->localDestination) + Session->localDestination->StopAcceptingStreams (); } break; } - default: ; + default: + ; } m_SocketType = eSAMSocketTypeTerminated; - if (m_Socket.is_open ()) + if (m_Socket && m_Socket->is_open()) m_Socket->close (); + m_Socket.reset (); + } + + void SAMSocket::Terminate (const char* reason) + { + if(m_Stream) { - boost::system::error_code ec; - m_Socket.shutdown (boost::asio::ip::tcp::socket::shutdown_both, ec); - m_Socket.close (); + m_Stream->Close (); + m_Stream.reset (); } - m_Owner.RemoveSocket(shared_from_this()); + auto Session = m_Owner.FindSession(m_ID); + + switch (m_SocketType) + { + case eSAMSocketTypeSession: + m_Owner.CloseSession (m_ID); + break; + case eSAMSocketTypeStream: + { + if (Session) + Session->DelSocket (this); + break; + } + case eSAMSocketTypeAcceptor: + { + if (Session) + { + Session->DelSocket (this); + if (m_IsAccepting && Session->localDestination) + Session->localDestination->StopAcceptingStreams (); + } + break; + } + default: + ; + } + m_SocketType = eSAMSocketTypeTerminated; + if (m_Socket && m_Socket->is_open()) m_Socket->close (); + m_Socket.reset (); } 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)); - } - - static bool SAMVersionAcceptable(const std::string & ver) - { - return ver == "3.0" || ver == "3.1"; - } - - static bool SAMVersionTooLow(const std::string & ver) - { - return ver.size() && ver[0] < '3'; - } - - static bool SAMVersionTooHigh(const std::string & ver) - { - return ver.size() && ver > "3.1"; + if(m_Socket) + 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)); } void SAMSocket::HandleHandshakeReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred) { 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 +121,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) { @@ -120,74 +132,51 @@ namespace client if (!strcmp (m_Buffer, SAM_HANDSHAKE)) { - std::string maxver("3.1"); - std::string minver("3.0"); + std::string version("3.0"); // try to find MIN and MAX, 3.0 if not found if (separator) { separator++; std::map params; ExtractParams (separator, params); - auto it = params.find (SAM_PARAM_MAX); - if (it != params.end ()) - maxver = it->second; - it = params.find(SAM_PARAM_MIN); - if (it != params.end ()) - minver = it->second; + //auto it = params.find (SAM_PARAM_MAX); + // TODO: check MIN as well + //if (it != params.end ()) + // version = it->second; } - // version negotiation - std::string version; - if (SAMVersionAcceptable(maxver)) - { - version = maxver; - } - else if (SAMVersionAcceptable(minver)) - { - version = minver; - } - else if (SAMVersionTooLow(minver) && SAMVersionTooHigh(maxver)) - { - version = "3.0"; - } - - if (SAMVersionAcceptable(version)) + if (version[0] == '3') // we support v3 (3.0 and 3.1) only { #ifdef _MSC_VER size_t l = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_HANDSHAKE_REPLY, version.c_str ()); #else size_t l = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_HANDSHAKE_REPLY, version.c_str ()); #endif - boost::asio::async_write (m_Socket, boost::asio::buffer (m_Buffer, l), boost::asio::transfer_all (), - std::bind(&SAMSocket::HandleHandshakeReplySent, shared_from_this (), + boost::asio::async_write (*m_Socket, boost::asio::buffer (m_Buffer, l), boost::asio::transfer_all (), + std::bind(&SAMSocket::HandleHandshakeReplySent, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } else - SendMessageReply (SAM_HANDSHAKE_NOVERSION, strlen (SAM_HANDSHAKE_NOVERSION), true); + SendMessageReply (SAM_HANDSHAKE_I2P_ERROR, strlen (SAM_HANDSHAKE_I2P_ERROR), true); } else { - LogPrint (eLogError, "SAM: Handshake mismatch"); + LogPrint (eLogError, "SAM: handshake mismatch"); Terminate ("SAM: handshake mismatch"); } } } - bool SAMSocket::IsSession(const std::string & id) const - { - 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"); } - else + else if(m_Socket) { - m_Socket.async_read_some (boost::asio::buffer(m_Buffer, SAM_SOCKET_BUFFER_SIZE), + m_Socket->async_read_some (boost::asio::buffer(m_Buffer, SAM_SOCKET_BUFFER_SIZE), std::bind(&SAMSocket::HandleMessage, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } @@ -197,8 +186,8 @@ namespace client { LogPrint (eLogDebug, "SAMSocket::SendMessageReply, close=",close?"true":"false", " reason: ", msg); - if (!m_IsSilent || m_SocketType == eSAMSocketTypeForward) - boost::asio::async_write (m_Socket, boost::asio::buffer (msg, len), boost::asio::transfer_all (), + if (!m_IsSilent) + boost::asio::async_write (*m_Socket, boost::asio::buffer (msg, len), boost::asio::transfer_all (), std::bind(&SAMSocket::HandleMessageReplySent, shared_from_this (), std::placeholders::_1, std::placeholders::_2, close)); else @@ -213,8 +202,8 @@ namespace client void SAMSocket::HandleMessageReplySent (const boost::system::error_code& ecode, std::size_t bytes_transferred, bool close) { 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"); } @@ -230,8 +219,8 @@ namespace client void SAMSocket::HandleMessage (const boost::system::error_code& ecode, std::size_t bytes_transferred) { 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 +234,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 +250,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 +275,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 (); @@ -314,34 +296,14 @@ namespace client } } - static bool IsAcceptableSessionName(const std::string & str) - { - auto itr = str.begin(); - while(itr != str.end()) - { - char ch = *itr; - ++itr; - if (ch == '<' || ch == '>' || ch == '"' || ch == '\'' || ch == '/') - return false; - } - return true; - } - 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]; std::string& id = params[SAM_PARAM_ID]; std::string& destination = params[SAM_PARAM_DESTINATION]; - - if(!IsAcceptableSessionName(id)) - { - // invalid session id - SendMessageReply (SAM_SESSION_CREATE_INVALID_ID, strlen(SAM_SESSION_CREATE_INVALID_ID), true); - return; - } m_ID = id; if (m_Owner.FindSession (id)) { @@ -350,83 +312,43 @@ 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()) - { - SendMessageReply (SAM_SESSION_STATUS_INVALID_KEY, strlen(SAM_SESSION_STATUS_INVALID_KEY), true); - return; - } - - if (destination != SAM_VALUE_TRANSIENT) - { - //ensure it's a base64 string - i2p::data::PrivateKeys keys; - if (!keys.FromBase64(destination)) - { - 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 +365,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 +385,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 +401,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,75 +420,41 @@ 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().InsertAddress(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); + SendMessageReply(SAM_SESSION_STATUS_INVALID_KEY, strlen(SAM_SESSION_STATUS_INVALID_KEY), true); } else 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; + session->AddSocket (shared_from_this ()); + 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 +463,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 +481,21 @@ namespace client if (session) { m_SocketType = eSAMSocketTypeAcceptor; - if (!session->GetLocalDestination ()->IsAcceptingStreams ()) + session->AddSocket (shared_from_this ()); + 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 +504,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 +527,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 @@ -729,67 +535,49 @@ namespace client i2p::data::CryptoKeyType cryptoType = i2p::data::CRYPTO_KEY_TYPE_ELGAMAL; auto it = params.find (SAM_PARAM_SIGNATURE_TYPE); if (it != params.end ()) - { - if (!m_Owner.ResolveSignatureType (it->second, signatureType)) - LogPrint (eLogWarning, "SAM: ", SAM_PARAM_SIGNATURE_TYPE, " is invalid ", it->second); - } + // TODO: extract string values + signatureType = std::stoi(it->second); it = params.find (SAM_PARAM_CRYPTO_TYPE); if (it != params.end ()) - { - try - { - cryptoType = std::stoi(it->second); - } - catch (const std::exception& ex) - { - LogPrint (eLogWarning, "SAM: ", SAM_PARAM_CRYPTO_TYPE, "error: ", ex.what ()); - } - } - auto keys = i2p::data::PrivateKeys::CreateRandomKeys (signatureType, cryptoType, true); + cryptoType = std::stoi(it->second); + 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 ()); #else size_t l = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_DEST_REPLY, - keys.GetPublic ()->ToBase64 ().c_str (), keys.ToBase64 ().c_str ()); + keys.GetPublic ()->ToBase64 ().c_str (), keys.ToBase64 ().c_str ()); #endif SendMessageReply (m_Buffer, l, false); } 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; + i2p::data::IdentHash ident; 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 ()); - else if ((identity = context.GetAddressBook ().GetFullAddress (name)) != nullptr) - SendNamingLookupReply (name, identity); - else if ((addr = context.GetAddressBook ().GetAddress (name))) + SendNamingLookupReply (dest->GetIdentity ()); + else if ((identity = context.GetAddressBook ().GetAddress (name)) != nullptr) + SendNamingLookupReply (identity); + else if (context.GetAddressBook ().GetIdentHash (name, ident)) { - if (addr->IsIdentHash ()) - { - auto leaseSet = dest->FindLeaseSet (addr->identHash); - if (leaseSet) - SendNamingLookupReply (name, leaseSet->GetIdentity ()); - else - dest->RequestDestination (addr->identHash, - std::bind (&SAMSocket::HandleNamingLookupLeaseSetRequestComplete, - shared_from_this (), std::placeholders::_1, name)); - } + auto leaseSet = dest->FindLeaseSet (ident); + if (leaseSet) + SendNamingLookupReply (leaseSet->GetIdentity ()); else - dest->RequestDestinationWithEncryptedLeaseSet (addr->blindedPublicKey, + dest->RequestDestination (ident, std::bind (&SAMSocket::HandleNamingLookupLeaseSetRequestComplete, - shared_from_this (), std::placeholders::_1, name)); + shared_from_this (), std::placeholders::_1, ident)); } 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,126 +587,45 @@ 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) + void SAMSocket::HandleNamingLookupLeaseSetRequestComplete (std::shared_ptr leaseSet, i2p::data::IdentHash ident) { if (leaseSet) { - context.GetAddressBook ().InsertFullAddress (leaseSet->GetIdentity ()); - SendNamingLookupReply (name, leaseSet->GetIdentity ()); + context.GetAddressBook ().InsertAddress (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 ", ident.ToBase32 (), " not found"); #ifdef _MSC_VER - size_t len = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY_INVALID_KEY, name.c_str()); + size_t len = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY_INVALID_KEY, + context.GetAddressBook ().ToAddress (ident).c_str()); #else - size_t len = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY_INVALID_KEY, name.c_str()); + size_t len = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY_INVALID_KEY, + context.GetAddressBook ().ToAddress (ident).c_str()); #endif SendMessageReply (m_Buffer, len, false); } } - 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); } @@ -944,45 +651,43 @@ namespace client void SAMSocket::Receive () { - if (m_SocketType == eSAMSocketTypeStream) + if (m_BufferOffset >= SAM_SOCKET_BUFFER_SIZE) { - if (m_IsReceiving) return; - size_t bufSize = SAM_SOCKET_BUFFER_SIZE; - size_t unsentSize = m_Stream ? m_Stream->GetSendBufferSize () : 0; - if (unsentSize) - { - if (unsentSize >= SAM_STREAM_MAX_SEND_BUFFER_SIZE) return; // buffer is full - if (unsentSize > SAM_STREAM_MAX_SEND_BUFFER_SIZE - SAM_SOCKET_BUFFER_SIZE) - bufSize = SAM_STREAM_MAX_SEND_BUFFER_SIZE - unsentSize; - } - m_IsReceiving = true; - m_Socket.async_read_some (boost::asio::buffer(m_Buffer, bufSize), - std::bind(&SAMSocket::HandleReceived, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); - } + LogPrint (eLogError, "SAM: Buffer is full, terminate"); + Terminate ("Buffer is full"); + return; + } else if (m_Socket) + m_Socket->async_read_some (boost::asio::buffer(m_Buffer + m_BufferOffset, SAM_SOCKET_BUFFER_SIZE - m_BufferOffset), + std::bind((m_SocketType == eSAMSocketTypeStream) ? &SAMSocket::HandleReceived : &SAMSocket::HandleMessage, + shared_from_this (), std::placeholders::_1, std::placeholders::_2)); else - m_Socket.async_read_some (boost::asio::buffer(m_Buffer + m_BufferOffset, SAM_SOCKET_BUFFER_SIZE - m_BufferOffset), - std::bind(&SAMSocket::HandleMessage, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); + LogPrint(eLogError, "SAM: receive with no native socket"); } void SAMSocket::HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred) { - m_IsReceiving = false; if (ecode) { - LogPrint (eLogError, "SAM: Read error: ", ecode.message ()); + LogPrint (eLogError, "SAM: read error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate ("read error"); } else { if (m_Stream) - { + { + bytes_transferred += m_BufferOffset; + m_BufferOffset = 0; + auto s = shared_from_this (); m_Stream->AsyncSend ((uint8_t *)m_Buffer, bytes_transferred, - std::bind(&SAMSocket::HandleStreamSend, shared_from_this(), std::placeholders::_1)); - Receive (); - } - else - Terminate("No Stream Remaining"); + [s](const boost::system::error_code& ecode) + { + if (!ecode) + s->m_Owner.GetService ().post ([s] { s->Receive (); }); + else + s->m_Owner.GetService ().post ([s] { s->Terminate ("AsyncSend failed"); }); + }); + } } } @@ -991,59 +696,57 @@ 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_STREAM_BUFFER_SIZE), + m_Stream->AsyncReceive (boost::asio::buffer (m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE), std::bind (&SAMSocket::HandleI2PReceive, shared_from_this(), std::placeholders::_1, std::placeholders::_2), SAM_SOCKET_CONNECTION_MAX_IDLE); } else // closed by peer { - uint8_t * buff = new uint8_t[SAM_STREAM_BUFFER_SIZE]; - // get remaining data - auto len = m_Stream->ReadSome (buff, SAM_STREAM_BUFFER_SIZE); + uint8_t * buff = new uint8_t[SAM_SOCKET_BUFFER_SIZE]; + // get remaning data + auto len = m_Stream->ReadSome (buff, SAM_SOCKET_BUFFER_SIZE); if (len > 0) // still some data { WriteI2PDataImmediate(buff, len); } else // no more data - { - delete [] buff; Terminate ("no more data"); - } } } } void SAMSocket::WriteI2PDataImmediate(uint8_t * buff, size_t sz) { - boost::asio::async_write ( - m_Socket, - boost::asio::buffer (buff, sz), - boost::asio::transfer_all(), - std::bind (&SAMSocket::HandleWriteI2PDataImmediate, shared_from_this (), std::placeholders::_1, buff)); // postpone termination + if(m_Socket) + boost::asio::async_write ( + *m_Socket, + boost::asio::buffer (buff, sz), + boost::asio::transfer_all(), + std::bind (&SAMSocket::HandleWriteI2PDataImmediate, shared_from_this (), std::placeholders::_1, buff)); // postpone termination + else + LogPrint(eLogError, "SAM: no native socket"); } void SAMSocket::HandleWriteI2PDataImmediate(const boost::system::error_code & ec, uint8_t * buff) { delete [] buff; } - + void SAMSocket::WriteI2PData(size_t sz) { - boost::asio::async_write ( - m_Socket, - boost::asio::buffer (m_StreamBuffer, sz), - boost::asio::transfer_all(), - std::bind(&SAMSocket::HandleWriteI2PData, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); + uint8_t * sendbuff = new uint8_t[sz]; + memcpy(sendbuff, m_StreamBuffer, sz); + WriteI2PDataImmediate(sendbuff, sz); } - + 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) @@ -1053,13 +756,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 @@ -1070,8 +773,7 @@ namespace client { WriteI2PData(bytes_transferred); } - else - I2PReceive(); + I2PReceive(); } } } @@ -1080,7 +782,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"); } @@ -1094,48 +796,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 ()); + context.GetAddressBook ().InsertAddress (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: session->ListSockets ()) + 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 (); @@ -1144,46 +834,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) @@ -1192,151 +845,73 @@ 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 { #ifdef _MSC_VER - size_t l = sprintf_s ((char *)m_StreamBuffer, SAM_STREAM_BUFFER_SIZE, SAM_DATAGRAM_RECEIVED, base64.c_str (), (long unsigned int)len); + size_t l = sprintf_s ((char *)m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE, SAM_DATAGRAM_RECEIVED, base64.c_str (), (long unsigned int)len); #else - size_t l = snprintf ((char *)m_StreamBuffer, SAM_STREAM_BUFFER_SIZE, SAM_DATAGRAM_RECEIVED, base64.c_str (), (long unsigned int)len); + size_t l = snprintf ((char *)m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE, SAM_DATAGRAM_RECEIVED, base64.c_str (), (long unsigned int)len); #endif - if (len < SAM_STREAM_BUFFER_SIZE - l) + if (len < SAM_SOCKET_BUFFER_SIZE - l) { memcpy (m_StreamBuffer + l, buf, len); WriteI2PData(len + l); } else - LogPrint (eLogWarning, "SAM: Received datagram size ", len," exceeds buffer"); + 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) + SAMSession::SAMSession (std::shared_ptr dest): + localDestination (dest), + UDPEndpoint(nullptr) { - 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_STREAM_BUFFER_SIZE, SAM_RAW_RECEIVED, (long unsigned int)len); -#else - size_t l = snprintf ((char *)m_StreamBuffer, SAM_STREAM_BUFFER_SIZE, SAM_RAW_RECEIVED, (long unsigned int)len); -#endif - if (len < SAM_STREAM_BUFFER_SIZE - l) - { - memcpy (m_StreamBuffer + l, buf, len); - WriteI2PData(len + l); - } - else - LogPrint (eLogWarning, "SAM: Received raw 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())); - } - - SAMSession::SAMSession (SAMBridge & parent, const std::string & id, SAMSessionType type): - m_Bridge(parent), Name(id), Type (type), UDPEndpoint(nullptr) + SAMSession::~SAMSession () { + CloseStreams(); + i2p::client::context.DeleteLocalDestination (localDestination); } void SAMSession::CloseStreams () { - for(const auto & itr : m_Bridge.ListSockets(Name)) + std::vector > socks; { - itr->Terminate(nullptr); + std::lock_guard lock(m_SocketsMutex); + for (const auto& sock : m_Sockets) { + socks.push_back(sock); + } } + for (auto & sock : socks ) sock->Terminate("SAMSession::CloseStreams()"); + m_Sockets.clear(); } - 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), - 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}, - {"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}, - } + 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) { } SAMBridge::~SAMBridge () { - if (IsRunning ()) + if (m_IsRunning) Stop (); } @@ -1344,50 +919,49 @@ namespace client { Accept (); ReceiveDatagram (); - StartIOService (); + m_IsRunning = true; + m_Thread = new std::thread (std::bind (&SAMBridge::Run, this)); } void SAMBridge::Stop () { - try + m_IsRunning = false; + m_Acceptor.cancel (); + for (auto& it: m_Sessions) + it.second->CloseStreams (); + m_Sessions.clear (); + m_Service.stop (); + if (m_Thread) { - m_Acceptor.cancel (); - } - catch (const std::exception& ex) - { - LogPrint (eLogError, "SAM: Runtime exception: ", ex.what ()); + m_Thread->join (); + delete m_Thread; + m_Thread = nullptr; } + } - decltype(m_Sessions) sessions; + void SAMBridge::Run () + { + while (m_IsRunning) { - std::unique_lock l(m_SessionsMutex); - m_Sessions.swap (sessions); - } - for (auto& it: sessions) - it.second->Close (); - - StopIOService (); + try + { + m_Service.run (); + } + catch (std::exception& ex) + { + LogPrint (eLogError, "SAM: runtime exception: ", ex.what ()); + } + } } void SAMBridge::Accept () { - auto newSocket = std::make_shared(*this); - m_Acceptor.async_accept (newSocket->GetSocket(), std::bind (&SAMBridge::HandleAccept, this, + auto native = std::make_shared(m_Service); + auto newSocket = std::make_shared (*this, native); + m_Acceptor.async_accept (*native, std::bind (&SAMBridge::HandleAccept, this, 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) @@ -1396,31 +970,28 @@ 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); 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 { @@ -1431,32 +1002,18 @@ 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); - } + // TODO: extract string values + signatureType = std::stoi(it->second); it = params->find (SAM_PARAM_CRYPTO_TYPE); if (it != params->end ()) - { - try - { - cryptoType = std::stoi(it->second); - } - catch (const std::exception& ex) - { - LogPrint (eLogWarning, "SAM: ", SAM_PARAM_CRYPTO_TYPE, "error: ", ex.what ()); - } - } + cryptoType = std::stoi(it->second); } - 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(localDestination); std::unique_lock l(m_SessionsMutex); auto ret = m_Sessions.insert (std::make_pair(id, session)); if (!ret.second) @@ -1466,13 +1023,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; @@ -1487,42 +1037,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); @@ -1532,21 +1052,12 @@ namespace client return nullptr; } - std::list > SAMBridge::ListSockets(const std::string & id) const + void SAMBridge::SendTo(const uint8_t * buf, size_t len, std::shared_ptr remote) { - std::list > list; + if(remote) { - std::unique_lock l(m_OpenSocketsMutex); - for (const auto & itr : m_OpenSockets) - if (itr->IsSession(id)) - list.push_back(itr); + m_DatagramSocket.send_to(boost::asio::buffer(buf, len), *remote); } - return list; - } - - void SAMBridge::SendTo (const std::vector& bufs, const boost::asio::ip::udp::endpoint& ep) - { - m_DatagramSocket.send_to (bufs, ep); } void SAMBridge::ReceiveDatagram () @@ -1563,76 +1074,37 @@ namespace client { m_DatagramReceiveBuffer[bytes_transferred] = 0; char * eol = strchr ((char *)m_DatagramReceiveBuffer, '\n'); - if(eol) + *eol = 0; eol++; + size_t payloadLen = bytes_transferred - ((uint8_t *)eol - m_DatagramReceiveBuffer); + LogPrint (eLogDebug, "SAM: datagram received ", m_DatagramReceiveBuffer," size=", payloadLen); + char * sessionID = strchr ((char *)m_DatagramReceiveBuffer, ' '); + if (sessionID) { - *eol = 0; eol++; - size_t payloadLen = bytes_transferred - ((uint8_t *)eol - m_DatagramReceiveBuffer); - LogPrint (eLogDebug, "SAM: Datagram received ", m_DatagramReceiveBuffer," size=", payloadLen); - char * sessionID = strchr ((char *)m_DatagramReceiveBuffer, ' '); - if (sessionID) + sessionID++; + char * destination = strchr (sessionID, ' '); + if (destination) { - sessionID++; - char * destination = strchr (sessionID, ' '); - if (destination) + *destination = 0; destination++; + auto session = FindSession (sessionID); + if (session) { - *destination = 0; destination++; - 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); - } - else - LogPrint (eLogError, "SAM: Session ", sessionID, " not found"); + i2p::data::IdentityEx dest; + dest.FromBase64 (destination); + session->localDestination->GetDatagramDestination ()-> + SendDatagramTo ((uint8_t *)eol, payloadLen, dest.GetIdentHash ()); } else - LogPrint (eLogError, "SAM: Missing destination key"); + LogPrint (eLogError, "SAM: Session ", sessionID, " not found"); } else - LogPrint (eLogError, "SAM: Missing sessionID"); + LogPrint (eLogError, "SAM: Missing destination key"); } else - LogPrint(eLogError, "SAM: Invalid datagram"); + LogPrint (eLogError, "SAM: Missing sessionID"); ReceiveDatagram (); } else - 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) - { - // name is not numeric, resolving - auto it = m_SignatureTypes.find (name); - if (it != m_SignatureTypes.end ()) - type = it->second; - else - return false; - } - catch (const std::exception& ex) - { - return false; - } - // name has been resolved - return true; + LogPrint (eLogError, "SAM: datagram receive error: ", ecode.message ()); } } } diff --git a/libi2pd_client/SAM.h b/libi2pd_client/SAM.h index cd619678..931d0f2b 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" @@ -29,45 +19,32 @@ namespace i2p namespace client { const size_t SAM_SOCKET_BUFFER_SIZE = 8192; - const size_t SAM_STREAM_BUFFER_SIZE = 16384; - const size_t SAM_STREAM_MAX_SEND_BUFFER_SIZE = 8*SAM_SOCKET_BUFFER_SIZE; const int SAM_SOCKET_CONNECTION_MAX_IDLE = 3600; // in seconds - const int SAM_SESSION_READINESS_CHECK_INTERVAL = 3; // in seconds - const size_t SAM_SESSION_MAX_ACCEPT_QUEUE_SIZE = 50; - const size_t SAM_SESSION_MAX_ACCEPT_INTERVAL = 3; // in seconds - + const 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"; const char SAM_HANDSHAKE_I2P_ERROR[] = "HELLO REPLY RESULT=I2P_ERROR\n"; const char SAM_SESSION_CREATE[] = "SESSION CREATE"; const char SAM_SESSION_CREATE_REPLY_OK[] = "SESSION STATUS RESULT=OK DESTINATION=%s\n"; const char SAM_SESSION_CREATE_DUPLICATED_ID[] = "SESSION STATUS RESULT=DUPLICATED_ID\n"; const char SAM_SESSION_CREATE_DUPLICATED_DEST[] = "SESSION STATUS RESULT=DUPLICATED_DEST\n"; - const char SAM_SESSION_CREATE_INVALID_ID[] = "SESSION STATUS RESULT=INVALID_ID\n"; const char SAM_SESSION_STATUS_INVALID_KEY[] = "SESSION STATUS RESULT=INVALID_KEY\n"; - 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_NAMING_REPLY_KEY_NOT_FOUND[] = "NAMING REPLY RESULT=INVALID_KEY_NOT_FOUND NAME=%s\n"; const char SAM_PARAM_MIN[] = "MIN"; const char SAM_PARAM_MAX[] = "MAX"; const char SAM_PARAM_STYLE[] = "STYLE"; @@ -78,16 +55,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 { @@ -95,7 +70,6 @@ namespace client eSAMSocketTypeSession, eSAMSocketTypeStream, eSAMSocketTypeAcceptor, - eSAMSocketTypeForward, eSAMSocketTypeTerminated }; @@ -106,21 +80,17 @@ namespace client public: typedef boost::asio::ip::tcp::socket Socket_t; - SAMSocket (SAMBridge& owner); - ~SAMSocket (); + SAMSocket (SAMBridge& owner, std::shared_ptr socket); + ~SAMSocket (); - Socket_t& GetSocket () { return m_Socket; }; + boost::asio::ip::tcp::socket& GetSocket () { return *m_Socket; }; void ReceiveHandshake (); void SetSocketType (SAMSocketType socketType) { m_SocketType = socketType; }; SAMSocketType GetSocketType () const { return m_SocketType; }; void Terminate (const char* reason); - bool IsSession(const std::string & id) const; - - private: - - void TerminateClose() { Terminate(nullptr); } + private: 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); @@ -133,30 +103,22 @@ 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 HandleNamingLookupLeaseSetRequestComplete (std::shared_ptr leaseSet, std::string name); + void SendNamingLookupReply (std::shared_ptr identity); + void HandleNamingLookupLeaseSetRequestComplete (std::shared_ptr leaseSet, i2p::data::IdentHash ident); void HandleSessionReadinessCheckTimer (const boost::system::error_code& ecode); void SendSessionCreateReplyOk (); @@ -164,132 +126,96 @@ namespace client void WriteI2PDataImmediate(uint8_t * ptr, size_t sz); void HandleWriteI2PDataImmediate(const boost::system::error_code & ec, uint8_t * buff); - void HandleStreamSend(const boost::system::error_code & ec); - private: SAMBridge& m_Owner; - Socket_t m_Socket; + std::shared_ptr m_Socket; boost::asio::deadline_timer m_Timer; char m_Buffer[SAM_SOCKET_BUFFER_SIZE + 1]; - size_t m_BufferOffset; // for session only - uint8_t m_StreamBuffer[SAM_STREAM_BUFFER_SIZE]; + size_t m_BufferOffset; + uint8_t m_StreamBuffer[SAM_SOCKET_BUFFER_SIZE]; SAMSocketType m_SocketType; std::string m_ID; // nickname bool m_IsSilent; bool m_IsAccepting; // for eSAMSocketTypeAcceptor only - bool m_IsReceiving; // for eSAMSocketTypeStream only std::shared_ptr m_Stream; }; - enum SAMSessionType - { - eSAMSessionTypeUnknown, - eSAMSessionTypeStream, - eSAMSessionTypeDatagram, - eSAMSessionTypeRaw, - eSAMSessionTypeMaster - }; - struct SAMSession { - SAMBridge & m_Bridge; - 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 () {}; + std::shared_ptr localDestination; + std::list > m_Sockets; + std::shared_ptr UDPEndpoint; + std::mutex m_SocketsMutex; - virtual std::shared_ptr GetLocalDestination () = 0; - virtual void StopLocalDestination () = 0; - virtual void Close () { CloseStreams (); }; + /** safely add a socket to this session */ + void AddSocket(std::shared_ptr sock) { + std::lock_guard lock(m_SocketsMutex); + m_Sockets.push_back(sock); + } + + /** safely remove a socket from this session */ + void DelSocket(SAMSocket * sock) { + std::lock_guard lock(m_SocketsMutex); + m_Sockets.remove_if([sock](const std::shared_ptr s) -> bool { return s.get() == sock; }); + } + + /** get a list holding a copy of all sam sockets from this session */ + std::list > ListSockets() { + std::list > l; + { + std::lock_guard lock(m_SocketsMutex); + for(const auto& sock : m_Sockets ) l.push_back(sock); + } + return l; + } + + SAMSession (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 AddSocket(std::shared_ptr socket); - void RemoveSocket(const std::shared_ptr & socket); - - bool ResolveSignatureType (const std::string& name, i2p::data::SigningKeyType& type) const; + void SendTo(const uint8_t * buf, size_t len, std::shared_ptr remote); 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; mutable std::mutex m_SessionsMutex; std::map > m_Sessions; - mutable std::mutex m_OpenSocketsMutex; - std::list > m_OpenSockets; uint8_t m_DatagramReceiveBuffer[i2p::datagram::MAX_DATAGRAM_SIZE+1]; - std::map m_SignatureTypes; public: diff --git a/libi2pd_client/SOCKS.cpp b/libi2pd_client/SOCKS.cpp index 27df33c8..ddf4bbe9 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, @@ -95,8 +84,8 @@ namespace proxy SOCKS5_HOST_UNREACH = 4, // Host unreachable SOCKS5_CONN_REFUSED = 5, // Connection refused by the peer SOCKS5_TTL_EXPIRED = 6, // TTL Expired - SOCKS5_CMD_UNSUP = 7, // Command unsupported - SOCKS5_ADDR_UNSUP = 8, // Address type unsupported + SOCKS5_CMD_UNSUP = 7, // Command unsuported + SOCKS5_ADDR_UNSUP = 8, // Address type unsuported SOCKS4_OK = 90, // No error for SOCKS4 SOCKS4_FAIL = 91, // Failed establishing connecting or not allowed SOCKS4_IDENTD_MISSING = 92, // Couldn't connect to the identd server @@ -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..0b8062d3 --- /dev/null +++ b/libi2pd_client/WebSocks.cpp @@ -0,0 +1,467 @@ +#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); + } + + 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, + 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]; + + WebSocksConn(const ServerConn & conn, WebSocksImpl * parent) : + IWebSocksConn(parent->Parent), + m_Conn(conn), + m_Stream(nullptr), + m_State(eWSCInitial), + m_Parent(parent) + { + + } + + ~WebSocksConn() + { + Close(); + } + + 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 { + 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 eWSCOkayConnect: + if(state == eWSCClose) { + // graceful close + m_State = eWSCClose; + Close(); + } else { + LogPrint(eLogWarning, "websocks: invalid state change ", m_State, " -> ", state); + } + 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); + } + EnterState(eWSCTryConnect); + } 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(); + 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..dd7c892d --- /dev/null +++ b/qt/i2pd_qt/DaemonQT.cpp @@ -0,0 +1,180 @@ +#include "DaemonQT.h" +#include "Daemon.h" +#include "mainwindow.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[]) + { + mutex=new QMutex(QMutex::Recursive); + setRunningCallback(0); + m_IsRunning=false; + return Daemon.init(argc,argv); + } + + 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; + + { + DaemonQTImpl daemon; + qDebug("Initialising the daemon..."); + bool daemonInitSuccess = daemon.init(argc, argv); + if(!daemonInitSuccess) + { + QMessageBox::critical(0, "Error", "Daemon init failed"); + return 1; + } + qDebug("Initialised, creating the main window..."); + MainWindow w; + qDebug("Before main window.show()..."); + w.show (); + + { + i2p::qt::Controller daemonQtController(daemon); + w.setI2PController(&daemonQtController); + qDebug("Starting the daemon..."); + emit daemonQtController.startDaemon(); + //daemon.start (); + qDebug("Starting GUI event loop..."); + result = app.exec(); + //daemon.stop (); + } + } + + //QMessageBox::information(&w, "Debug", "demon stopped"); + qDebug("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..d0add0e3 --- /dev/null +++ b/qt/i2pd_qt/DaemonQT.h @@ -0,0 +1,87 @@ +#ifndef DAEMONQT_H +#define DAEMONQT_H + +#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[]); + 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..029a3ea2 --- /dev/null +++ b/qt/i2pd_qt/ServerTunnelPane.cpp @@ -0,0 +1,279 @@ +#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); + } + { + uint32_t maxConns = tunnelConfig->getmaxConns(); + QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); + horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); + ui.maxConnsLabel = new QLabel(gridLayoutWidget_2); + maxConnsLabel->setObjectName(QStringLiteral("maxConnsLabel")); + horizontalLayout_2->addWidget(maxConnsLabel); + ui.maxConnsLineEdit = new QLineEdit(gridLayoutWidget_2); + maxConnsLineEdit->setObjectName(QStringLiteral("maxConnsLineEdit")); + maxConnsLineEdit->setText(QString::number(maxConns)); + maxConnsLineEdit->setMaximumWidth(80); + QObject::connect(maxConnsLineEdit, SIGNAL(textChanged(const QString &)), + this, SLOT(updated())); + horizontalLayout_2->addWidget(maxConnsLineEdit); + 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..556c8473 --- /dev/null +++ b/qt/i2pd_qt/ServerTunnelPane.h @@ -0,0 +1,172 @@ +#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; + + //maxConns + QLabel * maxConnsLabel; + QLineEdit * maxConnsLineEdit; + + //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)); + maxConnsLabel->setText(QApplication::translate("srvTunForm", "Max connections:", 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()); + + auto mcStr=maxConnsLineEdit->text(); + uint32_t mcInt=(uint32_t)mcStr.toInt(&ok); + if(!ok){ + highlightWrongInput(QApplication::tr("Bad maxConns, must be int.")+" "+cannotSaveSettings,maxConnsLineEdit); + return false; + } + stc->setmaxConns(mcInt); + + 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..41245dac --- /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 const 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..81216c0b --- /dev/null +++ b/qt/i2pd_qt/TunnelConfig.cpp @@ -0,0 +1,54 @@ +#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" + << "keys=" << keys << "\n" + << "destinationport=" << destinationPort << "\n" + << "signaturetype=" << sigType << "\n"; +} + + +void ServerTunnelConfig::saveToStringStream(std::stringstream& out) { + out << "host=" << host << "\n" + << "port=" << port << "\n" + << "keys=" << keys << "\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" + << "maxconns=" << maxConns << "\n"; +} + diff --git a/qt/i2pd_qt/TunnelConfig.h b/qt/i2pd_qt/TunnelConfig.h new file mode 100644 index 00000000..c714a4f5 --- /dev/null +++ b/qt/i2pd_qt/TunnelConfig.h @@ -0,0 +1,232 @@ +#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); + uint32_t maxConns = section.second.get(i2p::stream::I2CP_PARAM_STREAMING_MAX_CONNS_PER_MIN, i2p::stream::DEFAULT_MAX_CONNS_PER_MIN); + 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; + uint32_t maxConns; + 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_, + uint32_t maxConns_, + 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_), + maxConns(maxConns_), + 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;} + uint32_t getmaxConns(){return maxConns;} + 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 setmaxConns(uint32_t maxConns_){maxConns=maxConns_;} + 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/android/.gitignore b/qt/i2pd_qt/android/.gitignore new file mode 100644 index 00000000..2c41ac3e --- /dev/null +++ b/qt/i2pd_qt/android/.gitignore @@ -0,0 +1 @@ +/gen/ diff --git a/qt/i2pd_qt/android/AndroidManifest.xml b/qt/i2pd_qt/android/AndroidManifest.xml new file mode 100644 index 00000000..56b37e6a --- /dev/null +++ b/qt/i2pd_qt/android/AndroidManifest.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/qt/i2pd_qt/android/build.gradle b/qt/i2pd_qt/android/build.gradle new file mode 100644 index 00000000..ef416b0b --- /dev/null +++ b/qt/i2pd_qt/android/build.gradle @@ -0,0 +1,57 @@ +buildscript { + repositories { + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:1.1.0' + } +} + +allprojects { + repositories { + jcenter() + } +} + +apply plugin: 'com.android.application' + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) +} + +android { + /******************************************************* + * The following variables: + * - androidBuildToolsVersion, + * - androidCompileSdkVersion + * - qt5AndroidDir - holds the path to qt android files + * needed to build any Qt application + * on Android. + * + * are defined in gradle.properties file. This file is + * updated by QtCreator and androiddeployqt tools. + * Changing them manually might break the compilation! + *******************************************************/ + + compileSdkVersion androidCompileSdkVersion.toInteger() + + buildToolsVersion androidBuildToolsVersion + + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = [qt5AndroidDir + '/src', 'src', 'java'] + aidl.srcDirs = [qt5AndroidDir + '/src', 'src', 'aidl'] + res.srcDirs = [qt5AndroidDir + '/res', 'res'] + resources.srcDirs = ['src'] + renderscript.srcDirs = ['src'] + assets.srcDirs = ['assets'] + jniLibs.srcDirs = ['libs'] + } + } + + lintOptions { + abortOnError false + } +} diff --git a/qt/i2pd_qt/android/libs/android-support-v4.jar b/qt/i2pd_qt/android/libs/android-support-v4.jar new file mode 100644 index 00000000..2ff47f4f Binary files /dev/null and b/qt/i2pd_qt/android/libs/android-support-v4.jar differ diff --git a/qt/i2pd_qt/android/project.properties b/qt/i2pd_qt/android/project.properties new file mode 100644 index 00000000..4d07452b --- /dev/null +++ b/qt/i2pd_qt/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-11 diff --git a/qt/i2pd_qt/android/res/drawable-hdpi/icon.png b/qt/i2pd_qt/android/res/drawable-hdpi/icon.png new file mode 100644 index 00000000..9a2f7404 Binary files /dev/null and b/qt/i2pd_qt/android/res/drawable-hdpi/icon.png differ diff --git a/qt/i2pd_qt/android/res/drawable/itoopie_notification_icon.png b/qt/i2pd_qt/android/res/drawable/itoopie_notification_icon.png new file mode 100644 index 00000000..fa99e7fc Binary files /dev/null and b/qt/i2pd_qt/android/res/drawable/itoopie_notification_icon.png differ diff --git a/qt/i2pd_qt/android/res/layout/splash.xml b/qt/i2pd_qt/android/res/layout/splash.xml new file mode 100644 index 00000000..476d91a8 --- /dev/null +++ b/qt/i2pd_qt/android/res/layout/splash.xml @@ -0,0 +1,4 @@ + + diff --git a/qt/i2pd_qt/android/res/values-de/strings.xml b/qt/i2pd_qt/android/res/values-de/strings.xml new file mode 100644 index 00000000..320d9ec3 --- /dev/null +++ b/qt/i2pd_qt/android/res/values-de/strings.xml @@ -0,0 +1,6 @@ + + + Ministro-Dienst wurde nicht gefunden.\nAnwendung kann nicht gestartet werden + Diese Anwendung benötigt den Ministro-Dienst. Möchten Sie ihn installieren? + In Ihrer Anwendung ist ein schwerwiegender Fehler aufgetreten, sie kann nicht fortgesetzt werden + diff --git a/qt/i2pd_qt/android/res/values-el/strings.xml b/qt/i2pd_qt/android/res/values-el/strings.xml new file mode 100644 index 00000000..3cab212f --- /dev/null +++ b/qt/i2pd_qt/android/res/values-el/strings.xml @@ -0,0 +1,6 @@ + + + Δεν ήταν δυνατή η εύρεση της υπηρεσίας Ministro. Δεν είναι δυνατή η εκκίνηση της εφαρμογής. + Η εφαρμογή απαιτεί την υπηρεσία Ministro. Να εγκατασταθεί η υπηρεσία? + Παρουσιάστηκε ένα κρίσιμο σφάλμα και η εφαρμογή δεν μπορεί να συνεχίσει. + diff --git a/qt/i2pd_qt/android/res/values-es/strings.xml b/qt/i2pd_qt/android/res/values-es/strings.xml new file mode 100644 index 00000000..cf0b54d0 --- /dev/null +++ b/qt/i2pd_qt/android/res/values-es/strings.xml @@ -0,0 +1,6 @@ + + + Servicio Ministro inesistente. Imposible ejecutar la aplicación. + Esta aplicación requiere el servicio Ministro. Instalarlo? + La aplicación ha causado un error grave y no es posible continuar. + diff --git a/qt/i2pd_qt/android/res/values-et/strings.xml b/qt/i2pd_qt/android/res/values-et/strings.xml new file mode 100644 index 00000000..d55a3c14 --- /dev/null +++ b/qt/i2pd_qt/android/res/values-et/strings.xml @@ -0,0 +1,6 @@ + + + Ei suuda leida Ministro teenust.\nProgrammi ei saa käivitada. + See programm vajab Ministro teenust.\nKas soovite paigaldada? + Programmiga juhtus fataalne viga.\nKahjuks ei saa jätkata. + diff --git a/qt/i2pd_qt/android/res/values-fa/strings.xml b/qt/i2pd_qt/android/res/values-fa/strings.xml new file mode 100644 index 00000000..a8d1b874 --- /dev/null +++ b/qt/i2pd_qt/android/res/values-fa/strings.xml @@ -0,0 +1,6 @@ + + + سرویس Ministro را پیدا نمی‌کند. برنامه نمی‌تواند آغاز شود. + این نرم‌افزار به سرویس Ministro احتیاج دارد. آیا دوست دارید آن را نصب کنید؟ + خطایی اساسی در برنامه‌تان رخ داد و اجرای برنامه نمی‌تواند ادامه یابد. + diff --git a/qt/i2pd_qt/android/res/values-fr/strings.xml b/qt/i2pd_qt/android/res/values-fr/strings.xml new file mode 100644 index 00000000..efc0fb6e --- /dev/null +++ b/qt/i2pd_qt/android/res/values-fr/strings.xml @@ -0,0 +1,6 @@ + + + Le service Ministro est introuvable.\nL\'application ne peut pas démarrer. + Cette application requiert le service Ministro. Voulez-vous l\'installer? + Votre application a rencontré une erreur fatale et ne peut pas continuer. + diff --git a/qt/i2pd_qt/android/res/values-id/strings.xml b/qt/i2pd_qt/android/res/values-id/strings.xml new file mode 100644 index 00000000..aaa5bda0 --- /dev/null +++ b/qt/i2pd_qt/android/res/values-id/strings.xml @@ -0,0 +1,6 @@ + + + Layanan Ministro tidak bisa ditemukan.\nAplikasi tidak bisa dimulai. + Aplikasi ini membutuhkan layanan Ministro. Apakah Anda ingin menginstalnya? + Aplikasi Anda mengalami kesalahan fatal dan tidak dapat melanjutkan. + diff --git a/qt/i2pd_qt/android/res/values-it/strings.xml b/qt/i2pd_qt/android/res/values-it/strings.xml new file mode 100644 index 00000000..4773419c --- /dev/null +++ b/qt/i2pd_qt/android/res/values-it/strings.xml @@ -0,0 +1,6 @@ + + + Servizio Ministro inesistente. Impossibile eseguire \nl\'applicazione. + Questa applicazione richiede il servizio Ministro.Installarlo? + L\'applicazione ha provocato un errore grave e non puo\' continuare. + diff --git a/qt/i2pd_qt/android/res/values-ja/strings.xml b/qt/i2pd_qt/android/res/values-ja/strings.xml new file mode 100644 index 00000000..ba1cfda9 --- /dev/null +++ b/qt/i2pd_qt/android/res/values-ja/strings.xml @@ -0,0 +1,6 @@ + + + Ministroサービスが見つかりません。\nアプリケーションが起動できません。 + このアプリケーションにはMinistroサービスが必要です。 インストールしてもよろしいですか? + アプリケーションで致命的なエラーが発生したため続行できません。 + diff --git a/qt/i2pd_qt/android/res/values-ms/strings.xml b/qt/i2pd_qt/android/res/values-ms/strings.xml new file mode 100644 index 00000000..6e3952ea --- /dev/null +++ b/qt/i2pd_qt/android/res/values-ms/strings.xml @@ -0,0 +1,6 @@ + + + Tidak jumpa servis Ministro.\nAplikasi tidak boleh dimulakan. + Aplikasi ini memerlukan servis Ministro. Adakah anda ingin pasang servis itu? + Aplikasi anda menemui ralat muat dan tidak boleh diteruskan. + diff --git a/qt/i2pd_qt/android/res/values-nb/strings.xml b/qt/i2pd_qt/android/res/values-nb/strings.xml new file mode 100644 index 00000000..8a550e99 --- /dev/null +++ b/qt/i2pd_qt/android/res/values-nb/strings.xml @@ -0,0 +1,6 @@ + + + Kan ikke finne tjenesten Ministro. Applikasjonen kan ikke starte. + Denne applikasjonen krever tjenesten Ministro. Vil du installere denne? + Applikasjonen fikk en kritisk feil og kan ikke fortsette + diff --git a/qt/i2pd_qt/android/res/values-nl/strings.xml b/qt/i2pd_qt/android/res/values-nl/strings.xml new file mode 100644 index 00000000..8a45a724 --- /dev/null +++ b/qt/i2pd_qt/android/res/values-nl/strings.xml @@ -0,0 +1,6 @@ + + + De Ministro service is niet gevonden.\nDe applicatie kan niet starten. + Deze applicatie maakt gebruik van de Ministro service. Wilt u deze installeren? + Er is een fatale fout in de applicatie opgetreden. De applicatie kan niet verder gaan. + diff --git a/qt/i2pd_qt/android/res/values-pl/strings.xml b/qt/i2pd_qt/android/res/values-pl/strings.xml new file mode 100644 index 00000000..9fefc92d --- /dev/null +++ b/qt/i2pd_qt/android/res/values-pl/strings.xml @@ -0,0 +1,6 @@ + + + Usługa Ministro nie została znaleziona.\nAplikacja nie może zostać uruchomiona. + Aplikacja wymaga usługi Ministro. Czy chcesz ją zainstalować? + Wystąpił błąd krytyczny. Aplikacja zostanie zamknięta. + diff --git a/qt/i2pd_qt/android/res/values-pt-rBR/strings.xml b/qt/i2pd_qt/android/res/values-pt-rBR/strings.xml new file mode 100644 index 00000000..67ac3f9f --- /dev/null +++ b/qt/i2pd_qt/android/res/values-pt-rBR/strings.xml @@ -0,0 +1,6 @@ + + + Não foi possível encontrar o serviço Ministro.\nA aplicação não pode iniciar. + Essa aplicação requer o serviço Ministro. Gostaria de instalá-lo? + Sua aplicação encontrou um erro fatal e não pode continuar. + diff --git a/qt/i2pd_qt/android/res/values-ro/strings.xml b/qt/i2pd_qt/android/res/values-ro/strings.xml new file mode 100644 index 00000000..f88a442b --- /dev/null +++ b/qt/i2pd_qt/android/res/values-ro/strings.xml @@ -0,0 +1,6 @@ + + + Serviciul Ministro nu poate fi găsit.\nAplicaţia nu poate porni. + Această aplicaţie necesită serviciul Ministro.\nDoriţi să-l instalaţi? + Aplicaţia dumneavoastră a întâmpinat o eroare fatală şi nu poate continua. + diff --git a/qt/i2pd_qt/android/res/values-rs/strings.xml b/qt/i2pd_qt/android/res/values-rs/strings.xml new file mode 100644 index 00000000..3194ce90 --- /dev/null +++ b/qt/i2pd_qt/android/res/values-rs/strings.xml @@ -0,0 +1,6 @@ + + + Ministro servise nije pronađen. Aplikacija ne može biti pokrenuta. + Ova aplikacija zahteva Ministro servis. Želite li da ga instalirate? + Vaša aplikacija je naišla na fatalnu grešku i ne može nastaviti sa radom. + diff --git a/qt/i2pd_qt/android/res/values-ru/strings.xml b/qt/i2pd_qt/android/res/values-ru/strings.xml new file mode 100644 index 00000000..d3cee80f --- /dev/null +++ b/qt/i2pd_qt/android/res/values-ru/strings.xml @@ -0,0 +1,6 @@ + + + Сервис Ministro не найден.\nПриложение нельзя запустить. + Этому приложению необходим сервис Ministro. Вы хотите его установить? + Ваше приложение столкнулось с фатальной ошибкой и не может более работать. + diff --git a/qt/i2pd_qt/android/res/values-zh-rCN/strings.xml b/qt/i2pd_qt/android/res/values-zh-rCN/strings.xml new file mode 100644 index 00000000..2eb12698 --- /dev/null +++ b/qt/i2pd_qt/android/res/values-zh-rCN/strings.xml @@ -0,0 +1,6 @@ + + + 无法找到Ministro服务。\n应用程序无法启动。 + 此应用程序需要Ministro服务。您想安装它吗? + 您的应用程序遇到一个致命错误导致它无法继续。 + diff --git a/qt/i2pd_qt/android/res/values-zh-rTW/strings.xml b/qt/i2pd_qt/android/res/values-zh-rTW/strings.xml new file mode 100644 index 00000000..f6e68efa --- /dev/null +++ b/qt/i2pd_qt/android/res/values-zh-rTW/strings.xml @@ -0,0 +1,6 @@ + + + 無法找到Ministro服務。\n應用程序無法啟動。 + 此應用程序需要Ministro服務。您想安裝它嗎? + 您的應用程序遇到一個致命錯誤導致它無法繼續。 + diff --git a/qt/i2pd_qt/android/res/values/libs.xml b/qt/i2pd_qt/android/res/values/libs.xml new file mode 100644 index 00000000..4d68673c --- /dev/null +++ b/qt/i2pd_qt/android/res/values/libs.xml @@ -0,0 +1,25 @@ + + + + https://download.qt-project.org/ministro/android/qt5/qt-5.4 + + + + + + + + + + + + + + + + + + + + diff --git a/qt/i2pd_qt/android/res/values/strings.xml b/qt/i2pd_qt/android/res/values/strings.xml new file mode 100644 index 00000000..713c7aa0 --- /dev/null +++ b/qt/i2pd_qt/android/res/values/strings.xml @@ -0,0 +1,10 @@ + + + + Can\'t find Ministro service.\nThe application can\'t start. + This application requires Ministro service. Would you like to install it? + Your application encountered a fatal error and cannot continue. + i2pd started + i2pd stopped + i2pd + diff --git a/qt/i2pd_qt/android/src/org/kde/necessitas/ministro/IMinistro.aidl b/qt/i2pd_qt/android/src/org/kde/necessitas/ministro/IMinistro.aidl new file mode 100644 index 00000000..bbd8116d --- /dev/null +++ b/qt/i2pd_qt/android/src/org/kde/necessitas/ministro/IMinistro.aidl @@ -0,0 +1,60 @@ +/* + Copyright (c) 2011-2013, BogDan Vatra + Contact: http://www.qt.io/licensing/ + + Commercial License Usage + Licensees holding valid commercial Qt licenses may use this file in + accordance with the commercial license agreement provided with the + Software or, alternatively, in accordance with the terms contained in + a written agreement between you and The Qt Company. For licensing terms + and conditions see http://www.qt.io/terms-conditions. For further + information use the contact form at http://www.qt.io/contact-us. + + BSD License Usage + Alternatively, this file may be used under the BSD license as follows: + 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 AUTHOR ``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 AUTHOR 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. +*/ + + +package org.kde.necessitas.ministro; + +import org.kde.necessitas.ministro.IMinistroCallback; + +interface IMinistro +{ +/** +* Check/download required libs to run the application +* +* param callback - interface used by Minsitro service to notify the client when the loader is ready +* param parameters +* parameters fields: +* * Key Name Key type Explanations +* "sources" StringArray Sources list from where Ministro will download the libs. Make sure you are using ONLY secure locations. +* "repository" String Overwrites the default Ministro repository. Possible values: default, stable, testing and unstable +* "required.modules" StringArray Required modules by your application +* "application.title" String Application name, used to show more informations to user +* "qt.provider" String Qt libs provider, currently only "necessitas" is supported. +* "minimum.ministro.api" Integer Minimum Ministro API level, used to check if Ministro service compatible with your application. Current API Level is 3 ! +* "minimum.qt.version" Integer Minimim Qt version (e.g. 0x040800, which means Qt 4.8.0, check http://qt-project.org/doc/qt-4.8/qtglobal.html#QT_VERSION)! +*/ + void requestLoader(in IMinistroCallback callback, in Bundle parameters); +} diff --git a/qt/i2pd_qt/android/src/org/kde/necessitas/ministro/IMinistroCallback.aidl b/qt/i2pd_qt/android/src/org/kde/necessitas/ministro/IMinistroCallback.aidl new file mode 100644 index 00000000..f19caa69 --- /dev/null +++ b/qt/i2pd_qt/android/src/org/kde/necessitas/ministro/IMinistroCallback.aidl @@ -0,0 +1,65 @@ +/* + Copyright (c) 2011-2013, BogDan Vatra + Contact: http://www.qt.io/licensing/ + + Commercial License Usage + Licensees holding valid commercial Qt licenses may use this file in + accordance with the commercial license agreement provided with the + Software or, alternatively, in accordance with the terms contained in + a written agreement between you and The Qt Company. For licensing terms + and conditions see http://www.qt.io/terms-conditions. For further + information use the contact form at http://www.qt.io/contact-us. + + BSD License Usage + Alternatively, this file may be used under the BSD license as follows: + 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 AUTHOR ``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 AUTHOR 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. +*/ + +package org.kde.necessitas.ministro; + +oneway interface IMinistroCallback { +/** +* This method is called by the Ministro service back into the application which +* implements this interface. +* +* param in - loaderParams +* loaderParams fields: +* * Key Name Key type Explanations +* * "error.code" Integer See below +* * "error.message" String Missing if no error, otherwise will contain the error message translated into phone language where available. +* * "dex.path" String The list of jar/apk files containing classes and resources, needed to be passed to application DexClassLoader +* * "lib.path" String The list of directories containing native libraries; may be missing, needed to be passed to application DexClassLoader +* * "loader.class.name" String Loader class name. +* +* "error.code" field possible errors: +* - 0 no error. +* - 1 incompatible Ministro version. Ministro needs to be upgraded. +* - 2 not all modules could be satisfy. +* - 3 invalid parameters +* - 4 invalid qt version +* - 5 download canceled +* +* The parameter contains additional fields which are used by the loader to start your application, so it must be passed to the loader. +*/ + + void loaderReady(in Bundle loaderParams); +} diff --git a/qt/i2pd_qt/android/src/org/purplei2p/i2pd/I2PDMainActivity.java b/qt/i2pd_qt/android/src/org/purplei2p/i2pd/I2PDMainActivity.java new file mode 100644 index 00000000..aea50bf6 --- /dev/null +++ b/qt/i2pd_qt/android/src/org/purplei2p/i2pd/I2PDMainActivity.java @@ -0,0 +1,97 @@ +package org.purplei2p.i2pd; + +import org.qtproject.qt5.android.bindings.QtActivity; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Bundle; +import android.os.IBinder; + +public class I2PDMainActivity extends QtActivity +{ + + private static I2PDMainActivity instance; + + public I2PDMainActivity() {} + + /* (non-Javadoc) + * @see org.qtproject.qt5.android.bindings.QtActivity#onCreate(android.os.Bundle) + */ + @Override + public void onCreate(Bundle savedInstanceState) { + I2PDMainActivity.setInstance(this); + super.onCreate(savedInstanceState); + + //set the app be foreground (do not unload when RAM needed) + doBindService(); + } + + /* (non-Javadoc) + * @see org.qtproject.qt5.android.bindings.QtActivity#onDestroy() + */ + @Override + protected void onDestroy() { + I2PDMainActivity.setInstance(null); + doUnbindService(); + super.onDestroy(); + } + + public static I2PDMainActivity getInstance() { + return instance; + } + + private static void setInstance(I2PDMainActivity instance) { + I2PDMainActivity.instance = instance; + } + + + +// private LocalService mBoundService; + + private ServiceConnection mConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + // This is called when the connection with the service has been + // established, giving us the service object we can use to + // interact with the service. Because we have bound to a explicit + // service that we know is running in our own process, we can + // cast its IBinder to a concrete class and directly access it. +// mBoundService = ((LocalService.LocalBinder)service).getService(); + + // Tell the user about this for our demo. +// Toast.makeText(Binding.this, R.string.local_service_connected, +// Toast.LENGTH_SHORT).show(); + } + + public void onServiceDisconnected(ComponentName className) { + // This is called when the connection with the service has been + // unexpectedly disconnected -- that is, its process crashed. + // Because it is running in our same process, we should never + // see this happen. +// mBoundService = null; +// Toast.makeText(Binding.this, R.string.local_service_disconnected, +// Toast.LENGTH_SHORT).show(); + } + }; + + private boolean mIsBound; + + private void doBindService() { + // Establish a connection with the service. We use an explicit + // class name because we want a specific service implementation that + // we know will be running in our own process (and thus won't be + // supporting component replacement by other applications). + bindService(new Intent(this, + LocalService.class), mConnection, Context.BIND_AUTO_CREATE); + mIsBound = true; + } + + void doUnbindService() { + if (mIsBound) { + // Detach our existing connection. + unbindService(mConnection); + mIsBound = false; + } + } +} diff --git a/qt/i2pd_qt/android/src/org/purplei2p/i2pd/LocalService.java b/qt/i2pd_qt/android/src/org/purplei2p/i2pd/LocalService.java new file mode 100644 index 00000000..e2901504 --- /dev/null +++ b/qt/i2pd_qt/android/src/org/purplei2p/i2pd/LocalService.java @@ -0,0 +1,92 @@ +package org.purplei2p.i2pd; + +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.Service; +import android.content.Intent; +import android.os.Binder; +import android.os.IBinder; +import android.support.v4.app.NotificationCompat; +import android.util.Log; +import android.widget.Toast; + +public class LocalService extends Service { +// private NotificationManager mNM; + + // Unique Identification Number for the Notification. + // We use it on Notification start, and to cancel it. + private int NOTIFICATION = R.string.local_service_started; + + /** + * Class for clients to access. Because we know this service always + * runs in the same process as its clients, we don't need to deal with + * IPC. + */ + public class LocalBinder extends Binder { + LocalService getService() { + return LocalService.this; + } + } + + @Override + public void onCreate() { +// mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); + + // Display a notification about us starting. We put an icon in the status bar. + showNotification(); + + + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + Log.i("LocalService", "Received start id " + startId + ": " + intent); + return START_NOT_STICKY; + } + + @Override + public void onDestroy() { + // Cancel the persistent notification. + //mNM.cancel(NOTIFICATION); + stopForeground(true); + + // Tell the user we stopped. + Toast.makeText(this, R.string.local_service_stopped, Toast.LENGTH_SHORT).show(); + } + + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } + + // This is the object that receives interactions from clients. See + // RemoteService for a more complete example. + private final IBinder mBinder = new LocalBinder(); + + /** + * Show a notification while this service is running. + */ + private void showNotification() { + // In this sample, we'll use the same text for the ticker and the expanded notification + CharSequence text = getText(R.string.local_service_started); + + // The PendingIntent to launch our activity if the user selects this notification + PendingIntent contentIntent = PendingIntent.getActivity(this, 0, + new Intent(this, I2PDMainActivity.class), 0); + + // Set the info for the views that show in the notification panel. + Notification notification = new NotificationCompat.Builder(this) + .setSmallIcon(R.drawable.itoopie_notification_icon) // the status icon + .setTicker(text) // the status text + .setWhen(System.currentTimeMillis()) // the time stamp + .setContentTitle(getText(R.string.local_service_label)) // the label of the entry + .setContentText(text) // the contents of the entry + .setContentIntent(contentIntent) // The intent to send when the entry is clicked + .build(); + + // Send the notification. + //mNM.notify(NOTIFICATION, notification); + startForeground(NOTIFICATION, notification); + } +} \ No newline at end of file diff --git a/qt/i2pd_qt/android/src/org/qtproject/qt5/android/bindings/QtActivity.java b/qt/i2pd_qt/android/src/org/qtproject/qt5/android/bindings/QtActivity.java new file mode 100644 index 00000000..677e8f46 --- /dev/null +++ b/qt/i2pd_qt/android/src/org/qtproject/qt5/android/bindings/QtActivity.java @@ -0,0 +1,1621 @@ +/* + Copyright (c) 2012-2013, BogDan Vatra + Contact: http://www.qt.io/licensing/ + + Commercial License Usage + Licensees holding valid commercial Qt licenses may use this file in + accordance with the commercial license agreement provided with the + Software or, alternatively, in accordance with the terms contained in + a written agreement between you and The Qt Company. For licensing terms + and conditions see http://www.qt.io/terms-conditions. For further + information use the contact form at http://www.qt.io/contact-us. + + BSD License Usage + Alternatively, this file may be used under the BSD license as follows: + 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 AUTHOR ``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 AUTHOR 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. +*/ + +package org.qtproject.qt5.android.bindings; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.io.InputStream; +import java.io.FileOutputStream; +import java.io.FileInputStream; +import java.io.DataOutputStream; +import java.io.DataInputStream; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; + +import org.kde.necessitas.ministro.IMinistro; +import org.kde.necessitas.ministro.IMinistroCallback; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.ComponentName; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageInfo; +import android.content.res.Configuration; +import android.content.res.Resources.Theme; +import android.content.res.AssetManager; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.drawable.ColorDrawable; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.AttributeSet; +import android.util.Log; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; +import android.view.Window; +import android.view.WindowManager.LayoutParams; +import android.view.accessibility.AccessibilityEvent; +import dalvik.system.DexClassLoader; + +//@ANDROID-11 +import android.app.Fragment; +import android.view.ActionMode; +import android.view.ActionMode.Callback; +//@ANDROID-11 + + +public class QtActivity extends Activity +{ + private final static int MINISTRO_INSTALL_REQUEST_CODE = 0xf3ee; // request code used to know when Ministro instalation is finished + private static final int MINISTRO_API_LEVEL = 5; // Ministro api level (check IMinistro.aidl file) + private static final int NECESSITAS_API_LEVEL = 2; // Necessitas api level used by platform plugin + private static final int QT_VERSION = 0x050100; // This app requires at least Qt version 5.1.0 + + private static final String ERROR_CODE_KEY = "error.code"; + private static final String ERROR_MESSAGE_KEY = "error.message"; + private static final String DEX_PATH_KEY = "dex.path"; + private static final String LIB_PATH_KEY = "lib.path"; + private static final String LOADER_CLASS_NAME_KEY = "loader.class.name"; + private static final String NATIVE_LIBRARIES_KEY = "native.libraries"; + private static final String ENVIRONMENT_VARIABLES_KEY = "environment.variables"; + private static final String APPLICATION_PARAMETERS_KEY = "application.parameters"; + private static final String BUNDLED_LIBRARIES_KEY = "bundled.libraries"; + private static final String BUNDLED_IN_LIB_RESOURCE_ID_KEY = "android.app.bundled_in_lib_resource_id"; + private static final String BUNDLED_IN_ASSETS_RESOURCE_ID_KEY = "android.app.bundled_in_assets_resource_id"; + private static final String MAIN_LIBRARY_KEY = "main.library"; + private static final String STATIC_INIT_CLASSES_KEY = "static.init.classes"; + private static final String NECESSITAS_API_LEVEL_KEY = "necessitas.api.level"; + private static final String EXTRACT_STYLE_KEY = "extract.android.style"; + + /// Ministro server parameter keys + private static final String REQUIRED_MODULES_KEY = "required.modules"; + private static final String APPLICATION_TITLE_KEY = "application.title"; + private static final String MINIMUM_MINISTRO_API_KEY = "minimum.ministro.api"; + private static final String MINIMUM_QT_VERSION_KEY = "minimum.qt.version"; + private static final String SOURCES_KEY = "sources"; // needs MINISTRO_API_LEVEL >=3 !!! + // Use this key to specify any 3rd party sources urls + // Ministro will download these repositories into their + // own folders, check http://community.kde.org/Necessitas/Ministro + // for more details. + + private static final String REPOSITORY_KEY = "repository"; // use this key to overwrite the default ministro repsitory + private static final String ANDROID_THEMES_KEY = "android.themes"; // themes that your application uses + + + public String APPLICATION_PARAMETERS = null; // use this variable to pass any parameters to your application, + // the parameters must not contain any white spaces + // and must be separated with "\t" + // e.g "-param1\t-param2=value2\t-param3\tvalue3" + + public String ENVIRONMENT_VARIABLES = "QT_USE_ANDROID_NATIVE_STYLE=1\tQT_USE_ANDROID_NATIVE_DIALOGS=1\t"; + // use this variable to add any environment variables to your application. + // the env vars must be separated with "\t" + // e.g. "ENV_VAR1=1\tENV_VAR2=2\t" + // Currently the following vars are used by the android plugin: + // * QT_USE_ANDROID_NATIVE_STYLE - 1 to use the android widget style if available. + // * QT_USE_ANDROID_NATIVE_DIALOGS -1 to use the android native dialogs. + + public String[] QT_ANDROID_THEMES = null; // A list with all themes that your application want to use. + // The name of the theme must be the same with any theme from + // http://developer.android.com/reference/android/R.style.html + // The most used themes are: + // * "Theme" - (fallback) check http://developer.android.com/reference/android/R.style.html#Theme + // * "Theme_Black" - check http://developer.android.com/reference/android/R.style.html#Theme_Black + // * "Theme_Light" - (default for API <=10) check http://developer.android.com/reference/android/R.style.html#Theme_Light + // * "Theme_Holo" - check http://developer.android.com/reference/android/R.style.html#Theme_Holo + // * "Theme_Holo_Light" - (default for API 11-13) check http://developer.android.com/reference/android/R.style.html#Theme_Holo_Light + // * "Theme_DeviceDefault" - check http://developer.android.com/reference/android/R.style.html#Theme_DeviceDefault + // * "Theme_DeviceDefault_Light" - (default for API 14+) check http://developer.android.com/reference/android/R.style.html#Theme_DeviceDefault_Light + + public String QT_ANDROID_DEFAULT_THEME = null; // sets the default theme. + + private static final int INCOMPATIBLE_MINISTRO_VERSION = 1; // Incompatible Ministro version. Ministro needs to be upgraded. + private static final int BUFFER_SIZE = 1024; + + private ActivityInfo m_activityInfo = null; // activity info object, used to access the libs and the strings + private DexClassLoader m_classLoader = null; // loader object + private String[] m_sources = {"https://download.qt-project.org/ministro/android/qt5/qt-5.2"}; // Make sure you are using ONLY secure locations + private String m_repository = "default"; // Overwrites the default Ministro repository + // Possible values: + // * default - Ministro default repository set with "Ministro configuration tool". + // By default the stable version is used. Only this or stable repositories should + // be used in production. + // * stable - stable repository, only this and default repositories should be used + // in production. + // * testing - testing repository, DO NOT use this repository in production, + // this repository is used to push a new release, and should be used to test your application. + // * unstable - unstable repository, DO NOT use this repository in production, + // this repository is used to push Qt snapshots. + private String[] m_qtLibs = null; // required qt libs + private int m_displayDensity = -1; + + public QtActivity() + { + if (Build.VERSION.SDK_INT <= 10) { + QT_ANDROID_THEMES = new String[] {"Theme_Light"}; + QT_ANDROID_DEFAULT_THEME = "Theme_Light"; + } + else if ((Build.VERSION.SDK_INT >= 11 && Build.VERSION.SDK_INT <= 13) || Build.VERSION.SDK_INT >= 21){ + QT_ANDROID_THEMES = new String[] {"Theme_Holo_Light"}; + QT_ANDROID_DEFAULT_THEME = "Theme_Holo_Light"; + } else { + QT_ANDROID_THEMES = new String[] {"Theme_DeviceDefault_Light"}; + QT_ANDROID_DEFAULT_THEME = "Theme_DeviceDefault_Light"; + } + } + + // this function is used to load and start the loader + private void loadApplication(Bundle loaderParams) + { + try { + final int errorCode = loaderParams.getInt(ERROR_CODE_KEY); + if (errorCode != 0) { + if (errorCode == INCOMPATIBLE_MINISTRO_VERSION) { + downloadUpgradeMinistro(loaderParams.getString(ERROR_MESSAGE_KEY)); + return; + } + + // fatal error, show the error and quit + AlertDialog errorDialog = new AlertDialog.Builder(QtActivity.this).create(); + errorDialog.setMessage(loaderParams.getString(ERROR_MESSAGE_KEY)); + errorDialog.setButton(getResources().getString(android.R.string.ok), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + finish(); + } + }); + errorDialog.show(); + return; + } + + // add all bundled Qt libs to loader params + ArrayList libs = new ArrayList(); + if ( m_activityInfo.metaData.containsKey("android.app.bundled_libs_resource_id") ) + libs.addAll(Arrays.asList(getResources().getStringArray(m_activityInfo.metaData.getInt("android.app.bundled_libs_resource_id")))); + + String libName = null; + if ( m_activityInfo.metaData.containsKey("android.app.lib_name") ) { + libName = m_activityInfo.metaData.getString("android.app.lib_name"); + loaderParams.putString(MAIN_LIBRARY_KEY, libName); //main library contains main() function + } + + loaderParams.putStringArrayList(BUNDLED_LIBRARIES_KEY, libs); + loaderParams.putInt(NECESSITAS_API_LEVEL_KEY, NECESSITAS_API_LEVEL); + + // load and start QtLoader class + m_classLoader = new DexClassLoader(loaderParams.getString(DEX_PATH_KEY), // .jar/.apk files + getDir("outdex", Context.MODE_PRIVATE).getAbsolutePath(), // directory where optimized DEX files should be written. + loaderParams.containsKey(LIB_PATH_KEY) ? loaderParams.getString(LIB_PATH_KEY) : null, // libs folder (if exists) + getClassLoader()); // parent loader + + @SuppressWarnings("rawtypes") + Class loaderClass = m_classLoader.loadClass(loaderParams.getString(LOADER_CLASS_NAME_KEY)); // load QtLoader class + Object qtLoader = loaderClass.newInstance(); // create an instance + Method prepareAppMethod = qtLoader.getClass().getMethod("loadApplication", + Activity.class, + ClassLoader.class, + Bundle.class); + if (!(Boolean)prepareAppMethod.invoke(qtLoader, this, m_classLoader, loaderParams)) + throw new Exception(""); + + QtApplication.setQtActivityDelegate(qtLoader); + + // now load the application library so it's accessible from this class loader + if (libName != null) + System.loadLibrary(libName); + + Method startAppMethod=qtLoader.getClass().getMethod("startApplication"); + if (!(Boolean)startAppMethod.invoke(qtLoader)) + throw new Exception(""); + + } catch (Exception e) { + e.printStackTrace(); + AlertDialog errorDialog = new AlertDialog.Builder(QtActivity.this).create(); + if (m_activityInfo.metaData.containsKey("android.app.fatal_error_msg")) + errorDialog.setMessage(m_activityInfo.metaData.getString("android.app.fatal_error_msg")); + else + errorDialog.setMessage("Fatal error, your application can't be started."); + + errorDialog.setButton(getResources().getString(android.R.string.ok), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + finish(); + } + }); + errorDialog.show(); + } + } + + private ServiceConnection m_ministroConnection=new ServiceConnection() { + private IMinistro m_service = null; + @Override + public void onServiceConnected(ComponentName name, IBinder service) + { + m_service = IMinistro.Stub.asInterface(service); + try { + if (m_service != null) { + Bundle parameters = new Bundle(); + parameters.putStringArray(REQUIRED_MODULES_KEY, m_qtLibs); + parameters.putString(APPLICATION_TITLE_KEY, (String)QtActivity.this.getTitle()); + parameters.putInt(MINIMUM_MINISTRO_API_KEY, MINISTRO_API_LEVEL); + parameters.putInt(MINIMUM_QT_VERSION_KEY, QT_VERSION); + parameters.putString(ENVIRONMENT_VARIABLES_KEY, ENVIRONMENT_VARIABLES); + if (APPLICATION_PARAMETERS != null) + parameters.putString(APPLICATION_PARAMETERS_KEY, APPLICATION_PARAMETERS); + parameters.putStringArray(SOURCES_KEY, m_sources); + parameters.putString(REPOSITORY_KEY, m_repository); + if (QT_ANDROID_THEMES != null) + parameters.putStringArray(ANDROID_THEMES_KEY, QT_ANDROID_THEMES); + m_service.requestLoader(m_ministroCallback, parameters); + } + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + private IMinistroCallback m_ministroCallback = new IMinistroCallback.Stub() { + // this function is called back by Ministro. + @Override + public void loaderReady(final Bundle loaderParams) throws RemoteException { + runOnUiThread(new Runnable() { + @Override + public void run() { + unbindService(m_ministroConnection); + loadApplication(loaderParams); + } + }); + } + }; + + @Override + public void onServiceDisconnected(ComponentName name) { + m_service = null; + } + }; + + private void downloadUpgradeMinistro(String msg) + { + AlertDialog.Builder downloadDialog = new AlertDialog.Builder(this); + downloadDialog.setMessage(msg); + downloadDialog.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + try { + Uri uri = Uri.parse("market://search?q=pname:org.kde.necessitas.ministro"); + Intent intent = new Intent(Intent.ACTION_VIEW, uri); + startActivityForResult(intent, MINISTRO_INSTALL_REQUEST_CODE); + } catch (Exception e) { + e.printStackTrace(); + ministroNotFound(); + } + } + }); + + downloadDialog.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + QtActivity.this.finish(); + } + }); + downloadDialog.show(); + } + + private void ministroNotFound() + { + AlertDialog errorDialog = new AlertDialog.Builder(QtActivity.this).create(); + + if (m_activityInfo.metaData.containsKey("android.app.ministro_not_found_msg")) + errorDialog.setMessage(m_activityInfo.metaData.getString("android.app.ministro_not_found_msg")); + else + errorDialog.setMessage("Can't find Ministro service.\nThe application can't start."); + + errorDialog.setButton(getResources().getString(android.R.string.ok), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + finish(); + } + }); + errorDialog.show(); + } + + static private void copyFile(InputStream inputStream, OutputStream outputStream) + throws IOException + { + byte[] buffer = new byte[BUFFER_SIZE]; + + int count; + while ((count = inputStream.read(buffer)) > 0) + outputStream.write(buffer, 0, count); + } + + + private void copyAsset(String source, String destination) + throws IOException + { + // Already exists, we don't have to do anything + File destinationFile = new File(destination); + if (destinationFile.exists()) + return; + + File parentDirectory = destinationFile.getParentFile(); + if (!parentDirectory.exists()) + parentDirectory.mkdirs(); + + destinationFile.createNewFile(); + + AssetManager assetsManager = getAssets(); + InputStream inputStream = assetsManager.open(source); + OutputStream outputStream = new FileOutputStream(destinationFile); + copyFile(inputStream, outputStream); + + inputStream.close(); + outputStream.close(); + } + + private static void createBundledBinary(String source, String destination) + throws IOException + { + // Already exists, we don't have to do anything + File destinationFile = new File(destination); + if (destinationFile.exists()) + return; + + File parentDirectory = destinationFile.getParentFile(); + if (!parentDirectory.exists()) + parentDirectory.mkdirs(); + + destinationFile.createNewFile(); + + InputStream inputStream = new FileInputStream(source); + OutputStream outputStream = new FileOutputStream(destinationFile); + copyFile(inputStream, outputStream); + + inputStream.close(); + outputStream.close(); + } + + private boolean cleanCacheIfNecessary(String pluginsPrefix, long packageVersion) + { + File versionFile = new File(pluginsPrefix + "cache.version"); + + long cacheVersion = 0; + if (versionFile.exists() && versionFile.canRead()) { + try { + DataInputStream inputStream = new DataInputStream(new FileInputStream(versionFile)); + cacheVersion = inputStream.readLong(); + inputStream.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + if (cacheVersion != packageVersion) { + deleteRecursively(new File(pluginsPrefix)); + return true; + } else { + return false; + } + } + + private void extractBundledPluginsAndImports(String pluginsPrefix) + throws IOException + { + ArrayList libs = new ArrayList(); + + String libsDir = getApplicationInfo().nativeLibraryDir + "/"; + + long packageVersion = -1; + try { + PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), 0); + packageVersion = packageInfo.lastUpdateTime; + } catch (Exception e) { + e.printStackTrace(); + } + + if (!cleanCacheIfNecessary(pluginsPrefix, packageVersion)) + return; + + { + File versionFile = new File(pluginsPrefix + "cache.version"); + + File parentDirectory = versionFile.getParentFile(); + if (!parentDirectory.exists()) + parentDirectory.mkdirs(); + + versionFile.createNewFile(); + + DataOutputStream outputStream = new DataOutputStream(new FileOutputStream(versionFile)); + outputStream.writeLong(packageVersion); + outputStream.close(); + } + + { + String key = BUNDLED_IN_LIB_RESOURCE_ID_KEY; + java.util.Set keys = m_activityInfo.metaData.keySet(); + if (m_activityInfo.metaData.containsKey(key)) { + String[] list = getResources().getStringArray(m_activityInfo.metaData.getInt(key)); + + for (String bundledImportBinary : list) { + String[] split = bundledImportBinary.split(":"); + String sourceFileName = libsDir + split[0]; + String destinationFileName = pluginsPrefix + split[1]; + createBundledBinary(sourceFileName, destinationFileName); + } + } + } + + { + String key = BUNDLED_IN_ASSETS_RESOURCE_ID_KEY; + if (m_activityInfo.metaData.containsKey(key)) { + String[] list = getResources().getStringArray(m_activityInfo.metaData.getInt(key)); + + for (String fileName : list) { + String[] split = fileName.split(":"); + String sourceFileName = split[0]; + String destinationFileName = pluginsPrefix + split[1]; + copyAsset(sourceFileName, destinationFileName); + } + } + + } + } + + private void deleteRecursively(File directory) + { + File[] files = directory.listFiles(); + if (files != null) { + for (File file : files) { + if (file.isDirectory()) + deleteRecursively(file); + else + file.delete(); + } + + directory.delete(); + } + } + + private void cleanOldCacheIfNecessary(String oldLocalPrefix, String localPrefix) + { + File newCache = new File(localPrefix); + if (!newCache.exists()) { + { + File oldPluginsCache = new File(oldLocalPrefix + "plugins/"); + if (oldPluginsCache.exists() && oldPluginsCache.isDirectory()) + deleteRecursively(oldPluginsCache); + } + + { + File oldImportsCache = new File(oldLocalPrefix + "imports/"); + if (oldImportsCache.exists() && oldImportsCache.isDirectory()) + deleteRecursively(oldImportsCache); + } + + { + File oldQmlCache = new File(oldLocalPrefix + "qml/"); + if (oldQmlCache.exists() && oldQmlCache.isDirectory()) + deleteRecursively(oldQmlCache); + } + } + } + + private void startApp(final boolean firstStart) + { + try { + if (m_activityInfo.metaData.containsKey("android.app.qt_sources_resource_id")) { + int resourceId = m_activityInfo.metaData.getInt("android.app.qt_sources_resource_id"); + m_sources = getResources().getStringArray(resourceId); + } + + if (m_activityInfo.metaData.containsKey("android.app.repository")) + m_repository = m_activityInfo.metaData.getString("android.app.repository"); + + if (m_activityInfo.metaData.containsKey("android.app.qt_libs_resource_id")) { + int resourceId = m_activityInfo.metaData.getInt("android.app.qt_libs_resource_id"); + m_qtLibs = getResources().getStringArray(resourceId); + } + + if (m_activityInfo.metaData.containsKey("android.app.use_local_qt_libs") + && m_activityInfo.metaData.getInt("android.app.use_local_qt_libs") == 1) { + ArrayList libraryList = new ArrayList(); + + + String localPrefix = "/data/local/tmp/qt/"; + if (m_activityInfo.metaData.containsKey("android.app.libs_prefix")) + localPrefix = m_activityInfo.metaData.getString("android.app.libs_prefix"); + + String pluginsPrefix = localPrefix; + + boolean bundlingQtLibs = false; + if (m_activityInfo.metaData.containsKey("android.app.bundle_local_qt_libs") + && m_activityInfo.metaData.getInt("android.app.bundle_local_qt_libs") == 1) { + localPrefix = getApplicationInfo().dataDir + "/"; + pluginsPrefix = localPrefix + "qt-reserved-files/"; + cleanOldCacheIfNecessary(localPrefix, pluginsPrefix); + extractBundledPluginsAndImports(pluginsPrefix); + bundlingQtLibs = true; + } + + if (m_qtLibs != null) { + for (int i=0;i 0) { + if (lib.startsWith("lib/")) + libraryList.add(localPrefix + lib); + else + libraryList.add(pluginsPrefix + lib); + } + } + } + + + String dexPaths = new String(); + String pathSeparator = System.getProperty("path.separator", ":"); + if (!bundlingQtLibs && m_activityInfo.metaData.containsKey("android.app.load_local_jars")) { + String[] jarFiles = m_activityInfo.metaData.getString("android.app.load_local_jars").split(":"); + for (String jar:jarFiles) { + if (jar.length() > 0) { + if (dexPaths.length() > 0) + dexPaths += pathSeparator; + dexPaths += localPrefix + jar; + } + } + } + + Bundle loaderParams = new Bundle(); + loaderParams.putInt(ERROR_CODE_KEY, 0); + loaderParams.putString(DEX_PATH_KEY, dexPaths); + loaderParams.putString(LOADER_CLASS_NAME_KEY, "org.qtproject.qt5.android.QtActivityDelegate"); + if (m_activityInfo.metaData.containsKey("android.app.static_init_classes")) { + loaderParams.putStringArray(STATIC_INIT_CLASSES_KEY, + m_activityInfo.metaData.getString("android.app.static_init_classes").split(":")); + } + loaderParams.putStringArrayList(NATIVE_LIBRARIES_KEY, libraryList); + + + String themePath = getApplicationInfo().dataDir + "/qt-reserved-files/android-style/"; + String stylePath = themePath + m_displayDensity + "/"; + if (!(new File(stylePath)).exists()) + loaderParams.putString(EXTRACT_STYLE_KEY, stylePath); + ENVIRONMENT_VARIABLES += "\tMINISTRO_ANDROID_STYLE_PATH=" + stylePath + + "\tQT_ANDROID_THEMES_ROOT_PATH=" + themePath; + + loaderParams.putString(ENVIRONMENT_VARIABLES_KEY, ENVIRONMENT_VARIABLES + + "\tQML2_IMPORT_PATH=" + pluginsPrefix + "/qml" + + "\tQML_IMPORT_PATH=" + pluginsPrefix + "/imports" + + "\tQT_PLUGIN_PATH=" + pluginsPrefix + "/plugins"); + + if (APPLICATION_PARAMETERS != null) { + loaderParams.putString(APPLICATION_PARAMETERS_KEY, APPLICATION_PARAMETERS); + } else { + Intent intent = getIntent(); + if (intent != null) { + String parameters = intent.getStringExtra("applicationArguments"); + if (parameters != null) + loaderParams.putString(APPLICATION_PARAMETERS_KEY, parameters.replace(' ', '\t')); + } + } + + loadApplication(loaderParams); + return; + } + + try { + if (!bindService(new Intent(org.kde.necessitas.ministro.IMinistro.class.getCanonicalName()), + m_ministroConnection, + Context.BIND_AUTO_CREATE)) { + throw new SecurityException(""); + } + } catch (Exception e) { + if (firstStart) { + String msg = "This application requires Ministro service. Would you like to install it?"; + if (m_activityInfo.metaData.containsKey("android.app.ministro_needed_msg")) + msg = m_activityInfo.metaData.getString("android.app.ministro_needed_msg"); + downloadUpgradeMinistro(msg); + } else { + ministroNotFound(); + } + } + } catch (Exception e) { + Log.e(QtApplication.QtTAG, "Can't create main activity", e); + } + } + + + + /////////////////////////// forward all notifications //////////////////////////// + /////////////////////////// Super class calls //////////////////////////////////// + /////////////// PLEASE DO NOT CHANGE THE FOLLOWING CODE ////////////////////////// + ////////////////////////////////////////////////////////////////////////////////// + + @Override + public boolean dispatchKeyEvent(KeyEvent event) + { + if (QtApplication.m_delegateObject != null && QtApplication.dispatchKeyEvent != null) + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.dispatchKeyEvent, event); + else + return super.dispatchKeyEvent(event); + } + public boolean super_dispatchKeyEvent(KeyEvent event) + { + return super.dispatchKeyEvent(event); + } + //--------------------------------------------------------------------------- + + @Override + public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) + { + if (QtApplication.m_delegateObject != null && QtApplication.dispatchPopulateAccessibilityEvent != null) + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.dispatchPopulateAccessibilityEvent, event); + else + return super.dispatchPopulateAccessibilityEvent(event); + } + public boolean super_dispatchPopulateAccessibilityEvent(AccessibilityEvent event) + { + return super_dispatchPopulateAccessibilityEvent(event); + } + //--------------------------------------------------------------------------- + + @Override + public boolean dispatchTouchEvent(MotionEvent ev) + { + if (QtApplication.m_delegateObject != null && QtApplication.dispatchTouchEvent != null) + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.dispatchTouchEvent, ev); + else + return super.dispatchTouchEvent(ev); + } + public boolean super_dispatchTouchEvent(MotionEvent event) + { + return super.dispatchTouchEvent(event); + } + //--------------------------------------------------------------------------- + + @Override + public boolean dispatchTrackballEvent(MotionEvent ev) + { + if (QtApplication.m_delegateObject != null && QtApplication.dispatchTrackballEvent != null) + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.dispatchTrackballEvent, ev); + else + return super.dispatchTrackballEvent(ev); + } + public boolean super_dispatchTrackballEvent(MotionEvent event) + { + return super.dispatchTrackballEvent(event); + } + //--------------------------------------------------------------------------- + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) + { + + if (QtApplication.m_delegateObject != null && QtApplication.onActivityResult != null) { + QtApplication.invokeDelegateMethod(QtApplication.onActivityResult, requestCode, resultCode, data); + return; + } + if (requestCode == MINISTRO_INSTALL_REQUEST_CODE) + startApp(false); + super.onActivityResult(requestCode, resultCode, data); + } + public void super_onActivityResult(int requestCode, int resultCode, Intent data) + { + super.onActivityResult(requestCode, resultCode, data); + } + //--------------------------------------------------------------------------- + + @Override + protected void onApplyThemeResource(Theme theme, int resid, boolean first) + { + if (!QtApplication.invokeDelegate(theme, resid, first).invoked) + super.onApplyThemeResource(theme, resid, first); + } + public void super_onApplyThemeResource(Theme theme, int resid, boolean first) + { + super.onApplyThemeResource(theme, resid, first); + } + //--------------------------------------------------------------------------- + + + @Override + protected void onChildTitleChanged(Activity childActivity, CharSequence title) + { + if (!QtApplication.invokeDelegate(childActivity, title).invoked) + super.onChildTitleChanged(childActivity, title); + } + public void super_onChildTitleChanged(Activity childActivity, CharSequence title) + { + super.onChildTitleChanged(childActivity, title); + } + //--------------------------------------------------------------------------- + + @Override + public void onConfigurationChanged(Configuration newConfig) + { + if (!QtApplication.invokeDelegate(newConfig).invoked) + super.onConfigurationChanged(newConfig); + } + public void super_onConfigurationChanged(Configuration newConfig) + { + super.onConfigurationChanged(newConfig); + } + //--------------------------------------------------------------------------- + + @Override + public void onContentChanged() + { + if (!QtApplication.invokeDelegate().invoked) + super.onContentChanged(); + } + public void super_onContentChanged() + { + super.onContentChanged(); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onContextItemSelected(MenuItem item) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(item); + if (res.invoked) + return (Boolean)res.methodReturns; + else + return super.onContextItemSelected(item); + } + public boolean super_onContextItemSelected(MenuItem item) + { + return super.onContextItemSelected(item); + } + //--------------------------------------------------------------------------- + + @Override + public void onContextMenuClosed(Menu menu) + { + if (!QtApplication.invokeDelegate(menu).invoked) + super.onContextMenuClosed(menu); + } + public void super_onContextMenuClosed(Menu menu) + { + super.onContextMenuClosed(menu); + } + //--------------------------------------------------------------------------- + + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + + try { + m_activityInfo = getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA); + for (Field f : Class.forName("android.R$style").getDeclaredFields()) { + if (f.getInt(null) == m_activityInfo.getThemeResource()) { + QT_ANDROID_THEMES = new String[] {f.getName()}; + QT_ANDROID_DEFAULT_THEME = f.getName(); + } + } + } catch (Exception e) { + e.printStackTrace(); + finish(); + return; + } + + try { + setTheme(Class.forName("android.R$style").getDeclaredField(QT_ANDROID_DEFAULT_THEME).getInt(null)); + } catch (Exception e) { + e.printStackTrace(); + } + + if (Build.VERSION.SDK_INT > 10) { + try { + requestWindowFeature(Window.class.getField("FEATURE_ACTION_BAR").getInt(null)); + } catch (Exception e) { + e.printStackTrace(); + } + } else { + requestWindowFeature(Window.FEATURE_NO_TITLE); + } + + if (QtApplication.m_delegateObject != null && QtApplication.onCreate != null) { + QtApplication.invokeDelegateMethod(QtApplication.onCreate, savedInstanceState); + return; + } + + m_displayDensity = getResources().getDisplayMetrics().densityDpi; + + ENVIRONMENT_VARIABLES += "\tQT_ANDROID_THEME=" + QT_ANDROID_DEFAULT_THEME + + "/\tQT_ANDROID_THEME_DISPLAY_DPI=" + m_displayDensity + "\t"; + + if (null == getLastNonConfigurationInstance()) { + // if splash screen is defined, then show it + if (m_activityInfo.metaData.containsKey("android.app.splash_screen_drawable")) + getWindow().setBackgroundDrawableResource(m_activityInfo.metaData.getInt("android.app.splash_screen_drawable")); + else + getWindow().setBackgroundDrawable(new ColorDrawable(0xff000000)); + + if (m_activityInfo.metaData.containsKey("android.app.background_running") + && m_activityInfo.metaData.getBoolean("android.app.background_running")) { + ENVIRONMENT_VARIABLES += "QT_BLOCK_EVENT_LOOPS_WHEN_SUSPENDED=0\t"; + } else { + ENVIRONMENT_VARIABLES += "QT_BLOCK_EVENT_LOOPS_WHEN_SUSPENDED=1\t"; + } + + if (m_activityInfo.metaData.containsKey("android.app.auto_screen_scale_factor") + && m_activityInfo.metaData.getBoolean("android.app.auto_screen_scale_factor")) { + ENVIRONMENT_VARIABLES += "QT_AUTO_SCREEN_SCALE_FACTOR=1\t"; + } + + startApp(true); + } + } + //--------------------------------------------------------------------------- + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) + { + if (!QtApplication.invokeDelegate(menu, v, menuInfo).invoked) + super.onCreateContextMenu(menu, v, menuInfo); + } + public void super_onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) + { + super.onCreateContextMenu(menu, v, menuInfo); + } + //--------------------------------------------------------------------------- + + @Override + public CharSequence onCreateDescription() + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(); + if (res.invoked) + return (CharSequence)res.methodReturns; + else + return super.onCreateDescription(); + } + public CharSequence super_onCreateDescription() + { + return super.onCreateDescription(); + } + //--------------------------------------------------------------------------- + + @Override + protected Dialog onCreateDialog(int id) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(id); + if (res.invoked) + return (Dialog)res.methodReturns; + else + return super.onCreateDialog(id); + } + public Dialog super_onCreateDialog(int id) + { + return super.onCreateDialog(id); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onCreateOptionsMenu(Menu menu) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(menu); + if (res.invoked) + return (Boolean)res.methodReturns; + else + return super.onCreateOptionsMenu(menu); + } + public boolean super_onCreateOptionsMenu(Menu menu) + { + return super.onCreateOptionsMenu(menu); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onCreatePanelMenu(int featureId, Menu menu) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(featureId, menu); + if (res.invoked) + return (Boolean)res.methodReturns; + else + return super.onCreatePanelMenu(featureId, menu); + } + public boolean super_onCreatePanelMenu(int featureId, Menu menu) + { + return super.onCreatePanelMenu(featureId, menu); + } + //--------------------------------------------------------------------------- + + + @Override + public View onCreatePanelView(int featureId) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(featureId); + if (res.invoked) + return (View)res.methodReturns; + else + return super.onCreatePanelView(featureId); + } + public View super_onCreatePanelView(int featureId) + { + return super.onCreatePanelView(featureId); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onCreateThumbnail(Bitmap outBitmap, Canvas canvas) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(outBitmap, canvas); + if (res.invoked) + return (Boolean)res.methodReturns; + else + return super.onCreateThumbnail(outBitmap, canvas); + } + public boolean super_onCreateThumbnail(Bitmap outBitmap, Canvas canvas) + { + return super.onCreateThumbnail(outBitmap, canvas); + } + //--------------------------------------------------------------------------- + + @Override + public View onCreateView(String name, Context context, AttributeSet attrs) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(name, context, attrs); + if (res.invoked) + return (View)res.methodReturns; + else + return super.onCreateView(name, context, attrs); + } + public View super_onCreateView(String name, Context context, AttributeSet attrs) + { + return super.onCreateView(name, context, attrs); + } + //--------------------------------------------------------------------------- + + @Override + protected void onDestroy() + { + super.onDestroy(); + QtApplication.invokeDelegate(); + } + //--------------------------------------------------------------------------- + + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) + { + if (QtApplication.m_delegateObject != null && QtApplication.onKeyDown != null) + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onKeyDown, keyCode, event); + else + return super.onKeyDown(keyCode, event); + } + public boolean super_onKeyDown(int keyCode, KeyEvent event) + { + return super.onKeyDown(keyCode, event); + } + //--------------------------------------------------------------------------- + + + @Override + public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) + { + if (QtApplication.m_delegateObject != null && QtApplication.onKeyMultiple != null) + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onKeyMultiple, keyCode, repeatCount, event); + else + return super.onKeyMultiple(keyCode, repeatCount, event); + } + public boolean super_onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) + { + return super.onKeyMultiple(keyCode, repeatCount, event); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) + { + if (QtApplication.m_delegateObject != null && QtApplication.onKeyDown != null) + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onKeyUp, keyCode, event); + else + return super.onKeyUp(keyCode, event); + } + public boolean super_onKeyUp(int keyCode, KeyEvent event) + { + return super.onKeyUp(keyCode, event); + } + //--------------------------------------------------------------------------- + + @Override + public void onLowMemory() + { + if (!QtApplication.invokeDelegate().invoked) + super.onLowMemory(); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onMenuItemSelected(int featureId, MenuItem item) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(featureId, item); + if (res.invoked) + return (Boolean)res.methodReturns; + else + return super.onMenuItemSelected(featureId, item); + } + public boolean super_onMenuItemSelected(int featureId, MenuItem item) + { + return super.onMenuItemSelected(featureId, item); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onMenuOpened(int featureId, Menu menu) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(featureId, menu); + if (res.invoked) + return (Boolean)res.methodReturns; + else + return super.onMenuOpened(featureId, menu); + } + public boolean super_onMenuOpened(int featureId, Menu menu) + { + return super.onMenuOpened(featureId, menu); + } + //--------------------------------------------------------------------------- + + @Override + protected void onNewIntent(Intent intent) + { + if (!QtApplication.invokeDelegate(intent).invoked) + super.onNewIntent(intent); + } + public void super_onNewIntent(Intent intent) + { + super.onNewIntent(intent); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onOptionsItemSelected(MenuItem item) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(item); + if (res.invoked) + return (Boolean)res.methodReturns; + else + return super.onOptionsItemSelected(item); + } + public boolean super_onOptionsItemSelected(MenuItem item) + { + return super.onOptionsItemSelected(item); + } + //--------------------------------------------------------------------------- + + @Override + public void onOptionsMenuClosed(Menu menu) + { + if (!QtApplication.invokeDelegate(menu).invoked) + super.onOptionsMenuClosed(menu); + } + public void super_onOptionsMenuClosed(Menu menu) + { + super.onOptionsMenuClosed(menu); + } + //--------------------------------------------------------------------------- + + @Override + public void onPanelClosed(int featureId, Menu menu) + { + if (!QtApplication.invokeDelegate(featureId, menu).invoked) + super.onPanelClosed(featureId, menu); + } + public void super_onPanelClosed(int featureId, Menu menu) + { + super.onPanelClosed(featureId, menu); + } + //--------------------------------------------------------------------------- + + @Override + protected void onPause() + { + super.onPause(); + QtApplication.invokeDelegate(); + } + //--------------------------------------------------------------------------- + + @Override + protected void onPostCreate(Bundle savedInstanceState) + { + super.onPostCreate(savedInstanceState); + QtApplication.invokeDelegate(savedInstanceState); + } + //--------------------------------------------------------------------------- + + @Override + protected void onPostResume() + { + super.onPostResume(); + QtApplication.invokeDelegate(); + } + //--------------------------------------------------------------------------- + + @Override + protected void onPrepareDialog(int id, Dialog dialog) + { + if (!QtApplication.invokeDelegate(id, dialog).invoked) + super.onPrepareDialog(id, dialog); + } + public void super_onPrepareDialog(int id, Dialog dialog) + { + super.onPrepareDialog(id, dialog); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onPrepareOptionsMenu(Menu menu) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(menu); + if (res.invoked) + return (Boolean)res.methodReturns; + else + return super.onPrepareOptionsMenu(menu); + } + public boolean super_onPrepareOptionsMenu(Menu menu) + { + return super.onPrepareOptionsMenu(menu); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onPreparePanel(int featureId, View view, Menu menu) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(featureId, view, menu); + if (res.invoked) + return (Boolean)res.methodReturns; + else + return super.onPreparePanel(featureId, view, menu); + } + public boolean super_onPreparePanel(int featureId, View view, Menu menu) + { + return super.onPreparePanel(featureId, view, menu); + } + //--------------------------------------------------------------------------- + + @Override + protected void onRestart() + { + super.onRestart(); + QtApplication.invokeDelegate(); + } + //--------------------------------------------------------------------------- + + @Override + protected void onRestoreInstanceState(Bundle savedInstanceState) + { + if (!QtApplication.invokeDelegate(savedInstanceState).invoked) + super.onRestoreInstanceState(savedInstanceState); + } + public void super_onRestoreInstanceState(Bundle savedInstanceState) + { + super.onRestoreInstanceState(savedInstanceState); + } + //--------------------------------------------------------------------------- + + @Override + protected void onResume() + { + super.onResume(); + QtApplication.invokeDelegate(); + } + //--------------------------------------------------------------------------- + + @Override + public Object onRetainNonConfigurationInstance() + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(); + if (res.invoked) + return res.methodReturns; + else + return super.onRetainNonConfigurationInstance(); + } + public Object super_onRetainNonConfigurationInstance() + { + return super.onRetainNonConfigurationInstance(); + } + //--------------------------------------------------------------------------- + + @Override + protected void onSaveInstanceState(Bundle outState) + { + if (!QtApplication.invokeDelegate(outState).invoked) + super.onSaveInstanceState(outState); + } + public void super_onSaveInstanceState(Bundle outState) + { + super.onSaveInstanceState(outState); + + } + //--------------------------------------------------------------------------- + + @Override + public boolean onSearchRequested() + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(); + if (res.invoked) + return (Boolean)res.methodReturns; + else + return super.onSearchRequested(); + } + public boolean super_onSearchRequested() + { + return super.onSearchRequested(); + } + //--------------------------------------------------------------------------- + + @Override + protected void onStart() + { + super.onStart(); + QtApplication.invokeDelegate(); + } + //--------------------------------------------------------------------------- + + @Override + protected void onStop() + { + super.onStop(); + QtApplication.invokeDelegate(); + } + //--------------------------------------------------------------------------- + + @Override + protected void onTitleChanged(CharSequence title, int color) + { + if (!QtApplication.invokeDelegate(title, color).invoked) + super.onTitleChanged(title, color); + } + public void super_onTitleChanged(CharSequence title, int color) + { + super.onTitleChanged(title, color); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onTouchEvent(MotionEvent event) + { + if (QtApplication.m_delegateObject != null && QtApplication.onTouchEvent != null) + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onTouchEvent, event); + else + return super.onTouchEvent(event); + } + public boolean super_onTouchEvent(MotionEvent event) + { + return super.onTouchEvent(event); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onTrackballEvent(MotionEvent event) + { + if (QtApplication.m_delegateObject != null && QtApplication.onTrackballEvent != null) + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onTrackballEvent, event); + else + return super.onTrackballEvent(event); + } + public boolean super_onTrackballEvent(MotionEvent event) + { + return super.onTrackballEvent(event); + } + //--------------------------------------------------------------------------- + + @Override + public void onUserInteraction() + { + if (!QtApplication.invokeDelegate().invoked) + super.onUserInteraction(); + } + public void super_onUserInteraction() + { + super.onUserInteraction(); + } + //--------------------------------------------------------------------------- + + @Override + protected void onUserLeaveHint() + { + if (!QtApplication.invokeDelegate().invoked) + super.onUserLeaveHint(); + } + public void super_onUserLeaveHint() + { + super.onUserLeaveHint(); + } + //--------------------------------------------------------------------------- + + @Override + public void onWindowAttributesChanged(LayoutParams params) + { + if (!QtApplication.invokeDelegate(params).invoked) + super.onWindowAttributesChanged(params); + } + public void super_onWindowAttributesChanged(LayoutParams params) + { + super.onWindowAttributesChanged(params); + } + //--------------------------------------------------------------------------- + + @Override + public void onWindowFocusChanged(boolean hasFocus) + { + if (!QtApplication.invokeDelegate(hasFocus).invoked) + super.onWindowFocusChanged(hasFocus); + } + public void super_onWindowFocusChanged(boolean hasFocus) + { + super.onWindowFocusChanged(hasFocus); + } + //--------------------------------------------------------------------------- + + //////////////// Activity API 5 ///////////// +//@ANDROID-5 + @Override + public void onAttachedToWindow() + { + if (!QtApplication.invokeDelegate().invoked) + super.onAttachedToWindow(); + } + public void super_onAttachedToWindow() + { + super.onAttachedToWindow(); + } + //--------------------------------------------------------------------------- + + @Override + public void onBackPressed() + { + if (!QtApplication.invokeDelegate().invoked) + super.onBackPressed(); + } + public void super_onBackPressed() + { + super.onBackPressed(); + } + //--------------------------------------------------------------------------- + + @Override + public void onDetachedFromWindow() + { + if (!QtApplication.invokeDelegate().invoked) + super.onDetachedFromWindow(); + } + public void super_onDetachedFromWindow() + { + super.onDetachedFromWindow(); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onKeyLongPress(int keyCode, KeyEvent event) + { + if (QtApplication.m_delegateObject != null && QtApplication.onKeyLongPress != null) + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onKeyLongPress, keyCode, event); + else + return super.onKeyLongPress(keyCode, event); + } + public boolean super_onKeyLongPress(int keyCode, KeyEvent event) + { + return super.onKeyLongPress(keyCode, event); + } + //--------------------------------------------------------------------------- +//@ANDROID-5 + +//////////////// Activity API 8 ///////////// +//@ANDROID-8 +@Override + protected Dialog onCreateDialog(int id, Bundle args) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(id, args); + if (res.invoked) + return (Dialog)res.methodReturns; + else + return super.onCreateDialog(id, args); + } + public Dialog super_onCreateDialog(int id, Bundle args) + { + return super.onCreateDialog(id, args); + } + //--------------------------------------------------------------------------- + + @Override + protected void onPrepareDialog(int id, Dialog dialog, Bundle args) + { + if (!QtApplication.invokeDelegate(id, dialog, args).invoked) + super.onPrepareDialog(id, dialog, args); + } + public void super_onPrepareDialog(int id, Dialog dialog, Bundle args) + { + super.onPrepareDialog(id, dialog, args); + } + //--------------------------------------------------------------------------- +//@ANDROID-8 + //////////////// Activity API 11 ///////////// + +//@ANDROID-11 + @Override + public boolean dispatchKeyShortcutEvent(KeyEvent event) + { + if (QtApplication.m_delegateObject != null && QtApplication.dispatchKeyShortcutEvent != null) + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.dispatchKeyShortcutEvent, event); + else + return super.dispatchKeyShortcutEvent(event); + } + public boolean super_dispatchKeyShortcutEvent(KeyEvent event) + { + return super.dispatchKeyShortcutEvent(event); + } + //--------------------------------------------------------------------------- + + @Override + public void onActionModeFinished(ActionMode mode) + { + if (!QtApplication.invokeDelegate(mode).invoked) + super.onActionModeFinished(mode); + } + public void super_onActionModeFinished(ActionMode mode) + { + super.onActionModeFinished(mode); + } + //--------------------------------------------------------------------------- + + @Override + public void onActionModeStarted(ActionMode mode) + { + if (!QtApplication.invokeDelegate(mode).invoked) + super.onActionModeStarted(mode); + } + public void super_onActionModeStarted(ActionMode mode) + { + super.onActionModeStarted(mode); + } + //--------------------------------------------------------------------------- + + @Override + public void onAttachFragment(Fragment fragment) + { + if (!QtApplication.invokeDelegate(fragment).invoked) + super.onAttachFragment(fragment); + } + public void super_onAttachFragment(Fragment fragment) + { + super.onAttachFragment(fragment); + } + //--------------------------------------------------------------------------- + + @Override + public View onCreateView(View parent, String name, Context context, AttributeSet attrs) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(parent, name, context, attrs); + if (res.invoked) + return (View)res.methodReturns; + else + return super.onCreateView(parent, name, context, attrs); + } + public View super_onCreateView(View parent, String name, Context context, + AttributeSet attrs) { + return super.onCreateView(parent, name, context, attrs); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onKeyShortcut(int keyCode, KeyEvent event) + { + if (QtApplication.m_delegateObject != null && QtApplication.onKeyShortcut != null) + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onKeyShortcut, keyCode,event); + else + return super.onKeyShortcut(keyCode, event); + } + public boolean super_onKeyShortcut(int keyCode, KeyEvent event) + { + return super.onKeyShortcut(keyCode, event); + } + //--------------------------------------------------------------------------- + + @Override + public ActionMode onWindowStartingActionMode(Callback callback) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(callback); + if (res.invoked) + return (ActionMode)res.methodReturns; + else + return super.onWindowStartingActionMode(callback); + } + public ActionMode super_onWindowStartingActionMode(Callback callback) + { + return super.onWindowStartingActionMode(callback); + } + //--------------------------------------------------------------------------- +//@ANDROID-11 + //////////////// Activity API 12 ///////////// + +////@ANDROID-12 +// @Override +// public boolean dispatchGenericMotionEvent(MotionEvent ev) +// { +// if (QtApplication.m_delegateObject != null && QtApplication.dispatchGenericMotionEvent != null) +// return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.dispatchGenericMotionEvent, ev); +// else +// return super.dispatchGenericMotionEvent(ev); +// } +// public boolean super_dispatchGenericMotionEvent(MotionEvent event) +// { +// return super.dispatchGenericMotionEvent(event); +// } +// //--------------------------------------------------------------------------- +// +// @Override +// public boolean onGenericMotionEvent(MotionEvent event) +// { +// if (QtApplication.m_delegateObject != null && QtApplication.onGenericMotionEvent != null) +// return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onGenericMotionEvent, event); +// else +// return super.onGenericMotionEvent(event); +// } +// public boolean super_onGenericMotionEvent(MotionEvent event) +// { +// return super.onGenericMotionEvent(event); +// } +// //--------------------------------------------------------------------------- +////@ANDROID-12 + +} diff --git a/qt/i2pd_qt/android/src/org/qtproject/qt5/android/bindings/QtApplication.java b/qt/i2pd_qt/android/src/org/qtproject/qt5/android/bindings/QtApplication.java new file mode 100644 index 00000000..c78aeb7f --- /dev/null +++ b/qt/i2pd_qt/android/src/org/qtproject/qt5/android/bindings/QtApplication.java @@ -0,0 +1,159 @@ +/* + Copyright (c) 2012-2013, BogDan Vatra + Contact: http://www.qt.io/licensing/ + + Commercial License Usage + Licensees holding valid commercial Qt licenses may use this file in + accordance with the commercial license agreement provided with the + Software or, alternatively, in accordance with the terms contained in + a written agreement between you and The Qt Company. For licensing terms + and conditions see http://www.qt.io/terms-conditions. For further + information use the contact form at http://www.qt.io/contact-us. + + BSD License Usage + Alternatively, this file may be used under the BSD license as follows: + 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 AUTHOR ``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 AUTHOR 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. +*/ + +package org.qtproject.qt5.android.bindings; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; + +import android.app.Application; + +public class QtApplication extends Application +{ + public final static String QtTAG = "Qt"; + public static Object m_delegateObject = null; + public static HashMap> m_delegateMethods= new HashMap>(); + public static Method dispatchKeyEvent = null; + public static Method dispatchPopulateAccessibilityEvent = null; + public static Method dispatchTouchEvent = null; + public static Method dispatchTrackballEvent = null; + public static Method onKeyDown = null; + public static Method onKeyMultiple = null; + public static Method onKeyUp = null; + public static Method onTouchEvent = null; + public static Method onTrackballEvent = null; + public static Method onActivityResult = null; + public static Method onCreate = null; + public static Method onKeyLongPress = null; + public static Method dispatchKeyShortcutEvent = null; + public static Method onKeyShortcut = null; + public static Method dispatchGenericMotionEvent = null; + public static Method onGenericMotionEvent = null; + + public static void setQtActivityDelegate(Object listener) + { + QtApplication.m_delegateObject = listener; + + ArrayList delegateMethods = new ArrayList(); + for (Method m : listener.getClass().getMethods()) { + if (m.getDeclaringClass().getName().startsWith("org.qtproject.qt5.android")) + delegateMethods.add(m); + } + + ArrayList applicationFields = new ArrayList(); + for (Field f : QtApplication.class.getFields()) { + if (f.getDeclaringClass().getName().equals(QtApplication.class.getName())) + applicationFields.add(f); + } + + for (Method delegateMethod : delegateMethods) { + try { + QtActivity.class.getDeclaredMethod(delegateMethod.getName(), delegateMethod.getParameterTypes()); + if (QtApplication.m_delegateMethods.containsKey(delegateMethod.getName())) { + QtApplication.m_delegateMethods.get(delegateMethod.getName()).add(delegateMethod); + } else { + ArrayList delegateSet = new ArrayList(); + delegateSet.add(delegateMethod); + QtApplication.m_delegateMethods.put(delegateMethod.getName(), delegateSet); + } + for (Field applicationField:applicationFields) { + if (applicationField.getName().equals(delegateMethod.getName())) { + try { + applicationField.set(null, delegateMethod); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + } catch (Exception e) { + } + } + } + + @Override + public void onTerminate() { + if (m_delegateObject != null && m_delegateMethods.containsKey("onTerminate")) + invokeDelegateMethod(m_delegateMethods.get("onTerminate").get(0)); + super.onTerminate(); + } + + public static class InvokeResult + { + public boolean invoked = false; + public Object methodReturns = null; + } + + private static int stackDeep=-1; + public static InvokeResult invokeDelegate(Object... args) + { + InvokeResult result = new InvokeResult(); + if (m_delegateObject == null) + return result; + StackTraceElement[] elements = Thread.currentThread().getStackTrace(); + if (-1 == stackDeep) { + String activityClassName = QtActivity.class.getCanonicalName(); + for (int it=0;it + + + +

+ OpenSSL под Android в Qt + +

Запись от Wyn размещена 18.01.2016 в 18:22
Метки android, openssl, qt

Мини-руководство по тому, как быстро скомпилировать OpenSSL для Android и связать его с проектом Qt.
+Для Linux.

+Вначале действия полностью идентичны "расово-верному" руководству по компилянию OpenSSL для Android:
+Качаем исходники openssl нужной версии с их сайта, качаем setenv-android.sh(все ссылки на закачку выше по ссылке).
+Ложим их в одну папку. Запускаем консоль, переходим в ней в эту самую папку.
+Далее:
BashВыделить код
1
+2
+3
+
$ rm -rf openssl-1.0.1g/   # удаляем исходники(вместо версии 1.0.1g - подставляем свою), если они уже были распакованы
+$ tar xzf openssl-1.0.1g.tar.gz    # распаковываем исходники в подпапку
+$ chmod a+x setenv-android.sh    # разрешаем setenv-android.sh исполняться
Редактируем setenv-android.sh, настраивая там _ANDROID_EABI, _ANDROID_ARCH, _ANDROID_API на нужные значения.
+Дальше возвращаемся в консоль:
BashВыделить код
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
$ export ANDROID_NDK_ROOT=путь_до_ANDROID_NDK # указываем путь до Android NDK для setenv-android.sh
+$ . ./setenv-android.sh # запускаем скрипт, чтобы он нам в окружение проставил необходимые далее переменные
+$ cd openssl-1.0.1g/
+$ perl -pi -e 's/install: all install_docs install_sw/install: install_docs install_sw/g' Makefile.org
+# конфигурируем
+$ ./config shared no-ssl2 no-ssl3 no-comp no-hw no-engine --openssldir=/usr/local/ssl/$ANDROID_API
+# собираем
+$ make depend
+$ make all
+# устанавливаем
+$ sudo -E make install CC=$ANDROID_TOOLCHAIN/arm-linux-androideabi-gcc RANLIB=$ANDROID_TOOLCHAIN/arm-linux-androideabi-ranlib
И тут начинается интересное. Андроид не принимает versioned shared object (это *.so.x и подобные). Казалось бы 2016 год, космические корабли уже давно бороздят просторы Большого театра, но вот те на.

+Однако, есть обходной приём - нужно заменить *.so.x.x.x на *_x_x_x.so. Простым переименованием файлов данную проблему здесь, разумеется, не решить. Нужно лезть внутрь и переименовывать soname и внутренние ссылки на другие versioned shared object. В интернете есть много способов по подобному переименованию. Большинство из них обещают райскую жизнь с rpl, забывая упомянуть, что утилита уже давно отпета и закопана на большинстве дистрибутивов. Или хитро-хитро редактируют makefile, что в итоге на место левой руки собирается правая нога. В целом множество путей из разряда "как потратить много времени на полную фигню".

+В итоге предлагаю решить данную проблему методом топора:
+Качаем hex-редактор, если ещё нет(в моём случае таковым оказался Okteta). Запускаем его из под рута(kdesu okteta), открываем в нём файлы openssldir/lib/libcrypto.so.1.0.0. Заменяем(ctrl+r) в нём символы ".so.1.0.0" на char "_1_0_0.so". Проделываем тоже самое с libssl.so.1.0.0. Всё, теперь осталось только переименовать сами файлы(в libcrypto_1_0_0.so и libssl_1_0_0.so) и поправить ссылки libssl.so и libcrypto.so, чтобы они вели на них.

+Чтобы подключить и использовать данную библиотеку в проекте нужно добавить в .pro:
BashВыделить код
1
+2
+3
+4
+5
+
android: {
+    INCLUDEPATH += /usr/local/ssl/android-21/include
+    LIBS += -L/usr/local/ssl/android-21/lib
+}
+LIBS += -lcrypto
А затем в настройках проекта, в Buld/Build Steps/Bulild Android Apk добавить libcrypto_1_0_0.so и libssl_1_0_0.so в список Additional Libraries.

+На этом всё. + +
+

Original: http://www.cyberforum.ru/blogs/748276/blog4086.html

+ + 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..2abdeb05 --- /dev/null +++ b/qt/i2pd_qt/i2pd.qrc @@ -0,0 +1,5 @@ + + + images/icon.png + + diff --git a/qt/i2pd_qt/i2pd_qt.pro b/qt/i2pd_qt/i2pd_qt.pro new file mode 100644 index 00000000..f575438c --- /dev/null +++ b/qt/i2pd_qt/i2pd_qt.pro @@ -0,0 +1,282 @@ +QT += core gui + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +TARGET = i2pd_qt +TEMPLATE = app +QMAKE_CXXFLAGS *= -std=c++11 +DEFINES += USE_UPNP + +# change to your own path, where you will store all needed libraries with 'git clone' commands below. +MAIN_PATH = /path/to/libraries + +# 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 +BOOST_PATH = $$MAIN_PATH/Boost-for-Android-Prebuilt +OPENSSL_PATH = $$MAIN_PATH/OpenSSL-for-Android-Prebuilt +MINIUPNP_PATH = $$MAIN_PATH/MiniUPnP-for-Android-Prebuilt +IFADDRS_PATH = $$MAIN_PATH/android-ifaddrs + +# Steps in Android SDK manager: +# 1) Check Extras/Google Support Library https://developer.android.com/topic/libraries/support-library/setup.html +# 2) Check API 11 +# Finally, click Install. + +SOURCES += DaemonQT.cpp mainwindow.cpp \ + ../../libi2pd/api.cpp \ + ../../libi2pd/Base.cpp \ + ../../libi2pd/BloomFilter.cpp \ + ../../libi2pd/Config.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_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 + +#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 + +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 +} + +android { + message("Using Android settings") + DEFINES += ANDROID=1 + DEFINES += __ANDROID__ + + CONFIG += mobility + + MOBILITY = + + INCLUDEPATH += $$BOOST_PATH/boost_1_53_0/include \ + $$OPENSSL_PATH/openssl-1.0.2/include \ + $$MINIUPNP_PATH/miniupnp-2.0/include \ + $$IFADDRS_PATH + DISTFILES += android/AndroidManifest.xml + + ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android + + SOURCES += $$IFADDRS_PATH/ifaddrs.c + HEADERS += $$IFADDRS_PATH/ifaddrs.h + + equals(ANDROID_TARGET_ARCH, armeabi-v7a){ + DEFINES += ANDROID_ARM7A + # http://stackoverflow.com/a/30235934/529442 + LIBS += -L$$BOOST_PATH/boost_1_53_0/armeabi-v7a/lib \ + -lboost_system-gcc-mt-1_53 -lboost_date_time-gcc-mt-1_53 \ + -lboost_filesystem-gcc-mt-1_53 -lboost_program_options-gcc-mt-1_53 \ + -L$$OPENSSL_PATH/openssl-1.0.2/armeabi-v7a/lib/ -lcrypto -lssl \ + -L$$MINIUPNP_PATH/miniupnp-2.0/armeabi-v7a/lib/ -lminiupnpc + + PRE_TARGETDEPS += $$OPENSSL_PATH/openssl-1.0.2/armeabi-v7a/lib/libcrypto.a \ + $$OPENSSL_PATH/openssl-1.0.2/armeabi-v7a/lib/libssl.a + DEPENDPATH += $$OPENSSL_PATH/openssl-1.0.2/include + + ANDROID_EXTRA_LIBS += $$OPENSSL_PATH/openssl-1.0.2/armeabi-v7a/lib/libcrypto_1_0_0.so \ + $$OPENSSL_PATH/openssl-1.0.2/armeabi-v7a/lib/libssl_1_0_0.so \ + $$MINIUPNP_PATH/miniupnp-2.0/armeabi-v7a/lib/libminiupnpc.so + } + + equals(ANDROID_TARGET_ARCH, x86){ + # http://stackoverflow.com/a/30235934/529442 + LIBS += -L$$BOOST_PATH/boost_1_53_0/x86/lib \ + -lboost_system-gcc-mt-1_53 -lboost_date_time-gcc-mt-1_53 \ + -lboost_filesystem-gcc-mt-1_53 -lboost_program_options-gcc-mt-1_53 \ + -L$$OPENSSL_PATH/openssl-1.0.2/x86/lib/ -lcrypto -lssl \ + -L$$MINIUPNP_PATH/miniupnp-2.0/x86/lib/ -lminiupnpc + + PRE_TARGETDEPS += $$OPENSSL_PATH/openssl-1.0.2/x86/lib/libcrypto.a \ + $$OPENSSL_PATH/openssl-1.0.2/x86/lib/libssl.a + + DEPENDPATH += $$OPENSSL_PATH/openssl-1.0.2/include + + ANDROID_EXTRA_LIBS += $$OPENSSL_PATH/openssl-1.0.2/x86/lib/libcrypto_1_0_0.so \ + $$OPENSSL_PATH/openssl-1.0.2/x86/lib/libssl_1_0_0.so \ + $$MINIUPNP_PATH/miniupnp-2.0/x86/lib/libminiupnpc.so + } +} + +linux:!android { + message("Using Linux settings") + LIBS += -lcrypto -lssl -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread -lminiupnpc +} + +!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/images/icon.png b/qt/i2pd_qt/images/icon.png new file mode 100644 index 00000000..a5dc7b68 Binary files /dev/null and b/qt/i2pd_qt/images/icon.png differ diff --git a/qt/i2pd_qt/mainwindow.cpp b/qt/i2pd_qt/mainwindow.cpp new file mode 100644 index 00000000..fc1e5985 --- /dev/null +++ b/qt/i2pd_qt/mainwindow.cpp @@ -0,0 +1,907 @@ +#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" + +std::string programOptionsWriterCurrentSection; + +MainWindow::MainWindow(QWidget *parent) : + QMainWindow(parent) +#ifndef ANDROID + ,quitting(false) +#endif + ,wasSelectingAtStatusMainPage(false) + ,showHiddenInfoStatusMainPage(false) + ,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->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 + + //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::showSettingsPage(){ui->stackedWidget->setCurrentIndex(1);setStatusButtonsVisible(false);} +void MainWindow::showTunnelsPage(){ui->stackedWidget->setCurrentIndex(2);setStatusButtonsVisible(false);} +void MainWindow::showRestartPage(){ui->stackedWidget->setCurrentIndex(3);setStatusButtonsVisible(false);} +void MainWindow::showQuitPage(){ui->stackedWidget->setCurrentIndex(4);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);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(":/images/icon.png"); + 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(); +} +/** 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(); + + 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..cac97a1f --- /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" + +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; } +}; +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 + +public: + explicit MainWindow(QWidget *parent=0); + ~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 showSettingsPage(); + void showTunnelsPage(); + void showRestartPage(); + void showQuitPage(); + +private: + StatusPage statusPage; + QTimer * statusPageUpdateTimer; + bool wasSelectingAtStatusMainPage; + bool showHiddenInfoStatusMainPage; + + 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(); + + + /* + + TODO signaturetype + + */ + + 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; + uint32_t maxConns = i2p::stream::DEFAULT_MAX_CONNS_PER_MIN; + 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, + maxConns, + 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); + std::string keys = section.second.get (I2P_SERVER_TUNNEL_KEYS); + // optional params + int inPort = section.second.get (I2P_SERVER_TUNNEL_INPORT, 0); + std::string accessList = section.second.get (I2P_SERVER_TUNNEL_ACCESS_LIST, ""); + 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); + uint32_t maxConns = section.second.get(i2p::stream::I2CP_PARAM_STREAMING_MAX_CONNS_PER_MIN, i2p::stream::DEFAULT_MAX_CONNS_PER_MIN); + 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, + maxConns, + 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; +}; + +#endif // MAINWINDOW_H diff --git a/qt/i2pd_qt/mainwindow.ui b/qt/i2pd_qt/mainwindow.ui new file mode 100644 index 00000000..9b463f44 --- /dev/null +++ b/qt/i2pd_qt/mainwindow.ui @@ -0,0 +1,956 @@ + + + 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 + + + + + + + + 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 + + + + + + + + TextLabel + + + true + + + 10 + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 713 + 713 + + + + 2 + + + + + 0 + 0 + + + + + + 0 + 0 + 713 + 531 + + + + + QLayout::SetMaximumSize + + + + + + 15 + + + + Status + + + + + + + QLayout::SetMaximumSize + + + + + + + + + + 0 + 0 + + + + + + 0 + 0 + 711 + 531 + + + + + QLayout::SetMinAndMaxSize + + + + + + 15 + + + + General settings + + + + + + + + 0 + 0 + + + + Qt::ScrollBarAlwaysOn + + + Qt::ScrollBarAsNeeded + + + QAbstractScrollArea::AdjustIgnored + + + true + + + + + 0 + 0 + 689 + 496 + + + + + 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/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..f769ad35 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -1,66 +1,23 @@ -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 +TESTS = test-gost test-gost-sig test-base-64 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: test-gost-sig.cpp $(LIBI2PD) - $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS) - -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-gost-sig: ../libi2pd/Gost.cpp ../libi2pd/I2PEndian.cpp ../libi2pd/Signature.cpp ../libi2pd/Crypto.cpp ../libi2pd/Log.cpp test-gost-sig.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 deleted file mode 100644 index 2ba6a253..00000000 --- a/tests/test-aeadchacha20poly1305.cpp +++ /dev/null @@ -1,62 +0,0 @@ -#include -#include -#include - -#include "Crypto.h" - -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] = -{ - 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] = -{ - 0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7 -}; - -uint8_t nonce[12] = -{ - 0x07, 0x00, 0x00, 0x00, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47 -}; - -uint8_t tag[16] = -{ - 0x1a, 0xe1, 0x0b, 0x59, 0x4f, 0x09, 0xe2, 0x6a, 0x7e, 0x90, 0x2e, 0xcb, 0xd0, 0x60, 0x06, 0x91 -}; - -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, - 0x3d, 0xbe, 0xa4, 0x5e, 0x8c, 0xa9, 0x67, 0x12, 0x82, 0xfa, 0xfb, 0x69, 0xda, 0x92, 0x72, 0x8b, - 0x1a, 0x71, 0xde, 0x0a, 0x9e, 0x06, 0x0b, 0x29, 0x05, 0xd6, 0xa5, 0xb6, 0x7e, 0xcd, 0x3b, 0x36, - 0x92, 0xdd, 0xbd, 0x7f, 0x2d, 0x77, 0x8b, 0x8c, 0x98, 0x03, 0xae, 0xe3, 0x28, 0x09, 0x1b, 0x58, - 0xfa, 0xb3, 0x24, 0xe4, 0xfa, 0xd6, 0x75, 0x94, 0x55, 0x85, 0x80, 0x8b, 0x48, 0x31, 0xd7, 0xbc, - 0x3f, 0xf4, 0xde, 0xf0, 0x8e, 0x4b, 0x7a, 0x9d, 0xe5, 0x76, 0xd2, 0x65, 0x86, 0xce, 0xc6, 0x4b, - 0x61, 0x16 -}; - -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); - 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 (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); - 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;