pax_global_header00006660000000000000000000000064147527206770014533gustar00rootroot0000000000000052 comment=fb90b01f6c2f71831f854b3f43fc79fde246ee33 i2pd-2.56.0/000077500000000000000000000000001475272067700124635ustar00rootroot00000000000000i2pd-2.56.0/.dir-locals.el000066400000000000000000000001031475272067700151060ustar00rootroot00000000000000((c++-mode . ((indent-tabs-mode . t))) (c-mode . ((mode . c++)))) i2pd-2.56.0/.editorconfig000066400000000000000000000010401475272067700151330ustar00rootroot00000000000000# 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 i2pd-2.56.0/.gitattributes000066400000000000000000000000371475272067700153560ustar00rootroot00000000000000/build/build_mingw.cmd eol=crlfi2pd-2.56.0/.github/000077500000000000000000000000001475272067700140235ustar00rootroot00000000000000i2pd-2.56.0/.github/workflows/000077500000000000000000000000001475272067700160605ustar00rootroot00000000000000i2pd-2.56.0/.github/workflows/build-deb.yml000066400000000000000000000025741475272067700204420ustar00rootroot00000000000000name: 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 i2pd-2.56.0/.github/workflows/build-freebsd.yml000066400000000000000000000017321475272067700213150ustar00rootroot00000000000000name: 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 i2pd-2.56.0/.github/workflows/build-osx.yml000066400000000000000000000016111475272067700205100ustar00rootroot00000000000000name: 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 i2pd-2.56.0/.github/workflows/build-windows-msvc.yml-disabled000066400000000000000000000047201475272067700241100ustar00rootroot00000000000000name: 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.* i2pd-2.56.0/.github/workflows/build-windows.yml000066400000000000000000000206671475272067700214050ustar00rootroot00000000000000name: 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 i2pd-2.56.0/.github/workflows/build.yml000066400000000000000000000025561475272067700177120ustar00rootroot00000000000000name: 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 i2pd-2.56.0/.github/workflows/docker.yml000066400000000000000000000103751475272067700200600ustar00rootroot00000000000000name: 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 images: purplei2p/i2pd:latest-amd64,purplei2p/i2pd:latest-i386,purplei2p/i2pd:latest-arm64,purplei2p/i2pd:latest-armv7 push: true - name: Create and push latest manifest image to GHCR if: ${{ !startsWith(github.ref, 'refs/tags/') }} uses: Noelware/docker-manifest-action@master with: inputs: ghcr.io/purplei2p/i2pd:latest images: ghcr.io/purplei2p/i2pd:latest-amd64,ghcr.io/purplei2p/i2pd:latest-i386,ghcr.io/purplei2p/i2pd:latest-arm64,ghcr.io/purplei2p/i2pd:latest-armv7 push: true - name: Store release version to env if: ${{ startsWith(github.ref, 'refs/tags/') }} run: echo "RELEASE_VERSION=${GITHUB_REF:10}" >> $GITHUB_ENV - name: Create and push release manifest to Docker Hub if: ${{ startsWith(github.ref, 'refs/tags/') }} uses: Noelware/docker-manifest-action@master with: inputs: purplei2p/i2pd:latest,purplei2p/i2pd:latest-release,purplei2p/i2pd:release-${{ env.RELEASE_VERSION }} images: purplei2p/i2pd:latest-amd64,purplei2p/i2pd:latest-i386,purplei2p/i2pd:latest-arm64,purplei2p/i2pd:latest-armv7 push: true - name: Create and push release manifest to GHCR if: ${{ startsWith(github.ref, 'refs/tags/') }} uses: Noelware/docker-manifest-action@master with: inputs: ghcr.io/purplei2p/i2pd:latest,ghcr.io/purplei2p/i2pd:latest-release,ghcr.io/purplei2p/i2pd:release-${{ env.RELEASE_VERSION }} images: ghcr.io/purplei2p/i2pd:latest-amd64,ghcr.io/purplei2p/i2pd:latest-i386,ghcr.io/purplei2p/i2pd:latest-arm64,ghcr.io/purplei2p/i2pd:latest-armv7 push: true i2pd-2.56.0/.gitignore000066400000000000000000000063001475272067700144520ustar00rootroot00000000000000# i2pd *.o router.info router.keys i2p netDb /i2pd /libi2pd.a /libi2pdclient.a /libi2pdlang.a /libi2pd.so /libi2pdclient.so /libi2pdlang.so /libi2pd.dll /libi2pdclient.dll /libi2pdlang.dll *.exe # Autotools autom4te.cache .deps stamp-h1 #Makefile config.h config.h.in~ config.log config.status config.sub ################# ## Eclipse ################# *.pydevproject .project .metadata bin/ tmp/ *.tmp *.bak *.swp *~.nib local.properties .classpath .settings/ .loadpath # External tool builders .externalToolBuilders/ # Locally stored "Eclipse launch configurations" *.launch # CDT-specific .cproject # PDT-specific .buildpath ################# ## Visual Studio ################# ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files *.suo *.user *.sln.docstates # Build results [Dd]ebug/ [Rr]elease/ x64/ [Bb]in/ [Oo]bj/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* *_i.c *_p.c *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.log *.scc # Visual C++ cache files ipch/ *.aps *.ncb *.opensdf *.sdf *.cachefile # Visual Studio profiler *.psess *.vsp *.vspx # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # NCrunch *.ncrunch* .*crunch*.local.xml # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.Publish.xml *.pubxml # NuGet Packages Directory ## TODO: If you have NuGet Package Restore enabled, uncomment the next line #packages/ # Windows Azure Build Output csx *.build.csdef # Windows Store app package directory AppPackages/ # Others sql/ *.Cache ClientBin/ [Ss]tyle[Cc]op.* ~$* *~ *.dbmdl *.[Pp]ublish.xml *.pfx *.publishsettings # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file to a newer # Visual Studio version. Backup files are not needed, because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files App_Data/*.mdf App_Data/*.ldf ############# ## Windows detritus ############# # Windows image file caches Thumbs.db ehthumbs.db # Folder config file Desktop.ini # Recycle Bin used on file shares $RECYCLE.BIN/ # Mac crap .DS_Store ############# ## Python ############# *.py[co] # Packages *.egg *.egg-info dist/ eggs/ parts/ var/ sdist/ develop-eggs/ .installed.cfg # Installer logs pip-log.txt # Unit test / coverage reports .coverage .tox #Translations *.mo #Mr Developer .mr.developer.cfg # Sphinx docs/_build /androidIdea/ # Doxygen docs/generated # emacs files *~ *\#* # gdb files .gdb_history # cmake makefile build/Makefile # debian stuff debian/i2pd.1.gz .pc/ # qt qt/i2pd_qt/*.autosave qt/i2pd_qt/*.ui.bk* qt/i2pd_qt/*.ui_* #unknown android stuff android/libs/ #various logs *LOGS/ qt/build-*.sh* i2pd-2.56.0/ChangeLog000066400000000000000000001316511475272067700142440ustar00rootroot00000000000000# 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 - Re-create HTTP and SOCKS proxy by tunnel reload - Graceful shutdown as soon as no more transit tunnels ### Changed - Regenerate shared local destination by tunnel reload - Use transient local destination by default if not specified - Return correct code if pid file can't be created - Timing and number of attempts for adressbook requests - Certificates list ### Fixed - Malformed addressbook subsctiption request - Build with boost 1.66 - Few race conditions for SAM - Check LeaseSet's signature before update ## [2.17.0] - 2017-12-04 ### Added - Reseed through HTTP and SOCKS proxy - Show status of client services through web console - Change log level through web connsole - transient keys for tunnels - i2p.streaming.initialAckDelay parameter - CRYPTO_TYPE for SAM destination - signature and crypto type for newkeys BOB command ### Changed - Correct publication of ECIES destinations - Disable RSA signatures completely ### Fixed - CVE-2017-17066 - Possible buffer overflow for RSA-4096 - Shutdown from web console for Windows - Web console page layout ## [2.16.0] - 2017-11-13 ### Added - https and "Connect" method for HTTP proxy - outproxy for HTTP proxy - initial support of ECIES crypto - NTCP soft and hard descriptors limits - Support full timestamps in logs ### Changed - Faster implementation of GOST R 34.11 hash - Reject routers with RSA signtures - Reload config and shudown from Windows GUI - Update tunnels address(destination) without restart ### Fixed - BOB crashes if destination is not set - Correct SAM tunnel name - QT GUI issues ## [2.15.0] - 2017-08-17 ### Added - QT GUI - Ability to add and remove I2P tunnels without restart - Ability to disable SOCKS outproxy option ### Changed - Strip-out Accept-* hedaers in HTTP proxy - Don't run peer test if nat=false - Separate output of NTCP and SSU sessions in Transports tab ### Fixed - Handle lines with comments in hosts.txt file for address book - Run router with empty netdb for testnet - Skip expired introducers by iexp ## [2.14.0] - 2017-06-01 ### Added - Transit traffic bandwidth limitation - NTCP connections through HTTP and SOCKS proxies - Ability to disable address helper for HTTP proxy ### Changed - Reseed servers list - Minimal required version is 4.0 for Android ### Fixed - Ignore comments in addressbook feed ## [2.13.0] - 2017-04-06 ### Added - Persist local destination's tags - GOST signature types 9 and 10 - Exploratory tunnels configuration ### Changed - Reseed servers list - Inactive NTCP sockets get closed faster - Some EdDSA speed up ### Fixed - Multiple acceptors for SAM - Follow on data after STREAM CREATE for SAM - Memory leaks ## [2.12.0] - 2017-02-14 ### Added - Additional HTTP and SOCKS proxy tunnels - Reseed from ZIP archive - Some stats in a main window for Windows version ### Changed - Reseed servers list - MTU of 1488 for ipv6 - Android and Mac OS X versions use OpenSSL 1.1 - New logo for Android ### Fixed - Multiple memory leaks - Incomptibility of some EdDSA private keys with Java - Clock skew for Windows XP - Occasional crashes with I2PSnark ## [2.11.0] - 2016-12-18 ### Added - Websockets support - Reseed through a floodfill - Tunnel configuration for HTTP and SOCKS proxy - Zero-hops tunnels for destinations - Multiple acceptors for SAM ### Changed - Reseed servers list - DHT uses AVX if applicable - New logo - LeaseSet lookups ### Fixed - HTTP Proxy connection reset for Windows - Crash upon SAM session termination - Can't connect to a destination for a longer time after restart - Mass packet loss for UDP tunnels ## [2.10.2] - 2016-12-04 ### Fixed - Fixes UPnP discovery bug, producing excessive CPU usage - Fixes sudden SSU thread stop for Windows. ## [2.10.1] - 2016-11-07 ### Fixed - Fixed some performance issues for Windows and Android ## [2.10.0] - 2016-10-17 ### Added - Datagram i2p tunnels - Unique local addresses for server tunnels - Configurable list of reseed servers and initial addressbook - Configurable netid - Initial iOS support ### Changed - Reduced file descriptors usage - Strict reseed checks enabled by default ## Fixed - Multiple fixes in I2CP and BOB implementations ## [2.9.0] - 2016-08-12 ### Changed - Proxy refactoring & speedup - Transmission-I2P support - Graceful shutdown for Windows - Android without QT - Reduced number of timers in SSU - ipv6 peer test support - Reseed from SU3 file ## [2.8.0] - 2016-06-20 ### Added - Basic Android support - I2CP implementation - 'doxygen' target ### Changed - I2PControl refactoring & fixes (proper jsonrpc responses on errors) - boost::regex no more needed ### Fixed - initscripts: added openrc one, in sysv-ish make I2PD_PORT optional - properly close NTCP sessions (memleak) ## [2.7.0] - 2016-05-18 ### Added - Precomputed El-Gamal/DH tables - Configurable limit of transit tunnels ### Changed - Speed-up of asymmetric crypto for non-x64 platforms - Refactoring of web-console ## [2.6.0] - 2016-03-31 ### Added - Graceful shutdown on SIGINT - Numeric bandwidth limits (was: by router class) - Jumpservices in web-console - Logging to syslog - Tray icon for windows application ### Changed - Logs refactoring - Improved statistics in web-console ### Deprecated: - Renamed main/tunnels config files (will use old, if found, but emits warning) ## [2.5.1] - 2016-03-10 ### Fixed - Doesn't create ~/.i2pd dir if missing ## [2.5.0] - 2016-03-04 ### Added - IRC server tunnels - SOCKS outproxy support - Support for gzipped addressbook updates - Support for router families ### Changed - Shared RTT/RTO between streams - Filesystem work refactoring ## [2.4.0] - 2016-02-03 ### Added - X-I2P-* headers for server http-tunnels - I2CP options for I2P tunnels - Show I2P tunnels in webconsole ### Changed - Refactoring of cmdline/config parsing ## [2.3.0] - 2016-01-12 ### Added - Support for new router bandwidth class codes (P and X) - I2PControl supports external webui - Added --pidfile and --notransit parameters - Ability to specify signature type for i2p tunnel ### Changed - Fixed multiple floodfill-related bugs - New webconsole layout ## [2.2.0] - 2015-12-22 ### Added - Ability to connect to router without ip via introducer ### Changed - Persist temporary encryption keys for local destinations - Performance improvements for EdDSA - New addressbook structure ## [2.1.0] - 2015-11-12 ### Added - Implementation of EdDSA ### Changed - EdDSA is default signature type for new RouterInfos i2pd-2.56.0/LICENSE000066400000000000000000000027321475272067700134740ustar00rootroot00000000000000Copyright (c) 2013-2023, The PurpleI2P Project All rights reserved. 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. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 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 COPYRIGHT HOLDER 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. i2pd-2.56.0/Makefile000066400000000000000000000126271475272067700141330ustar00rootroot00000000000000.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) ARLIB := libi2pd.a SHLIB_LANG := libi2pdlang.$(SHARED_SUFFIX) ARLIB_LANG := libi2pdlang.a SHLIB_CLIENT := libi2pdclient.$(SHARED_SUFFIX) ARLIB_CLIENT := libi2pdclient.a SHLIB_WRAP := libi2pdwrapper.$(SHARED_SUFFIX) ARLIB_WRAP := libi2pdwrapper.a I2PD := i2pd LIB_SRC_DIR := libi2pd LIB_CLIENT_SRC_DIR := libi2pd_client WRAP_SRC_DIR := libi2pd_wrapper LANG_SRC_DIR := i18n DAEMON_SRC_DIR := daemon # import source files lists include filelist.mk USE_STATIC := $(or $(USE_STATIC),no) USE_UPNP := $(or $(USE_UPNP),no) DEBUG := $(or $(DEBUG),yes) # for debugging purposes only, when commit hash needed in trunk builds in i2pd version string USE_GIT_VERSION := $(or $(USE_GIT_VERSION),no) # for MacOS only, waiting for "1", not "yes" HOMEBREW := $(or $(HOMEBREW),0) ifeq ($(DEBUG),yes) CXX_DEBUG = -g else CXX_DEBUG = -Os LD_DEBUG = -s endif ifneq (, $(DESTDIR)) PREFIX = $(DESTDIR) endif ifneq (, $(findstring darwin, $(SYS))) DAEMON_SRC += $(DAEMON_SRC_DIR)/UnixDaemon.cpp ifeq ($(HOMEBREW),1) include Makefile.homebrew else include Makefile.osx endif else ifneq (, $(findstring mingw, $(SYS))$(findstring windows-gnu, $(SYS))$(findstring cygwin, $(SYS))) DAEMON_SRC += Win32/DaemonWin32.cpp Win32/Win32App.cpp Win32/Win32Service.cpp Win32/Win32NetState.cpp include Makefile.mingw else ifneq (, $(findstring linux, $(SYS))$(findstring gnu, $(SYS))) DAEMON_SRC += $(DAEMON_SRC_DIR)/UnixDaemon.cpp include Makefile.linux else ifneq (, $(findstring freebsd, $(SYS))$(findstring openbsd, $(SYS))) DAEMON_SRC += $(DAEMON_SRC_DIR)/UnixDaemon.cpp include Makefile.bsd else ifneq (, $(findstring haiku, $(SYS))) DAEMON_SRC += $(DAEMON_SRC_DIR)/UnixDaemon.cpp include Makefile.haiku else # not supported $(error Not supported platform) endif INCFLAGS += -I$(LIB_SRC_DIR) -I$(LIB_CLIENT_SRC_DIR) -I$(LANG_SRC_DIR) DEFINES += -DOPENSSL_SUPPRESS_DEPRECATED NEEDED_CXXFLAGS += -MMD -MP ifeq ($(USE_GIT_VERSION),yes) GIT_VERSION := $(shell git describe --tags) DEFINES += -DGITVER=$(GIT_VERSION) 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) ## Build all code (libi2pd, libi2pdclient, libi2pdlang), link it to .a and build binary all: $(ARLIB) $(ARLIB_CLIENT) $(ARLIB_LANG) $(I2PD) mk_obj_dir: @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) ## 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. ## 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. obj/%.o: %.cpp | mk_obj_dir $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(DEFINES) $(INCFLAGS) -c -o $@ $< # '-' is 'ignore if missing' on first run -include $(DEPS) $(I2PD): $(DAEMON_OBJS) $(ARLIB) $(ARLIB_CLIENT) $(ARLIB_LANG) $(CXX) $(DEFINES) $(LDFLAGS) -o $@ $^ $(LDLIBS) $(SHLIB): $(LIB_OBJS) ifneq ($(USE_STATIC),yes) $(CXX) $(LDFLAGS) -shared -o $@ $^ $(LDLIBS) endif $(SHLIB_CLIENT): $(LIB_CLIENT_OBJS) $(SHLIB) $(SHLIB_LANG) ifneq ($(USE_STATIC),yes) $(CXX) $(LDFLAGS) -shared -o $@ $^ $(LDLIBS) $(SHLIB) $(SHLIB_LANG) endif $(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) $(AR) -r $@ $^ $(ARLIB_CLIENT): $(LIB_CLIENT_OBJS) $(AR) -r $@ $^ $(ARLIB_WRAP): $(WRAP_LIB_OBJS) $(AR) -r $@ $^ $(ARLIB_LANG): $(LANG_OBJS) $(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) strip: $(I2PD) $(SHLIB) $(SHLIB_CLIENT) $(SHLIB_LANG) strip $^ LATEST_TAG=$(shell git describe --tags --abbrev=0 openssl) BRANCH=$(shell git rev-parse --abbrev-ref HEAD) dist: git archive --format=tar.gz -9 --worktree-attributes \ --prefix=i2pd_$(LATEST_TAG)/ $(LATEST_TAG) -o i2pd_$(LATEST_TAG).tar.gz last-dist: git archive --format=tar.gz -9 --worktree-attributes \ --prefix=i2pd_$(LATEST_TAG)/ $(BRANCH) -o ../i2pd_$(LATEST_TAG).orig.tar.gz doxygen: doxygen -s docs/Doxyfile .PHONY: all .PHONY: clean .PHONY: doxygen .PHONY: dist .PHONY: last-dist .PHONY: api .PHONY: api_client .PHONY: client .PHONY: lang .PHONY: mk_obj_dir .PHONY: install .PHONY: strip i2pd-2.56.0/Makefile.bsd000066400000000000000000000020541475272067700146730ustar00rootroot00000000000000CXX = clang++ CXXFLAGS ?= ${CXX_DEBUG} -Wall -Wextra -Wno-unused-parameter -pedantic -Wno-misleading-indentation DEFINES = -D_GLIBCXX_USE_NANOSLEEP=1 INCFLAGS = -I/usr/include/ -I/usr/local/include/ LDFLAGS = ${LD_DEBUG} -Wl,-rpath,/usr/local/lib -L/usr/local/lib LDLIBS = -lssl -lcrypto -lz -lpthread -lboost_system -lboost_program_options ## NOTE: NEEDED_CXXFLAGS is here so that custom CXXFLAGS can be specified at build time ## **without** overwriting the CXXFLAGS which we need in order to build. ## For example, when adding 'hardening flags' to the build ## (e.g. -fstack-protector-strong -Wformat -Werror=format-security), we do not want to remove ## -std=c++11. If you want to remove this variable please do so in a way that allows setting ## custom FLAGS to work at build-time. CXXVER := $(shell $(CXX) -dumpversion|cut -c 1-2) ifeq (${CXXVER}, "4.") # older clang always returned 4.2.1 $(error Compiler too old) else ifeq (${CXXVER}, ${filter ${CXXVER},16 17 18 19}) # clang 16 - 19 NEEDED_CXXFLAGS = -std=c++20 else NEEDED_CXXFLAGS = -std=c++17 endif i2pd-2.56.0/Makefile.haiku000066400000000000000000000004441475272067700152250ustar00rootroot00000000000000CXX = g++ CXXFLAGS := -Wall -std=c++17 INCFLAGS = -I/system/develop/headers DEFINES = -D_DEFAULT_SOURCE -D_GNU_SOURCE LDLIBS = -lbe -lbsd -lnetwork -lz -lssl -lcrypto -lboost_system -lboost_program_options -lpthread ifeq ($(USE_UPNP),yes) DEFINES += -DUSE_UPNP LDLIBS += -lminiupnpc endif i2pd-2.56.0/Makefile.homebrew000066400000000000000000000041171475272067700157350ustar00rootroot00000000000000# root directory holding homebrew BREWROOT = /opt/homebrew BOOSTROOT = ${BREWROOT}/opt/boost SSLROOT = ${BREWROOT}/opt/openssl@1.1 UPNPROOT = ${BREWROOT}/opt/miniupnpc CXXFLAGS ?= ${CXX_DEBUG} -Wall -Wno-overloaded-virtual NEEDED_CXXFLAGS ?= -std=c++17 INCFLAGS ?= -I${SSLROOT}/include -I${BOOSTROOT}/include LDFLAGS ?= ${LD_DEBUG} DEFINES += -DMAC_OSX ifeq ($(USE_STATIC),yes) LDLIBS = -lz ${SSLROOT}/lib/libcrypto.a ${SSLROOT}/lib/libssl.a ${BOOSTROOT}/lib/libboost_system.a ${BOOSTROOT}/lib/libboost_filesystem.a ${BOOSTROOT}/lib/libboost_program_options.a ifeq ($(USE_UPNP),yes) LDLIBS += ${UPNPROOT}/lib/libminiupnpc.a endif LDLIBS += -lpthread -ldl else LDFLAGS += -L${SSLROOT}/lib -L${BOOSTROOT}/lib LDLIBS = -lz -lssl -lcrypto -lboost_system -lboost_filesystem -lboost_program_options -lpthread ifeq ($(USE_UPNP),yes) LDFLAGS += -L${UPNPROOT}/lib LDLIBS += -lminiupnpc endif endif ifeq ($(USE_UPNP),yes) DEFINES += -DUSE_UPNP INCFLAGS += -I${UPNPROOT}/include 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 i2pd-2.56.0/Makefile.linux000066400000000000000000000056701475272067700152710ustar00rootroot00000000000000# set defaults instead redefine CXXFLAGS ?= ${CXX_DEBUG} -Wall -Wextra -Wno-unused-parameter -pedantic -Wno-psabi LDFLAGS ?= ${LD_DEBUG} ## 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. # detect proper flag for c++17 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 else # not supported $(error Compiler too old) endif NEEDED_CXXFLAGS += -fPIC 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) 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 else LDLIBS += -lssl -lcrypto -lz -lboost_program_options -lpthread -latomic ifeq ($(USE_UPNP),yes) LDLIBS += -lminiupnpc endif endif # UPNP Support (miniupnpc 1.5 and higher) ifeq ($(USE_UPNP),yes) DEFINES += -DUSE_UPNP 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 i2pd-2.56.0/Makefile.mingw000066400000000000000000000022771475272067700152530ustar00rootroot00000000000000# 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 $@ i2pd-2.56.0/Makefile.osx000066400000000000000000000014401475272067700147320ustar00rootroot00000000000000CXX = clang++ CXXFLAGS := ${CXX_DEBUG} -Wall -std=c++17 INCFLAGS = -I/usr/local/include DEFINES := -DMAC_OSX LDFLAGS := -Wl,-rpath,/usr/local/lib -L/usr/local/lib LDFLAGS += -Wl,-dead_strip LDFLAGS += -Wl,-dead_strip_dylibs 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 else LDLIBS = -lz -lssl -lcrypto -lboost_system -lboost_filesystem -lboost_program_options -lpthread endif ifeq ($(USE_UPNP),yes) LDFLAGS += -ldl DEFINES += -DUSE_UPNP ifeq ($(USE_STATIC),yes) LDLIBS += /usr/local/lib/libminiupnpc.a else LDLIBS += -lminiupnpc endif endif OSARCH = $(shell uname -p) ifneq ($(OSARCH),powerpc) CXXFLAGS += -msse endif i2pd-2.56.0/README.md000066400000000000000000000135021475272067700137430ustar00rootroot00000000000000[![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 ==== [Русская версия](https://github.com/PurpleI2P/i2pd_docs_ru/blob/master/README.md) i2pd (I2P Daemon) is a full-featured C++ implementation of I2P client. I2P (Invisible Internet Protocol) is a universal anonymous network layer. All communications over I2P are anonymous and end-to-end encrypted, participants don't reveal their real IP addresses. I2P client is a software used for building and using anonymous I2P networks. Such networks are commonly used for anonymous peer-to-peer applications (filesharing, cryptocurrencies) and anonymous client-server applications (websites, instant messengers, chat-servers). I2P allows people from all around the world to communicate and share information without restrictions. Features -------- * Distributed anonymous networking framework * End-to-end encrypted communications * Small footprint, simple dependencies, fast performance * Rich set of APIs for developers of secure applications Resources --------- * [Website](http://i2pd.website) * [Documentation](https://i2pd.readthedocs.io/en/latest/) * [Wiki](https://github.com/PurpleI2P/i2pd/wiki) * [Tickets/Issues](https://github.com/PurpleI2P/i2pd/issues) * [Specifications](https://geti2p.net/spec) * [Twitter](https://twitter.com/hashtag/i2pd) 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 -------- 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: * [unix](https://i2pd.readthedocs.io/en/latest/devs/building/unix/) * [windows](https://i2pd.readthedocs.io/en/latest/devs/building/windows/) * [iOS](https://i2pd.readthedocs.io/en/latest/devs/building/ios/) * [android](https://i2pd.readthedocs.io/en/latest/devs/building/android/) **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) * iOS 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``` License ------- This project is licensed under the BSD 3-clause license, which can be found in the file LICENSE in the root of the project source code. i2pd-2.56.0/Win32/000077500000000000000000000000001475272067700133655ustar00rootroot00000000000000i2pd-2.56.0/Win32/DaemonWin32.cpp000066400000000000000000000042061475272067700161210ustar00rootroot00000000000000/* * 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" #include "Daemon.h" #include "util.h" #include "Log.h" #ifdef _WIN32 #include "Win32Service.h" #ifdef WIN32_APP #include #include "Win32App.h" #endif namespace i2p { namespace util { bool DaemonWin32::init(int argc, char* argv[]) { 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 ); } ); if (!Daemon_Singleton::init(argc, argv)) return false; if (isDaemon) { LogPrint(eLogDebug, "Daemon: running as service"); I2PService service((PSTR)SERVICE_NAME); if (!I2PService::Run(service)) { LogPrint(eLogCritical, "Daemon: Service failed to run w/err 0x%08lx\n", GetLastError()); return false; } return false; } return true; } bool DaemonWin32::start() { setlocale(LC_CTYPE, ""); SetConsoleCP(1251); SetConsoleOutputCP(1251); //setlocale(LC_ALL, "Russian"); setlocale(LC_TIME, "C"); #ifdef WIN32_APP if (!i2p::win32::StartWin32App (isDaemon)) return false; #endif bool ret = Daemon_Singleton::start(); if (ret && i2p::log::Logger().GetLogType() == eLogFile) { // TODO: find out where this garbage to console comes from SetStdHandle(STD_OUTPUT_HANDLE, INVALID_HANDLE_VALUE); SetStdHandle(STD_ERROR_HANDLE, INVALID_HANDLE_VALUE); } bool insomnia; i2p::config::GetOption("insomnia", insomnia); if (insomnia) SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED); return ret; } bool DaemonWin32::stop() { #ifdef WIN32_APP i2p::win32::StopWin32App (); #endif return Daemon_Singleton::stop(); } void DaemonWin32::run () { #ifdef WIN32_APP i2p::win32::RunWin32App (); #else while (running) { std::this_thread::sleep_for (std::chrono::seconds(1)); } #endif } } } #endif //_WIN32 i2pd-2.56.0/Win32/Resource.rc000066400000000000000000000011221475272067700154760ustar00rootroot00000000000000#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 i2pd-2.56.0/Win32/Resource.rc2000066400000000000000000000020531475272067700155640ustar00rootroot00000000000000#ifdef APSTUDIO_INVOKED #error this file is not editable by Microsoft Visual C++ #endif //APSTUDIO_INVOKED #include "version.h" VS_VERSION_INFO VERSIONINFO FILEVERSION I2PD_VERSION_MAJOR,I2PD_VERSION_MINOR,I2PD_VERSION_MICRO,I2PD_VERSION_PATCH PRODUCTVERSION I2P_VERSION_MAJOR,I2P_VERSION_MINOR,I2P_VERSION_MICRO,I2P_VERSION_PATCH FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L #else FILEFLAGS 0x0L #endif FILEOS 0x40004L FILETYPE 0x1L FILESUBTYPE 0x0L BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904b0" BEGIN VALUE "CompanyName", "Purple I2P" VALUE "FileDescription", "C++ I2P daemon" VALUE "FileVersion", I2PD_VERSION VALUE "InternalName", CODENAME VALUE "LegalCopyright", "Copyright (C) 2013-2023, The PurpleI2P Project" VALUE "OriginalFilename", "i2pd" VALUE "ProductName", "Purple I2P" VALUE "ProductVersion", I2P_VERSION END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x409, 1200 END END i2pd-2.56.0/Win32/Win32App.cpp000066400000000000000000000345401475272067700154420ustar00rootroot00000000000000/* * 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; } } } i2pd-2.56.0/Win32/Win32App.h000066400000000000000000000010201475272067700150720ustar00rootroot00000000000000/* * 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__ i2pd-2.56.0/Win32/Win32NetState.cpp000066400000000000000000000055301475272067700164460ustar00rootroot00000000000000/* * 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 i2pd-2.56.0/Win32/Win32NetState.h000066400000000000000000000043561475272067700161200ustar00rootroot00000000000000/* * 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 i2pd-2.56.0/Win32/Win32Service.cpp000066400000000000000000000142661475272067700163250ustar00rootroot00000000000000/* * 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 "Win32Service.h" #include #include #include "Daemon.h" #include "Log.h" I2PService *I2PService::s_service = NULL; BOOL I2PService::isService() { BOOL bIsService = FALSE; HWINSTA hWinStation = GetProcessWindowStation(); if (hWinStation != NULL) { USEROBJECTFLAGS uof = { FALSE, FALSE, 0 }; if (GetUserObjectInformation(hWinStation, UOI_FLAGS, &uof, sizeof(USEROBJECTFLAGS), NULL) && ((uof.dwFlags & WSF_VISIBLE) == 0)) { bIsService = TRUE; } } return bIsService; } BOOL I2PService::Run(I2PService &service) { s_service = &service; SERVICE_TABLE_ENTRY serviceTable[] = { { service.m_name, ServiceMain }, { NULL, NULL } }; return StartServiceCtrlDispatcher(serviceTable); } void WINAPI I2PService::ServiceMain(DWORD dwArgc, PSTR *pszArgv) { assert(s_service != NULL); s_service->m_statusHandle = RegisterServiceCtrlHandler( s_service->m_name, ServiceCtrlHandler); if (s_service->m_statusHandle == NULL) { throw GetLastError(); } s_service->Start(dwArgc, pszArgv); } void WINAPI I2PService::ServiceCtrlHandler(DWORD dwCtrl) { switch (dwCtrl) { case SERVICE_CONTROL_STOP: s_service->Stop(); break; case SERVICE_CONTROL_PAUSE: s_service->Pause(); break; case SERVICE_CONTROL_CONTINUE: s_service->Continue(); break; case SERVICE_CONTROL_SHUTDOWN: s_service->Shutdown(); break; case SERVICE_CONTROL_INTERROGATE: break; default: break; } } I2PService::I2PService(PSTR pszServiceName, BOOL fCanStop, BOOL fCanShutdown, BOOL fCanPauseContinue) { m_name = (pszServiceName == NULL) ? (PSTR)"" : pszServiceName; m_statusHandle = NULL; m_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS; m_status.dwCurrentState = SERVICE_START_PENDING; DWORD dwControlsAccepted = 0; if (fCanStop) dwControlsAccepted |= SERVICE_ACCEPT_STOP; if (fCanShutdown) dwControlsAccepted |= SERVICE_ACCEPT_SHUTDOWN; if (fCanPauseContinue) dwControlsAccepted |= SERVICE_ACCEPT_PAUSE_CONTINUE; m_status.dwControlsAccepted = dwControlsAccepted; m_status.dwWin32ExitCode = NO_ERROR; m_status.dwServiceSpecificExitCode = 0; m_status.dwCheckPoint = 0; m_status.dwWaitHint = 0; m_fStopping = FALSE; // Create a manual-reset event that is not signaled at first to indicate // the stopped signal of the service. m_hStoppedEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if (m_hStoppedEvent == NULL) { throw GetLastError(); } } I2PService::~I2PService(void) { if (m_hStoppedEvent) { CloseHandle(m_hStoppedEvent); m_hStoppedEvent = NULL; } } void I2PService::Start(DWORD dwArgc, PSTR *pszArgv) { try { SetServiceStatus(SERVICE_START_PENDING); OnStart(dwArgc, pszArgv); SetServiceStatus(SERVICE_RUNNING); } catch (DWORD dwError) { LogPrint(eLogCritical, "Win32Service: Start error: ", dwError); SetServiceStatus(SERVICE_STOPPED, dwError); } catch (...) { LogPrint(eLogCritical, "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, ")"); Daemon.start(); _worker = new std::thread(std::bind(&I2PService::WorkerThread, this)); } void I2PService::WorkerThread() { while (!m_fStopping) { ::Sleep(1000); // Simulate some lengthy operations. } // Signal the stopped event. SetEvent(m_hStoppedEvent); } void I2PService::Stop() { DWORD dwOriginalState = m_status.dwCurrentState; try { SetServiceStatus(SERVICE_STOP_PENDING); OnStop(); SetServiceStatus(SERVICE_STOPPED); } catch (DWORD dwError) { LogPrint(eLogInfo, "Win32Service: Stop error: ", dwError); SetServiceStatus(dwOriginalState); } catch (...) { LogPrint(eLogCritical, "Win32Service: Failed to stop: ", EVENTLOG_ERROR_TYPE); SetServiceStatus(dwOriginalState); } } void I2PService::OnStop() { // Log a service stop message to the Application log. LogPrint(eLogInfo, "Win32Service: in OnStop (", EVENTLOG_INFORMATION_TYPE, ")"); Daemon.stop(); m_fStopping = TRUE; if (WaitForSingleObject(m_hStoppedEvent, INFINITE) != WAIT_OBJECT_0) { throw GetLastError(); } _worker->join(); delete _worker; } void I2PService::Pause() { try { SetServiceStatus(SERVICE_PAUSE_PENDING); OnPause(); SetServiceStatus(SERVICE_PAUSED); } catch (DWORD dwError) { LogPrint(eLogCritical, "Win32Service: Pause error: ", dwError); SetServiceStatus(SERVICE_RUNNING); } catch (...) { LogPrint(eLogCritical, "Win32Service: Failed to pause: ", EVENTLOG_ERROR_TYPE); SetServiceStatus(SERVICE_RUNNING); } } void I2PService::OnPause() { } void I2PService::Continue() { try { SetServiceStatus(SERVICE_CONTINUE_PENDING); OnContinue(); SetServiceStatus(SERVICE_RUNNING); } catch (DWORD dwError) { LogPrint(eLogCritical, "Win32Service: Continue error: ", dwError); SetServiceStatus(SERVICE_PAUSED); } catch (...) { LogPrint(eLogCritical, "Win32Service: Failed to resume: ", EVENTLOG_ERROR_TYPE); SetServiceStatus(SERVICE_PAUSED); } } void I2PService::OnContinue() { } void I2PService::Shutdown() { try { OnShutdown(); SetServiceStatus(SERVICE_STOPPED); } catch (DWORD dwError) { LogPrint(eLogCritical, "Win32Service: Shutdown error: ", dwError); } catch (...) { LogPrint(eLogCritical, "Win32Service: Failed to shut down: ", EVENTLOG_ERROR_TYPE); } } void I2PService::OnShutdown() { } void I2PService::SetServiceStatus(DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwWaitHint) { static DWORD dwCheckPoint = 1; m_status.dwCurrentState = dwCurrentState; m_status.dwWin32ExitCode = dwWin32ExitCode; m_status.dwWaitHint = dwWaitHint; m_status.dwCheckPoint = ((dwCurrentState == SERVICE_RUNNING) || (dwCurrentState == SERVICE_STOPPED)) ? 0 : dwCheckPoint++; ::SetServiceStatus(m_statusHandle, &m_status); } //***************************************************************************** void FreeHandles(SC_HANDLE schSCManager, SC_HANDLE schService) { if (schSCManager) { CloseServiceHandle(schSCManager); schSCManager = NULL; } if (schService) { CloseServiceHandle(schService); schService = NULL; } } i2pd-2.56.0/Win32/Win32Service.h000066400000000000000000000024771475272067700157730ustar00rootroot00000000000000/* * 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" class I2PService { public: I2PService(PSTR pszServiceName, BOOL fCanStop = TRUE, BOOL fCanShutdown = TRUE, BOOL fCanPauseContinue = FALSE); virtual ~I2PService(void); static BOOL isService(); static BOOL Run(I2PService &service); void Stop(); 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); 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; BOOL m_fStopping; HANDLE m_hStoppedEvent; std::thread* _worker; }; #endif // WIN_32_SERVICE_H__ i2pd-2.56.0/Win32/mask.ico000066400000000000000000004616241475272067700150310ustar00rootroot00000000000000 &f (@@ (B00 %(  N h,_PNG  IHDR\rfIDATx]ޛ^\hG= "`Qc,$D$15Mc,ĚQ齷z۾;}PwtݝyfW}Ї>} pRbʔ{ؖ]FI$I ;k\zuwp2M6:x$.5͍26J~_y"==ȓ}Ї ceuO|$1|, ޜgY\K@h#>TRn!!=hqo:7ynؑ{W(b) F@N*V ),O\{Uȼ]xEp!1墺j>GWӋ/{H)IKF@N*x< ~}ojP72Z.CjTu2{#W!G'ClWܔ-Z^P,=o5͙Ikq:0t^EZٿ^ҫ9wb?ubAY\#&s$ *j?رeu {~Y~Nu߿Er~mb,M0K`î΂w7xY!Gbk~Ǹ]KrsC\;@KK w`LL<y}Yg#OE$,c68W74u矜7/}#\8*~Y6[ uThm~oެ^~ϱo9ZΙh#Egٸ2#yخ]Mv}6_>]0ۯxΊvDuhYgEի,kJׯq?SXRw;'a!:7q%0.}ɨX+k. $^ŸH8sĒ ҌTB짭|vى ;UW$a(^{*?EZ~pyef)TV:,@#S(Pqdq \4azW9]^we_Q -f5`:럭=b7(pW'NUgg2 [Qeʱu0P()53'G>=0SYWn9܊ɀ0Xſ)vtٴ5/YFÏ2AY55rPRW <4HbF{|rzGWo(E}fzZS}ef֕SEih}w]~u'pj?%@eQpmR=s;~Ɠ5vs; of8AN_c"͚)iE V ݯuu.8UG4 aul.[(ń.c{zDs;339?+ lV*֗n XیS]3pE5*#ށG浯}xe٧9jVyjgk KxUZ5K@eCLX70#i0%?T#E:Esm\囃_LKqڡ|;)/EqQOXP1(wHK,FI%~8A}}#~NhaY|#+Wn}~qBd_6qv 6⭝飯z4ϷlܠDOD TXAM7~i-YUW-Nʆ%=}-W;57UXŐzS}D׏E@D#FKlWШUeg9`O_ϩ>yd332/v9|Ybix1uHҮ`$wN|t}%~P#1O~R7?3?of5]$ImsБxѺu뺠?#Y?j7PfbZ+7qDZ?tjǓ@KOpK0wr`|R 晢:U3`3ߡO~.J](m*۴wɀB3d9 cDžB|ww2ȐtS, X.7/- ?ZtBQj˞.FX'qtFU n $4v㨾Fz ;q `ؐWrLGkln9mJ{#UEwn)[I0klw@31RŴ]ݨ s'y{O0h$o" X(+qG6trUjS?]YcѓJ+ӫ>v߲T ;zf~vK 9i6QFpp@? -ZfYwv _=emaVGDh0.`gG$8|{M2]Ӓ4 Sgaȳ:>0ҘgI(Qe??ɩݸh)Wx@&gE6>sj 7)2 93$bV 0 /+zy.%ew9Af47 ZHa # %ά7K U;Y==X8`_5yXtMO2)?#F\}i:@E 횤\_lgd_pRx}?;ok+ ,YAYSN 7ǀoᏏ-d߇TמH AAsGY{8Т;&])mZn~@2]`ܸq9 M Tٌ^4 PoPHYjuƞhk?Ur胡-Տgv̛o 8˫TCIo8 @+;J/]~%>n_-W V>بxLb 0 78M.nEr'?ˀTvG/njnђT^htyh r!O]3Mn.l ;E{:R8^`vu)/G*yd2{١-Xt.v}rM:{j €ۿgq-1 ËZ~i|Vo辀>?[ugb[l, S@xu@nl> 2)t]#D'V&,8w5~%qqLh__Q霳2VAsXPuh[CZtmNu"n>H(;>a~%rF#IJ(jYX=sYO)i:[K䍂S?]놆<HH7t6&k+yq{*^:tP.wS nטN.V[ϪMz$hU%c.LH] ^a]woLweaG˿_ޒԋ`d*e_ue Ȍa#wK [×C=4Rc϶Z=ݎ9TQlˁ:]O+>| A64fkN{D`Lׯٹ;cٓq;xrV !FQXAZ3rXͳ^z7|ifL|TxP.le} DѰ5]!@EBˤUҖgBbVpUb;h,?fes!/ܠZ"rq ai#RݻK_;C_;ŗK$ڦwuO@ʙlVe$=hzg4#l{M7iр'Pd86^0QDu %uM'B}Ҝa&f݀iK?Tuӎ?HUߡ`k'7-jWsՖ'|bPq-Dޖ\t.-,ǿ3gL7Y)5)q5rXb#Lc Ṋ65 ?_r0'z_ǩ,L]FAR+x1k")8qgY\Z|GԔ;:bNƹTVǺihLFTj.HŸ8O'#H==qG 滭F2{0-P8%24`DպXJe n6I:[5 9LuJ?I퀰5??fmキƿ tqvY ~d]X'Q?HuE jCZÿZ]=wyRMO+*|p6}}86jnim E `qō bo;qy82 8@@Qe2Wp;[Go 4)/w BAnvd`0<`f$kԕ9&n7^<_׭U;3*p/N/bЫ N/ՈXF+}Q@+ĉ73?4K\TA-C8! q@#vV3.Z/׋[ 34b]֟btCj2$@XI5Dv+YczOZ6 Q*_a=a0U*~9$&i Qd տ&N8w?d⋭Le#-24#~2+1X&0^?x4}}}^B8=-:&~"x܎>zd`<90 )wnK5Q&\zh*RRրYQeTŎv0NJAN/̺ZxhPEc$F7mh xXգBۨ6qc8{[Pa)bW1\`%2,)S} G@b^pe[qͶ=5}82F0=u-_Fl$)GtIr;U`p]~uΝ;7/1Ve"Vlltt0xMmq\?Ԝc cwލ(}Cڅ!bǹ4Q:ql,7]vٕ}NF6|Lѻh^K *t*]iPH*maBO]sOϽ/fAV㘡3'DLzo}@c.dWn|]UܘHHf3̽%%Cif;Q~[!"#IYw 2AX񠪢Swᄱ+@3/ҢE[zÑ7^SoMDS;&J;F+cԲuՒRӶatlrpOL0_>t |mqNQcuLH7}*հZ@oꯉ[G*L{(UV+\r,r-ɉBtTŸc20jdmb]oT~, (L+XL;?A7׃ ` (iSa֗~O>B'7kxٯFZh 1=.Z@guZ\$5MWEq Hf8m3A+T!3,eo+#GzA˳dUTbZ{Ze_|'̇{K=?=eeNZe%L 1Jr w+/j^}gO̻/={mP1- *>wҁߓw7*mׅcw[#8_G9 y!mL!WR'=~?m]k~ꖆ.hGO\7@f#]X_esJzAHK" C 57yz/zj OjŒ ɰnz\GAqE4+vݰ!e|>IJ9Y1W_}NޢOw^ %-c=4{2ȪEZ6ahn3fw'L8iXLƇ`t>pa^ qvsNZ]ߠv&Lף/Dvvb|+t /qźf!Fp@H3ꨰSKJL^d,+*Jt?Y{饗~Y?S) ?-78"S~zN;VOg30ʈN;$s7؟RH*(X#+Wy|X XRyܿqXb&k+Rn8U!طV_{G]oOuXۻyV ӎ9C@3TIS"J,Y4,y?x$EUdr(!߻qD& D,Y+`e&`=-TOZ.څ+"r ضm8v5˽<8P nVԑqYNqiun#4 W;ڪϷ9/F* Y،$ %Ko8}~Cv= "d"SEcc;XqNr93/[O-V TFK Y5]UUQIIXRbE9Eh7xrY_@,~C%w`8G$,fi%0H$h>h0FkJ b,а%daCׂR:)h)@UhvNF(|^~NbhvH4 lnlh>l~e+G=a8 3~[6Xqb~C@b#$mZ#Ək*f)Fak\#41H몛I%nwJKM,CjFJkswt9&V-fkLo± 03**!2? ĵoQ#rmkwu[LCȔ'^.la\bevB)ƌbR7!mS6Hu0RYQq[IIROB7ТbR##8NQ5YvwBٞM8EISTƙS +6bNXX Xh+6lؐNVoh.v`!=2#q! !yjwI&zLf U/`Â" 7 Xe6euZTP1 4jwJ3|$C+!Ifld>,`#ɈxCݛ|<Ł-ϙzN=;#$wYדd|.N3@7JU n ƾ#rcMHg%ق="0db Y0 GYF(T`.^4`"$̺Vib+V+rÛj='%~%7$9T2gFʤ҂ ȴUn NJ7Ǻ4ۗ돨5u#Y4o| ns%E)/om7:@i)S;֮xϴq6l}]q/V<3I}R ?ͻҗ;_2j`~{N$aTS\8 l[.lU,-jcj5 $Zlw11 02`2DA!< =qE]9)!=x&A!ӊɺH5K5 òr.s*qe&;̅Hl!Uf+pY1OwL*&7'5L06|~?2MôȄi 2i"D46*S5;ؖ ;>=dT}m79:  7`AK fT~kTۿ'%ZnH68٢r,nnxfk!"cF<6_5tՐ kt?í aQɡV)X8^a8ƏF|S=Nl#+q{=Ѡ<R_C[FcӞ~l75A4>KBHB%&'~;fMnQN|@RKtp D]er8 l!${U5ݱ5sfzlzz!WDvFdOh+ym=b;s^yݫ>&BŊy@8-+޿4H3M1S~%7;7nDo?/rkϳ߯{ML{J4-12R]I2k;̕'Ց2=Iû0Ս8rwgMVwn!L_yuMxt+;luJۿ{&HBӠC4]zbs?WsB8'QTZ]ʏrgr%DA#LΊz2tŖ#wl~m۶nYX``W9a6. a3 M1vmx\ldt] 7w<|xQav'h V k/vl*K]V8ܤ[ kW+:N7|;WY  G" b(O i2)!`4:2WyAf2?k ԭG#ZHi;@N{nc|~W(:#A~4g,w'Fo|VhPC|j36lǮl^KKˆӍ;j@!?3I\Y,+S KS=>:IRB6CL]8QK~Q+۟) {ُt;lĀD[vXo[dMr-~7sE$G<9 $!GlI:-UEBY6=ǥنs k.bdF= j*4ka > u<$ЫDQ'n!dt5ˬZ>{YʲLQfIf[q7{pQRQOlr\8}Y s?/7=r#!XsS %8vp63SoyM]($m\T׼;ȁb/eW8aEڦп#oJI^TG S+,c]k|6߾ڌ"8g>0 93mh_O`kXi(h^5e[1C(KOhk ZobZu!hڠ.YBdvqԽF$`n!OcD6@C + KׯgFƟQ/IE"oMGL0;3@ÌvdPb" :㠸 o揯͜93 [NN?C{PJKa?aة Ԉ[3O˹)w Xa__EB)ZM (  /ڢ**1E\@?vn VPddwpKS@G"O[vDXOv rc0_@滶cw>d _zE@i)xD1wFb S奅1NeOF{ V$v4Bcx6}T|GtEUhT#hO&ҙ í!GefP r8-.Gub-{MQ&1غj ͜|ZMw"e:ٷjY.0!Q>n $wE-%Ů?=͐77g\66B,QQ'(}3E߅[\`+g QkJ`@\m #!.D0 ^|V \*yOn@B &bP-@c hkz`B"cZ;壥VBzlwiwG.6V_1keƿ>pd@>#^c$FY9ƑI@zAf!Z`DSҍvl 6vk,5Wsm_|kn/LwAPt|o}p٠V;q8eB>6F6*nJIV(2(ƚtu󻙚L{s:IYƠ k":F+A ʅlc4rV!s]^m [!!2_y0V¢}-NN9 3(<#$+G;-Y%+C긴9Fu ؄eJ|XP_ޘha=}8N:sj63[ HZQnn ߶BR+#1*pl[A 7_;b#Vn@&n uz P?OV2y%v2KZql՟1ks6!#e .ĸE|1'ؾzEN5؋o~O/)+rsln1ϤW-J[qL*u>r|}HX⯋rN2dkKNFqí4S{dۦvV. (\9XY7v4:Po]D+Н`CSEakk>`@8 c.R[4%F d$V;+s`BNڪ Ƙښ[A=puݺGzp8 `/}U~]ʙme=UvbIi @wj>c`}n7zg0P?Ȁ Nqx-Zx,߾:IZb37M&>HJATr=0sL -1+H5p`tz k`kȌ%Ls"bMVXO?,d:к;ߋq*@Gηke?qS-q"V\:!. a{'4!(H">ăSwgv{<ȆDBU d5DžLb8H窅}F1D'sۡ3 _⅋GgCUh:jsgvOO> ?g6坰h{bv4xPACxN)-])ZY# Zjp8ufμ#ǩL8u+71v~LE8ι^O( 4W0!m<.<A!5i$Xj!cIj؍!q {+a}>fmM0< ?FTX 5 :\87eYLN}E; )״q9"O78ȟ4FR{V`qmn 1kKkq1z>%B+fʨR )9Ѩ%bD4!jk%3'><7=ql\?$y `tPV{~C[0a @f&~vCPv6`n(!EB7ڥh4eƘI!BY 0kT" D$.ɂo EXwwϗMF^v Јp~|psaP?,y o?k|6\::tShߢdLl5aX e8<_ VCGTzojI&s1}6E~!5!_elf+p!+!w[uIˠ69<4f o-yet@\>)$V6UV/rl8#lzۿR*iTgu4tTP^>{h+mTI1>/v k6"[K+&~`ڈAϵ%X5^"x(3n)Ãci*O3,h q|8; <%l1 g tAjQ8x"9Y3쉚VM f p]%6yss^\ oo MS<~?Cpk@@y0&DϩfC7JH-7 UHLV[tFX.;S|w8ӡg-Ĵja[ h*q{ ʉ\B|`R3xGo7I hx")Ԥ~kC&D4 8"No6 N@5cAܨc\?oT?l7!BuIx/<ˆӫ\qӨ>xo*MPT\#=fĠn >S׫=UAĕpkq9PkAg`(8`Rk|Ig J/bqӑ_e%Ij;!&Q)Zhw Vi䦺 YU![/wWi~hePFmfx:yݚښnxhSj^TIM!'V5i24&i7m eYw)E3q<.`Wfo2?tYrF3o¸~.$Uxfi Jo1 ~Z:S >~=!`B3b\cj%J jQxҺ\{Z.; V3M"DiE:agæ\`>Ӭg.2mMG LV aQkĈu ,pp}ϫ OOnC_\+޴i3a䟳zٶɂP~H,n8s L3J{h0hxh@Hk1Y[p7G͇Ʃ±> ZhmV8ta.`]2T=#."ZA4Tbeu^_ p!bB+]1⦅A4c$ĕ^">i ?P gO@"dVR"(fM#6Xm: ##u׎UF&j``^ˀ%HR9h֖`-Ro%ԦͰ1?Jj@+yPN^:V`rE8Cf)J09G|iFxV50n>Lz&q Hi|Z%g!-qw­tB9bVğ7͟MXb aԔ:iPaccOjr+O>]5aj9(~bPOuPE40md.h9)ex3[0iZ?BXB| &/'(i%p^CtB&q 8ku\wDI`5HۇJi.P8$e2qz>猄H l~Lf=^o%^U 1bbGW%i{ hSv;.; %u5 >|cPiE*"Q+fw/χ. )0-"8 Dx~9I^D׋/,$" VuCPx \6SJV\Jуiuh61zvP a NE*&Y `OnasAKB ֈAb@4.(I(.Ӧ $7G1=1JJ DSűr έdq`z'Ph`:{?FZ@%zE.Ǡז|b-LL:  nHӁh*DR)N0"9% 4Hw1 z0[}%E?܂GŮX[۸oYXha >*p22TzA"f,o4͇~?F1*>H# 'v~\mLG:T v،SۉqOA8aOT0!_/.U.(#Dn^E=n[7p4?d50GE->L@ 1/f5a1J@\ !Lt=t ,kdzG7jc'1k /OXI s)l8T7L[N/i#w*SOcu/_nyx*|س{[ 6 PF_AhsN.wh;L3TEM6ؘ5=ugC1@"hH`k3: !KedaNn:9&Tۚ.Z{ˉezG;f"*!eH`=PMM] F7 -tpCIK76b)`0 }iq.M= xXS?y1sA,@Tc[lM3']IH{IܸΛ;`4hr1Gv;y=9UqrfU‡Xmu8hK]Aq|oWQMYٯfE'"u8?)ߍDP0ؗ__] e~bsq+"pDžEp7ht;sd^|!jRe7kɎ{~ \2,X#p['jtgy_PN؛[to 3H! |gzٸ 0Z}bC7`ʚ0ju37X軽*!XJ``f==Ȩ`3Y/s~b ~xa1u3/~OiVtH,kfgUd Ԩ >5H5)tZ5H1hsW:DPRqM~EYƘEe:&X(1-%uǣ3#pہxfC9bc̼v6_ҒT>2lCgV<|>ҵ7X9G~2Π:dz qMlIե nv=qhьƑaLX2^?YeNuY>TXip,AՋ0ZHpC $V32a=H]fg33a(*ºTf<~^5G3t55Hkd$3oBpC>TJJd < NPY;-k=qn;o [oz1df>gu;\C?)/O(,y V]`E8ӜI^?!˟X?(O Y*]Zp+5s+w}TNJ{,yTt^d̉g7U˿@A2Ƿ 섻|&8Oʺ=S'/Ϟ&i|vH.pu!ݰ~3Jgn Igu7oL{@2aVg:Ls]Ռs_VYy,ze`9iUe|pmO^Ko>Ҍ|ɠe޳~㸆YI r}k>/Lv>5|9o1#zwܼժ={z?Cn{<{c WpŠg%")0,15vPQ#5z"*ᶧ[`[s*:0wG~߽MSaDpts;@㰨Ez _(CNl[iO.-q6Zֻ5_|^=[/}eO~r;ܲ$>7'6#z|zyڪ%Z.$H(쾑 PePd:b^fUIΆ\` d%p[Rr钳7¬c2$ϔaQՍQW2 (G4fწ`&!&A(+dsɏW}ˆ]0 DeNũ{&Xv@gϔӘ+Fp @X Wc'>8{:>Kp87\4KGlՄ XGe0 f z= DdZ{\|X!ÊG6S-E+`z6Y`3POH%OKD$4=`QX@o`rNڴEn$(9hRlEax 85Nd9!"!P('piܤtZXe#Aeʲ>92SҬGѥP@9/9O1CV|;Ċ}xc?K?~`tK .ܵxQ_9Ő\okk_񩶾v#qpvza !% A9 )V"w|Ar .oi#u <':N>2t P•7"' KNmp(BLin!;֨*3VOͱ`0w>?M|b?=b-2faP&–(PGi$8 ͏BC- b992: 9^5D[wﺤ/ѴG' {t@&Aزɵe;PyFI#KAA~ ! TYMs%˞OA`HPC*$;Z,6[Fx=}%Ճ' kߏA8ɴV7'﷾a8L$p_v5\3pwGgola֝w)>W+mdVхxbK9>yK4" `w!:pFӸըN5ɊnM?cGD#:d7{ǁ-~O'tHSR{A9@YX 9`eUCKxƢbKD3a%̅(( !P{Iw{- rjgl $@ t>ೲr/cۘVM?™̙w"װ>?/:ht"E>e(]y+v i=8+Og!uva lCc7vR2m\0KgϳψyUY'N jkj2IYy4B F@vb ^Sꃀ&b s2iQ;7#rfew QH!3_ f>,y nUhFgiPz/|l7|""&#hPA!,[ ڞ eH; #]DJ/, (}6v5tJZz{-c{}, ؆˛9q hyN4dL=6oV $):V͇: ePkͱQnu87¿ގ5eP%hmNf]tW~a! IS[?j kKϙ6vw͢Pfe +5% p~B4{:r^b$ 4p_E1L[[cbV0YP`Ŋ͛7608t];R,+ldK59 kmU[3ro8Oy ?RrLS\vnyHA\T*ICA&^':r˛.{kb2iu t^/.8kv~|J!*bNjxZ_ʒ"(˶RMglͥAU7Q+fjP@SC\*-b@hʟ; ^2ax>2*et៰7 jE Fxew?T4+iI&?Iݐ_2.CFQh:XjÓ[N+|N8t-a}xOr?Ӹ[]jPyxIi7rFVfzۋ& 9Ț*j9>VqU#*+1-8Ȫl/2?ʛF#XǓZeEyLaC+Nv^dXYB#& jpo0íõXkj2)XI@  aTBП DR84TNDT[BJF9``7Js=îPac..3 dc#kGu0 H n cاhr,qYh$X2KчՃia,H0Hރ5mC) HE|4 ,kl}IhOZyؔ3qSzp9wnx~{/u%D-p%7bIzN+r—,e3!c^o>" 袼]7nAK.`]Ɠ#LM@dN@:~L1&t,<^/GTHmlȃUw}YKxs+ T aSS gUNtVlQ*3[cӼKcg!e5X&h EҢ11.N4CTCXѯa@ jx(eFYy(* BdU;X VmV~]dY63]˲{`~Ev< a= ĬCDzPAyfڠ4%B3{T]m͇D8TDcFܩa={`Szd`нg5ĥ`Z6\Wzsd,MթjJ9m /Z 3u@).05B'[LHˌ +bV)3)Yܳ8{z2eu6b(1."AklS="57|<&xA}ay])?k_ͰOHqG4 k}0'!!=ej PJguph&iq% 9F@CX 1bp${- 7w9-ˏuT6íXml".s !3d%&jkW͠10!?T`Ra y֦M]2G L+4%~v^6uLW1zAc<ᵚ~, %<JSJ}y5̯ҮBEe>ʦ ~tv`Qyf-WӶX0Ej0N +ń\K+l‡ 8t[)N~_s]Vf)&VC_(3˰AzC"UgK7m1/*'np#EU"rHM(1-Nr]ўX_ƨ-OȹY&ٖӉzIUe!$:8Flbi.v DR5pZAgنMnԵS nrZ Qs]Q m ylZLi0g49c_k@`aQ2O`*֪\\57c, E Tn{6l%3 ##&423dvD뒻}oZONxQ;YWRg) IX+RVb"_zinwoG2pv̙ۇɢt>n0G h_cig{ʄBiVR: e$ͨ7/Rj0%M$bҦ5LLLZMATK@T/j䀒ұClJ*) N!XY2w#Aej8?Nio_n`L #c'jekEIȿ;: 3˼pьpktv}ei8d$=t;ԀiŠSU}:`W^̄N'EXzص7 v1#TT2[uZKnOho5g9b q Y(X)"b4 !#9WjjbG}'ZL@|t_eV0%Uټ[>t(5 O>2I̮,ѰX(ɪHjdF8BW /eDz2̣qqFp;oV#g<[GS}gKDL FUEL_}'=#l7L·i h(0N"C2,/ eVxhbXQ_!_ > Ls@߅s"DӇWwgTw wh)>2Mm#ڡr=?Kw?ٜ+VsS ٮHE (0v-lkKQxִ AMC;' vY<ב^S=g.OMkqM >yҦ0LcP`ЅɍmT'*6'}Z, !k vڹ𹹙P!h՜h߭M!6xbc$, AьC1eX%'R4sKe;O<l cA4Bu  _[ c9NNC0眖M_Q"c9e]EAk: .悏G2Z 8֒b&kuhnn7#[ҹp\<c@6!D Hrؔ=Uek544GߏpCejHˮi,4)56)8!p15:/gcJVE^Myhnoc S aZqSx0r4~ֶT!›`-sdSŠ9t 4U&AhKO0%9 cC;qK6X9^[Vz0gor <5 M| c*CI.G{+=\'48 4bq~;8}`K`*_Yg [X-ygs#' 9ly.~e?>ʬ*yZ\McN68|1"H4D4#eQi (BW^ (-2UҎ^xzKl&VҚj[0ѱRPF^b3h#~eAq~c2|"('.@@)x%Z)p V( Y>]vU<[@ |4 7?86a̳ҏYV#zȦz'[|% BOYw}u/)̍TYL//KB4TV)x 38mu/p-9!(`[|lIT-I\jc WxِuB zw8h}6 O5aK`%q# V00fWx\4/~J `{KO]ۈF0 آMiH|Y1?b#3H#Ɠe;tJx|7i^K.>?HdI2;2xxWH瞝իyG؝q{c'=->}{E+g;&8gn"WOc{rX$<3 t0DRpP0(bO+v@ᴙߝv$nb<N,LY/I̷SAp XIfӀߎ@gzj{?1ӊ?Bg`dZgB,aZ&ޑ/O8Xړv0 /9;oVxXSlwt Da,6r_Ӻi;=+{x7,l ܚF%r=1lHN0~6exjM`N ɭyiEp4? R,?1X1ɱczE^o ٱʑV Dl"Z  t/ ( :zFoz3+Ld2 0O,k1d9ŒӸGz.|2v@[hy1h#glӃ7pC1a*泱dX<ں;N0y '+b&^Zdf?7L@a1%E.Usύ~-Q:k2H#! 6FjnXh-9NF>#¨+@ĻH3M^ƃLC:hNZn))8"3Έqa*W:ܹCR\8d-,€|^676~z&,sY.zsOD *8X;D#q>`-,xpn=cUլ5o1yG6\;< Vݔ 8B+ 1Li6|c$=n/\x< ]ts'֖lú)`g6I䷥=$C0dq'ZSQqyy+ +-mƲuĪ<mq?><k mYqMyN;2|us^J# xYDT;<z${9XlmpT qFMM2,[춎 'W=lç*l@J5ڑ_^Ne395Qʤ*31=@t<+gsf)ʺG `ؕ9kV@P 3d<=.d4BleSH08)G=PdŠJv h dm0`k>qmÅY$/ЬJcEء8Oh@e\*$ _y}ŋ+ksy/>DނHAE6}E.(Q3'UXK\b>'8y+5]bFa J1S$`z\91nv|Bx-bd1/|$6 xvRZ`W[6q!ĉܱ]~)hq<#|!eH7 _1r8b)ėmqpD? "QPx&l5GkIbuϼ<% b nfsj`Mp䒯O:`ue"d#[sy>pO,r6vd%GU3{Y)xln[3,,]SNE9F<a0C\fπ !l샥su `w[ &pc-@ |uR4+!B;yJ0qa,"ӑ%@DXz `%%I{̹=t e<3v8neߙ oj_spCic ,B[T͗flؖk^hHP2胼kjZp.(03=|>e_MXM\I#Zju_w^RUT@џMϙNhވZcQJIQ9kY/姃Jq1iM<# rdyNȊQD5!$uSslyPf)Pz[S*C# KuJ>Q\'gZ8dzʸ%b- "1KG0mN);00Ku Vm>h—gÕ3h.?6`ęcmR?D`*%F&ftaOoRR`V08Fzk;cAxmoNRhy.Rl Ck_w:e3}z/l<@k'!_]~chܢxN2rv )%EX hjBUDha5Mp=׿4k4IEe-pօe|\WT(A Y?b,1Y&3ч&`g jZX8Y8هy(Sz=A APC{ƾҒco RSQ9.cx 8?yF;#LBM}' N+vR ?}y /&OK0)Tw"߂,w _ʺJBo>gU[21Hgs~VdDUd@b-.se| 8dZ`$1'sax(n֞`O' U!BuUm8;86hU( v"? T 2\!!ra(y(3?9q:\32HN:U.,&·{Cwb8hV)b 38hC79lGI 5;Fkz(]>Sw[XoR : 97J, #XFl,U5I@DMTJ`\=E^QWGKXh%\{r ,qזS>3{Ӏ\d#C@1HӀX j(.Q0t6#PIƘs]kjGc xm ehBq V7L"? 阳anY\1+#fMhEyc7{(32E~ǺDqߘ;s.ә ?i)1X?( i,͘%b1 Jq<&뵵{ _+p8S>#/vx(Ls=?*?T[:$Qs&XBg7 x%N)>: 4+6P 3Np zЍ- >77 ] 2mm+π+.'`$m FAXK3 ?~_ !/=Fv*:p?']I|sad..uͅ qBc0AQ%{buѧ⯿N7^p8ʰN6'qLa2+x/b}\iFf,,׆Bjp' ϴes`=;Zt~ 雷Px 'CLfZ7oXnjƒB `x v:څ蚟Hr}, 3p Zy6a#-4FBdGSf+~n\0 3&VJJR?"CB(~\Me^3'N5'UzX?,LdA#(sZj& [ d́*Wy)y,?m8-k6 50M_Œ?`$\lD/)b~nXNk54 NƐ=ޘLT qnO!M|ZϞ3'z!S<,`0oH#[n;G10F9Jr0¿%>t0%1]m89Qpm;, } @-qg~L+ ]KkN B$".C`okptrØ;daQQRHlUL{%eD԰(hBX:Pp ^b?MDȇS >bRj]J4B('۪"1a5FUX% R:LVQQ=LPK'YgB)rA,JRAƟ q0Nݏʨɳ[Ϟ%O(7Q.kH#M?q; |FMgIHimPzPO|v̰LEZe8:] UaG[~jN\>PENի-vKA5=z4u\+18+"dfP&lfrpMҧKiܧ6|fy 9_%R̼R9Vpp6%ɴA{~frFpi> {K0 W"NYף{XOmǼr~"rKiz_^gi iztW i߾{\ Yoy!( {{l.q-}tvٓhS< bw0L+t„B;zJAu.)ɑ89P m 0+Ï.QC,?Xw ܗ"oׅuKئo*2TJ#UR!,RF_(/ aR-2HƑm5]ZۈQ"̺C}C BS NКxD߾5CLfV %ټzDqwN43˄nH-rj#Ԗ.Xo.EGrW8279X]3ooUDw =`Y斻 Gf,K4in{Ctb!,5'zqu wcA XfK4*se {'7ʇҢ _ {:r Nң,Pz_@V^veSWm\uc<_%;Zǃ9r~x]T # e7'woz6H7051n9Sϟc疟 rJ%; J5*tx ߎ:gUY;@xݰ<% 88h#\1OCrN5=(]s2>ƌӰrkw DdN|"U0I#aVDOoWkp/\37  {~R &zLt { :h'2H˶/ɘLW)g I@e̜l}]ޖݻM Sf|?_g϶Z[N \?;{/,,b/K4QK%O!䧉ILb%QAAz/˲la{mk}3,guν{ι|0Ζ #5ˇO&s#\>9MttzFK1턿n3c |TN7 f<]T`CJ<"%}1J4`ɩGF{6tÿֵC}w6lT' :u+ =!3t3=X`1#۷QD$J}~.HWUU088.AbgN9)ɧgjg$bn'm>3~ "C.6o~Mg] @IgaQ}M-YT;n'E}t FKtYv_ॵ l;71U_Xi08RqL 2t2q4ᅽ. u h=|VУ:V۾#BooG7tz1['VX|F&ܥ/fHcCl(}$g -PmU9 8y m~8 G1,G'Ο@(h}VK`I2م-SД bh„ᰄ5In> o$x4pxbfM,gա{I6ih. &aZÀ` ]''gÔq4h@hPzv5n>8 3K3rDخ̧[pc Ixy( m@z چl (_~ 64`g ) NyfyPkx3$h$ }O]FaE~N݉W_V) /b=>\XTpK;²Ol!_H_/?Z%LϵM) X.A CT#2ŃqVtw9O'8:}EA@P1NMUX,Xa2}YlD]Էj'4s~*|mV!Ahc?!ZNL66A@gpNy|ez\09)P==!bjQc@Ux~B!v`]] ۛrb0i p$3:7hcNwXshV> 3+t??WLM~n$#ԆOs诔et#a2bM&=T|ܰr_ 4:ဝ`ʔRO^L):̜{b 7ĐFF˅_Za,2ڬN{d}ʯ+:[))v XCQfDq D2K*lcz5(*)Ys{`-ԭHOɆ@#\2 s28bD!LhJ( >2.ʃ<yE. ]`q{P#' TUws5=3rM 3o_\5ulKeDdKj݁zzq=F q8:S2>;9/,CFKȲek3j]ۑGgBPzy~\0& F.2ƻO~j w9gD@owI.Ҡw7:?^xL ]]vjx5ÀXKqv"?>9ֆ8,L BPyH7dxfX^(  aD:(iRi*z7<UE>^pߜs{x$ xL0QPqш8LxݬT>}=G q>>X3d fDz)38o9YbNhX N$xldg;\\w-}V.%T߯^L<sn?A',Jusz@ X6;.4>OTȨ}Ix6 >D0_TQ6R- sl%ݦ0u;r,H>DNZ/լ#sk 1U[oٲnU]Jt%Zb;V!MQ4>X@Kn9z4ȴ¬1ey5>Jc >d77/kMFKZ //rX`/oഌ@d*}@]VCSVZ52?z2[~oh!Y]44td?"v[Km5CtYzv?Sʧmn.i^5М0O IR9<'ÃNA];hŊZV.OsNc7a!ؑ@Wݼú3OE46 =ʄV*F}Hthz} %'ݼ]B/p&tn- ; n 9V<cm^,H4H;Ϝ#CgG t•16g H$#TCcNdU~~ǽ^ut"5d<^gq@釞S?aL34YjF9v 2*23hA=6$aV|mfGJu $[/|NO"6^%7, &kLRY< MK+&f=?33jӏL#dt"^2q[x']$Z`J'_#csNqhЙ@j(vť} oio z=^paz{{XEaf{ܾDhL/3.=fQ?4 UQ¹#o8;. ǑSXjaI7uò]^hp%.A*0 &I]! )u+OЯN]Ȑ% k&Mm~n* f73yLW8:+gu,R`EkZ_ ?ZWO}#:A$%OGUUꋯN/^uM0 O63SpXy}7sxB$"d ~yi!7y~$p ѵ0s=֎>@ P APu>>d(3cF/uWTgMn͟  WG)xc[:ۛS!6dvXQ:dDܼͬ4 '8ے{x9 N&D4.ؑIl#ɾ4(]S%| _}޽{Q׸r)N03N WZ,X+b#i-~( k {`V~@N/bybDZC]>&!'hԭ0Tk4CNR#bPP&&=z/rk~bI_r<13}0NX3u0K o>RM Nᜀ/ |'0k޴{U"&ah3YVΐpy`bSto#o'TML˦MBiM~LjfMKuќt{$ *4#mVL}V1}A$@zt7+|:ɕe$p8h[N%HzPPk wqQ^>K::FscK;EA [wC+jqњ#/ Xdb;)ɈVw@$eT f>h5dGa}`f3tHhȮa#ҋgxvѡz2 IHʼ25" $-: ?!on@ `erPX EF0}6>>u->a ܋ga9La[MNZm\4=><_t'TUU \X8cGcNX<yh4أ1Izz}UpVpNQ2ci8#@oO9SneWwv@4)je&xHLnPP$ao Şؠk]egPY01FU( Kqs];q})8 㾄.nZ]ޅ|/1ory8/;?$=Qx@zpyydzCG~Y=jT1+5Aw5Q#I:/iC>ʱ' v8+Q$u%f±ʛC_ N8iEOg Bz~y'e˖IA9p8R{m?;\*4j:ViD*+yLc.ao4/DMvJ{ד՟.Iu\qr@)`˲9Y'j$)"X9h5q6ApZ1BqpiFoD:LA1Xa u cQ$d,vCve,< $ЃQnE1\Y_% |M?ʉj:z%cKƝH`X3b?9EF6P$[Z.5HگLyegɶYqrP8Z7@M@GQخG _Vm*Z'x3R->K$kX^pLiĖ* * \4Fd=g9 FMD9 cXƪ%pi>MA3ɣl\whW/k3ze 6]WbFb jnp{;}SY4􆫫ExIjt%j,QUHI.BBc9d)+`UƏ:}'B*M"nKE2y| 9ċzR,ȁaX-~%7 ~;Y =mVUAzT  j Mm|3)b&< _(qm.7>Q aci=ګ4WHk,+ ޶uwʌ7BMgHQ/$VIsll v73sڶnLb(̙q޽ ҮT. D_z"2PӞ@%px *wgo; ,֟5>8`rr&f M֧D(f^>{~ɞ^{^A*%4gbLf&.t_^3a"ADަe ^BT fi*S"oaS \ 6M*5 eV@w b=EދFdk\19Shtvw>eÄ?z EI  % ̈-5;f[rJhil12=|>LX \sqbHP T0'h$[dzL-:ܲѮ;UT?>opsO{us?bXQ\yvii~kKM  U|=PuSϦ˻t25i[aUp$Zr:]˛WD|4[UwmaW$uz.$Y&m1ʴ0tQfb9l}5_a^F"ݶ}WM;`VqbEUQw>5K$)GР}t$B3A 9e}CFq D9KSPza<[8e'Q ZN.J5, v fHs0j8W~}\!YmtCj@'ߞ$_jӐysDqıS=Dta*; Z h$8QWowO(!x_ kye_W"|&[0V(S-}+q1{nm:hU˻|/$ <T~tCB8'Vlv-3m9 dwg$CLD1x0!}ےYYNwIi֪,+!@ØM)j}6szE )ŞNPSҥ5J g  eJJwO /$YQm[Ȍj:^hc\ĥO Q Ku_٬=U/h֔~ _o(2.p~fs{ǝ 5R,ST#a7Z-ʝN\U͵Un#ցNL|@TJy``S<0D69tn@o?LF?ӓVԿ9Yj䗿DSt>h-C]{^vٷ\gq˯J:WEQR#(4G{uBj *hbJJڵ#`3~n=oc& |$>cXx !v- qOz 5icn B뼫Ni>f '%|KSg 8zM]uyI7;kjϭ&M!-C_voSk3;v~G/S$'`LyIȭ hm5Pu[̌U#]+6ZuW]P{A߰qpH1c2 WNeL5iXQ}fGFtGƢڥ3XKmR=~হ\ս:fgksN ި>+xaqǀS3U ߞ;"VX7e˖sQ Ň)ϟov643U PP|JZ$AEn뻻,O3/,Nrfby #ٳݾ:A朥hGBVH@i/d~e7#p)rRUŖ9S`W7\o5*-:(ʯNBLYVhmBOY_?gHЄ˯L\hcg9:ZA2{n}xG.vLS>wԼjَs% Hap# x;1oq`ٵ5q77n八]$'l nꤺ p xQ4HHZS v>HHK8_rJZ,Zp(`嬸Sb?K5j}{g4¦$iyX`bl4@"ͪ5!tT/]Fh@ʠ+*ܛRܭ&^ *.0/1 &?n) 9S:T瞈6!^Udhf`?ڄǡO:d̴^j V.1'J.|4lMoi=D ,s`@ 7 $V?eb^pj-Yc|;UI,ˌ*DO5i`!"^-lme1*{wo}[E7'p(HTpx& e}_z2,W䁞Eܴvc@q0&]qqNO!S/D_hP@>Tی]RB\[nSXzk JyzMwp*(p=A&Yށ|(JIL 2j@Nn1Z̋ck2O'Xǀ/HagiJ?KuAQ%]%׭ЧnѶh2!RoM-I_(3׈6kI@0/?r o%p$l:MfiYz "N'/b榐[{ ~giJَH-:+Xa։ٯYסC2],'uNjjƷAK&u1sDZ? z)دykCj}a?#@cNΤ35sbՔ0SWVquj|D|8N> QtkZٹifY& YBH\?O?,}b 4?*>-8I"(7`5py]%,k>'V!a~z YTCP^Ԭܞ& \J%9MoȺicGNN`MJ7^+0eJud/t߇HZq.,| ̫V}RF,.sZƲٗ81$$s;oX4Ao{{. q1XhQiK?*|L$6OWzL7DaNՆn\CNJZ!d3bϓVKUQA5C4k OT6Ӧ5aX(w^;:e{g{8c< ØXv$j"[ DRf! ;.0Mr~6-8*i]O?C8:UG'/;C\Nʁ6n?HCm'z81 b*}p!C%́t> ˺A-?Lģ jlM^pk60џU.[ NxHO"3X;lBXvŏ>=Bhڄ1ߛT鹭rb,'WV3lfo?ڱ'z1\%1&Kos3f$P`]i#;cYG}Im{[(40QJ98RP2&یr pv_TYN TzDL>6/_E4ϸRl&y뭕L8>[k+1g'X LeCSeSbfLak&hTZQ{Jj@bi1u:4jqgղ2 S I'8N#GaG6U xaI lٴ;m lYv[ q}Dn¹Iӭ"!jdn>92~Tj ōzY{L{'ɿc> 3f|GX\4Ɩ0-h p83AŔXP{{/U©Az'-04= -gL鄹-`DqTVjB;>ܹS3LVSX UP5Ћ͂Cbf/ $("wo,jjjL2ct`L%.;Sw &36AK\1CY36cAkWաsi\/x7A2a8w! xm͘.!$-&|0oh+۷˿ԣ)u-)9'p18A|oL1/l@VTӤo`UnSjK_7EV}d/Y2=τoQ(LPfIXդGI90c\~7_?穻:қOU bpv:-ӸRpp"cB\ 6O;؆]xyaIy d 3c2R|a?C`zʓ Fʆ|ХpjxqƢELj֍g=uNz9|(D<:[^޿c]\ :~ K[c[fsl2@F{A%w_8]q8~0rgzJ ۯip-#}[=_~ɌW_]>xӸ1, ?n5hF@*l8%lO `_54g{)'̶8Y =_ bU oeU/!*'uJ;PZU=ZͅMµtft>\_zD8,"I)`KIi ,!p A%VSx7d+^޿NtLٱW}$[?ʙKmVA.&F/Ppo=L=\tywO.\X (o!S OvaW :[i;~rHu&/lBo[F.>*ǀSUWT 0U]yM[ř gfl:@ڥ^qcxW>eI_C|o{>a\vsDk̶ 6cLW;3q+~础[q!UT5]9Zf<[C{`]ҵfu> zi'[㟴*<1j ;WhxNg@VH3mkWS8:U'WPpŬ/ECF: ΩMHI;jqK_7C {v鯣xOpPL~_üZ8 oET?xzV^Q߹{ 4b0Q|}ISe3gb12K:{49LUBRs3Ql,j äR f&y2iD!U2 ~wA `(.$%9xg1TElm<{ɖ}Bm[z+<՞CmGlfZ3bc&T61,a1} aG46k \ sotCd%m?<)&=`rb`&M歏vE_NVD<"Bi`O, Y +ob˰ί( †ޱݪְVhq*!Nܸ2f7vT3?tt ٹ'k /TO᛿4uw>$jk_z8D%9ł:ILLQaYLj(!d2d½RCw4(}#8|.曋dy_n%3 aYApJ"ƻ#?JG`_법?;w~#BNHr+hcV4%$2SNG\+)wޓ`Xz)tTA>U|Y<3) ]%W\D?} jvμoڴ'WONB2JKL3T; ѐ@$`$?(St}\}kqWj'/f\d~ Ζ'ׯVq6jocpiiekn}68QT>+SM,kS1 $ (Lgym0*FF##m^>d$ k0b;g,I'k[^>{:#NGc_G%K Γ֟T4a.8֤"Ĭ?dYSSEn\~tFu n͸D &/IEUx֢i8b7ȦŽ6׋KQ?@p$ku ƧIJ yFFp? ϴw[='8F,Hi71$~+$x!#awGff[)gTD}X '͚vn!S95YRM`2Dqca")[6G~"Zޭnn&zs֟Gqcԑ @ww{Ұ_j1ԝ~<'6 ˨ Nq: H(°Ga]&3~j?813@nٗyRv{5 m}_& Qǘq7,w Af?dK~6Ǩb̙ڦ[dKcaxYmO}'8F ~grud7{&po&~ QAZZ ;jݛ_W"ٹ NLyK͝Owv^~˄81*(+?.+G||reINUMTCR>zõn}?Xv?81 w~^ռ+6!/OCc^{ͮ6mLZqQ@bxWc0<ɔ):gz{-.O%.: @Wٜ%}70JS4! gi^7;kx( Nq Tk]wyR }yW|`kް)g};% Qٳr@]qxtlC#_5$3]~881j/N×r3lw{7uwkN=~'8F\XRbuXK#8Ǘq#/1G_b;F{IENDB`( ...6NT*66?33'88-Z_jo#5 NUU'7Z /  5f$H"Y?? 1a"a"3'' -Z l'J%RUUU$$$8:' U$$$'tSn'_!35**AFidkaf%>AJ*** `Nn&k&C#:H$$#=Bl%BG`H$$SMo'm'X *x***8 EK OSD1'HIm'n'e#8(7<3bhfl (9b=Dk&n'l'C  7UUU-44";BQFLa(/q$ -;G2< V 4@h%n'n'T(e33*D2 """,:f$n(o(n'Y +u??kBPmwug|IU(-k3<g$n(o(n(`"6"""*?  dAMkvwsWg2< ->?i&n'o(n(h%9 U%*0,.m",/Q$66 b@Mmwwuav<GBJDk&n'o(o(m'? &*"*X` ~=@W??? kDQqwwvjDP &V ?[Gn&o(o(o(n'E+515H ah"~KYsxxxmJX#(d $HHuNp'o(o(o(n(O*N?U!88M ls( #(XiwxxxpM\%*g --)Vo(o(o(o(o(Y &j$#/5+cj ov V\ 5.7f{wxxwpN]#(d !-c"o(o(o(o(o(a#&  15wGNLS/50***fBPrxxxwpJY!'[=5i%o(o(o(o(o(b#+  ?).c '!%DUUU?33/%/8 " &]pwxxxwnFS!L33iEl&o(o(o(o(o(d$7///2<:F#':333%80)0%em '+/A7=+p(f(f(f)h*k)n*s,x+{.---.~.y-q,g/[%K@4(  3U`ANswxvjOa9&Q(1O!9R#6K!-: %*f%%%"333333 mAm&n'o(o(o(o(n(m'M ;07dxN_$(8**1$Z` ry "- AKQRTUVY Y [!["["[#["Z!Y TRMIHD<1'z%^"< '   %' -e{wu_q7(Q.Lt8o=~A?9u.T~$4H!'*U''' ---$Wn'o(o(o(o(o(o(o'O H4AMrMZ %/333&15CINX]BGh?$o,?N[h$m&m'm(m'm(n(n(n(n(n(n'n'o'n&o&n'j%d#^"["T H>90} \<$$ |KZv[n10U5iC8p9t=~EGE9v)Fh*4y!!,UUUQ5g%n'o(o(o(o(o(o(o'O P$(,lXiuJV 'U80$$$$$ :c,4@O\!c"i%o'n'n(n(o(n(n(o(o(o(n(o(o(n'n(m'k'j&e$[!SM<2/kC ) ;09Sc3/V8qFF.W/Z-UCGHF@-U~!-:(((55#Nn'o(o(o(o(o(o(o(n'R,V5@ktCP 33" ?#f'5=FSb"h$l&n'n(n(o(o(o(o(o(o(o(o(o(n(o(o(n(l'g%d$Y KC4 %m+: &,G8nFHG:x.W8qGHHHGC/X"-9...!!!E. d#n'o(o(o(o(o(o(o(o'V4aUUU &WO_wn=I 333 /&P)-9IXf%m'o(o(o(o(o(o(o(o(o(o(o(o(n(o(o(o(n'n'j&b#\!N>5#O^/A7nGHHHGEGHHHHHHB.T~%-5 Im&o'o(o(o(o(o(o(o(o(Y!3m330;f{wh|4= UUU***'""4"_*3F\"j&o(o(o(o(o(o(o(o(o(o(o(o(o(o(o(o(o(o(o(m(i&^"B "+<6lFHHHGGGHHHHHHHHA)Ge,_#n'o(o(o(o(o(o(o(o(o(]"3v$$"YM]tv^r&.c$$$###+#l(<Vi&n(o(o(o(o(o(o(o(o(o(o(o(o(o(o(o(o(o(n'`&5%55eFHHHHGHHGHHHHHHHF\!l'n(o(o(o(o(o(o(o(o(o(o(o(o(j'E(/LrCGHHE9n7-T}?5NtU)n'o(o(o(o(o(n(o(`"5{"%*Yiuwwm8D"33A 7Xk%n(o(o(o(o(n'f'< -2^EGHF3f -!QOPOW^FTcptxiefC(-IE /1G<|GHHHF1_7n4j+Ot)<'3dGHHGC-V<}:qE$5k'o(o(o(o(o(n'T.X @!'O_sxxxw[p' .s ':' !!!K&Df$e'= ,2\FH@$Lv+B(W@F?=}3c+PxBGHF<$Mx "*==GHGG4h4fA7GGGGGD,`,&IoDHHH<}-WD6\R,n'o(o(o(n(n'O MA 8Dbvwwxxwv\n'0t 407j:EPaYk1;)CcAF7v#Ku-`AGGG2b5iB2b.V:wADGGGE+]$74jGHGC-T?=|@+?j'o(o(o(o(l'F, 3O`oxxxxxwo@L*$ (-Y7EL\_sns`q14Z<~E7v.dGGF0\9tD7@^_(o(o(o(o(h%? %GTi}vxxxxxxvXj)0i!%609tCP[nktvvo@$U2]8q,U&Gj2dAGHHGH1]9tGGGEA7n.WEGHGA"Jr&PzDFD5h3fF6^R,n(o(o(o(d$MDtvwxxxxxwl:E  !$E3=K[cvrwwwww\n.8\,T~.X8q9u2b,S|;zGHGH1^9tF=EGH>1^EGHHGA-[8q?3g8p0\E;wC(9k'o(o(o(_&RVwwxxxxxxuPa O#&V6AO_g{rwwwxxwrC$Y,Mt0^BGHF;x,R{<{GGH1^:tC/ZAGHEBGGDGGHD9s5k-W;y.WDA8:Te'n(o(n'T'_lwxxxxxxwey2;$! 'b9ETdjuwwxxxxxwdv47`=CGHHHG:v0\DGH1^:uC.VAHHHHHE3e3e?DGD/[2a@-TBF5Lr[)n(o(l&K/kwxxxxxwpFS!!5 *#(l;FWglvwwwxxxxxxvPa1UFGHHHHHFAFGH1^:uC.WAHHHHHG@4f,T}/Y2c0^,S|?B,S|AG7dM!0n(o(d$OEqwxxxxxuWg" '`$$$  ("(p;HWimwwxxxxxxxxxwdu2#F9rHHHHHHHHHHGH1^:tC.WAHHHHHHGGB<{6m7q@GC-S}AH<{A(9l(n'Y$W^wxxxxwvg{9B UUU!$j9DXgmvxxxxxxxxxxxwh{A;*4M@GHHHHHHHHHGH1]9tB-WAHHHHHHHHGGGGGHE9tDHA:6Ne(k&O.ewxxxxxwoES#(33""!/2K3:r(55L:::  ]5?VglwxxxxxxxxxxxxwlKBB!1JnFGHHHHHHHHHGH1^8sB.WAHHHHHHHHHHHHHHGFGHD5Ik]'d%LCrwxxxxuSe&+W3 FKf|.GJR???$I. 6P^j~wxxxxxxxxxxxxxxpNKX$E*4aGGHGG?4q-a+]0g8yBH1`7oB.WAGHHHHHHHHHHHHHHHHHF4YW*S&\fwxxxwv_r19+$$#tzW\33 1(1GUfyvxxxxxxxxxxxxxxwtUZU'h%=%5;vGGG?(U)@     &4P0h/\7oB.VBFFHGGHHHHHHHHHHHHHG8iC .I8lxxxxwg{;E $$1sy OUw "$h>I_qsxxxxxxxxxxxwxxxxw_lO,j%b%31F@GF6u0J    7U+Qz:vB-WA0h"Js-`:}DGGHHHHHHHHHHHHR[vxxxwlCP4***"""%+/]d y`f(49, E/7Rbmwxxxxxxxxxxxxxwtng|XhI6e%n(T'0JnDF3n"4   #5/b,R|?B0^@>` )<]2kBGHHHHHHHHHHH?31Xh|wxxwpJY!(E33 &d," %69Y"7;R*33UUU '$ *}ERfzvwxxxxwwxxxwuoeyYlL\@L2;%-+Y"n'k'@!.6hG5t"4      7T7t5j0\EFAD=^   :Z5sEHHHHHHHHHHC6K2<( /"sW>-fOk&m'b&20E?>1M     /-D9X!?^#A_/Y6l,R{>GHHF&Q}  &&R@HHHHHHHHHD6Jyb!wvwqQ_( -_ $$$ ![4IO7Q   7(/JYj~vwwwwwvph|WhGT9D+2 ! }Z  9$*** =d#m'n'R'0JnE;}+Z-]0e2i3i2e/\+Nt&Cb)Jn*Ns.W>GGHG>3k$8  7U:{GHHHHHHHF6XZ!nvqO_'/` 88  '$c5L^!R2% 7$$$!#m9F\oswvwvtjZkIX6A& + ! f A) ? ,nW!n'n'j(@ /6jD2c0]0]/Z-V-U~-V*Mq&Ba*Kp-U3c;x?CGGG8q-V/c*A    &:3mFGHHHHHG5dHZi~JY).W ??9$w:Ob"g#J+"U**E9E<<<UUU  S,8P`lvwvodxRb=H- 5"a=3%0Ei%o'n'a&12I?F<{9v:v;x?>1`,Rz3f5h/X-S}-T},S|-U9sEB9r2b.V4l(U2L '    ,.bEGHHHHG7o%-,1')n"""33':' ,X)@Se$l&a!?%_55C#AFf[aMR /2FD&,CQdzrpi~WiFS6@% +vG!3** 4] n'n'm'Q(0LrEGGGGG?.X0]8q0\-U8rADC<|-U4h?,S{;{7n,R{3c8u7v2j*X$Kt!Bf=^<\%Nx>GHHHHH;x6$07!y,,,4*88f33888 !!!-"`&:I^!i%n'j&S5<$$$***#DGd CN/ 7L\XkTfFT5A' .#U .  3*3e-T~<}F;z,S|1_6k/[.W@=,R|<~.W8rGGC;w2b.W-U~.X7pFHHHHHHHH>;/Ef%n'l'b#QB7/)"! " (1;CKVc#k&n'o(o(n(j&W7%J** 8 OS } {X_(  UUU#AJk&o(o(n'Z&0,Rz<{GGGGE0^5i?-S{?5j0[EGHHHHHHHHHHHHHHH>(d??--6a#n(o(o(o(m)M)4`E-U>HHHHHE2b2`FHF=~6^5X:tD4g2d<}-S|B@-U7oFHHHHHGBEHHE2b;wG9qF!/m*n(o(o(o(o(o(o(o(o(o(o(o(o(o(n(n'j&\!D+z,***UU'GKm'o(o(o(o(m)M)5`E/[;yHHHHF6n-WBHD6b;1FO-S+=*=6a>,S|>1`5jF>,T}7mFGGGG?.YAGF6m.XCF6^O)m'o(o(o(o(o(o(o(o(o(o(o(o(n'n'l&]!G05 $004`"o(o(o(o(o(n(Q'2XE3e5jHHHG9r,S{?GB4S{D$4c(n(n'c(=):7j3d3e>+Qy>G?.W1_>DD;w,S{7qFF9v-Rz?GC4Ee]*n'o(o(o(o(o(o(o(o(o(n(m(n'j%\ F1 7 UU"IIl&o(o(o(o(o(n(X&2HjD;z.VDHE7o,R{=G@5LpK"0h)m(o(o(m'_(67P8r,S{<~7n.VCGC4h,S{.V-U-U:vFF:w+Qy<|==~=};.Bh'n'o(o(o(o(o(o(n'n'n'k%d"TC0t1* 0_"o'o(o(o(o(o(n(e(80E>D0\1`8q0\.V>G@5GiO /j)n(o(o(o(n(l(O,3S}5k.XB3d/ZBGGC>>DHD7n,Qz<|A,T}9u5\P-l'n(o(o(o(n(n(n(n&g$\ M<-Y#UFGk&n(o(o(o(o(o(n(m(M.5XEB3e0]7nCF=~6BaS-l)n(o(o(o(o(o(n(f';,?9r0^4eC3d-U~;zEGGGF=0\.W>C/Z4g>87Oc)n(n(n(n'm'k&f$^!N?0n':*?&0]!o'n(o(o(o(o(o(o(n(f(@):5bBGGGA8f35[H+l'n'o(o(o(o(o(o(o(m'U*3Lq>-U6lD9t-V.X3d4i3d.Y-T~6lCC0\1`C6VQ.l'm'j'h%_"SK=,r,9***U FFl&o(o(o(o(o(o(o(o(n(n'S#- 65Co7Z8_7S:=lM&e^pL5i$o'o(o(o(o(o(o(o(o(i'A(98m?-U~5gDC;y5k4g5k:yBF?.W2bC8n4(8V#Y!PC86z-N0---??((.["n(o(o(o(o(o(o(o(o(o(c#3 LXdvY"mV$l]pj}ttRR^#o'n(o(o(o(o(o(o(o(n']'68S?@0[/X<{EGGGFA4h,R{6mD;y'FD<~7o5j6m:vAFD8h;4c?K %)FEj%n'o(o(o(o(o(o(o(m'P%###+ #^[lxwxxxxwQaOm'o(o(o(o(o(o(o(o(o(o(l(U+85M6c@EGGGFC:s5JwH)di ~pO`%,D  +[!n'o(o(o(o(o(o(n(n'Y(  ?333 P[lxxxxxxrANY:c#o(o(o(o(o(o(o(o(o(o(o(m(c(= -(4O4N}8a8f8c6T8>kF,b_ srwwu]q6Ax  ($IEj%n'o(o(o(o(o(o(n'^!/V*** HScg|rvvwwxxxxwui}O^7A"(}> *$$ &,5**UUU!MDk&n'o(o(o(o(o(n(g$; &&& =Jpxxxwwdw+1#2Fi%o(o(o(o(o(o(o(o(o(o(o'c#+ 3 %-D4?KY^qnvvwwxxxxxwkZkBO3< %t L&  *???,a*** !-.4]<GN]`rjqvwxxxwxxti~[oP_=J09+4+0i!'MB  ;3 . * *  , /!E_d %(-YUUU$UHk&o(o(o(o(o(o(j&F"%%%"$09ocvvxxwx[n$, "9a$m'o(o(o(o(o(o(o(o(n(n'T"$00((,',[;DETN_\pjortwxxyxvqmey[lQaK[JXGTBM<F7B-6  ip v|-)->%%%1`!n'o(o(o(o(o(k'J$''''#.GTcwxxwx[l&,(3Dg$n(o(o(o(o(o(o(o(o(n'e$6A3 !&5,5[/67@FTQ`XiZm[m_qbvcwdxexcvat]p[lWhP`FU8C) 1 "YCDIfmkrho$BKT... ***#eJm&n(o(o(o(n(k'K%!!!- +IVuxxwx\m(,*(XMk&n'o(o(o(o(o(o(o(o(n'O! %1 E U# (^/5e2;o1_"n'o(o(o(o(o(o(o(Z!.'''-"vCh%j&W9#&&&(UUU =Hpvw^r#+zUU%0;\"m'o(o(o(o(o(o(m'S+$$$33 #f@c#Y ?$H$6$*+4o`tvwfz/8'3V k'n(o(o(o(o(o(l&L$<----28(04?#^5N<(Q"""UUU4GVtvo6A /Nh%n'o(o(o(o(n'i&Dp** (55LR:?/'Q((UUU2;gzvv?K+&(lGe$n(n(o(o(o(n(f$<W$$$(+/F -0 33 !MQauwJXG* %Q?^"m'o(o(o(o(n'a"5?UUU+.1R  %CGu8  9CmwUg%f***86Vl&o(o(o(o(n'Z.)//7 OUah ho,5:4UUU$ )bVgv`t! )#.Mi$n'n(o(n'm&T*(..&AFeFJm(-22333 '>Ioh})2$(eDb"m'o(o(n(k&G#s.. 333*** %+tZll6@ #H9Y l&n(o(n(f$<!KUUU />Kj>I +-0Qi%n'n(n'\!/ ' (.ZlCR 7)|Ge$o(n(m&N&"""5AMBNB"33%^<_"n(n'h%<H'/.;#e88 $$$E4X m'n'Y ****5.5&KQ xrx!HM\?333.Ok&l'A"X333!$'M $;;8#*#$(Hh%\!(' ))> AFm$**$xB`#?QT 5((OVhp }'?BT???""i<>8=HLx&,1.??&..!>CnGM!;=c888$$$ $}\c [a/'/ **** ***33DKs !$$E???!EJc z x##(9-6?GJeknu"FMp666&&&!.$*??? ??????????? ???????????0 ?? ????  ???????(@ BUU'NX/OO[[5??]aD !7B... &*0,%,$$$8pN**5Z]!@~?TT*pvTOW .B^!U )33 }***(3]"d#9X^bFntET1:% +)%)X!k&E333&!;GO_ERy// %"U n'U!,""BN`sZlAK^' S n']#.1$;HxcwjQa06/*$Tn(d$:O$$ 9Bocws^p;GR$$%/X!n(i%@s[bCy~PW# 8Eyg{vfy@Lk 'A]!o(l'H???3sx_ANoxh}BOp2ec#o(n(O PXbgp[d8???&??$$$ %/Sdvwg{?Ke3***<i%o(o(S*.4M)7***ciM swu'''        333???5?lxv]p*5Z$$***Jn'o(o(Z!)ALBQUhow mr2CJLMNOOMIFBo6P23 !.Ugt\$w?Az3\2a+Ij"0>%+W[!o(o(o(^"'-#+TfIX8BTYX=A:-T>NZ!b#h%l'm'l'l&k&i%d$]"W!MD{8M- ' 9DQ&n=e;y6lDA2a#5G9""3>i%o(o(o(_"45:CWezFUx***!+59]@LX c#l'o(o(o(o(o(m'j&e$[!RGz5C!*>8fFC=GHE5f($-qSo(o(o(o(b$< ?P`lBNX???.16lEZ"j&o(o(o(o(o(o(o(n(i&J/6eFHFGHHHC5Eha&o(o(o(o(e$;I6?Ug|f|7<3+R@[!l'o(o(o(o(o(o(^+Ip:TBHHA7gO+Bm'o(o(o(f$>N%"SctZk( ($$$,bF`"n'o(o(o(f(@BcCF8`D.;U3;I-96XAHF6n=Vc+o(o(o(f$=K CNnqGTy 1|Og%o(l'I+B?E.^/A=TnGa}KIU9)4FiEH>8lO.Gn(o(o(c$<C!!<Ei|wcv/:0333$8?]!X.Ih|wsKY'' 4>5BM::8Nw>/e6uD6n5j:wCF<#Gn?G9s=qY"7o(o([",,L\nxwbv095-6BP\Vhf|lC6q7o2h?GF8pD=7n>G:|-_D;{DC@EF=9u5k:xBXh)m']Guxxw^q( 5&-22GV`sqwxxuI7xAGHC@F8p:uBHH@7p;{6k:w9u@s^0i'a^xxxk@M_)41FT`tqwxxxw^l:TGHHHHF8p9tCHHHD?AD=~BT*@a-jwxxrRb$$GO PT6???$+#CM_rrxxxxxxhsK8>qHF>8x;A7o9tCHHHHHHHGEH:X[Bsxv^p4:'(5C cg@;E_Xjoxxxxxxxn\;N'*$333**? 1;RPakpf{XhDS3>{" )J &(V l'P&;>|8r6m5k.Y-U0\6l=}C5j'Nw*? &-`FHH?{AT>Fw.. 333/KDX!V @w(RY(px4?PaXkM\=G-3T !& Ejg$j'A>]AFD7n2c6m:v:x:v6n5i9t3f0c/a.a4nDHHAB#7>0s#:' $$.!&55ZAO`#e#S 7J$$$_fP *8q,3J"",.!Y!n'c(:S}9tHE6m;z9v9t<{7n7p5j;yD>:v;zFHHHBQ(?k'`#U NKLS Z!c$j&l'_"G-"&??U]hT\B??Cug&o(],:e9sHFB=~:xFE=7n6m7o@HHGHHGEBT'=n(o(o(o(o(o(o(o(m'e$Q5L 1$Y"n(o([0a5k6l<}7oAGG>EA:x@y[ 3o(o(o(o(o(o(n'g%V >f,Bwg%o(o(_,=j7oDA7nABT]1l(Y 48X6l6m@8r9t8r:wA6m>@]d*o(o(o(n(k&d$V Af++#V!n(o(o(h(CKr=~6l8r?CMua.n(o(m(J1K4d8r7oAA@=7n9u6lJ6Sm(n(k&e$]!O?L0 ?uf%o(o(o(n(V1;S@qAgG;zY2n(o(o(o(c+=T6l:w8r7n7o;y:x7o:ZV+^#V KBQ.!$$$-"T n'o(o(o(i&D%\mf#j qZFl'o(o(o(n(U&<=k8r8r;{;z7p:v8k+3H{594"=tf$o(o(o(l'O/+|i}xxvIK`#o(o(o(o(l(S+B>f>=~=~>|CZR(q>H!+#T m'o(o(n(V-_-73h|xxp:COl'o(o(o(o(m(S,32WADJBW0}l vlN^/:[?yf%o(o(n([!1{ //`sxxk4>b8?_"o(o(o(o(o(b#/DPx[nltwxug{Sa=Iy-6=$ ***im:{vRZ;,(U n(o(o(`#7 Whwxh}2;Q$$Jh&o(o(o(o(j&@(2CMLQa^qg}nrtndyYkQaGS@L6?y(.r"w qv???***Ag$o(o(a#9$LYsxh|29K2R k'o(o(o(n(X!*B$02B2=IWIXvN^SbVhXiTdK[EQ9Fs' -T 7Y`jelsRZ"0:X!n(n(b"<(@LSmxi~6?P45Z"m'o(o(o(i&@"""**   **Ej&o(b#<%07%ewwl6A];M]"n(o(o(o(^"5`34j`#n'`";33 Ufup>Go>Z_"n(o(o(m'S05.BTk&X 8~EOcnrFR**@[_#n(o(o(j&J)(2Ma"L0T 07%atuN] 333>R[#m'o(o(g%C-$?H"+/;=K7{%"$$$N]sWi "9CW k&o(o(b#9| ?G &!&(3337BEh|`s%+)0/Og%o(n(\!5_"KK%| QV5Uefy0I34DRi&e$A 3?q.@888 1)Hd%[!2KUio tz^'A]"Ekrm |""":vB AFm&?L??KRG!IP&"""gmuzu???dhz nqa**? ==!???????|????0 x 0??(0` %UUUrw1ip"UU ***$HH BG#VK&***[[?_<?Z!BTfo> xt3330/`"S __qvn 6N;Do3;0%^"^!4", ,CQUgLYd*8/ \#f$CH$$GV|cw]n>L9-"]"k&Kp$$$ FTsi}g|JW` .,_#m'OruCZZ IVolM]s88 5Cc$o(W  qvc ]f???(PPUU''UfvmM[m**=ph%o(\"( ';@!(Cq???orG SWR2#0%3(5+5+4'1. . 8C[jv\s-/Qp'8I-$$$* Km'o(a$/ AOtCRi]f<}!.4BR[!`#b#a$`#]#W PHj?@/   RdX.=]8oFmNY a#j&o(n(m'j&f%`#W Ow=B.2W@vB=GB1^? i%o(o(e$C5@MOeyJXK***,8?DV h%o(o(o(o(n(k'M&;=wGDDHH?H/Hm(o(o(g%B=& &Ykh}>D)???+)AY!j&o(o(o(_1AfE?cH6M?8]@{H@~1h0f7p7nA/W,WF=?\f-o(d$0IBKf{vau1:;DM]_[oG%a8h6t?A:x:w<}D2k9xA;qX+Do(^)P[nxpIYd$$;F/Tdf{qo>E~:v9vAB=}B@CC:{9u9tKFkm(a?sxv]q& 3;D8Tdi}txw]'|@{GFBB:u?HF<|;x9u:wEbf*bUwxlEQB:?0Rai}txxxlyC8iEGDDA9t@HHGEFCCzY7jrxrSdz**[ev{:/8M[zfztxxxwp]@CRA(R*>*?(R~9s;{:}CHHHHDM)[su]n5 C$$$BE\ X >KN^pqwuoh|^qSaQ=]/=h Ad %7,Y?2k$0G3lEHHER:tat?F$$$$419(q?E0LL ???.9,Pai}pg{ZjIX;Go07E" )%=Od$Q.H:t,Y+V(Lr+Rz8rE8t-C  %LuCHGHF\nAM+<?LN=d. qv6+!EM^ZlQ`BMp09: !6Y i'CJqCA6l3e7n9t9t6m4j,Y'Nu&Oy;~HH=P~A 3T&((-'<DGzT^"U!=>pud $7Oj4=:"* EQg%c+;ZAE9u;y<{=8q6k<|B>@HHHFXg&a$Z!W Y ^#d$i%b#N/$HHRY%???<X!n(_1;g@GA;zCBs9r6m9u@GGEEAHP{l(o(o(o(o(m'f%U >=***ETg%o(a0=i=B:wAtR4Qa0DDh5i:v;{<|<{>:x=|P9Xn(o(o(l&d$X!DJ' -U!n(o(i)FOy:v:v?jV-Fm(o(`29X7p;y=<}:v8r;`^4j&e%^!QD8. ARe%o(o(m'N6M>S@\$pe-o(o(m(O5S9n9u:x:y9v8p9AaP!KbB65 -U m'o(n(Z!CBrwmT0n'o(o(i+LBe5).19DG ~333A^f$o(e%C/8fzxat%/>%]#n(o(o(d$:\/?BQ2M]_Vg[m_q]pViL[AL,3h*9YxszK1W n(f%F&\nvcw*3DGc$n(o(n'R'$3 ': **  ???Fi%e$F$$Q`grg|-4'?I^d%o(o(h&G$$$:S`#`#B->I-jj9F:***Jcd%o(o(b#=W3@RS;V 3 [mnBNT**IZa$n(n(["8;_g }."*=p+IWWkJYwDG\"l'm'U -'hkG job22`sRc<.V!j%k&O$3L TZ-*UU KZsSd$$5Nf$h%G$$$18$BO''3 Fe`#^#;E5Cu~iUUU?<HW!M":N ??739#,eiy"UU"DD??mt{ bh1NN^b>TT <<<p<8800 ??? ??( @ D2bio[`_D?,["G+??w{>*,NrHT[?L<"_#Sb3DN]x]oSbC33? c%Y"UU$HH33 Teyh|Zjn88 ;+d$`"3 8 q3ff"ff': Zko\mtHHDJh%c%<  @X7Rb`x}36$UK[QaPbQXPFH.7**=J:i~Y+4Q.S}7$$$Pl'f%E!HV[GVGbb V\/6AKyX!_#e%i&g%c$^"W!rC#H@@{Ax?@4Lqu^#o(g%K,3D\mRb> 9J VW!g%n(o(k'Q0MADwC~G>m_ 4o(h%L2Q_XeyJZ/CZZ"j&`"8Co6fDJbAGB==MNzfMvlJ])wwnyHT*^powwrX#Y@y._%Ks3i:y?FHGFigpqXjVjqnt8FTeij~ojauVfV?P6X*U);1G4k8u'9#Fk?HG{f#]or$$H3 F:>r$!$E]];!Ea\N]]oVhKWU<B*-F3a'FW=3g3d9u9t*TfD<|=~=|7n<}BDGE\&=d%a$b$e%`#T u?UUUH5g%d4>jB>|JXQ=`8d:x?@>Ane4o(m&g%["I-U**Y"m'j*GKyAjKH{h/l)IAd8q;z;y8rCIoc&`#X"hI&3D4e$o(c$TTm%cfh'o(e4DV:r?@(  @io)dk&u!XnI!&P Vmm@%[KXhBH$mM!]"?ffss ??LL _pvbxtE\ T!-c$Q"_o hs, x%DKJRi^"b#`%gSAYOG>vR,Ei&W$#Pa?Nb'N' T!-^"g&U:\?gArLTi*V=Gbt\hLf bqAU*k?lkoQE6q:y@BY=~mXn333 sx5_r(Tedbwat{ZJAU*R}5l(Qz>MVYLSR"%R GGO"DLev-H[??W#`!6CrBo@b<{AQMyf%b$X!\D"`$og,S<])U_(B?e@hN2Y`#OUR e%[&j}aSj(Y#?Z+dwVg[<K3py;mH$W"w\#N' f{raqac$qg&Z"IO_Xc?O\\ UU W^FH$FT _o0[md_a$ic$T*xx?_66gTUU?Y GT#{6$6w -GL2kq-i2pd-2.56.0/Win32/resource.h000066400000000000000000000004401475272067700153630ustar00rootroot00000000000000//{{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 i2pd-2.56.0/Win32/winres.h000066400000000000000000000001051475272067700150410ustar00rootroot00000000000000#ifndef WINRES_H__ #define WINRES_H__ #include #endif i2pd-2.56.0/build/000077500000000000000000000000001475272067700135625ustar00rootroot00000000000000i2pd-2.56.0/build/.gitignore000066400000000000000000000006131475272067700155520ustar00rootroot00000000000000# 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 # windows build script i2pd*.zip build*.log # MVS project files *.vcxproj *.vcxproj.filters *.sln i2pd-2.56.0/build/CMakeLists.txt000066400000000000000000000317541475272067700163340ustar00rootroot00000000000000cmake_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() # for debugging #set(CMAKE_VERBOSE_MAKEFILE on) # paths set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules") set(CMAKE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/..") set(LIBI2PD_SRC_DIR ${CMAKE_SOURCE_DIR}/libi2pd) set(LIBI2PD_CLIENT_SRC_DIR ${CMAKE_SOURCE_DIR}/libi2pd_client) set(LANG_SRC_DIR ${CMAKE_SOURCE_DIR}/i18n) set(DAEMON_SRC_DIR ${CMAKE_SOURCE_DIR}/daemon) include(Version) set_version("${LIBI2PD_SRC_DIR}/version.h" PROJECT_VERSION) project( i2pd VERSION ${PROJECT_VERSION} HOMEPAGE_URL "https://i2pd.website/" LANGUAGES C CXX ) # 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 include(TargetArch) target_architecture(ARCHITECTURE) include(CheckAtomic) if(WITH_STATIC) if(MSVC) set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") endif() endif() include_directories(${LIBI2PD_SRC_DIR}) FILE(GLOB LIBI2PD_SRC ${LIBI2PD_SRC_DIR}/*.cpp) add_library(libi2pd ${LIBI2PD_SRC}) set_target_properties(libi2pd PROPERTIES PREFIX "") if(WITH_LIBRARY) install(TARGETS libi2pd EXPORT libi2pd ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT Libraries) endif() include_directories(${LIBI2PD_CLIENT_SRC_DIR}) FILE(GLOB CLIENT_SRC ${LIBI2PD_CLIENT_SRC_DIR}/*.cpp) add_library(libi2pdclient ${CLIENT_SRC}) set_target_properties(libi2pdclient PROPERTIES PREFIX "") if(WITH_LIBRARY) install(TARGETS libi2pdclient EXPORT libi2pdclient ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT Libraries) endif() include_directories(${LANG_SRC_DIR}) FILE(GLOB LANG_SRC ${LANG_SRC_DIR}/*.cpp) add_library(libi2pdlang ${LANG_SRC}) set_target_properties(libi2pdlang PROPERTIES PREFIX "") 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 "${DAEMON_SRC_DIR}/Daemon.cpp" "${DAEMON_SRC_DIR}/HTTPServer.cpp" "${DAEMON_SRC_DIR}/I2PControl.cpp" "${DAEMON_SRC_DIR}/I2PControlHandlers.cpp" "${DAEMON_SRC_DIR}/i2pd.cpp" "${DAEMON_SRC_DIR}/UPnP.cpp" ) if(WIN32) set(WIN32_SRC_DIR ${CMAKE_SOURCE_DIR}/Win32) include_directories(${WIN32_SRC_DIR}) list(APPEND DAEMON_SRC "${WIN32_SRC_DIR}/DaemonWin32.cpp" "${WIN32_SRC_DIR}/Win32App.cpp" "${WIN32_SRC_DIR}/Win32Service.cpp" "${WIN32_SRC_DIR}/Win32NetState.cpp" ) file(GLOB WIN32_RC ${WIN32_SRC_DIR}/*.rc) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DWIN32_APP -DWIN32_LEAN_AND_MEAN -DNOMINMAX") endif() if(WITH_UPNP) add_definitions(-DUSE_UPNP) endif() if(WITH_GIT_VERSION) include(GetGitRevisionDescription) git_describe(GIT_VERSION) add_definitions(-DGITVER=${GIT_VERSION}) endif() if(APPLE) add_definitions(-DMAC_OSX) endif() if(HAIKU) add_definitions(-D_DEFAULT_SOURCE -D_GNU_SOURCE) endif() if(MSVC) add_definitions(-DWINVER=0x0600) add_definitions(-D_WIN32_WINNT=0x0600) else() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Winvalid-pch -Wno-unused-parameter -Wno-uninitialized") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -pedantic") # TODO: The following is incompatible with static build and enabled hardening for OpenWRT. # Multiple definitions of __stack_chk_fail(libssp & libc) if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang") set(CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS_MINSIZEREL} -flto -s") endif() set(CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS_MINSIZEREL} -ffunction-sections -fdata-sections") set(CMAKE_EXE_LINKER_FLAGS_MINSIZEREL "-Wl,--gc-sections") # -flto is added from above 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++ endif() if(NOT APPLE) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-const-variable -Wno-overloaded-virtual -Wno-c99-extensions") 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)) # "'sleep_for' is not a member of 'std::this_thread'" in gcc 4.7/4.8 add_definitions("-D_GLIBCXX_USE_NANOSLEEP=1") 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") endif() endif() if (CMAKE_COMPILER_IS_GNUCC AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 10.0 AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 8.0) # gcc 8-9 list(APPEND CMAKE_REQUIRED_LIBRARIES "stdc++fs") endif() # Use std::atomic instead of GCC builtins on macOS PowerPC: # For more information refer to: https://github.com/PurpleI2P/i2pd/issues/1726#issuecomment-1306335111 # This has been fixed in Boost 1.81, nevertheless we retain the setting for the sake of compatibility. if(APPLE AND CMAKE_OSX_ARCHITECTURES MATCHES "ppc") add_definitions(-DBOOST_SP_USE_STD_ATOMIC) endif() # libraries set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) if(WITH_STATIC) if(NOT MSVC) set(CMAKE_FIND_LIBRARY_SUFFIXES ".a") endif() set(Boost_USE_STATIC_LIBS ON) 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() 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() 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!") endif() find_package(OpenSSL REQUIRED) if(NOT DEFINED OPENSSL_FOUND) 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}) endif() endif() find_package(ZLIB) if(ZLIB_FOUND) link_directories(${ZLIB_ROOT}/lib) endif() # C++ standard to use, based on compiler and version of boost if(NOT MSVC) # check for c++20 & c++17 support include(CheckCXXCompilerFlag) if(Boost_VERSION VERSION_GREATER_EQUAL "1.83") # min boost version for c++20 CHECK_CXX_COMPILER_FLAG("-std=c++20" CXX20_SUPPORTED) endif() CHECK_CXX_COMPILER_FLAG("-std=c++17" CXX17_SUPPORTED) if(CXX20_SUPPORTED) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++20") elseif(CXX17_SUPPORTED) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17") else() message(SEND_ERROR "C++20 nor C++17 standard not seems to be supported by compiler. Too old version?") endif() endif() # load includes include_directories(SYSTEM ${Boost_INCLUDE_DIRS} ${OPENSSL_INCLUDE_DIR} ${ZLIB_INCLUDE_DIR}) # show summary message(STATUS "---------------------------------------") message(STATUS "Build type : ${CMAKE_BUILD_TYPE}") 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 " HARDENING : ${WITH_HARDENING}") message(STATUS " LIBRARY : ${WITH_LIBRARY}") message(STATUS " BINARY : ${WITH_BINARY}") message(STATUS " STATIC BUILD : ${WITH_STATIC}") message(STATUS " UPnP : ${WITH_UPNP}") if(WITH_GIT_VERSION) message(STATUS " GIT VERSION : ${WITH_GIT_VERSION} (${GIT_VERSION})") else() message(STATUS " GIT VERSION : ${WITH_GIT_VERSION}") endif() message(STATUS " ADDRSANITIZER : ${WITH_ADDRSANITIZER}") message(STATUS " THREADSANITIZER : ${WITH_THREADSANITIZER}") message(STATUS "---------------------------------------") if(WITH_BINARY) if(WIN32) add_executable("${PROJECT_NAME}" WIN32 ${DAEMON_SRC} ${WIN32_RC}) else() add_executable("${PROJECT_NAME}" ${DAEMON_SRC}) endif() if(WIN32) list(APPEND MINGW_EXTRA "wsock32" "ws2_32" "iphlpapi") # OpenSSL may require Crypt32 library on MSVC build, which is not added by CMake lesser than 3.21 if(MSVC AND ${CMAKE_VERSION} VERSION_LESS 3.21) list(APPEND MINGW_EXTRA "crypt32") endif() endif() 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") set_target_properties("${PROJECT_NAME}" PROPERTIES LINK_FLAGS "-z relro -z now") endif() # FindBoost pulls pthread for thread which is broken for static linking at least on Ubuntu 15.04 list(GET Boost_LIBRARIES -1 LAST_Boost_LIBRARIES) if(${LAST_Boost_LIBRARIES} MATCHES ".*pthread.*") list(REMOVE_AT Boost_LIBRARIES -1) endif() # synchronization library is incompatible with Windows 7 if(WIN32) get_target_property(BOOSTFSLIBS Boost::filesystem INTERFACE_LINK_LIBRARIES) list(REMOVE_ITEM BOOSTFSLIBS synchronization) set_target_properties(Boost::filesystem PROPERTIES INTERFACE_LINK_LIBRARIES "${BOOSTFSLIBS}") endif() if(WITH_STATIC) set(DL_LIB ${CMAKE_DL_LIBS}) endif() target_link_libraries("${PROJECT_NAME}" libi2pd libi2pdclient libi2pdlang ${Boost_LIBRARIES} OpenSSL::SSL OpenSSL::Crypto ${MINIUPNPC_LIBRARY} ZLIB::ZLIB Threads::Threads ${MINGW_EXTRA} ${DL_LIB} ${CMAKE_REQUIRED_LIBRARIES}) install(TARGETS "${PROJECT_NAME}" RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT Runtime) set(APPS "\${CMAKE_INSTALL_PREFIX}/bin/${PROJECT_NAME}${CMAKE_EXECUTABLE_SUFFIX}") set(DIRS "${Boost_LIBRARY_DIR};${OPENSSL_INCLUDE_DIR}/../bin;${ZLIB_INCLUDE_DIR}/../bin;/mingw32/bin") endif() if(BUILD_TESTING) add_subdirectory(${CMAKE_SOURCE_DIR}/tests ${CMAKE_CURRENT_BINARY_DIR}/tests) endif() i2pd-2.56.0/build/build_mingw.cmd000066400000000000000000000107051475272067700165520ustar00rootroot00000000000000@echo off setlocal enableextensions enabledelayedexpansion title Building i2pd REM Copyright (c) 2013-2022, 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 setting up variables for MSYS REM Note: if you installed MSYS64 to different path, edit WD variable (only C:\msys64 needed to edit) set MSYS2_PATH_TYPE=inherit set CHERE_INVOKING=enabled_from_arguments set MSYSTEM=MINGW32 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 we must work in root of repo cd .. REM deleting old log files del /S build_*.log >> nul 2>&1 echo Receiving latest commit and cleaning up... %xSH% "git checkout contrib/* && git pull && make clean" > build\build.log 2>&1 REM set to variable current commit hash 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 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 echo Build complete... pause 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 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 i2pd-2.56.0/build/cmake_modules/000077500000000000000000000000001475272067700163725ustar00rootroot00000000000000i2pd-2.56.0/build/cmake_modules/CheckAtomic.cmake000066400000000000000000000067761475272067700215660ustar00rootroot00000000000000# 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") CHECK_CXX_SOURCE_COMPILES(" #include std::atomic x; std::atomic y; std::atomic z; int main() { ++z; ++y; return ++x; } " ${varname}) set(CMAKE_REQUIRED_FLAGS ${OLD_CMAKE_REQUIRED_FLAGS}) 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}") 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}) set(CMAKE_REQUIRED_FLAGS ${OLD_CMAKE_REQUIRED_FLAGS}) endfunction(check_working_cxx_atomics64) # Check for (non-64-bit) atomic operations. if(MSVC) set(HAVE_CXX_ATOMICS_WITHOUT_LIB True) else() # First check if atomics work without the library. check_working_cxx_atomics(HAVE_CXX_ATOMICS_WITHOUT_LIB) # If not, check if the library exists, and atomics work with it. if(NOT HAVE_CXX_ATOMICS_WITHOUT_LIB) check_library_exists(atomic __atomic_fetch_add_4 "" HAVE_LIBATOMIC) if(HAVE_LIBATOMIC) list(APPEND CMAKE_REQUIRED_LIBRARIES "atomic") check_working_cxx_atomics(HAVE_CXX_ATOMICS_WITH_LIB) if (NOT HAVE_CXX_ATOMICS_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() endif() # Check for 64 bit atomic operations. 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() endif() endif() ## TODO: This define is only used for the legacy atomic operations in ## llvm's Atomic.h, which should be replaced. Other code simply ## assumes C++11 works. CHECK_CXX_SOURCE_COMPILES(" #ifdef _MSC_VER #include #endif int main() { #ifdef _MSC_VER volatile LONG val = 1; MemoryBarrier(); InterlockedCompareExchange(&val, 0, 1); InterlockedIncrement(&val); InterlockedDecrement(&val); #else volatile unsigned long val = 1; __sync_synchronize(); __sync_val_compare_and_swap(&val, 1, 0); __sync_add_and_fetch(&val, 1); __sync_sub_and_fetch(&val, 1); #endif return 0; } " LLVM_HAS_ATOMICS) if( NOT LLVM_HAS_ATOMICS ) message(STATUS "Warning: LLVM will be built thread-unsafe because atomic builtins are missing") endif() i2pd-2.56.0/build/cmake_modules/FindCheck.cmake000066400000000000000000000033211475272067700212110ustar00rootroot00000000000000# - 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 ) i2pd-2.56.0/build/cmake_modules/FindMiniUPnPc.cmake000066400000000000000000000013221475272067700217750ustar00rootroot00000000000000# - Find MINIUPNPC if(MINIUPNPC_INCLUDE_DIR AND MINIUPNPC_LIBRARY) set(MINIUPNPC_FOUND TRUE) else() find_path(MINIUPNPC_INCLUDE_DIR miniupnpc/miniupnpc.h /usr/include /usr/local/include /opt/local/include $ENV{SystemDrive} ${PROJECT_SOURCE_DIR}/../.. ) find_library(MINIUPNPC_LIBRARY miniupnpc) if(MINIUPNPC_INCLUDE_DIR AND MINIUPNPC_LIBRARY) set(MINIUPNPC_FOUND TRUE) message(STATUS "Found MiniUPnP headers: ${MINIUPNPC_INCLUDE_DIR}") message(STATUS "Found MiniUPnP library: ${MINIUPNPC_LIBRARY}") else() set(MINIUPNPC_FOUND FALSE) message(STATUS "MiniUPnP not found.") endif() mark_as_advanced(MINIUPNPC_INCLUDE_DIR MINIUPNPC_LIBRARY) endif() i2pd-2.56.0/build/cmake_modules/GetGitRevisionDescription.cmake000066400000000000000000000224601475272067700245060ustar00rootroot00000000000000# - 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() i2pd-2.56.0/build/cmake_modules/GetGitRevisionDescription.cmake.in000066400000000000000000000025121475272067700251070ustar00rootroot00000000000000# # 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() i2pd-2.56.0/build/cmake_modules/TargetArch.cmake000066400000000000000000000153711475272067700214270ustar00rootroot00000000000000# 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) # 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__) \\ || 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__) \\ || defined(__ARM_ARCH_6J__) \\ || defined(__ARM_ARCH_6T2__) \\ || defined(__ARM_ARCH_6Z__) \\ || defined(__ARM_ARCH_6K__) \\ || defined(__ARM_ARCH_6ZK__) \\ || defined(__ARM_ARCH_6M__) \\ || (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 #error cmake_ARCH arm #endif #elif defined(__i386) || defined(__i386__) || defined(_M_IX86) #error cmake_ARCH i386 #elif defined(__x86_64) || defined(__x86_64__) || defined(__amd64) || defined(_M_X64) #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__) \\ || defined(_ARCH_COM) || defined(_ARCH_PWR) || defined(_ARCH_PPC) \\ || defined(_M_MPPC) || defined(_M_PPC) #if defined(__ppc64__) || defined(__powerpc64__) || defined(__64BIT__) #error cmake_ARCH ppc64 #else #error cmake_ARCH ppc #endif #endif #error cmake_ARCH unknown ") # Set ppc_support to TRUE before including this file on ppc and ppc64 # will be treated as invalid architectures since they are no longer supported by Apple function(target_architecture output_var) if(APPLE AND CMAKE_OSX_ARCHITECTURES) # On OS X we use CMAKE_OSX_ARCHITECTURES *if* it was set # 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. # 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. 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) else() message(FATAL_ERROR "Invalid OS X arch name: ${osx_arch}") endif() endforeach() # Now add all the architectures in our normalized order if(osx_arch_ppc) list(APPEND ARCH ppc) endif() if(osx_arch_ppc64) list(APPEND ARCH ppc64) endif() if(osx_arch_i386) list(APPEND ARCH i386) endif() if(osx_arch_x86_64) list(APPEND ARCH x86_64) endif() if(osx_arch_arm64) list(APPEND ARCH arm64) endif() else() file(WRITE "${CMAKE_BINARY_DIR}/arch.c" "${archdetect_c_code}") 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 # 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( run_result_unused compile_result_unused "${CMAKE_BINARY_DIR}" "${CMAKE_BINARY_DIR}/arch.c" COMPILE_OUTPUT_VARIABLE ARCH CMAKE_FLAGS CMAKE_OSX_ARCHITECTURES=${CMAKE_OSX_ARCHITECTURES} ) # Parse the architecture name from the compiler output string(REGEX MATCH "cmake_ARCH ([a-zA-Z0-9_]+)" ARCH "${ARCH}") # Get rid of the value marker leaving just the architecture name string(REPLACE "cmake_ARCH " "" ARCH "${ARCH}") # If we are compiling with an unknown architecture this variable should # already be set to "unknown" but in the case that it's empty (i.e. due # to a typo in the code), then set it to unknown if (NOT ARCH) set(ARCH unknown) endif() endif() set(${output_var} "${ARCH}" PARENT_SCOPE) endfunction() i2pd-2.56.0/build/cmake_modules/Version.cmake000066400000000000000000000010471475272067700210230ustar00rootroot00000000000000# 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() i2pd-2.56.0/build/win_installer.iss000066400000000000000000000041341475272067700171560ustar00rootroot00000000000000#define I2Pd_AppName "i2pd" #define I2Pd_Publisher "PurpleI2P" [Setup] AppName={#I2Pd_AppName} AppVersion={#I2Pd_TextVer} 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 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 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: ..\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 Name: {group}\Readme; Filename: {app}\Readme.txt [UninstallDelete] Type: filesandordirs; Name: {app} i2pd-2.56.0/contrib/000077500000000000000000000000001475272067700141235ustar00rootroot00000000000000i2pd-2.56.0/contrib/apparmor/000077500000000000000000000000001475272067700157445ustar00rootroot00000000000000i2pd-2.56.0/contrib/apparmor/docker-i2pd000066400000000000000000000023711475272067700177750ustar00rootroot00000000000000# _________________________________________ # / 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, } i2pd-2.56.0/contrib/apparmor/usr.bin.i2pd000066400000000000000000000012641475272067700201070ustar00rootroot00000000000000# Basic profile for i2pd # Should work without modifications with Ubuntu/Debian packages # Author: Darknet Villain # #include profile i2pd /{usr/,}bin/i2pd { #include #include #include # path specific (feel free to modify if you have another paths) /etc/i2pd/** r, /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, # user homedir (if started not by init.d or systemd) owner @{HOME}/.i2pd/ rw, owner @{HOME}/.i2pd/** rwk, #include if exists } i2pd-2.56.0/contrib/certificates/000077500000000000000000000000001475272067700165705ustar00rootroot00000000000000i2pd-2.56.0/contrib/certificates/family/000077500000000000000000000000001475272067700200515ustar00rootroot00000000000000i2pd-2.56.0/contrib/certificates/family/gostcoin.crt000066400000000000000000000013251475272067700224110ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIB6jCCAY+gAwIBAgIJAPeWi4iUKLBJMAoGCCqGSM49BAMCMHoxCzAJBgNVBAYT AlhYMQswCQYDVQQIDAJYWDELMAkGA1UEBwwCWFgxHjAcBgNVBAoMFUkyUCBBbm9u eW1vdXMgTmV0d29yazEPMA0GA1UECwwGZmFtaWx5MSAwHgYDVQQDDBdnb3N0Y29p bi5mYW1pbHkuaTJwLm5ldDAeFw0xNzA4MDExMzQ4MzdaFw0yNzA3MzAxMzQ4Mzda MHoxCzAJBgNVBAYTAlhYMQswCQYDVQQIDAJYWDELMAkGA1UEBwwCWFgxHjAcBgNV BAoMFUkyUCBBbm9ueW1vdXMgTmV0d29yazEPMA0GA1UECwwGZmFtaWx5MSAwHgYD VQQDDBdnb3N0Y29pbi5mYW1pbHkuaTJwLm5ldDBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABC+9iIYumUNnsqKbnTluHimV8OdGvo7yeGxuqhfNNB2b3jvbFJ81scgH dsZtMQmUxgKM5nH+NQJMoCxHhSlRy2QwCgYIKoZIzj0EAwIDSQAwRgIhANNh7mOp nBBPRh2a/ipG1VYS0d+mNjSrpz8xWcG3CXPLAiEAjM5MTfv9sOJ74PeZVhFZ02w4 vhgyZCeLJ57f123Lm1A= -----END CERTIFICATE----- i2pd-2.56.0/contrib/certificates/family/i2p-dev.crt000066400000000000000000000014011475272067700220250ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIICCjCCAa2gAwIBAgIEfT9YJTAMBggqhkjOPQQDAgUAMHkxCzAJBgNVBAYTAlhY MQswCQYDVQQIEwJYWDELMAkGA1UEBxMCWFgxHjAcBgNVBAoTFUkyUCBBbm9ueW1v dXMgTmV0d29yazEPMA0GA1UECxMGZmFtaWx5MR8wHQYDVQQDExZpMnAtZGV2LmZh bWlseS5pMnAubmV0MB4XDTE1MTIwOTIxNDIzM1oXDTI1MTIwODIxNDIzM1oweTEL MAkGA1UEBhMCWFgxCzAJBgNVBAgTAlhYMQswCQYDVQQHEwJYWDEeMBwGA1UEChMV STJQIEFub255bW91cyBOZXR3b3JrMQ8wDQYDVQQLEwZmYW1pbHkxHzAdBgNVBAMT FmkycC1kZXYuZmFtaWx5LmkycC5uZXQwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC AAR7FPSglYrxeSPzv74A1fTwjajZWV0TljqEMBS/56juZQB/7xOwrsHFHA0eEEF9 dTH64wx3lhV/9sh/stwPU2MToyEwHzAdBgNVHQ4EFgQUQh4uRP1aaX8TJX5dljrS CeFNjcAwDAYIKoZIzj0EAwIFAANJADBGAiEAhXlEKGCjJ4urpi2db3OIMl9pB+9t M+oVtAqBamWvVBICIQDBaIqfwLzFameO5ULgGRMysKQkL0O5mH6xo910YQV8jQ== -----END CERTIFICATE----- i2pd-2.56.0/contrib/certificates/family/i2pd-dev.crt000066400000000000000000000013251475272067700221760ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIB6TCCAY+gAwIBAgIJAI7G9MXxh7OjMAoGCCqGSM49BAMCMHoxCzAJBgNVBAYT AlhYMQswCQYDVQQIDAJYWDELMAkGA1UEBwwCWFgxHjAcBgNVBAoMFUkyUCBBbm9u eW1vdXMgTmV0d29yazEPMA0GA1UECwwGZmFtaWx5MSAwHgYDVQQDDBdpMnBkLWRl di5mYW1pbHkuaTJwLm5ldDAeFw0xNjAyMjAxNDE2MzhaFw0yNjAyMTcxNDE2Mzha MHoxCzAJBgNVBAYTAlhYMQswCQYDVQQIDAJYWDELMAkGA1UEBwwCWFgxHjAcBgNV BAoMFUkyUCBBbm9ueW1vdXMgTmV0d29yazEPMA0GA1UECwwGZmFtaWx5MSAwHgYD VQQDDBdpMnBkLWRldi5mYW1pbHkuaTJwLm5ldDBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABMlWL3loKVOfsA8Rm91QR53Il69mQiaB7n3rUhfPkJb9MYc1S4198azE iSnNZSXicKDPIifaCgvONmbACzElHc8wCgYIKoZIzj0EAwIDSAAwRQIgYWmSFuai TJvVrlB5RlbiiNFCEootjWP8BFM3t/yFeaQCIQDkg4xcQIRGTHhjrCsxmlz9KcRF G+eIF+ATfI93nPseLw== -----END CERTIFICATE----- i2pd-2.56.0/contrib/certificates/family/mca2-i2p.crt000066400000000000000000000012341475272067700220750ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIBwTCCAWigAwIBAgIJAOZBC10+/38EMAkGByqGSM49BAEwZzELMAkGA1UEBhMC QVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdp dHMgUHR5IEx0ZDEgMB4GA1UEAwwXbWNhMi1pMnAuZmFtaWx5LmkycC5uZXQwHhcN MTYwMzI4MjIwMjMxWhcNMjYwMzI2MjIwMjMxWjBnMQswCQYDVQQGEwJBVTETMBEG A1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkg THRkMSAwHgYDVQQDDBdtY2EyLWkycC5mYW1pbHkuaTJwLm5ldDBZMBMGByqGSM49 AgEGCCqGSM49AwEHA0IABNNyfzJr/rMSUeWliVBbJHRF2+qMypOlHEZ9m1nNATVX 64OhuyuVCmbF9R3oDkcZZJQQK1ovXd/EsbAIWDI8K/gwCQYHKoZIzj0EAQNIADBF AiEApmv2tvMwzlvPjHJG1/5aXOSjYWw2s4ETeGt4abWPQkACIBbF3RuCHuzg+KN8 N0n9hAJztAqhRCdG3hilxF4fbVLp -----END CERTIFICATE----- i2pd-2.56.0/contrib/certificates/family/stormycloud.crt000066400000000000000000000014521475272067700231510ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIICKDCCAc6gAwIBAgIUcPHZXtYSqGNRCD6z8gp79WUFtI0wCgYIKoZIzj0EAwIw gZMxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEPMA0GA1UEBwwGQXVzdGlu MRgwFgYDVQQKDA9TdG9ybXlDbG91ZCBJbmMxIzAhBgNVBAMMGnN0b3JteWNsb3Vk LmZhbWlseS5pMnAubmV0MSQwIgYJKoZIhvcNAQkBFhVhZG1pbkBzdG9ybXljbG91 ZC5vcmcwHhcNMjIwMzE5MTU1MjU2WhcNMzIwMzE2MTU1MjU2WjCBkzELMAkGA1UE BhMCVVMxDjAMBgNVBAgMBVRleGFzMQ8wDQYDVQQHDAZBdXN0aW4xGDAWBgNVBAoM D1N0b3JteUNsb3VkIEluYzEjMCEGA1UEAwwac3Rvcm15Y2xvdWQuZmFtaWx5Lmky cC5uZXQxJDAiBgkqhkiG9w0BCQEWFWFkbWluQHN0b3JteWNsb3VkLm9yZzBZMBMG ByqGSM49AgEGCCqGSM49AwEHA0IABFUli0hvJEmowNjJVjbKEIWBJhqe973S4VdL cJuA5yY3dC4Y998abWEox7/Y1BhnBbpJuiodA341bXKkLMXQy/kwCgYIKoZIzj0E AwIDSAAwRQIgD12F/TfY3iV1/WDF7BSKgbD5g2MfELUIy1dtUlJQuJUCIQD69mZw V1Z9j2x0ZsuirS3i6AMfVyTDj0RFS3U1jeHzIQ== -----END CERTIFICATE----- i2pd-2.56.0/contrib/certificates/family/volatile.crt000066400000000000000000000012401475272067700223770ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIBxDCCAWmgAwIBAgIJAJnJIdKHYwWcMAoGCCqGSM49BAMCMGcxCzAJBgNVBAYT AkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRn aXRzIFB0eSBMdGQxIDAeBgNVBAMMF3ZvbGF0aWxlLmZhbWlseS5pMnAubmV0MB4X DTE2MDQyNjE1MjAyNloXDTI2MDQyNDE1MjAyNlowZzELMAkGA1UEBhMCQVUxEzAR BgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5 IEx0ZDEgMB4GA1UEAwwXdm9sYXRpbGUuZmFtaWx5LmkycC5uZXQwWTATBgcqhkjO PQIBBggqhkjOPQMBBwNCAARf6LBfbbfL6HInvC/4wAGaN3rj0eeLE/OdBpA93R3L s8EUp0YTEJHWPo9APiKMmAwQSsMJfjhNrbp+UWEnnx2LMAoGCCqGSM49BAMCA0kA MEYCIQDpQu2KPV5G1JOFLoZvdj+rcvEnjxM/FxkaqikwkVx8FAIhANP7DkUal+GT SuiCtcqM4QyIBsfsCJBWEMzovft164Bo -----END CERTIFICATE----- i2pd-2.56.0/contrib/certificates/reseed/000077500000000000000000000000001475272067700200375ustar00rootroot00000000000000i2pd-2.56.0/contrib/certificates/reseed/acetone_at_mail.i2p.crt000066400000000000000000000036601475272067700243530ustar00rootroot00000000000000-----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----- i2pd-2.56.0/contrib/certificates/reseed/admin_at_stormycloud.org.crt000066400000000000000000000040461475272067700255630ustar00rootroot00000000000000-----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----- i2pd-2.56.0/contrib/certificates/reseed/arnavbhatt288_at_mail.i2p.crt000066400000000000000000000040521475272067700253250ustar00rootroot00000000000000-----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----- i2pd-2.56.0/contrib/certificates/reseed/creativecowpat_at_mail.i2p.crt000066400000000000000000000041431475272067700257520ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIGAzCCA+ugAwIBAgIRAJNGLpTSm2U3GjXmFkjT/0cwDQYJKoZIhvcNAQELBQAw dzELMAkGA1UEBhMCWFgxCzAJBgNVBAcTAlhYMQswCQYDVQQJEwJYWDEeMBwGA1UE ChMVSTJQIEFub255bW91cyBOZXR3b3JrMQwwCgYDVQQLEwNJMlAxIDAeBgNVBAMM F2NyZWF0aXZlY293cGF0QG1haWwuaTJwMB4XDTE3MDUyNjE5NDQzOVoXDTI3MDUy NjE5NDQzOVowdzELMAkGA1UEBhMCWFgxCzAJBgNVBAcTAlhYMQswCQYDVQQJEwJY WDEeMBwGA1UEChMVSTJQIEFub255bW91cyBOZXR3b3JrMQwwCgYDVQQLEwNJMlAx IDAeBgNVBAMMF2NyZWF0aXZlY293cGF0QG1haWwuaTJwMIICIjANBgkqhkiG9w0B AQEFAAOCAg8AMIICCgKCAgEAo3XP4JToVbfM5e4GxyAqzu2DJV7ohpzlLqMLyz/9 XgZ7ipctNoxVZytoaNgMeAHInJn5OhUC4D+emsgsLJqFjnb2pxf6v45sRZLBMieb wJlxUmskucpTXwDwuHBk/s3xmH4IluadmzwiCMyycQFH/CNXmu5bonAuZ075rT1Q a8W0vb8eSfNYXn+FKQBROqsL5Ep+iJM6FX+oWMxJPk/zNluIu9qTdZL7Fts2+ObP X5WLE4Dtot57vMI2Tg3fjnpgvk3ynQjacS8+CBEbvA/j32PBS1mQB+ebl56CQTBv glHrXiNdp24TAwy8mwWHknhpt4cvRXOJGZphSVNRYFVk0vv7CTjmQg6WOBGD+d/P cosvyKxQz4WUSmtaKUftgCBdnemeM7BppZv2URflEOY6Uv3f9xlEC6yVEzSaa2Md tG6XRkDyupWCBFwmSm1uS+SXXhxAQGn3eMXPFA1XkwNnZtmM9kvSVt34FBE231oN 4oM7rE3ZDyTocZw7cv7bl8idmqsLXDTSFn5Q2iLwvw6ZeTenk8qHrq9kVH1UVE2l 31iKDNdGQkkVcnTWYfiqriwGLpTqbeD/8n9OBgCke1TiKQzP1o66nhkGJTiiRLFK A8rlSpqBcjGbXDs/X+Ote9MrCxE089eCqN51kzDeQ4Yvy8gDOTBPGEhBLirx+3pp yWkCAwEAAaOBiTCBhjAOBgNVHQ8BAf8EBAMCAoQwHQYDVR0lBBYwFAYIKwYBBQUH AwIGCCsGAQUFBwMBMA8GA1UdEwEB/wQFMAMBAf8wIAYDVR0OBBkEF2NyZWF0aXZl Y293cGF0QG1haWwuaTJwMCIGA1UdIwQbMBmAF2NyZWF0aXZlY293cGF0QG1haWwu aTJwMA0GCSqGSIb3DQEBCwUAA4ICAQCYzeYyPYhW+/SZSfpDl6JTzXy8S6NG+yjq pcinxaIF4XFoXLwWD3uHR4jgpU750mhHJjpGIaltZjFaqLbqtysbqb0vdShyaK/n Td4CXrNBvEHvLI6DZyDX4BcDlhCI7/dMCSHXwFIhRHhYSnTsJO32BdP5DsUUAlSW G0FlEEWjlxcdRwIITv70cFNlNOqJeyvtk9DPT+nEzssKWxVZcqN4GK8dvQVWgL91 8uzrcAYpAEQfmkKzsGmV4v5gWumLZmnzc24hUhVsHhIph4HAmjPMFCppI1tgiwg7 fH71MYB8b9KBJKipkLdAL292mDLS4G3MGQwMbcjnTyIqOktmyyj/1CorZAKqBtzu Qyo7z8FM2pd5nzk7QDx/vsJ4bNAYvVu7titDW5mv5JDoQcp2uDVGePlonX3I8iFx CqKFzGHiR0EU8oWw0Pqf+y2rEV4L74agmUR7VbA+/ovz0UnDUoXIynSwpK7Kfo8D B7ky9RnmsxJX6TXaMVW06IlYuwIUsAWbMhKvdXbGZur5VVi1ZY1/HgZZnoXejzCe w3mMl6movkcA0noDXQ+eauUDHjktrVUJdZKYvZNjfnz2rB+MI5wB/hzeBv4KuYFE oTFt8SwTzs0joM4c7RomTxc+QFe832SvjPAnxQn17qSjD8z4c7Ako6sCKvpdBSDm Hz8KWVkHZg== -----END CERTIFICATE----- i2pd-2.56.0/contrib/certificates/reseed/echelon3_at_mail.i2p.crt000066400000000000000000000040251475272067700244310ustar00rootroot00000000000000-----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----- i2pd-2.56.0/contrib/certificates/reseed/hankhill19580_at_gmail.com.crt000066400000000000000000000040561475272067700253710ustar00rootroot00000000000000-----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----- i2pd-2.56.0/contrib/certificates/reseed/hottuna_at_mail.i2p.crt000066400000000000000000000040211475272067700244070ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIFxzCCA6+gAwIBAgIQZfqn0yiJL3dGgCjeOeWS6DANBgkqhkiG9w0BAQsFADBw MQswCQYDVQQGEwJYWDELMAkGA1UEBxMCWFgxCzAJBgNVBAkTAlhYMR4wHAYDVQQK ExVJMlAgQW5vbnltb3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEZMBcGA1UEAwwQ aG90dHVuYUBtYWlsLmkycDAeFw0xNjExMDkwMzE1MzJaFw0yNjExMDkwMzE1MzJa MHAxCzAJBgNVBAYTAlhYMQswCQYDVQQHEwJYWDELMAkGA1UECRMCWFgxHjAcBgNV BAoTFUkyUCBBbm9ueW1vdXMgTmV0d29yazEMMAoGA1UECxMDSTJQMRkwFwYDVQQD DBBob3R0dW5hQG1haWwuaTJwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC AgEA21Bfgcc9VVH4l2u1YvYlTw2OPUyQb16X2IOW0PzdsUO5W78Loueu974BkiKi 84lQZanLr0OwEopdfutGc6gegSLmwaWx5YCG5uwpLOPkDiObfX+nptH6As/B1cn+ mzejYdVKRnWd7EtHW0iseSsILBK1YbGw4AGpXJ8k18DJSzUt2+spOkpBW6XqectN 8y2JDSTns8yiNxietVeRN/clolDXT9ZwWHkd+QMHTKhgl3Uz1knOffU0L9l4ij4E oFgPfQo8NL63kLM24hF1hM/At7XvE4iOlObFwPXE+H5EGZpT5+A7Oezepvd/VMzM tCJ49hM0OlR393tKFONye5GCYeSDJGdPEB6+rBptpRrlch63tG9ktpCRrg2wQWgC e3aOE1xVRrmwiTZ+jpfsOCbZrrSA/C4Bmp6AfGchyHuDGGkRU/FJwa1YLJe0dkWG ITLWeh4zeVuAS5mctdv9NQ5wflSGz9S8HjsPBS5+CDOFHh4cexXRG3ITfk6aLhuY KTMlkIO4SHKmnwAvy1sFlsqj6PbfVjpHPLg625fdNxBpe57TLxtIdBB3C7ccQSRW +UG6Cmbcmh80PbsSR132NLMlzLhbaOjxeCWWJRo6cLuHBptAFMNwqsXt8xVf9M0N NdJoKUmblyvjnq0N8aMEqtQ1uGMTaCB39cutHQq+reD/uzsCAwEAAaNdMFswDgYD VR0PAQH/BAQDAgKEMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAPBgNV HRMBAf8EBTADAQH/MBkGA1UdDgQSBBBob3R0dW5hQG1haWwuaTJwMA0GCSqGSIb3 DQEBCwUAA4ICAQCibFV8t4pajP176u3jx31x1kgqX6Nd+0YFARPZQjq99kUyoZer GyHGsMWgM281RxiZkveHxR7Hm7pEd1nkhG3rm+d7GdJ2p2hujr9xUvl0zEqAAqtm lkYI6uJ13WBjFc9/QuRIdeIeSUN+eazSXNg2nJhoV4pF9n2Q2xDc9dH4GWO93cMX JPKVGujT3s0b7LWsEguZBPdaPW7wwZd902Cg/M5fE1hZQ8/SIAGUtylb/ZilVeTS spxWP1gX3NT1SSvv0s6oL7eADCgtggWaMxEjZhi6WMnPUeeFY8X+6trkTlnF9+r/ HiVvvzQKrPPtB3j1xfQCAF6gUKN4iY+2AOExv4rl/l+JJbPhpd/FuvD8AVkLMZ8X uPe0Ew2xv30cc8JjGDzQvoSpBmVTra4f+xqH+w8UEmxnx97Ye2aUCtnPykACnFte oT97K5052B1zq+4fu4xaHZnEzPYVK5POzOufNLPgciJsWrR5GDWtHd+ht/ZD37+b +j1BXpeBWUBQgluFv+lNMVNPJxc2OMELR1EtEwXD7mTuuUEtF5Pi63IerQ5LzD3G KBvXhMB0XhpE6WG6pBwAvkGf5zVv/CxClJH4BQbdZwj9HYddfEQlPl0z/XFR2M0+ 9/8nBfGSPYIt6KeHBCeyQWTdE9gqSzMwTMFsennXmaT8gyc7eKqKF6adqw== -----END CERTIFICATE----- i2pd-2.56.0/contrib/certificates/reseed/i2p-reseed_at_mk16.de.crt000066400000000000000000000040321475272067700244200ustar00rootroot00000000000000-----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----- i2pd-2.56.0/contrib/certificates/reseed/igor_at_novg.net.crt000066400000000000000000000040051475272067700240120ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIFvjCCA6agAwIBAgIQBnsUOmOu2oZZIwHBmQc1BDANBgkqhkiG9w0BAQsFADBt MQswCQYDVQQGEwJYWDELMAkGA1UEBxMCWFgxCzAJBgNVBAkTAlhYMR4wHAYDVQQK ExVJMlAgQW5vbnltb3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEWMBQGA1UEAwwN aWdvckBub3ZnLm5ldDAeFw0yMzAxMjgxNDM4MzFaFw0zMzAxMjgxNDM4MzFaMG0x 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/ 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== -----END CERTIFICATE----- i2pd-2.56.0/contrib/certificates/reseed/lazygravy_at_mail.i2p.crt000066400000000000000000000040321475272067700247570ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIFzTCCA7WgAwIBAgIQCnVoosrOolXsY+bR5kByeTANBgkqhkiG9w0BAQsFADBy MQswCQYDVQQGEwJYWDELMAkGA1UEBxMCWFgxCzAJBgNVBAkTAlhYMR4wHAYDVQQK ExVJMlAgQW5vbnltb3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEbMBkGA1UEAwwS bGF6eWdyYXZ5QG1haWwuaTJwMB4XDTE2MTIyNzE1NDEzNloXDTI2MTIyNzE1NDEz NlowcjELMAkGA1UEBhMCWFgxCzAJBgNVBAcTAlhYMQswCQYDVQQJEwJYWDEeMBwG A1UEChMVSTJQIEFub255bW91cyBOZXR3b3JrMQwwCgYDVQQLEwNJMlAxGzAZBgNV BAMMEmxhenlncmF2eUBtYWlsLmkycDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC AgoCggIBAN3q+0nUzz9+CBSoXUNf8K6kIc9zF+OP1NVBmOu3zTtkcEnhTtoDNXeU EV8DhlBhEACbPomA+szQ5zp3O3OYQc2NV50S7KKqlfn5LBBE3BL2grTeBxUMysDd 0TlpxcHKwaog4TZtkHxeNO94F1vgeOkOnlpCQ6H3cMkPEGG3zu1A1ccgPiYO838/ HNMkSF//VZJLOfPe1vmn9xTB7wZ0DLpEh12QZGg3irA+QDX5zy6Ffl+/Lp+L4tXT uPZUaC6CL6EABX4DvQcFrOtiWfkbi/ROgYCeTrYw1XbDHfPc+MBxGo1bX7JjnD0o mFFvo+PjxvWDmCad2TaITh6DwGEeWKu8NtJAyaO5p1ntauuWGB5Xzua4aMmIy7GT esHQkhW+5IooM0R5bZI8/KXo4Bj52bX5qv+oBiExc6PUUTLWyjoWHb7fKdddwGfc lUfniV/fw7/9ysIkQZcXLDCXR6O/nH9aGDZ7bxHedw4/LxAXYPfNojb5j7ZVa65o PWD5xuQfbE+95DdbnKjcjYiam4kjApe7YPwOhtoRJYSGAkrpIMfzFxCXgjTsi3Kw Ov+sYmBvWBK4ROWQZTgHei3x4FpAGWHCAeTeeQGKmWQ8tT7ZklWD9fBm3J/KXo7I WCxRW9oedItyqbRuAGxqaoaGSk6TtPVjyPIUExDp1dr4p1nM1TOLAgMBAAGjXzBd MA4GA1UdDwEB/wQEAwIChDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEw DwYDVR0TAQH/BAUwAwEB/zAbBgNVHQ4EFAQSbGF6eWdyYXZ5QG1haWwuaTJwMA0G CSqGSIb3DQEBCwUAA4ICAQA2fei/JajeQ7Rn0Hu3IhgF9FDXyxDfcS9Kp+gHE56A 50VOtOcvAQabi/+lt5DqkiBwanj0Ti/ydFRyEmPo45+fUfFuCgXcofro8PGGqFEz rZGtknH/0hiGfhLR9yQXY8xFS4yvLZvuIcTHa9QPJg3tB9KeYQzF91NQVb5XAyE7 O3RvollADTV31Xbhxjb7lgra6ff9dZQJE6xtlSk/mnhILjlW80+iPKuj3exBgbJv ktiR4ZT4xjh1ZgNJX5br86MZrhyyyGWwHWHS0e443eSrrmAPD69zxsfvhoikRX1z tDz0zB70DwS4pSbVrFuWaIAcbg36vWO8tYPBzV8iBB/tBTURGJjv6Q0EoI5GHmJi LOhU3B6xublv8Tcoc3tgMqI9STnWROtTiCS6LsWNSXhVpIZqvaiOEtPN4HyL33sf j5rfPq76gKrTloeLnwLGq0Rs94ScffYkBap3fQ/ALb87LQcwSN4EkObur5pcd7TS qNdanvCGK8v1UYVzH4l9jekPGsM5euohwAkIl1kZ6+tqGY/MTa7HwTTQyLDTco1t sPy6neN46+H5DYHADyU5H2G39Kk3WcLmPtfxlPDM6e73+47fJkXnmiaWM0Lrt80y Enng6bFGMZH01ZsqBk09H+Uswv8h7k69q9uWAS95KE0omCMVtIpoPZXTnRhe6mBC +g== -----END CERTIFICATE----- i2pd-2.56.0/contrib/certificates/reseed/orignal_at_mail.i2p.crt000066400000000000000000000036601475272067700243700ustar00rootroot00000000000000-----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----- i2pd-2.56.0/contrib/certificates/reseed/r4sas-reseed_at_mail.i2p.crt000066400000000000000000000036741475272067700252430ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIFiTCCA3GgAwIBAgIEY2XeQjANBgkqhkiG9w0BAQ0FADB1MQswCQYDVQQGEwJY WDELMAkGA1UECAwCWFgxHjAcBgNVBAcMFUkyUCBBbm9ueW1vdXMgTmV0d29yazEL MAkGA1UECgwCWFgxDDAKBgNVBAsMA0kyUDEeMBwGA1UEAwwVcjRzYXMtcmVzZWVk QG1haWwuaTJwMB4XDTE3MDYyMjEwNTQ1NFoXDTI3MDYyMDEwNTQ1NFowdTELMAkG A1UEBhMCWFgxCzAJBgNVBAgMAlhYMR4wHAYDVQQHDBVJMlAgQW5vbnltb3VzIE5l dHdvcmsxCzAJBgNVBAoMAlhYMQwwCgYDVQQLDANJMlAxHjAcBgNVBAMMFXI0c2Fz LXJlc2VlZEBtYWlsLmkycDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB ANgsj5LhF4uGG4RDueShqYQZsG5Rz6XUAtK9sVGFdmdJTDZirUMZcCGCGZP/Harz QaZU9EYxOCztnpLCQksSCpdRsij56MURS0tW/1x7LHIDUOi911Of57jgIHH+3E5n 6tuRxEk6J/9Ji3PI+89kl0sPKMVFMyKkINprVTA5zr/keyYEG0p6HSEYYiJkQH78 8uoOCAmlk9mxkJFb+zviCk6jsYwdH+ofD6Lw5ueOlYUbeZ9Nd7jfSdf20XM7ofIw W2COtsbq3J7vNrQJMV7HkHxVx/7OqmjQF02OahZFZREVZqbHpL501iTn9Iqd5qKq IsxYjk7ZnP4UUCBk8NOU5TuWsy0qNw+TJDI9s55Fi4KPtXWf47HIl6CdpM5y/D5L eufCojSwPKlrD6x9gTyJdBggBZRIyplXdKffo/95hUhEkv86yfsVVR7Gu1uy0O8T Gtb8Da/oi5eEZBHWonLVicLPei5jeo+1gbR09PQ6s41uMZlOhMe4RSgiIQj/7UVo ffKdl1MPNKr1u2fgVj8kxqg8ZivWKQ2taEgimU2EkQcNcE96M9yQlNNpNvqSAQVk wYXlHt0AN6A1A8u1pItxaTwXnbmx+OBJZoKl4ZQeaC8wtKjTgAgVXp+g5iot2gir LjxCRx1WLG1c8vRg1W8CDZII8Swc8EWpMhI+0hPv7/4/AgMBAAGjITAfMB0GA1Ud DgQWBBTN5sKbrNzwE8sgMGDekfOPgX8/JDANBgkqhkiG9w0BAQ0FAAOCAgEAjLaB bHqvFTs0ikAtesk9r8+8XVIsP5FR57zZCek2vxkHcCQWw8Uqs3ndInRX4FirKSLT WRb4aSwFCkrmwueecTpXN/RBC+fZj+POCfdILEsA+FGreAM2q5ZXv/Q0jyIXOXEM +KL0JZXnNS0/dqR3IYbC7f39CL6Sf40gRGTwTWWGg3KnynoS0v1zQcZLTMhHBD2X tgdIPbroq9t4gXa7Dhm0egYfQOI/7re2wiZT7UWVVwEpYqKf6JApFHa1nNOFMrLF 45JHQIHArkoxpQdfSe9HBoyJiB5vz398rHZeqbJaF3PIg9rxWWY/NvvOVuIk8U5z 0jExhg29a88B32U7ndvQJqIuGiQghzCiLxC/y1+wAdpeDSbD3OAOHqplvMj3BUn9 yhDSLSjtfBJjnXKxtEcWLR0edHCGEk5mAcL7q1WNxDpxaICwGGpNZN53CtFx7amb egYil448DmiqoQTCTE9pBz8YjwiVfCYLYv17O0NJyYM9Efy/wL3rFlsPJniWHMuH imZybVU4ukjvfOZ+LY4COTwz6w4sfA7a+i+2mOynC7eKX8Yg6i1nXlcY1Z8ykNgi 7B3kz1T/DV56CIm6QUWtepfuKTYq4C6QrBBIXLk1d5g95aWA21u1LRqNZ9GLH+eA gfvIm7v+cELj8a53EQY0LafzZqNC5kQAp916coU= -----END CERTIFICATE----- i2pd-2.56.0/contrib/certificates/reseed/rambler_at_mail.i2p.crt000066400000000000000000000040211475272067700243510ustar00rootroot00000000000000-----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----- i2pd-2.56.0/contrib/certificates/reseed/reseed_at_diva.exchange.crt000066400000000000000000000040421475272067700252700ustar00rootroot00000000000000-----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----- i2pd-2.56.0/contrib/debian/000077500000000000000000000000001475272067700153455ustar00rootroot00000000000000i2pd-2.56.0/contrib/debian/README000066400000000000000000000005441475272067700162300ustar00rootroot00000000000000This 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. i2pd-2.56.0/contrib/debian/bionic/000077500000000000000000000000001475272067700166105ustar00rootroot00000000000000i2pd-2.56.0/contrib/debian/bionic/compat000066400000000000000000000000031475272067700200070ustar00rootroot0000000000000011 i2pd-2.56.0/contrib/debian/bionic/control000066400000000000000000000015021475272067700202110ustar00rootroot00000000000000Source: 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. i2pd-2.56.0/contrib/debian/i2pd.service000077700000000000000000000000001475272067700222132../i2pd.serviceustar00rootroot00000000000000i2pd-2.56.0/contrib/debian/i2pd.tmpfile000066400000000000000000000001021475272067700175560ustar00rootroot00000000000000d /run/i2pd 0755 i2pd i2pd - - d /var/log/i2pd 0755 i2pd i2pd - - i2pd-2.56.0/contrib/debian/trusty/000077500000000000000000000000001475272067700167175ustar00rootroot00000000000000i2pd-2.56.0/contrib/debian/trusty/compat000066400000000000000000000000021475272067700201150ustar00rootroot000000000000009 i2pd-2.56.0/contrib/debian/trusty/control000066400000000000000000000015001475272067700203160ustar00rootroot00000000000000Source: 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. i2pd-2.56.0/contrib/debian/trusty/patches/000077500000000000000000000000001475272067700203465ustar00rootroot00000000000000i2pd-2.56.0/contrib/debian/trusty/patches/01-upnp.patch000066400000000000000000000010011475272067700225570ustar00rootroot00000000000000Description: 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 i2pd-2.56.0/contrib/debian/trusty/patches/02-service.patch000066400000000000000000000012351475272067700232470ustar00rootroot00000000000000Description: 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" i2pd-2.56.0/contrib/debian/trusty/patches/series000066400000000000000000000000371475272067700215630ustar00rootroot0000000000000001-upnp.patch 02-service.patch i2pd-2.56.0/contrib/debian/trusty/rules000077500000000000000000000005201475272067700177740ustar00rootroot00000000000000#!/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: i2pd-2.56.0/contrib/debian/xenial/000077500000000000000000000000001475272067700166255ustar00rootroot00000000000000i2pd-2.56.0/contrib/debian/xenial/compat000066400000000000000000000000021475272067700200230ustar00rootroot000000000000009 i2pd-2.56.0/contrib/debian/xenial/control000066400000000000000000000015001475272067700202240ustar00rootroot00000000000000Source: 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. i2pd-2.56.0/contrib/debian/xenial/patches/000077500000000000000000000000001475272067700202545ustar00rootroot00000000000000i2pd-2.56.0/contrib/debian/xenial/patches/01-upnp.patch000066400000000000000000000010011475272067700224650ustar00rootroot00000000000000Description: 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 i2pd-2.56.0/contrib/debian/xenial/patches/02-service.patch000066400000000000000000000012351475272067700231550ustar00rootroot00000000000000Description: 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" i2pd-2.56.0/contrib/debian/xenial/patches/series000066400000000000000000000000371475272067700214710ustar00rootroot0000000000000001-upnp.patch 02-service.patch i2pd-2.56.0/contrib/debian/xenial/rules000077500000000000000000000004111475272067700177010ustar00rootroot00000000000000#!/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: i2pd-2.56.0/contrib/dinit/000077500000000000000000000000001475272067700152325ustar00rootroot00000000000000i2pd-2.56.0/contrib/dinit/i2pd000066400000000000000000000004411475272067700160120ustar00rootroot00000000000000type = 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 i2pd-2.56.0/contrib/docker/000077500000000000000000000000001475272067700153725ustar00rootroot00000000000000i2pd-2.56.0/contrib/docker/Dockerfile000066400000000000000000000055131475272067700173700ustar00rootroot00000000000000# # 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 # Expose git branch, tag and URL variables as arguments ARG GIT_BRANCH="openssl" ENV GIT_BRANCH=${GIT_BRANCH} ARG GIT_TAG="" ENV GIT_TAG=${GIT_TAG} ARG REPO_URL="https://github.com/PurpleI2P/i2pd.git" 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. RUN apk update \ && apk --no-cache --virtual build-dependendencies add make gcc g++ libtool zlib-dev boost-dev build-base openssl-dev openssl miniupnpc-dev git \ && mkdir -p /tmp/build \ && cd /tmp/build && git clone -b ${GIT_BRANCH} ${REPO_URL} \ && cd i2pd \ && if [ -n "${GIT_TAG}" ]; then git checkout tags/${GIT_TAG}; fi \ && make -j$(nproc) USE_UPNP=yes \ && 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 # 2. Adding required libraries to run i2pd to ensure it will run. RUN apk --no-cache add boost-filesystem boost-system boost-program_options boost-date_time boost-thread boost-iostreams openssl miniupnpc musl-utils libstdc++ # 3. Copy preconfigured config file and entrypoint COPY i2pd-docker.conf "$DATA_DIR/i2pd.conf" RUN chown i2pd:nobody "$DATA_DIR/i2pd.conf" COPY entrypoint.sh /entrypoint.sh RUN chmod a+x /entrypoint.sh RUN echo "export DATA_DIR=${DATA_DIR}" >> /etc/profile VOLUME "$DATA_DIR" EXPOSE 7070 4444 4447 7656 2827 7654 7650 USER i2pd ENTRYPOINT [ "/entrypoint.sh" ] i2pd-2.56.0/contrib/docker/entrypoint.sh000066400000000000000000000004251475272067700201420ustar00rootroot00000000000000#!/bin/sh COMMAND=/usr/local/bin/i2pd # To make ports exposeable # Note: $DATA_DIR is defined in /etc/profile if [ "$1" = "--help" ]; then set -- $COMMAND --help else ln -s /i2pd_certificates "$DATA_DIR"/certificates set -- $COMMAND $DEFAULT_ARGS $@ fi exec "$@" i2pd-2.56.0/contrib/docker/i2pd-docker.conf000066400000000000000000000014041475272067700203430ustar00rootroot00000000000000## 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 i2pd-2.56.0/contrib/i18n/000077500000000000000000000000001475272067700147025ustar00rootroot00000000000000i2pd-2.56.0/contrib/i18n/English.po000066400000000000000000000365531475272067700166470ustar00rootroot00000000000000# 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 "" i2pd-2.56.0/contrib/i18n/README.md000066400000000000000000000011761475272067700161660ustar00rootroot00000000000000`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 ``` i2pd-2.56.0/contrib/i2pd.conf000066400000000000000000000232321475272067700156320ustar00rootroot00000000000000## Configuration file for a typical i2pd user ## See https://i2pd.readthedocs.io/en/latest/user-guide/configuration/ ## for more options you can use in this file. ## Lines that begin with "## " try to explain what's going on. Lines ## that begin with just "#" are disabled commands: you can enable them ## by removing the "#" symbol. ## Tunnels config file ## 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 ## 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) ## If you set it to none, logging will be disabled # loglevel = warn ## Write full CLF-formatted date and time to log (default: write only time) # logclftime = true ## Daemon mode. Router will go to background after start. Ignored on Windows ## (default: true) # daemon = true ## Specify a family, router belongs to (default - none) # 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 ## 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 ## By default i2pd picks random port. You MUST pick a random number too, ## don't just uncomment this # port = 4567 ## Enable communication through ipv4 (default: true) ipv4 = true ## Enable communication through ipv6 (default: false) ipv6 = false ## Bandwidth configuration ## L limit bandwidth to 32 KB/sec, O - to 256 KB/sec, P - to 2048 KB/sec, ## X - unlimited ## Default is L (regular node) and X if floodfill mode enabled. ## If you want to share more bandwidth without floodfill mode, uncomment ## that line and adjust value to your possibilities. Value can be set to ## integer in kilobytes, it will apply that limit and flag will be used ## from next upper limit (example: if you set 4096 flag will be X, but real ## limit will be 4096 KB/s). Same can be done when floodfill mode is used, ## but keep in mind that low values may be negatively evaluated by Java ## router algorithms. # bandwidth = L ## Max % of bandwidth limit for transit. 0-100 (default: 100) # share = 100 ## Router will not accept transit tunnels, disabling transit traffic completely ## (default: false) # notransit = true ## Router will be floodfill (default: false) ## Note: that mode uses much more network connections and CPU! # floodfill = true [ntcp2] ## Enable NTCP2 transport (default: true) # enabled = true ## Publish address in RouterInfo (default: true) # published = true ## Port for incoming connections (default is global port option value) # port = 4567 [ssu2] ## Enable SSU2 transport (default: true) # enabled = true ## Publish address in RouterInfo (default: true) # published = true ## Port for incoming connections (default is global port option value) # port = 4567 [http] ## Web Console settings ## Enable the Web Console (default: true) # 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 [precomputation] ## Enable or disable elgamal precomputation table ## By default, enabled on i386 hosts # elgamal = true [upnp] ## Enable or disable UPnP: automatic port forwarding (enabled by default in WINDOWS, ANDROID) # enabled = false ## Name i2pd appears in UPnP forwardings list (default: I2Pd) # name = I2Pd [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) 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 ## Optional subscriptions URLs, separated by comma # subscriptions = http://reg.i2p/hosts.txt,http://identiguy.i2p/hosts.txt,http://stats.i2p/cgi-bin/newhosts.txt [limits] ## Maximum active transit sessions (default: 5000) ## 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) # 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 [exploratory] ## Exploratory tunnels settings with default values # inbound.length = 2 # inbound.quantity = 3 # outbound.length = 2 # outbound.quantity = 3 [persist] ## Save peer profiles on disk (default: true) # profiles = true ## Save full addresses on disk (default: true) # addressbook = true i2pd-2.56.0/contrib/i2pd.logrotate000066400000000000000000000001761475272067700167070ustar00rootroot00000000000000"/var/log/i2pd/*.log" { copytruncate daily rotate 5 compress delaycompress missingok notifempty } i2pd-2.56.0/contrib/i2pd.service000066400000000000000000000022371475272067700163470ustar00rootroot00000000000000[Unit] Description=I2P Router written in C++ Documentation=man:i2pd(1) https://i2pd.readthedocs.io/en/latest/ After=network.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 i2pd-2.56.0/contrib/openrc/000077500000000000000000000000001475272067700154115ustar00rootroot00000000000000i2pd-2.56.0/contrib/openrc/i2pd.openrc000066400000000000000000000016031475272067700174570ustar00rootroot00000000000000#!/sbin/openrc-run 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" description="i2p router written in C++" required_dirs="/var/lib/i2pd" required_files="$mainconf" start_stop_daemon_args="--chuid i2pd" depend() { need mountall use net after bootmisc } start_pre() { if [ -r /etc/default/i2pd ]; then . /etc/default/i2pd fi if [ "x$I2PD_ENABLED" != "xyes" ]; then ewarn "i2pd disabled in /etc/default/i2pd" exit 1 fi checkpath -f -o i2pd:adm $logfile checkpath -f -o i2pd:adm $pidfile if [ -n "$DAEMON_OPTS" ]; then command_args="$command_args $DAEMON_OPTS" fi } i2pd-2.56.0/contrib/rpm/000077500000000000000000000000001475272067700147215ustar00rootroot00000000000000i2pd-2.56.0/contrib/rpm/i2pd-git.spec000066400000000000000000000166121475272067700172220ustar00rootroot00000000000000%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 i2pd-2.56.0/contrib/rpm/i2pd.spec000066400000000000000000000226151475272067700164410ustar00rootroot00000000000000Name: i2pd Version: 2.56.0 Release: 1%{?dist} Summary: I2P router written in C++ Conflicts: i2pd-git License: BSD URL: https://github.com/PurpleI2P/i2pd Source0: https://github.com/PurpleI2P/i2pd/archive/%{version}/%name-%version.tar.gz %if 0%{?rhel} == 7 BuildRequires: cmake3 %else BuildRequires: cmake %endif BuildRequires: chrpath BuildRequires: gcc-c++ 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 %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}/%{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 %{__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 %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 * 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 - update to 2.18.0 * Sat Jan 27 2018 l-n-s - 2.17.0-1 - Added certificates and default configuration files - Merge i2pd with i2pd-systemd package - Fixed package changelogs to comply with guidelines * Mon Dec 04 2017 orignal - 2.17.0 - update to 2.17.0 * Mon Nov 13 2017 orignal - 2.16.0 - update to 2.16.0 * Thu Aug 17 2017 orignal - 2.15.0 - update to 2.15.0 * Thu Jun 01 2017 orignal - 2.14.0 - update to 2.14.0 * Thu Apr 06 2017 orignal - 2.13.0 - update to 2.13.0 * Tue Feb 14 2017 orignal - 2.12.0 - update to 2.12.0 * Mon Dec 19 2016 orignal - 2.11.0 - update to 2.11.0 * Thu Oct 20 2016 Anatolii Vorona - 2.10.0-3 - add support C7 - move rpm-related files to contrib folder * Sun Oct 16 2016 Oleg Girko - 2.10.0-1 - update to 2.10.0 * Sun Aug 14 2016 Oleg Girko - 2.9.0-1 - update to 2.9.0 * Sun Aug 07 2016 Oleg Girko - 2.8.0-2 - rename daemon subpackage to systemd * Sat Aug 06 2016 Oleg Girko - 2.8.0-1 - update to 2.8.0 - remove wrong rpath from i2pd binary - add daemon subpackage with systemd unit file * Sat May 21 2016 Oleg Girko - 2.7.0-1 - update to 2.7.0 * Tue Apr 05 2016 Oleg Girko - 2.6.0-1 - update to 2.6.0 * Tue Jan 26 2016 Yaroslav Sidlovsky - 2.3.0-1 - initial package for version 2.3.0 i2pd-2.56.0/contrib/subscriptions.txt000066400000000000000000000001771475272067700176000ustar00rootroot00000000000000http://reg.i2p/hosts.txt http://identiguy.i2p/hosts.txt http://stats.i2p/cgi-bin/newhosts.txt http://i2p-projekt.i2p/hosts.txt i2pd-2.56.0/contrib/tunnels.conf000066400000000000000000000012021475272067700164550ustar00rootroot00000000000000[IRC-ILITA] type = client address = 127.0.0.1 port = 6668 destination = irc.ilita.i2p destinationport = 6667 keys = irc-keys.dat i2p.streaming.profile=2 #[IRC-IRC2P] #type = client #address = 127.0.0.1 #port = 6669 #destination = irc.postman.i2p #destinationport = 6667 #keys = irc-keys.dat #[SMTP] #type = client #address = 127.0.0.1 #port = 7659 #destination = smtp.postman.i2p #destinationport = 25 #keys = smtp-keys.dat #[POP3] #type = client #address = 127.0.0.1 #port = 7660 #destination = pop.postman.i2p #destinationport = 110 #keys = pop3-keys.dat # see more examples at https://i2pd.readthedocs.io/en/latest/user-guide/tunnels/ i2pd-2.56.0/contrib/tunnels.d/000077500000000000000000000000001475272067700160355ustar00rootroot00000000000000i2pd-2.56.0/contrib/tunnels.d/IRC-Ilita.conf000066400000000000000000000002101475272067700203520ustar00rootroot00000000000000#[IRC-ILITA] #type = client #address = 127.0.0.1 #port = 6669 #destination = irc.ilita.i2p #destinationport = 6667 #keys = irc-keys.dat i2pd-2.56.0/contrib/tunnels.d/IRC-Irc2P.conf000066400000000000000000000002121475272067700202310ustar00rootroot00000000000000#[IRC-IRC2P] #type = client #address = 127.0.0.1 #port = 6668 #destination = irc.postman.i2p #destinationport = 6667 #keys = irc-keys.dat i2pd-2.56.0/contrib/tunnels.d/README000066400000000000000000000002731475272067700167170ustar00rootroot00000000000000# 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 i2pd-2.56.0/contrib/upstart/000077500000000000000000000000001475272067700156255ustar00rootroot00000000000000i2pd-2.56.0/contrib/upstart/i2pd.upstart000066400000000000000000000004271475272067700201120ustar00rootroot00000000000000description "i2p client daemon" start on runlevel [2345] stop on runlevel [016] or unmounting-filesystem # these can be overridden in /etc/init/i2pd.override env LOGFILE="/var/log/i2pd/i2pd.log" expect fork exec /usr/bin/i2pd --daemon --service --log=file --logfile=$LOGFILE i2pd-2.56.0/contrib/webconsole/000077500000000000000000000000001475272067700162635ustar00rootroot00000000000000i2pd-2.56.0/contrib/webconsole/style.css000066400000000000000000000113371475272067700201420ustar00rootroot00000000000000/* * 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; } } i2pd-2.56.0/daemon/000077500000000000000000000000001475272067700137265ustar00rootroot00000000000000i2pd-2.56.0/daemon/Daemon.cpp000066400000000000000000000301741475272067700156420ustar00rootroot00000000000000/* * 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 "Daemon.h" #include "Config.h" #include "Log.h" #include "FS.h" #include "Base.h" #include "version.h" #include "Transports.h" #include "RouterInfo.h" #include "RouterContext.h" #include "Tunnel.h" #include "HTTP.h" #include "NetDb.hpp" #include "Garlic.h" #include "Streaming.h" #include "Destination.h" #include "HTTPServer.h" #include "I2PControl.h" #include "ClientContext.h" #include "Crypto.h" #include "UPnP.h" #include "Timestamp.h" #include "I18N.h" namespace i2p { namespace util { class Daemon_Singleton::Daemon_Singleton_Private { 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 == "") { config = i2p::fs::DataDirPath("i2pd.conf"); if (!i2p::fs::Exists (config)) { // use i2pd.conf only if exists config = ""; /* reset */ } } 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); #ifndef _WIN32 } else if (logs == "syslog") { LogPrint(eLogInfo, "Log: Sending messages to syslog"); i2p::log::Logger().SendTo("i2pd", LOG_DAEMON); #endif } else { // use stdout -- default } 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) { LogPrint(eLogInfo, "Daemon: Router configured as floodfill"); i2p::context.SetFloodfill (true); } else i2p::context.SetFloodfill (false); bool transit; i2p::config::GetOption("notransit", transit); i2p::context.SetAcceptsTunnels (!transit); uint32_t transitTunnels; i2p::config::GetOption("limits.transittunnels", transitTunnels); if (isFloodfill && i2p::config::IsDefault ("limits.transittunnels")) transitTunnels *= 2; // double default number of transit tunnels for floodfill i2p::tunnel::tunnels.SetMaxNumTransitTunnels (transitTunnels); /* this section also honors 'floodfill' flag, if set above */ std::string bandwidth; i2p::config::GetOption("bandwidth", bandwidth); if (bandwidth.length () > 0) { if (bandwidth.length () == 1 && ((bandwidth[0] >= 'K' && bandwidth[0] <= 'P') || bandwidth[0] == 'X' )) { i2p::context.SetBandwidth (bandwidth[0]); LogPrint(eLogInfo, "Daemon: Bandwidth set to ", i2p::context.GetBandwidthLimit (), "KBps"); } else { 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) { 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 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 { 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 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 { 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 ()); } } 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) { d.UPnP->Stop (); d.UPnP = nullptr; } if (d.m_NTPSync) { d.m_NTPSync->Stop (); d.m_NTPSync = 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(); return true; } } } i2pd-2.56.0/daemon/Daemon.h000066400000000000000000000044421475272067700153060ustar00rootroot00000000000000/* * 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 { namespace util { class Daemon_Singleton_Private; class Daemon_Singleton { public: 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 () {}; virtual void setDataDir (std::string path); bool isDaemon; bool running; 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; }; #if defined(QT_GUI_LIB) // check if QT #define Daemon i2p::util::DaemonQT::Instance() // dummy, invoked from RunQT class DaemonQT: public i2p::util::Daemon_Singleton { public: static DaemonQT& Instance() { static DaemonQT instance; return instance; } }; #elif defined(_WIN32) #define Daemon i2p::util::DaemonWin32::Instance() class DaemonWin32 : public Daemon_Singleton { public: static DaemonWin32& Instance() { static DaemonWin32 instance; return instance; } bool init(int argc, char* argv[]); bool start(); bool stop(); void run (); bool isGraceful; 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; return instance; } bool start(); bool stop(); void run (); private: std::string pidfile; int pidFH; public: int gracefulShutdownInterval; // in seconds }; #endif } } #endif // DAEMON_H__ i2pd-2.56.0/daemon/HTTPServer.cpp000066400000000000000000001734711475272067700164150ustar00rootroot00000000000000/* * 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 "Base.h" #include "FS.h" #include "Log.h" #include "Config.h" #include "Tunnel.h" #include "Transports.h" #include "NetDb.hpp" #include "HTTP.h" #include "LeaseSet.h" #include "Destination.h" #include "RouterContext.h" #include "ClientContext.h" #include "HTTPServer.h" #include "Daemon.h" #include "util.h" #include "ECIESX25519AEADRatchetSession.h" #include "I18N.h" #ifdef WIN32_APP #include "Win32App.h" #endif // For image, style 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 = ""; } } static void GetStyles (std::stringstream& s) { if (externalCSS.length() != 0) s << "\r\n"; else s << internalCSS; } const char HTTP_PAGE_TUNNELS[] = "tunnels"; const char HTTP_PAGE_TRANSIT_TUNNELS[] = "transit_tunnels"; const char HTTP_PAGE_TRANSPORTS[] = "transports"; const char HTTP_PAGE_LOCAL_DESTINATIONS[] = "local_destinations"; const char HTTP_PAGE_LOCAL_DESTINATION[] = "local_destination"; const char HTTP_PAGE_I2CP_LOCAL_DESTINATION[] = "i2cp_local_destination"; const char HTTP_PAGE_SAM_SESSIONS[] = "sam_sessions"; const char HTTP_PAGE_SAM_SESSION[] = "sam_session"; const char HTTP_PAGE_I2P_TUNNELS[] = "i2p_tunnels"; const char HTTP_PAGE_COMMANDS[] = "commands"; const char HTTP_PAGE_LEASESETS[] = "leasesets"; const char HTTP_COMMAND_ENABLE_TRANSIT[] = "enable_transit"; const char HTTP_COMMAND_DISABLE_TRANSIT[] = "disable_transit"; const char HTTP_COMMAND_SHUTDOWN_START[] = "shutdown_start"; 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_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; } static void ShowUptime (std::stringstream& s, int seconds) { int num; if ((num = seconds / 86400) > 0) { s << ntr("%d day", "%d days", num, num) << ", "; seconds -= num * 86400; } if ((num = seconds / 3600) > 0) { s << ntr("%d hour", "%d hours", num, num) << ", "; seconds -= num * 3600; } if ((num = seconds / 60) > 0) { s << ntr("%d minute", "%d minutes", num, num) << ", "; seconds -= num * 60; } s << ntr("%d second", "%d seconds", seconds, seconds); } static void ShowTraffic (std::stringstream& s, uint64_t bytes) { s << std::fixed << std::setprecision(2); auto numKBytes = (double) bytes / 1024; if (numKBytes < 1024) s << tr(/* tr: Kibibyte */ "%.2f KiB", numKBytes); else if (numKBytes < 1024 * 1024) s << tr(/* tr: Mebibyte */ "%.2f MiB", numKBytes / 1024); else s << tr(/* tr: Gibibyte */ "%.2f GiB", numKBytes / 1024 / 1024); } static void ShowTunnelDetails (std::stringstream& s, enum i2p::tunnel::TunnelState eState, bool explr, int bytes) { std::string state, stateText; switch (eState) { 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::eTunnelStateEstablished : state = "established"; break; default: state = "unknown"; break; } if (stateText.empty ()) stateText = tr(state); s << " " << stateText << ((explr) ? " (" + tr("exploratory") + ")" : "") << ", "; ShowTraffic(s, bytes); s << "\r\n"; } static void SetLogLevel (const std::string& level) { if (level == "none" || level == "critical" || level == "error" || level == "warn" || level == "info" || level == "debug") i2p::log::Logger().SetLogLevel(level); else { LogPrint(eLogError, "HTTPServer: Unknown loglevel set attempted"); return; } i2p::log::Logger().Reopen (); } 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: Find something to parse html/template system. This is horrible. */ " \r\n" " \r\n" " \r\n" " " << tr(/* tr: Webconsole page title */ "Purple I2P Webconsole") << "\r\n"; GetStyles(s); 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"; if (i2p::client::context.GetSAMBridge ()) s << " " << tr("SAM sessions") << "
\r\n"; s << "
\r\n" "
"; } static void ShowPageTail (std::stringstream& s) { s << "
\r\n
\r\n" "\r\n" "\r\n"; } static void ShowError(std::stringstream& s, const std::string& string) { s << "" << tr("ERROR") << ": " << string << "
\r\n"; } static void ShowNetworkStatus (std::stringstream& s, RouterStatus status, bool testing, RouterError error) { 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") << ": "; 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 << "" << tr("Network status v6") << ": "; ShowNetworkStatus (s, i2p::context.GetStatusV6 (), i2p::context.GetTestingV6(), i2p::context.GetErrorV6 ()); s << "
\r\n"; } #if ((!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) || defined(ANDROID_BINARY)) 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 << "
\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") << ": "; 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") << ": "; 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") << ": "; 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) { for (const auto& address : *addresses) { if (!address) continue; s << "\r\n\r\n"; if (address->published) s << "\r\n"; else { s << "\r\n"; } s << "\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"; 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"; 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"; } } void ShowLocalDestinations (std::stringstream& s) { std::string webroot; i2p::config::GetOption("http.webroot", webroot); s << "" << tr("Local Destinations") << ":
\r\n
\r\n"; for (auto& it: i2p::client::context.GetDestinations ()) { auto ident = it.second->GetIdentHash (); s << "\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"; for (auto& it: i2cpServer->GetSessions ()) { auto dest = it.second->GetDestination (); if (dest) { auto ident = dest->GetIdentHash (); auto& name = dest->GetNickname (); 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) { auto identHash = ident.GetIdentHash(); auto router = i2p::data::netdb.FindRouter(identHash); s << i2p::data::GetIdentHashAbbreviation(identHash); if (router) s << " " << router->GetBandwidthCap() << ""; } static void ShowLeaseSetDestination (std::stringstream& s, std::shared_ptr dest, uint32_t token) { s << "Base32:
\r\n
\r\n
\r\n"; s << "Base64:
\r\n
\r\n
\r\n"; if (dest->IsEncryptedLeaseSet ()) { i2p::data::BlindedPublicKey blinded (dest->GetIdentity (), dest->IsPerClientAuth ()); 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 << "" << "" << ""; 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"; } else s << "" << tr("LeaseSets") << ": 0
\r\n
\r\n"; auto pool = dest->GetTunnelPool (); if (pool) { s << "" << tr("Inbound tunnels") << ":
\r\n
\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()) << " )"; ShowTunnelDetails(s, it->GetState (), false, it->GetNumReceivedBytes ()); s << "
\r\n"; } s << "
\r\n"; s << "" << tr("Outbound tunnels") << ":
\r\n
\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()) << " )"; ShowTunnelDetails(s, it->GetState (), false, it->GetNumSentBytes ()); s << "
\r\n"; } } s << "
\r\n"; s << "" << tr("Tags") << "
\r\n" << tr("Incoming") << ": " << dest->GetNumIncomingTags () << "
\r\n"; if (!dest->GetSessions ().empty ()) { std::stringstream tmp_s; uint32_t out_tags = 0; for (const auto& it: dest->GetSessions ()) { tmp_s << "" << i2p::client::context.GetAddressBook ().ToAddress(it.first) << "" << it.second->GetNumOutgoingTags () << "\r\n"; out_tags += it.second->GetNumOutgoingTags (); } s << "
\r\n" << "\r\n" << "
\r\n" << "\r\n\r\n" << "\r\n" << tmp_s.str () << "
" << tr("Destination") << "" << tr("Amount") << "
\r\n
\r\n
\r\n"; } else s << tr("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) { s << "" << tr("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"; 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 << ""; s << ""; s << ""; s << ""; s << "\r\n"; } s << "\r\n
" << tr("Streams") << "
StreamID DestinationSentReceivedOutInBufRTTWindowStatus
" << it->GetRecvStreamID () << ""; } s << "" << streamDestShort << "" << it->GetNumSentBytes () << "" << it->GetNumReceivedBytes () << "" << it->GetSendQueueSize () << "" << it->GetReceiveQueueSize () << "" << it->GetSendBufferSize () << "" << it->GetRTT () << "" << it->GetWindowSize () << "" << (int)it->GetStatus () << "
"; } else ShowError(s, tr("Such destination is not found")); } 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"; auto it = i2cpServer->GetSessions ().find (std::stoi (id)); if (it != i2cpServer->GetSessions ().end ()) ShowLeaseSetDestination (s, it->second->GetDestination (), 0); else ShowError(s, tr("I2CP session not found")); } else ShowError(s, tr("I2CP is not enabled")); } void ShowLeasesSets(std::stringstream& s) { if (i2p::data::netdb.GetNumLeaseSets ()) { s << "" << tr("LeaseSets") << ":
\r\n
\r\n"; int counter = 1; // for each lease set i2p::data::netdb.VisitLeaseSets( [&s, &counter](const i2p::data::IdentHash dest, std::shared_ptr leaseSet) { // create copy of lease set so we extract leases auto storeType = leaseSet->GetStoreType (); std::unique_ptr ls; if (storeType == i2p::data::NETDB_STORE_TYPE_LEASESET) ls.reset (new i2p::data::LeaseSet (leaseSet->GetBuffer(), leaseSet->GetBufferLen())); else { 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"; } ); // 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"; } } 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"; auto ExplPool = i2p::tunnel::tunnels.GetExploratoryPool (); s << "" << tr("Inbound tunnels") << ":
\r\n
\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()) << " )"; 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"; 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()) << " )"; ShowTunnelDetails(s, it->GetState (), (it->GetTunnelPool () == ExplPool), it->GetNumSentBytes ()); 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"; if (i2p::context.AcceptsTunnels ()) s << " " << tr("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)) if (Daemon.gracefulShutdownInterval) s << " " << tr("Cancel graceful shutdown") << "
\r\n"; else s << " " << tr("Start graceful shutdown") << "
\r\n"; #elif defined(WIN32_APP) if (i2p::util::DaemonWin32::Instance().isGraceful) s << " " << tr("Cancel graceful shutdown") << "
\r\n"; else s << " " << tr("Start graceful shutdown") << "
\r\n"; #endif 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"; } void ShowTransitTunnels (std::stringstream& s) { if (i2p::tunnel::tunnels.CountTransitTunnels()) { 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"; } } void ShowTransports (std::stringstream& s) { s << "" << tr("Transports") << ":
\r\n"; auto ntcp2Server = i2p::transport::transports.GetNTCP2Server (); if (ntcp2Server) { auto sessions = ntcp2Server->GetNTCP2Sessions (); if (!sessions.empty ()) ShowTransportSessions (s, sessions, "NTCP2"); } auto ssu2Server = i2p::transport::transports.GetSSU2Server (); if (ssu2Server) { auto sessions = ssu2Server->GetSSU2Sessions (); if (!sessions.empty ()) ShowTransportSessions (s, sessions, "SSU2"); } } 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")); return; } auto session = sam->FindSession (id); if (!session) { ShowError(s, tr("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"; s << "
\r\n"; s << "" << tr("Streams") << ":
\r\n
\r\n"; for (const auto& it: sam->ListSockets(id)) { s << "
"; switch (it->GetSocketType ()) { case i2p::client::eSAMSocketTypeSession : s << "session"; break; case i2p::client::eSAMSocketTypeStream : s << "stream"; break; case i2p::client::eSAMSocketTypeAcceptor : s << "acceptor"; break; case i2p::client::eSAMSocketTypeForward : s << "forward"; break; default: s << "unknown"; break; } s << " [" << it->GetSocket ().remote_endpoint() << "]"; s << "
\r\n"; } s << "
\r\n"; } void ShowI2PTunnels (std::stringstream& s) { std::string webroot; i2p::config::GetOption("http.webroot", webroot); auto& clientTunnels = i2p::client::context.GetClientTunnels (); auto httpProxy = i2p::client::context.GetHttpProxy (); auto socksProxy = i2p::client::context.GetSocksProxy (); if (!clientTunnels.empty () || httpProxy || socksProxy) { s << "" << tr("Client Tunnels") << ":
\r\n
\r\n"; 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& serverTunnels = i2p::client::context.GetServerTunnels (); if (!serverTunnels.empty ()) { s << "
\r\n" << tr("Server Tunnels") << ":
\r\n
\r\n"; for (auto& it: serverTunnels) { auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); s << "
"; s << it.second->GetName () << " ⇒ "; s << i2p::client::context.GetAddressBook ().ToAddress(ident); s << ":" << it.second->GetLocalPort (); 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"; for (auto& it: clientForwards) { auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); s << "
"; s << it.second->GetName () << " ⇐ "; s << i2p::client::context.GetAddressBook ().ToAddress(ident); 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"; for (auto& it: serverForwards) { auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); s << "
"; s << it.second->GetName () << " ⇐ "; s << i2p::client::context.GetAddressBook ().ToAddress(ident); 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) { /* cache options */ i2p::config::GetOption("http.auth", needAuth); i2p::config::GetOption("http.user", user); i2p::config::GetOption("http.pass", pass); } 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)); } void HTTPConnection::HandleReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (ecode) { if (ecode != boost::asio::error::operation_aborted) Terminate (ecode); return; } m_Buffer[bytes_transferred] = '\0'; m_BufferLen = bytes_transferred; RunRequest(); Receive (); } void HTTPConnection::RunRequest () { HTTPReq request; int ret = request.parse(m_Buffer); if (ret < 0) { m_Buffer[0] = '\0'; m_BufferLen = 0; return; /* error */ } if (ret == 0) return; /* need more data */ HandleRequest (request); } void HTTPConnection::Terminate (const boost::system::error_code& ecode) { if (ecode == boost::asio::error::operation_aborted) return; boost::system::error_code ignored_ec; m_Socket->shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignored_ec); m_Socket->close (); } 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; if (url.parse(req.uri) && url.user == user && url.pass == pass) return true; } /* method #2: 'Authorization' header sent */ auto provided = req.GetHeader ("Authorization"); if (provided.length () > 0) { std::string expected = "Basic " + i2p::data::ToBase64Standard (user + ":" + pass); if (expected == provided) return true; } LogPrint(eLogWarning, "HTTPServer: Auth failure from ", m_Socket->remote_endpoint().address ()); return false; } void HTTPConnection::HandleRequest (const HTTPReq & req) { std::stringstream s; std::string content; HTTPRes res; LogPrint(eLogDebug, "HTTPServer: Request: ", req.uri); if (needAuth && !CheckAuth(req)) { res.code = 401; res.add_header("WWW-Authenticate", "Basic realm=\"WebAdmin\""); SendReply(res, content); 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 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); res.add_header("Refresh", "10"); } ShowPageTail (s); res.code = 200; content = s.str (); SendReply (res, content); } 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; std::string page(""); URL url; url.parse(req.uri); url.parse_query(params); page = params["page"]; if (page == HTTP_PAGE_TRANSPORTS) ShowTransports (s); else if (page == HTTP_PAGE_TUNNELS) ShowTunnels (s); else if (page == HTTP_PAGE_COMMANDS) { uint32_t token = CreateToken (); ShowCommands (s, token); } else if (page == HTTP_PAGE_TRANSIT_TUNNELS) ShowTransitTunnels (s); 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); } else if (page == HTTP_PAGE_I2CP_LOCAL_DESTINATION) ShowI2CPLocalDestination (s, params["i2cp_id"]); else if (page == HTTP_PAGE_SAM_SESSIONS) ShowSAMSessions (s); else if (page == HTTP_PAGE_SAM_SESSION) ShowSAMSession (s, params["sam_id"]); else if (page == HTTP_PAGE_I2P_TUNNELS) ShowI2PTunnels (s); else if (page == HTTP_PAGE_LEASESETS) ShowLeasesSets(s); else { res.code = 400; ShowError(s, tr("Unknown page") + ": " + page); return; } } void HTTPConnection::HandleCommand (const HTTPReq& req, HTTPRes& res, std::stringstream& s) { std::map params; URL url; 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")); 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) 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) { i2p::context.SetAcceptsTunnels (false); #if ((!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) || defined(ANDROID_BINARY)) Daemon.gracefulShutdownInterval = 10*60; #elif defined(WIN32_APP) i2p::win32::GracefulShutdown (); #endif } else if (cmd == HTTP_COMMAND_SHUTDOWN_CANCEL) { i2p::context.SetAcceptsTunnels (true); #if ((!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) || defined(ANDROID_BINARY)) Daemon.gracefulShutdownInterval = 0; #elif defined(WIN32_APP) i2p::win32::StopGracefulShutdown (); #endif } else if (cmd == HTTP_COMMAND_SHUTDOWN_NOW) { #ifndef WIN32_APP Daemon.running = false; #else i2p::win32::StopWin32App (); #endif } 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]; char * sig = new char[signatureLen*2]; std::stringstream out; out << name << "=" << dest->GetIdentity ()->ToBase64 (); dest->Sign ((uint8_t *)out.str ().c_str (), out.str ().length (), signature); auto len = i2p::data::ByteStreamToBase64 (signature, signatureLen, sig, signatureLen*2); sig[len] = 0; 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; delete[] sig; } 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 { res.code = 400; ShowError(s, tr("Unknown command") + ": " + cmd); return; } s << "" << tr("SUCCESS") << ": " << tr("Command accepted") << "

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

" << tr("You will be redirected in %d seconds", COMMAND_REDIRECT_TIMEOUT) << ""; res.add_header("Refresh", redirect.c_str()); } void HTTPConnection::SendReply (HTTPRes& reply, std::string& content) { reply.add_header("X-Frame-Options", "SAMEORIGIN"); reply.add_header("X-Content-Type-Options", "nosniff"); reply.add_header("X-XSS-Protection", "1; mode=block"); reply.add_header("Content-Type", "text/html"); reply.body = content; m_SendBuffer = reply.to_string(); boost::asio::async_write (*m_Socket, boost::asio::buffer(m_SendBuffer), boost::asio::transfer_all (), 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) { } HTTPServer::~HTTPServer () { Stop (); } void HTTPServer::Start () { bool needAuth; i2p::config::GetOption("http.auth", needAuth); std::string user; i2p::config::GetOption("http.user", user); std::string pass; i2p::config::GetOption("http.pass", pass); /* generate pass if needed */ if (needAuth && pass == "") { uint8_t random[16]; char alnum[] = "0123456789" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz"; pass.resize(sizeof(random)); RAND_bytes(random, sizeof(random)); for (size_t i = 0; i < sizeof(random); i++) { pass[i] = alnum[random[i] % (sizeof(alnum) - 1)]; } i2p::config::SetOption("http.pass", pass); LogPrint(eLogInfo, "HTTPServer: Password set to ", pass); } m_IsRunning = true; m_Thread.reset (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) { m_Thread->join (); m_Thread = nullptr; } } void HTTPServer::Run () { i2p::util::SetThreadName("Webconsole"); while (m_IsRunning) { try { m_Service.run (); } catch (std::exception& ex) { LogPrint (eLogError, "HTTPServer: Runtime exception: ", ex.what ()); } } } void HTTPServer::Accept () { auto newSocket = std::make_shared (m_Service); m_Acceptor.async_accept (*newSocket, std::bind (&HTTPServer::HandleAccept, this, std::placeholders::_1, newSocket)); } void HTTPServer::HandleAccept(const boost::system::error_code& ecode, std::shared_ptr newSocket) { if (!ecode) CreateConnection(newSocket); else { if (newSocket) newSocket->close(); LogPrint(eLogError, "HTTP Server: Error handling accept: ", ecode.message()); } Accept (); } void HTTPServer::CreateConnection(std::shared_ptr newSocket) { auto conn = std::make_shared (m_Hostname, newSocket); conn->Receive (); } } // http } // i2p i2pd-2.56.0/daemon/HTTPServer.h000066400000000000000000000062731475272067700160550ustar00rootroot00000000000000/* * 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__ #include #include #include #include #include #include #include #include "HTTP.h" namespace i2p { 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); void Receive (); private: void HandleReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred); void Terminate (const boost::system::error_code& ecode); void RunRequest (); bool CheckAuth (const HTTPReq & req); void HandleRequest (const HTTPReq & req); 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; 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 }; class HTTPServer { public: HTTPServer (const std::string& address, int port); ~HTTPServer (); void Start (); void Stop (); private: void Run (); void Accept (); void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr newSocket); void CreateConnection(std::shared_ptr newSocket); private: bool m_IsRunning; std::unique_ptr m_Thread; boost::asio::io_context m_Service; boost::asio::executor_work_guard 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); } // http } // i2p #endif /* HTTP_SERVER_H__ */ i2pd-2.56.0/daemon/HTTPServerResources.h000066400000000000000000000140271475272067700177440ustar00rootroot00000000000000/* * 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__ */ i2pd-2.56.0/daemon/I2PControl.cpp000066400000000000000000000354251475272067700163760ustar00rootroot00000000000000/* * 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 // Use global placeholders from boost introduced when local_time.hpp is loaded #define BOOST_BIND_GLOBAL_PLACEHOLDERS #include #include #include "FS.h" #include "Log.h" #include "Config.h" #include "NetDb.hpp" #include "Tunnel.h" #include "Daemon.h" #include "I2PControl.h" namespace i2p { namespace client { I2PControlService::I2PControlService (const std::string& address, int port): m_IsRunning (false), m_Thread (nullptr), m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::make_address(address), port)), m_SSLContext (boost::asio::ssl::context::sslv23), m_ShutdownTimer (m_Service) { i2p::config::GetOption("i2pcontrol.password", m_Password); // certificate / keys std::string i2pcp_crt; i2p::config::GetOption("i2pcontrol.cert", i2pcp_crt); std::string i2pcp_key; i2p::config::GetOption("i2pcontrol.key", i2pcp_key); if (i2pcp_crt.at(0) != '/') 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"); CreateCertificate (i2pcp_crt.c_str(), i2pcp_key.c_str()); } 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"); } // 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; // I2PControl m_I2PControlHandlers["i2pcontrol.password"] = &I2PControlService::PasswordHandler; // RouterManager m_RouterManagerHandlers["Reseed"] = &I2PControlService::ReseedHandler; m_RouterManagerHandlers["Shutdown"] = &I2PControlService::ShutdownHandler; m_RouterManagerHandlers["ShutdownGraceful"] = &I2PControlService::ShutdownGracefulHandler; } I2PControlService::~I2PControlService () { Stop (); } void I2PControlService::Start () { if (!m_IsRunning) { Accept (); m_IsRunning = true; m_Thread = new std::thread (std::bind (&I2PControlService::Run, this)); } } void I2PControlService::Stop () { if (m_IsRunning) { m_IsRunning = false; m_Acceptor.cancel (); m_Service.stop (); if (m_Thread) { m_Thread->join (); delete m_Thread; m_Thread = nullptr; } } } 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 ()); } } } void I2PControlService::Accept () { auto newSocket = std::make_shared (m_Service, m_SSLContext); m_Acceptor.async_accept (newSocket->lowest_layer(), std::bind (&I2PControlService::HandleAccept, this, std::placeholders::_1, newSocket)); } 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 ()); return; } 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, 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); } void I2PControlService::ReadRequest (std::shared_ptr socket) { auto request = std::make_shared(); socket->async_read_some ( #if defined(BOOST_ASIO_HAS_STD_ARRAY) boost::asio::buffer (*request), #else boost::asio::buffer (request->data (), request->size ()), #endif std::bind(&I2PControlService::HandleRequestReceived, this, std::placeholders::_1, std::placeholders::_2, socket, request)); } 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 ()); return; } else { bool isHtml = !memcmp (buf->data (), "POST", 4); try { std::stringstream ss; ss.write (buf->data (), bytes_transferred); if (isHtml) { std::string header; size_t contentLength = 0; while (!ss.eof () && header != "\r") { std::getline(ss, header); auto colon = header.find (':'); if (colon != std::string::npos && header.substr (0, colon) == "Content-Length") contentLength = std::stoi (header.substr (colon + 1)); } if (ss.eof ()) { LogPrint (eLogError, "I2PControl: Malformed request, HTTP header expected"); return; // TODO: } std::streamoff rem = contentLength + ss.tellg () - bytes_transferred; // more bytes to read if (rem > 0) { bytes_transferred = boost::asio::read (*socket, boost::asio::buffer (buf->data (), rem)); ss.write (buf->data (), bytes_transferred); } } std::ostringstream response; boost::property_tree::ptree pt; boost::property_tree::read_json (ss, pt); std::string id = pt.get("id"); std::string method = pt.get("method"); auto it = m_MethodHandlers.find (method); if (it != m_MethodHandlers.end ()) { response << "{\"id\":" << id << ",\"result\":{"; (this->*(it->second))(pt.get_child ("params"), response); response << "},\"jsonrpc\":\"2.0\"}"; } else { LogPrint (eLogWarning, "I2PControl: Unknown method ", method); response << "{\"id\":null,\"error\":"; response << "{\"code\":-32601,\"message\":\"Method not found\"},"; response << "\"jsonrpc\":\"2.0\"}"; } SendResponse (socket, buf, response, isHtml); } catch (std::exception& ex) { LogPrint (eLogError, "I2PControl: Exception when handle request: ", ex.what ()); std::ostringstream response; response << "{\"id\":null,\"error\":"; response << "{\"code\":-32700,\"message\":\"" << ex.what () << "\"},"; response << "\"jsonrpc\":\"2.0\"}"; SendResponse (socket, buf, response, isHtml); } catch (...) { LogPrint (eLogError, "I2PControl: Handle request unknown exception"); } } } void I2PControlService::SendResponse (std::shared_ptr socket, std::shared_ptr buf, std::ostringstream& response, bool isHtml) { size_t len = response.str ().length (), offset = 0; if (isHtml) { std::ostringstream header; header << "HTTP/1.1 200 OK\r\n"; header << "Connection: close\r\n"; header << "Content-Length: " << boost::lexical_cast(len) << "\r\n"; header << "Content-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"; header << "\r\n"; offset = header.str ().size (); memcpy (buf->data (), header.str ().c_str (), offset); } memcpy (buf->data () + offset, response.str ().c_str (), len); boost::asio::async_write (*socket, boost::asio::buffer (buf->data (), offset + len), boost::asio::transfer_all (), std::bind(&I2PControlService::HandleResponseSent, this, std::placeholders::_1, std::placeholders::_2, socket, buf)); } void I2PControlService::HandleResponseSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, std::shared_ptr socket, std::shared_ptr buf) { if (ecode) { LogPrint (eLogError, "I2PControl: Write error: ", ecode.message ()); } } // handlers void I2PControlService::AuthenticateHandler (const boost::property_tree::ptree& params, std::ostringstream& results) { int api = params.get ("API"); auto password = params.get ("Password"); LogPrint (eLogDebug, "I2PControl: Authenticate API=", api, " Password=", password); if (password != m_Password) { LogPrint (eLogError, "I2PControl: Authenticate - Invalid password: ", password); return; } InsertParam (results, "API", api); results << ","; std::string token = boost::lexical_cast(i2p::util::GetSecondsSinceEpoch ()); m_Tokens.insert (token); InsertParam (results, "Token", token); } void I2PControlService::EchoHandler (const boost::property_tree::ptree& params, std::ostringstream& results) { auto echo = params.get ("Echo"); LogPrint (eLogDebug, "I2PControl Echo Echo=", echo); InsertParam (results, "Result", echo); } // I2PControl void I2PControlService::I2PControlHandler (const boost::property_tree::ptree& params, std::ostringstream& results) { for (auto& it: params) { LogPrint (eLogDebug, "I2PControl: I2PControl request: ", it.first); auto it1 = m_I2PControlHandlers.find (it.first); if (it1 != m_I2PControlHandlers.end ()) { (this->*(it1->second))(it.second.data ()); InsertParam (results, it.first, ""); } else LogPrint (eLogError, "I2PControl: I2PControl unknown request: ", it.first); } } void I2PControlService::PasswordHandler (const std::string& value) { LogPrint (eLogWarning, "I2PControl: New password=", value, ", to make it persistent you should update your config!"); m_Password = value; m_Tokens.clear (); } // RouterManager void I2PControlService::RouterManagerHandler (const boost::property_tree::ptree& params, std::ostringstream& results) { for (auto it = params.begin (); it != params.end (); it++) { LogPrint (eLogDebug, "I2PControl: RouterManager request: ", it->first); auto it1 = m_RouterManagerHandlers.find (it->first); if (it1 != m_RouterManagerHandlers.end ()) { if (it != params.begin ()) results << ","; (this->*(it1->second))(results); } else LogPrint (eLogError, "I2PControl: RouterManager unknown request: ", it->first); } } void I2PControlService::ShutdownHandler (std::ostringstream& results) { LogPrint (eLogInfo, "I2PControl: Shutdown requested"); InsertParam (results, "Shutdown", ""); 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; }); } void I2PControlService::ShutdownGracefulHandler (std::ostringstream& results) { i2p::context.SetAcceptsTunnels (false); int timeout = i2p::tunnel::tunnels.GetTransitTunnelsExpirationTimeout (); LogPrint (eLogInfo, "I2PControl: Graceful shutdown requested, ", timeout, " seconds remains"); InsertParam (results, "ShutdownGraceful", ""); 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; }); } void I2PControlService::ReseedHandler (std::ostringstream& results) { LogPrint (eLogInfo, "I2PControl: Reseed requested"); InsertParam (results, "Reseed", ""); i2p::data::netdb.Reseed (); } // certificate void I2PControlService::CreateCertificate (const char *crt_path, const char *key_path) { FILE *f = NULL; EVP_PKEY * pkey = EVP_PKEY_new (); RSA * rsa = RSA_new (); BIGNUM * e = BN_dup (i2p::crypto::GetRSAE ()); RSA_generate_key_ex (rsa, 4096, e, NULL); BN_free (e); if (rsa) { EVP_PKEY_assign_RSA (pkey, rsa); X509 * x509 = X509_new (); ASN1_INTEGER_set (X509_get_serialNumber (x509), 1); X509_gmtime_adj (X509_getm_notBefore (x509), 0); X509_gmtime_adj (X509_getm_notAfter (x509), I2P_CONTROL_CERTIFICATE_VALIDITY*24*60*60); // expiration X509_set_pubkey (x509, pkey); // public key X509_NAME * name = X509_get_subject_name (x509); X509_NAME_add_entry_by_txt (name, "C", MBSTRING_ASC, (unsigned char *)"A1", -1, -1, 0); // country (Anonymous proxy) X509_NAME_add_entry_by_txt (name, "O", MBSTRING_ASC, (unsigned char *)I2P_CONTROL_CERTIFICATE_ORGANIZATION, -1, -1, 0); // organization X509_NAME_add_entry_by_txt (name, "CN", MBSTRING_ASC, (unsigned char *)I2P_CONTROL_CERTIFICATE_COMMON_NAME, -1, -1, 0); // common name X509_set_issuer_name (x509, name); // set issuer to ourselves X509_sign (x509, pkey, EVP_sha1 ()); // sign, last param must be NULL for EdDSA // save cert if ((f = fopen (crt_path, "wb")) != NULL) { LogPrint (eLogInfo, "I2PControl: Saving new cert to ", crt_path); PEM_write_X509 (f, x509); fclose (f); } else { LogPrint (eLogError, "I2PControl: Can't write cert: ", strerror(errno)); } // save key if ((f = fopen (key_path, "wb")) != NULL) { LogPrint (eLogInfo, "I2PControl: saving cert key to ", key_path); PEM_write_PrivateKey (f, pkey, NULL, NULL, 0, NULL, NULL); fclose (f); } else { LogPrint (eLogError, "I2PControl: Can't write key: ", strerror(errno)); } X509_free (x509); } else { LogPrint (eLogError, "I2PControl: Can't create RSA key for certificate"); } EVP_PKEY_free (pkey); } } } i2pd-2.56.0/daemon/I2PControl.h000066400000000000000000000070271475272067700160400ustar00rootroot00000000000000/* * 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 I2P_CONTROL_H__ #define I2P_CONTROL_H__ #include #include #include #include #include #include #include #include #include #include #include #include "I2PControlHandlers.h" namespace i2p { namespace client { const size_t I2P_CONTROL_MAX_REQUEST_SIZE = 1024; typedef std::array I2PControlBuffer; const long I2P_CONTROL_CERTIFICATE_VALIDITY = 365*10; // 10 years const char I2P_CONTROL_CERTIFICATE_COMMON_NAME[] = "i2pd.i2pcontrol"; const char I2P_CONTROL_CERTIFICATE_ORGANIZATION[] = "Purple I2P"; class I2PControlService: public I2PControlHandlers { typedef boost::asio::ssl::stream ssl_socket; public: I2PControlService (const std::string& address, int port); ~I2PControlService (); void Start (); void Stop (); private: void Run (); void Accept (); void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr socket); void Handshake (std::shared_ptr socket); void HandleHandshake (const boost::system::error_code& ecode, std::shared_ptr socket); void ReadRequest (std::shared_ptr socket); void HandleRequestReceived (const boost::system::error_code& ecode, size_t bytes_transferred, std::shared_ptr socket, std::shared_ptr buf); 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: // 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 RouterManagerHandler (const boost::property_tree::ptree& params, std::ostringstream& results); // I2PControl typedef void (I2PControlService::*I2PControlRequestHandler)(const std::string& value); void PasswordHandler (const std::string& value); // RouterManager typedef void (I2PControlService::*RouterManagerRequestHandler)(std::ostringstream& results); void ShutdownHandler (std::ostringstream& results); void ShutdownGracefulHandler (std::ostringstream& results); void ReseedHandler (std::ostringstream& results); private: std::string m_Password; bool m_IsRunning; std::thread * m_Thread; boost::asio::io_context 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_RouterManagerHandlers; }; } } #endif i2pd-2.56.0/daemon/I2PControlHandlers.cpp000066400000000000000000000322271475272067700200540ustar00rootroot00000000000000/* * 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); } } } i2pd-2.56.0/daemon/I2PControlHandlers.h000066400000000000000000000062661475272067700175250ustar00rootroot00000000000000/* * 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 i2pd-2.56.0/daemon/UPnP.cpp000066400000000000000000000167111475272067700152620ustar00rootroot00000000000000/* * 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 "Log.h" #include "RouterContext.h" #include "UPnP.h" #include "NetDb.hpp" #include "util.h" #include "RouterInfo.h" #include "Config.h" #include #include namespace i2p { namespace transport { UPnP::UPnP () : m_IsRunning(false), m_Thread (nullptr), m_Timer (m_Service) { } void UPnP::Stop () { if (m_IsRunning) { LogPrint(eLogInfo, "UPnP: Stopping"); m_IsRunning = false; m_Timer.cancel (); m_Service.stop (); if (m_Thread) { m_Thread->join (); m_Thread.reset (nullptr); } CloseMapping (); Close (); } } void UPnP::Start() { m_IsRunning = true; LogPrint(eLogInfo, "UPnP: Starting"); boost::asio::post (m_Service, std::bind (&UPnP::Discover, this)); std::unique_lock l(m_StartedMutex); m_Thread.reset (new std::thread (std::bind (&UPnP::Run, this))); m_Started.wait_for (l, std::chrono::seconds (5)); // 5 seconds maximum } UPnP::~UPnP () { Stop (); } void UPnP::Run () { i2p::util::SetThreadName("UPnP"); while (m_IsRunning) { try { m_Service.run (); // Discover failed break; // terminate the thread } catch (std::exception& ex) { LogPrint (eLogError, "UPnP: Runtime exception: ", ex.what ()); PortMapping (); } } } 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); #else m_Devlist = upnpDiscover (UPNP_RESPONSE_TIMEOUT, NULL, NULL, 0, 0, &err); #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 std::unique_lock l(m_StartedMutex); m_Started.notify_all (); } if (isError) { 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) { LogPrint (eLogError, "UPnP: Unable to get external address: error ", err); 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"); return; } } } else { LogPrint (eLogError, "UPnP: Unable to find valid Internet Gateway Device: error ", err); return; } // UPnP discovered LogPrint (eLogDebug, "UPnP: ExternalIPAddress is ", m_externalIPAddress); i2p::context.UpdateAddress (boost::asio::ip::make_address (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) { if (address && !address->host.is_v6 () && address->port) TryPortMapping (address); } m_Timer.expires_from_now (boost::posix_time::minutes(UPNP_PORT_FORWARDING_INTERVAL)); // every 20 minutes m_Timer.async_wait ([this](const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) PortMapping (); }); } void UPnP::TryPortMapping (std::shared_ptr address) { std::string strType (GetProto (address)), strPort (std::to_string (address->port)); 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); #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); #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; } } else { LogPrint (eLogDebug, "UPnP: External forward from ", m_NetworkAddr, ":", strPort, " exists on current Internet Gateway Device"); 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); } } void UPnP::Close () { freeUPNPDevlist (m_Devlist); m_Devlist = 0; if(m_upnpUrlsInitialized){ FreeUPNPUrls (&m_upnpUrls); m_upnpUrlsInitialized=false; } } std::string UPnP::GetProto (std::shared_ptr address) { switch (address->transportStyle) { case i2p::data::RouterInfo::eTransportNTCP2: return "TCP"; break; case i2p::data::RouterInfo::eTransportSSU2: default: return "UDP"; } } } } #else /* USE_UPNP */ namespace i2p { namespace transport { } } #endif /* USE_UPNP */ i2pd-2.56.0/daemon/UPnP.h000066400000000000000000000037751475272067700147350ustar00rootroot00000000000000/* * 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__ #ifdef USE_UPNP #include #include #include #include #include #include #include #include #include #include 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: UPnP (); ~UPnP (); void Close (); void Start (); void Stop (); 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 Run (); std::string GetProto (std::shared_ptr address); 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; // For miniupnpc struct UPNPDev * m_Devlist = 0; char m_NetworkAddr[64]; char m_externalIPAddress[40]; }; } } #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 () {}; }; } } #endif // USE_UPNP #endif // __UPNP_H__ i2pd-2.56.0/daemon/UnixDaemon.cpp000066400000000000000000000150371475272067700165070ustar00rootroot00000000000000/* * 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 #include #include #include #include #include #include #include #include "Config.h" #include "FS.h" #include "Log.h" #include "Tunnel.h" #include "RouterContext.h" #include "ClientContext.h" #include "Transports.h" #include "util.h" void handle_signal(int sig) { switch (sig) { case SIGHUP: LogPrint(eLogInfo, "Daemon: Got SIGHUP, reopening tunnel configuration..."); i2p::client::context.ReloadConfig(); break; case SIGUSR1: LogPrint(eLogInfo, "Daemon: Got SIGUSR1, reopening logs..."); i2p::log::Logger().Reopen (); break; case SIGINT: if (i2p::context.AcceptsTunnels () && !Daemon.gracefulShutdownInterval) { i2p::context.SetAcceptsTunnels (false); Daemon.gracefulShutdownInterval = 10*60; // 10 minutes LogPrint(eLogInfo, "Graceful shutdown after ", Daemon.gracefulShutdownInterval, " seconds"); } else Daemon.running = 0; break; case SIGABRT: case SIGTERM: Daemon.running = 0; // Exit loop break; 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; } } namespace i2p { namespace util { bool DaemonLinux::start() { if (isDaemon) { pid_t pid; pid = fork(); if (pid > 0) // parent ::exit (EXIT_SUCCESS); if (pid < 0) // error { LogPrint(eLogError, "Daemon: Could not fork: ", strerror(errno)); std::cerr << "i2pd: Could not fork: " << strerror(errno) << std::endl; return false; } // child umask(S_IWGRP | S_IRWXO); // 0027 int sid = setsid(); if (sid < 0) { LogPrint(eLogError, "Daemon: Could not create process group."); std::cerr << "i2pd: Could not create process group." << std::endl; 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; return false; } // point std{in,out,err} descriptors to /dev/null freopen("/dev/null", "r", stdin); freopen("/dev/null", "w", stdout); freopen("/dev/null", "w", stderr); } // set proc limits struct rlimit limit; 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"); } 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 ", nfiles, " (system limit is ", limit.rlim_max, ")"); } else { 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); } uint32_t cfsize; i2p::config::GetOption("limits.coresize", cfsize); if (cfsize) // core file size set { cfsize *= 1024; getrlimit(RLIMIT_CORE, &limit); 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)); } else if (cfsize == 0) { LogPrint(eLogInfo, "Daemon: coredumps disabled"); } else { 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); } } // Pidfile // this code is c-styled and a bit ugly, but we need fd for locking pidfile std::string pidfile; i2p::config::GetOption("pidfile", pidfile); if (pidfile == "") { pidfile = i2p::fs::DataDirPath("i2pd.pid"); } if (pidfile != "") { 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; 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; 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; 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; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; sigaction(SIGHUP, &sa, 0); sigaction(SIGUSR1, &sa, 0); 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); } return Daemon_Singleton::start(); } 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)); if (gracefulShutdownInterval) { gracefulShutdownInterval--; // - 1 second if (gracefulShutdownInterval <= 0 || i2p::tunnel::tunnels.CountTransitTunnels() <= 0) { LogPrint(eLogInfo, "Graceful shutdown"); return; } } } } } } #endif i2pd-2.56.0/daemon/i2pd.cpp000066400000000000000000000014571475272067700152770ustar00rootroot00000000000000/* * 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 main( int argc, char* argv[] ) { return i2p::qt::RunQT (argc, argv); } #else int main( int argc, char* argv[] ) { if (Daemon.init(argc, argv)) { if (Daemon.start()) Daemon.run (); else return EXIT_FAILURE; Daemon.stop(); } return EXIT_SUCCESS; } #endif #ifdef _WIN32 #include int CALLBACK WinMain( _In_ HINSTANCE hInstance, _In_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow ) { return main(__argc, __argv); } #endif i2pd-2.56.0/debian/000077500000000000000000000000001475272067700137055ustar00rootroot00000000000000i2pd-2.56.0/debian/.gitignore000066400000000000000000000002211475272067700156700ustar00rootroot00000000000000debhelper-build-stamp files i2pd-dbg.substvars i2pd.postinst.debhelper i2pd.postrm.debhelper i2pd.prerm.debhelper i2pd.substvars i2pd/ i2pd-dbg/ i2pd-2.56.0/debian/NEWS000066400000000000000000000003161475272067700144040ustar00rootroot00000000000000i2pd (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 i2pd-2.56.0/debian/changelog000066400000000000000000000236131475272067700155640ustar00rootroot00000000000000i2pd (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 -- orignal Tue, 30 Jan 2018 16:00:00 +0000 i2pd (2.17.0-1) unstable; urgency=low * updated to version 2.17.0/0.9.32 -- orignal Mon, 4 Dec 2017 18:00:00 +0000 i2pd (2.16.0-1) unstable; urgency=low * updated to version 2.16.0/0.9.32 -- orignal Mon, 13 Nov 2017 18:00:00 +0000 i2pd (2.15.0-1) unstable; urgency=low * updated to version 2.15.0/0.9.31 -- orignal Thu, 17 Aug 2017 18:00:00 +0000 i2pd (2.14.0-1) unstable; urgency=low * updated to version 2.14.0/0.9.30 * updated debian/control * renamed logrotate to i2pd.logrotate * fixed init.d script -- orignal Thu, 1 Jun 2017 14:00:00 +0000 i2pd (2.13.0-1) unstable; urgency=low * updated to version 2.13.0/0.9.29 * updated debian/control * renamed logrotate to i2pd.logrotate * fixed init.d script -- orignal Thu, 6 Apr 2017 14:00:00 +0000 i2pd (2.12.0-1) unstable; urgency=low * updated to version 2.12.0/0.9.28 -- orignal Tue, 14 Feb 2017 17:59:30 +0000 i2pd (2.11.0-1) unstable; urgency=low * updated to version 2.11.0/0.9.28 -- orignal Sun, 18 Dec 2016 21:01:30 +0000 i2pd (2.10.2-1) unstable; urgency=low * updated to version 2.10.2 -- orignal Sun, 4 Dec 2016 19:38:30 +0000 i2pd (2.10.1-1) unstable; urgency=low * updated to version 2.10.1 -- orignal Mon, 7 Nov 2016 14:18:30 +0000 i2pd (2.10.0-1) unstable; urgency=low * updated to version 2.10.0/0.9.27 * reseed.verify set to true by default -- orignal Sun, 16 Oct 2016 13:55:40 +0000 i2pd (2.9.0-1) unstable; urgency=low * updated to version 2.9.0 * updated tune-patch * removed I2PD_PORT in i2pd.default * removed all port assigments in services files * fixed logrotate * subscriptions.txt and tunnels.conf taken from docs folder -- orignal Fri, 12 Aug 2016 14:25:40 +0000 i2pd (2.7.0-1) unstable; urgency=low * updated to version 2.7.0/0.9.25 -- hagen Wed, 18 May 2016 01:11:04 +0000 i2pd (2.2.0-2) unstable; urgency=low * updated to version 2.2.0 -- hagen Wed, 23 Dec 2015 01:29:40 +0000 i2pd (2.1.0-1) unstable; urgency=low * updated to version 2.1.0/0.9.23 * updated deps -- hagen Fri, 19 Sep 2014 05:16:12 +0000 i2pd-2.56.0/debian/compat000066400000000000000000000000031475272067700151040ustar00rootroot0000000000000012 i2pd-2.56.0/debian/control000066400000000000000000000015021475272067700153060ustar00rootroot00000000000000Source: 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 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. i2pd-2.56.0/debian/copyright000066400000000000000000000053121475272067700156410ustar00rootroot00000000000000Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: i2pd Source: https://github.com/PurpleI2P Files: * Copyright: 2013-2023 PurpleI2P License: BSD-3-clause Files: debian/* Copyright: 2013-2015 Kill Your TV 2014-2016 hagen 2016-2023 R4SAS 2017-2020 Yangfl License: GPL-2+ License: BSD-3-clause Copyright (c) 2013-2023, The PurpleI2P Project . All rights reserved. . 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. . 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. . 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 COPYRIGHT HOLDER 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 the Free Software Foundation; either version 2 of the License, or (at your option) any later version. . This package is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. . You should have received a copy of the GNU General Public License along with this program. If not, see . On Debian systems, the complete text of the GNU General Public License version 2 can be found in "/usr/share/common-licenses/GPL-2". i2pd-2.56.0/debian/docs000066400000000000000000000000121475272067700145510ustar00rootroot00000000000000README.md i2pd-2.56.0/debian/i2pd.1000066400000000000000000000104441475272067700146300ustar00rootroot00000000000000.TH "I2PD" "1" "June 20, 2018" .SH "NAME" i2pd \- Full-featured C++ implementation of I2P client. .SH "SYNOPSIS" .B i2pd [\fIOPTION1\fR] [\fIOPTION2\fR]... .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" .TP \fB\-\-help\fR Show available options. .TP \fB\-\-conf=\fR Config file (default: \fI~/.i2pd/i2pd.conf\fR or \fI/var/lib/i2pd/i2pd.conf\fR) .BR This parameter will be silently ignored if the specified config file does not exist. Options specified on the command line take precedence over those in the config file. .TP \fB\-\-tunconf=\fR Tunnels config file (default: \fI~/.i2pd/tunnels.conf\fR or \fI/var/lib/i2pd/tunnels.conf\fR) .TP \fB\-\-pidfile=\fR Where to write pidfile (don\'t write by default) .TP \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 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) .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) .TP \fB\-\-service\fR Router will use system folders like \fI/var/lib/i2pd\fR (\fIdisabled\fR by default) .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" /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/ .RS 4 i2pd profile directory (when running as a system service, see \fB\-\-service\fR above) .RE .PP $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 On Debian systems, the complete text of the GNU General Public License can be found in \fI/usr/share/common-licenses/GPL\fR i2pd-2.56.0/debian/i2pd.default000066400000000000000000000005441475272067700161140ustar00rootroot00000000000000# Defaults for i2pd initscript # sourced by /etc/init.d/i2pd # installed at /etc/default/i2pd by the maintainer scripts I2PD_ENABLED="yes" # Additional options that are passed to the Daemon. # see possible switches in /usr/share/doc/i2pd/configuration.md.gz DAEMON_OPTS="" # If you have problems with hunging i2pd, you can try enable this ulimit -n 8192 i2pd-2.56.0/debian/i2pd.init000066400000000000000000000067741475272067700154460ustar00rootroot00000000000000#!/bin/sh ### BEGIN INIT INFO # Provides: i2pd # Required-Start: $network $local_fs $remote_fs # Required-Stop: $remote_fs # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: i2p router written in C++ ### END INIT INFO # Author: hagen 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_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" # Exit if the package is not installed [ -x $DAEMON ] || exit 0 [ -r /etc/default/$NAME ] && . /etc/default/$NAME . /lib/init/vars.sh . /lib/lsb/init-functions # Function that starts the daemon/service do_start() { # Return # 0 if daemon has been started # 1 if daemon was already running # 2 if daemon could not be started if [ "x$I2PD_ENABLED" != "xyes" ]; then log_warning_msg "$NAME disabled in config" return 2 fi test -e /var/run/i2pd || install -m 755 -o i2pd -g i2pd -d /var/run/i2pd touch "$PIDFILE" chown -f $USER:adm "$PIDFILE" test -e /var/log/i2pd || install -m 755 -o i2pd -g i2pd -d /var/log/i2pd touch "$LOGFILE" chown -f $USER:adm "$LOGFILE" start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --chuid "$USER" --test > /dev/null \ || 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 \ || return 2 return $? } # Function that stops the daemon/service do_stop() { # Return # 0 if daemon has been stopped # 1 if daemon was already stopped # 2 if daemon could not be stopped # other if a failure occurred start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME RETVAL="$?" [ "$RETVAL" = 2 ] && return 2 start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON [ "$?" = 2 ] && return 2 rm -f $PIDFILE return "$RETVAL" } # Function that sends a SIGHUP to the daemon/service do_reload() { start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME return 0 } case "$1" in start) [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC " "$NAME" do_start case "$?" in 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; esac ;; stop) [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" do_stop case "$?" in 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; esac ;; status) status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $? ;; reload|force-reload) log_daemon_msg "Reloading $DESC" "$NAME" do_reload log_end_msg $? ;; restart) log_daemon_msg "Restarting $DESC" "$NAME" do_stop case "$?" in 0|1) do_start case "$?" in 0) log_end_msg 0 ;; 1) log_end_msg 1 ;; # Old process is still running *) log_end_msg 1 ;; # Failed to start esac ;; *) # Failed to stop log_end_msg 1 ;; esac ;; *) echo "Usage: $0 {start|stop|status|restart|reload}" >&2 exit 3 ;; esac : i2pd-2.56.0/debian/i2pd.install000066400000000000000000000003161475272067700161330ustar00rootroot00000000000000i2pd usr/bin/ contrib/i2pd.conf etc/i2pd/ contrib/tunnels.conf etc/i2pd/ contrib/certificates/ usr/share/i2pd/ contrib/tunnels.d/README etc/i2pd/tunnels.conf.d/ contrib/apparmor/usr.bin.i2pd etc/apparmor.d i2pd-2.56.0/debian/i2pd.links000066400000000000000000000002771475272067700156130ustar00rootroot00000000000000etc/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 usr/share/i2pd/certificates var/lib/i2pd/certificates i2pd-2.56.0/debian/i2pd.logrotate000077700000000000000000000000001475272067700231132../contrib/i2pd.logrotateustar00rootroot00000000000000i2pd-2.56.0/debian/i2pd.manpages000066400000000000000000000000161475272067700162550ustar00rootroot00000000000000debian/i2pd.1 i2pd-2.56.0/debian/i2pd.service000077700000000000000000000000001475272067700234352../contrib/debian/i2pd.serviceustar00rootroot00000000000000i2pd-2.56.0/debian/i2pd.tmpfile000077700000000000000000000000001475272067700234352../contrib/debian/i2pd.tmpfileustar00rootroot00000000000000i2pd-2.56.0/debian/lintian-overrides000066400000000000000000000001031475272067700172600ustar00rootroot00000000000000# GPL come from debian/ i2pd: possible-gpl-code-linked-with-openssli2pd-2.56.0/debian/patches/000077500000000000000000000000001475272067700153345ustar00rootroot00000000000000i2pd-2.56.0/debian/patches/01-upnp.patch000066400000000000000000000010011475272067700175450ustar00rootroot00000000000000Description: 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 i2pd-2.56.0/debian/patches/series000066400000000000000000000000161475272067700165460ustar00rootroot0000000000000001-upnp.patch i2pd-2.56.0/debian/postinst000077500000000000000000000016151475272067700155210ustar00rootroot00000000000000#!/bin/sh set -e LOGFILE='/var/log/i2pd/i2pd.log' I2PDHOME='/var/lib/i2pd' I2PDUSER='i2pd' case "$1" in configure|reconfigure) # Older versions of adduser created the home directory. # The version of adduser in Debian unstable does not. # Create user and group as a system user. if getent passwd $I2PDUSER > /dev/null 2>&1; then groupadd -f $I2PDUSER || true else adduser --system --quiet --group --home $I2PDHOME $I2PDUSER fi mkdir -p -m0750 /var/log/i2pd chown -f ${I2PDUSER}:adm /var/log/i2pd touch $LOGFILE chmod 640 $LOGFILE chown -f ${I2PDUSER}:adm $LOGFILE mkdir -p -m0750 $I2PDHOME chown -f -P ${I2PDUSER}:${I2PDUSER} ${I2PDHOME} ;; abort-upgrade|abort-remove|abort-deconfigure) echo "Aborting upgrade" exit 0 ;; *) echo "postinst called with unknown argument '$1'" >&2 exit 0 ;; esac #DEBHELPER# exit 0 i2pd-2.56.0/debian/postrm000077500000000000000000000002571475272067700151630ustar00rootroot00000000000000#!/bin/sh set -e if [ "$1" = "purge" ]; then rm -f /etc/default/i2pd rm -rf /etc/i2pd rm -rf /var/lib/i2pd rm -rf /var/log/i2pd rm -rf /run/i2pd fi #DEBHELPER# exit 0 i2pd-2.56.0/debian/rules000077500000000000000000000003761475272067700147730ustar00rootroot00000000000000#!/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 $@ override_dh_auto_install: i2pd-2.56.0/debian/source/000077500000000000000000000000001475272067700152055ustar00rootroot00000000000000i2pd-2.56.0/debian/source/format000066400000000000000000000000141475272067700164130ustar00rootroot000000000000003.0 (quilt) i2pd-2.56.0/debian/watch000066400000000000000000000002551475272067700147400ustar00rootroot00000000000000version=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 i2pd-2.56.0/docs/000077500000000000000000000000001475272067700134135ustar00rootroot00000000000000i2pd-2.56.0/docs/Doxyfile000066400000000000000000003106711475272067700151310ustar00rootroot00000000000000# Doxyfile 1.8.8 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. # # All text after a double hash (##) is considered a comment and is placed in # front of the TAG it is preceding. # # All text after a single hash (#) is considered a comment and will be ignored. # The format is: # TAG = value [value, ...] # For lists, items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (\" \"). #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- # This tag specifies the encoding used for all characters in the config file # that follow. The default is UTF-8 which is also the encoding used for all text # before the first occurrence of this tag. Doxygen uses libiconv (or the iconv # built into libc) for the transcoding. See http://www.gnu.org/software/libiconv # for the list of possible encodings. # The default value is: UTF-8. DOXYFILE_ENCODING = UTF-8 # The PROJECT_NAME tag is a single word (or a sequence of words surrounded by # double-quotes, unless you are using Doxywizard) that should identify the # project for which the documentation is generated. This name is used in the # title of most generated pages and in a few other places. # The default value is: My Project. PROJECT_NAME = "i2pd" # The PROJECT_NUMBER tag can be used to enter a project or revision number. This # could be handy for archiving the generated documentation or if some version # control system is used. PROJECT_NUMBER = # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a # quick idea about the purpose of the project. Keep the description short. PROJECT_BRIEF = "load-balanced unspoofable packet switching network" # With the PROJECT_LOGO tag one can specify an logo or icon that is included in # the documentation. The maximum height of the logo should not exceed 55 pixels # and the maximum width should not exceed 200 pixels. Doxygen will copy the logo # to the output directory. PROJECT_LOGO = # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path # into which the generated documentation will be written. If a relative path is # entered, it will be relative to the location where doxygen was started. If # left blank the current directory will be used. OUTPUT_DIRECTORY = docs/generated # If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 4096 sub- # directories (in 2 levels) under the output directory of each output format and # will distribute the generated files over these directories. Enabling this # option can be useful when feeding doxygen a huge amount of source files, where # putting all generated files in the same directory would otherwise causes # performance problems for the file system. # The default value is: NO. CREATE_SUBDIRS = NO # If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII # characters to appear in the names of generated files. If set to NO, non-ASCII # characters will be escaped, for example _xE3_x81_x84 will be used for Unicode # U+3044. # The default value is: NO. ALLOW_UNICODE_NAMES = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. # Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, # Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), # Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, # Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), # Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, # Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, # Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, # Ukrainian and Vietnamese. # The default value is: English. OUTPUT_LANGUAGE = English # If the BRIEF_MEMBER_DESC tag is set to YES doxygen will include brief member # descriptions after the members that are listed in the file and class # documentation (similar to Javadoc). Set to NO to disable this. # The default value is: YES. BRIEF_MEMBER_DESC = YES # If the REPEAT_BRIEF tag is set to YES doxygen will prepend the brief # description of a member or function before the detailed description # # Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. # The default value is: YES. REPEAT_BRIEF = YES # This tag implements a quasi-intelligent brief description abbreviator that is # used to form the text in various listings. Each string in this list, if found # as the leading text of the brief description, will be stripped from the text # and the result, after processing the whole list, is used as the annotated # text. Otherwise, the brief description is used as-is. If left blank, the # following values are used ($name is automatically replaced with the name of # the entity):The $name class, The $name widget, The $name file, is, provides, # specifies, contains, represents, a, an and the. ABBREVIATE_BRIEF = # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then # doxygen will generate a detailed section even if there is only a brief # description. # The default value is: NO. ALWAYS_DETAILED_SEC = NO # If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all # inherited members of a class in the documentation of that class as if those # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. # The default value is: NO. INLINE_INHERITED_MEMB = NO # If the FULL_PATH_NAMES tag is set to YES doxygen will prepend the full path # before files name in the file list and in the header files. If set to NO the # shortest path that makes the file name unique will be used # The default value is: YES. FULL_PATH_NAMES = YES # The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. # Stripping is only done if one of the specified strings matches the left-hand # part of the path. The tag can be used to show relative paths in the file list. # If left blank the directory from which doxygen is run is used as the path to # strip. # # Note that you can specify absolute paths here, but also relative paths, which # will be relative from the directory where doxygen is started. # This tag requires that the tag FULL_PATH_NAMES is set to YES. STRIP_FROM_PATH = # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the # path mentioned in the documentation of a class, which tells the reader which # header file to include in order to use a class. If left blank only the name of # the header file containing the class definition is used. Otherwise one should # specify the list of include paths that are normally passed to the compiler # using the -I flag. STRIP_FROM_INC_PATH = # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but # less readable) file names. This can be useful is your file systems doesn't # support long names like on DOS, Mac, or CD-ROM. # The default value is: NO. SHORT_NAMES = NO # If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the # first line (until the first dot) of a Javadoc-style comment as the brief # description. If set to NO, the Javadoc-style will behave just like regular Qt- # style comments (thus requiring an explicit @brief command for a brief # description.) # The default value is: NO. JAVADOC_AUTOBRIEF = NO # If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first # line (until the first dot) of a Qt-style comment as the brief description. If # set to NO, the Qt-style will behave just like regular Qt-style comments (thus # requiring an explicit \brief command for a brief description.) # The default value is: NO. QT_AUTOBRIEF = NO # The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a # multi-line C++ special comment block (i.e. a block of //! or /// comments) as # a brief description. This used to be the default behavior. The new default is # to treat a multi-line C++ comment block as a detailed description. Set this # tag to YES if you prefer the old behavior instead. # # Note that setting this tag to YES also means that rational rose comments are # not recognized any more. # The default value is: NO. MULTILINE_CPP_IS_BRIEF = NO # If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the # documentation from any documented member that it re-implements. # The default value is: YES. INHERIT_DOCS = YES # If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce a # new page for each member. If set to NO, the documentation of a member will be # part of the file/class/namespace that contains it. # The default value is: NO. SEPARATE_MEMBER_PAGES = NO # The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen # uses this value to replace tabs by spaces in code fragments. # Minimum value: 1, maximum value: 16, default value: 4. TAB_SIZE = 4 # This tag can be used to specify a number of aliases that act as commands in # the documentation. An alias has the form: # name=value # For example adding # "sideeffect=@par Side Effects:\n" # will allow you to put the command \sideeffect (or @sideeffect) in the # documentation, which will result in a user-defined paragraph with heading # "Side Effects:". You can put \n's in the value part of an alias to insert # newlines. ALIASES = # This tag can be used to specify a number of word-keyword mappings (TCL only). # A mapping has the form "name=value". For example adding "class=itcl::class" # will allow you to use the command class in the itcl::class meaning. TCL_SUBST = # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources # only. Doxygen will then generate output that is more tailored for C. For # instance, some of the names that are used will be different. The list of all # members will be omitted, etc. # The default value is: NO. OPTIMIZE_OUTPUT_FOR_C = NO # Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or # Python sources only. Doxygen will then generate output that is more tailored # for that language. For instance, namespaces will be presented as packages, # qualified scopes will look different, etc. # The default value is: NO. OPTIMIZE_OUTPUT_JAVA = NO # Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran # sources. Doxygen will then generate output that is tailored for Fortran. # The default value is: NO. OPTIMIZE_FOR_FORTRAN = NO # Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL # sources. Doxygen will then generate output that is tailored for VHDL. # The default value is: NO. OPTIMIZE_OUTPUT_VHDL = NO # Doxygen selects the parser to use depending on the extension of the files it # parses. With this tag you can assign which parser to use for a given # extension. Doxygen has a built-in mapping, but you can override or extend it # using this tag. The format is ext=language, where ext is a file extension, and # language is one of the parsers supported by doxygen: IDL, Java, Javascript, # C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: # FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: # Fortran. In the later case the parser tries to guess whether the code is fixed # or free formatted code, this is the default for Fortran type files), VHDL. For # instance to make doxygen treat .inc files as Fortran files (default is PHP), # and .f files as C (default is Fortran), use: inc=Fortran f=C. # # Note For files without extension you can use no_extension as a placeholder. # # Note that for custom extensions you also need to set FILE_PATTERNS otherwise # the files are not read by doxygen. EXTENSION_MAPPING = # If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments # according to the Markdown format, which allows for more readable # documentation. See http://daringfireball.net/projects/markdown/ for details. # The output of markdown processing is further processed by doxygen, so you can # mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in # case of backward compatibilities issues. # The default value is: YES. MARKDOWN_SUPPORT = YES # When enabled doxygen tries to link words that correspond to documented # classes, or namespaces to their corresponding documentation. Such a link can # be prevented in individual cases by by putting a % sign in front of the word # or globally by setting AUTOLINK_SUPPORT to NO. # The default value is: YES. AUTOLINK_SUPPORT = YES # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want # to include (a tag file for) the STL sources as input, then you should set this # tag to YES in order to let doxygen match functions declarations and # definitions whose arguments contain STL classes (e.g. func(std::string); # versus func(std::string) {}). This also make the inheritance and collaboration # diagrams that involve STL classes more complete and accurate. # The default value is: NO. BUILTIN_STL_SUPPORT = NO # If you use Microsoft's C++/CLI language, you should set this option to YES to # enable parsing support. # The default value is: NO. CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip (see: # http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen # will parse them like normal C++ but will assume all classes use public instead # of private inheritance when no explicit protection keyword is present. # The default value is: NO. SIP_SUPPORT = NO # For Microsoft's IDL there are propget and propput attributes to indicate # getter and setter methods for a property. Setting this option to YES will make # doxygen to replace the get and set methods by a property in the documentation. # This will only work if the methods are indeed getting or setting a simple # type. If this is not the case, or you want to show the methods anyway, you # should set this option to NO. # The default value is: YES. IDL_PROPERTY_SUPPORT = YES # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC # tag is set to YES, then doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. # The default value is: NO. DISTRIBUTE_GROUP_DOC = NO # Set the SUBGROUPING tag to YES to allow class member groups of the same type # (for instance a group of public functions) to be put as a subgroup of that # type (e.g. under the Public Functions section). Set it to NO to prevent # subgrouping. Alternatively, this can be done per class using the # \nosubgrouping command. # The default value is: YES. SUBGROUPING = YES # When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions # are shown inside the group in which they are included (e.g. using \ingroup) # instead of on a separate page (for HTML and Man pages) or section (for LaTeX # and RTF). # # Note that this feature does not work in combination with # SEPARATE_MEMBER_PAGES. # The default value is: NO. INLINE_GROUPED_CLASSES = NO # When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions # with only public data fields or simple typedef fields will be shown inline in # the documentation of the scope in which they are defined (i.e. file, # namespace, or group documentation), provided this scope is documented. If set # to NO, structs, classes, and unions are shown on a separate page (for HTML and # Man pages) or section (for LaTeX and RTF). # The default value is: NO. INLINE_SIMPLE_STRUCTS = NO # When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or # enum is documented as struct, union, or enum with the name of the typedef. So # typedef struct TypeS {} TypeT, will appear in the documentation as a struct # with name TypeT. When disabled the typedef will appear as a member of a file, # namespace, or class. And the struct will be named TypeS. This can typically be # useful for C code in case the coding convention dictates that all compound # types are typedef'ed and only the typedef is referenced, never the tag name. # The default value is: NO. TYPEDEF_HIDES_STRUCT = NO # The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This # cache is used to resolve symbols given their name and scope. Since this can be # an expensive process and often the same symbol appears multiple times in the # code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small # doxygen will become slower. If the cache is too large, memory is wasted. The # cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range # is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 # symbols. At the end of a run doxygen will report the cache usage and suggest # the optimal cache size from a speed point of view. # Minimum value: 0, maximum value: 9, default value: 0. LOOKUP_CACHE_SIZE = 0 #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- # If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in # documentation are documented, even if no documentation was available. Private # class members and static file members will be hidden unless the # EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. # Note: This will also disable the warnings about undocumented members that are # normally produced when WARNINGS is set to YES. # The default value is: NO. EXTRACT_ALL = NO # If the EXTRACT_PRIVATE tag is set to YES all private members of a class will # be included in the documentation. # The default value is: NO. EXTRACT_PRIVATE = NO # If the EXTRACT_PACKAGE tag is set to YES all members with package or internal # scope will be included in the documentation. # The default value is: NO. EXTRACT_PACKAGE = NO # If the EXTRACT_STATIC tag is set to YES all static members of a file will be # included in the documentation. # The default value is: NO. EXTRACT_STATIC = NO # If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) defined # locally in source files will be included in the documentation. If set to NO # only classes defined in header files are included. Does not have any effect # for Java sources. # The default value is: YES. EXTRACT_LOCAL_CLASSES = YES # This flag is only useful for Objective-C code. When set to YES local methods, # which are defined in the implementation section but not in the interface are # included in the documentation. If set to NO only methods in the interface are # included. # The default value is: NO. EXTRACT_LOCAL_METHODS = NO # If this flag is set to YES, the members of anonymous namespaces will be # extracted and appear in the documentation as a namespace called # 'anonymous_namespace{file}', where file will be replaced with the base name of # the file that contains the anonymous namespace. By default anonymous namespace # are hidden. # The default value is: NO. EXTRACT_ANON_NSPACES = NO # If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all # undocumented members inside documented classes or files. If set to NO these # members will be included in the various overviews, but no documentation # section is generated. This option has no effect if EXTRACT_ALL is enabled. # The default value is: NO. HIDE_UNDOC_MEMBERS = NO # If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. If set # to NO these classes will be included in the various overviews. This option has # no effect if EXTRACT_ALL is enabled. # The default value is: NO. HIDE_UNDOC_CLASSES = NO # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend # (class|struct|union) declarations. If set to NO these declarations will be # included in the documentation. # The default value is: NO. HIDE_FRIEND_COMPOUNDS = NO # If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any # documentation blocks found inside the body of a function. If set to NO these # blocks will be appended to the function's detailed documentation block. # The default value is: NO. HIDE_IN_BODY_DOCS = NO # The INTERNAL_DOCS tag determines if documentation that is typed after a # \internal command is included. If the tag is set to NO then the documentation # will be excluded. Set it to YES to include the internal documentation. # The default value is: NO. INTERNAL_DOCS = NO # If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file # names in lower-case letters. If set to YES upper-case letters are also # allowed. This is useful if you have classes or files whose names only differ # in case and if your file system supports case sensitive file names. Windows # and Mac users are advised to set this option to NO. # The default value is: system dependent. CASE_SENSE_NAMES = YES # If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with # their full class and namespace scopes in the documentation. If set to YES the # scope will be hidden. # The default value is: NO. HIDE_SCOPE_NAMES = NO # If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of # the files that are included by a file in the documentation of that file. # The default value is: YES. SHOW_INCLUDE_FILES = YES # If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each # grouped member an include statement to the documentation, telling the reader # which file to include in order to use the member. # The default value is: NO. SHOW_GROUPED_MEMB_INC = NO # If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include # files with double quotes in the documentation rather than with sharp brackets. # The default value is: NO. FORCE_LOCAL_INCLUDES = NO # If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the # documentation for inline members. # The default value is: YES. INLINE_INFO = YES # If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the # (detailed) documentation of file and class members alphabetically by member # name. If set to NO the members will appear in declaration order. # The default value is: YES. SORT_MEMBER_DOCS = YES # If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief # descriptions of file, namespace and class members alphabetically by member # name. If set to NO the members will appear in declaration order. Note that # this will also influence the order of the classes in the class list. # The default value is: NO. SORT_BRIEF_DOCS = NO # If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the # (brief and detailed) documentation of class members so that constructors and # destructors are listed first. If set to NO the constructors will appear in the # respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. # Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief # member documentation. # Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting # detailed member documentation. # The default value is: NO. SORT_MEMBERS_CTORS_1ST = NO # If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy # of group names into alphabetical order. If set to NO the group names will # appear in their defined order. # The default value is: NO. SORT_GROUP_NAMES = NO # If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by # fully-qualified names, including namespaces. If set to NO, the class list will # be sorted only by class name, not including the namespace part. # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. # Note: This option applies only to the class list, not to the alphabetical # list. # The default value is: NO. SORT_BY_SCOPE_NAME = NO # If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper # type resolution of all parameters of a function it will reject a match between # the prototype and the implementation of a member function even if there is # only one candidate or it is obvious which candidate to choose by doing a # simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still # accept a match between prototype and implementation in such cases. # The default value is: NO. STRICT_PROTO_MATCHING = NO # The GENERATE_TODOLIST tag can be used to enable ( YES) or disable ( NO) the # todo list. This list is created by putting \todo commands in the # documentation. # The default value is: YES. GENERATE_TODOLIST = YES # The GENERATE_TESTLIST tag can be used to enable ( YES) or disable ( NO) the # test list. This list is created by putting \test commands in the # documentation. # The default value is: YES. GENERATE_TESTLIST = YES # The GENERATE_BUGLIST tag can be used to enable ( YES) or disable ( NO) the bug # list. This list is created by putting \bug commands in the documentation. # The default value is: YES. GENERATE_BUGLIST = YES # The GENERATE_DEPRECATEDLIST tag can be used to enable ( YES) or disable ( NO) # the deprecated list. This list is created by putting \deprecated commands in # the documentation. # The default value is: YES. GENERATE_DEPRECATEDLIST= YES # The ENABLED_SECTIONS tag can be used to enable conditional documentation # sections, marked by \if ... \endif and \cond # ... \endcond blocks. ENABLED_SECTIONS = # The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the # initial value of a variable or macro / define can have for it to appear in the # documentation. If the initializer consists of more lines than specified here # it will be hidden. Use a value of 0 to hide initializers completely. The # appearance of the value of individual variables and macros / defines can be # controlled using \showinitializer or \hideinitializer command in the # documentation regardless of this setting. # Minimum value: 0, maximum value: 10000, default value: 30. MAX_INITIALIZER_LINES = 30 # Set the SHOW_USED_FILES tag to NO to disable the list of files generated at # the bottom of the documentation of classes and structs. If set to YES the list # will mention the files that were used to generate the documentation. # The default value is: YES. SHOW_USED_FILES = YES # Set the SHOW_FILES tag to NO to disable the generation of the Files page. This # will remove the Files entry from the Quick Index and from the Folder Tree View # (if specified). # The default value is: YES. SHOW_FILES = YES # Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces # page. This will remove the Namespaces entry from the Quick Index and from the # Folder Tree View (if specified). # The default value is: YES. SHOW_NAMESPACES = YES # The FILE_VERSION_FILTER tag can be used to specify a program or script that # doxygen should invoke to get the current version for each file (typically from # the version control system). Doxygen will invoke the program by executing (via # popen()) the command command input-file, where command is the value of the # FILE_VERSION_FILTER tag, and input-file is the name of an input file provided # by doxygen. Whatever the program writes to standard output is used as the file # version. For an example see the documentation. FILE_VERSION_FILTER = # The LAYOUT_FILE tag can be used to specify a layout file which will be parsed # by doxygen. The layout file controls the global structure of the generated # output files in an output format independent way. To create the layout file # that represents doxygen's defaults, run doxygen with the -l option. You can # optionally specify a file name after the option, if omitted DoxygenLayout.xml # will be used as the name of the layout file. # # Note that if you run doxygen from a directory containing a file called # DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE # tag is left empty. LAYOUT_FILE = # The CITE_BIB_FILES tag can be used to specify one or more bib files containing # the reference definitions. This must be a list of .bib files. The .bib # extension is automatically appended if omitted. This requires the bibtex tool # to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. # For LaTeX the style of the bibliography can be controlled using # LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the # search path. See also \cite for info how to create references. CITE_BIB_FILES = #--------------------------------------------------------------------------- # Configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated to # standard output by doxygen. If QUIET is set to YES this implies that the # messages are off. # The default value is: NO. QUIET = YES # The WARNINGS tag can be used to turn on/off the warning messages that are # generated to standard error ( stderr) by doxygen. If WARNINGS is set to YES # this implies that the warnings are on. # # Tip: Turn warnings on while writing the documentation. # The default value is: YES. WARNINGS = YES # If the WARN_IF_UNDOCUMENTED tag is set to YES, then doxygen will generate # warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag # will automatically be disabled. # The default value is: YES. WARN_IF_UNDOCUMENTED = NO # If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for # potential errors in the documentation, such as not documenting some parameters # in a documented function, or documenting parameters that don't exist or using # markup commands wrongly. # The default value is: YES. WARN_IF_DOC_ERROR = YES # This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that # are documented, but have no documentation for their parameters or return # value. If set to NO doxygen will only warn about wrong or incomplete parameter # documentation, but not about the absence of documentation. # The default value is: NO. WARN_NO_PARAMDOC = NO # The WARN_FORMAT tag determines the format of the warning messages that doxygen # can produce. The string should contain the $file, $line, and $text tags, which # will be replaced by the file and line number from which the warning originated # and the warning text. Optionally the format may contain $version, which will # be replaced by the version of the file (if it could be obtained via # FILE_VERSION_FILTER) # The default value is: $file:$line: $text. WARN_FORMAT = "$file:$line: $text" # The WARN_LOGFILE tag can be used to specify a file to which warning and error # messages should be written. If left blank the output is written to standard # error (stderr). WARN_LOGFILE = #--------------------------------------------------------------------------- # Configuration options related to the input files #--------------------------------------------------------------------------- # The INPUT tag is used to specify the files and/or directories that contain # documented source files. You may enter file names like myfile.cpp or # directories like /usr/src/myproject. Separate the files or directories with # spaces. # Note: If this tag is empty the current directory is searched. INPUT = # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses # libiconv (or the iconv built into libc) for the transcoding. See the libiconv # documentation (see: http://www.gnu.org/software/libiconv) for the list of # possible encodings. # The default value is: UTF-8. INPUT_ENCODING = UTF-8 # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and # *.h) to filter out the source-files in the directories. If left blank the # following patterns are tested:*.c, *.cc, *.cxx, *.cpp, *.c++, *.java, *.ii, # *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, # *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, # *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, # *.qsf, *.as and *.js. FILE_PATTERNS = *.cpp \ *.h \ *.hpp # The RECURSIVE tag can be used to specify whether or not subdirectories should # be searched for input files as well. # The default value is: NO. RECURSIVE = YES # The EXCLUDE tag can be used to specify files and/or directories that should be # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. # # Note that relative paths are relative to the directory from which doxygen is # run. EXCLUDE = # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded # from the input. # The default value is: NO. EXCLUDE_SYMLINKS = NO # If the value of the INPUT tag contains directories, you can use the # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude # certain files from those directories. # # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories for example use the pattern */test/* EXCLUDE_PATTERNS = # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, # AClass::ANamespace, ANamespace::*Test # # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories use the pattern */test/* EXCLUDE_SYMBOLS = # The EXAMPLE_PATH tag can be used to specify one or more files or directories # that contain example code fragments that are included (see the \include # command). EXAMPLE_PATH = # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and # *.h) to filter out the source-files in the directories. If left blank all # files are included. EXAMPLE_PATTERNS = # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude commands # irrespective of the value of the RECURSIVE tag. # The default value is: NO. EXAMPLE_RECURSIVE = NO # The IMAGE_PATH tag can be used to specify one or more files or directories # that contain images that are to be included in the documentation (see the # \image command). IMAGE_PATH = # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program # by executing (via popen()) the command: # # # # where is the value of the INPUT_FILTER tag, and is the # name of an input file. Doxygen will then use the output that the filter # program writes to standard output. If FILTER_PATTERNS is specified, this tag # will be ignored. # # Note that the filter must not add or remove lines; it is applied before the # code is scanned, but not when the output code is generated. If lines are added # or removed, the anchors will not be placed correctly. INPUT_FILTER = # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. Doxygen will compare the file name with each pattern and apply the # filter if there is a match. The filters are a list of the form: pattern=filter # (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how # filters are used. If the FILTER_PATTERNS tag is empty or if none of the # patterns match the file name, INPUT_FILTER is applied. FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER ) will also be used to filter the input files that are used for # producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). # The default value is: NO. FILTER_SOURCE_FILES = NO # The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file # pattern. A pattern will override the setting for FILTER_PATTERN (if any) and # it is also possible to disable source filtering for a specific pattern using # *.ext= (so without naming a filter). # This tag requires that the tag FILTER_SOURCE_FILES is set to YES. FILTER_SOURCE_PATTERNS = # If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that # is part of the input, its contents will be placed on the main page # (index.html). This can be useful if you have a project on for instance GitHub # and want to reuse the introduction page also for the doxygen output. USE_MDFILE_AS_MAINPAGE = #--------------------------------------------------------------------------- # Configuration options related to source browsing #--------------------------------------------------------------------------- # If the SOURCE_BROWSER tag is set to YES then a list of source files will be # generated. Documented entities will be cross-referenced with these sources. # # Note: To get rid of all source code in the generated output, make sure that # also VERBATIM_HEADERS is set to NO. # The default value is: NO. SOURCE_BROWSER = NO # Setting the INLINE_SOURCES tag to YES will include the body of functions, # classes and enums directly into the documentation. # The default value is: NO. INLINE_SOURCES = NO # Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any # special comment blocks from generated source code fragments. Normal C, C++ and # Fortran comments will always remain visible. # The default value is: YES. STRIP_CODE_COMMENTS = YES # If the REFERENCED_BY_RELATION tag is set to YES then for each documented # function all documented functions referencing it will be listed. # The default value is: NO. REFERENCED_BY_RELATION = NO # If the REFERENCES_RELATION tag is set to YES then for each documented function # all documented entities called/used by that function will be listed. # The default value is: NO. REFERENCES_RELATION = NO # If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set # to YES, then the hyperlinks from functions in REFERENCES_RELATION and # REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will # link to the documentation. # The default value is: YES. REFERENCES_LINK_SOURCE = YES # If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the # source code will show a tooltip with additional information such as prototype, # brief description and links to the definition and documentation. Since this # will make the HTML file larger and loading of large files a bit slower, you # can opt to disable this feature. # The default value is: YES. # This tag requires that the tag SOURCE_BROWSER is set to YES. SOURCE_TOOLTIPS = YES # If the USE_HTAGS tag is set to YES then the references to source code will # point to the HTML generated by the htags(1) tool instead of doxygen built-in # source browser. The htags tool is part of GNU's global source tagging system # (see http://www.gnu.org/software/global/global.html). You will need version # 4.8.6 or higher. # # To use it do the following: # - Install the latest version of global # - Enable SOURCE_BROWSER and USE_HTAGS in the config file # - Make sure the INPUT points to the root of the source tree # - Run doxygen as normal # # Doxygen will invoke htags (and that will in turn invoke gtags), so these # tools must be available from the command line (i.e. in the search path). # # The result: instead of the source browser generated by doxygen, the links to # source code will now point to the output of htags. # The default value is: NO. # This tag requires that the tag SOURCE_BROWSER is set to YES. USE_HTAGS = NO # If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a # verbatim copy of the header file for each class for which an include is # specified. Set to NO to disable this. # See also: Section \class. # The default value is: YES. VERBATIM_HEADERS = NO # If the CLANG_ASSISTED_PARSING tag is set to YES, then doxygen will use the # clang parser (see: http://clang.llvm.org/) for more accurate parsing at the # cost of reduced performance. This can be particularly helpful with template # rich C++ code for which doxygen's built-in parser lacks the necessary type # information. # Note: The availability of this option depends on whether or not doxygen was # compiled with the --with-libclang option. # The default value is: NO. CLANG_ASSISTED_PARSING = NO # If clang assisted parsing is enabled you can provide the compiler with command # line options that you would normally use when invoking the compiler. Note that # the include paths will already be set by doxygen for the files and directories # specified with INPUT and INCLUDE_PATH. # This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. CLANG_OPTIONS = #--------------------------------------------------------------------------- # Configuration options related to the alphabetical class index #--------------------------------------------------------------------------- # If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all # compounds will be generated. Enable this if the project contains a lot of # classes, structs, unions or interfaces. # The default value is: YES. ALPHABETICAL_INDEX = YES # The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in # which the alphabetical index list will be split. # Minimum value: 1, maximum value: 20, default value: 5. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. COLS_IN_ALPHA_INDEX = 5 # In case all classes in a project start with a common prefix, all classes will # be put under the same header in the alphabetical index. The IGNORE_PREFIX tag # can be used to specify a prefix (or a list of prefixes) that should be ignored # while generating the index headers. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. IGNORE_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the HTML output #--------------------------------------------------------------------------- # If the GENERATE_HTML tag is set to YES doxygen will generate HTML output # The default value is: YES. GENERATE_HTML = YES # The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a # relative path is entered the value of OUTPUT_DIRECTORY will be put in front of # it. # The default directory is: html. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_OUTPUT = html # The HTML_FILE_EXTENSION tag can be used to specify the file extension for each # generated HTML page (for example: .htm, .php, .asp). # The default value is: .html. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a user-defined HTML header file for # each generated HTML page. If the tag is left blank doxygen will generate a # standard header. # # To get valid HTML the header file that includes any scripts and style sheets # that doxygen needs, which is dependent on the configuration options used (e.g. # the setting GENERATE_TREEVIEW). It is highly recommended to start with a # default header using # doxygen -w html new_header.html new_footer.html new_stylesheet.css # YourConfigFile # and then modify the file new_header.html. See also section "Doxygen usage" # for information on how to generate the default header that doxygen normally # uses. # Note: The header is subject to change so you typically have to regenerate the # default header when upgrading to a newer version of doxygen. For a description # of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_HEADER = # The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each # generated HTML page. If the tag is left blank doxygen will generate a standard # footer. See HTML_HEADER for more information on how to generate a default # footer and what special commands can be used inside the footer. See also # section "Doxygen usage" for information on how to generate the default footer # that doxygen normally uses. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FOOTER = # The HTML_STYLESHEET tag can be used to specify a user-defined cascading style # sheet that is used by each HTML page. It can be used to fine-tune the look of # the HTML output. If left blank doxygen will generate a default style sheet. # See also section "Doxygen usage" for information on how to generate the style # sheet that doxygen normally uses. # Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as # it is more robust and this tag (HTML_STYLESHEET) will in the future become # obsolete. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_STYLESHEET = # The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined # 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. # 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 # list). For an example see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_STYLESHEET = # The HTML_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the HTML output directory. Note # that these files will be copied to the base HTML output directory. Use the # $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these # files. In the HTML_STYLESHEET file, use the file name only. Also note that the # files will be copied as-is; there are no commands or markers available. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_FILES = # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen # will adjust the colors in the stylesheet and background images according to # this color. Hue is specified as an angle on a colorwheel, see # http://en.wikipedia.org/wiki/Hue for more information. For instance the value # 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 # purple, and 360 is red again. # Minimum value: 0, maximum value: 359, default value: 220. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_HUE = 220 # The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors # in the HTML output. For a value of 0 the output will use grayscales only. A # value of 255 will produce the most vivid colors. # Minimum value: 0, maximum value: 255, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_SAT = 100 # The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the # luminance component of the colors in the HTML output. Values below 100 # gradually make the output lighter, whereas values above 100 make the output # darker. The value divided by 100 is the actual gamma applied, so 80 represents # a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not # change the gamma. # Minimum value: 40, maximum value: 240, default value: 80. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_GAMMA = 80 # If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML # page will contain the date and time when the page was generated. Setting this # to NO can help when comparing the output of multiple runs. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_TIMESTAMP = YES # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the # page has loaded. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_DYNAMIC_SECTIONS = NO # With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries # shown in the various tree structured indices initially; the user can expand # and collapse entries dynamically later on. Doxygen will expand the tree to # such a level that at most the specified number of entries are visible (unless # a fully collapsed tree already exceeds this amount). So setting the number of # entries 1 will produce a full collapsed tree by default. 0 is a special value # representing an infinite number of entries and will result in a full expanded # tree by default. # Minimum value: 0, maximum value: 9999, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_INDEX_NUM_ENTRIES = 100 # If the GENERATE_DOCSET tag is set to YES, additional index files will be # generated that can be used as input for Apple's Xcode 3 integrated development # environment (see: http://developer.apple.com/tools/xcode/), introduced with # OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a # Makefile in the HTML output directory. Running make will produce the docset in # that directory and running make install will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at # startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html # for more information. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_DOCSET = NO # This tag determines the name of the docset feed. A documentation feed provides # an umbrella under which multiple documentation sets from a single provider # (such as a company or product suite) can be grouped. # The default value is: Doxygen generated docs. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_FEEDNAME = "Doxygen generated docs" # This tag specifies a string that should uniquely identify the documentation # set bundle. This should be a reverse domain-name style string, e.g. # com.mycompany.MyDocSet. Doxygen will append .docset to the name. # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_BUNDLE_ID = org.doxygen.Project # The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify # the documentation publisher. This should be a reverse domain-name style # string, e.g. com.mycompany.MyDocSet.documentation. # The default value is: org.doxygen.Publisher. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_PUBLISHER_ID = org.doxygen.Publisher # The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. # The default value is: Publisher. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_PUBLISHER_NAME = Publisher # If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three # additional HTML index files: index.hhp, index.hhc, and index.hhk. The # index.hhp is a project file that can be read by Microsoft's HTML Help Workshop # (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on # Windows. # # The HTML Help Workshop contains a compiler that can convert all HTML output # generated by doxygen into a single compiled HTML file (.chm). Compiled HTML # files are now used as the Windows 98 help format, and will replace the old # Windows help format (.hlp) on all Windows platforms in the future. Compressed # HTML files also contain an index, a table of contents, and you can search for # words in the documentation. The HTML workshop also contains a viewer for # compressed HTML files. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_HTMLHELP = NO # The CHM_FILE tag can be used to specify the file name of the resulting .chm # file. You can add a path in front of the file if the result should not be # written to the html output directory. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_FILE = # The HHC_LOCATION tag can be used to specify the location (absolute path # including file name) of the HTML help compiler ( hhc.exe). If non-empty # doxygen will try to run the HTML help compiler on the generated index.hhp. # The file has to be specified with full path. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. HHC_LOCATION = # The GENERATE_CHI flag controls if a separate .chi index file is generated ( # YES) or that it should be included in the master .chm file ( NO). # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. GENERATE_CHI = NO # The CHM_INDEX_ENCODING is used to encode HtmlHelp index ( hhk), content ( hhc) # and project file content. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_INDEX_ENCODING = # The BINARY_TOC flag controls whether a binary table of contents is generated ( # YES) or a normal table of contents ( NO) in the .chm file. Furthermore it # enables the Previous and Next buttons. # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. BINARY_TOC = NO # The TOC_EXPAND flag can be set to YES to add extra items for group members to # the table of contents of the HTML help documentation and to the tree view. # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. TOC_EXPAND = NO # If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and # QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that # can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help # (.qch) of the generated HTML documentation. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_QHP = NO # If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify # the file name of the resulting .qch file. The path specified is relative to # the HTML output folder. # This tag requires that the tag GENERATE_QHP is set to YES. QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help # Project output. For more information please see Qt Help Project / Namespace # (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_QHP is set to YES. QHP_NAMESPACE = org.doxygen.Project # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt # Help Project output. For more information please see Qt Help Project / Virtual # Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- # folders). # The default value is: doc. # This tag requires that the tag GENERATE_QHP is set to YES. QHP_VIRTUAL_FOLDER = doc # If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom # filter to add. For more information please see Qt Help Project / Custom # Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- # filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_NAME = # The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the # custom filter to add. For more information please see Qt Help Project / Custom # Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- # filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_ATTRS = # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this # project's filter section matches. Qt Help Project / Filter Attributes (see: # http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_SECT_FILTER_ATTRS = # The QHG_LOCATION tag can be used to specify the location of Qt's # qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the # generated .qhp file. # This tag requires that the tag GENERATE_QHP is set to YES. QHG_LOCATION = # If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be # generated, together with the HTML files, they form an Eclipse help plugin. To # install this plugin and make it available under the help contents menu in # Eclipse, the contents of the directory containing the HTML and XML files needs # to be copied into the plugins directory of eclipse. The name of the directory # within the plugins directory should be the same as the ECLIPSE_DOC_ID value. # After copying Eclipse needs to be restarted before the help appears. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_ECLIPSEHELP = NO # A unique identifier for the Eclipse help plugin. When installing the plugin # the directory name containing the HTML and XML files should also have this # name. Each documentation set should have its own identifier. # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. ECLIPSE_DOC_ID = org.doxygen.Project # If you want full control over the layout of the generated HTML pages it might # be necessary to disable the index and replace it with your own. The # DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top # of each HTML page. A value of NO enables the index and the value YES disables # it. Since the tabs in the index contain the same information as the navigation # tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. DISABLE_INDEX = NO # The GENERATE_TREEVIEW tag is used to specify whether a tree-like index # structure should be generated to display hierarchical information. If the tag # value is set to YES, a side panel will be generated containing a tree-like # index structure (just like the one that is generated for HTML Help). For this # to work a browser that supports JavaScript, DHTML, CSS and frames is required # (i.e. any modern browser). Windows users are probably better off using the # HTML help feature. Via custom stylesheets (see HTML_EXTRA_STYLESHEET) one can # further fine-tune the look of the index. As an example, the default style # sheet generated by doxygen has an example that shows how to put an image at # the root of the tree instead of the PROJECT_NAME. Since the tree basically has # the same information as the tab index, you could consider setting # DISABLE_INDEX to YES when enabling this option. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_TREEVIEW = NO # The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that # doxygen will group on one line in the generated HTML documentation. # # Note that a value of 0 will completely suppress the enum values from appearing # in the overview section. # Minimum value: 0, maximum value: 20, default value: 4. # This tag requires that the tag GENERATE_HTML is set to YES. ENUM_VALUES_PER_LINE = 4 # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used # to set the initial width (in pixels) of the frame in which the tree is shown. # Minimum value: 0, maximum value: 1500, default value: 250. # This tag requires that the tag GENERATE_HTML is set to YES. TREEVIEW_WIDTH = 250 # When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open links to # external symbols imported via tag files in a separate window. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. EXT_LINKS_IN_WINDOW = NO # Use this tag to change the font size of LaTeX formulas included as images in # the HTML documentation. When you change the font size after a successful # doxygen run you need to manually remove any form_*.png images from the HTML # output directory to force them to be regenerated. # Minimum value: 8, maximum value: 50, default value: 10. # This tag requires that the tag GENERATE_HTML is set to YES. FORMULA_FONTSIZE = 10 # Use the FORMULA_TRANPARENT tag to determine whether or not the images # generated for formulas are transparent PNGs. Transparent PNGs are not # supported properly for IE 6.0, but are supported on all modern browsers. # # Note that when changing this option you need to delete any form_*.png files in # the HTML output directory before the changes have effect. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. FORMULA_TRANSPARENT = YES # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see # http://www.mathjax.org) which uses client side Javascript for the rendering # instead of using prerendered bitmaps. Use this if you do not have LaTeX # installed or if you want to formulas look prettier in the HTML output. When # enabled you may also need to install MathJax separately and configure the path # to it using the MATHJAX_RELPATH option. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. USE_MATHJAX = NO # When MathJax is enabled you can set the default output format to be used for # the MathJax output. See the MathJax site (see: # http://docs.mathjax.org/en/latest/output.html) for more details. # Possible values are: HTML-CSS (which is slower, but has the best # compatibility), NativeMML (i.e. MathML) and SVG. # The default value is: HTML-CSS. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_FORMAT = HTML-CSS # When MathJax is enabled you need to specify the location relative to the HTML # output directory using the MATHJAX_RELPATH option. The destination directory # should contain the MathJax.js script. For instance, if the mathjax directory # is located at the same level as the HTML output directory, then # MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax # Content Delivery Network so you can quickly see the result without installing # MathJax. However, it is strongly recommended to install a local copy of # MathJax from http://www.mathjax.org before deployment. # The default value is: http://cdn.mathjax.org/mathjax/latest. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest # The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax # extension names that should be enabled during MathJax rendering. For example # MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_EXTENSIONS = # The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces # of code that will be used on startup of the MathJax code. See the MathJax site # (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an # example see the documentation. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_CODEFILE = # When the SEARCHENGINE tag is enabled doxygen will generate a search box for # the HTML output. The underlying search engine uses javascript and DHTML and # should work on any modern browser. Note that when using HTML help # (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) # there is already a search function so this one should typically be disabled. # For large projects the javascript based search engine can be slow, then # enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to # search using the keyboard; to jump to the search box use + S # (what the is depends on the OS and browser, but it is typically # , /

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); } 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::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; } } 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 () { std::string caps; uint8_t c = GetCaps (); if (c & eFloodfill) { if (c & eExtraBandwidth) caps += (c & eHighBandwidth) ? CAPS_FLAG_EXTRA_BANDWIDTH2 : // 'X' CAPS_FLAG_EXTRA_BANDWIDTH1; // 'P' else caps += CAPS_FLAG_HIGH_BANDWIDTH; // 'O' caps += CAPS_FLAG_FLOODFILL; // floodfill } else { if (c & eExtraBandwidth) caps += (c & eHighBandwidth) ? CAPS_FLAG_EXTRA_BANDWIDTH2 /* 'X' */ : CAPS_FLAG_EXTRA_BANDWIDTH1; /*'P' */ else caps += (c & eHighBandwidth) ? CAPS_FLAG_HIGH_BANDWIDTH /* 'O' */: CAPS_FLAG_LOW_BANDWIDTH2 /* 'L' */; // bandwidth } if (c & eHidden) caps += CAPS_FLAG_HIDDEN; // hidden if (c & eReachable) caps += CAPS_FLAG_REACHABLE; // reachable if (c & eUnreachable) caps += CAPS_FLAG_UNREACHABLE; // unreachable switch (GetCongestion ()) { case eMediumCongestion: caps += CAPS_FLAG_MEDIUM_CONGESTION; break; case eHighCongestion: caps += CAPS_FLAG_HIGH_CONGESTION; break; case eRejectAll: caps += CAPS_FLAG_REJECT_ALL_CONGESTION; break; default: ; }; SetProperty ("caps", caps); } bool LocalRouterInfo::UpdateCongestion (Congestion c) { if (c != GetCongestion ()) { SetCongestion (c); UpdateCapsProperty (); return true; } return false; } void LocalRouterInfo::WriteToStream (std::ostream& s) const { auto addresses = GetAddresses (); if (!addresses) return; uint64_t ts = htobe64 (GetTimestamp ()); s.write ((const char *)&ts, sizeof (ts)); // addresses uint8_t numAddresses = 0; for (size_t idx = 0; idx < addresses->size(); idx++) { auto addr_ptr = (*addresses)[idx]; if (!addr_ptr) continue; if (idx == eNTCP2V6Idx && addr_ptr == (*addresses)[eNTCP2V4Idx]) continue; if (idx == eSSU2V6Idx && addr_ptr == (*addresses)[eSSU2V4Idx]) continue; numAddresses++; } s.write ((char *)&numAddresses, sizeof (numAddresses)); for (size_t idx = 0; idx < addresses->size(); idx++) { auto addr_ptr = (*addresses)[idx]; if (!addr_ptr) continue; if (idx == eNTCP2V6Idx && addr_ptr == (*addresses)[eNTCP2V4Idx]) continue; if (idx == eSSU2V6Idx && addr_ptr == (*addresses)[eSSU2V4Idx]) continue; const Address& address = *addr_ptr; // calculate cost uint8_t cost = 0x7f; if (address.transportStyle == 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.date, sizeof (address.date)); std::stringstream properties; bool isPublished = address.published && !address.host.is_unspecified () && address.port; if (address.transportStyle == eTransportNTCP2) { 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); // caps 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 << ';'; } } 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) { // write introducers if any if (address.ssu && !address.ssu->introducers.empty()) { int i = 0; for (const auto& introducer: address.ssu->introducers) { if (!introducer.iTag) continue; if (introducer.iExp) // expiration is specified { WriteString ("iexp" + boost::lexical_cast(i), properties); properties << '='; WriteString (boost::lexical_cast(introducer.iExp), properties); properties << ';'; } i++; } i = 0; for (const auto& introducer: address.ssu->introducers) { if (!introducer.iTag) continue; WriteString ("ih" + boost::lexical_cast(i), properties); properties << '='; char value[64]; size_t l = ByteStreamToBase64 (introducer.iH, 32, value, 64); value[l] = 0; WriteString (value, properties); properties << ';'; i++; } i = 0; for (const auto& introducer: address.ssu->introducers) { if (!introducer.iTag) continue; WriteString ("itag" + boost::lexical_cast(i), properties); properties << '='; WriteString (boost::lexical_cast(introducer.iTag), properties); properties << ';'; i++; } } } if (address.transportStyle == eTransportSSU2) { // write mtu if (address.ssu && address.ssu->mtu) { WriteString ("mtu", properties); properties << '='; WriteString (boost::lexical_cast(address.ssu->mtu), properties); properties << ';'; } } if (isPublished && address.port) { WriteString ("port", properties); properties << '='; WriteString (boost::lexical_cast(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 << ';'; } uint16_t size = htobe16 (properties.str ().size ()); s.write ((char *)&size, sizeof (size)); s.write (properties.str ().c_str (), properties.str ().size ()); } // peers uint8_t numPeers = 0; s.write ((char *)&numPeers, sizeof (numPeers)); // properties std::stringstream properties; for (const auto& p : m_Properties) { WriteString (p.first, properties); properties << '='; WriteString (p.second, properties); properties << ';'; } uint16_t size = htobe16 (properties.str ().size ()); s.write ((char *)&size, sizeof (size)); s.write (properties.str ().c_str (), properties.str ().size ()); } void LocalRouterInfo::SetProperty (const std::string& key, const std::string& value) { m_Properties[key] = value; } void LocalRouterInfo::DeleteProperty (const std::string& key) { m_Properties.erase (key); } std::string LocalRouterInfo::GetProperty (const std::string& key) const { auto it = m_Properties.find (key); if (it != m_Properties.end ()) return it->second; return ""; } void LocalRouterInfo::UpdateFloodfillProperty (bool floodfill) { if (floodfill) { UpdateCaps (GetCaps () | i2p::data::RouterInfo::eFloodfill); SetFloodfill (); } else { UpdateCaps (GetCaps () & ~i2p::data::RouterInfo::eFloodfill); ResetFloodfill (); } } void LocalRouterInfo::WriteString (const std::string& str, std::ostream& s) const { uint8_t len = str.size (); s.write ((char *)&len, 1); s.write (str.c_str (), len); } std::shared_ptr LocalRouterInfo::NewBuffer () const { return std::make_shared (); } std::shared_ptr LocalRouterInfo::NewAddress () const { return std::make_shared
(); } RouterInfo::AddressesPtr LocalRouterInfo::NewAddresses () const { return RouterInfo::AddressesPtr(new RouterInfo::Addresses ()); } std::shared_ptr LocalRouterInfo::NewIdentity (const uint8_t * buf, size_t len) const { return std::make_shared (buf, len); } bool LocalRouterInfo::AddSSU2Introducer (const Introducer& introducer, bool v4) { auto addresses = GetAddresses (); if (!addresses) return false; auto addr = (*addresses)[v4 ? eSSU2V4Idx : eSSU2V6Idx]; if (addr) { for (auto& intro: addr->ssu->introducers) if (intro.iTag == introducer.iTag) return false; // already presented addr->ssu->introducers.push_back (introducer); SetReachableTransports (GetReachableTransports () | ((addr->IsV4 () ? eSSU2V4 : eSSU2V6))); return true; } return false; } bool LocalRouterInfo::RemoveSSU2Introducer (const IdentHash& h, bool v4) { auto addresses = GetAddresses (); if (!addresses) return false; auto addr = (*addresses)[v4 ? eSSU2V4Idx : eSSU2V6Idx]; if (addr) { for (auto it = addr->ssu->introducers.begin (); it != addr->ssu->introducers.end (); ++it) if (h == it->iH) { addr->ssu->introducers.erase (it); if (addr->ssu->introducers.empty ()) SetReachableTransports (GetReachableTransports () & ~(addr->IsV4 () ? eSSU2V4 : eSSU2V6)); return true; } } return false; } bool LocalRouterInfo::UpdateSSU2Introducer (const IdentHash& h, bool v4, uint32_t iTag, uint32_t iExp) { auto addresses = GetAddresses (); if (!addresses) return false; auto addr = (*addresses)[v4 ? eSSU2V4Idx : eSSU2V6Idx]; if (addr) { for (auto& it: addr->ssu->introducers) if (h == it.iH) { it.iTag = iTag; it.iExp = iExp; return true; } } return false; } } } i2pd-2.56.0/libi2pd/RouterInfo.h000066400000000000000000000365051475272067700162660ustar00rootroot00000000000000/* * 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 #ifndef __cpp_lib_atomic_shared_ptr #include #endif #include "Identity.h" #include "Profiling.h" #include "Family.h" namespace i2p { 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"; const char CAPS_FLAG_FLOODFILL = 'f'; const char CAPS_FLAG_HIDDEN = 'H'; const char CAPS_FLAG_REACHABLE = 'R'; const char CAPS_FLAG_UNREACHABLE = 'U'; /* 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 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 size_t MAX_RI_BUFFER_SIZE = 3072; // if RouterInfo exceeds 3K we consider it as malformed, might extend later const int HIGH_CONGESTION_INTERVAL = 15*60; // in seconds, 15 minutes const int INTRODUCER_UPDATE_INTERVAL = 20*60*1000; // in milliseconds, 20 minutes class RouterInfo: public RoutingDestination { public: enum SupportedTransportsIdx { eNTCP2V4Idx = 0, eNTCP2V6Idx, eSSU2V4Idx, eSSU2V6Idx, eNTCP2V6MeshIdx, eNumTransports }; #define TransportBit(tr) e##tr = (1 << e##tr##Idx) enum SupportedTransports { TransportBit(NTCP2V4), // 0x01 TransportBit(NTCP2V6), // 0x02 TransportBit(SSU2V4), // 0x04 TransportBit(SSU2V6), // 0x08 TransportBit(NTCP2V6Mesh), // 0x10 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 }; enum TransportStyle { eTransportUnknown = 0, eTransportNTCP2, eTransportSSU2 }; struct Introducer { Introducer (): iTag (0), iExp (0) { iH.Fill(0); }; IdentHash iH; uint32_t iTag; uint32_t iExp; }; struct SSUExt { int mtu; std::vector introducers; }; struct Address { TransportStyle transportStyle; boost::asio::ip::address host; Tag<32> s, i; // keys, i is first 16 bytes for NTCP2 and 32 bytes intro key for SSU int port; uint64_t date; uint8_t caps; bool published = false; 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 ()); } bool operator==(const Address& other) const { 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 ()); }; }; 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 (const std::string& fullPath); RouterInfo (const RouterInfo& ) = default; RouterInfo& operator=(const RouterInfo& ) = default; RouterInfo (std::shared_ptr&& buf, size_t len); RouterInfo (const uint8_t * buf, size_t len); virtual ~RouterInfo (); 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 (const std::string& key, const std::string& value) {}; virtual void ClearProperties () {}; AddressesPtr GetAddresses () const; // should be called for local RI only, otherwise must return shared_ptr std::shared_ptr GetNTCP2V4Address () const; std::shared_ptr GetNTCP2V6Address () const; 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; 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 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 IsHidden () const { return m_Caps & eHidden; }; bool IsHighBandwidth () const { return m_Caps & RouterInfo::eHighBandwidth; }; bool IsExtraBandwidth () const { return m_Caps & RouterInfo::eExtraBandwidth; }; bool IsEligibleFloodfill () const; bool IsDeclaredFloodfill () const { return m_Caps & RouterInfo::eFloodfill; }; bool IsPublished (bool v4) const; bool IsPublishedOn (CompatibleTransports transports) const; bool IsNAT2NATOnly (const RouterInfo& other) const; // only NAT-to-NAT connection is possible bool IsSSU2PeerTesting (bool v4) const; bool IsSSU2Introducer (bool v4) const; bool IsHighCongestion (bool highBandwidth) const; uint8_t GetCaps () const { return m_Caps; }; char GetBandwidthCap() const { return m_BandwidthCap; }; void SetCaps (uint8_t caps) { m_Caps = caps; }; Congestion GetCongestion () const { return m_Congestion; }; void SetUnreachable (bool unreachable) { m_IsUnreachable = unreachable; }; bool IsUnreachable () const { return m_IsUnreachable; }; void ExcludeReachableTransports (CompatibleTransports transports) { m_ReachableTransports &= ~transports; }; const uint8_t * GetBuffer () const { return m_Buffer ? m_Buffer->data () : nullptr; }; const uint8_t * LoadBuffer (const std::string& fullPath); // load if necessary size_t GetBufferLen () const { return m_Buffer ? m_Buffer->GetBufferLen () : 0; }; void DeleteBuffer () { m_Buffer = nullptr; m_IsBufferScheduledToDelete = false; }; std::shared_ptr GetSharedBuffer () const { return m_Buffer; }; std::shared_ptr CopyBuffer () const; void ScheduleBufferToDelete () { m_IsBufferScheduledToDelete = true; }; void CancelBufferToDelete () { m_IsBufferScheduledToDelete = false; }; bool IsBufferScheduledToDelete () const { return m_IsBufferScheduledToDelete; }; bool IsUpdated () const { return m_IsUpdated; }; void SetUpdated (bool updated) { m_IsUpdated = updated; }; bool SaveToFile (const std::string& fullPath); static bool SaveToFile (const std::string& fullPath, std::shared_ptr buf); std::shared_ptr GetProfile () const; void DropProfile () { m_Profile = nullptr; }; bool HasProfile () const { return (bool)m_Profile; }; bool Update (const uint8_t * buf, size_t len); 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; // implements RoutingDestination std::shared_ptr GetIdentity () const { return m_RouterIdentity; }; void Encrypt (const uint8_t * data, uint8_t * encrypted) 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); void ReadFromStream (std::istream& s); void ReadFromBuffer (bool verifySignature); size_t ReadString (char* str, size_t len, std::istream& s) const; void ExtractCaps (const char * value); uint8_t ExtractAddressCaps (const char * value) const; 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 (const std::string& key, const std::string& value) override; void DeleteProperty (const std::string& key); std::string GetProperty (const std::string& key) const; void ClearProperties () override { m_Properties.clear (); }; void UpdateFloodfillProperty (bool floodfill); bool AddSSU2Introducer (const Introducer& introducer, bool v4); bool RemoveSSU2Introducer (const IdentHash& h, bool v4); bool UpdateSSU2Introducer (const IdentHash& h, bool v4, uint32_t iTag, uint32_t iExp); private: void WriteToStream (std::ostream& s) const; void UpdateCapsProperty (); void WriteString (const std::string& str, std::ostream& s) const; std::shared_ptr NewBuffer () const override; std::shared_ptr
NewAddress () const override; RouterInfo::AddressesPtr NewAddresses () const override; std::shared_ptr NewIdentity (const uint8_t * buf, size_t len) const override; private: std::map m_Properties; }; } } #endif i2pd-2.56.0/libi2pd/SSU2.cpp000066400000000000000000001605711475272067700152620ustar00rootroot00000000000000/* * 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); m_IncomingTokens.emplace (ep, std::make_pair (token, uint32_t(ts + SSU2_TOKEN_EXPIRATION_TIMEOUT))); return token; } std::pair SSU2Server::NewIncomingToken (const boost::asio::ip::udp::endpoint& ep) { m_IncomingTokens.erase (ep); // drop previous uint64_t token; RAND_bytes ((uint8_t *)&token, 8); auto ret = std::make_pair (token, uint32_t(i2p::util::GetSecondsSinceEpoch () + SSU2_NEXT_TOKEN_EXPIRATION_TIMEOUT)); m_IncomingTokens.emplace (ep, ret); return ret; } 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; } } } i2pd-2.56.0/libi2pd/SSU2.h000066400000000000000000000260161475272067700147220ustar00rootroot00000000000000/* * 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 i2pd-2.56.0/libi2pd/SSU2OutOfSession.cpp000066400000000000000000000307301475272067700175740ustar00rootroot00000000000000/* * 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) { #if __cplusplus >= 202002L // C++20 m_SignedData.assign (signedData, signedData + signedDataLen); #else m_SignedData.resize (signedDataLen); memcpy (m_SignedData.data (), signedData, signedDataLen); #endif 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 uint32_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) { #if __cplusplus >= 202002L // C++20 m_RelayResponseBlock.assign (relayResponseBlock, relayResponseBlock + relayResponseBlockLen); #else m_RelayResponseBlock.resize (relayResponseBlockLen); memcpy (m_RelayResponseBlock.data (), relayResponseBlock, relayResponseBlockLen); #endif 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); } } } i2pd-2.56.0/libi2pd/SSU2OutOfSession.h000066400000000000000000000054271475272067700172460ustar00rootroot00000000000000/* * 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 i2pd-2.56.0/libi2pd/SSU2Session.cpp000066400000000000000000003415611475272067700166260ustar00rootroot00000000000000/* * 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 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 ())) { LogPrint (eLogInfo, "SSU2: Remote endpoint update ", m_RemoteEndpoint, "->", from); m_RemoteEndpoint = from; SendPathChallenge (); } 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) { i2p::data::IdentHash hash; SHA256 (buf + offset, size, hash); if (hash == *m_PathChallenge) 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 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 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 s; s.Insert ((const uint8_t *)"RelayRequestData", 16); // prologue s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash s.Insert (i2p::context.GetIdentHash (), 32); // chash s.Insert (buf + 33, 14); // nonce, relay tag, timestamp, ver, asz uint8_t asz = buf[46]; s.Insert (buf + 47, asz); // Alice Port, Alice IP if (s.Verify (r->GetIdentity (), buf + 47 + asz)) { // 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) { 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 ()) { if (it->second.first && it->second.first->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 = it->second.first->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 (); it->second.first->m_SentPackets.emplace (packetNum, packet); } } else { // we are Alice, message from Bob if (!buf[1]) // status code accepted? { // verify signature uint8_t csz = buf[11]; SignedData s; s.Insert ((const uint8_t *)"RelayAgreementOK", 16); // prologue s.Insert (GetRemoteIdentity ()->GetIdentHash (), 32); // bhash s.Insert (buf + 2, 10 + csz); // nonce, timestamp, ver, csz and Charlie's endpoint if (s.Verify (it->second.first->GetRemoteIdentity (), buf + 12 + csz)) { if (it->second.first->m_State == eSSU2SessionStateIntroduced) // HolePunch not received yet { // update Charlie's endpoint if (ExtractEndpoint (buf + 12, csz, it->second.first->m_RemoteEndpoint)) { // update token uint64_t token; memcpy (&token, buf + len - 8, 8); m_Server.UpdateOutgoingToken (it->second.first->m_RemoteEndpoint, token, i2p::util::GetSecondsSinceEpoch () + SSU2_TOKEN_EXPIRATION_TIMEOUT); // connect to Charlie, HolePunch will be ignored it->second.first->ConnectAfterIntroduction (); } else LogPrint (eLogWarning, "SSU2: RelayResponse can't extract endpoint"); } } else { LogPrint (eLogWarning, "SSU2: RelayResponse signature verification failed"); it->second.first->Done (); } } else { LogPrint (eLogInfo, "SSU2: RelayResponse status code=", (int)buf[1], " nonce=", bufbe32toh (buf + 2)); it->second.first->Done (); } } m_RelaySessions.erase (it); } 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 uint8_t asz = buf[offset + 9]; std::vector newSignedData (asz + 10 + i2p::context.GetIdentity ()->GetSignatureLen ()); memcpy (newSignedData.data (), buf + offset, asz + 10); SignedData 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) { uint8_t asz = buf[offset + 9]; SignedData 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 () { auto addr = FindLocalAddress (); if (addr && addr->ssu) { int mtu = addr->ssu->mtu; if (!mtu && addr->IsV4 ()) mtu = SSU2_MAX_PACKET_SIZE; if (m_Address && m_Address->ssu && (!mtu || m_Address->ssu->mtu < mtu)) mtu = m_Address->ssu->mtu; if (mtu) { 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 ()() & 0x0F; // 0 - 15 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 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 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) { if (len > m_MaxPayloadSize - 3) { LogPrint (eLogWarning, "SSU2: Incorrect data size for path response ", len); return; } uint8_t payload[SSU2_MAX_PACKET_SIZE]; payload[0] = eSSU2BlkPathResponse; htobe16buf (payload + 1, len); memcpy (payload + 3, data, len); size_t payloadSize = len + 3; if (payloadSize < m_MaxPayloadSize) payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize, payloadSize < 8 ? 8 : 0); SendData (payload, payloadSize); } void SSU2Session::SendPathChallenge () { uint8_t payload[SSU2_MAX_PACKET_SIZE]; payload[0] = eSSU2BlkPathChallenge; size_t len = m_Server.GetRng ()() % (m_MaxPayloadSize - 3); htobe16buf (payload + 1, len); if (len > 0) { RAND_bytes (payload + 3, len); i2p::data::IdentHash * hash = new i2p::data::IdentHash (); SHA256 (payload + 3, len, *hash); m_PathChallenge.reset (hash); } len += 3; if (len < m_MaxPayloadSize) len += CreatePaddingBlock (payload + len, m_MaxPayloadSize - len, len < 8 ? 8 : 0); SendData (payload, len); } 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; } } } i2pd-2.56.0/libi2pd/SSU2Session.h000066400000000000000000000412621475272067700162660ustar00rootroot00000000000000/* * 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 (); 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 (); 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 i2pd-2.56.0/libi2pd/Signature.cpp000066400000000000000000000106671475272067700164670ustar00rootroot00000000000000/* * 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 "Log.h" #include "Signature.h" namespace i2p { namespace crypto { #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) { memcpy (m_PublicKeyEncoded, signingKey, EDDSA25519_PUBLIC_KEY_LENGTH); BN_CTX * ctx = BN_CTX_new (); m_PublicKey = GetEd25519 ()->DecodePublicKey (m_PublicKeyEncoded, ctx); BN_CTX_free (ctx); } bool EDDSA25519Verifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const { uint8_t digest[64]; SHA512_CTX ctx; SHA512_Init (&ctx); SHA512_Update (&ctx, signature, EDDSA25519_SIGNATURE_LENGTH/2); // R SHA512_Update (&ctx, m_PublicKeyEncoded, EDDSA25519_PUBLIC_KEY_LENGTH); // public key SHA512_Update (&ctx, buf, len); // data SHA512_Final (digest, &ctx); return GetEd25519 ()->Verify (m_PublicKey, digest, signature); } #endif EDDSA25519SignerCompat::EDDSA25519SignerCompat (const uint8_t * signingPrivateKey, const uint8_t * signingPublicKey) { // expand key Ed25519::ExpandPrivateKey (signingPrivateKey, m_ExpandedPrivateKey); // generate and encode public key BN_CTX * ctx = BN_CTX_new (); auto publicKey = GetEd25519 ()->GeneratePublicKey (m_ExpandedPrivateKey, ctx); GetEd25519 ()->EncodePublicKey (publicKey, m_PublicKeyEncoded, ctx); if (signingPublicKey && memcmp (m_PublicKeyEncoded, signingPublicKey, EDDSA25519_PUBLIC_KEY_LENGTH)) { // keys don't match, it means older key with 0x1F LogPrint (eLogWarning, "Older EdDSA key detected"); m_ExpandedPrivateKey[EDDSA25519_PRIVATE_KEY_LENGTH - 1] &= 0xDF; // drop third bit publicKey = GetEd25519 ()->GeneratePublicKey (m_ExpandedPrivateKey, ctx); GetEd25519 ()->EncodePublicKey (publicKey, m_PublicKeyEncoded, ctx); } BN_CTX_free (ctx); } EDDSA25519SignerCompat::~EDDSA25519SignerCompat () { } void EDDSA25519SignerCompat::Sign (const uint8_t * buf, int len, uint8_t * signature) const { GetEd25519 ()->Sign (m_ExpandedPrivateKey, m_PublicKeyEncoded, buf, len, signature); } #if OPENSSL_EDDSA 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 } } i2pd-2.56.0/libi2pd/Signature.h000066400000000000000000000355501475272067700161320ustar00rootroot00000000000000/* * 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 SIGNATURE_H__ #define SIGNATURE_H__ #include #include #include #include #include #include #include "Crypto.h" #include "Ed25519.h" #include "Gost.h" namespace i2p { namespace crypto { class Verifier { public: virtual ~Verifier () {}; virtual bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const = 0; 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 { public: virtual ~Signer () {}; virtual void Sign (const uint8_t * buf, int len, uint8_t * signature) const = 0; }; 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 () { m_PublicKey = CreateDSA (); } void SetPublicKey (const uint8_t * signingKey) { DSA_set0_key (m_PublicKey, BN_bin2bn (signingKey, DSA_PUBLIC_KEY_LENGTH, NULL), NULL); } ~DSAVerifier () { DSA_free (m_PublicKey); } bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const { // calculate SHA1 digest uint8_t digest[20]; SHA1 (buf, len, digest); // signature DSA_SIG * sig = DSA_SIG_new(); DSA_SIG_set0 (sig, BN_bin2bn (signature, DSA_SIGNATURE_LENGTH/2, NULL), BN_bin2bn (signature + DSA_SIGNATURE_LENGTH/2, DSA_SIGNATURE_LENGTH/2, NULL)); // DSA verification int ret = DSA_do_verify (digest, 20, sig, m_PublicKey); DSA_SIG_free(sig); return ret; } size_t GetPublicKeyLen () const { return DSA_PUBLIC_KEY_LENGTH; }; size_t GetSignatureLen () const { return DSA_SIGNATURE_LENGTH; }; private: DSA * m_PublicKey; }; class DSASigner: public Signer { public: DSASigner (const uint8_t * signingPrivateKey, const uint8_t * signingPublicKey) // openssl 1.1 always requires DSA public key even for signing { m_PrivateKey = CreateDSA (); DSA_set0_key (m_PrivateKey, BN_bin2bn (signingPublicKey, DSA_PUBLIC_KEY_LENGTH, NULL), BN_bin2bn (signingPrivateKey, DSA_PRIVATE_KEY_LENGTH, NULL)); } ~DSASigner () { 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: DSA * m_PrivateKey; }; 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); } struct SHA256Hash { static void CalculateHash (const uint8_t * buf, size_t len, uint8_t * digest) { SHA256 (buf, len, digest); } enum { hashLen = 32 }; }; struct SHA384Hash { static void CalculateHash (const uint8_t * buf, size_t len, uint8_t * digest) { SHA384 (buf, len, digest); } enum { hashLen = 48 }; }; struct SHA512Hash { static void CalculateHash (const uint8_t * buf, size_t len, uint8_t * digest) { SHA512 (buf, len, digest); } enum { hashLen = 64 }; }; // EcDSA template class ECDSAVerifier: public Verifier { public: ECDSAVerifier () { 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); BN_free (x); BN_free (y); } ~ECDSAVerifier () { EC_KEY_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); ECDSA_SIG * sig = ECDSA_SIG_new(); auto r = BN_bin2bn (signature, GetSignatureLen ()/2, NULL); auto s = BN_bin2bn (signature + GetSignatureLen ()/2, GetSignatureLen ()/2, NULL); ECDSA_SIG_set0(sig, r, s); // ECDSA verification int ret = ECDSA_do_verify (digest, Hash::hashLen, sig, m_PublicKey); ECDSA_SIG_free(sig); return ret; } size_t GetPublicKeyLen () const { return keyLen; }; size_t GetSignatureLen () const { return keyLen; }; // signature length = key length private: EC_KEY * m_PublicKey; }; template class ECDSASigner: public Signer { public: ECDSASigner (const uint8_t * signingPrivateKey) { m_PrivateKey = EC_KEY_new_by_curve_name (curve); EC_KEY_set_private_key (m_PrivateKey, BN_bin2bn (signingPrivateKey, keyLen/2, NULL)); } ~ECDSASigner () { EC_KEY_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); ECDSA_SIG * sig = ECDSA_do_sign (digest, Hash::hashLen, m_PrivateKey); const BIGNUM * r, * s; ECDSA_SIG_get0 (sig, &r, &s); // signatureLen = keyLen bn2buf (r, signature, keyLen/2); bn2buf (s, signature + keyLen/2, keyLen/2); ECDSA_SIG_free(sig); } private: EC_KEY * m_PrivateKey; }; inline void CreateECDSARandomKeys (int curve, size_t keyLen, uint8_t * signingPrivateKey, uint8_t * signingPublicKey) { EC_KEY * signingKey = EC_KEY_new_by_curve_name (curve); EC_KEY_generate_key (signingKey); bn2buf (EC_KEY_get0_private_key (signingKey), signingPrivateKey, keyLen/2); BIGNUM * x = BN_new(), * y = BN_new(); EC_POINT_get_affine_coordinates_GFp (EC_KEY_get0_group(signingKey), EC_KEY_get0_public_key (signingKey), x, y, NULL); bn2buf (x, signingPublicKey, keyLen/2); bn2buf (y, signingPublicKey + keyLen/2, keyLen/2); BN_free (x); BN_free (y); EC_KEY_free (signingKey); } // ECDSA_SHA256_P256 const size_t ECDSAP256_KEY_LENGTH = 64; typedef ECDSAVerifier ECDSAP256Verifier; typedef ECDSASigner ECDSAP256Signer; inline void CreateECDSAP256RandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey) { CreateECDSARandomKeys (NID_X9_62_prime256v1, ECDSAP256_KEY_LENGTH, signingPrivateKey, signingPublicKey); } // ECDSA_SHA384_P384 const size_t ECDSAP384_KEY_LENGTH = 96; typedef ECDSAVerifier ECDSAP384Verifier; typedef ECDSASigner ECDSAP384Signer; inline void CreateECDSAP384RandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey) { CreateECDSARandomKeys (NID_secp384r1, ECDSAP384_KEY_LENGTH, signingPrivateKey, signingPublicKey); } // ECDSA_SHA512_P521 const size_t ECDSAP521_KEY_LENGTH = 132; typedef ECDSAVerifier ECDSAP521Verifier; typedef ECDSASigner ECDSAP521Signer; inline void CreateECDSAP521RandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey) { CreateECDSARandomKeys (NID_secp521r1, ECDSAP521_KEY_LENGTH, signingPrivateKey, signingPublicKey); } // EdDSA class EDDSA25519Verifier: public Verifier { public: EDDSA25519Verifier (); void SetPublicKey (const uint8_t * signingKey); ~EDDSA25519Verifier (); bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const; size_t GetPublicKeyLen () const { return EDDSA25519_PUBLIC_KEY_LENGTH; }; size_t GetSignatureLen () const { return EDDSA25519_SIGNATURE_LENGTH; }; private: #if OPENSSL_EDDSA EVP_PKEY * m_Pkey; #else EDDSAPoint m_PublicKey; uint8_t m_PublicKeyEncoded[EDDSA25519_PUBLIC_KEY_LENGTH]; #endif }; class EDDSA25519SignerCompat: public Signer { public: EDDSA25519SignerCompat (const uint8_t * signingPrivateKey, const uint8_t * signingPublicKey = nullptr); // we pass signingPublicKey to check if it matches private key ~EDDSA25519SignerCompat (); void Sign (const uint8_t * buf, int len, uint8_t * signature) const; const uint8_t * GetPublicKey () const { return m_PublicKeyEncoded; }; // for keys creation private: uint8_t m_ExpandedPrivateKey[64]; uint8_t m_PublicKeyEncoded[EDDSA25519_PUBLIC_KEY_LENGTH]; }; #if OPENSSL_EDDSA 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; private: EVP_PKEY * m_Pkey; EDDSA25519SignerCompat * m_Fallback; }; #else typedef EDDSA25519SignerCompat EDDSA25519Signer; #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 } // ГОСТ Р 34.11 struct GOSTR3411_256_Hash { static void CalculateHash (const uint8_t * buf, size_t len, uint8_t * digest) { GOSTR3411_2012_256 (buf, len, digest); } enum { hashLen = 32 }; }; struct GOSTR3411_512_Hash { static void CalculateHash (const uint8_t * buf, size_t len, uint8_t * digest) { GOSTR3411_2012_512 (buf, len, digest); } enum { hashLen = 64 }; }; // ГОСТ Р 34.10 const size_t GOSTR3410_256_PUBLIC_KEY_LENGTH = 64; const size_t GOSTR3410_512_PUBLIC_KEY_LENGTH = 128; template class GOSTR3410Verifier: public Verifier { public: enum { keyLen = Hash::hashLen }; GOSTR3410Verifier (GOSTR3410ParamSet paramSet): m_ParamSet (paramSet), m_PublicKey (nullptr) { } void SetPublicKey (const uint8_t * signingKey) { BIGNUM * x = BN_bin2bn (signingKey, GetPublicKeyLen ()/2, NULL); BIGNUM * y = BN_bin2bn (signingKey + GetPublicKeyLen ()/2, GetPublicKeyLen ()/2, NULL); m_PublicKey = GetGOSTR3410Curve (m_ParamSet)->CreatePoint (x, y); BN_free (x); BN_free (y); } ~GOSTR3410Verifier () { if (m_PublicKey) EC_POINT_free (m_PublicKey); } bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const { uint8_t digest[Hash::hashLen]; Hash::CalculateHash (buf, len, digest); BIGNUM * d = BN_bin2bn (digest, Hash::hashLen, nullptr); BIGNUM * r = BN_bin2bn (signature, GetSignatureLen ()/2, NULL); BIGNUM * s = BN_bin2bn (signature + GetSignatureLen ()/2, GetSignatureLen ()/2, NULL); bool ret = GetGOSTR3410Curve (m_ParamSet)->Verify (m_PublicKey, d, r, s); BN_free (d); BN_free (r); BN_free (s); return ret; } size_t GetPublicKeyLen () const { return keyLen*2; } size_t GetSignatureLen () const { return keyLen*2; } private: GOSTR3410ParamSet m_ParamSet; EC_POINT * m_PublicKey; }; template class GOSTR3410Signer: public Signer { public: enum { keyLen = Hash::hashLen }; GOSTR3410Signer (GOSTR3410ParamSet paramSet, const uint8_t * signingPrivateKey): m_ParamSet (paramSet) { m_PrivateKey = BN_bin2bn (signingPrivateKey, keyLen, nullptr); } ~GOSTR3410Signer () { BN_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); BIGNUM * d = BN_bin2bn (digest, Hash::hashLen, nullptr); BIGNUM * r = BN_new (), * s = BN_new (); GetGOSTR3410Curve (m_ParamSet)->Sign (m_PrivateKey, d, r, s); bn2buf (r, signature, keyLen); bn2buf (s, signature + keyLen, keyLen); BN_free (d); BN_free (r); BN_free (s); } private: GOSTR3410ParamSet m_ParamSet; BIGNUM * m_PrivateKey; }; inline void CreateGOSTR3410RandomKeys (GOSTR3410ParamSet paramSet, uint8_t * signingPrivateKey, uint8_t * signingPublicKey) { const auto& curve = GetGOSTR3410Curve (paramSet); auto keyLen = curve->GetKeyLen (); RAND_bytes (signingPrivateKey, keyLen); BIGNUM * priv = BN_bin2bn (signingPrivateKey, keyLen, nullptr); auto pub = curve->MulP (priv); BN_free (priv); BIGNUM * x = BN_new (), * y = BN_new (); curve->GetXY (pub, x, y); EC_POINT_free (pub); bn2buf (x, signingPublicKey, keyLen); bn2buf (y, signingPublicKey + keyLen, keyLen); BN_free (x); BN_free (y); } typedef GOSTR3410Verifier GOSTR3410_256_Verifier; 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); } } } #endif i2pd-2.56.0/libi2pd/Siphash.h000066400000000000000000000054701475272067700155660ustar00rootroot00000000000000/** * 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 i2pd-2.56.0/libi2pd/Socks5.h000066400000000000000000000167171475272067700153440ustar00rootroot00000000000000/* * 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 i2pd-2.56.0/libi2pd/Streaming.cpp000066400000000000000000002046621475272067700164570ustar00rootroot00000000000000/* * 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 "Crypto.h" #include "Log.h" #include "RouterInfo.h" #include "RouterContext.h" #include "Tunnel.h" #include "Timestamp.h" #include "Destination.h" #include "Streaming.h" namespace i2p { namespace stream { void SendBufferQueue::Add (std::shared_ptr buf) { if (buf) { m_Buffers.push_back (buf); m_Size += buf->len; } } size_t SendBufferQueue::Get (uint8_t * buf, size_t len) { size_t offset = 0; while (!m_Buffers.empty () && offset < len) { auto nextBuffer = m_Buffers.front (); auto rem = nextBuffer->GetRemainingSize (); if (offset + rem <= len) { // whole buffer memcpy (buf + offset, nextBuffer->GetRemaningBuffer (), rem); offset += rem; m_Buffers.pop_front (); // delete it } else { // partially rem = len - offset; memcpy (buf + offset, nextBuffer->GetRemaningBuffer (), rem); nextBuffer->offset += rem; offset = len; // break } } m_Size -= offset; return offset; } void SendBufferQueue::CleanUp () { if (!m_Buffers.empty ()) { for (auto it: m_Buffers) it->Cancel (); m_Buffers.clear (); m_Size = 0; } } Stream::Stream (boost::asio::io_context& service, StreamingDestination& local, std::shared_ptr remote, int port): m_Service (service), m_SendStreamID (0), m_SequenceNumber (0), m_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_LastACKSendTime (0), m_PacketACKInterval (1), m_PacketACKIntervalRem (0), // for limit inbound speed m_NumResendAttempts (0), m_NumPacketsToSend (0), m_MTU (STREAMING_MTU) { RAND_bytes ((uint8_t *)&m_RecvStreamID, 4); m_RemoteIdentity = remote->GetIdentity (); auto outboundSpeed = local.GetOwner ()->GetStreamingOutboundSpeed (); if (outboundSpeed) m_MinPacingTime = (1000000LL*STREAMING_MTU)/outboundSpeed; auto inboundSpeed = local.GetOwner ()->GetStreamingInboundSpeed (); // for limit inbound speed if (inboundSpeed) m_PacketACKInterval = (1000000LL*STREAMING_MTU)/inboundSpeed; } Stream::Stream (boost::asio::io_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_LastACKSendTime (0), m_PacketACKInterval (1), m_PacketACKIntervalRem (0), // for limit inbound speed m_NumResendAttempts (0), m_NumPacketsToSend (0), m_MTU (STREAMING_MTU) { RAND_bytes ((uint8_t *)&m_RecvStreamID, 4); auto outboundSpeed = local.GetOwner ()->GetStreamingOutboundSpeed (); if (outboundSpeed) m_MinPacingTime = (1000000LL*STREAMING_MTU)/outboundSpeed; auto inboundSpeed = local.GetOwner ()->GetStreamingInboundSpeed (); // for limit inbound speed if (inboundSpeed) m_PacketACKInterval = (1000000LL*STREAMING_MTU)/inboundSpeed; } Stream::~Stream () { CleanUp (); LogPrint (eLogDebug, "Streaming: Stream deleted"); } void Stream::Terminate (bool deleteFromDestination) // should be called from StreamingDestination::Stop only { 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 ()); } void Stream::CleanUp () { m_SendBuffer.CleanUp (); while (!m_ReceiveQueue.empty ()) { auto packet = m_ReceiveQueue.front (); m_ReceiveQueue.pop (); m_LocalDestination.DeletePacket (packet); } m_NACKedPackets.clear (); for (auto it: m_SentPackets) m_LocalDestination.DeletePacket (it); m_SentPackets.clear (); for (auto it: m_SavedPackets) m_LocalDestination.DeletePacket (it); m_SavedPackets.clear (); } 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) { uint16_t flags = packet->GetFlags (); if (flags) // plain ack with options ProcessOptions (flags, packet); else // plain ack { LogPrint (eLogDebug, "Streaming: Plain ACK received"); if (m_IsImmediateAckRequested) { auto ts = i2p::util::GetMillisecondsSinceEpoch (); if (m_IsFirstRttSample) { m_RTT = ts - m_LastSendTime; m_IsFirstRttSample = false; } else m_RTT = (m_RTT + (ts - m_LastSendTime)) / 2; m_IsImmediateAckRequested = false; } } m_LocalDestination.DeletePacket (packet); return; } LogPrint (eLogDebug, "Streaming: Received seqn=", receivedSeqn, " on sSID=", m_SendStreamID); if (receivedSeqn == m_LastReceivedSequenceNumber + 1) { // 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 ();) { if ((*it)->GetSeqn () == (uint32_t)(m_LastReceivedSequenceNumber + 1)) { Packet * savedPacket = *it; m_SavedPackets.erase (it++); ProcessPacket (savedPacket); if (m_Status == eStreamStatusTerminated) return; } else break; } // schedule ack for last message if (m_Status == eStreamStatusOpen) { if (!m_IsAckSendScheduled) { auto ackTimeout = m_RTT/10; if (ackTimeout > m_AckDelay) ackTimeout = m_AckDelay; ScheduleAck (ackTimeout); } } else if (packet->IsSYN ()) // we have to send SYN back to incoming connection SendBuffer (); // also sets m_IsOpen } else { if (receivedSeqn <= m_LastReceivedSequenceNumber) { // 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; m_LocalDestination.DeletePacket (packet); // packet dropped if (!m_IsAckSendScheduled) { SendQuickAck (); // resend ack for previous message again auto ackTimeout = m_RTT/10; if (ackTimeout > m_AckDelay) ackTimeout = m_AckDelay; ScheduleAck (ackTimeout); } } else { LogPrint (eLogWarning, "Streaming: Missing messages on sSID=", m_SendStreamID, ": from ", m_LastReceivedSequenceNumber + 1, " to ", receivedSeqn - 1); // save message and wait for missing message again SavePacket (packet); if (m_LastReceivedSequenceNumber >= 0) { if (!m_IsAckSendScheduled) { // send NACKs for missing messages SendQuickAck (); auto ackTimeout = m_RTT/10; if (ackTimeout > m_AckDelay) ackTimeout = m_AckDelay; ScheduleAck (ackTimeout); } } else // wait for SYN ScheduleAck (SYN_TIMEOUT); } } } void Stream::SavePacket (Packet * packet) { if (!m_SavedPackets.insert (packet).second) m_LocalDestination.DeletePacket (packet); } void Stream::ProcessPacket (Packet * packet) { uint32_t receivedSeqn = packet->GetSeqn (); uint16_t flags = packet->GetFlags (); LogPrint (eLogDebug, "Streaming: Process seqn=", receivedSeqn, ", flags=", flags); if (!ProcessOptions (flags, packet)) { m_LocalDestination.DeletePacket (packet); Terminate (); return; } packet->offset = packet->GetPayload () - packet->buf; if (packet->GetLength () > 0) { m_ReceiveQueue.push (packet); m_ReceiveTimer.cancel (); } else m_LocalDestination.DeletePacket (packet); m_LastReceivedSequenceNumber = receivedSeqn; if (flags & PACKET_FLAG_RESET) { LogPrint (eLogDebug, "Streaming: closing stream sSID=", m_SendStreamID, ", rSID=", m_RecvStreamID, ": reset flag received in packet #", receivedSeqn); m_Status = eStreamStatusReset; Close (); } else if (flags & PACKET_FLAG_CLOSE) { if (m_Status != eStreamStatusClosed) SendClose (); m_Status = eStreamStatusClosed; Terminate (); } } 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) { uint8_t signature[256]; auto signatureLen = m_TransientVerifier ? m_TransientVerifier->GetSignatureLen () : m_RemoteIdentity->GetSignatureLen (); if(signatureLen <= sizeof(signature)) { memcpy (signature, optionData, signatureLen); memset (const_cast(optionData), 0, signatureLen); bool verified = m_TransientVerifier ? m_TransientVerifier->Verify (packet->GetBuffer (), packet->GetLength (), signature) : m_RemoteIdentity->Verify (packet->GetBuffer (), packet->GetLength (), signature); if (!verified) { LogPrint (eLogError, "Streaming: Signature verification failed, sSID=", m_SendStreamID, ", rSID=", m_RecvStreamID); Close (); flags |= PACKET_FLAG_CLOSE; } memcpy (const_cast(optionData), signature, signatureLen); optionData += signatureLen; } else { LogPrint (eLogError, "Streaming: Signature too big, ", signatureLen, " bytes"); 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 ();) { auto seqn = (*it)->GetSeqn (); if (seqn <= ackThrough) { if (nackCount > 0) { bool nacked = false; for (int i = 0; i < nackCount; i++) if (seqn == packet->GetNACK (i)) { m_NACKedPackets.insert (*it); m_IsNAcked = true; nacked = true; break; } if (nacked) { LogPrint (eLogDebug, "Streaming: Packet ", seqn, " NACK"); ++it; continue; } } 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) { m_IsFirstRttSample = true; rttSample = rtt < 0 ? 1 : rtt; } else if (!sentPacket->resent && seqn > m_TunnelsChangeSequenceNumber && rtt >= 0) rttSample = std::min (rttSample, (int)rtt); LogPrint (eLogDebug, "Streaming: Packet ", seqn, " acknowledged rtt=", rtt, " sentTime=", sentPacket->sendTime); m_SentPackets.erase (it++); m_LocalDestination.DeletePacket (sentPacket); acknowledged = true; if (m_WindowIncCounter < MAX_WINDOW_SIZE && !m_IsFirstACK && !m_IsWinDropped) incCounter++; } else break; } if (m_LastACKRecieveTime) { uint64_t interval = ts - m_LastACKRecieveTime; if (m_ACKRecieveInterval) m_ACKRecieveInterval = (m_ACKRecieveInterval + interval) / 2; else m_ACKRecieveInterval = interval; } m_LastACKRecieveTime = ts; if (rttSample != INT_MAX) { if (m_IsFirstRttSample && !m_IsFirstACK) { m_RTT = rttSample; m_SlowRTT = rttSample; m_SlowRTT2 = rttSample; m_PrevRTTSample = rttSample; m_Jitter = rttSample / 10; // 10% m_Jitter += 15; // for low-latency connections m_IsFirstRttSample = false; } else m_RTT = (m_PrevRTTSample + rttSample) / 2; if (!m_IsWinDropped) { m_SlowRTT = SLOWRTT_EWMA_ALPHA * m_RTT + (1.0 - SLOWRTT_EWMA_ALPHA) * m_SlowRTT; m_SlowRTT2 = RTT_EWMA_ALPHA * m_RTT + (1.0 - RTT_EWMA_ALPHA) * m_SlowRTT2; // calculate jitter double jitter = 0; if (rttSample > m_PrevRTTSample) jitter = rttSample - m_PrevRTTSample; else if (rttSample < m_PrevRTTSample) jitter = m_PrevRTTSample - rttSample; else jitter = rttSample / 10; // 10% jitter += 15; // for low-latency connections m_Jitter = (0.05 * jitter) + (1.0 - 0.05) * m_Jitter; } if (rttSample > m_SlowRTT) { incCounter = 0; m_DoubleWinIncCounter = 1; } else if (rttSample < m_SlowRTT) { if (m_DoubleWinIncCounter) { incCounter = incCounter * 2; m_DoubleWinIncCounter = 0; } } m_WindowIncCounter = m_WindowIncCounter + incCounter; // // delay-based CC if ((m_SlowRTT2 > m_SlowRTT + m_Jitter && rttSample > m_SlowRTT2 && rttSample > m_PrevRTTSample) && !m_IsWinDropped && !m_IsClientChoked) // Drop window if RTT grows too fast, late detection { LogPrint (eLogDebug, "Streaming: Congestion detected, reduce window size"); ProcessWindowDrop (); } UpdatePacingTime (); m_PrevRTTSample = rttSample; bool wasInitial = m_RTO == INITIAL_RTO; m_RTO = std::max (MIN_RTO, (int)(m_RTT * 1.3 + m_Jitter + m_ACKRecieveInterval)); // TODO: implement it better if (wasInitial) ScheduleResend (); } if (m_IsClientChoked && ackThrough > m_DropWindowDelaySequenceNumber) { m_IsClientChoked = false; } if (m_IsWinDropped && ackThrough > m_DropWindowDelaySequenceNumber) { m_IsFirstRttSample = true; m_IsWinDropped = false; } if (m_WindowDropTargetSize && int(m_SentPackets.size ()) <= m_WindowDropTargetSize) { m_WindowSize = m_WindowDropTargetSize; m_WindowDropTargetSize = 0; } if (acknowledged || m_IsNAcked) { ScheduleResend (); } if (m_SendBuffer.IsEmpty () && m_SentPackets.size () > 0) // tail loss { m_IsResendNeeded = true; m_RTO = std::max (MIN_RTO, (int)(m_RTT * 1.5 + m_Jitter + m_ACKRecieveInterval)); // to prevent spurious retransmit } if (m_SentPackets.empty () && m_SendBuffer.IsEmpty ()) { 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) Terminate (); else if (m_Status == eStreamStatusClosing) Close (); // check is all outgoing messages have been sent and we can send close } size_t Stream::Receive (uint8_t * buf, size_t len, int timeout) { if (!len) return 0; size_t ret = 0; volatile bool done = false; std::condition_variable newDataReceived; std::mutex newDataReceivedMutex; AsyncReceive (boost::asio::buffer (buf, len), [&ret, &done, &newDataReceived, &newDataReceivedMutex](const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (ecode == boost::asio::error::timed_out) ret = 0; else ret = bytes_transferred; std::unique_lock l(newDataReceivedMutex); newDataReceived.notify_all (); done = true; }, timeout); if (!done) { std::unique_lock l(newDataReceivedMutex); if (!done && newDataReceived.wait_for (l, std::chrono::seconds (timeout)) == std::cv_status::timeout) ret = 0; } if (!done) { // make sure that AsycReceive complete auto s = shared_from_this(); boost::asio::post (m_Service, [s]() { s->m_ReceiveTimer.cancel (); }); int i = 0; while (!done && i < 100) // 1 sec { std::this_thread::sleep_for (std::chrono::milliseconds(10)); i++; } } return ret; } size_t Stream::Send (const uint8_t * buf, size_t len) { AsyncSend (buf, len, nullptr); return len; } 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); else if (handler) handler(boost::system::error_code ()); auto s = shared_from_this (); boost::asio::post (m_Service, [s, buffer]() { if (buffer) s->m_SendBuffer.Add (buffer); s->SendBuffer (); }); } void Stream::SendBuffer () { if (m_RemoteLeaseSet) // don't scheudle send for first SYN for incoming stream ScheduleSend (); auto ts = i2p::util::GetMillisecondsSinceEpoch (); int numMsgs = m_WindowSize - m_SentPackets.size (); if (numMsgs <= 0 || !m_IsSendTime) // window is full { m_LastSendTime = ts; return; } else if (numMsgs > m_NumPacketsToSend) numMsgs = m_NumPacketsToSend; bool isNoAck = m_LastReceivedSequenceNumber < 0; // first packet std::vector packets; while ((m_Status == eStreamStatusNew) || (IsEstablished () && !m_SendBuffer.IsEmpty () && numMsgs > 0)) { 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) { // first SYN packet packet[size] = 8; size++; // NACK count memcpy (packet + size, m_RemoteIdentity->GetIdentHash (), 32); size += 32; } else { packet[size] = 0; size++; // NACK count } packet[size] = m_RTO/1000; size++; // resend delay if (m_Status == eStreamStatusNew) { // initial packet m_Status = eStreamStatusOpen; if (!m_RemoteLeaseSet) m_RemoteLeaseSet = m_LocalDestination.GetOwner ()->FindLeaseSet (m_RemoteIdentity->GetIdentHash ());; if (m_RemoteLeaseSet) { m_RoutingSession = m_LocalDestination.GetOwner ()->GetRoutingSession (m_RemoteLeaseSet, true, !m_IsIncoming); m_MTU = (m_RoutingSession && m_RoutingSession->IsRatchets ()) ? STREAMING_MTU_RATCHETS : STREAMING_MTU; } uint16_t flags = PACKET_FLAG_SYNCHRONIZE | PACKET_FLAG_FROM_INCLUDED | PACKET_FLAG_SIGNATURE_INCLUDED | PACKET_FLAG_MAX_PACKET_SIZE_INCLUDED; if (isNoAck) flags |= PACKET_FLAG_NO_ACK; bool isOfflineSignature = m_LocalDestination.GetOwner ()->GetPrivateKeys ().IsOfflineSignature (); if (isOfflineSignature) flags |= PACKET_FLAG_OFFLINE_SIGNATURE; htobe16buf (packet + size, flags); size += 2; // flags size_t identityLen = m_LocalDestination.GetOwner ()->GetIdentity ()->GetFullLen (); size_t signatureLen = m_LocalDestination.GetOwner ()->GetPrivateKeys ().GetSignatureLen (); uint8_t * optionsSize = packet + size; // set options size later size += 2; // options size m_LocalDestination.GetOwner ()->GetIdentity ()->ToBuffer (packet + size, identityLen); size += identityLen; // from htobe16buf (packet + size, m_MTU); size += 2; // max packet size if (isOfflineSignature) { const auto& offlineSignature = m_LocalDestination.GetOwner ()->GetPrivateKeys ().GetOfflineSignature (); memcpy (packet + size, offlineSignature.data (), offlineSignature.size ()); size += offlineSignature.size (); // offline signature } uint8_t * signature = packet + size; // set it later memset (signature, 0, signatureLen); // zeroes for now size += signatureLen; // signature htobe16buf (optionsSize, packet + size - 2 - optionsSize); // actual options size size += m_SendBuffer.Get (packet + size, m_MTU); // payload m_LocalDestination.GetOwner ()->Sign (packet, size, signature); } 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) { if (m_SavedPackets.empty ()) // no NACKS { m_IsAckSendScheduled = false; m_AckSendTimer.cancel (); } bool isEmpty = m_SentPackets.empty (); // 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) ScheduleResend (); } } 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 } } } if (lastReceivedSeqn < 0) { LogPrint (eLogError, "Streaming: No packets have been received yet"); return; } Packet p; uint8_t * packet = p.GetBuffer (); size_t size = 0; htobe32buf (packet + size, m_SendStreamID); size += 4; // sendStreamID htobe32buf (packet + size, m_RecvStreamID); size += 4; // receiveStreamID htobuf32 (packet + size, 0); // this is plain Ack message size += 4; // sequenceNum htobe32buf (packet + size, lastReceivedSeqn); size += 4; // ack Through uint8_t numNacks = 0; bool choking = false; if (lastReceivedSeqn > m_LastReceivedSequenceNumber) { // fill NACKs uint8_t * nacks = packet + size + 1; auto nextSeqn = m_LastReceivedSequenceNumber + 1; 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; break; } for (uint32_t i = nextSeqn; i < seqn; i++) { htobe32buf (nacks, i); nacks += 4; numNacks++; } nextSeqn = seqn + 1; } packet[size] = numNacks; size++; // NACK count size += numNacks*4; // NACKs } else { // No NACKs 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 += 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 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); switch (m_Status) { case eStreamStatusOpen: m_Status = eStreamStatusClosing; Close (); // recursion if (m_Status == eStreamStatusClosing) //still closing LogPrint (eLogDebug, "Streaming: Trying to send stream data before closing, sSID=", m_SendStreamID); break; case eStreamStatusReset: // TODO: send reset Terminate (); break; case eStreamStatusClosing: if (m_SentPackets.empty () && m_SendBuffer.IsEmpty ()) // nothing to send { m_Status = eStreamStatusClosed; SendClose(); } break; case eStreamStatusClosed: // already closed Terminate (); break; default: LogPrint (eLogWarning, "Streaming: Unexpected stream status=", (int)m_Status, " for sSID=", m_SendStreamID); }; } void Stream::SendClose () { Packet * p = m_LocalDestination.NewPacket (); uint8_t * packet = p->GetBuffer (); 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 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 (); htobe16buf (packet + size, signatureLen); // signature only size += 2; // options size uint8_t * signature = packet + size; memset (packet + size, 0, signatureLen); size += signatureLen; // signature m_LocalDestination.GetOwner ()->Sign (packet, size, signature); p->len = size; boost::asio::post (m_Service, std::bind (&Stream::SendPacket, shared_from_this (), p)); LogPrint (eLogDebug, "Streaming: FIN sent, sSID=", m_SendStreamID); } size_t Stream::ConcatenatePackets (uint8_t * buf, size_t len) { size_t pos = 0; while (pos < len && !m_ReceiveQueue.empty ()) { Packet * packet = m_ReceiveQueue.front (); size_t l = std::min (packet->GetLength (), len - pos); memcpy (buf + pos, packet->GetBuffer (), l); pos += l; packet->offset += l; if (!packet->GetLength ()) { m_ReceiveQueue.pop (); m_LocalDestination.DeletePacket (packet); } } return pos; } bool Stream::SendPacket (Packet * packet) { if (packet) { if (m_IsAckSendScheduled) { 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); if (isEmpty) ScheduleResend (); return true; } else return false; } void Stream::SendPackets (const std::vector& packets) { if (!m_RemoteLeaseSet) { CancelRemoteLeaseChange (); UpdateCurrentRemoteLease (); if (!m_RemoteLeaseSet) { LogPrint (eLogError, "Streaming: Can't send packets, missing remote LeaseSet, sSID=", m_SendStreamID); 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_CurrentOutboundTunnel && m_RoutingSession) // first message to send { // try to get shared path first auto routingPath = m_RoutingSession->GetSharedRoutingPath (); if (routingPath) { m_CurrentOutboundTunnel = routingPath->outboundTunnel; m_CurrentRemoteLease = routingPath->remoteLease; m_RTT = routingPath->rtt; } } auto ts = i2p::util::GetMillisecondsSinceEpoch (); if (!m_CurrentRemoteLease || !m_CurrentRemoteLease->endDate) // excluded from LeaseSet { CancelRemoteLeaseChange (); UpdateCurrentRemoteLease (true); } if (m_RemoteLeaseChangeTime && m_IsRemoteLeaseChangeInProgress && ts > m_RemoteLeaseChangeTime + INITIAL_RTO) { LogPrint (eLogDebug, "Streaming: RemoteLease changed, set initial window size"); CancelRemoteLeaseChange (); m_CurrentRemoteLease = m_NextRemoteLease; ResetWindowSize (); } auto currentRemoteLease = m_CurrentRemoteLease; if (!m_IsRemoteLeaseChangeInProgress && m_RemoteLeaseSet && m_CurrentRemoteLease && ts >= m_CurrentRemoteLease->endDate - i2p::data::LEASE_ENDDATE_THRESHOLD) { auto leases = m_RemoteLeaseSet->GetNonExpiredLeases (false); if (leases.size ()) { m_IsRemoteLeaseChangeInProgress = true; UpdateCurrentRemoteLease (true); m_NextRemoteLease = m_CurrentRemoteLease; } else UpdateCurrentRemoteLease (true); } if (m_CurrentRemoteLease && ts < m_CurrentRemoteLease->endDate + i2p::data::LEASE_ENDDATE_THRESHOLD) { bool freshTunnel = false; if (!m_CurrentOutboundTunnel) { auto leaseRouter = i2p::data::netdb.FindRouter (m_CurrentRemoteLease->tunnelGateway); m_CurrentOutboundTunnel = m_LocalDestination.GetOwner ()->GetTunnelPool ()->GetNextOutboundTunnel (nullptr, leaseRouter ? leaseRouter->GetCompatibleTransports (false) : (i2p::data::RouterInfo::CompatibleTransports)i2p::data::RouterInfo::eAllTransports); freshTunnel = true; } else if (!m_CurrentOutboundTunnel->IsEstablished ()) std::tie(m_CurrentOutboundTunnel, freshTunnel) = m_LocalDestination.GetOwner ()->GetTunnelPool ()->GetNewOutboundTunnel (m_CurrentOutboundTunnel); if (!m_CurrentOutboundTunnel) { LogPrint (eLogError, "Streaming: No outbound tunnels in the pool, sSID=", m_SendStreamID); m_CurrentRemoteLease = nullptr; return; } if (freshTunnel) { LogPrint (eLogDebug, "Streaming: OutboundTunnel changed, set initial window size"); ResetWindowSize (); // m_TunnelsChangeSequenceNumber = m_SequenceNumber; // should be determined more precisely } std::vector msgs; for (const auto& it: packets) { auto msg = m_RoutingSession->WrapSingleMessage (m_LocalDestination.CreateDataMessage ( it->GetBuffer (), it->GetLength (), m_Port, !m_RoutingSession->IsRatchets (), it->IsSYN ())); msgs.push_back (i2p::tunnel::TunnelMessageBlock { i2p::tunnel::eDeliveryTypeTunnel, m_CurrentRemoteLease->tunnelGateway, m_CurrentRemoteLease->tunnelID, 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); } else { LogPrint (eLogWarning, "Streaming: Remote lease is not available, sSID=", m_SendStreamID); if (m_RoutingSession) m_RoutingSession->SetSharedRoutingPath (nullptr); // invalidate routing path } } void Stream::SendUpdatedLeaseSet () { if (m_RoutingSession && !m_RoutingSession->IsTerminated ()) { if (m_RoutingSession->IsLeaseSetNonConfirmed ()) { auto ts = i2p::util::GetMillisecondsSinceEpoch (); if (ts > m_RoutingSession->GetLeaseSetSubmissionTime () + i2p::garlic::LEASESET_CONFIRMATION_TIMEOUT) { // LeaseSet was not confirmed, should try other tunnels LogPrint (eLogWarning, "Streaming: LeaseSet was not confirmed in ", i2p::garlic::LEASESET_CONFIRMATION_TIMEOUT, " milliseconds. Trying to resubmit"); m_RoutingSession->SetSharedRoutingPath (nullptr); m_CurrentOutboundTunnel = nullptr; m_CurrentRemoteLease = nullptr; SendQuickAck (); } } else if (m_RoutingSession->IsLeaseSetUpdated ()) { LogPrint (eLogDebug, "Streaming: sending updated LeaseSet"); 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) { 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_WindowSize)) 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--; } 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--; } } else break; } 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)); } } 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) { if (ts >= it->sendTime + m_RTO) { if (ts < it->sendTime + m_RTO*2) it->resent = true; else it->resent = false; it->sendTime = ts; packets.push_back (it); if ((int)packets.size () >= m_NumPacketsToSend) break; } } } else { for (auto it : m_SentPackets) { if (ts >= it->sendTime + m_RTO) { if (ts < it->sendTime + m_RTO*2) it->resent = true; else it->resent = false; it->sendTime = ts; packets.push_back (it); if ((int)packets.size () >= m_NumPacketsToSend) break; } } } // select tunnels if necessary and send if (packets.size () > 0 && m_IsSendTime) { if (m_IsNAcked) m_NumResendAttempts = 1; else if (m_IsTimeOutResend) m_NumResendAttempts++; if (m_NumResendAttempts == 1 && m_RTO != INITIAL_RTO) { // loss-based CC if (!m_IsWinDropped && LOSS_BASED_CONTROL_ENABLED && !m_IsClientChoked) { LogPrint (eLogDebug, "Streaming: Packet loss, reduce window size"); ProcessWindowDrop (); } } else if (m_IsTimeOutResend) { m_IsTimeOutResend = false; m_RTO = INITIAL_RTO; // drop RTO to initial upon tunnels pair change m_WindowDropTargetSize = INITIAL_WINDOW_SIZE; m_LastWindowDropSize = 0; m_WindowIncCounter = 0; m_IsWinDropped = true; m_IsFirstRttSample = true; m_DropWindowDelaySequenceNumber = 0; m_IsFirstACK = true; m_LastACKRecieveTime = 0; m_ACKRecieveInterval = m_AckDelay; UpdatePacingTime (); if (m_RoutingSession) m_RoutingSession->SetSharedRoutingPath (nullptr); if (m_NumResendAttempts & 1) { // pick another outbound tunnel m_CurrentOutboundTunnel = m_LocalDestination.GetOwner ()->GetTunnelPool ()->GetNextOutboundTunnel (m_CurrentOutboundTunnel); LogPrint (eLogWarning, "Streaming: Resend #", m_NumResendAttempts, ", another outbound tunnel has been selected for stream with sSID=", m_SendStreamID); } else { CancelRemoteLeaseChange (); UpdateCurrentRemoteLease (); // pick another lease LogPrint (eLogWarning, "Streaming: Resend #", m_NumResendAttempts, ", another remote lease has been selected for stream with rSID=", m_RecvStreamID, ", sSID=", m_SendStreamID); } } SendPackets (packets); m_LastSendTime = ts; m_IsSendTime = false; if (m_IsNAcked || m_IsResendNeeded || m_IsClientChoked) ScheduleSend (); } else if (!m_IsClientChoked) SendBuffer (); if (!m_IsNAcked && !m_IsResendNeeded) ScheduleResend (); if (m_IsClientChoked) ScheduleSend (); } void Stream::ScheduleAck (int timeout) { if (m_IsAckSendScheduled) m_AckSendTimer.cancel (); m_IsAckSendScheduled = true; if (timeout < MIN_SEND_ACK_TIMEOUT) timeout = MIN_SEND_ACK_TIMEOUT; m_AckSendTimer.expires_from_now (boost::posix_time::milliseconds(timeout)); m_AckSendTimer.async_wait (std::bind (&Stream::HandleAckSendTimer, shared_from_this (), std::placeholders::_1)); } void Stream::HandleAckSendTimer (const boost::system::error_code& ecode) { if (m_IsAckSendScheduled) { if (m_LastReceivedSequenceNumber < 0) { LogPrint (eLogWarning, "Streaming: SYN has not been received after ", SYN_TIMEOUT, " milliseconds after follow on, terminate rSID=", m_RecvStreamID, ", sSID=", m_SendStreamID); m_Status = eStreamStatusReset; Close (); return; } if (m_Status == eStreamStatusOpen) { 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; } } SendQuickAck (); } m_IsAckSendScheduled = false; } } void Stream::UpdateCurrentRemoteLease (bool expired) { bool isLeaseChanged = true; if (!m_RemoteLeaseSet || m_RemoteLeaseSet->IsExpired ()) { auto remoteLeaseSet = m_LocalDestination.GetOwner ()->FindLeaseSet (m_RemoteIdentity->GetIdentHash ()); if (!remoteLeaseSet) { LogPrint (eLogWarning, "Streaming: LeaseSet ", m_RemoteIdentity->GetIdentHash ().ToBase64 (), m_RemoteLeaseSet ? " expired" : " not found"); if (!m_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 (); } } if (m_RemoteLeaseSet) { if (!m_RoutingSession) m_RoutingSession = m_LocalDestination.GetOwner ()->GetRoutingSession (m_RemoteLeaseSet, true); auto leases = m_RemoteLeaseSet->GetNonExpiredLeases (false); // try without threshold first 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 ()); leases = m_RemoteLeaseSet->GetNonExpiredLeases (true); // then with threshold } if (!leases.empty ()) { bool updated = false; if (expired && m_CurrentRemoteLease) { for (const auto& it: leases) if ((it->tunnelGateway == m_CurrentRemoteLease->tunnelGateway) && (it->tunnelID != m_CurrentRemoteLease->tunnelID)) { m_CurrentRemoteLease = it; updated = true; break; } } if (!updated) { uint32_t i = m_LocalDestination.GetRandom () % 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; } m_CurrentRemoteLease = leases[i]; } } else { LogPrint (eLogWarning, "Streaming: All remote leases are expired"); m_RemoteLeaseSet = nullptr; m_CurrentRemoteLease = nullptr; // we have requested expired before, no need to do it twice } } else { 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_WindowSize > m_LastWindowDropSize) { m_LastWindowDropSize = (m_LastWindowDropSize + m_WindowSize + m_WindowSizeTail) / 2; if (m_LastWindowDropSize > MAX_WINDOW_SIZE) m_LastWindowDropSize = MAX_WINDOW_SIZE; } else m_LastWindowDropSize = m_WindowSize; m_WindowDropTargetSize = m_LastWindowDropSize - (m_LastWindowDropSize / 4); // -25%; 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 ()) { } StreamingDestination::~StreamingDestination () { for (auto& it: m_SavedPackets) { for (auto it1: it.second) DeletePacket (it1); it.second.clear (); } m_SavedPackets.clear (); } void StreamingDestination::Start () { } void StreamingDestination::Stop () { ResetAcceptor (); m_PendingIncomingTimer.cancel (); m_PendingIncomingStreams.clear (); { std::unique_lock l(m_StreamsMutex); for (auto it: m_Streams) it.second->Terminate (false); // we delete here m_Streams.clear (); m_IncomingStreams.clear (); m_LastStream = nullptr; } } void StreamingDestination::HandleNextPacket (Packet * packet) { 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); } else { LogPrint (eLogInfo, "Streaming: Unknown stream sSID=", sendStreamID); DeletePacket (packet); } } 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 ()) { // already pending LogPrint(eLogWarning, "Streaming: Incoming streaming with rSID=", receiveStreamID, " already exists"); it1->second->ResetRoutingPath (); // Ack was not delivered, changing path DeletePacket (packet); // drop it, because previous should be connected return; } if (m_Owner->GetStreamingMaxConcurrentStreams () > 0 && (int)m_Streams.size () > m_Owner->GetStreamingMaxConcurrentStreams ()) { LogPrint(eLogWarning, "Streaming: Number of streams exceeds ", m_Owner->GetStreamingMaxConcurrentStreams ()); DeletePacket (packet); return; } auto incomingStream = CreateNewIncomingStream (receiveStreamID); incomingStream->HandleNextPacket (packet); // SYN if (!incomingStream->GetRemoteLeaseSet ()) { LogPrint (eLogWarning, "Streaming: No remote LeaseSet for incoming stream. Terminated"); incomingStream->Terminate (); // can't send FIN anyway return; } // handle saved packets if any { auto it = m_SavedPackets.find (receiveStreamID); if (it != m_SavedPackets.end ()) { LogPrint (eLogDebug, "Streaming: Processing ", it->second.size (), " saved packets for rSID=", receiveStreamID); for (auto it1: it->second) incomingStream->HandleNextPacket (it1); m_SavedPackets.erase (it); } } // accept if (m_Acceptor != nullptr) m_Acceptor (incomingStream); else { LogPrint (eLogWarning, "Streaming: Acceptor for incoming stream is not set"); if (m_PendingIncomingStreams.size () < MAX_PENDING_INCOMING_BACKLOG) { m_PendingIncomingStreams.push_back (incomingStream); m_PendingIncomingTimer.cancel (); m_PendingIncomingTimer.expires_from_now (boost::posix_time::seconds(PENDING_INCOMING_TIMEOUT)); m_PendingIncomingTimer.async_wait (std::bind (&StreamingDestination::HandlePendingIncomingTimer, shared_from_this (), std::placeholders::_1)); LogPrint (eLogDebug, "Streaming: Pending incoming stream added, rSID=", receiveStreamID); } else { LogPrint (eLogWarning, "Streaming: Pending incoming streams backlog exceeds ", MAX_PENDING_INCOMING_BACKLOG); incomingStream->Close (); } } } 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; } // save follow on packet auto it = m_SavedPackets.find (receiveStreamID); if (it != m_SavedPackets.end ()) it->second.push_back (packet); else { m_SavedPackets[receiveStreamID] = std::list{ packet }; auto timer = std::make_shared (m_Owner->GetService ()); timer->expires_from_now (boost::posix_time::seconds(PENDING_INCOMING_TIMEOUT)); auto s = shared_from_this (); timer->async_wait ([s,timer,receiveStreamID](const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) { auto it = s->m_SavedPackets.find (receiveStreamID); if (it != s->m_SavedPackets.end ()) { for (auto it1: it->second) s->DeletePacket (it1); it->second.clear (); s->m_SavedPackets.erase (it); } } }); } } } } std::shared_ptr StreamingDestination::CreateNewOutgoingStream (std::shared_ptr remote, int port) { auto s = std::make_shared (m_Owner->GetService (), *this, remote, port); std::unique_lock l(m_StreamsMutex); m_Streams.emplace (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) { 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); return s; } void StreamingDestination::DeleteStream (std::shared_ptr 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 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) { // take care about incoming queue for (auto& it: s->m_PendingIncomingStreams) if (it->GetStatus () == eStreamStatusOpen) // still open? s->m_Acceptor (it); s->m_PendingIncomingStreams.clear (); s->m_PendingIncomingTimer.cancel (); }); } void StreamingDestination::ResetAcceptor () { if (m_Acceptor) m_Acceptor (nullptr); m_Acceptor = nullptr; } void StreamingDestination::AcceptOnce (const Acceptor& acceptor) { boost::asio::post (m_Owner->GetService (), [acceptor, this](void) { if (!m_PendingIncomingStreams.empty ()) { acceptor (m_PendingIncomingStreams.front ()); m_PendingIncomingStreams.pop_front (); if (m_PendingIncomingStreams.empty ()) m_PendingIncomingTimer.cancel (); } else // we must save old acceptor and set it back { m_Acceptor = std::bind (&StreamingDestination::AcceptOnceAcceptor, this, std::placeholders::_1, acceptor, m_Acceptor); } }); } void StreamingDestination::AcceptOnceAcceptor (std::shared_ptr stream, Acceptor acceptor, Acceptor prev) { m_Acceptor = prev; 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) { LogPrint (eLogWarning, "Streaming: Pending incoming timeout expired"); for (auto& it: m_PendingIncomingStreams) it->Close (); m_PendingIncomingStreams.clear (); } } void StreamingDestination::HandleDataMessagePayload (const uint8_t * buf, size_t len) { // unzip it Packet * uncompressed = NewPacket (); uncompressed->offset = 0; uncompressed->len = m_Inflator.Inflate (buf, len, uncompressed->buf, MAX_PACKET_SIZE); if (uncompressed->len) HandleNextPacket (uncompressed); else DeletePacket (uncompressed); } std::shared_ptr StreamingDestination::CreateDataMessage ( const uint8_t * payload, size_t len, uint16_t toPort, bool checksum, bool gzip) { size_t size; auto msg = (len <= STREAMING_MTU_RATCHETS) ? m_I2NPMsgsPool.AcquireShared () : NewI2NPMessage (); uint8_t * buf = msg->GetPayload (); buf += 4; // reserve for lengthlength msg->len += 4; if (m_Gzip || gzip) size = m_Deflator.Deflate (payload, len, buf, msg->maxLen - msg->len); else size = i2p::data::GzipNoCompression (payload, len, buf, msg->maxLen - msg->len); if (size) { htobe32buf (msg->GetPayload (), size); // length htobe16buf (buf + 4, m_LocalPort); // source port htobe16buf (buf + 6, toPort); // destination port buf[9] = i2p::client::PROTOCOL_TYPE_STREAMING; // streaming protocol msg->len += size; msg->FillI2NPMessageHeader (eI2NPData, 0, checksum); } else msg = nullptr; return msg; } uint32_t StreamingDestination::GetRandom () { if (m_Owner) { auto pool = m_Owner->GetTunnelPool (); if (pool) return pool->GetRng ()(); } return rand (); } } } i2pd-2.56.0/libi2pd/Streaming.h000066400000000000000000000376451475272067700161310ustar00rootroot00000000000000/* * 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 STREAMING_H__ #define STREAMING_H__ #include #include #include #include #include #include #include #include #include #include "Base.h" #include "Gzip.h" #include "I2PEndian.h" #include "Identity.h" #include "LeaseSet.h" #include "I2NPProtocol.h" #include "Garlic.h" #include "Tunnel.h" #include "util.h" // MemoryPool namespace i2p { namespace client { class ClientDestination; } namespace stream { const uint16_t PACKET_FLAG_SYNCHRONIZE = 0x0001; const uint16_t PACKET_FLAG_CLOSE = 0x0002; const uint16_t PACKET_FLAG_RESET = 0x0004; const uint16_t PACKET_FLAG_SIGNATURE_INCLUDED = 0x0008; const uint16_t PACKET_FLAG_SIGNATURE_REQUESTED = 0x0010; const uint16_t PACKET_FLAG_FROM_INCLUDED = 0x0020; const uint16_t PACKET_FLAG_DELAY_REQUESTED = 0x0040; const uint16_t PACKET_FLAG_MAX_PACKET_SIZE_INCLUDED = 0x0080; 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; const size_t MAX_PACKET_SIZE = 4096; 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 double RTT_EWMA_ALPHA = 0.25; const double SLOWRTT_EWMA_ALPHA = 0.05; const double PREV_SPEED_KEEP_TIME_COEFF = 0.35; // 0.1 - 1 // how long will the window size stay around the previous drop level, less is longer const int MIN_RTO = 20; // in milliseconds const int INITIAL_RTT = 1500; // in milliseconds const int INITIAL_RTO = 9000; // in milliseconds const int INITIAL_PACING_TIME = 1000 * INITIAL_RTT / INITIAL_WINDOW_SIZE; // in microseconds const int MIN_SEND_ACK_TIMEOUT = 2; // in milliseconds const int SYN_TIMEOUT = 200; // how long we wait for SYN after follow-on, in milliseconds const size_t MAX_PENDING_INCOMING_BACKLOG = 1024; const int PENDING_INCOMING_TIMEOUT = 10; // in seconds const int MAX_RECEIVE_TIMEOUT = 20; // in seconds const uint16_t DELAY_CHOKING = 60000; // in milliseconds const uint64_t SEND_INTERVAL = 10000; // in microseconds const uint64_t SEND_INTERVAL_VARIANCE = 2000; // in microseconds const uint64_t REQUEST_IMMEDIATE_ACK_INTERVAL = 7500; // in milliseconds const uint64_t REQUEST_IMMEDIATE_ACK_INTERVAL_VARIANCE = 3200; // in milliseconds const bool LOSS_BASED_CONTROL_ENABLED = 1; // 0/1 const uint64_t STREAMING_DESTINATION_POOLS_CLEANUP_INTERVAL = 646; // in seconds struct Packet { size_t len, offset; uint8_t buf[MAX_PACKET_SIZE]; uint64_t sendTime; bool resent; Packet (): len (0), offset (0), sendTime (0), resent (false) {}; uint8_t * GetBuffer () { return buf + offset; }; size_t GetLength () const { return len > offset ? len - offset : 0; }; uint32_t GetSendStreamID () const { return bufbe32toh (buf); }; uint32_t GetReceiveStreamID () const { return bufbe32toh (buf + 4); }; uint32_t GetSeqn () const { return bufbe32toh (buf + 8); }; 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 ()); }; const uint8_t * GetOptionData () const { return GetOption () + 2; }; const uint8_t * GetPayload () const { return GetOptionData () + GetOptionSize (); }; 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 { bool operator() (const Packet * p1, const Packet * p2) const { return p1->GetSeqn () < p2->GetSeqn (); }; }; typedef std::function SendHandler; struct SendBuffer { uint8_t * buf; size_t len, offset; SendHandler handler; SendBuffer (const uint8_t * b, size_t l, SendHandler h): len(l), offset (0), handler(h) { 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; if (handler) handler(boost::system::error_code ()); } size_t GetRemainingSize () const { return len - offset; }; const uint8_t * GetRemaningBuffer () const { return buf + offset; }; void Cancel () { if (handler) handler (boost::asio::error::make_error_code (boost::asio::error::operation_aborted)); handler = nullptr; }; }; class SendBufferQueue { public: SendBufferQueue (): m_Size (0) {}; ~SendBufferQueue () { CleanUp (); }; void Add (std::shared_ptr buf); size_t Get (uint8_t * buf, size_t len); size_t GetSize () const { return m_Size; }; bool IsEmpty () const { return m_Buffers.empty (); }; void CleanUp (); private: std::list > m_Buffers; size_t m_Size; }; enum StreamStatus { eStreamStatusNew = 0, eStreamStatusOpen, eStreamStatusReset, eStreamStatusClosing, eStreamStatusClosed, eStreamStatusTerminated }; class StreamingDestination; class Stream: public std::enable_shared_from_this { public: Stream (boost::asio::io_context& service, StreamingDestination& local, std::shared_ptr remote, int port = 0); // outgoing Stream (boost::asio::io_context& service, StreamingDestination& local); // incoming ~Stream (); uint32_t GetSendStreamID () const { return m_SendStreamID; }; uint32_t GetRecvStreamID () const { return m_RecvStreamID; }; std::shared_ptr GetRemoteLeaseSet () const { return m_RemoteLeaseSet; }; 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 (); }; size_t GetNumSentBytes () const { return m_NumSentBytes; }; size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; }; size_t GetSendQueueSize () const { return m_SentPackets.size (); }; size_t GetReceiveQueueSize () const { return m_ReceiveQueue.size (); }; size_t GetSendBufferSize () const { return m_SendBuffer.GetSize (); }; int GetWindowSize () const { return m_WindowSize; }; int GetRTT () const { return m_RTT; }; void Terminate (bool deleteFromDestination = true); private: void CleanUp (); void SendBuffer (); void SendQuickAck (); void SendClose (); bool SendPacket (Packet * packet); void SendPackets (const std::vector& packets); void SendUpdatedLeaseSet (); 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 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; 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; size_t m_NumSentBytes, m_NumReceivedBytes; uint16_t m_Port; 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; // milliseconds uint64_t m_LastACKSendTime, m_PacketACKInterval, m_PacketACKIntervalRem; // for limit inbound speed int m_NumResendAttempts, m_NumPacketsToSend; size_t m_MTU; }; class StreamingDestination: public std::enable_shared_from_this { public: typedef std::function)> Acceptor; StreamingDestination (std::shared_ptr owner, uint16_t localPort = 0, bool gzip = false); ~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); 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); Packet * NewPacket () { return m_PacketsPool.Acquire(); } void DeletePacket (Packet * p) { return m_PacketsPool.Release(p); } uint32_t GetRandom (); private: void HandleNextPacket (Packet * packet); std::shared_ptr CreateNewIncomingStream (uint32_t receiveStreamID); void HandlePendingIncomingTimer (const boost::system::error_code& ecode); 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; Acceptor m_Acceptor; std::list > m_PendingIncomingStreams; boost::asio::deadline_timer m_PendingIncomingTimer; std::unordered_map > m_SavedPackets; // receiveStreamID->packets, arrived before SYN i2p::util::MemoryPool m_PacketsPool; i2p::util::MemoryPool > m_I2NPMsgsPool; uint64_t m_LastCleanupTime; // in seconds public: i2p::data::GzipInflator m_Inflator; i2p::data::GzipDeflator m_Deflator; // for HTTP only const decltype(m_Streams)& GetStreams () const { return m_Streams; }; }; //------------------------------------------------- template 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) { 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); else { 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) { s->HandleReceiveTimer(ec, buffer, handler, left); }); } }); } template void Stream::HandleReceiveTimer (const boost::system::error_code& ecode, Buffer& buffer, ReceiveHandler handler, int remainingTimeout) { size_t received = ConcatenatePackets ((uint8_t *)buffer.data (), buffer.size ()); if (received > 0) handler (boost::system::error_code (), received); else if (ecode == boost::asio::error::operation_aborted) { // timeout not expired if (m_Status == eStreamStatusReset) handler (boost::asio::error::make_error_code (boost::asio::error::connection_reset), 0); else handler (boost::asio::error::make_error_code (boost::asio::error::operation_aborted), 0); } else { // timeout expired if (remainingTimeout <= 0) handler (boost::asio::error::make_error_code (boost::asio::error::timed_out), received); else { // itermediate interrupt SendUpdatedLeaseSet (); // send our leaseset if applicable AsyncReceive (buffer, handler, remainingTimeout); } } } } } #endif i2pd-2.56.0/libi2pd/Tag.h000066400000000000000000000047511475272067700147030ustar00rootroot00000000000000/* * 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 TAG_H__ #define TAG_H__ #include #include #include #include #include #include "Base.h" namespace i2p { 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 { for (size_t i = 0; i < sz/8; ++i) if (ll[i]) return false; return true; } 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 { char str[sz*2]; size_t l = i2p::data::ByteStreamToBase64 (m_Buf, len, str, sz*2); return std::string (str, str + l); } std::string ToBase32 (size_t len = sz) const { char str[sz*2]; size_t l = i2p::data::ByteStreamToBase32 (m_Buf, len, str, sz*2); return std::string (str, str + l); } size_t FromBase32 (std::string_view s) { return i2p::data::Base32ToByteStream (s.data (), s.length (), m_Buf, sz); } size_t FromBase64 (std::string_view s) { return i2p::data::Base64ToByteStream (s.data (), s.length (), m_Buf, sz); } uint8_t GetBit (int i) const { int pos = i >> 3; // /8 if (pos >= (int)sz) return 0; return m_Buf[pos] & (0x80 >> (i & 0x07)); } private: union // 8 bytes aligned { 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__ */ i2pd-2.56.0/libi2pd/Timestamp.cpp000066400000000000000000000153141475272067700164630ustar00rootroot00000000000000/* * 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 #ifndef _WIN64 #define _USE_32BIT_TIME_T #endif #endif 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) { LogPrint (eLogInfo, "Timestamp: NTP request to ", address); boost::asio::io_context service; boost::system::error_code ec; auto endpoints = boost::asio::ip::udp::resolver (service).resolve (address, "ntp", ec); if (!ec) { 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; } boost::asio::ip::udp::socket socket (service); socket.open (ep.protocol (), ec); if (!ec) { uint8_t buf[48];// 48 bytes NTP request/response memset (buf, 0, 48); htobe32buf (buf, (3 << 27) | (3 << 24)); // RFC 4330 size_t len = 0; try { socket.send_to (boost::asio::buffer (buf, 48), ep); int i = 0; while (!socket.available() && i < 10) // 10 seconds max { std::this_thread::sleep_for (std::chrono::seconds(1)); i++; } if (socket.available ()) len = socket.receive_from (boost::asio::buffer (buf, 48), ep); } catch (std::exception& e) { LogPrint (eLogError, "Timestamp: NTP error: ", e.what ()); } if (len >= 8) { auto ourTs = GetLocalSecondsSinceEpoch (); 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"); } } 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; } } } i2pd-2.56.0/libi2pd/Timestamp.h000066400000000000000000000025641475272067700161330ustar00rootroot00000000000000/* * 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 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 { public: NTPTimeSync (); ~NTPTimeSync (); 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; }; } } #endif i2pd-2.56.0/libi2pd/TransitTunnel.cpp000066400000000000000000000513021475272067700173270ustar00rootroot00000000000000/* * 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" 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) { } 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); 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) { EncryptTunnelMsg (tunnelMsg, tunnelMsg); m_NumTransmittedBytes += tunnelMsg->GetLength (); htobe32buf (tunnelMsg->GetPayload (), GetNextTunnelID ()); tunnelMsg->FillI2NPMessageHeader (eI2NPTunnelData); m_TunnelDataMsgs.push_back (tunnelMsg); } void TransitTunnelParticipant::FlushTunnelDataMsgs () { if (!m_TunnelDataMsgs.empty ()) { auto num = m_TunnelDataMsgs.size (); if (num > 1) LogPrint (eLogDebug, "TransitTunnel: ", GetTunnelID (), "->", GetNextTunnelID (), " ", num); if (!m_Sender) m_Sender = std::make_unique(); m_Sender->SendMessagesTo (GetNextIdentHash (), m_TunnelDataMsgs); // send and clear } } std::string TransitTunnelParticipant::GetNextPeerName () const { if (m_Sender) { auto transport = m_Sender->GetCurrentTransport (); if (transport) return TransitTunnel::GetNextPeerName () + "-" + i2p::data::RouterInfo::GetTransportName (transport->GetTransportType ()); } return TransitTunnel::GetNextPeerName (); } void TransitTunnelGateway::SendTunnelDataMsg (std::shared_ptr msg) { TunnelMessageBlock block; block.deliveryType = eDeliveryTypeLocal; block.data = msg; std::lock_guard l(m_SendMutex); m_Gateway.PutTunnelDataMsg (block); } void TransitTunnelGateway::FlushTunnelDataMsgs () { std::lock_guard l(m_SendMutex); m_Gateway.SendBuffer (); } std::string TransitTunnelGateway::GetNextPeerName () const { const auto& sender = m_Gateway.GetSender (); if (sender) { auto transport = sender->GetCurrentTransport (); if (transport) return TransitTunnel::GetNextPeerName () + "-" + i2p::data::RouterInfo::GetTransportName (transport->GetTransportType ()); } return TransitTunnel::GetNextPeerName (); } void TransitTunnelEndpoint::HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg) { auto newMsg = CreateEmptyTunnelDataMsg (true); EncryptTunnelMsg (tunnelMsg, newMsg); LogPrint (eLogDebug, "TransitTunnel: handle msg for endpoint ", GetTunnelID ()); std::lock_guard l(m_HandleMutex); m_Endpoint.HandleDecryptedTunnelDataMsg (newMsg); } void TransitTunnelEndpoint::FlushTunnelDataMsgs () { std::lock_guard l(m_HandleMutex); m_Endpoint.FlushI2NPMsgs (); } void TransitTunnelEndpoint::Cleanup () { std::lock_guard l(m_HandleMutex); m_Endpoint.Cleanup (); } std::string TransitTunnelEndpoint::GetNextPeerName () const { 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, bool isGateway, bool isEndpoint) { if (isEndpoint) { LogPrint (eLogDebug, "TransitTunnel: endpoint ", receiveTunnelID, " created"); return std::make_shared (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey); } else if (isGateway) { LogPrint (eLogInfo, "TransitTunnel: gateway ", receiveTunnelID, " created"); return std::make_shared (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey); } else { LogPrint (eLogDebug, "TransitTunnel: ", receiveTunnelID, "->", nextTunnelID, " created"); 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; } } } i2pd-2.56.0/libi2pd/TransitTunnel.h000066400000000000000000000117701475272067700170010ustar00rootroot00000000000000/* * 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 "Crypto.h" #include "Queue.h" #include "I2NPProtocol.h" #include "TunnelEndpoint.h" #include "TunnelGateway.h" #include "TunnelBase.h" namespace i2p { namespace tunnel { class TransitTunnel: public TunnelBase { public: TransitTunnel (uint32_t receiveTunnelID, const i2p::data::IdentHash& nextIdent, uint32_t nextTunnelID, const i2p::crypto::AESKey& layerKey, const i2p::crypto::AESKey& ivKey); virtual size_t GetNumTransmittedBytes () const { return 0; }; virtual std::string GetNextPeerName () const; // implements TunnelBase void SendTunnelDataMsg (std::shared_ptr msg) override; void HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg) override; void EncryptTunnelMsg (std::shared_ptr in, std::shared_ptr out) override; private: i2p::crypto::AESKey m_LayerKey, m_IVKey; std::unique_ptr m_Encryption; }; class TransitTunnelParticipant: public TransitTunnel { public: TransitTunnelParticipant (uint32_t receiveTunnelID, const i2p::data::IdentHash& nextIdent, uint32_t nextTunnelID, const i2p::crypto::AESKey& layerKey, const i2p::crypto::AESKey& ivKey): TransitTunnel (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey), m_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; private: size_t m_NumTransmittedBytes; std::list > m_TunnelDataMsgs; std::unique_ptr m_Sender; }; class TransitTunnelGateway: public TransitTunnel { public: TransitTunnelGateway (uint32_t receiveTunnelID, const i2p::data::IdentHash& nextIdent, uint32_t nextTunnelID, const i2p::crypto::AESKey& layerKey, const i2p::crypto::AESKey& ivKey): TransitTunnel (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey), m_Gateway(*this) {}; void SendTunnelDataMsg (std::shared_ptr msg) override; void FlushTunnelDataMsgs () override; size_t GetNumTransmittedBytes () const override { return m_Gateway.GetNumSentBytes (); }; std::string GetNextPeerName () const override; private: std::mutex m_SendMutex; TunnelGateway m_Gateway; }; class TransitTunnelEndpoint: public TransitTunnel { 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), m_Endpoint (false) {}; // transit endpoint is always outbound void Cleanup () override; void HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg) override; void FlushTunnelDataMsgs () override; size_t GetNumTransmittedBytes () const override { return m_Endpoint.GetNumReceivedBytes (); } std::string GetNextPeerName () const override; private: std::mutex m_HandleMutex; 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, 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 (); }; }; } } #endif i2pd-2.56.0/libi2pd/TransportSession.h000066400000000000000000000150401475272067700175210ustar00rootroot00000000000000/* * 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 TRANSPORT_SESSION_H__ #define TRANSPORT_SESSION_H__ #include #include #include #include #include #include "Identity.h" #include "Crypto.h" #include "RouterInfo.h" #include "I2NPProtocol.h" #include "Timestamp.h" 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; class SignedData { public: SignedData () {} SignedData (const SignedData& other) { m_Stream << other.m_Stream.rdbuf (); } void Reset () { m_Stream.str(""); } void Insert (const uint8_t * buf, size_t len) { m_Stream.write ((char *)buf, len); } template void Insert (T t) { m_Stream.write ((char *)&t, sizeof (T)); } bool Verify (std::shared_ptr ident, const uint8_t * signature) const { return ident->Verify ((const uint8_t *)m_Stream.str ().c_str (), m_Stream.str ().size (), signature); } void Sign (const i2p::data::PrivateKeys& keys, uint8_t * signature) const { keys.Sign ((const uint8_t *)m_Stream.str ().c_str (), m_Stream.str ().size (), signature); } private: 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) { if (router) m_RemoteIdentity = router->GetRouterIdentity (); m_CreationTime = m_LastActivityTimestamp; } virtual ~TransportSession () {}; virtual void Done () = 0; 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; } 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; }; uint32_t GetCreationTime () const { return m_CreationTime; }; void SetCreationTime (uint32_t ts) { m_CreationTime = ts; }; // for introducers uint64_t GetLastActivityTimestamp () const { return m_LastActivityTimestamp; }; void SetLastActivityTimestamp (uint64_t ts) { m_LastActivityTimestamp = ts; }; virtual uint32_t GetRelayTag () const { return 0; }; virtual void SendLocalRouterInfo (bool update = false) { 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; 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; }; } } #endif i2pd-2.56.0/libi2pd/Transports.cpp000066400000000000000000001250461475272067700167030ustar00rootroot00000000000000/* * 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" #include "I2NPProtocol.h" #include "NetDb.hpp" #include "Transports.h" #include "Config.h" #include "HTTP.h" #include "util.h" using namespace i2p::data; namespace i2p { namespace transport { template EphemeralKeysSupplier::EphemeralKeysSupplier (int size): m_QueueSize (size), m_IsRunning (false) { } template EphemeralKeysSupplier::~EphemeralKeysSupplier () { Stop (); } template void EphemeralKeysSupplier::Start () { m_IsRunning = true; m_Thread.reset (new std::thread (std::bind (&EphemeralKeysSupplier::Run, this))); } template void EphemeralKeysSupplier::Stop () { { std::unique_lock l(m_AcquiredMutex); m_IsRunning = false; m_Acquired.notify_one (); } if (m_Thread) { m_Thread->join (); m_Thread = nullptr; } if (!m_Queue.empty ()) { // clean up queue std::queue > tmp; std::swap (m_Queue, tmp); } m_KeysPool.CleanUpMt (); } template void EphemeralKeysSupplier::Run () { i2p::util::SetThreadName("Ephemerals"); while (m_IsRunning) { int num, total = 0; while ((num = m_QueueSize - (int)m_Queue.size ()) > 0 && total < m_QueueSize) { CreateEphemeralKeys (num); total += num; } if (total > m_QueueSize) { LogPrint (eLogWarning, "Transports: ", total, " ephemeral keys generated at the time"); std::this_thread::sleep_for (std::chrono::seconds(1)); // take a break } else { m_KeysPool.CleanUpMt (); std::unique_lock l(m_AcquiredMutex); if (!m_IsRunning) break; m_Acquired.wait (l); // wait for element gets acquired } } } template void EphemeralKeysSupplier::CreateEphemeralKeys (int num) { if (num > 0) { for (int i = 0; i < num; i++) { auto pair = m_KeysPool.AcquireSharedMt (); pair->GenerateKeys (); std::unique_lock l(m_AcquiredMutex); m_Queue.push (pair); } } } template std::shared_ptr EphemeralKeysSupplier::Acquire () { { std::unique_lock l(m_AcquiredMutex); if (!m_Queue.empty ()) { auto pair = m_Queue.front (); m_Queue.pop (); m_Acquired.notify_one (); return pair; } } // queue is empty, create new auto pair = m_KeysPool.AcquireSharedMt (); pair->GenerateKeys (); return pair; } template void EphemeralKeysSupplier::Return (std::shared_ptr pair) { if (pair) { std::unique_lock l(m_AcquiredMutex); if ((int)m_Queue.size () < 2*m_QueueSize) m_Queue.push (pair); } else LogPrint(eLogError, "Transports: Return null keys"); } void Peer::UpdateParams (std::shared_ptr router) { if (router) { isHighBandwidth = router->IsHighBandwidth (); isEligible =(bool)router->GetCompatibleTransports (true) && // reachable router->GetCongestion () != i2p::data::RouterInfo::eRejectAll && // accepts tunnel router->IsECIES () && router->GetVersion () >= NETDB_MIN_HIGHBANDWIDTH_VERSION; // not too old } } Transports transports; Transports::Transports (): m_IsOnline (true), m_IsRunning (false), m_IsNAT (true), m_CheckReserved(true), m_Thread (nullptr), m_Service (nullptr), m_Work (nullptr), m_PeerCleanupTimer (nullptr), m_PeerTestTimer (nullptr), m_UpdateBandwidthTimer (nullptr), m_SSU2Server (nullptr), m_NTCP2Server (nullptr), m_X25519KeysPairSupplier (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) { } Transports::~Transports () { Stop (); if (m_Service) { 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) { if (!m_Service) { m_Service = new boost::asio::io_context (); m_Work = new boost::asio::executor_work_guard (m_Service->get_executor ()); m_PeerCleanupTimer = new boost::asio::deadline_timer (*m_Service); m_PeerTestTimer = new boost::asio::deadline_timer (*m_Service); m_UpdateBandwidthTimer = new boost::asio::deadline_timer (*m_Service); } bool ipv4; i2p::config::GetOption("ipv4", ipv4); bool ipv6; i2p::config::GetOption("ipv6", ipv6); i2p::config::GetOption("nat", m_IsNAT); m_X25519KeysPairSupplier.Start (); m_IsRunning = true; m_Thread = new std::thread (std::bind (&Transports::Run, this)); std::string ntcp2proxy; i2p::config::GetOption("ntcp2.proxy", ntcp2proxy); i2p::http::URL proxyurl; // create NTCP2. TODO: move to acceptor if (enableNTCP2 || i2p::context.SupportsMesh ()) { if(!ntcp2proxy.empty() && enableNTCP2) { if(proxyurl.parse(ntcp2proxy)) { if(proxyurl.schema == "socks" || proxyurl.schema == "http") { 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); } else LogPrint(eLogCritical, "Transports: Unsupported NTCP2 proxy URL ", ntcp2proxy); } else LogPrint(eLogCritical, "Transports: Invalid NTCP2 proxy URL ", ntcp2proxy); } else m_NTCP2Server = new NTCP2Server (); } // create SSU2 server if (enableSSU2) { m_SSU2Server = new SSU2Server (); std::string ssu2proxy; i2p::config::GetOption("ssu2.proxy", ssu2proxy); if (!ssu2proxy.empty()) { if (proxyurl.parse (ssu2proxy) && proxyurl.schema == "socks") { if (m_SSU2Server->SetProxy (proxyurl.host, proxyurl.port)) { i2p::context.SetStatus (eRouterStatusProxy); if (ipv6) i2p::context.SetStatusV6 (eRouterStatusProxy); } else LogPrint(eLogCritical, "Transports: Can't set SSU2 proxy ", ssu2proxy); } else LogPrint(eLogCritical, "Transports: Invalid SSU2 proxy URL ", ssu2proxy); } } // 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->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)); } } void Transports::Stop () { if (m_PeerCleanupTimer) m_PeerCleanupTimer->cancel (); if (m_PeerTestTimer) m_PeerTestTimer->cancel (); if (m_SSU2Server) { m_SSU2Server->Stop (); delete m_SSU2Server; m_SSU2Server = nullptr; } if (m_NTCP2Server) { m_NTCP2Server->Stop (); delete m_NTCP2Server; m_NTCP2Server = nullptr; } m_X25519KeysPairSupplier.Stop (); m_IsRunning = false; if (m_Service) m_Service->stop (); if (m_Thread) { m_Thread->join (); delete m_Thread; m_Thread = nullptr; } m_Peers.clear (); } void Transports::Run () { i2p::util::SetThreadName("Transports"); while (m_IsRunning && m_Service) { try { m_Service->run (); } catch (std::exception& ex) { LogPrint (eLogError, "Transports: Runtime exception: ", ex.what ()); } } } void Transports::UpdateBandwidthValues(int interval, uint32_t& in, uint32_t& out, uint32_t& transit) { TrafficSample& sample1 = m_TrafficSamples[m_TrafficSamplePtr]; TrafficSample& sample2 = m_TrafficSamples[(TRAFFIC_SAMPLE_COUNT + m_TrafficSamplePtr - interval) % TRAFFIC_SAMPLE_COUNT]; auto delta = (int64_t)sample1.Timestamp - (int64_t)sample2.Timestamp; if (delta <= 0) { LogPrint (eLogError, "Transports: Backward clock jump detected, got ", delta, " instead of ", interval * 1000); return; } in = (sample1.TotalReceivedBytes - sample2.TotalReceivedBytes) * 1000 / delta; out = (sample1.TotalSentBytes - sample2.TotalSentBytes) * 1000 / delta; transit = (sample1.TotalTransitTransmittedBytes - sample2.TotalTransitTransmittedBytes) * 1000 / delta; } void Transports::HandleUpdateBandwidthTimer (const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) { 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 { return PostMessages (ident, msgs); })); } std::shared_ptr Transports::PostMessages (const i2p::data::IdentHash& ident, std::list >& msgs) { if (ident == i2p::context.GetRouterInfo ().GetIdentHash ()) { // we send it to ourself for (auto& it: msgs) m_LoopbackHandler.PutNextMessage (std::move (it)); m_LoopbackHandler.Flush (); return nullptr; } if(RoutesRestricted() && !IsRestrictedPeer(ident)) return nullptr; std::shared_ptr peer; { std::lock_guard l(m_PeersMutex); auto it = m_Peers.find (ident); if (it != m_Peers.end ()) peer = it->second; } if (!peer) { // check if not banned if (i2p::data::IsRouterBanned (ident)) return nullptr; // don't create peer to unreachable router // try to connect bool connected = false; try { auto r = netdb.FindRouter (ident); if (r && (r->IsUnreachable () || !r->IsReachableFrom (i2p::context.GetRouterInfo ()))) return nullptr; // router found but non-reachable peer = std::make_shared(r, i2p::util::GetSecondsSinceEpoch ()); { std::lock_guard l(m_PeersMutex); peer = m_Peers.emplace (ident, peer).first->second; } if (peer) connected = ConnectToPeer (ident, peer); } catch (std::exception& ex) { LogPrint (eLogError, "Transports: PostMessages exception:", ex.what ()); } if (!connected) return nullptr; } if (!peer) return nullptr; if (peer->IsConnected ()) { auto session = peer->sessions.front (); if (session) session->SendI2NPMessages (msgs); return session; } else { auto sz = peer->delayedMessages.size (); if (sz < 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); } 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); } } return nullptr; } bool Transports::ConnectToPeer (const i2p::data::IdentHash& ident, std::shared_ptr 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->priority.empty ()) SetPriority (peer); while (peer->numAttempts < (int)peer->priority.size ()) { auto tr = peer->priority[peer->numAttempts]; peer->numAttempts++; switch (tr) { case i2p::data::RouterInfo::eNTCP2V4: case i2p::data::RouterInfo::eNTCP2V6: { if (!m_NTCP2Server) continue; std::shared_ptr address = (tr == i2p::data::RouterInfo::eNTCP2V6) ? peer->router->GetPublishedNTCP2V6Address () : peer->router->GetPublishedNTCP2V4Address (); if (address && IsInReservedRange(address->host)) address = nullptr; if (address) { 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)) return true; } break; } case i2p::data::RouterInfo::eNTCP2V6Mesh: { if (!m_NTCP2Server) continue; auto address = peer->router->GetYggdrasilAddress (); if (address) { auto s = std::make_shared (*m_NTCP2Server, peer->router, address); m_NTCP2Server->Connect (s); return true; } break; } default: LogPrint (eLogError, "Transports: Unknown transport ", (int)tr); } } LogPrint (eLogInfo, "Transports: No compatible addresses available"); if (!i2p::context.IsLimitedConnectivity () && peer->router->IsReachableFrom (i2p::context.GetRouterInfo ())) i2p::data::netdb.SetUnreachable (ident, true); // we are here because all connection attempts failed but router claimed them peer->Done (); std::lock_guard l(m_PeersMutex); m_Peers.erase (ident); return false; } else if (i2p::data::IsRouterBanned (ident)) { LogPrint (eLogWarning, "Transports: Router ", ident.ToBase64 (), " is banned. Peer dropped"); peer->Done (); std::lock_guard l(m_PeersMutex); m_Peers.erase (ident); return false; } else // otherwise request RI { LogPrint (eLogInfo, "Transports: RouterInfo for ", ident.ToBase64 (), " not found, requested"); i2p::data::netdb.RequestDestination (ident, std::bind ( &Transports::RequestComplete, this, std::placeholders::_1, ident)); } 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 (); bool isReal = peer->router->GetProfile ()->IsReal (); bool ssu2 = isReal ? (m_Rng () & 1) : 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)); } void Transports::HandleRequestComplete (std::shared_ptr r, i2p::data::IdentHash ident) { std::shared_ptr peer; { std::lock_guard l(m_PeersMutex); auto it = m_Peers.find (ident); if (it != m_Peers.end ()) { if (r) peer = it->second; else m_Peers.erase (it); } } if (peer && !peer->router && r) { LogPrint (eLogDebug, "Transports: RouterInfo for ", ident.ToBase64 (), " found, trying to connect"); peer->SetRouter (r); if (!peer->IsConnected ()) ConnectToPeer (ident, peer); } else if (!r) LogPrint (eLogInfo, "Transports: RouterInfo not found, failed to send messages"); } void Transports::DetectExternalIP () { if (RoutesRestricted()) { LogPrint(eLogInfo, "Transports: Restricted routes enabled, not detecting IP"); i2p::context.SetStatus (eRouterStatusOK); return; } if (m_SSU2Server) PeerTest (); else LogPrint (eLogWarning, "Transports: Can't detect external IP. SSU or SSU2 is not available"); } void Transports::PeerTest (bool ipv4, bool ipv6) { if (RoutesRestricted() || !m_SSU2Server || m_SSU2Server->UsesProxy ()) return; if (ipv4 && i2p::context.SupportsV4 ()) { LogPrint (eLogInfo, "Transports: Started peer test IPv4"); std::unordered_set excluded; excluded.insert (i2p::context.GetIdentHash ()); // don't pick own router int testDelay = 0; for (int i = 0; i < 5; i++) { auto router = i2p::data::netdb.GetRandomSSU2PeerTestRouter (true, excluded); // v4 if (router) { if (!i2p::context.GetTesting ()) { i2p::context.SetTesting (true); // send first peer test immediately m_SSU2Server->StartPeerTest (router, true); } else { testDelay += PEER_TEST_DELAY_INTERVAL + m_Rng() % PEER_TEST_DELAY_INTERVAL_VARIANCE; if (m_Service) { auto delayTimer = std::make_shared(*m_Service); delayTimer->expires_from_now (boost::posix_time::milliseconds (testDelay)); delayTimer->async_wait ( [this, router, delayTimer](const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) m_SSU2Server->StartPeerTest (router, true); }); } } excluded.insert (router->GetIdentHash ()); } } 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); }); } } excluded.insert (router->GetIdentHash ()); } } if (excluded.size () <= 1) LogPrint (eLogWarning, "Transports: Can't find routers for peer test IPv6"); } } std::shared_ptr Transports::GetNextX25519KeysPair () { return m_X25519KeysPairSupplier.Acquire (); } void Transports::ReuseX25519KeysPair (std::shared_ptr pair) { m_X25519KeysPairSupplier.Return (pair); } void Transports::PeerConnected (std::shared_ptr session) { boost::asio::post (*m_Service, [session, this]() { auto remoteIdentity = session->GetRemoteIdentity (); if (!remoteIdentity) return; auto ident = remoteIdentity->GetIdentHash (); 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 bool sendDatabaseStore = true; if (it->second->delayedMessages.size () > 0) { // check if first message is our DatabaseStore (publishing) auto firstMsg = peer->delayedMessages.front (); if (firstMsg && firstMsg->GetTypeID () == eI2NPDatabaseStore && i2p::data::IdentHash(firstMsg->GetPayload () + DATABASE_STORE_KEY_OFFSET) == i2p::context.GetIdentHash ()) sendDatabaseStore = false; // we have it in the list already } if (sendDatabaseStore) session->SendLocalRouterInfo (); else session->SetTerminationTimeout (10); // most likely it's publishing, no follow-up messages expected, set timeout to 10 seconds peer->sessions.push_back (session); session->SendI2NPMessages (peer->delayedMessages); // send and clear } else // incoming connection or peer test { if(RoutesRestricted() && ! IsRestrictedPeer(ident)) { // not trusted 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); } }); } void Transports::PeerDisconnected (std::shared_ptr session) { boost::asio::post (*m_Service, [session, this]() { auto remoteIdentity = session->GetRemoteIdentity (); if (!remoteIdentity) return; auto ident = remoteIdentity->GetIdentHash (); 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 ()) { if (peer->delayedMessages.size () > 0) { if (wasConnected) // we had an active session before peer->numAttempts = 0; // start over ConnectToPeer (ident, peer); } 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 (); } } } }); } 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 auto it = m_Peers.find (ident); return it != m_Peers.end (); #endif } void Transports::HandlePeerCleanupTimer (const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) { 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) { 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); 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)); m_PeerCleanupTimer->async_wait (std::bind (&Transports::HandlePeerCleanupTimer, this, std::placeholders::_1)); } } void Transports::HandlePeerTestTimer (const boost::system::error_code& ecode) { 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->async_wait (std::bind (&Transports::HandlePeerTestTimer, this, std::placeholders::_1)); } } template std::shared_ptr Transports::GetRandomPeer (Filter filter) const { if (m_Peers.empty()) return nullptr; auto ts = i2p::util::GetSecondsSinceEpoch (); bool found = false; i2p::data::IdentHash ident; { uint16_t inds[3]; RAND_bytes ((uint8_t *)inds, sizeof (inds)); std::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; } 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) { 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); } } void Transports::RestrictRoutesToRouters(const std::set& routers) { std::lock_guard lock(m_TrustedRoutersMutex); m_TrustedRouters.clear(); for (const auto & ri : routers ) 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; } /** XXX: if routes are not restricted this dies */ std::shared_ptr Transports::GetRestrictedPeer() { { std::lock_guard l(m_FamilyMutex); i2p::data::FamilyID fam = 0; auto sz = m_TrustedFamilies.size(); if(sz > 1) { auto it = m_TrustedFamilies.begin (); std::advance(it, m_Rng() % sz); fam = *it; } else if (sz == 1) { fam = m_TrustedFamilies[0]; } if (fam) return i2p::data::netdb.GetRandomRouterInFamily(fam); } { std::lock_guard l(m_TrustedRoutersMutex); auto sz = m_TrustedRouters.size(); if (sz) { if(sz == 1) return i2p::data::netdb.FindRouter(m_TrustedRouters[0]); auto it = m_TrustedRouters.begin(); std::advance(it, m_Rng() % sz); return i2p::data::netdb.FindRouter(*it); } } return nullptr; } bool Transports::IsRestrictedPeer(const i2p::data::IdentHash & ih) const { { std::lock_guard l(m_TrustedRoutersMutex); for (const auto & r : m_TrustedRouters ) if ( r == ih ) return true; } { std::lock_guard l(m_FamilyMutex); auto ri = i2p::data::netdb.FindRouter(ih); for (const auto & fam : m_TrustedFamilies) if(ri->IsFamily(fam)) return true; } 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 } } } } i2pd-2.56.0/libi2pd/Transports.h000066400000000000000000000221641475272067700163450ustar00rootroot00000000000000/* * 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 "TransportSession.h" #include "SSU2.h" #include "NTCP2.h" #include "RouterInfo.h" #include "I2NPProtocol.h" #include "Identity.h" #include "util.h" namespace i2p { namespace transport { template class EphemeralKeysSupplier { // called from this file only, so implementation is in Transports.cpp public: EphemeralKeysSupplier (int size); ~EphemeralKeysSupplier (); void Start (); void Stop (); std::shared_ptr Acquire (); void Return (std::shared_ptr pair); private: void Run (); void CreateEphemeralKeys (int num); private: const int m_QueueSize; i2p::util::MemoryPoolMt m_KeysPool; std::queue > m_Queue; bool m_IsRunning; std::unique_ptr m_Thread; std::condition_variable m_Acquired; std::mutex m_AcquiredMutex; }; typedef EphemeralKeysSupplier X25519KeysPairSupplier; const int PEER_ROUTER_INFO_UPDATE_INTERVAL = 31*60; // in seconds const int PEER_ROUTER_INFO_UPDATE_INTERVAL_VARIANCE = 7*60; // in seconds const 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; 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; }; class Transports { public: Transports (); ~Transports (); void Start (bool enableNTCP2=true, bool enableSSU2=true); void Stop (); bool IsRunning () const { return m_IsRunning; } bool IsBoundSSU2() const { return m_SSU2Server != nullptr; } bool IsBoundNTCP2() const { return m_NTCP2Server != nullptr; } bool IsOnline() const { return m_IsOnline; }; void SetOnline (bool online); auto& GetService () { return *m_Service; }; std::shared_ptr GetNextX25519KeysPair (); void ReuseX25519KeysPair (std::shared_ptr pair); std::future > SendMessage (const i2p::data::IdentHash& ident, std::shared_ptr msg); std::future > SendMessages (const i2p::data::IdentHash& ident, std::list >&& msgs); void PeerConnected (std::shared_ptr session); void PeerDisconnected (std::shared_ptr session); bool IsConnected (const i2p::data::IdentHash& ident) const; void UpdateSentBytes (uint64_t numBytes) { m_TotalSentBytes += numBytes; }; void UpdateReceivedBytes (uint64_t numBytes) { m_TotalReceivedBytes += numBytes; }; uint64_t GetTotalSentBytes () const { return m_TotalSentBytes; }; 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 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; size_t GetNumPeers () const { return m_Peers.size (); }; std::shared_ptr GetRandomPeer (bool isHighBandwidth) const; /** get a trusted first hop for restricted routes */ std::shared_ptr GetRestrictedPeer(); /** 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); 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; 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 HandlePeerCleanupTimer (const boost::system::error_code& ecode); void HandlePeerTestTimer (const boost::system::error_code& ecode); void HandleUpdateBandwidthTimer (const boost::system::error_code& ecode); void UpdateBandwidthValues (int interval, uint32_t& in, uint32_t& out, uint32_t& transit); void DetectExternalIP (); template std::shared_ptr GetRandomPeer (Filter filter) const; private: volatile bool m_IsOnline; bool m_IsRunning, m_IsNAT, m_CheckReserved; std::thread * m_Thread; boost::asio::io_context * m_Service; boost::asio::executor_work_guard * m_Work; boost::asio::deadline_timer * m_PeerCleanupTimer, * m_PeerTestTimer, * m_UpdateBandwidthTimer; SSU2Server * m_SSU2Server; NTCP2Server * m_NTCP2Server; mutable std::mutex m_PeersMutex; std::unordered_map > m_Peers; X25519KeysPairSupplier m_X25519KeysPairSupplier; std::atomic m_TotalSentBytes, m_TotalReceivedBytes, m_TotalTransitTransmittedBytes; TrafficSample m_TrafficSamples[TRAFFIC_SAMPLE_COUNT]; int m_TrafficSamplePtr; // Bandwidth per second uint32_t m_InBandwidth, m_OutBandwidth, m_TransitBandwidth; // Bandwidth during last 15 seconds uint32_t m_InBandwidth15s, m_OutBandwidth15s, m_TransitBandwidth15s; // Bandwidth during last 5 minutes uint32_t m_InBandwidth5m, m_OutBandwidth5m, m_TransitBandwidth5m; /** which router families to trust for first hops */ std::vector m_TrustedFamilies; mutable std::mutex m_FamilyMutex; /** which routers for first hop to trust */ std::vector m_TrustedRouters; 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 decltype(m_Peers)& GetPeers () const { return m_Peers; }; }; extern Transports transports; void InitAddressFromIface (); void InitTransports (); } } #endif i2pd-2.56.0/libi2pd/Tunnel.cpp000066400000000000000000001014571475272067700157710ustar00rootroot00000000000000/* * 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 #include "Crypto.h" #include "RouterContext.h" #include "Log.h" #include "Timestamp.h" #include "I2NPProtocol.h" #include "Transports.h" #include "NetDb.hpp" #include "Config.h" #include "Tunnel.h" #include "TunnelPool.h" #include "util.h" #include "ECIESX25519AEADRatchetSession.h" namespace i2p { 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) { } Tunnel::~Tunnel () { } void Tunnel::Build (uint32_t replyMsgID, std::shared_ptr outboundTunnel) { auto numHops = m_Config->GetNumHops (); const int numRecords = numHops <= STANDARD_NUM_RECORDS ? STANDARD_NUM_RECORDS : MAX_NUM_RECORDS; auto msg = numRecords <= STANDARD_NUM_RECORDS ? NewI2NPShortMessage () : NewI2NPMessage (); *msg->GetPayload () = numRecords; const size_t recordSize = m_Config->IsShort () ? SHORT_TUNNEL_BUILD_RECORD_SIZE : TUNNEL_BUILD_RECORD_SIZE; msg->len += numRecords*recordSize + 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()())); // create real records uint8_t * records = msg->GetPayload () + 1; TunnelHopConfig * hop = m_Config->GetFirstHop (); int i = 0; while (hop) { uint32_t msgID; if (hop->next) // we set replyMsgID for last hop only RAND_bytes ((uint8_t *)&msgID, 4); else msgID = replyMsgID; hop->recordIndex = recordIndicies[i]; i++; hop->CreateBuildRequestRecord (records, msgID); hop = hop->next; } // fill up fake records with random data for (int i = numHops; i < numRecords; i++) { int idx = recordIndicies[i]; RAND_bytes (records + idx*recordSize, recordSize); } // decrypt real records hop = m_Config->GetLastHop ()->prev; while (hop) { // decrypt records after current hop TunnelHopConfig * hop1 = hop->next; while (hop1) { hop->DecryptRecord (records, hop1->recordIndex); 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); }; // 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); } 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; } 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; while (hop1) { auto idx = hop1->recordIndex; if (idx >= 0 && idx < num) hop->DecryptRecord (msg + 1, idx); else 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); 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); }); 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); 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 { auto latency = GetMeanLatency(); return latency >= lowerbound && latency <= upperbound; } void Tunnel::EncryptTunnelMsg (std::shared_ptr in, std::shared_ptr out) { const uint8_t * inPayload = in->GetPayload () + 4; uint8_t * outPayload = out->GetPayload () + 4; for (auto& it: m_Hops) { it.decryption.Decrypt (inPayload, outPayload); inPayload = outPayload; } } void Tunnel::SendTunnelDataMsg (std::shared_ptr msg) { LogPrint (eLogWarning, "Tunnel: Can't send I2NP messages without delivery instructions"); } std::vector > Tunnel::GetPeers () const { auto peers = GetInvertedPeers (); std::reverse (peers.begin (), peers.end ()); return peers; } std::vector > Tunnel::GetInvertedPeers () const { // hops are in inverted order std::vector > ret; for (const auto& it: m_Hops) ret.push_back (it.ident); return ret; } void Tunnel::SetState(TunnelState state) { m_State = state; } void Tunnel::VisitTunnelHops(TunnelHopVisitor v) { // hops are in inverted order, we must return 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; } } return false; } ZeroHopsInboundTunnel::ZeroHopsInboundTunnel (): InboundTunnel (std::make_shared ()), m_NumReceivedBytes (0) { } void ZeroHopsInboundTunnel::SendTunnelDataMsg (std::shared_ptr msg) { if (msg) { m_NumReceivedBytes += msg->GetLength (); msg->from = GetSharedFromThis (); HandleI2NPMessage (msg); } } void OutboundTunnel::SendTunnelDataMsgTo (const uint8_t * gwHash, uint32_t gwTunnel, std::shared_ptr msg) { TunnelMessageBlock block; block.tunnelID = 0; // Initialize tunnelID to a default value if (gwHash) { block.hash = gwHash; if (gwTunnel) { block.deliveryType = eDeliveryTypeTunnel; block.tunnelID = gwTunnel; // Set tunnelID only if gwTunnel is non-zero } else { block.deliveryType = eDeliveryTypeRouter; } } else { block.deliveryType = eDeliveryTypeLocal; } block.data = msg; SendTunnelDataMsgs({block}); } void OutboundTunnel::SendTunnelDataMsgs (const std::vector& msgs) { std::unique_lock l(m_SendMutex); for (auto& it : msgs) m_Gateway.PutTunnelDataMsg (it); m_Gateway.SendBuffer (); } void OutboundTunnel::HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg) { LogPrint (eLogError, "Tunnel: Incoming message for outbound tunnel ", GetTunnelID ()); } bool OutboundTunnel::Recreate () { if (!IsRecreated ()) { auto pool = GetTunnelPool (); if (pool) { SetRecreated (true); pool->RecreateOutboundTunnel (std::static_pointer_cast(shared_from_this ())); return true; } } return false; } ZeroHopsOutboundTunnel::ZeroHopsOutboundTunnel (): OutboundTunnel (std::make_shared ()), m_NumSentBytes (0) { } void ZeroHopsOutboundTunnel::SendTunnelDataMsgs (const std::vector& msgs) { for (auto& msg : msgs) { if (!msg.data) continue; m_NumSentBytes += msg.data->GetLength (); switch (msg.deliveryType) { case eDeliveryTypeLocal: HandleI2NPMessage (msg.data); break; case eDeliveryTypeTunnel: i2p::transport::transports.SendMessage (msg.hash, i2p::CreateTunnelGatewayMsg (msg.tunnelID, msg.data)); break; case eDeliveryTypeRouter: i2p::transport::transports.SendMessage (msg.hash, msg.data); break; default: LogPrint (eLogError, "Tunnel: Unknown delivery type ", (int)msg.deliveryType); } } } 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 () { 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); } std::shared_ptr Tunnels::GetPendingOutboundTunnel (uint32_t replyMsgID) { return GetPendingTunnel (replyMsgID, m_PendingOutboundTunnels); } template std::shared_ptr Tunnels::GetPendingTunnel (uint32_t replyMsgID, const std::map >& pendingTunnels) { auto it = pendingTunnels.find(replyMsgID); if (it != pendingTunnels.end () && it->second->GetState () == eTunnelStatePending) { it->second->SetState (eTunnelStateBuildReplyReceived); return it->second; } return nullptr; } std::shared_ptr Tunnels::GetNextInboundTunnel () { std::shared_ptr tunnel; size_t minReceived = 0; for (const auto& it : m_InboundTunnels) { if (!it->IsEstablished ()) continue; if (!tunnel || it->GetNumReceivedBytes () < minReceived) { tunnel = it; minReceived = it->GetNumReceivedBytes (); } } return tunnel; } std::shared_ptr Tunnels::GetNextOutboundTunnel () { if (m_OutboundTunnels.empty ()) return nullptr; uint32_t ind = m_Rng () % m_OutboundTunnels.size (), i = 0; std::shared_ptr tunnel; for (const auto& it: m_OutboundTunnels) { if (it->IsEstablished ()) { tunnel = it; i++; } if (i > ind && tunnel) break; } return tunnel; } std::shared_ptr Tunnels::CreateTunnelPool (int numInboundHops, int numOutboundHops, int numInboundTunnels, int numOutboundTunnels, int inboundVariance, int outboundVariance, bool isHighBandwidth) { auto pool = std::make_shared (numInboundHops, numOutboundHops, numInboundTunnels, numOutboundTunnels, inboundVariance, outboundVariance, isHighBandwidth); std::unique_lock l(m_PoolsMutex); m_Pools.push_back (pool); return pool; } void Tunnels::DeleteTunnelPool (std::shared_ptr pool) { if (pool) { StopTunnelPool (pool); { std::unique_lock l(m_PoolsMutex); m_Pools.remove (pool); } } } void Tunnels::StopTunnelPool (std::shared_ptr pool) { if (pool) { pool->SetActive (false); pool->DetachTunnels (); } } 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) { m_Thread->join (); delete m_Thread; m_Thread = 0; } } 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; while (m_IsRunning) { try { if (m_Queue.Wait (1,0)) // 1 sec { m_Queue.GetWholeQueue (msgs); int numMsgs = 0; uint32_t prevTunnelID = 0, tunnelID = 0; std::shared_ptr prevTunnel; while (!msgs.empty ()) { auto msg = msgs.front (); msgs.pop_front (); if (!msg) continue; std::shared_ptr tunnel; uint8_t typeID = msg->GetTypeID (); switch (typeID) { case eI2NPTunnelData: case eI2NPTunnelGateway: { tunnelID = bufbe32toh (msg->GetPayload ()); if (tunnelID == prevTunnelID) tunnel = prevTunnel; else if (prevTunnel) prevTunnel->FlushTunnelDataMsgs (); if (!tunnel) tunnel = GetTunnel (tunnelID); if (tunnel) { if (typeID == eI2NPTunnelData) tunnel->HandleTunnelDataMsg (std::move (msg)); else // tunnel gateway assumed HandleTunnelGatewayMsg (tunnel, msg); } else 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; default: LogPrint (eLogWarning, "Tunnel: Unexpected message type ", (int) typeID); } prevTunnelID = tunnelID; prevTunnel = tunnel; numMsgs++; if (msgs.empty ()) { if (numMsgs < MAX_TUNNEL_MSGS_BATCH_SIZE && !m_Queue.IsEmpty ()) m_Queue.GetWholeQueue (msgs); // try more else if (tunnel) tunnel->FlushTunnelDataMsgs (); // otherwise flush last } } } if (i2p::transport::transports.IsOnline()) { uint64_t ts = i2p::util::GetSecondsSinceEpoch (); if (ts - lastTs >= TUNNEL_MANAGE_INTERVAL || // manage tunnels every 15 seconds ts + TUNNEL_MANAGE_INTERVAL < lastTs) { 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; } } } catch (std::exception& ex) { LogPrint (eLogError, "Tunnel: Runtime exception: ", ex.what ()); } } } void Tunnels::HandleTunnelGatewayMsg (std::shared_ptr tunnel, std::shared_ptr msg) { if (!tunnel) { LogPrint (eLogError, "Tunnel: Missing tunnel for gateway"); return; } const uint8_t * payload = msg->GetPayload (); uint16_t len = bufbe16toh(payload + TUNNEL_GATEWAY_HEADER_LENGTH_OFFSET); // we make payload as new I2NP message to send 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); 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); tunnel->SendTunnelDataMsg (msg); } void Tunnels::HandleShortTunnelBuildMsg (std::shared_ptr msg) { if (!msg) return; auto tunnel = GetPendingInboundTunnel (msg->GetMsgID()); // replyMsgID if (tunnel) { // endpoint of inbound tunnel LogPrint (eLogDebug, "Tunnel: ShortTunnelBuild reply for tunnel ", tunnel->GetTunnelID ()); if (tunnel->HandleTunnelBuildResponse (msg->GetPayload(), msg->GetPayloadLength())) { LogPrint (eLogInfo, "Tunnel: Inbound tunnel ", tunnel->GetTunnelID (), " has been created"); tunnel->SetState (eTunnelStateEstablished); AddInboundTunnel (tunnel); } else { LogPrint (eLogInfo, "Tunnel: Inbound tunnel ", tunnel->GetTunnelID (), " has been declined"); tunnel->SetState (eTunnelStateBuildFailed); } return; } else m_TransitTunnels.PostTransitTunnelBuildMsg (std::move (msg)); } void Tunnels::HandleVariableTunnelBuildMsg (std::shared_ptr msg) { auto tunnel = GetPendingInboundTunnel (msg->GetMsgID()); // replyMsgID if (tunnel) { // endpoint of inbound tunnel LogPrint (eLogDebug, "Tunnel: VariableTunnelBuild reply for tunnel ", tunnel->GetTunnelID ()); if (tunnel->HandleTunnelBuildResponse (msg->GetPayload(), msg->GetPayloadLength())) { LogPrint (eLogInfo, "Tunnel: Inbound tunnel ", tunnel->GetTunnelID (), " has been created"); tunnel->SetState (eTunnelStateEstablished); AddInboundTunnel (tunnel); } else { LogPrint (eLogInfo, "Tunnel: Inbound tunnel ", tunnel->GetTunnelID (), " has been declined"); tunnel->SetState (eTunnelStateBuildFailed); } } else m_TransitTunnels.PostTransitTunnelBuildMsg (std::move (msg)); } void Tunnels::HandleTunnelBuildReplyMsg (std::shared_ptr msg, bool isShort) { auto tunnel = GetPendingOutboundTunnel (msg->GetMsgID()); // replyMsgID if (tunnel) { // reply for outbound tunnel LogPrint (eLogDebug, "Tunnel: TunnelBuildReply for tunnel ", tunnel->GetTunnelID ()); if (tunnel->HandleTunnelBuildResponse (msg->GetPayload(), msg->GetPayloadLength())) { LogPrint (eLogInfo, "Tunnel: Outbound tunnel ", tunnel->GetTunnelID (), " has been created"); tunnel->SetState (eTunnelStateEstablished); AddOutboundTunnel (tunnel); } else { LogPrint (eLogInfo, "Tunnel: Outbound tunnel ", tunnel->GetTunnelID (), " has been declined"); tunnel->SetState (eTunnelStateBuildFailed); } } else LogPrint (eLogWarning, "Tunnel: Pending tunnel for message ", msg->GetMsgID(), " not found"); } void Tunnels::ManageTunnels (uint64_t ts) { ManagePendingTunnels (ts); std::vector > tunnelsToRecreate; ManageInboundTunnels (ts, tunnelsToRecreate); ManageOutboundTunnels (ts, tunnelsToRecreate); // rec-create in random order if (!tunnelsToRecreate.empty ()) { if (tunnelsToRecreate.size () > 1) std::shuffle (tunnelsToRecreate.begin(), tunnelsToRecreate.end(), m_Rng); for (auto& it: tunnelsToRecreate) it->Recreate (); } } void Tunnels::ManagePendingTunnels (uint64_t ts) { ManagePendingTunnels (m_PendingInboundTunnels, ts); ManagePendingTunnels (m_PendingOutboundTunnels, ts); } template void Tunnels::ManagePendingTunnels (PendingTunnels& pendingTunnels, uint64_t ts) { // check pending tunnel. delete failed or timeout for (auto it = pendingTunnels.begin (); it != pendingTunnels.end ();) { auto tunnel = it->second; switch (tunnel->GetState ()) { case eTunnelStatePending: if (ts > tunnel->GetCreationTime () + TUNNEL_CREATION_TIMEOUT || ts + TUNNEL_CREATION_TIMEOUT < tunnel->GetCreationTime ()) { LogPrint (eLogDebug, "Tunnel: Pending build request ", it->first, " timeout, deleted"); // update stats auto config = tunnel->GetTunnelConfig (); if (config) { auto hop = config->GetFirstHop (); while (hop) { if (hop->ident) i2p::data::UpdateRouterProfile (hop->ident->GetIdentHash (), [](std::shared_ptr profile) { if (profile) profile->TunnelNonReplied (); }); hop = hop->next; } } // delete it = pendingTunnels.erase (it); FailedTunnelCreation(); } else ++it; break; case eTunnelStateBuildFailed: LogPrint (eLogDebug, "Tunnel: Pending build request ", it->first, " failed, deleted"); it = pendingTunnels.erase (it); FailedTunnelCreation(); break; case eTunnelStateBuildReplyReceived: // intermediate state, will be either established of build failed ++it; break; default: // success it = pendingTunnels.erase (it); SuccesiveTunnelCreation(); } } } void Tunnels::ManageOutboundTunnels (uint64_t ts, std::vector >& toRecreate) { for (auto it = m_OutboundTunnels.begin (); it != m_OutboundTunnels.end ();) { auto tunnel = *it; if (tunnel->IsFailed () || ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT || ts + TUNNEL_EXPIRATION_TIMEOUT < tunnel->GetCreationTime ()) { 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 ()) { 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); } ++it; } } if (m_OutboundTunnels.size () < 3) { // trying to create one more outbound tunnel auto inboundTunnel = GetNextInboundTunnel (); auto router = i2p::transport::transports.RoutesRestricted() ? i2p::transport::transports.GetRestrictedPeer() : i2p::data::netdb.GetRandomRouter (i2p::context.GetSharedRouterInfo (), false, true, false); // reachable by us if (!inboundTunnel || !router) return; LogPrint (eLogDebug, "Tunnel: Creating one hop outbound tunnel"); CreateTunnel ( std::make_shared (std::vector > { router->GetRouterIdentity () }, inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash (), false), nullptr ); } } void Tunnels::ManageInboundTunnels (uint64_t ts, std::vector >& toRecreate) { for (auto it = m_InboundTunnels.begin (); it != m_InboundTunnels.end ();) { auto tunnel = *it; if (tunnel->IsFailed () || ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT || ts + TUNNEL_EXPIRATION_TIMEOUT < tunnel->GetCreationTime ()) { 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 ()) { 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 (); } it++; } } if (m_InboundTunnels.empty ()) { LogPrint (eLogDebug, "Tunnel: Creating zero hops inbound tunnel"); CreateZeroHopsInboundTunnel (nullptr); CreateZeroHopsOutboundTunnel (nullptr); 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->SetLocalDestination (i2p::context.GetSharedDestination ()); } return; } if (m_OutboundTunnels.empty () || m_InboundTunnels.size () < 3) { // 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); if (!router) { LogPrint (eLogWarning, "Tunnel: Can't find any router, skip creating tunnel"); return; } LogPrint (eLogDebug, "Tunnel: Creating one hop inbound tunnel"); CreateTunnel ( std::make_shared (std::vector > { router->GetRouterIdentity () }, false), nullptr ); } } void Tunnels::ManageTunnelPools (uint64_t ts) { std::unique_lock l(m_PoolsMutex); for (auto& pool : m_Pools) { if (pool && pool->IsActive ()) pool->ManageTunnels (ts); } } void Tunnels::PostTunnelData (std::shared_ptr msg) { if (msg) m_Queue.Put (msg); } void Tunnels::PostTunnelData (std::list >& msgs) { m_Queue.Put (msgs); } template std::shared_ptr Tunnels::CreateTunnel (std::shared_ptr config, std::shared_ptr pool, 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); newTunnel->Build (replyMsgID, outboundTunnel); return newTunnel; } std::shared_ptr Tunnels::CreateInboundTunnel (std::shared_ptr config, std::shared_ptr pool, std::shared_ptr outboundTunnel) { if (config) return CreateTunnel(config, pool, outboundTunnel); else return CreateZeroHopsInboundTunnel (pool); } std::shared_ptr Tunnels::CreateOutboundTunnel (std::shared_ptr config, std::shared_ptr pool) { if (config) return CreateTunnel(config, pool); else return CreateZeroHopsOutboundTunnel (pool); } void Tunnels::AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr tunnel) { m_PendingInboundTunnels[replyMsgID] = tunnel; } void Tunnels::AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr tunnel) { m_PendingOutboundTunnels[replyMsgID] = tunnel; } void Tunnels::AddOutboundTunnel (std::shared_ptr newTunnel) { // we don't need to insert it to m_Tunnels m_OutboundTunnels.push_back (newTunnel); auto pool = newTunnel->GetTunnelPool (); if (pool && pool->IsActive ()) pool->TunnelCreated (newTunnel); else newTunnel->SetTunnelPool (nullptr); } void Tunnels::AddInboundTunnel (std::shared_ptr newTunnel) { if (AddTunnel (newTunnel)) { m_InboundTunnels.push_back (newTunnel); auto pool = newTunnel->GetTunnelPool (); if (!pool) { // build symmetric outbound tunnel CreateTunnel (std::make_shared(newTunnel->GetInvertedPeers (), newTunnel->GetNextTunnelID (), newTunnel->GetNextIdentHash (), false), nullptr, GetNextOutboundTunnel ()); } else { if (pool->IsActive ()) pool->TunnelCreated (newTunnel); else newTunnel->SetTunnelPool (nullptr); } } else LogPrint (eLogError, "Tunnel: Tunnel with id ", newTunnel->GetTunnelID (), " already exists"); } std::shared_ptr Tunnels::CreateZeroHopsInboundTunnel (std::shared_ptr pool) { auto inboundTunnel = std::make_shared (); inboundTunnel->SetTunnelPool (pool); inboundTunnel->SetState (eTunnelStateEstablished); m_InboundTunnels.push_back (inboundTunnel); AddTunnel (inboundTunnel); return inboundTunnel; } std::shared_ptr Tunnels::CreateZeroHopsOutboundTunnel (std::shared_ptr pool) { 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 (); } size_t Tunnels::CountTransitTunnels() const { return m_TransitTunnels.GetNumTransitTunnels (); } size_t Tunnels::CountInboundTunnels() const { // TODO: locking return m_InboundTunnels.size(); } size_t Tunnels::CountOutboundTunnels() const { // 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; } } } } i2pd-2.56.0/libi2pd/Tunnel.h000066400000000000000000000334501475272067700154330ustar00rootroot00000000000000/* * 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__ #include #include #include #include #include #include #include #include #include #include #include "util.h" #include "Queue.h" #include "Crypto.h" #include "TunnelConfig.h" #include "TunnelPool.h" #include "TransitTunnel.h" #include "TunnelEndpoint.h" #include "TunnelGateway.h" #include "TunnelBase.h" #include "I2NPProtocol.h" namespace i2p { namespace tunnel { 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 enum TunnelState { eTunnelStatePending, eTunnelStateBuildReplyReceived, eTunnelStateBuildFailed, eTunnelStateEstablished, eTunnelStateTestFailed, eTunnelStateFailed, eTunnelStateExpiring }; class OutboundTunnel; class InboundTunnel; class Tunnel: public TunnelBase, public std::enable_shared_from_this { struct TunnelHop { std::shared_ptr ident; i2p::crypto::TunnelDecryption decryption; }; public: /** function for visiting a hops stored in a tunnel */ typedef std::function)> TunnelHopVisitor; Tunnel (std::shared_ptr config); ~Tunnel (); void Build (uint32_t replyMsgID, std::shared_ptr outboundTunnel = nullptr); std::shared_ptr GetTunnelConfig () const { return m_Config; } std::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 IsFailed () const { return m_State == eTunnelStateFailed; }; bool IsRecreated () const { return m_IsRecreated; }; void SetRecreated (bool recreated) { m_IsRecreated = recreated; }; int GetNumHops () const { return m_Hops.size (); }; virtual bool IsInbound() const = 0; virtual bool Recreate () = 0; std::shared_ptr GetTunnelPool () const { return m_Pool; }; void SetTunnelPool (std::shared_ptr pool) { m_Pool = pool; }; bool HandleTunnelBuildResponse (uint8_t * msg, size_t len); // implements TunnelBase void SendTunnelDataMsg (std::shared_ptr msg) override; void EncryptTunnelMsg (std::shared_ptr in, std::shared_ptr out) override; /** @brief add latency sample */ void AddLatencySample(const int us) { m_Latency = LatencyIsKnown() ? (m_Latency + us) >> 1 : us; } /** @brief get this tunnel's estimated latency */ int GetMeanLatency() const { return (m_Latency + 500) / 1000; } /** @brief return true if this tunnel's latency fits in range [lowerbound, upperbound] */ bool LatencyFitsRange(int lowerbound, int upperbound) const; bool LatencyIsKnown() const { return m_Latency != UNKNOWN_LATENCY; } bool IsSlow () const { return LatencyIsKnown() && m_Latency > HIGH_LATENCY_PER_HOP*GetNumHops (); } /** visit all hops we currently store */ void VisitTunnelHops(TunnelHopVisitor v); private: std::shared_ptr m_Config; std::vector m_Hops; bool m_IsShortBuildMessage; std::shared_ptr m_Pool; // pool, tunnel belongs to, or null TunnelState m_State; i2p::data::RouterInfo::CompatibleTransports m_FarEndTransports; bool m_IsRecreated; // if tunnel is replaced by new, or new tunnel requested to replace int m_Latency; // in microseconds }; class OutboundTunnel: public Tunnel { public: OutboundTunnel (std::shared_ptr config): Tunnel (config), m_Gateway (*this), m_EndpointIdentHash (config->GetLastIdentHash ()) {}; void SendTunnelDataMsgTo (const uint8_t * gwHash, uint32_t gwTunnel, std::shared_ptr msg); virtual void SendTunnelDataMsgs (const std::vector& msgs); // multiple messages const i2p::data::IdentHash& GetEndpointIdentHash () const { return m_EndpointIdentHash; }; virtual size_t GetNumSentBytes () const { return m_Gateway.GetNumSentBytes (); }; // implements TunnelBase void HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg) override; bool IsInbound() const override { return false; } bool Recreate () override; private: std::mutex m_SendMutex; TunnelGateway m_Gateway; i2p::data::IdentHash m_EndpointIdentHash; }; class InboundTunnel: public Tunnel { public: InboundTunnel (std::shared_ptr config): Tunnel (config), m_Endpoint (true) {}; void HandleTunnelDataMsg (std::shared_ptr&& msg) override; virtual size_t GetNumReceivedBytes () const { return m_Endpoint.GetNumReceivedBytes (); }; bool IsInbound() const override { return true; } bool Recreate () override; // override TunnelBase void Cleanup () override { m_Endpoint.Cleanup (); }; protected: std::shared_ptr GetSharedFromThis () { return std::static_pointer_cast(shared_from_this ()); } private: TunnelEndpoint m_Endpoint; }; class ZeroHopsInboundTunnel: public InboundTunnel { public: ZeroHopsInboundTunnel (); void SendTunnelDataMsg (std::shared_ptr msg) override; size_t GetNumReceivedBytes () const override { return m_NumReceivedBytes; }; private: size_t m_NumReceivedBytes; }; class ZeroHopsOutboundTunnel: public OutboundTunnel { public: ZeroHopsOutboundTunnel (); void SendTunnelDataMsgs (const std::vector& msgs) override; size_t GetNumSentBytes () const override { return m_NumSentBytes; }; private: size_t m_NumSentBytes; }; class Tunnels { public: Tunnels (); ~Tunnels (); void Start (); void Stop (); std::shared_ptr GetPendingInboundTunnel (uint32_t replyMsgID); std::shared_ptr GetPendingOutboundTunnel (uint32_t replyMsgID); std::shared_ptr GetNextInboundTunnel (); 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 AddOutboundTunnel (std::shared_ptr newTunnel); void AddInboundTunnel (std::shared_ptr newTunnel); std::shared_ptr CreateInboundTunnel (std::shared_ptr config, std::shared_ptr pool, std::shared_ptr outboundTunnel); std::shared_ptr CreateOutboundTunnel (std::shared_ptr config, std::shared_ptr pool); void PostTunnelData (std::shared_ptr msg); void PostTunnelData (std::list >& msgs); // and cleanup msgs void 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); 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); 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); template void ManagePendingTunnels (PendingTunnels& pendingTunnels, uint64_t ts); void ManageTunnelPools (uint64_t ts); std::shared_ptr CreateZeroHopsInboundTunnel (std::shared_ptr pool); std::shared_ptr CreateZeroHopsOutboundTunnel (std::shared_ptr pool); // Calculating of tunnel creation success rate void SuccesiveTunnelCreation() { // total TCSR m_TotalNumSuccesiveTunnelCreations++; // A modified version of the EWMA algorithm, where alpha is increased at the beginning to accelerate similarity double alpha = TCSR_SMOOTHING_CONSTANT + (1 - TCSR_SMOOTHING_CONSTANT)/++m_TunnelCreationAttemptsNum; m_TunnelCreationSuccessRate = alpha * 1 + (1 - alpha) * m_TunnelCreationSuccessRate; } void FailedTunnelCreation() { m_TotalNumFailedTunnelCreations++; double alpha = TCSR_SMOOTHING_CONSTANT + (1 - TCSR_SMOOTHING_CONSTANT)/++m_TunnelCreationAttemptsNum; m_TunnelCreationSuccessRate = alpha * 0 + (1 - alpha) * m_TunnelCreationSuccessRate; } private: bool m_IsRunning; std::thread * m_Thread; i2p::util::MemoryPoolMt > m_I2NPTunnelEndpointMessagesMemoryPool; i2p::util::MemoryPoolMt > m_I2NPTunnelMessagesMemoryPool; std::map > m_PendingInboundTunnels; // by replyMsgID std::map > m_PendingOutboundTunnels; // by replyMsgID std::list > m_InboundTunnels; std::list > m_OutboundTunnels; mutable std::mutex m_TunnelsMutex; std::unordered_map > m_Tunnels; // tunnelID->tunnel known by this id mutable 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; 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 (); }; 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 totalNum = m_TotalNumSuccesiveTunnelCreations + m_TotalNumFailedTunnelCreations; return totalNum ? m_TotalNumSuccesiveTunnelCreations*100/totalNum : 0; } }; extern Tunnels tunnels; } } #endif i2pd-2.56.0/libi2pd/TunnelBase.cpp000066400000000000000000000037301475272067700165570ustar00rootroot00000000000000/* * 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 >(); } } } i2pd-2.56.0/libi2pd/TunnelBase.h000066400000000000000000000057531475272067700162330ustar00rootroot00000000000000/* * 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; const size_t TUNNEL_DATA_ENCRYPTED_SIZE = 1008; const size_t TUNNEL_DATA_MAX_PAYLOAD_SIZE = 1003; enum TunnelDeliveryType { eDeliveryTypeLocal = 0, eDeliveryTypeTunnel = 1, eDeliveryTypeRouter = 2 }; struct TunnelMessageBlock { TunnelDeliveryType deliveryType; i2p::data::IdentHash hash; uint32_t tunnelID; std::shared_ptr data; }; class TunnelBase { public: TunnelBase (uint32_t tunnelID, uint32_t nextTunnelID, const i2p::data::IdentHash& nextIdent): m_TunnelID (tunnelID), m_NextTunnelID (nextTunnelID), m_NextIdent (nextIdent), m_CreationTime (i2p::util::GetSecondsSinceEpoch ()) {}; virtual ~TunnelBase () {}; virtual void Cleanup () {}; 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; uint32_t GetNextTunnelID () const { return m_NextTunnelID; }; const i2p::data::IdentHash& GetNextIdentHash () const { return m_NextIdent; }; virtual uint32_t GetTunnelID () const { return m_TunnelID; }; // as known at our side uint32_t GetCreationTime () const { return m_CreationTime; }; void SetCreationTime (uint32_t t) { m_CreationTime = t; }; private: uint32_t m_TunnelID, m_NextTunnelID; i2p::data::IdentHash m_NextIdent; uint32_t m_CreationTime; // seconds since epoch }; struct TunnelCreationTimeCmp { template bool operator() (const std::shared_ptr & t1, const 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; }; } } #endif i2pd-2.56.0/libi2pd/TunnelConfig.cpp000066400000000000000000000214641475272067700171160ustar00rootroot00000000000000/* * 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; } } }i2pd-2.56.0/libi2pd/TunnelConfig.h000066400000000000000000000147771475272067700165740ustar00rootroot00000000000000/* * 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 "Identity.h" #include "RouterContext.h" #include "Crypto.h" namespace i2p { namespace tunnel { struct TunnelHopConfig { std::shared_ptr ident; i2p::data::IdentHash nextIdent; uint32_t tunnelID, nextTunnelID; uint8_t layerKey[32]; uint8_t ivKey[32]; uint8_t replyKey[32]; uint8_t replyIV[16]; bool isGateway, isEndpoint; TunnelHopConfig * next, * prev; int recordIndex; // record # in tunnel build message TunnelHopConfig (std::shared_ptr r); virtual ~TunnelHopConfig () {}; 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); 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 }; 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; }; 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; }; 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; }; class TunnelConfig { public: TunnelConfig (const std::vector >& peers, bool isShort, i2p::data::RouterInfo::CompatibleTransports farEndTransports = i2p::data::RouterInfo::eAllTransports): // inbound m_IsShort (isShort), m_FarEndTransports (farEndTransports) { CreatePeers (peers); m_LastHop->SetNextIdent (i2p::context.GetIdentHash ()); } TunnelConfig (const std::vector >& peers, uint32_t replyTunnelID, const i2p::data::IdentHash& replyIdent, bool isShort, i2p::data::RouterInfo::CompatibleTransports farEndTransports = i2p::data::RouterInfo::eAllTransports): // outbound m_IsShort (isShort), m_FarEndTransports (farEndTransports) { CreatePeers (peers); m_FirstHop->isGateway = false; m_LastHop->SetReplyHop (replyTunnelID, replyIdent); } virtual ~TunnelConfig () { TunnelHopConfig * hop = m_FirstHop; while (hop) { auto tmp = hop; hop = hop->next; delete tmp; } } bool IsShort () const { return m_IsShort; } i2p::data::RouterInfo::CompatibleTransports GetFarEndTransports () const { return m_FarEndTransports; } TunnelHopConfig * GetFirstHop () const { return m_FirstHop; } TunnelHopConfig * GetLastHop () const { return m_LastHop; } int GetNumHops () const { int num = 0; TunnelHopConfig * hop = m_FirstHop; while (hop) { num++; hop = hop->next; } return num; } bool IsEmpty () const { return !m_FirstHop; } virtual bool IsInbound () const { return m_FirstHop->isGateway; } virtual uint32_t GetTunnelID () const { if (!m_FirstHop) return 0; return IsInbound () ? m_LastHop->nextTunnelID : m_FirstHop->tunnelID; } virtual uint32_t GetNextTunnelID () const { if (!m_FirstHop) return 0; return m_FirstHop->tunnelID; } virtual const i2p::data::IdentHash& GetNextIdentHash () const { return m_FirstHop->ident->GetIdentHash (); } virtual const i2p::data::IdentHash& GetLastIdentHash () const { return m_LastHop->ident->GetIdentHash (); } std::vector > GetPeers () const { std::vector > peers; TunnelHopConfig * hop = m_FirstHop; while (hop) { peers.push_back (hop->ident); hop = hop->next; } 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) { } private: void CreatePeers (const std::vector >& peers); private: TunnelHopConfig * m_FirstHop, * m_LastHop; bool m_IsShort; i2p::data::RouterInfo::CompatibleTransports m_FarEndTransports; }; class ZeroHopsTunnelConfig: public TunnelConfig { public: ZeroHopsTunnelConfig () { RAND_bytes ((uint8_t *)&m_TunnelID, 4);}; bool IsInbound () const { return true; }; // TODO: uint32_t GetTunnelID () const { return m_TunnelID; }; uint32_t GetNextTunnelID () const { return m_TunnelID; }; const i2p::data::IdentHash& GetNextIdentHash () const { return i2p::context.GetIdentHash (); }; const i2p::data::IdentHash& GetLastIdentHash () const { return i2p::context.GetIdentHash (); }; private: uint32_t m_TunnelID; }; } } #endif i2pd-2.56.0/libi2pd/TunnelEndpoint.cpp000066400000000000000000000302021475272067700174570ustar00rootroot00000000000000/* * 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" #include "Log.h" #include "NetDb.hpp" #include "I2NPProtocol.h" #include "Transports.h" #include "RouterContext.h" #include "Timestamp.h" #include "TunnelEndpoint.h" namespace i2p { namespace tunnel { 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 if (zero) { uint8_t * fragment = zero + 1; // verify checksum memcpy (msg->GetPayload () + TUNNEL_DATA_MSG_SIZE, msg->GetPayload () + 4, 16); // copy iv to the end uint8_t hash[32]; SHA256(fragment, TUNNEL_DATA_MSG_SIZE -(fragment - msg->GetPayload ()) + 16, hash); // payload + iv if (memcmp (hash, decrypted, 4)) { LogPrint (eLogError, "TunnelMessage: Checksum verification failed"); return; } // process fragments while (fragment < decrypted + TUNNEL_DATA_ENCRYPTED_SIZE) { uint8_t flag = fragment[0]; fragment++; bool isFollowOnFragment = flag & 0x80, isLastFragment = true; uint32_t msgID = 0; int fragmentNum = 0; 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) { case eDeliveryTypeLocal: // 0 break; case eDeliveryTypeTunnel: // 1 m_CurrentMessage.tunnelID = bufbe32toh (fragment); fragment += 4; // tunnelID m_CurrentMessage.hash = i2p::data::IdentHash (fragment); fragment += 32; // hash break; case eDeliveryTypeRouter: // 2 m_CurrentMessage.hash = i2p::data::IdentHash (fragment); fragment += 32; // to hash break; default: ; } bool isFragmented = flag & 0x08; if (isFragmented) { // Message ID msgID = bufbe32toh (fragment); fragment += 4; m_CurrentMsgID = msgID; isLastFragment = false; } } else { // follow on msgID = bufbe32toh (fragment); // MessageID fragment += 4; fragmentNum = (flag >> 1) & 0x3F; // 6 bits isLastFragment = flag & 0x01; } uint16_t size = bufbe16toh (fragment); fragment += 2; // handle fragment if (isFollowOnFragment) { // 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; } } 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; if (isLastFragment) { // 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); } 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"); } void TunnelEndpoint::HandleFollowOnFragment (uint32_t msgID, bool isLastFragment, uint8_t fragmentNum, const uint8_t * fragment, size_t size) { auto it = m_IncompleteMessages.find (msgID); if (it != m_IncompleteMessages.end()) { auto& msg = it->second; if (fragmentNum == msg.nextFragmentNum) { if (ConcatFollowOnFragment (msg, fragment, size)) { if (isLastFragment) { // message complete HandleNextMessage (msg); m_IncompleteMessages.erase (it); } else { msg.nextFragmentNum++; HandleOutOfSequenceFragments (msgID, msg); } } else { LogPrint (eLogError, "TunnelMessage: Fragment ", fragmentNum, " 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); } } else { LogPrint (eLogDebug, "TunnelMessage: First fragment of message ", msgID, " not found, saved"); AddOutOfSequenceFragment (msgID, fragmentNum, isLastFragment, fragment, size); } } bool TunnelEndpoint::ConcatFollowOnFragment (TunnelMessageBlockEx& msg, const uint8_t * fragment, size_t size) const { 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); } void TunnelEndpoint::HandleOutOfSequenceFragments (uint32_t msgID, TunnelMessageBlockEx& msg) { while (ConcatNextOutOfSequenceFragment (msgID, msg)) { 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"); break; } } } bool TunnelEndpoint::ConcatNextOutOfSequenceFragment (uint32_t msgID, TunnelMessageBlockEx& msg) { auto it = m_OutOfSequenceFragments.find ((uint64_t)msgID << 32 | 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 (); 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); *newMsg = *(msg.data); msg.data = newMsg; } if (msg.data->Concat (it->second.data.data (), size) < size) // concatenate out-of-sync fragment LogPrint (eLogError, "TunnelMessage: Tunnel endpoint I2NP buffer overflow ", msg.data->maxLen); if (it->second.isLastFragment) // message complete msg.nextFragmentNum = 0; else msg.nextFragmentNum++; m_OutOfSequenceFragments.erase (it); return true; } return false; } void TunnelEndpoint::HandleNextMessage (const TunnelMessageBlock& msg) { if (!m_IsInbound && msg.data->IsExpired ()) { 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); switch (msg.deliveryType) { case eDeliveryTypeLocal: i2p::HandleI2NPMessage (msg.data); break; case eDeliveryTypeTunnel: if (!m_IsInbound) // outbound transit tunnel SendMessageTo (msg.hash, i2p::CreateTunnelGatewayMsg (msg.tunnelID, msg.data)); else LogPrint (eLogError, "TunnelMessage: Delivery type 'tunnel' arrived from an inbound tunnel, dropped"); break; case eDeliveryTypeRouter: if (!m_IsInbound) // outbound transit tunnel i2p::transport::transports.SendMessage (msg.hash, msg.data); // send right away, because most likely it's single message else // we shouldn't send this message. possible leakage LogPrint (eLogError, "TunnelMessage: Delivery type 'router' arrived from an inbound tunnel, dropped"); break; default: LogPrint (eLogError, "TunnelMessage: Unknown delivery type ", (int)msg.deliveryType); }; } void TunnelEndpoint::Cleanup () { auto ts = i2p::util::GetMillisecondsSinceEpoch (); // out-of-sequence fragments for (auto it = m_OutOfSequenceFragments.begin (); it != m_OutOfSequenceFragments.end ();) { if (ts > it->second.receiveTime + i2p::I2NP_MESSAGE_EXPIRATION_TIMEOUT) it = m_OutOfSequenceFragments.erase (it); else ++it; } // incomplete messages for (auto it = m_IncompleteMessages.begin (); it != m_IncompleteMessages.end ();) { if (ts > it->second.receiveTime + i2p::I2NP_MESSAGE_EXPIRATION_TIMEOUT) it = m_IncompleteMessages.erase (it); else ++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; } } } i2pd-2.56.0/libi2pd/TunnelEndpoint.h000066400000000000000000000055131475272067700171330ustar00rootroot00000000000000/* * 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 "I2NPProtocol.h" #include "TunnelBase.h" namespace i2p { namespace tunnel { class TunnelEndpoint final { struct TunnelMessageBlockEx: public TunnelMessageBlock { uint64_t receiveTime; // milliseconds since epoch uint8_t nextFragmentNum; }; 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; uint64_t receiveTime; // milliseconds since epoch std::vector data; }; public: TunnelEndpoint (bool isInbound): m_IsInbound (isInbound), m_NumReceivedBytes (0), m_CurrentMsgID (0) {}; ~TunnelEndpoint () = default; size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; }; void Cleanup (); void HandleDecryptedTunnelDataMsg (std::shared_ptr msg); void FlushI2NPMsgs (); const i2p::data::IdentHash * GetCurrentHash () const; // return null if not available const std::unique_ptr& GetSender () const { return m_Sender; }; private: void HandleFollowOnFragment (uint32_t msgID, bool isLastFragment, uint8_t fragmentNum, const uint8_t * fragment, size_t size); bool ConcatFollowOnFragment (TunnelMessageBlockEx& msg, const uint8_t * fragment, size_t size) const; // true if success void HandleCurrenMessageFollowOnFragment (const uint8_t * fragment, size_t size, bool isLastFragment); void HandleNextMessage (const TunnelMessageBlock& msg); void SendMessageTo (const i2p::data::IdentHash& to, std::shared_ptr msg); void AddOutOfSequenceFragment (uint32_t msgID, uint8_t fragmentNum, bool isLastFragment, const uint8_t * fragment, size_t size); bool ConcatNextOutOfSequenceFragment (uint32_t msgID, TunnelMessageBlockEx& msg); // true if something added void HandleOutOfSequenceFragments (uint32_t msgID, TunnelMessageBlockEx& msg); void AddIncompleteCurrentMessage (); private: std::unordered_map m_IncompleteMessages; std::unordered_map m_OutOfSequenceFragments; // ((msgID << 8) + fragment#)->fragment bool m_IsInbound; size_t m_NumReceivedBytes; TunnelMessageBlockEx m_CurrentMessage; uint32_t m_CurrentMsgID; // I2NP messages to send std::list > m_I2NPMsgs; // to send i2p::data::IdentHash m_CurrentHash; // send msgs to std::unique_ptr m_Sender; }; } } #endif i2pd-2.56.0/libi2pd/TunnelGateway.cpp000066400000000000000000000174751475272067700173210ustar00rootroot00000000000000/* * 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" #include "Log.h" #include "RouterContext.h" #include "Transports.h" #include "TunnelGateway.h" namespace i2p { namespace tunnel { TunnelGatewayBuffer::TunnelGatewayBuffer (): m_CurrentTunnelDataMsg (nullptr), m_RemainingSize (0), m_NonZeroRandomBuffer (nullptr) { } TunnelGatewayBuffer::~TunnelGatewayBuffer () { ClearTunnelDataMsgs (); if (m_NonZeroRandomBuffer) delete[] m_NonZeroRandomBuffer; } void TunnelGatewayBuffer::PutI2NPMsg (const TunnelMessageBlock& block) { bool messageCreated = false; 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; } // create delivery instructions uint8_t di[43]; // max delivery instruction length is 43 for tunnel size_t diLen = 1;// flag if (block.deliveryType != eDeliveryTypeLocal) // tunnel or router { if (block.deliveryType == eDeliveryTypeTunnel) { htobe32buf (di + diLen, block.tunnelID); diLen += 4; // tunnelID } memcpy (di + diLen, block.hash, 32); diLen += 32; //len } di[0] = block.deliveryType << 5; // set delivery type // 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 htobe16buf (di + diLen, msg->GetLength ()); diLen += 2; // size memcpy (m_CurrentTunnelDataMsg->buf + m_CurrentTunnelDataMsg->len, di, diLen); memcpy (m_CurrentTunnelDataMsg->buf + m_CurrentTunnelDataMsg->len + diLen, msg->GetBuffer (), msg->GetLength ()); m_CurrentTunnelDataMsg->len += diLen + msg->GetLength (); m_RemainingSize -= diLen + msg->GetLength (); if (!m_RemainingSize) CompleteCurrentTunnelDataMessage (); } else { if (diLen + 6 <= m_RemainingSize) { // delivery instructions fit uint32_t msgID; memcpy (&msgID, msg->GetHeader () + I2NP_HEADER_MSGID_OFFSET, 4); // in network bytes order size_t size = m_RemainingSize - diLen - 6; // 6 = 4 (msgID) + 2 (size) // first fragment di[0] |= 0x08; // fragmented htobuf32 (di + diLen, msgID); diLen += 4; // Message ID htobe16buf (di + diLen, size); diLen += 2; // size memcpy (m_CurrentTunnelDataMsg->buf + m_CurrentTunnelDataMsg->len, di, diLen); memcpy (m_CurrentTunnelDataMsg->buf + m_CurrentTunnelDataMsg->len + diLen, msg->GetBuffer (), size); m_CurrentTunnelDataMsg->len += diLen + size; CompleteCurrentTunnelDataMessage (); // follow on fragments int fragmentNumber = 1; while (size < msg->GetLength ()) { CreateCurrentTunnelDataMessage (); uint8_t * buf = m_CurrentTunnelDataMsg->GetBuffer (); buf[0] = 0x80 | (fragmentNumber << 1); // frag bool isLastFragment = false; size_t s = msg->GetLength () - size; if (s > TUNNEL_DATA_MAX_PAYLOAD_SIZE - 7) // 7 follow on instructions s = TUNNEL_DATA_MAX_PAYLOAD_SIZE - 7; else // last fragment { buf[0] |= 0x01; isLastFragment = true; } htobuf32 (buf + 1, msgID); //Message ID htobe16buf (buf + 5, s); // size memcpy (buf + 7, msg->GetBuffer () + size, s); m_CurrentTunnelDataMsg->len += s+7; if (isLastFragment) { if(m_RemainingSize < (s+7)) { LogPrint (eLogError, "TunnelGateway: remaining size overflow: ", m_RemainingSize, " < ", s+7); } else { m_RemainingSize -= s+7; if (m_RemainingSize == 0) CompleteCurrentTunnelDataMessage (); } } else CompleteCurrentTunnelDataMessage (); size += s; fragmentNumber++; } } else { // delivery instructions don't fit. Create new message CompleteCurrentTunnelDataMessage (); PutI2NPMsg (block); // don't delete msg because it's taken care inside } } } void TunnelGatewayBuffer::ClearTunnelDataMsgs () { m_TunnelDataMsgs.clear (); m_CurrentTunnelDataMsg = nullptr; } void TunnelGatewayBuffer::CreateCurrentTunnelDataMessage () { m_CurrentTunnelDataMsg = NewI2NPTunnelMessage (true); // tunnel endpoint is at least of two tunnel messages size // we reserve space for padding m_CurrentTunnelDataMsg->offset += TUNNEL_DATA_MSG_SIZE + I2NP_HEADER_SIZE; m_CurrentTunnelDataMsg->len = m_CurrentTunnelDataMsg->offset; m_RemainingSize = TUNNEL_DATA_MAX_PAYLOAD_SIZE; } void TunnelGatewayBuffer::CompleteCurrentTunnelDataMessage () { if (!m_CurrentTunnelDataMsg) return; uint8_t * payload = m_CurrentTunnelDataMsg->GetBuffer (); size_t size = m_CurrentTunnelDataMsg->len - m_CurrentTunnelDataMsg->offset; m_CurrentTunnelDataMsg->offset = m_CurrentTunnelDataMsg->len - TUNNEL_DATA_MSG_SIZE - I2NP_HEADER_SIZE; uint8_t * buf = m_CurrentTunnelDataMsg->GetPayload (); RAND_bytes (buf + 4, 16); // original IV memcpy (payload + size, buf + 4, 16); // copy IV for checksum uint8_t hash[32]; SHA256(payload, size+16, hash); memcpy (buf+20, hash, 4); // checksum payload[-1] = 0; // zero 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); } // we can't fill message header yet because encryption is required m_TunnelDataMsgs.push_back (m_CurrentTunnelDataMsg); m_CurrentTunnelDataMsg = nullptr; } void TunnelGateway::SendTunnelDataMsg (const TunnelMessageBlock& block) { if (block.data) { PutTunnelDataMsg (block); SendBuffer (); } } void TunnelGateway::PutTunnelDataMsg (const TunnelMessageBlock& block) { if (block.data) m_Buffer.PutI2NPMsg (block); } void TunnelGateway::SendBuffer () { // create list or tunnel messages m_Buffer.CompleteCurrentTunnelDataMessage (); std::list > newTunnelMsgs; const auto& tunnelDataMsgs = m_Buffer.GetTunnelDataMsgs (); for (auto& tunnelMsg : tunnelDataMsgs) { auto newMsg = CreateEmptyTunnelDataMsg (false); m_Tunnel.EncryptTunnelMsg (tunnelMsg, newMsg); htobe32buf (newMsg->GetPayload (), m_Tunnel.GetNextTunnelID ()); 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)); } } } i2pd-2.56.0/libi2pd/TunnelGateway.h000066400000000000000000000030501475272067700167460ustar00rootroot00000000000000/* * 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__ #include #include #include #include "I2NPProtocol.h" #include "TunnelBase.h" namespace i2p { namespace tunnel { class TunnelGatewayBuffer { public: TunnelGatewayBuffer (); ~TunnelGatewayBuffer (); void PutI2NPMsg (const TunnelMessageBlock& block); const std::vector >& GetTunnelDataMsgs () const { return m_TunnelDataMsgs; }; void ClearTunnelDataMsgs (); void CompleteCurrentTunnelDataMessage (); private: void CreateCurrentTunnelDataMessage (); private: std::vector > m_TunnelDataMsgs; std::shared_ptr m_CurrentTunnelDataMsg; size_t m_RemainingSize; uint8_t * m_NonZeroRandomBuffer; }; class TunnelGateway { public: 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; TunnelGatewayBuffer m_Buffer; size_t m_NumSentBytes; std::unique_ptr m_Sender; }; } } #endif i2pd-2.56.0/libi2pd/TunnelPool.cpp000066400000000000000000000664331475272067700166270ustar00rootroot00000000000000/* * 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 "Tunnel.h" #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" 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): 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) { 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 () { DetachTunnels (); } void TunnelPool::SetExplicitPeers (std::shared_ptr > explicitPeers) { m_ExplicitPeers = explicitPeers; if (m_ExplicitPeers) { int size = m_ExplicitPeers->size (); if (m_NumInboundHops > size) { m_NumInboundHops = size; LogPrint (eLogInfo, "Tunnels: Inbound tunnel length has been adjusted to ", size, " for explicit peers"); } if (m_NumOutboundHops > size) { m_NumOutboundHops = size; LogPrint (eLogInfo, "Tunnels: Outbound tunnel length has been adjusted to ", size, " for explicit peers"); } m_NumInboundTunnels = 1; m_NumOutboundTunnels = 1; } } void TunnelPool::DetachTunnels () { { std::unique_lock l(m_InboundTunnelsMutex); for (auto& it: m_InboundTunnels) it->SetTunnelPool (nullptr); m_InboundTunnels.clear (); } { std::unique_lock l(m_OutboundTunnelsMutex); for (auto& it: m_OutboundTunnels) 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; } void TunnelPool::TunnelCreated (std::shared_ptr createdTunnel) { if (!m_IsActive) return; { 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); } void TunnelPool::TunnelExpired (std::shared_ptr expiredTunnel) { if (expiredTunnel) { expiredTunnel->SetTunnelPool (nullptr); { std::unique_lock l(m_TestsMutex); for (auto& it: m_Tests) if (it.second.second == expiredTunnel) it.second.second = nullptr; } std::unique_lock l(m_InboundTunnelsMutex); m_InboundTunnels.erase (expiredTunnel); } } void TunnelPool::TunnelCreated (std::shared_ptr createdTunnel) { if (!m_IsActive) return; { std::unique_lock l(m_OutboundTunnelsMutex); m_OutboundTunnels.insert (createdTunnel); } } void TunnelPool::TunnelExpired (std::shared_ptr expiredTunnel) { if (expiredTunnel) { expiredTunnel->SetTunnelPool (nullptr); { std::unique_lock l(m_TestsMutex); for (auto& it: m_Tests) if (it.second.first == expiredTunnel) it.second.first = nullptr; } std::unique_lock l(m_OutboundTunnelsMutex); m_OutboundTunnels.erase (expiredTunnel); } } std::vector > TunnelPool::GetInboundTunnels (int num) const { 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++; } } } 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::unique_lock l(m_OutboundTunnelsMutex); return GetNextTunnel (m_OutboundTunnels, excluded, compatible); } std::shared_ptr TunnelPool::GetNextInboundTunnel (std::shared_ptr excluded, i2p::data::RouterInfo::CompatibleTransports compatible) { std::unique_lock l(m_InboundTunnelsMutex); return GetNextTunnel (m_InboundTunnels, excluded, compatible); } template typename TTunnels::value_type TunnelPool::GetNextTunnel (TTunnels& tunnels, typename TTunnels::value_type excluded, i2p::data::RouterInfo::CompatibleTransports compatible) { if (tunnels.empty ()) return nullptr; uint32_t ind = m_Rng () % (tunnels.size ()/2 + 1), i = 0; bool skipped = false; typename TTunnels::value_type tunnel = nullptr; for (const auto& it: tunnels) { if (it->IsEstablished () && it != excluded && (compatible & it->GetFarEndTransports ())) { if (it->IsSlow () || (HasLatencyRequirement() && it->LatencyIsKnown() && !it->LatencyFitsRange(m_MinLatency, m_MaxLatency))) { i++; skipped = true; continue; } tunnel = it; i++; } if (i > ind && tunnel) break; } if (!tunnel && skipped) { ind = m_Rng () % (tunnels.size ()/2 + 1), i = 0; for (const auto& it: tunnels) { if (it->IsEstablished () && it != excluded) { tunnel = it; i++; } if (i > ind && tunnel) break; } } if (!tunnel && excluded && excluded->IsEstablished ()) tunnel = excluded; return tunnel; } std::pair, bool> TunnelPool::GetNewOutboundTunnel (std::shared_ptr old) { if (old && old->IsEstablished ()) return std::make_pair(old, false); std::shared_ptr tunnel; bool freshTunnel = false; if (old) { std::unique_lock l(m_OutboundTunnelsMutex); for (const auto& it: m_OutboundTunnels) if (it->IsEstablished () && old->GetEndpointIdentHash () == it->GetEndpointIdentHash ()) { tunnel = it; break; } } if (!tunnel) { tunnel = GetNextOutboundTunnel (); freshTunnel = true; } return std::make_pair(tunnel, freshTunnel); } void TunnelPool::CreateTunnels () { int num = 0; { std::unique_lock l(m_OutboundTunnelsMutex); 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 (); } num = 0; { std::unique_lock l(m_InboundTunnelsMutex); 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 (); } if (num < m_NumInboundTunnels && m_NumInboundHops <= 0 && m_LocalDestination) // zero hops IB m_LocalDestination->SetLeaseSetUpdated (true); // update LeaseSet immediately } void TunnelPool::TestTunnels () { decltype(m_Tests) tests; { std::unique_lock l(m_TestsMutex); tests.swap(m_Tests); } for (auto& it: tests) { LogPrint (eLogWarning, "Tunnels: Test of tunnel ", it.first, " failed"); // if test failed again with another tunnel we consider it failed if (it.second.first) { if (it.second.first->GetState () == eTunnelStateTestFailed) { it.second.first->SetState (eTunnelStateFailed); std::unique_lock l(m_OutboundTunnelsMutex); if (m_OutboundTunnels.size () > 1 || m_NumOutboundTunnels <= 1) // don't fail last tunnel m_OutboundTunnels.erase (it.second.first); else it.second.first->SetState (eTunnelStateTestFailed); } else if (it.second.first->GetState () != eTunnelStateExpiring) it.second.first->SetState (eTunnelStateTestFailed); } if (it.second.second) { if (it.second.second->GetState () == eTunnelStateTestFailed) { it.second.second->SetState (eTunnelStateFailed); { bool failed = false; { std::unique_lock l(m_InboundTunnelsMutex); if (m_InboundTunnels.size () > 1 || m_NumInboundTunnels <= 1) // don't fail last tunnel { m_InboundTunnels.erase (it.second.second); failed = true; } else it.second.second->SetState (eTunnelStateTestFailed); } if (failed && m_LocalDestination) m_LocalDestination->SetLeaseSetUpdated (true); } if (m_LocalDestination) m_LocalDestination->SetLeaseSetUpdated (true); } else if (it.second.second->GetState () != eTunnelStateExpiring) it.second.second->SetState (eTunnelStateTestFailed); } } // new tests if (!m_LocalDestination) return; std::vector, std::shared_ptr > > newTests; std::vector > outboundTunnels; { std::unique_lock l(m_OutboundTunnelsMutex); for (auto& it: m_OutboundTunnels) if (it->IsEstablished ()) outboundTunnels.push_back (it); } std::shuffle (outboundTunnels.begin(), outboundTunnels.end(), m_Rng); std::vector > inboundTunnels; { std::unique_lock l(m_InboundTunnelsMutex); for (auto& it: m_InboundTunnels) if (it->IsEstablished ()) inboundTunnels.push_back (it); } std::shuffle (inboundTunnels.begin(), inboundTunnels.end(), m_Rng); auto it1 = outboundTunnels.begin (); auto it2 = inboundTunnels.begin (); while (it1 != outboundTunnels.end () && it2 != inboundTunnels.end ()) { newTests.push_back(std::make_pair (*it1, *it2)); ++it1; ++it2; } bool isECIES = m_LocalDestination->SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); for (auto& it: newTests) { uint32_t msgID; RAND_bytes ((uint8_t *)&msgID, 4); { std::unique_lock l(m_TestsMutex); m_Tests[msgID] = it; } auto msg = CreateTunnelTestMsg (msgID); auto outbound = it.first; auto s = shared_from_this (); msg->onDrop = [msgID, outbound, s]() { // if test msg dropped locally it's outbound tunnel to blame outbound->SetState (eTunnelStateFailed); { std::unique_lock l(s->m_TestsMutex); s->m_Tests.erase (msgID); } { std::unique_lock l(s->m_OutboundTunnelsMutex); s->m_OutboundTunnels.erase (outbound); } }; // encrypt if (isECIES) { uint8_t key[32]; RAND_bytes (key, 32); uint64_t tag; RAND_bytes ((uint8_t *)&tag, 8); m_LocalDestination->SubmitECIESx25519Key (key, tag); msg = i2p::garlic::WrapECIESX25519Message (msg, key, tag); } else { uint8_t key[32], tag[32]; RAND_bytes (key, 32); RAND_bytes (tag, 32); m_LocalDestination->SubmitSessionKey (key, tag); i2p::garlic::ElGamalAESSession garlic (key, tag); msg = garlic.WrapSingleMessage (msg); } outbound->SendTunnelDataMsgTo (it.second->GetNextIdentHash (), it.second->GetNextTunnelID (), msg); } } void TunnelPool::ManageTunnels (uint64_t ts) { 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; } } void TunnelPool::ProcessGarlicMessage (std::shared_ptr msg) { if (m_LocalDestination) m_LocalDestination->ProcessGarlicMessage (msg); else 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; { std::unique_lock l(m_TestsMutex); auto it = m_Tests.find (msgID); if (it != m_Tests.end ()) { found = true; test = it->second; m_Tests.erase (it); } } 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); } } 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 { bool tryClient = !IsExploratory () && !i2p::context.IsLimitedConnectivity (); std::shared_ptr hop; for (int i = 0; i < TUNNEL_POOL_MAX_HOP_SELECTION_ATTEMPTS; i++) { hop = tryClient ? (m_IsHighBandwidth ? i2p::data::netdb.GetHighBandwidthRandomRouter (prevHop, reverse, endpoint) : i2p::data::netdb.GetRandomRouter (prevHop, reverse, endpoint, true)): i2p::data::netdb.GetRandomRouter (prevHop, reverse, endpoint, false); if (hop) { if (!hop->HasProfile () || !hop->GetProfile ()->IsBad ()) break; } else if (tryClient) tryClient = false; else return nullptr; } return hop; } bool TunnelPool::StandardSelectPeers(Path & path, int numHops, bool inbound, SelectHopFunc nextHop) { int start = 0; std::shared_ptr 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); prevHop = hop; start++; } else if (i2p::transport::transports.GetNumPeers () > 100 || (inbound && i2p::transport::transports.GetNumPeers () > 25)) { auto r = i2p::transport::transports.GetRandomPeer (m_IsHighBandwidth && !i2p::context.IsLimitedConnectivity ()); if (r && r->IsECIES () && (!r->HasProfile () || !r->GetProfile ()->IsBad ()) && (numHops > 1 || (r->IsV4 () && (!inbound || r->IsPublished (true))))) // first inbound must be published ipv4 { prevHop = r; path.Add (r); start++; } } for(int i = start; 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; } if (!hop) { LogPrint (eLogError, "Tunnels: Can't select next hop for ", prevHop->GetIdentHashBase64 ()); return false; } prevHop = hop; path.Add (hop); } path.farEndTransports = prevHop->GetCompatibleTransports (inbound); // last hop return true; } bool TunnelPool::SelectPeers (Path& path, 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; } } // 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 StandardSelectPeers(path, numHops, isInbound, std::bind(&TunnelPool::SelectNextHop, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); } bool TunnelPool::SelectExplicitPeers (Path& path, bool isInbound) { if (!m_ExplicitPeers->size ()) return false; int numHops = isInbound ? m_NumInboundHops : m_NumOutboundHops; if (numHops > (int)m_ExplicitPeers->size ()) numHops = m_ExplicitPeers->size (); for (int i = 0; i < numHops; i++) { auto& ident = (*m_ExplicitPeers)[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; } } else { LogPrint (eLogInfo, "Tunnels: Can't find router for ", ident.ToBase64 ()); i2p::data::netdb.RequestDestination (ident); return false; } } return true; } void TunnelPool::CreateInboundTunnel () { LogPrint (eLogDebug, "Tunnels: Creating destination inbound tunnel..."); Path path; if (SelectPeers (path, 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); } auto tunnel = tunnels.CreateInboundTunnel (config, shared_from_this (), outboundTunnel); if (tunnel->IsEstablished ()) // zero hops TunnelCreated (tunnel); } else LogPrint (eLogError, "Tunnels: Can't create inbound tunnel, no peers available"); } 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 ()); 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); } } void TunnelPool::CreateOutboundTunnel () { LogPrint (eLogDebug, "Tunnels: Creating destination outbound tunnel..."); Path path; if (SelectPeers (path, false)) { auto inboundTunnel = GetNextInboundTunnel (nullptr, path.farEndTransports); if (!inboundTunnel) inboundTunnel = tunnels.GetNextInboundTunnel (); if (!inboundTunnel) { 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 ()); tunnel->SetTunnelPool (shared_from_this ()); } else tunnel = tunnels.CreateOutboundTunnel (config, shared_from_this ()); if (tunnel && tunnel->IsEstablished ()) // zero hops TunnelCreated (tunnel); } else LogPrint (eLogError, "Tunnels: Can't create outbound tunnel, no peers available"); } 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 ()); if (!inboundTunnel) inboundTunnel = tunnels.GetNextInboundTunnel (); if (inboundTunnel) { 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); } } else LogPrint (eLogDebug, "Tunnels: Can't re-create outbound tunnel, no inbound tunnels found"); } 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); } void TunnelPool::SetCustomPeerSelector(ITunnelPeerSelector * selector) { std::lock_guard lock(m_CustomPeerSelectorMutex); m_CustomPeerSelector = selector; } void TunnelPool::UnsetCustomPeerSelector() { SetCustomPeerSelector(nullptr); } bool TunnelPool::HasCustomPeerSelector() { std::lock_guard lock(m_CustomPeerSelectorMutex); 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; for (const auto & itr : m_InboundTunnels) { if(!itr->LatencyIsKnown()) continue; auto l = itr->GetMeanLatency(); if (l >= min) continue; tun = itr; if(tun == exclude) continue; min = l; } return tun; } std::shared_ptr TunnelPool::GetLowestLatencyOutboundTunnel(std::shared_ptr exclude) const { std::shared_ptr tun = nullptr; std::unique_lock lock(m_OutboundTunnelsMutex); int min = 1000000; for (const auto & itr : m_OutboundTunnels) { if(!itr->LatencyIsKnown()) continue; auto l = itr->GetMeanLatency(); if (l >= min) continue; tun = itr; if(tun == exclude) continue; min = l; } return tun; } } } i2pd-2.56.0/libi2pd/TunnelPool.h000066400000000000000000000161151475272067700162640ustar00rootroot00000000000000/* * 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__ #include #include #include #include #include #include #include #include "Identity.h" #include "LeaseSet.h" #include "RouterInfo.h" #include "I2NPProtocol.h" #include "TunnelBase.h" #include "RouterContext.h" #include "Garlic.h" 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 (); }; /** interface for custom tunnel peer selection algorithm */ struct ITunnelPeerSelector { virtual ~ITunnelPeerSelector() {}; virtual bool SelectPeers(Path & peers, int hops, bool isInbound) = 0; }; 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 (); std::shared_ptr GetLocalDestination () const { return m_LocalDestination; }; void SetLocalDestination (std::shared_ptr destination) { m_LocalDestination = destination; }; void SetExplicitPeers (std::shared_ptr > explicitPeers); void CreateTunnels (); void TunnelCreated (std::shared_ptr createdTunnel); void TunnelExpired (std::shared_ptr expiredTunnel); void TunnelCreated (std::shared_ptr createdTunnel); void TunnelExpired (std::shared_ptr expiredTunnel); 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); 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 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; // for overriding tunnel peer selection std::shared_ptr SelectNextHop (std::shared_ptr prevHop, bool reverse, bool endpoint) const; bool StandardSelectPeers(Path & path, int numHops, bool inbound, SelectHopFunc nextHop); std::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; private: std::shared_ptr m_LocalDestination; int m_NumInboundHops, m_NumOutboundHops, m_NumInboundTunnels, m_NumOutboundTunnels, m_InboundVariance, m_OutboundVariance; std::shared_ptr > m_ExplicitPeers; mutable std::mutex m_InboundTunnelsMutex; std::set, TunnelCreationTimeCmp> m_InboundTunnels; // recent tunnel appears first mutable std::mutex m_OutboundTunnelsMutex; 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 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 std::mt19937 m_Rng; public: // for HTTP only const decltype(m_OutboundTunnels)& GetOutboundTunnels () const { return m_OutboundTunnels; }; const decltype(m_InboundTunnels)& GetInboundTunnels () const { return m_InboundTunnels; }; }; } } #endif i2pd-2.56.0/libi2pd/api.cpp000066400000000000000000000101331475272067700152630ustar00rootroot00000000000000/* * 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" #include "Log.h" #include "NetDb.hpp" #include "Transports.h" #include "Tunnel.h" #include "RouterContext.h" #include "Identity.h" #include "Destination.h" #include "Crypto.h" #include "FS.h" #include "api.h" namespace i2p { namespace api { void InitI2P (int argc, char* argv[], const char * appName) { i2p::config::Init (); i2p::config::ParseCmdline (argc, argv, true); // ignore unknown options and help i2p::config::Finalize (); std::string datadir; i2p::config::GetOption("datadir", datadir); i2p::fs::SetAppName (appName); i2p::fs::DetectDataDir(datadir, false); i2p::fs::Init(); bool precomputation; i2p::config::GetOption("precomputation.elgamal", precomputation); i2p::crypto::InitCrypto (precomputation); int netID; i2p::config::GetOption("netid", netID); i2p::context.SetNetID (netID); bool checkReserved; i2p::config::GetOption("reservedrange", checkReserved); i2p::transport::transports.SetCheckReserved(checkReserved); i2p::context.Init (); } void TerminateI2P () { i2p::crypto::TerminateCrypto (); } void StartI2P (std::shared_ptr logStream) { if (logStream) i2p::log::Logger().SendTo (logStream); else i2p::log::Logger().SendTo (i2p::fs::DataDirPath (i2p::fs::GetAppName () + ".log")); i2p::log::Logger().Start (); i2p::transport::InitTransports (); LogPrint(eLogInfo, "API: Starting NetDB"); i2p::data::netdb.Start(); LogPrint(eLogInfo, "API: Starting Transports"); i2p::transport::transports.Start(); LogPrint(eLogInfo, "API: Starting Tunnels"); i2p::tunnel::tunnels.Start(); LogPrint(eLogInfo, "API: Starting Router context"); i2p::context.Start(); } void StopI2P () { LogPrint(eLogInfo, "API: Shutting down"); LogPrint(eLogInfo, "API: Stopping Router context"); i2p::context.Stop(); LogPrint(eLogInfo, "API: Stopping Tunnels"); i2p::tunnel::tunnels.Stop(); LogPrint(eLogInfo, "API: Stopping Transports"); i2p::transport::transports.Stop(); LogPrint(eLogInfo, "API: Stopping NetDB"); i2p::data::netdb.Stop(); i2p::log::Logger().Stop (); } void RunPeerTest () { i2p::transport::transports.PeerTest (); } std::shared_ptr CreateLocalDestination (const i2p::data::PrivateKeys& keys, bool isPublic, const std::map * params) { auto localDestination = std::make_shared (keys, isPublic, params); localDestination->Start (); return localDestination; } std::shared_ptr CreateLocalDestination (bool isPublic, i2p::data::SigningKeyType sigType, const std::map * params) { i2p::data::PrivateKeys keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType); auto localDestination = std::make_shared (keys, isPublic, params); localDestination->Start (); return localDestination; } void DestroyLocalDestination (std::shared_ptr dest) { if (dest) dest->Stop (); } void RequestLeaseSet (std::shared_ptr dest, const i2p::data::IdentHash& remote) { if (dest) dest->RequestDestination (remote); } std::shared_ptr CreateStream (std::shared_ptr dest, const i2p::data::IdentHash& remote) { if (!dest) return nullptr; auto leaseSet = dest->FindLeaseSet (remote); if (leaseSet) { auto stream = dest->CreateStream (leaseSet); stream->Send (nullptr, 0); // connect return stream; } else { RequestLeaseSet (dest, remote); return nullptr; } } void AcceptStream (std::shared_ptr dest, const i2p::stream::StreamingDestination::Acceptor& acceptor) { if (dest) dest->AcceptStreams (acceptor); } void DestroyStream (std::shared_ptr stream) { if (stream) stream->Close (); } } } i2pd-2.56.0/libi2pd/api.h000066400000000000000000000033761475272067700147430ustar00rootroot00000000000000/* * 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__ #include #include #include "Identity.h" #include "Destination.h" #include "Streaming.h" namespace i2p { namespace api { // initialization start and stop void InitI2P (int argc, char* argv[], const char * appName); void TerminateI2P (); void StartI2P (std::shared_ptr logStream = nullptr); // write system log to logStream, if not specified to .log in application's folder void StopI2P (); void RunPeerTest (); // should be called after UPnP // destinations std::shared_ptr CreateLocalDestination (const i2p::data::PrivateKeys& keys, bool isPublic = true, const std::map * params = nullptr); std::shared_ptr CreateLocalDestination (bool isPublic = false, i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256, const std::map * params = nullptr); // transient destinations usually not published void DestroyLocalDestination (std::shared_ptr dest); // streams void RequestLeaseSet (std::shared_ptr dest, const i2p::data::IdentHash& remote); std::shared_ptr CreateStream (std::shared_ptr dest, const i2p::data::IdentHash& remote); void AcceptStream (std::shared_ptr dest, const i2p::stream::StreamingDestination::Acceptor& acceptor); void DestroyStream (std::shared_ptr stream); } } #endif i2pd-2.56.0/libi2pd/util.cpp000066400000000000000000000465751475272067700155120ustar00rootroot00000000000000/* * 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 "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 #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 #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) { struct sockaddr_storage ss; int size = sizeof (ss); char src_copy[INET6_ADDRSTRLEN + 1]; ZeroMemory (&ss, sizeof (ss)); strncpy (src_copy, src, INET6_ADDRSTRLEN + 1); src_copy[INET6_ADDRSTRLEN] = 0; if (WSAStringToAddress (src_copy, af, NULL, (struct sockaddr *)&ss, &size) == 0) { switch (af) { case AF_INET: *(struct in_addr *)dst = ((struct sockaddr_in *)&ss)->sin_addr; return 1; case AF_INET6: *(struct in6_addr *)dst = ((struct sockaddr_in6 *)&ss)->sin6_addr; return 1; } } return 0; } const char *inet_ntop_xp(int af, const void *src, char *dst, socklen_t size) { struct sockaddr_storage ss; unsigned long s = size; ZeroMemory(&ss, sizeof(ss)); ss.ss_family = af; switch (af) { case AF_INET: ((struct sockaddr_in *)&ss)->sin_addr = *(struct in_addr *)src; break; case AF_INET6: ((struct sockaddr_in6 *)&ss)->sin6_addr = *(struct in6_addr *)src; break; default: return NULL; } /* cannot directly use &size because of strict aliasing rules */ return (WSAAddressToString((struct sockaddr *)&ss, sizeof(ss), NULL, dst, &s) == 0)? dst : NULL; } #else /* !_WIN32 => UNIX */ #include #ifdef ANDROID #include "ifaddrs.h" #else #include #endif #endif #define address_pair_v4(a,b) { 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) { 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 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); } 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; } pCurrAddresses = pAddresses; while (pCurrAddresses) { pUnicast = pCurrAddresses->FirstUnicastAddress; if (pUnicast == nullptr) LogPrint(eLogError, "NetIface: GetMTU: Not a unicast IPv4 address, this is not supported"); 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); 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; } int GetMTUWindowsIpv6 (sockaddr_in6 inputAddress, int fallback) { typedef const char *(* IPN)(int af, const void *src, char *dst, socklen_t size); IPN inetntop = (IPN)(void*)GetProcAddress (GetModuleHandle ("ws2_32.dll"), "InetNtop"); if (!inetntop) inetntop = inet_ntop_xp; // use own implementation if not found ULONG outBufLen = 0; PIP_ADAPTER_ADDRESSES pAddresses = nullptr; PIP_ADAPTER_ADDRESSES pCurrAddresses = nullptr; PIP_ADAPTER_UNICAST_ADDRESS pUnicast = nullptr; if (GetAdaptersAddresses(AF_INET6, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen) == 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: GetMTU: Enclosed GetAdaptersAddresses() call has failed"); FREE(pAddresses); return fallback; } 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"); while (pUnicast != nullptr) { LPSOCKADDR lpAddr = pUnicast->Address.lpSockaddr; sockaddr_in6 *localInterfaceAddress = (sockaddr_in6*) lpAddr; 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) { #ifdef UNICODE string localAddress_temporary = localAddress.to_string(); wstring localAddressUniversal(localAddress_temporary.begin(), localAddress_temporary.end()); #else 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; 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) { 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) { if (!ifa->ifa_addr) continue; 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_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) { 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); } else LogPrint(eLogError, "NetIface: Failed to create datagram socket"); } else LogPrint(eLogWarning, "NetIface: Interface for local address", localAddress.to_string(), " not found"); freeifaddrs(ifaddr); return mtu; } #endif // _WIN32 int GetMTU (const boost::asio::ip::address& localAddress) { int fallback = localAddress.is_v6 () ? 1280 : 620; // fallback MTU #ifdef _WIN32 return GetMTUWindows(localAddress, fallback); #else return GetMTUUnix(localAddress, fallback); #endif return fallback; } 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"); #else int af = (ipv6 ? AF_INET6 : AF_INET); ifaddrs *addrs; try { if (!getifaddrs(&addrs)) { 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) { // 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); } } } } catch (std::exception& ex) { LogPrint(eLogError, "NetIface: Exception while searching address using ifaddr: ", ex.what()); } if (addrs) freeifaddrs(addrs); std::string fallback; if (ipv6) { fallback = "::1"; 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); } return boost::asio::ip::make_address(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::vector< std::pair > reservedIPv4Ranges { address_pair_v4("0.0.0.0", "0.255.255.255"), address_pair_v4("10.0.0.0", "10.255.255.255"), address_pair_v4("100.64.0.0", "100.127.255.255"), 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::vector< std::pair > 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 i2pd-2.56.0/libi2pd/util.h000066400000000000000000000122001475272067700151310ustar00rootroot00000000000000/* * 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 #include #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); } } #endif #endif namespace i2p { namespace util { template class MemoryPool { //BOOST_STATIC_ASSERT_MSG(sizeof(T) >= sizeof(void*), "size cannot be less that general pointer size"); public: MemoryPool (): m_Head (nullptr) {} ~MemoryPool () { CleanUp (); } void CleanUp () { CleanUp (m_Head); m_Head = nullptr; } template T * Acquire (TArgs&&... args) { if (!m_Head) return new T(std::forward(args)...); else { auto tmp = m_Head; m_Head = static_cast(*(void * *)m_Head); // next return new (tmp)T(std::forward(args)...); } } void Release (T * t) { if (!t) return; t->~T (); *(void * *)t = m_Head; // next m_Head = t; } template std::unique_ptr > AcquireUnique (TArgs&&... args) { return std::unique_ptr >(Acquire (std::forward(args)...), std::bind (&MemoryPool::Release, this, std::placeholders::_1)); } template std::shared_ptr AcquireShared (TArgs&&... args) { return std::shared_ptr(Acquire (std::forward(args)...), 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 { public: MemoryPoolMt () {} template T * AcquireMt (TArgs&&... args) { if (!this->m_Head) return new T(std::forward(args)...); std::lock_guard l(m_Mutex); return this->Acquire (std::forward(args)...); } void ReleaseMt (T * t) { std::lock_guard l(m_Mutex); 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) { std::lock_guard l(m_Mutex); for (auto& it: c) 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; } } } #endif i2pd-2.56.0/libi2pd/version.h000066400000000000000000000021101475272067700156400ustar00rootroot00000000000000/* * 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_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 VERSION I2PD_VERSION #define I2PD_NET_ID 2 #define I2P_VERSION_MAJOR 0 #define I2P_VERSION_MINOR 9 #define I2P_VERSION_MICRO 65 #define I2P_VERSION_PATCH 0 #define I2P_VERSION MAKE_VERSION(I2P_VERSION_MAJOR, I2P_VERSION_MINOR, I2P_VERSION_MICRO) #define I2P_VERSION_NUMBER MAKE_VERSION_NUMBER(I2P_VERSION_MAJOR, I2P_VERSION_MINOR, I2P_VERSION_MICRO) #endif i2pd-2.56.0/libi2pd_client/000077500000000000000000000000001475272067700153465ustar00rootroot00000000000000i2pd-2.56.0/libi2pd_client/AddressBook.cpp000066400000000000000000001021731475272067700202560ustar00rootroot00000000000000/* * 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 "Base.h" #include "util.h" #include "Timestamp.h" #include "Identity.h" #include "FS.h" #include "Log.h" #include "HTTP.h" #include "NetDb.hpp" #include "ClientContext.h" #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 { // 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; }; bool AddressBookFilesystemStorage::Init() { storage.SetPlace(i2p::fs::GetDataDir()); // init storage if (storage.Init(i2p::data::GetBase32SubstitutionTable(), 32)) { // init ETags etagsPath = i2p::fs::StorageRootPath (storage, "etags"); if (!i2p::fs::Exists (etagsPath)) i2p::fs::CreateDirectory (etagsPath); // init address files indexPath = i2p::fs::StorageRootPath (storage, "addresses.csv"); localPath = i2p::fs::StorageRootPath (storage, "local.csv"); return true; } return false; } std::shared_ptr AddressBookFilesystemStorage::GetAddress (const i2p::data::IdentHash& ident) { auto ts = i2p::util::GetMonotonicSeconds (); { std::lock_guard l(m_FullAddressCacheMutex); auto it = m_FullAddressCache.find (ident); if (it != m_FullAddressCache.end ()) { it->second.second = ts; return std::make_shared(it->second.first.data (), it->second.first.size ()); } } if (!m_IsPersist) { LogPrint(eLogDebug, "Addressbook: Persistence is disabled"); return nullptr; } std::string filename = storage.Path(ident.ToBase32()); std::ifstream f(filename, std::ifstream::binary); 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) { 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); } void AddressBookFilesystemStorage::AddAddress (std::shared_ptr address) { if (!address) return; size_t len = address->GetFullLen (); std::vector buf; if (!len) return; // invalid address { std::lock_guard l(m_FullAddressCacheMutex); auto [it, inserted] = m_FullAddressCache.try_emplace (address->GetIdentHash(), len, i2p::util::GetMonotonicSeconds ()); if (inserted) address->ToBuffer (it->second.first.data (), len); if (m_IsPersist) buf = it->second.first; } if (m_IsPersist && !buf.empty ()) { std::string path = storage.Path(address->GetIdentHash().ToBase32()); std::ofstream f (path, std::ofstream::binary | std::ofstream::out); if (!f.is_open ()) { LogPrint (eLogError, "Addressbook: Can't open file ", path); return; } f.write ((const char *)buf.data (), len); } } void AddressBookFilesystemStorage::RemoveAddress (const i2p::data::IdentHash& ident) { { std::lock_guard l(m_FullAddressCacheMutex); m_FullAddressCache.erase (ident); } if (!m_IsPersist) return; storage.Remove( ident.ToBase32() ); } int AddressBookFilesystemStorage::LoadFromFile (const std::string& filename, Addresses& addresses) { int num = 0; std::ifstream f (filename, std::ifstream::in); // in text mode if (!f) return -1; addresses.clear (); while (!f.eof ()) { std::string s; getline(f, s); if (!s.length()) continue; // skip empty line std::size_t pos = s.find(','); if (pos != std::string::npos) { std::string name = s.substr(0, pos++); std::string addr = s.substr(pos); addresses[name] = std::make_shared
(addr); num++; } } return num; } int AddressBookFilesystemStorage::Load (Addresses& addresses) { int num = LoadFromFile (indexPath, addresses); if (num < 0) { LogPrint(eLogWarning, "Addressbook: Can't open ", indexPath); return 0; } LogPrint(eLogInfo, "Addressbook: Using index file ", indexPath); LogPrint (eLogInfo, "Addressbook: ", num, " addresses loaded from storage"); return num; } int AddressBookFilesystemStorage::LoadLocal (Addresses& addresses) { int num = LoadFromFile (localPath, addresses); if (num < 0) return 0; LogPrint (eLogInfo, "Addressbook: ", num, " local addresses loaded"); return num; } int AddressBookFilesystemStorage::Save (const Addresses& addresses) { 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); } return num; } void AddressBookFilesystemStorage::SaveEtag (const i2p::data::IdentHash& subscription, const std::string& etag, const std::string& lastModified) { std::string fname = etagsPath + i2p::fs::dirSep + subscription.ToBase32 () + ".txt"; std::ofstream f (fname, std::ofstream::out | std::ofstream::trunc); if (f) { f << etag << std::endl; f<< lastModified << std::endl; } } bool AddressBookFilesystemStorage::GetEtag (const i2p::data::IdentHash& subscription, std::string& etag, std::string& lastModified) { std::string fname = etagsPath + i2p::fs::dirSep + subscription.ToBase32 () + ".txt"; std::ifstream f (fname, std::ofstream::in); if (!f || f.eof ()) return false; std::getline (f, etag); if (f.eof ()) return false; std::getline (f, lastModified); 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 () { Stop (); } 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 (); } } void AddressBook::StartResolvers () { LoadLocal (); } void AddressBook::Stop () { StopLookups (); StopSubscriptions (); if (m_SubscriptionsUpdateTimer) { m_SubscriptionsUpdateTimer->cancel (); m_SubscriptionsUpdateTimer = nullptr; } if (m_AddressCacheUpdateTimer) { 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++) { 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; } } } if (!isDownloading) m_Downloading.get (); else LogPrint (eLogError, "Addressbook: Subscription download timeout"); } if (m_Storage) { m_Storage->Save (m_Addresses); delete m_Storage; m_Storage = nullptr; } m_DefaultSubscription = nullptr; m_Subscriptions.clear (); } std::shared_ptr AddressBook::GetAddress (std::string_view address) { 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; } else { pos = address.find (".i2p"); if (pos != std::string::npos) { if (!m_IsEnabled) return nullptr; auto addr = FindAddress (address); if (!addr) LookupAddress (address); // TODO: return addr; } } // if not .b32 we assume full base64 address i2p::data::IdentityEx dest; if (!dest.FromBase64 (address)) return nullptr; return std::make_shared(dest.GetIdentHash ()); } std::shared_ptr AddressBook::FindAddress (std::string_view address) { auto it = m_Addresses.find (address); if (it != m_Addresses.end ()) return it->second; return nullptr; } bool AddressBook::RecordExists (const std::string& address, const std::string& jump) { auto addr = FindAddress(address); if (!addr) return false; auto pos = jump.find(".b32.i2p"); if (pos != std::string::npos) { i2p::data::IdentHash identHash; if (identHash.FromBase32(jump.substr (0, pos)) && identHash == addr->identHash) return true; } else { i2p::data::IdentityEx ident; if (ident.FromBase64 (jump) && ident.GetIdentHash () == addr->identHash) return true; } return false; } void AddressBook::InsertAddress (const std::string& address, const std::string& jump) { auto pos = jump.find(".b32.i2p"); if (pos != std::string::npos) { m_Addresses[address] = std::make_shared
(jump.substr (0, pos)); LogPrint (eLogInfo, "Addressbook: Added ", address," -> ", jump); } 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); } } void AddressBook::InsertFullAddress (std::shared_ptr 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; } void AddressBook::LoadHosts () { if (!m_Storage) return; if (m_Storage->Load (m_Addresses) > 0) { m_IsLoaded = true; return; } // then try hosts.txt std::ifstream f (i2p::fs::DataDirPath("hosts.txt"), std::ifstream::in); // in text mode if (f.is_open ()) { 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) { std::unique_lock l(m_AddressBookMutex); int numAddresses = 0; bool incomplete = false; std::string s; while (!f.eof ()) { getline(f, s); if (!s.length() || s[0] == '#') continue; // skip empty or comment line size_t pos = s.find('='); if (pos != std::string::npos) { std::string name = s.substr(0, pos++); std::string addr = s.substr(pos); size_t pos = addr.find('#'); if (pos != std::string::npos) addr = addr.substr(0, pos); // remove comments pos = name.find(".b32.i2p"); if (pos != std::string::npos) { LogPrint (eLogError, "Addressbook: Skipped adding of b32 address: ", name); continue; } pos = name.find(".i2p"); if (pos == std::string::npos) { LogPrint (eLogError, "Addressbook: Malformed domain: ", name); continue; } auto ident = std::make_shared (); if (!ident->FromBase64(addr)) { 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->second->IsIdentHash () && it->second->identHash != ident->GetIdentHash () && // address changed? ident->GetSigningKeyType () != i2p::data::SIGNING_KEY_TYPE_DSA_SHA1) // don't replace by DSA { it->second->identHash = ident->GetIdentHash (); if (m_Storage) { m_Storage->AddAddress (ident); m_Storage->RemoveAddress (it->second->identHash); } LogPrint (eLogInfo, "Addressbook: Updated host: ", name); } } else { m_Addresses.emplace (name, std::make_shared
(ident->GetIdentHash ())); if (m_Storage) m_Storage->AddAddress (ident); if (is_update) LogPrint (eLogInfo, "Addressbook: Added new host: ", name); } } else incomplete = f.eof (); } LogPrint (eLogInfo, "Addressbook: ", numAddresses, " addresses processed"); if (numAddresses > 0) { if (!incomplete) m_IsLoaded = true; if (m_Storage) m_Storage->Save (m_Addresses); } return !incomplete; } void AddressBook::LoadSubscriptions () { if (!m_Subscriptions.size ()) { std::ifstream f (i2p::fs::DataDirPath ("subscriptions.txt"), std::ifstream::in); // in text mode if (f.is_open ()) { std::string s; while (!f.eof ()) { getline(f, s); if (s.empty () || s[0] == '#') continue; // skip empty line or comment 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); 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"); } } else LogPrint (eLogError, "Addressbook: Subscriptions already loaded"); } void AddressBook::LoadLocal () { if (!m_Storage) return; AddressBookStorage::Addresses 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 dest = context.FindLocalDestination (it1->second->identHash); if (dest) { // address is ours std::shared_ptr resolver; auto it2 = m_Resolvers.find (it1->second->identHash); 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)); } resolver->AddAddress (it.first, it.second->identHash); } } } } } bool AddressBook::GetEtag (const i2p::data::IdentHash& subscription, std::string& etag, std::string& lastModified) { if (m_Storage) return m_Storage->GetEtag (subscription, etag, lastModified); else return false; } void AddressBook::DownloadComplete (bool success, const i2p::data::IdentHash& subscription, const std::string& etag, const std::string& lastModified) { m_NumRetries++; int nextUpdateTimeout = m_NumRetries*CONTINIOUS_SUBSCRIPTION_RETRY_TIMEOUT; if (m_NumRetries > CONTINIOUS_SUBSCRIPTION_MAX_NUM_RETRIES || nextUpdateTimeout > CONTINIOUS_SUBSCRIPTION_UPDATE_TIMEOUT) nextUpdateTimeout = CONTINIOUS_SUBSCRIPTION_UPDATE_TIMEOUT; if (success) { m_NumRetries = 0; if (m_DefaultSubscription) m_DefaultSubscription = nullptr; if (m_IsLoaded) nextUpdateTimeout = CONTINIOUS_SUBSCRIPTION_UPDATE_TIMEOUT; else m_IsLoaded = true; if (m_Storage) m_Storage->SaveEtag (subscription, etag, lastModified); } if (m_SubscriptionsUpdateTimer) { m_SubscriptionsUpdateTimer->expires_from_now (boost::posix_time::minutes(nextUpdateTimeout)); m_SubscriptionsUpdateTimer->async_wait (std::bind (&AddressBook::HandleSubscriptionsUpdateTimer, this, std::placeholders::_1)); } } void AddressBook::StartSubscriptions () { LoadSubscriptions (); if (m_IsLoaded && m_Subscriptions.empty ()) return; auto dest = i2p::client::context.GetSharedLocalDestination (); if (dest) { m_SubscriptionsUpdateTimer = std::make_unique(dest->GetService ()); m_SubscriptionsUpdateTimer->expires_from_now (boost::posix_time::minutes(INITIAL_SUBSCRIPTION_UPDATE_TIMEOUT)); m_SubscriptionsUpdateTimer->async_wait (std::bind (&AddressBook::HandleSubscriptionsUpdateTimer, this, std::placeholders::_1)); } else LogPrint (eLogCritical, "Addressbook: Can't start subscriptions: missing shared local destination"); } void AddressBook::StopSubscriptions () { if (m_SubscriptionsUpdateTimer) m_SubscriptionsUpdateTimer->cancel (); } void AddressBook::HandleSubscriptionsUpdateTimer (const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) { auto dest = i2p::client::context.GetSharedLocalDestination (); if (!dest) { 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_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); if (!m_DefaultSubscription) m_DefaultSubscription = std::make_shared(*this, defaultSubURL); m_Downloading = std::async (std::launch::async, std::bind (&AddressBookSubscription::CheckUpdates, m_DefaultSubscription)); } else if (!m_Subscriptions.empty ()) { // pick random subscription auto ind = rand () % m_Subscriptions.size(); m_Downloading = std::async (std::launch::async, std::bind (&AddressBookSubscription::CheckUpdates, m_Subscriptions[ind])); } } else { // try it again later m_SubscriptionsUpdateTimer->expires_from_now (boost::posix_time::minutes(INITIAL_SUBSCRIPTION_RETRY_TIMEOUT)); m_SubscriptionsUpdateTimer->async_wait (std::bind (&AddressBook::HandleSubscriptionsUpdateTimer, this, std::placeholders::_1)); } } } void AddressBook::StartLookups () { auto dest = i2p::client::context.GetSharedLocalDestination (); if (dest) { auto datagram = dest->GetDatagramDestination (); if (!datagram) datagram = dest->CreateDatagramDestination (); datagram->SetReceiver (std::bind (&AddressBook::HandleLookupResponse, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5), ADDRESS_RESPONSE_DATAGRAM_PORT); } } void AddressBook::StopLookups () { auto dest = i2p::client::context.GetSharedLocalDestination (); if (dest) { auto datagram = dest->GetDatagramDestination (); if (datagram) datagram->ResetReceiver (ADDRESS_RESPONSE_DATAGRAM_PORT); } } void AddressBook::LookupAddress (std::string_view address) { std::shared_ptr addr; auto dot = address.find ('.'); if (dot != std::string::npos) addr = FindAddress (address.substr (dot + 1)); if (!addr || !addr->IsIdentHash ()) // TODO: { LogPrint (eLogError, "Addressbook: Can't find domain for ", address); return; } auto dest = i2p::client::context.GetSharedLocalDestination (); if (dest) { auto datagram = dest->GetDatagramDestination (); if (datagram) { uint32_t nonce; RAND_bytes ((uint8_t *)&nonce, 4); { std::unique_lock l(m_LookupsMutex); m_Lookups[nonce] = address; } LogPrint (eLogDebug, "Addressbook: Lookup of ", address, " to ", addr->identHash.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); delete[] buf; } } } void AddressBook::HandleLookupResponse (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) { if (len < 44) { LogPrint (eLogError, "Addressbook: Lookup response is too short ", len); return; } uint32_t nonce = bufbe32toh (buf + 4); LogPrint (eLogDebug, "Addressbook: Lookup response received from ", from.GetIdentHash ().ToBase32 (), " nonce=", nonce); std::string address; { std::unique_lock l(m_LookupsMutex); auto it = m_Lookups.find (nonce); if (it != m_Lookups.end ()) { address = it->second; m_Lookups.erase (it); } } if (address.length () > 0) { // TODO: verify from i2p::data::IdentHash hash(buf + 8); if (!hash.IsZero ()) m_Addresses[address] = std::make_shared
(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): 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); } bool AddressBookSubscription::MakeRequest () { 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); return false; } auto addr = m_Book.GetAddress (url.host); if (!addr || !addr->IsIdentHash ()) { LogPrint (eLogError, "Addressbook: Can't resolve ", url.host); return false; } else m_Ident = addr->identHash; // save url parts for later use std::string dest_host = url.host; int dest_port = url.port ? url.port : 80; // try to create stream to addressbook site auto stream = i2p::client::context.GetSharedLocalDestination ()->CreateStream (m_Ident, dest_port); if (!stream) { LogPrint (eLogError, "Addressbook: LeaseSet for address ", url.host, " not found"); return false; } if (m_Etag.empty() && m_LastModified.empty()) { m_Book.GetEtag (m_Ident, m_Etag, m_LastModified); LogPrint (eLogDebug, "Addressbook: Loaded for ", url.host, ": ETag: ", m_Etag, ", Last-Modified: ", m_LastModified); } // create http request & send it 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"; std::string request = req.to_string(); stream->Send ((const uint8_t *) request.data(), request.length()); // read response std::string response; uint8_t recv_buf[4096]; bool end = false; int numAttempts = 0; while (!end) { size_t received = stream->Receive (recv_buf, 4096, SUBSCRIPTION_REQUEST_TIMEOUT); if (received) { response.append ((char *)recv_buf, received); if (!stream->IsOpen ()) end = true; } else if (!stream->IsOpen ()) end = true; else { LogPrint (eLogError, "Addressbook: Subscriptions request timeout expired"); numAttempts++; if (numAttempts > 5) end = true; } } // process remaining buffer while (size_t len = stream->ReadSome (recv_buf, sizeof(recv_buf))) response.append ((char *)recv_buf, len); // 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); return false; } if (res_head_len == 0) { LogPrint(eLogError, "Addressbook: Incomplete http response from ", dest_host, ", interrupted by timeout"); return false; } // 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"); return false; } if (res.code != 200) { 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"); return false; } if (!res.is_gzipped () && len > 0 && len != (int) response.length()) { LogPrint(eLogError, "Addressbook: Response size mismatch, expected: ", len, ", got: ", response.length(), "bytes"); return false; } // 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"); if (it != res.headers.end()) m_LastModified = it->second; if (res.is_chunked()) { std::stringstream in(response), out; i2p::http::MergeChunkedResponse (in, out); response = out.str(); } 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"); return false; } response = out.str(); } std::stringstream ss(response); LogPrint (eLogInfo, "Addressbook: Got update from ", dest_host); m_Book.LoadHostsFromStream (ss, true); return true; } AddressResolver::AddressResolver (std::shared_ptr destination): m_LocalDestination (destination) { if (m_LocalDestination) { auto datagram = m_LocalDestination->GetDatagramDestination (); if (!datagram) datagram = m_LocalDestination->CreateDatagramDestination (); datagram->SetReceiver (std::bind (&AddressResolver::HandleRequest, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5), ADDRESS_RESOLVER_DATAGRAM_PORT); } } AddressResolver::~AddressResolver () { if (m_LocalDestination) { auto datagram = m_LocalDestination->GetDatagramDestination (); if (datagram) datagram->ResetReceiver (ADDRESS_RESOLVER_DATAGRAM_PORT); } } void AddressResolver::HandleRequest (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) { if (len < 9 || len < buf[8] + 9U) { LogPrint (eLogError, "Addressbook: Address request is too short ", len); return; } // read requested address uint8_t l = buf[8]; char address[255]; memcpy (address, buf + 9, l); address[l] = 0; LogPrint (eLogDebug, "Addressbook: Address request ", address); // send response uint8_t response[44]; memset (response, 0, 4); // reserved memcpy (response + 4, buf + 4, 4); // nonce auto it = m_LocalAddresses.find (address); // address lookup if (it != m_LocalAddresses.end ()) memcpy (response + 8, it->second, 32); // ident else memset (response + 8, 0, 32); // not found memset (response + 40, 0, 4); // set expiration time to zero m_LocalDestination->GetDatagramDestination ()->SendDatagramTo (response, 44, from.GetIdentHash(), toPort, fromPort); } void AddressResolver::AddAddress (const std::string& name, const i2p::data::IdentHash& ident) { m_LocalAddresses[name] = ident; } } } i2pd-2.56.0/libi2pd_client/AddressBook.h000066400000000000000000000140771475272067700177300ustar00rootroot00000000000000/* * 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 { namespace client { const int INITIAL_SUBSCRIPTION_UPDATE_TIMEOUT = 3; // in minutes const int INITIAL_SUBSCRIPTION_RETRY_TIMEOUT = 1; // in minutes 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 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 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 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; class AddressResolver; 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 LoadHostsFromStream (std::istream& f, bool is_update); void DownloadComplete (bool success, const i2p::data::IdentHash& subscription, const std::string& etag, const std::string& lastModified); //This method returns the ".b32.i2p" address std::string ToAddress(const i2p::data::IdentHash& ident) { return GetB32Address(ident); } 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 (); void StopSubscriptions (); void LoadHosts (); void LoadSubscriptions (); void LoadLocal (); void HandleSubscriptionsUpdateTimer (const boost::system::error_code& ecode); void StartLookups (); 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_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; 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; }; class AddressBookSubscription { public: AddressBookSubscription (AddressBook& book, std::string_view link); void CheckUpdates (); private: bool MakeRequest (); private: AddressBook& m_Book; std::string m_Link, m_Etag, m_LastModified; i2p::data::IdentHash m_Ident; // m_Etag must be surrounded by "" }; class AddressResolver { public: AddressResolver (std::shared_ptr destination); ~AddressResolver (); void AddAddress (const std::string& name, const i2p::data::IdentHash& ident); private: void HandleRequest (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); private: std::shared_ptr m_LocalDestination; std::map m_LocalAddresses; }; } } #endif i2pd-2.56.0/libi2pd_client/BOB.cpp000066400000000000000000000665341475272067700164720ustar00rootroot00000000000000/* * 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 "ClientContext.h" #include "util.h" #include "BOB.h" namespace i2p { namespace client { BOBI2PInboundTunnel::BOBI2PInboundTunnel (const boost::asio::ip::tcp::endpoint& ep, std::shared_ptr localDestination): BOBI2PTunnel (localDestination), m_Acceptor (localDestination->GetService (), ep) { } BOBI2PInboundTunnel::~BOBI2PInboundTunnel () { Stop (); } void BOBI2PInboundTunnel::Start () { m_Acceptor.listen (); Accept (); } void BOBI2PInboundTunnel::Stop () { m_Acceptor.close(); ClearHandlers (); } void BOBI2PInboundTunnel::Accept () { auto receiver = std::make_shared (); receiver->socket = std::make_shared (GetService ()); m_Acceptor.async_accept (*receiver->socket, std::bind (&BOBI2PInboundTunnel::HandleAccept, this, std::placeholders::_1, receiver)); } void BOBI2PInboundTunnel::HandleAccept (const boost::system::error_code& ecode, std::shared_ptr receiver) { if (!ecode) { Accept (); ReceiveAddress (receiver); } } 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), std::bind(&BOBI2PInboundTunnel::HandleReceivedAddress, this, 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 ()); else { receiver->bufferOffset += bytes_transferred; receiver->buffer[receiver->bufferOffset] = 0; char * eol = strchr (receiver->buffer, '\n'); if (eol) { *eol = 0; 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) { 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)); } else GetLocalDestination ()->RequestDestinationWithEncryptedLeaseSet (addr->blindedPublicKey, 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"); } } } void BOBI2PInboundTunnel::HandleDestinationRequestComplete (std::shared_ptr leaseSet, std::shared_ptr receiver) { if (leaseSet) CreateConnection (receiver, leaseSet); else LogPrint (eLogError, "BOB: LeaseSet for inbound destination not found"); } void BOBI2PInboundTunnel::CreateConnection (std::shared_ptr receiver, std::shared_ptr leaseSet) { LogPrint (eLogDebug, "BOB: New inbound connection"); auto connection = std::make_shared(this, receiver->socket, leaseSet); AddHandler (connection); connection->I2PConnect (receiver->data, receiver->dataLen); } BOBI2POutboundTunnel::BOBI2POutboundTunnel (const std::string& outhost, uint16_t port, std::shared_ptr localDestination, bool quiet): BOBI2PTunnel (localDestination), m_Endpoint (boost::asio::ip::make_address (outhost), port), m_IsQuiet (quiet) { } void BOBI2POutboundTunnel::Start () { Accept (); } void BOBI2POutboundTunnel::Stop () { ClearHandlers (); } void BOBI2POutboundTunnel::Accept () { auto localDestination = GetLocalDestination (); if (localDestination) localDestination->AcceptStreams (std::bind (&BOBI2POutboundTunnel::HandleAccept, this, std::placeholders::_1)); else LogPrint (eLogError, "BOB: Local destination not set for server tunnel"); } void BOBI2POutboundTunnel::HandleAccept (std::shared_ptr stream) { if (stream) { auto conn = std::make_shared (this, stream, 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): 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) { } BOBDestination::~BOBDestination () { delete m_OutboundTunnel; delete m_InboundTunnel; i2p::client::context.DeleteLocalDestination (m_LocalDestination); } void BOBDestination::Start () { if (m_OutboundTunnel) m_OutboundTunnel->Start (); if (m_InboundTunnel) m_InboundTunnel->Start (); m_IsRunning = true; } void BOBDestination::Stop () { StopTunnels (); m_LocalDestination->Stop (); } void BOBDestination::StopTunnels () { m_IsRunning = false; if (m_OutboundTunnel) { m_OutboundTunnel->Stop (); delete m_OutboundTunnel; m_OutboundTunnel = nullptr; } if (m_InboundTunnel) { m_InboundTunnel->Stop (); delete m_InboundTunnel; m_InboundTunnel = nullptr; } } void BOBDestination::CreateInboundTunnel (uint16_t port, const std::string& inhost) { 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 ()) { boost::system::error_code ec; auto addr = boost::asio::ip::make_address (inhost, ec); if (!ec) ep.address (addr); else LogPrint (eLogError, "BOB: ", ec.message ()); } m_InboundTunnel = new BOBI2PInboundTunnel (ep, m_LocalDestination); } } void BOBDestination::CreateOutboundTunnel (const std::string& outhost, uint16_t 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); } } 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_InPort (0), m_OutPort (0), m_CurrentDestination (nullptr) { } BOBCommandSession::~BOBCommandSession () { } void BOBCommandSession::Terminate () { m_Socket.close (); m_IsOpen = false; } 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)); } void BOBCommandSession::HandleReceivedLine(const boost::system::error_code& ecode, std::size_t bytes_transferred) { if(ecode) { 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()) { (this->*(it->second))(operand.c_str(), operand.length()); } else { LogPrint (eLogError, "BOB: Unknown command ", command.c_str()); SendReplyError ("unknown command"); } } } void BOBCommandSession::Send () { boost::asio::async_write (m_Socket, m_SendBuffer, boost::asio::transfer_all (), std::bind(&BOBCommandSession::HandleSent, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } 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 ()); if (ecode != boost::asio::error::operation_aborted) Terminate (); } else { if (m_IsOpen) Receive (); else Terminate (); } } void BOBCommandSession::SendReplyOK (const char * msg) { std::ostream os(&m_SendBuffer); os << "OK"; if(msg) { os << " " << msg; } os << std::endl; Send (); } void BOBCommandSession::SendReplyError (const char * msg) { std::ostream os(&m_SendBuffer); os << "ERROR " << msg << std::endl; Send (); } void BOBCommandSession::SendVersion () { std::ostream os(&m_SendBuffer); os << "BOB 00.00.10" << std::endl; SendReplyOK(); } void BOBCommandSession::SendRaw (const char * data) { std::ostream os(&m_SendBuffer); os << data << std::endl; } void BOBCommandSession::BuildStatusLine(bool currentTunnel, std::shared_ptr dest, std::string &out) { // 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(); } void BOBCommandSession::ZapCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: zap"); Terminate (); } void BOBCommandSession::QuitCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: quit"); m_IsOpen = false; SendReplyOK ("Bye!"); } void BOBCommandSession::StartCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: start ", m_Nickname); if (m_IsActive) { 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_Owner.AddDestination (m_Nickname, m_CurrentDestination); } if (m_InPort) m_CurrentDestination->CreateInboundTunnel (m_InPort, m_InHost); if (m_OutPort && !m_OutHost.empty ()) m_CurrentDestination->CreateOutboundTunnel (m_OutHost, m_OutPort, m_IsQuiet); m_CurrentDestination->Start (); SendReplyOK ("Tunnel starting"); m_IsActive = true; } void BOBCommandSession::StopCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: stop ", m_Nickname); if (!m_IsActive) { SendReplyError ("tunnel is inactive"); return; } auto dest = m_Owner.FindDestination (m_Nickname); if (dest) { dest->StopTunnels (); SendReplyOK ("Tunnel stopping"); } else SendReplyError ("tunnel not found"); m_IsActive = false; } 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"); } 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_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"); } else SendReplyError ("no nickname has been set"); } void BOBCommandSession::NewkeysCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: newkeys"); i2p::data::SigningKeyType signatureType = i2p::data::SIGNING_KEY_TYPE_DSA_SHA1; i2p::data::CryptoKeyType cryptoType = i2p::data::CRYPTO_KEY_TYPE_ELGAMAL; if (*operand) { try { char * operand1 = (char *)strchr (operand, ' '); if (operand1) { *operand1 = 0; operand1++; cryptoType = std::stoi(operand1); } signatureType = std::stoi(operand); } catch (std::invalid_argument& ex) { LogPrint (eLogWarning, "BOB: Error on newkeys: ", ex.what ()); } } m_Keys = i2p::data::PrivateKeys::CreateRandomKeys (signatureType, cryptoType, true); SendReplyOK (m_Keys.GetPublic ()->ToBase64 ().c_str ()); } void BOBCommandSession::SetkeysCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: setkeys ", operand); if (*operand && m_Keys.FromBase64 (operand)) SendReplyOK (m_Keys.GetPublic ()->ToBase64 ().c_str ()); else SendReplyError ("invalid keys"); } void BOBCommandSession::GetkeysCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: getkeys"); if (m_Keys.GetPublic ()) // keys are set ? SendReplyOK (m_Keys.ToBase64 ().c_str ()); else SendReplyError ("keys are not set"); } void BOBCommandSession::GetdestCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: getdest"); if (m_Keys.GetPublic ()) // keys are set ? SendReplyOK (m_Keys.GetPublic ()->ToBase64 ().c_str ()); else SendReplyError ("keys are not set"); } 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"); } 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"); } else SendReplyError ("empty outport"); } 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"); } 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"); } else SendReplyError ("empty inport"); } void BOBCommandSession::QuietCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: quiet"); if (m_Nickname.length () > 0) { if (!m_IsActive) { m_IsQuiet = true; SendReplyOK ("Quiet set"); } else SendReplyError ("tunnel is active"); } else SendReplyError ("no nickname has been set"); } void BOBCommandSession::LookupCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: lookup ", operand); if (*operand) { auto addr = context.GetAddressBook ().GetAddress (operand); if (!addr) { SendReplyError ("Address Not found"); return; } auto localDestination = (m_CurrentDestination && m_CurrentDestination->IsRunning ()) ? m_CurrentDestination->GetLocalDestination () : i2p::client::context.GetSharedLocalDestination (); if (!localDestination) { SendReplyError ("No local destination"); return; } if (addr->IsIdentHash ()) { // we might have leaseset already auto leaseSet = localDestination->FindLeaseSet (addr->identHash); if (leaseSet) { SendReplyOK (leaseSet->GetIdentity ()->ToBase64 ().c_str ()); return; } } // trying to request auto s = shared_from_this (); auto requstCallback = [s](std::shared_ptr ls) { if (ls) s->SendReplyOK (ls->GetIdentity ()->ToBase64 ().c_str ()); else s->SendReplyError ("LeaseSet Not found"); }; if (addr->IsIdentHash ()) localDestination->RequestDestination (addr->identHash, requstCallback); else 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) { LogPrint (eLogDebug, "BOB: clear"); m_Owner.DeleteDestination (m_Nickname); m_Nickname = ""; SendReplyOK ("cleared"); } 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()); } SendReplyOK ("Listing done"); } void BOBCommandSession::OptionCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: option ", operand); const char * value = strchr (operand, '='); if (value) { std::string msg ("option "); *(const_cast(value)) = 0; m_Options[operand] = value + 1; msg += operand; *(const_cast(value)) = '='; msg += " set to "; msg += value + 1; SendReplyOK (msg.c_str ()); } else SendReplyError ("malformed"); } 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) { // tunnel destination exists BuildStatusLine(false, dest, statusLine); SendReplyOK(statusLine.c_str()); } else { if(m_Nickname == name && !name.empty()) { // tunnel is incomplete / has not been started yet BuildStatusLine(true, nullptr, statusLine); SendReplyOK(statusLine.c_str()); } else { SendReplyError("no nickname has been set"); } } } 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) { ss << " " << x.first; } const std::string &str = ss.str(); SendReplyOK(str.c_str()); } else { auto it = helpStrings.find(operand); if (it != helpStrings.end ()) { SendReplyOK(it->second.c_str()); return; } SendReplyError("No such command"); } } 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)) { // command -> handler m_CommandHandlers[BOB_COMMAND_ZAP] = &BOBCommandSession::ZapCommandHandler; m_CommandHandlers[BOB_COMMAND_QUIT] = &BOBCommandSession::QuitCommandHandler; m_CommandHandlers[BOB_COMMAND_START] = &BOBCommandSession::StartCommandHandler; m_CommandHandlers[BOB_COMMAND_STOP] = &BOBCommandSession::StopCommandHandler; m_CommandHandlers[BOB_COMMAND_SETNICK] = &BOBCommandSession::SetNickCommandHandler; m_CommandHandlers[BOB_COMMAND_GETNICK] = &BOBCommandSession::GetNickCommandHandler; m_CommandHandlers[BOB_COMMAND_NEWKEYS] = &BOBCommandSession::NewkeysCommandHandler; m_CommandHandlers[BOB_COMMAND_GETKEYS] = &BOBCommandSession::GetkeysCommandHandler; m_CommandHandlers[BOB_COMMAND_SETKEYS] = &BOBCommandSession::SetkeysCommandHandler; m_CommandHandlers[BOB_COMMAND_GETDEST] = &BOBCommandSession::GetdestCommandHandler; m_CommandHandlers[BOB_COMMAND_OUTHOST] = &BOBCommandSession::OuthostCommandHandler; m_CommandHandlers[BOB_COMMAND_OUTPORT] = &BOBCommandSession::OutportCommandHandler; m_CommandHandlers[BOB_COMMAND_INHOST] = &BOBCommandSession::InhostCommandHandler; 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 (); } void BOBCommandChannel::Start () { Accept (); StartIOService (); } void BOBCommandChannel::Stop () { for (auto& it: m_Destinations) it.second->Stop (); m_Acceptor.cancel (); StopIOService (); } void BOBCommandChannel::AddDestination (const std::string& name, std::shared_ptr dest) { m_Destinations.emplace (name, dest); } void BOBCommandChannel::DeleteDestination (const std::string& name) { auto it = m_Destinations.find (name); if (it != m_Destinations.end ()) { it->second->Stop (); m_Destinations.erase (it); } } std::shared_ptr BOBCommandChannel::FindDestination (const std::string& name) { auto it = m_Destinations.find (name); if (it != m_Destinations.end ()) return it->second; return nullptr; } void BOBCommandChannel::Accept () { auto newSession = std::make_shared (*this); m_Acceptor.async_accept (newSession->GetSocket (), std::bind (&BOBCommandChannel::HandleAccept, this, std::placeholders::_1, newSession)); } void BOBCommandChannel::HandleAccept(const boost::system::error_code& ecode, std::shared_ptr session) { if (ecode != boost::asio::error::operation_aborted) Accept (); if (!ecode) { LogPrint (eLogInfo, "BOB: New command connection from ", session->GetSocket ().remote_endpoint ()); session->SendVersion (); } else LogPrint (eLogError, "BOB: Accept error: ", ecode.message ()); } } } i2pd-2.56.0/libi2pd_client/BOB.h000066400000000000000000000246651475272067700161360ustar00rootroot00000000000000/* * 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 BOB_H__ #define BOB_H__ #include #include #include #include #include #include #include "util.h" #include "I2PTunnel.h" #include "I2PService.h" #include "Identity.h" #include "LeaseSet.h" namespace i2p { namespace client { const size_t BOB_COMMAND_BUFFER_SIZE = 1024; const char BOB_COMMAND_ZAP[] = "zap"; const char BOB_COMMAND_QUIT[] = "quit"; const char BOB_COMMAND_START[] = "start"; const char BOB_COMMAND_STOP[] = "stop"; const char BOB_COMMAND_SETNICK[] = "setnick"; const char BOB_COMMAND_GETNICK[] = "getnick"; const char BOB_COMMAND_NEWKEYS[] = "newkeys"; const char BOB_COMMAND_GETKEYS[] = "getkeys"; const char BOB_COMMAND_SETKEYS[] = "setkeys"; const char BOB_COMMAND_GETDEST[] = "getdest"; const char BOB_COMMAND_OUTHOST[] = "outhost"; const char BOB_COMMAND_OUTPORT[] = "outport"; const char BOB_COMMAND_INHOST[] = "inhost"; 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."; class BOBI2PTunnel: public I2PService { public: BOBI2PTunnel (std::shared_ptr localDestination): I2PService (localDestination) {}; virtual void Start () {}; virtual void Stop () {}; }; 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; AddressReceiver (): data (nullptr), dataLen (0), bufferOffset (0) {}; }; public: BOBI2PInboundTunnel (const boost::asio::ip::tcp::endpoint& ep, std::shared_ptr localDestination); ~BOBI2PInboundTunnel (); void Start (); void Stop (); private: void Accept (); void HandleAccept (const boost::system::error_code& ecode, std::shared_ptr receiver); void ReceiveAddress (std::shared_ptr receiver); void HandleReceivedAddress (const boost::system::error_code& ecode, std::size_t bytes_transferred, std::shared_ptr receiver); void HandleDestinationRequestComplete (std::shared_ptr leaseSet, std::shared_ptr receiver); void CreateConnection (std::shared_ptr receiver, std::shared_ptr leaseSet); private: boost::asio::ip::tcp::acceptor m_Acceptor; }; class BOBI2POutboundTunnel: public BOBI2PTunnel { public: BOBI2POutboundTunnel (const std::string& outhost, uint16_t port, std::shared_ptr localDestination, bool quiet); void Start (); void Stop (); void SetQuiet () { m_IsQuiet = true; }; private: void Accept (); void HandleAccept (std::shared_ptr stream); private: boost::asio::ip::tcp::endpoint m_Endpoint; bool m_IsQuiet; }; class BOBDestination { 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 (); 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; } const i2p::data::PrivateKeys& GetKeys () const { return m_LocalDestination->GetPrivateKeys (); }; std::shared_ptr GetLocalDestination () const { return m_LocalDestination; }; private: 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; class BOBCommandSession: public std::enable_shared_from_this { public: BOBCommandSession (BOBCommandChannel& owner); ~BOBCommandSession (); void Terminate (); boost::asio::ip::tcp::socket& GetSocket () { return m_Socket; }; void SendVersion (); // command handlers void ZapCommandHandler (const char * operand, size_t len); void QuitCommandHandler (const char * operand, size_t len); void StartCommandHandler (const char * operand, size_t len); void StopCommandHandler (const char * operand, size_t len); void SetNickCommandHandler (const char * operand, size_t len); void GetNickCommandHandler (const char * operand, size_t len); void NewkeysCommandHandler (const char * operand, size_t len); void SetkeysCommandHandler (const char * operand, size_t len); void GetkeysCommandHandler (const char * operand, size_t len); void GetdestCommandHandler (const char * operand, size_t len); void OuthostCommandHandler (const char * operand, size_t len); void OutportCommandHandler (const char * operand, size_t len); void InhostCommandHandler (const char * operand, size_t len); 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 HandleSent (const boost::system::error_code& ecode, std::size_t bytes_transferred); void SendReplyOK (const char * msg = nullptr); void SendReplyError (const char * msg); void SendRaw (const char * data); void BuildStatusLine(bool currentTunnel, std::shared_ptr destination, std::string &out); private: BOBCommandChannel& m_Owner; boost::asio::ip::tcp::socket m_Socket; boost::asio::streambuf m_ReceiveBuffer, m_SendBuffer; bool m_IsOpen, m_IsQuiet, m_IsActive; std::string m_Nickname, m_InHost, m_OutHost; uint16_t m_InPort, m_OutPort; i2p::data::PrivateKeys m_Keys; std::map m_Options; std::shared_ptr m_CurrentDestination; }; typedef void (BOBCommandSession::*BOBCommandHandler)(const char * operand, size_t len); class BOBCommandChannel: private i2p::util::RunnableService { public: BOBCommandChannel (const std::string& address, uint16_t port); ~BOBCommandChannel (); void Start (); void Stop (); auto& GetService () { return GetIOService (); }; void AddDestination (const std::string& name, std::shared_ptr dest); void DeleteDestination (const std::string& name); std::shared_ptr FindDestination (const std::string& name); private: void Accept (); void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr session); private: boost::asio::ip::tcp::acceptor m_Acceptor; 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 i2pd-2.56.0/libi2pd_client/ClientContext.cpp000066400000000000000000001162621475272067700206450ustar00rootroot00000000000000/* * 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 "Config.h" #include "FS.h" #include "Log.h" #include "Identity.h" #include "util.h" #include "ClientContext.h" #include "HTTPProxy.h" #include "SOCKS.h" #include "MatchedDestination.h" namespace i2p { namespace client { ClientContext context; ClientContext::ClientContext (): m_SharedLocalDestination (nullptr), m_HttpProxy (nullptr), m_SocksProxy (nullptr), m_SamBridge (nullptr), m_BOBCommandChannel (nullptr), m_I2CPServer (nullptr) { } ClientContext::~ClientContext () { delete m_HttpProxy; delete m_SocksProxy; delete m_SamBridge; delete m_BOBCommandChannel; delete m_I2CPServer; } void ClientContext::Start () { // shared local destination if (!m_SharedLocalDestination) CreateNewSharedLocalDestination (); // addressbook m_AddressBook.Start (); // HTTP proxy ReadHttpProxy (); // SOCKS proxy ReadSocksProxy (); // I2P tunnels ReadTunnels (); // SAM bool sam; i2p::config::GetOption("sam.enabled", 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 ()); } } // BOB bool bob; i2p::config::GetOption("bob.enabled", bob); 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 ()); } } // I2CP bool i2cp; i2p::config::GetOption("i2cp.enabled", i2cp); if (i2cp) { 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); try { m_I2CPServer = new I2CPServer (i2cpAddr, i2cpPort, singleThread); 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 ()); } } m_AddressBook.StartResolvers (); // start UDP cleanup if (!m_ServerForwards.empty ()) { m_CleanupUDPTimer.reset (new boost::asio::deadline_timer(m_SharedLocalDestination->GetService ())); ScheduleCleanupUDP(); } } void ClientContext::Stop () { if (m_HttpProxy) { LogPrint(eLogInfo, "Clients: Stopping HTTP Proxy"); m_HttpProxy->Stop(); delete m_HttpProxy; m_HttpProxy = nullptr; } if (m_SocksProxy) { LogPrint(eLogInfo, "Clients: Stopping SOCKS Proxy"); m_SocksProxy->Stop(); delete m_SocksProxy; m_SocksProxy = nullptr; } for (auto& it: m_ClientTunnels) { 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"); it.second->Stop (); } m_ServerTunnels.clear (); if (m_SamBridge) { LogPrint(eLogInfo, "Clients: Stopping SAM bridge"); m_SamBridge->Stop (); delete m_SamBridge; m_SamBridge = nullptr; } if (m_BOBCommandChannel) { LogPrint(eLogInfo, "Clients: Stopping BOB command channel"); m_BOBCommandChannel->Stop (); delete m_BOBCommandChannel; m_BOBCommandChannel = nullptr; } if (m_I2CPServer) { LogPrint(eLogInfo, "Clients: Stopping I2CP"); m_I2CPServer->Stop (); delete m_I2CPServer; m_I2CPServer = nullptr; } 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 (); m_SharedLocalDestination = nullptr; } void ClientContext::ReloadConfig () { // TODO: handle config changes /*std::string config; i2p::config::GetOption("conf", config); i2p::config::ParseConfig(config);*/ // change shared local destination m_SharedLocalDestination->Release (); CreateNewSharedLocalDestination (); // recreate HTTP proxy 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); // delete unused destinations std::unique_lock l(m_DestinationsMutex); for (auto it = m_Destinations.begin (); it != m_Destinations.end ();) { auto dest = it->second; if (dest->GetRefCounter () > 0) ++it; // skip else { dest->Stop (); it = m_Destinations.erase (it); } } } bool ClientContext::LoadPrivateKeys (i2p::data::PrivateKeys& keys, const std::string& filename, i2p::data::SigningKeyType sigType, i2p::data::CryptoKeyType cryptoType) { static const std::string transient("transient"); if (!filename.compare (0, transient.length (), transient)) // starts with transient { keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType, cryptoType, true); LogPrint (eLogInfo, "Clients: New transient keys address ", m_AddressBook.ToAddress(keys.GetPublic ()->GetIdentHash ()), " created"); return true; } bool success = true; std::string fullPath = i2p::fs::DataDirPath (filename); std::ifstream s(fullPath, std::ifstream::binary); if (s.is_open ()) { s.seekg (0, std::ios::end); size_t len = s.tellg(); s.seekg (0, std::ios::beg); uint8_t * buf = new uint8_t[len]; s.read ((char *)buf, len); if(!keys.FromBuffer (buf, len)) { LogPrint (eLogCritical, "Clients: Failed to load keyfile ", filename); success = false; } else LogPrint (eLogInfo, "Clients: Local address ", m_AddressBook.ToAddress(keys.GetPublic ()->GetIdentHash ()), " loaded"); delete[] buf; } else { LogPrint (eLogCritical, "Clients: Can't open file ", fullPath, " Creating new one with signature type ", sigType, " crypto type ", cryptoType); keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType, cryptoType, true); std::ofstream f (fullPath, std::ofstream::binary | std::ofstream::out); size_t len = keys.GetFullLen (); uint8_t * buf = new uint8_t[len]; len = keys.ToBuffer (buf, len); f.write ((char *)buf, len); delete[] buf; LogPrint (eLogInfo, "Clients: New private keys file ", fullPath, " for ", m_AddressBook.ToAddress(keys.GetPublic ()->GetIdentHash ()), " created"); } return success; } std::vector > ClientContext::GetForwardInfosFor(const i2p::data::IdentHash & destination) { std::vector > infos; std::lock_guard lock(m_ForwardsMutex); for(const auto & c : m_ClientForwards) { if (c.second->IsLocalDestination(destination)) { for (auto & i : c.second->GetSessions()) infos.push_back(i); break; } } for(const auto & s : m_ServerForwards) { if(std::get<0>(s.first) == destination) { for( auto & i : s.second->GetSessions()) infos.push_back(i); break; } } return infos; } std::shared_ptr ClientContext::CreateNewLocalDestination (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 (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); 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) { std::unique_lock l(m_DestinationsMutex); m_Destinations[localDestination->GetIdentHash ()] = localDestination; localDestination->Start (); } void ClientContext::DeleteLocalDestination (std::shared_ptr destination) { if (!destination) return; auto it = m_Destinations.find (destination->GetIdentHash ()); if (it != m_Destinations.end ()) { auto d = it->second; { std::unique_lock l(m_DestinationsMutex); m_Destinations.erase (it); } d->Stop (); } } std::shared_ptr ClientContext::CreateNewLocalDestination (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 (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); 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->Acquire (); } std::shared_ptr ClientContext::FindLocalDestination (const i2p::data::IdentHash& destination) const { auto it = m_Destinations.find (destination); if (it != m_Destinations.end ()) return it->second; return nullptr; } 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)); } template std::string ClientContext::GetI2CPStringOption (const Section& section, const std::string& name, const std::string& value) const { return section.second.get (boost::property_tree::ptree::path_type (name, '/'), value); } 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_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 { std::string value; if (i2p::config::GetOption(prefix + I2CP_PARAM_INBOUND_TUNNEL_LENGTH, value)) 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; 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 (it.substr(it.size() - 5) != ".conf") continue; // skip files which not ends with ".conf" LogPrint(eLogDebug, "Clients: Tunnels extra config file: ", it); ReadTunnels (it, numClientTunnels, numServerTunnels); } } } 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 { boost::property_tree::read_ini (tunConf, pt); } catch (std::exception& ex) { LogPrint (eLogWarning, "Clients: Can't read ", tunConf, ": ", ex.what ()); return; } std::map > destinations; // keys -> destination for (auto& section: pt) { std::string name = section.first; 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); uint16_t port = section.second.get (I2P_CLIENT_TUNNEL_PORT); // optional params bool matchTunnels = section.second.get (I2P_CLIENT_TUNNEL_MATCH_TUNNELS, false); std::string keys = section.second.get (I2P_CLIENT_TUNNEL_KEYS, "transient"); std::string address = section.second.get (I2P_CLIENT_TUNNEL_ADDRESS, "127.0.0.1"); uint16_t destinationPort = section.second.get (I2P_CLIENT_TUNNEL_DESTINATION_PORT, 0); i2p::data::SigningKeyType sigType = section.second.get (I2P_CLIENT_TUNNEL_SIGNATURE_TYPE, i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519); i2p::data::CryptoKeyType cryptoType = section.second.get (I2P_CLIENT_TUNNEL_CRYPTO_TYPE, i2p::data::CRYPTO_KEY_TYPE_ELGAMAL); // I2CP std::map options; ReadI2CPOptions (section, false, options); // Set I2CP name if not set auto itopt = options.find (I2CP_PARAM_OUTBOUND_NICKNAME); if (itopt == options.end ()) options[I2CP_PARAM_OUTBOUND_NICKNAME] = name; std::shared_ptr localDestination = nullptr; if (keys.length () > 0) { auto it = destinations.find (keys); if (it != destinations.end ()) localDestination = it->second; else { i2p::data::PrivateKeys k; if(LoadPrivateKeys (k, keys, sigType, cryptoType)) { 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 (type == I2P_TUNNELS_SECTION_TYPE_UDPCLIENT) { // udp client // TODO: hostnames boost::asio::ip::udp::endpoint end (boost::asio::ip::make_address(address), port); if (!localDestination) localDestination = m_SharedLocalDestination; bool gzip = section.second.get (I2P_CLIENT_TUNNEL_GZIP, true); auto clientTunnel = std::make_shared (name, dest, end, localDestination, destinationPort, gzip); auto ins = m_ClientForwards.insert (std::make_pair (end, clientTunnel)); if (ins.second) { clientTunnel->Start (); numClientTunnels++; } 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; 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 (); } 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 (); } else if (type == I2P_TUNNELS_SECTION_TYPE_WEBSOCKS) { LogPrint(eLogWarning, "Clients: I2P Client tunnel websocks is deprecated, not starting ", name, " tunnel"); continue; } 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); } } uint32_t timeout = section.second.get(I2P_CLIENT_TUNNEL_CONNECT_TIMEOUT, 0); if(timeout) { clientTunnel->SetConnectTimeout(timeout); LogPrint(eLogInfo, "Clients: I2P Client tunnel connect timeout set to ", timeout); } auto ins = m_ClientTunnels.insert (std::make_pair (clientEndpoint, clientTunnel)); if (ins.second) { clientTunnel->Start (); numClientTunnels++; } else { // TODO: update 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) { // mandatory params std::string host = section.second.get (I2P_SERVER_TUNNEL_HOST); uint16_t 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, ""); std::string webircpass = section.second.get (I2P_SERVER_TUNNEL_WEBIRC_PASSWORD, ""); bool gzip = section.second.get (I2P_SERVER_TUNNEL_GZIP, false); i2p::data::SigningKeyType sigType = section.second.get (I2P_SERVER_TUNNEL_SIGNATURE_TYPE, i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519); i2p::data::CryptoKeyType cryptoType = section.second.get (I2P_CLIENT_TUNNEL_CRYPTO_TYPE, i2p::data::CRYPTO_KEY_TYPE_ELGAMAL); std::string address = section.second.get (I2P_SERVER_TUNNEL_ADDRESS, ""); bool isUniqueLocal = section.second.get (I2P_SERVER_TUNNEL_ENABLE_UNIQUE_LOCAL, true); bool ssl = section.second.get (I2P_SERVER_TUNNEL_SSL, false); // I2CP std::map options; ReadI2CPOptions (section, true, options); // Set I2CP name if not set auto itopt = options.find (I2CP_PARAM_INBOUND_NICKNAME); if (itopt == options.end ()) options[I2CP_PARAM_INBOUND_NICKNAME] = name; std::shared_ptr localDestination = nullptr; auto it = destinations.find (keys); if (it != destinations.end ()) { localDestination = it->second; localDestination->SetPublic (true); } 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); } 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); if(!isUniqueLocal) { 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) { 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"); } continue; } std::shared_ptr serverTunnel; if (type == I2P_TUNNELS_SECTION_TYPE_HTTP) serverTunnel = std::make_shared (name, host, port, localDestination, hostOverride, inPort, gzip); else if (type == I2P_TUNNELS_SECTION_TYPE_IRC) serverTunnel = std::make_shared (name, host, port, localDestination, webircpass, inPort, gzip); else // regular server tunnel by default serverTunnel = std::make_shared (name, host, port, localDestination, inPort, gzip); if (!address.empty ()) serverTunnel->SetLocalAddress (address); if (!isUniqueLocal) { LogPrint(eLogInfo, "Clients: Disabling loopback address mapping"); serverTunnel->SetUniqueLocal(isUniqueLocal); } if (ssl) serverTunnel->SetSSL (true); if (accessList.length () > 0) { std::set idents; 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); serverTunnel->SetAccessList (idents); } auto ins = m_ServerTunnels.insert (std::make_pair ( std::make_pair (localDestination->GetIdentHash (), inPort), serverTunnel)); if (ins.second) { serverTunnel->Start (); numServerTunnels++; } else { // TODO: update 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"); } } else 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 ()); } } } void ClientContext::ReadHttpProxy () { std::shared_ptr localDestination; 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.length () > 0) { i2p::data::PrivateKeys keys; if(LoadPrivateKeys (keys, httpProxyKeys, sigType)) { std::map params; ReadI2CPOptionsFromConfig ("httpproxy.", params); params[I2CP_PARAM_OUTBOUND_NICKNAME] = "HTTPProxy"; localDestination = CreateNewLocalDestination (keys, false, ¶ms); if (localDestination) localDestination->Acquire (); } else LogPrint(eLogCritical, "Clients: Failed to load HTTP Proxy key"); } try { m_HttpProxy = new i2p::proxy::HTTPProxy("HTTP Proxy", httpProxyAddr, httpProxyPort, httpOutProxyURL, httpAddresshelper, httpSendUserAgent, 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 ()); } } } 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 (httpProxyKeys == socksProxyKeys && m_HttpProxy) { localDestination = m_HttpProxy->GetLocalDestination (); localDestination->Acquire (); } else if (socksProxyKeys.length () > 0) { i2p::data::PrivateKeys keys; if (LoadPrivateKeys (keys, socksProxyKeys, sigType)) { std::map params; ReadI2CPOptionsFromConfig ("socksproxy.", params); params[I2CP_PARAM_OUTBOUND_NICKNAME] = "SOCKSProxy"; localDestination = CreateNewLocalDestination (keys, false, ¶ms); if (localDestination) localDestination->Acquire (); } else LogPrint(eLogCritical, "Clients: Failed to load SOCKS Proxy key"); } try { m_SocksProxy = new i2p::proxy::SOCKSProxy("SOCKS", socksProxyAddr, socksProxyPort, socksOutProxy, socksOutProxyAddr, socksOutProxyPort, localDestination); m_SocksProxy->Start(); } catch (std::exception& e) { LogPrint(eLogCritical, "Clients: Exception in SOCKS Proxy: ", e.what()); ThrowFatal ("Unable to start SOCKS Proxy at ", socksProxyAddr, ":", socksProxyPort, ": ", e.what ()); } } } void ClientContext::ScheduleCleanupUDP() { if (m_CleanupUDPTimer) { // schedule cleanup in 17 seconds m_CleanupUDPTimer->expires_from_now (boost::posix_time::seconds (17)); m_CleanupUDPTimer->async_wait(std::bind(&ClientContext::CleanupUDP, this, std::placeholders::_1)); } } void ClientContext::CleanupUDP(const boost::system::error_code & ecode) { if(!ecode) { std::lock_guard lock(m_ForwardsMutex); for (auto & s : m_ServerForwards ) s.second->ExpireStale(); ScheduleCleanupUDP(); } } void ClientContext::VisitTunnels (bool clean) { for (auto it = m_ClientTunnels.begin (); it != m_ClientTunnels.end ();) { if(clean && !it->second->isUpdated) { it->second->Stop (); it = m_ClientTunnels.erase(it); } else { it->second->isUpdated = false; 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++; } } } } } i2pd-2.56.0/libi2pd_client/ClientContext.h000066400000000000000000000200601475272067700203000ustar00rootroot00000000000000/* * 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 CLIENT_CONTEXT_H__ #define CLIENT_CONTEXT_H__ #include #include #include #include #include "Destination.h" #include "I2PService.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 { namespace client { const char I2P_TUNNELS_SECTION_TYPE[] = "type"; 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"; const char I2P_CLIENT_TUNNEL_PORT[] = "port"; 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_SERVER_TUNNEL_HOST[] = "host"; const char I2P_SERVER_TUNNEL_HOST_OVERRIDE[] = "hostoverride"; const char I2P_SERVER_TUNNEL_PORT[] = "port"; const char I2P_SERVER_TUNNEL_KEYS[] = "keys"; 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 { public: ClientContext (); ~ClientContext (); void Start (); void Stop (); void ReloadConfig (); 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::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); void DeleteLocalDestination (std::shared_ptr destination); std::shared_ptr FindLocalDestination (const i2p::data::IdentHash& destination) const; bool LoadPrivateKeys (i2p::data::PrivateKeys& keys, const std::string& filename, i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519, i2p::data::CryptoKeyType cryptoType = i2p::data::CRYPTO_KEY_TYPE_ELGAMAL); AddressBook& GetAddressBook () { return m_AddressBook; }; const BOBCommandChannel * GetBOBCommandChannel () const { return m_BOBCommandChannel; }; const SAMBridge * GetSAMBridge () const { return m_SamBridge; }; const I2CPServer * GetI2CPServer () const { return m_I2CPServer; }; 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 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); void CreateNewSharedLocalDestination (); void AddLocalDestination (std::shared_ptr localDestination); private: std::mutex m_DestinationsMutex; std::map > m_Destinations; std::shared_ptr m_SharedLocalDestination; AddressBook m_AddressBook; I2PService * m_HttpProxy, * m_SocksProxy; std::map > m_ClientTunnels; // local endpoint -> tunnel std::map, std::shared_ptr > m_ServerTunnels; // -> tunnel std::mutex m_ForwardsMutex; std::map > m_ClientForwards; // local endpoint -> udp tunnel std::map, std::shared_ptr > m_ServerForwards; // -> udp tunnel SAMBridge * m_SamBridge; BOBCommandChannel * m_BOBCommandChannel; I2CPServer * m_I2CPServer; 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; } }; extern ClientContext context; } } #endif i2pd-2.56.0/libi2pd_client/HTTPProxy.cpp000066400000000000000000000657041475272067700177070ustar00rootroot00000000000000/* * 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" #include "Destination.h" #include "HTTPProxy.h" #include "util.h" #include "Identity.h" #include "Streaming.h" #include "Destination.h" #include "ClientContext.h" #include "I2PEndian.h" #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=" } }; 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) { auto pos = str.rfind (suffix); if (pos == std::string::npos) return false; /* not found */ if (str.length() == (pos + std::strlen(suffix))) return true; /* match */ return false; } class HTTPReqHandler: public i2p::client::I2PServiceHandler, public std::enable_shared_from_this { private: bool HandleRequest(); 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); 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 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); typedef std::function ProxyResolvedHandler; void HandleUpstreamProxyResolved(const boost::system::error_code & ecode, boost::asio::ip::tcp::resolver::results_type endpoints, 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; 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 ()) {} ~HTTPReqHandler() { Terminate(); } void Handle () { AsyncSockRead(); } /* overload */ }; void HTTPReqHandler::AsyncSockRead() { LogPrint(eLogDebug, "HTTPProxy: Async sock read"); if (!m_sock) { 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)); } void HTTPReqHandler::Terminate() { if (Kill()) return; if (m_sock) { LogPrint(eLogDebug, "HTTPProxy: Close sock"); m_sock->close(); m_sock = nullptr; } if(m_proxysock) { LogPrint(eLogDebug, "HTTPProxy: Close proxysock"); if(m_proxysock->is_open()) m_proxysock->close(); m_proxysock = nullptr; } Done(shared_from_this()); } void HTTPReqHandler::GenericProxyError(std::string_view title, std::string_view description) { std::stringstream ss; ss << "

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

\r\n"; ss << "

" << description << "

\r\n"; SendProxyError(ss.str ()); } void HTTPReqHandler::GenericProxyInfo(std::string_view title, std::string_view description) { std::stringstream ss; ss << "

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

\r\n"; ss << "

" << description << "

\r\n"; SendProxyError(ss.str ()); } void HTTPReqHandler::HostNotFound(std::string_view host) { std::stringstream ss; ss << "

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

\r\n" << "

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

\r\n" << "

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

\r\n" << "\r\n"; SendProxyError(ss.str ()); } void HTTPReqHandler::SendProxyError(std::string_view content) { i2p::http::HTTPRes res; res.code = 500; res.add_header("Content-Type", "text/html; charset=UTF-8"); res.add_header("Connection", "close"); std::stringstream ss; ss << "\r\n" << pageHead << "" << 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)); } void HTTPReqHandler::SendRedirect(const std::string& address) { i2p::http::HTTPRes res; res.code = 302; res.add_header("Location", address); res.add_header("Connection", "close"); m_Response = res.to_string(); boost::asio::async_write(*m_sock, boost::asio::buffer(m_Response), boost::asio::transfer_all(), std::bind(&HTTPReqHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); } bool HTTPReqHandler::ExtractAddressHelper(i2p::http::URL& url, std::string& jump, bool& confirm) { confirm = false; const char *param = "i2paddresshelper="; std::size_t pos = url.query.find(param); std::size_t len = std::strlen(param); std::map params; if (pos == std::string::npos) return false; /* not found */ if (!url.parse_query(params)) return false; 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; } // 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 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) { /* drop common headers */ req.RemoveHeader("Via"); req.RemoveHeader("From"); req.RemoveHeader("Forwarded"); req.RemoveHeader("DNT"); // Useless DoNotTrack flag req.RemoveHeader("Accept", "Accept-Encoding"); // Accept*, but Accept-Encoding /* drop proxy-disclosing headers */ req.RemoveHeader("X-Forwarded"); req.RemoveHeader("Proxy-"); // Proxy-* /* replace headers */ if (!m_SendUserAgent) req.UpdateHeader("User-Agent", "MYOB/6.66 (AN/ON)"); /** * i2pd PR #1816: * Android Webview send this with the value set to the application ID, so we drop it, * but only if it does not belong to an AJAX request (*HttpRequest, like XMLHttpRequest). */ if(req.GetHeader("X-Requested-With") != "") { auto h = req.GetHeader ("X-Requested-With"); auto x = h.find("HttpRequest"); if (x == std::string::npos) // not found req.RemoveHeader("X-Requested-With"); } /** * 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"); } /* 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"); } /** * @brief Try to parse request from @a m_recv_buf * If parsing success, rebuild request and store to @a m_send_buf * with remaining data tail * @return true on processed request or false if more data needed */ bool HTTPReqHandler::HandleRequest() { 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")); return true; /* parse error */ } /* parsing success, now let's look inside request */ LogPrint(eLogDebug, "HTTPProxy: Requested: ", m_ClientRequest.uri); m_RequestURL.parse(m_ClientRequest.uri); bool m_Confirm; std::string jump; if (ExtractAddressHelper(m_RequestURL, jump, m_Confirm)) { if (!m_Addresshelper || !i2p::client::context.GetAddressBook ().IsEnabled ()) { LogPrint(eLogWarning, "HTTPProxy: Addresshelper request rejected"); GenericProxyError(tr("Invalid request"), tr("Addresshelper is not supported")); return true; } if (i2p::client::context.GetAddressBook ().RecordExists (m_RequestURL.host, jump)) { std::string full_url = m_RequestURL.to_string(); SendRedirect(full_url); return true; } else if (!i2p::client::context.GetAddressBook ().FindAddress (m_RequestURL.host) || m_Confirm) { const std::string referer_raw = m_ClientRequest.GetHeader("Referer"); i2p::http::URL referer_url; if (!referer_raw.empty ()) { referer_url.parse (referer_raw); } if (m_RequestURL.host != referer_url.host) { if (m_Confirm) // Attempt to forced overwriting by link with "&update=true" from harmful URL { LogPrint (eLogWarning, "HTTPProxy: Address update from addresshelper rejected for ", m_RequestURL.host, " (referer is ", m_RequestURL.host.empty() ? "empty" : "harmful", ")"); std::string full_url = m_RequestURL.to_string(); std::stringstream ss; ss << tr("Host %s is already in router's addressbook. Be careful: source of this URL may be harmful! Click here to update record: Continue.", m_RequestURL.host.c_str(), full_url.c_str(), (full_url.find('?') != std::string::npos ? "&i2paddresshelper=" : "?i2paddresshelper="), jump.c_str()); GenericProxyInfo(tr("Addresshelper forced update rejected"), ss.str()); } else // Preventing unauthorized additions to the address book { LogPrint (eLogDebug, "HTTPProxy: Adding address from addresshelper for ", m_RequestURL.host, " (generate refer-base page)"); std::string full_url = m_RequestURL.to_string(); std::stringstream ss; ss << tr("To add host %s in router's addressbook, click here: Continue.", m_RequestURL.host.c_str(), full_url.c_str(), (full_url.find('?') != std::string::npos ? "&i2paddresshelper=" : "?i2paddresshelper="), jump.c_str()); GenericProxyInfo(tr("Addresshelper request"), ss.str()); } return true; /* request processed */ } i2p::client::context.GetAddressBook ().InsertAddress (m_RequestURL.host, jump); LogPrint (eLogInfo, "HTTPProxy: Added address from addresshelper for ", m_RequestURL.host); std::string full_url = m_RequestURL.to_string(); std::stringstream ss; ss << tr("Host %s added to router's addressbook from helper. Click here to proceed: Continue.", m_RequestURL.host.c_str(), full_url.c_str()); GenericProxyInfo(tr("Addresshelper adding"), ss.str()); return true; /* request processed */ } else { std::string full_url = m_RequestURL.to_string(); std::stringstream ss; ss << tr("Host %s is already in router's addressbook. Click here to update record: Continue.", m_RequestURL.host.c_str(), full_url.c_str(), (full_url.find('?') != std::string::npos ? "&i2paddresshelper=" : "?i2paddresshelper="), jump.c_str()); GenericProxyInfo(tr("Addresshelper update"), ss.str()); return true; /* request processed */ } } std::string dest_host; uint16_t dest_port; bool useConnect = false; if(m_ClientRequest.method == "CONNECT") { const std::string& uri = m_ClientRequest.uri; auto pos = uri.find(":"); if(pos == std::string::npos || pos == uri.size() - 1) { GenericProxyError(tr("Invalid request"), tr("Invalid request URI")); return true; } else { useConnect = true; dest_port = std::stoi(uri.substr(pos+1)); dest_host = uri.substr(0, pos); } } else { SanitizeHTTPRequest(m_ClientRequest); dest_host = m_RequestURL.host; dest_port = m_RequestURL.port; /* always set port, even if missing in request */ if (!dest_port) dest_port = (m_RequestURL.schema == "https") ? 443 : 80; /* detect dest_host, set proper 'Host' header in upstream request */ if (dest_host != "") { /* absolute url, replace 'Host' header */ std::string h (dest_host); if (dest_port != 0 && dest_port != 80) h += ":" + std::to_string(dest_port); m_ClientRequest.UpdateHeader("Host", h); } else { auto h = m_ClientRequest.GetHeader ("Host"); if (h.length () > 0) { /* relative url and 'Host' header provided. transparent proxy mode? */ i2p::http::URL u; std::string t = "http://" + h; u.parse(t); dest_host = u.host; dest_port = u.port; } else { /* relative url and missing 'Host' header */ GenericProxyError(tr("Invalid request"), tr("Can't detect destination host from request")); return true; } } } /* check dest_host really exists and inside I2P network */ if (str_rmatch(dest_host, ".i2p")) { if (!i2p::client::context.GetAddressBook ().GetAddress (dest_host)) { HostNotFound(dest_host); return true; /* request processed */ } } else { if(m_OutproxyUrl.size()) { LogPrint (eLogDebug, "HTTPProxy: Using outproxy ", m_OutproxyUrl); if(m_ProxyURL.parse(m_OutproxyUrl)) ForwardToUpstreamProxy(); else GenericProxyError(tr("Outproxy failure"), tr("Bad outproxy settings")); } else { LogPrint (eLogWarning, "HTTPProxy: Outproxy failure for ", dest_host, ": no outproxy enabled"); std::stringstream ss; ss << tr("Host %s is not inside I2P network, but outproxy is not enabled", dest_host.c_str ()); GenericProxyError(tr("Outproxy failure"), ss.str()); } return true; } if(useConnect) { HTTPConnect(dest_host, dest_port); return true; } /* make relative url */ m_RequestURL.schema = ""; m_RequestURL.host = ""; m_ClientRequest.uri = m_RequestURL.to_string(); /* drop original request from recv buffer */ m_recv_buf.erase(0, m_req_len); /* build new buffer from modified request and data from original request */ 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); GetOwner()->CreateStream (std::bind (&HTTPReqHandler::HandleStreamRequestComplete, shared_from_this(), std::placeholders::_1), dest_host, dest_port); return true; } void HTTPReqHandler::ForwardToUpstreamProxy() { LogPrint(eLogDebug, "HTTPProxy: Forwarded to upstream"); /* 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 */ 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); 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)); })); } else { /* unknown type, complain */ GenericProxyError(tr("Unknown outproxy URL"), m_ProxyURL.to_string()); } } void HTTPReqHandler::HandleUpstreamProxyResolved(const boost::system::error_code & ec, boost::asio::ip::tcp::resolver::results_type endpoints, ProxyResolvedHandler handler) { if(ec) GenericProxyError(tr("Cannot resolve upstream proxy"), ec.message()); else handler(*endpoints.begin ()); } void HTTPReqHandler::HandleUpstreamSocksProxyConnect(const boost::system::error_code & ec) { if(!ec) { if(m_RequestURL.host.size() > 255) { GenericProxyError(tr("Hostname is too long"), m_RequestURL.host); return; } uint16_t port = m_RequestURL.port; if(!port) port = 80; LogPrint(eLogDebug, "HTTPProxy: Connected to SOCKS upstream"); std::string host = m_RequestURL.host; auto s = shared_from_this (); i2p::transport::Socks5Handshake (*m_proxysock, std::make_pair(host, port), [s](const boost::system::error_code& ec) { if (!ec) s->SocksProxySuccess(); else s->GenericProxyError(tr("SOCKS proxy error"), ec.message ()); }); } else GenericProxyError(tr("Cannot connect to upstream SOCKS proxy"), ec.message()); } void HTTPReqHandler::HandoverToUpstreamProxy() { LogPrint(eLogDebug, "HTTPProxy: Handover to SOCKS proxy"); auto connection = CreateSocketsPipe (GetOwner(), m_proxysock, m_sock); m_sock = nullptr; m_proxysock = nullptr; GetOwner()->AddHandler(connection); connection->Start(); Terminate(); } void HTTPReqHandler::HTTPConnect(std::string_view host, uint16_t port) { LogPrint(eLogDebug, "HTTPProxy: CONNECT ",host, ":", port); if(str_rmatch(host, ".i2p")) GetOwner()->CreateStream (std::bind (&HTTPReqHandler::HandleHTTPConnectStreamRequestComplete, shared_from_this(), std::placeholders::_1), host, port); else ForwardToUpstreamProxy(); } void HTTPReqHandler::HandleHTTPConnectStreamRequestComplete(std::shared_ptr stream) { if(stream) { m_ClientResponse.code = 200; m_ClientResponse.status = "OK"; m_send_buf = m_ClientResponse.to_string(); m_sock->send(boost::asio::buffer(m_send_buf)); auto connection = std::make_shared(GetOwner(), m_sock, stream); GetOwner()->AddHandler(connection); connection->I2PConnect(); m_sock = nullptr; Terminate(); } else { GenericProxyError(tr("CONNECT error"), tr("Failed to connect")); } } void HTTPReqHandler::SocksProxySuccess() { 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()); 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()); else HandoverToUpstreamProxy(); }); } } 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()); } /* 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()); if(ecode) { LogPrint(eLogWarning, "HTTPProxy: Sock recv got error: ", ecode); Terminate(); return; } m_recv_buf.append(reinterpret_cast(m_recv_chunk), len); if (HandleRequest()) { m_recv_buf.clear(); return; } AsyncSockRead(); } void HTTPReqHandler::SentHTTPFailed(const boost::system::error_code & ecode) { if (ecode) LogPrint (eLogError, "HTTPProxy: Closing socket after sending failure because: ", ecode.message ()); Terminate(); } 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.")); return; } if (Kill()) return; LogPrint (eLogDebug, "HTTPProxy: Created new I2PTunnel stream, sSID=", stream->GetSendStreamID(), ", rSID=", stream->GetRecvStreamID()); auto connection = std::make_shared(GetOwner(), m_sock, stream); GetOwner()->AddHandler (connection); connection->I2PConnect (reinterpret_cast(m_send_buf.data()), m_send_buf.length()); 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) { } std::shared_ptr HTTPProxy::CreateHandler(std::shared_ptr socket) { return std::make_shared (this, socket); } } // http } // i2p i2pd-2.56.0/libi2pd_client/HTTPProxy.h000066400000000000000000000025201475272067700173370ustar00rootroot00000000000000/* * 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__ namespace i2p { 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() {}; 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 #endif i2pd-2.56.0/libi2pd_client/I2CP.cpp000066400000000000000000001102051475272067700165460ustar00rootroot00000000000000/* * 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 "I2PEndian.h" #include "Log.h" #include "Timestamp.h" #include "LeaseSet.h" #include "ClientContext.h" #include "Transports.h" #include "Signature.h" #include "Config.h" #include "I2CP.h" 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) { } void I2CPDestination::Stop () { m_LeaseSetCreationTimer.cancel (); LeaseSetDestination::Stop (); m_Owner = nullptr; } void I2CPDestination::SetEncryptionPrivateKey (const uint8_t * key) { m_Decryptor = i2p::data::PrivateKeys::CreateDecryptor (m_Identity->GetCryptoKeyType (), key); } void I2CPDestination::SetECIESx25519EncryptionPrivateKey (const uint8_t * key) { if (!m_ECIESx25519Decryptor || memcmp (m_ECIESx25519PrivateKey, key, 32)) // new key? { 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); else 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; } 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); } void I2CPDestination::CreateNewLeaseSet (const std::vector >& tunnels) { boost::asio::post (GetService (), std::bind (&I2CPDestination::PostCreateNewLeaseSet, this, tunnels)); } void I2CPDestination::PostCreateNewLeaseSet (std::vector > tunnels) { if (m_IsCreatingLeaseSet) { LogPrint (eLogInfo, "I2CP: LeaseSet is being created"); return; } uint8_t priv[256] = {0}; i2p::data::LocalLeaseSet ls (m_Identity, priv, tunnels); // we don't care about encryption key, we need leases only m_LeaseSetExpirationTime = ls.GetExpirationTime (); uint8_t * leases = ls.GetLeases (); int numLeases = leases[-1]; if (m_Owner && numLeases) { 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"); } 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); 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 (); uint8_t * buf = msg->GetPayload (); htobe32buf (buf, len); memcpy (buf + 4, payload, len); msg->len += len + 4; msg->FillI2NPMessageHeader (eI2NPData); 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); }); } } 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); } else if (s->m_Owner) s->m_Owner->SendMessageStatusMessage (nonce, eI2CPMessageStatusNoLeaseSet); }); } } bool I2CPDestination::SendMsg (std::shared_ptr msg, std::shared_ptr remote) { auto remoteSession = GetRoutingSession (remote, true); if (!remoteSession) { 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; if (path) { if (!remoteSession->CleanupUnconfirmedTags ()) // no stuck tags { outboundTunnel = path->outboundTunnel; remoteLease = path->remoteLease; } else remoteSession->SetSharedRoutingPath (nullptr); } if (!outboundTunnel || !remoteLease) { auto leases = remote->GetNonExpiredLeases (false); // without threshold if (leases.empty ()) leases = remote->GetNonExpiredLeases (true); // with threshold if (!leases.empty ()) { auto pool = GetTunnelPool (); remoteLease = leases[(pool ? pool->GetRng ()() : rand ()) % leases.size ()]; auto leaseRouter = i2p::data::netdb.FindRouter (remoteLease->tunnelGateway); outboundTunnel = GetTunnelPool ()->GetNextOutboundTunnel (nullptr, leaseRouter ? leaseRouter->GetCompatibleTransports (false) : (i2p::data::RouterInfo::CompatibleTransports)i2p::data::RouterInfo::eAllTransports); } if (remoteLease && outboundTunnel) remoteSession->SetSharedRoutingPath (std::make_shared ( i2p::garlic::GarlicRoutingPath{outboundTunnel, remoteLease, 10000, 0})); // 10 secs RTT 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 } }); return true; } else { if (outboundTunnel) LogPrint (eLogWarning, "I2CP: Failed to send message. All leases expired"); 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 () { Terminate (); } 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 (); } void I2CPSession::Stop () { Terminate (); } void I2CPSession::ReadProtocolByte () { if (m_Socket) { 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 s->Terminate (); }); } } 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)); } void I2CPSession::HandleReceivedHeader (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (ecode) Terminate (); else { 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 (); } } else // no following payload { HandleMessage (); ReceiveHeader (); // next message } } } 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)); } void I2CPSession::HandleReceivedPayload (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (ecode) Terminate (); else { HandleMessage (); m_PayloadLen = 0; ReceiveHeader (); // next message } } void I2CPSession::HandleMessage () { auto handler = m_Owner.GetMessagesHandlers ()[m_Header[I2CP_HEADER_TYPE_OFFSET]]; if (handler) (this->*handler)(m_Payload, m_PayloadLen); else LogPrint (eLogError, "I2CP: Unknown I2CP message ", (int)m_Header[I2CP_HEADER_TYPE_OFFSET]); } void I2CPSession::Terminate () { if (m_Destination) { m_Destination->Stop (); m_Destination = nullptr; } if (m_Socket) { 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; } } 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) { 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 (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)); } } } void I2CPSession::HandleI2CPMessageSent (const boost::system::error_code& ecode, std::size_t bytes_transferred) { 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; } std::string_view 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 }; } size_t I2CPSession::PutString (uint8_t * buf, size_t len, std::string_view str) { auto l = str.length (); if (l + 1 >= len) l = len - 1; if (l > 255) l = 255; // 1 byte max buf[0] = l; memcpy (buf + 1, str.data (), l); return l + 1; } 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); offset += param.length () + 1; if (buf[offset] != '=') { LogPrint (eLogWarning, "I2CP: Unexpected character ", buf[offset], " instead '=' after ", param); break; } offset++; auto value = ExtractString (buf + offset, len - offset); offset += value.length () + 1; if (buf[offset] != ';') { LogPrint (eLogWarning, "I2CP: Unexpected character ", buf[offset], " instead ';' after ", value); break; } offset++; mapping.emplace (param, value); } } void I2CPSession::GetDateMessageHandler (const uint8_t * buf, size_t len) { // get version auto version = ExtractString (buf, len); auto l = version.length () + 1 + 8; uint8_t * payload = new uint8_t[l]; // set date auto ts = i2p::util::GetMillisecondsSinceEpoch (); htobe64buf (payload, ts); // echo vesrion back PutString (payload + 8, l - 8, version); SendI2CPMessage (I2CP_SET_DATE_MESSAGE, payload, l); delete[] payload; } void I2CPSession::CreateSessionMessageHandler (const uint8_t * buf, size_t len) { RAND_bytes ((uint8_t *)&m_SessionID, 2); 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 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 return; } std::map params; ExtractMapping (buf + offset, optionsSize, params); offset += optionsSize; // options if (params[I2CP_PARAM_MESSAGE_RELIABILITY] == "none") m_IsSendAccepted = false; offset += 8; // date if (identity->Verify (buf, offset, buf + offset)) // signature { 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); } } else { LogPrint (eLogError, "I2CP: Session already exists"); SendSessionStatusMessage (eI2CPSessionStatusRefused); // refused } } else { LogPrint (eLogError, "I2CP: Create session signature verification failed"); SendSessionStatusMessage (eI2CPSessionStatusInvalid); // invalid } } void I2CPSession::DestroySessionMessageHandler (const uint8_t * buf, size_t len) { SendSessionStatusMessage (eI2CPSessionStatusDestroyed); // destroy LogPrint (eLogDebug, "I2CP: Session ", m_SessionID, " destroyed"); Terminate (); } 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); } void I2CPSession::SendSessionStatusMessage (I2CPSessionStatus status) { uint8_t buf[3]; htobe16buf (buf, m_SessionID); buf[2] = (uint8_t)status; SendI2CPMessage (I2CP_SESSION_STATUS_MESSAGE, buf, 3); } void I2CPSession::SendMessageStatusMessage (uint32_t nonce, I2CPMessageStatus status) { if (!nonce) return; // don't send status with zero nonce uint8_t buf[15]; htobe16buf (buf, m_SessionID); htobe32buf (buf + 2, m_MessageID++); buf[6] = (uint8_t)status; memset (buf + 7, 0, 4); // size htobe32buf (buf + 11, nonce); 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); if (sessionID == m_SessionID) { size_t offset = 2; if (m_Destination) { offset += i2p::crypto::DSA_PRIVATE_KEY_LENGTH; // skip signing private key // we always assume this field as 20 bytes (DSA) regardless actual size // instead of //offset += m_Destination->GetIdentity ()->GetSigningPrivateKeyLen (); m_Destination->SetEncryptionPrivateKey (buf + offset); offset += 256; m_Destination->LeaseSetCreated (buf + offset, len - offset); } } 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); } void I2CPSession::SendMessageMessageHandler (const uint8_t * buf, size_t len) { uint16_t sessionID = bufbe16toh (buf); if (sessionID == m_SessionID) { size_t offset = 2; if (m_Destination) { const uint8_t * ident = buf + offset; size_t identSize = i2p::data::GetIdentityBufferLen (ident, len - offset); if (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); } } else LogPrint(eLogError, "I2CP: Cannot send message, too big"); } else LogPrint(eLogError, "I2CP: Invalid identity"); } } else LogPrint (eLogError, "I2CP: Unexpected sessionID ", sessionID); } void I2CPSession::SendMessageExpiresMessageHandler (const uint8_t * buf, size_t len) { SendMessageMessageHandler (buf, len - 8); // ignore flags(2) and expiration(6) } void I2CPSession::HostLookupMessageHandler (const uint8_t * buf, size_t len) { uint16_t sessionID = bufbe16toh (buf); if (sessionID == m_SessionID || sessionID == 0xFFFF) // -1 means without session { uint32_t requestID = bufbe32toh (buf + 2); //uint32_t timeout = bufbe32toh (buf + 6); i2p::data::IdentHash ident; switch (buf[10]) { case 0: // hash ident = i2p::data::IdentHash (buf + 11); break; case 1: // address { auto name = ExtractString (buf + 11, len - 11); auto addr = i2p::client::context.GetAddressBook ().GetAddress (name); if (!addr || !addr->IsIdentHash ()) { // TODO: handle blinded addresses 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"); SendHostReplyMessage (requestID, nullptr); return; } std::shared_ptr destination = m_Destination; if(!destination) destination = i2p::client::context.GetSharedLocalDestination (); if (destination) { auto ls = destination->FindLeaseSet (ident); if (ls) SendHostReplyMessage (requestID, ls->GetIdentity ()); else { auto s = shared_from_this (); destination->RequestDestination (ident, [s, requestID](std::shared_ptr leaseSet) { s->SendHostReplyMessage (requestID, leaseSet ? leaseSet->GetIdentity () : nullptr); }); } } else SendHostReplyMessage (requestID, nullptr); } else LogPrint (eLogError, "I2CP: Unexpected sessionID ", sessionID); } void I2CPSession::SendHostReplyMessage (uint32_t requestID, std::shared_ptr identity) { if (identity) { size_t l = identity->GetFullLen () + 7; uint8_t * buf = new uint8_t[l]; htobe16buf (buf, m_SessionID); htobe32buf (buf + 2, requestID); buf[6] = 0; // result code identity->ToBuffer (buf + 7, l - 7); SendI2CPMessage (I2CP_HOST_REPLY_MESSAGE, buf, l); delete[] buf; } else { uint8_t buf[7]; htobe16buf (buf, m_SessionID); htobe32buf (buf + 2, requestID); buf[6] = 1; // result code SendI2CPMessage (I2CP_HOST_REPLY_MESSAGE, buf, 7); } } void I2CPSession::DestLookupMessageHandler (const uint8_t * buf, size_t len) { if (m_Destination) { auto ls = m_Destination->FindLeaseSet (buf); if (ls) { auto l = ls->GetIdentity ()->GetFullLen (); uint8_t * identBuf = new uint8_t[l]; ls->GetIdentity ()->ToBuffer (identBuf, l); SendI2CPMessage (I2CP_DEST_REPLY_MESSAGE, identBuf, l); delete[] identBuf; } else { auto s = shared_from_this (); i2p::data::IdentHash ident (buf); m_Destination->RequestDestination (ident, [s, ident](std::shared_ptr leaseSet) { if (leaseSet) // found { auto l = leaseSet->GetIdentity ()->GetFullLen (); uint8_t * identBuf = new uint8_t[l]; leaseSet->GetIdentity ()->ToBuffer (identBuf, l); s->SendI2CPMessage (I2CP_DEST_REPLY_MESSAGE, identBuf, l); delete[] identBuf; } else s->SendI2CPMessage (I2CP_DEST_REPLY_MESSAGE, ident, 32); // not found }); } } else SendI2CPMessage (I2CP_DEST_REPLY_MESSAGE, buf, 32); } void I2CPSession::GetBandwidthLimitsMessageHandler (const uint8_t * buf, size_t len) { 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 SendI2CPMessage (I2CP_BANDWIDTH_LIMITS_MESSAGE, limits, 64); } void I2CPSession::SendMessagePayloadMessage (const uint8_t * payload, size_t len) { // 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; 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 (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)); } } } 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)) { memset (m_MessagesHandlers, 0, sizeof (m_MessagesHandlers)); m_MessagesHandlers[I2CP_GET_DATE_MESSAGE] = &I2CPSession::GetDateMessageHandler; m_MessagesHandlers[I2CP_CREATE_SESSION_MESSAGE] = &I2CPSession::CreateSessionMessageHandler; 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; m_MessagesHandlers[I2CP_DEST_LOOKUP_MESSAGE] = &I2CPSession::DestLookupMessageHandler; m_MessagesHandlers[I2CP_GET_BANDWIDTH_LIMITS_MESSAGE] = &I2CPSession::GetBandwidthLimitsMessageHandler; } I2CPServer::~I2CPServer () { if (IsRunning ()) Stop (); } void I2CPServer::Start () { Accept (); StartIOService (); } void I2CPServer::Stop () { m_Acceptor.cancel (); decltype(m_Sessions) sessions; m_Sessions.swap (sessions); for (auto& it: sessions) it.second->Stop (); StopIOService (); } void I2CPServer::Accept () { auto newSocket = std::make_shared (GetIOService ()); 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) { if (!ecode && socket) { boost::system::error_code ec; auto ep = socket->remote_endpoint (ec); if (!ec) { 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 ()); } else LogPrint (eLogError, "I2CP: Accept error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Accept (); } bool I2CPServer::InsertSession (std::shared_ptr session) { if (!session) return false; if (!m_Sessions.insert({session->GetSessionID (), session}).second) { LogPrint (eLogError, "I2CP: Duplicate session id ", session->GetSessionID ()); return false; } return true; } void I2CPServer::RemoveSession (uint16_t sessionID) { 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; } } } i2pd-2.56.0/libi2pd_client/I2CP.h000066400000000000000000000233701475272067700162210ustar00rootroot00000000000000/* * 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 I2CP_H__ #define I2CP_H__ #include #include #include #include #include #include #include #include #include #include "util.h" #include "Destination.h" #include "Streaming.h" namespace i2p { 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_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; const size_t I2CP_HEADER_SIZE = I2CP_HEADER_TYPE_OFFSET + 1; const uint8_t I2CP_GET_DATE_MESSAGE = 32; const uint8_t I2CP_SET_DATE_MESSAGE = 33; const uint8_t I2CP_CREATE_SESSION_MESSAGE = 1; const uint8_t I2CP_RECONFIGURE_SESSION_MESSAGE = 2; const uint8_t I2CP_SESSION_STATUS_MESSAGE = 20; 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; const uint8_t I2CP_MESSAGE_STATUS_MESSAGE = 22; const uint8_t I2CP_HOST_LOOKUP_MESSAGE = 38; const uint8_t I2CP_HOST_REPLY_MESSAGE = 39; const uint8_t I2CP_DEST_LOOKUP_MESSAGE = 34; const uint8_t I2CP_DEST_REPLY_MESSAGE = 35; const uint8_t I2CP_GET_BANDWIDTH_LIMITS_MESSAGE = 8; const uint8_t I2CP_BANDWIDTH_LIMITS_MESSAGE = 23; enum I2CPMessageStatus { 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_MESSAGE_RELIABILITY[] = "i2cp.messageReliability"; class I2CPSession; class I2CPDestination: public LeaseSetDestination { 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 (); 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; bool SupportsEncryptionType (i2p::data::CryptoKeyType keyType) const; const uint8_t * GetEncryptionPublicKey (i2p::data::CryptoKeyType keyType) const; // for 4 only std::shared_ptr GetIdentity () const { return m_Identity; }; protected: void CleanupDestination (); // I2CP void HandleDataMessage (const uint8_t * buf, size_t len); void CreateNewLeaseSet (const std::vector >& tunnels); private: std::shared_ptr GetSharedFromThis () { return std::static_pointer_cast(shared_from_this ()); } bool SendMsg (std::shared_ptr msg, std::shared_ptr remote); bool SendMsg (std::shared_ptr garlic, std::shared_ptr outboundTunnel, std::shared_ptr remoteLease); void PostCreateNewLeaseSet (std::vector > tunnels); private: std::shared_ptr m_Owner; std::shared_ptr m_Identity; i2p::data::CryptoKeyType m_EncryptionKeyType; std::shared_ptr m_Decryptor; // standard std::shared_ptr m_ECIESx25519Decryptor; uint8_t m_ECIESx25519PrivateKey[32]; uint64_t m_LeaseSetExpirationTime; bool m_IsCreatingLeaseSet, m_IsSameThread; boost::asio::deadline_timer m_LeaseSetCreationTimer; 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; class I2CPSession: public std::enable_shared_from_this { public: I2CPSession (I2CPServer& owner, std::shared_ptr socket); ~I2CPSession (); void Start (); void Stop (); uint16_t GetSessionID () const { return m_SessionID; }; std::shared_ptr GetDestination () const { return m_Destination; }; // called from I2CPDestination 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); void CreateSessionMessageHandler (const uint8_t * buf, size_t len); 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); void DestLookupMessageHandler (const uint8_t * buf, size_t len); void GetBandwidthLimitsMessageHandler (const uint8_t * buf, size_t len); private: void ReadProtocolByte (); void ReceiveHeader (); void HandleReceivedHeader (const boost::system::error_code& ecode, std::size_t bytes_transferred); void ReceivePayload (); void HandleReceivedPayload (const boost::system::error_code& ecode, std::size_t bytes_transferred); void HandleMessage (); void Terminate (); void HandleI2CPMessageSent (const boost::system::error_code& ecode, std::size_t bytes_transferred); std::string_view ExtractString (const uint8_t * buf, size_t len); 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); void SendSessionStatusMessage (I2CPSessionStatus 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]; 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 { public: I2CPServer (const std::string& interface, uint16_t port, bool isSingleThread); ~I2CPServer (); void Start (); void Stop (); auto& GetService () { return GetIOService (); }; bool IsSingleThread () const { return m_IsSingleThread; }; bool InsertSession (std::shared_ptr session); void RemoveSession (uint16_t sessionID); std::shared_ptr FindSessionByIdentHash (const i2p::data::IdentHash& ident) const; private: void Accept (); 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; public: const decltype(m_MessagesHandlers)& GetMessagesHandlers () const { return m_MessagesHandlers; }; // for HTTP const decltype(m_Sessions)& GetSessions () const { return m_Sessions; }; }; } } #endif i2pd-2.56.0/libi2pd_client/I2PService.cpp000066400000000000000000000103451475272067700177700ustar00rootroot00000000000000/* * 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" #include "I2PService.h" #include namespace i2p { namespace client { static const i2p::data::SigningKeyType I2P_SERVICE_DEFAULT_KEY_TYPE = i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519; 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) { m_LocalDestination->Acquire (); } I2PService::I2PService (i2p::data::SigningKeyType kt): m_LocalDestination (i2p::client::context.CreateNewLocalDestination (false, kt)), m_ReadyTimer(m_LocalDestination->GetService()), m_ConnectTimeout(0), isUpdated (true) { m_LocalDestination->Acquire (); } I2PService::~I2PService () { ClearHandlers (); if (m_LocalDestination) m_LocalDestination->Release (); } void I2PService::ClearHandlers () { if(m_ConnectTimeout) m_ReadyTimer.cancel(); std::unique_lock l(m_HandlersMutex); for (auto it: m_Handlers) it->Terminate (); m_Handlers.clear(); } void I2PService::SetConnectTimeout(uint32_t timeout) { 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; 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; } void I2PService::HandleReadyCheckTimer(const boost::system::error_code &ec) { if(ec || m_LocalDestination->IsReady()) { for(auto & itr : m_ReadyCallbacks) itr.first(ec); m_ReadyCallbacks.clear(); } else if(!m_LocalDestination->IsReady()) { // expire timed out requests uint32_t now = i2p::util::GetSecondsSinceEpoch (); auto itr = m_ReadyCallbacks.begin(); while(itr != m_ReadyCallbacks.end()) { if(itr->second != NEVER_TIMES_OUT && now >= itr->second) { itr->first(boost::asio::error::timed_out); itr = m_ReadyCallbacks.erase(itr); } else ++itr; } } if(!ec && m_ReadyCallbacks.size()) TriggerReadyCheckTimer(); else m_ReadyTimerTriggered = false; } void I2PService::CreateStream (StreamRequestComplete streamRequestComplete, std::string_view dest, uint16_t port) { assert(streamRequestComplete); auto address = i2p::client::context.GetAddressBook ().GetAddress (dest); if (address) CreateStream(streamRequestComplete, address, port); else { LogPrint (eLogWarning, "I2PService: Remote destination not found: ", dest); streamRequestComplete (nullptr); } } void I2PService::CreateStream(StreamRequestComplete streamRequestComplete, std::shared_ptr address, uint16_t port) { if(m_ConnectTimeout && !m_LocalDestination->IsReady()) { 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); else this->m_LocalDestination->CreateStream (streamRequestComplete, address->blindedPublicKey, port); } }); } else { if (address->IsIdentHash ()) m_LocalDestination->CreateStream (streamRequestComplete, address->identHash, port); else m_LocalDestination->CreateStream (streamRequestComplete, address->blindedPublicKey, port); } } } } i2pd-2.56.0/libi2pd_client/I2PService.h000066400000000000000000000217161475272067700174410ustar00rootroot00000000000000/* * 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__ #include #include #include #include #include #include "Destination.h" #include "Identity.h" #include "AddressBook.h" namespace i2p { namespace client { class I2PServiceHandler; class I2PService : public std::enable_shared_from_this { public: typedef std::function ReadyCallback; public: I2PService (std::shared_ptr localDestination = nullptr); I2PService (i2p::data::SigningKeyType kt); virtual ~I2PService (); inline void AddHandler (std::shared_ptr conn) { std::unique_lock l(m_HandlersMutex); m_Handlers.insert(conn); } inline void RemoveHandler (std::shared_ptr conn) { std::unique_lock l(m_HandlersMutex); m_Handlers.erase(conn); } void ClearHandlers (); void SetConnectTimeout(uint32_t timeout); void AddReadyCallback(ReadyCallback cb); inline std::shared_ptr GetLocalDestination () { 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 (); } virtual void Start () = 0; virtual void Stop () = 0; 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 }; /*Simple interface for I2PHandlers, allows detection of finalization amongst other things */ 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 inline bool Dead () { return m_Dead; } // Call when done to clean up (make sure Kill is called first) inline void Done (std::shared_ptr me) { if(m_Service) m_Service->RemoveHandler(me); } // Call to talk with the owner 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; // bidirectional pipe for 2 stream sockets template class SocketsPipe: 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"; } protected: virtual std::shared_ptr CreateHandler(std::shared_ptr socket) = 0; 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; }; class TCPIPAcceptor: public ServiceAcceptor { public: TCPIPAcceptor (const std::string& address, uint16_t port, std::shared_ptr localDestination = nullptr) : ServiceAcceptor (boost::asio::ip::tcp::endpoint (boost::asio::ip::make_address(address), port), localDestination) {} }; } } #endif i2pd-2.56.0/libi2pd_client/I2PTunnel.cpp000066400000000000000000000651331475272067700176420ustar00rootroot00000000000000/* * 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 "Destination.h" #include "ClientContext.h" #include "I2PTunnel.h" #include "util.h" namespace i2p { namespace client { /** set standard socket options */ static void I2PTunnelSetSocketOptions (std::shared_ptr socket) { if (socket && socket->is_open()) { boost::asio::socket_base::receive_buffer_size option(I2P_TUNNEL_CONNECTION_BUFFER_SIZE); socket->set_option(option); } } I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, std::shared_ptr socket, std::shared_ptr leaseSet, uint16_t port): I2PServiceHandler(owner), m_Socket (socket), m_RemoteEndpoint (socket->remote_endpoint ()), m_IsQuiet (true) { m_Stream = GetOwner()->GetLocalDestination ()->CreateStream (leaseSet, port); } I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, std::shared_ptr socket, std::shared_ptr stream): I2PServiceHandler(owner), m_Socket (socket), m_Stream (stream), m_RemoteEndpoint (socket->remote_endpoint ()), m_IsQuiet (true) { } I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, std::shared_ptr stream, const boost::asio::ip::tcp::endpoint& target, bool quiet, std::shared_ptr sslCtx): I2PServiceHandler(owner), m_Stream (stream), m_RemoteEndpoint (target), m_IsQuiet (quiet) { m_Socket = std::make_shared (owner->GetService ()); if (sslCtx) m_SSL = std::make_shared > (*m_Socket, *sslCtx); } I2PTunnelConnection::~I2PTunnelConnection () { } void I2PTunnelConnection::I2PConnect (const uint8_t * msg, size_t len) { if (m_Stream) { if (msg) m_Stream->Send (msg, len); // connect and send else m_Stream->Send (m_Buffer, 0); // connect } StreamReceive (); Receive (); } boost::asio::ip::address GetLoopbackAddressFor(const i2p::data::IdentHash & addr) { boost::asio::ip::address_v4::bytes_type bytes; const uint8_t * ident = addr; bytes[0] = 127; memcpy (bytes.data ()+1, ident, 3); boost::asio::ip::address ourIP = boost::asio::ip::address_v4 (bytes); return ourIP; } #ifdef __linux__ static void MapToLoopback(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 ()); } } #endif void I2PTunnelConnection::Connect (bool isUniqueLocal) { if (m_Socket) { I2PTunnelSetSocketOptions (m_Socket); #ifdef __linux__ if (isUniqueLocal && m_RemoteEndpoint.address ().is_v4 () && m_RemoteEndpoint.address ().to_v4 ().to_bytes ()[0] == 127) { m_Socket->open (boost::asio::ip::tcp::v4 ()); auto ident = m_Stream->GetRemoteIdentity()->GetIdentHash(); MapToLoopback(m_Socket, ident); } #endif m_Socket->async_connect (m_RemoteEndpoint, std::bind (&I2PTunnelConnection::HandleConnect, shared_from_this (), std::placeholders::_1)); } } 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 (); m_Stream.reset (); } boost::system::error_code ec; m_Socket->shutdown(boost::asio::ip::tcp::socket::shutdown_send, ec); // avoid RST m_Socket->close (); Done(shared_from_this ()); } void I2PTunnelConnection::Receive () { if (m_SSL) m_SSL->async_read_some (boost::asio::buffer(m_Buffer, I2P_TUNNEL_CONNECTION_BUFFER_SIZE), std::bind(&I2PTunnelConnection::HandleReceive, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); else m_Socket->async_read_some (boost::asio::buffer(m_Buffer, I2P_TUNNEL_CONNECTION_BUFFER_SIZE), std::bind(&I2PTunnelConnection::HandleReceive, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } void I2PTunnelConnection::HandleReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (ecode) { if (ecode != boost::asio::error::operation_aborted) { LogPrint (eLogError, "I2PTunnel: Read error: ", ecode.message ()); Terminate (); } } else WriteToStream (m_Buffer, bytes_transferred); } void I2PTunnelConnection::WriteToStream (const uint8_t * buf, size_t len) { if (m_Stream) { auto s = shared_from_this (); m_Stream->AsyncSend (buf, len, [s](const boost::system::error_code& ecode) { if (!ecode) s->Receive (); else s->Terminate (); }); } } void I2PTunnelConnection::HandleWrite (const boost::system::error_code& ecode) { if (ecode) { LogPrint (eLogError, "I2PTunnel: Write error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate (); } else StreamReceive (); } void I2PTunnelConnection::StreamReceive () { if (m_Stream) { if (m_Stream->GetStatus () == i2p::stream::eStreamStatusNew || m_Stream->GetStatus () == i2p::stream::eStreamStatusOpen) // regular { m_Stream->AsyncReceive (boost::asio::buffer (m_StreamBuffer, I2P_TUNNEL_CONNECTION_BUFFER_SIZE), std::bind (&I2PTunnelConnection::HandleStreamReceive, shared_from_this (), 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_BUFFER_SIZE); if (len > 0) // still some data Write (m_StreamBuffer, len); else // no more data Terminate (); } } } void I2PTunnelConnection::HandleStreamReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (ecode) { if (ecode != boost::asio::error::operation_aborted) { 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 ()) StreamReceive (); else Terminate (); } else Terminate (); } else Write (m_StreamBuffer, bytes_transferred); } 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)); } void I2PTunnelConnection::HandleConnect (const boost::system::error_code& ecode) { if (ecode) { 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)); else Established (); } } void I2PTunnelConnection::HandleHandshake (const boost::system::error_code& ecode) { if (ecode) { LogPrint (eLogError, "I2PTunnel: Handshake error: ", ecode.message ()); Terminate (); } else { LogPrint (eLogDebug, "I2PTunnel: SSL connected"); Established (); } } void I2PTunnelConnection::Established () { if (m_IsQuiet) StreamReceive (); else { // send destination first like received from I2P std::string dest = m_Stream->GetRemoteIdentity ()->ToBase64 (); dest += "\n"; if(sizeof(m_StreamBuffer) >= dest.size()) { memcpy (m_StreamBuffer, dest.c_str (), dest.size ()); } HandleStreamReceive (boost::system::error_code (), dest.size ()); } Receive (); } void I2PClientTunnelConnectionHTTP::Write (const uint8_t * buf, size_t len) { if (m_HeaderSent) I2PTunnelConnection::Write (buf, len); else { m_InHeader.clear (); m_InHeader.write ((const char *)buf, len); std::string line; bool endOfHeader = false; while (!endOfHeader) { std::getline(m_InHeader, line); if (!m_InHeader.fail ()) { if (line == "\r") endOfHeader = true; else { 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_ConnectionSent = true; } else if (!m_ProxyConnectionSent && !line.compare(0, 16, "Proxy-Connection")) { m_OutHeader << "Proxy-Connection: close\r\n"; m_ProxyConnectionSent = true; } else 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"; 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, true, sslCtx), m_Host (host), m_XI2P (XI2P), m_HeaderSent (false), m_ResponseHeaderSent (false) { if (sslCtx) SSL_set_tlsext_host_name(GetSSL ()->native_handle(), host.c_str ()); } void I2PServerTunnelConnectionHTTP::Write (const uint8_t * buf, size_t len) { if (m_HeaderSent) I2PTunnelConnection::Write (buf, len); else { 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::vector 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 (line == "\r") endOfHeader = true; else { static const std::vector 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) m_OutHeader << line << "\n"; } } else { // insert incomplete line back m_InHeader.clear (); m_InHeader << line; break; } } if (endOfHeader) { 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 (""); } 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, true, sslCtx), m_From (stream->GetRemoteIdentity ()), m_NeedsWebIrc (webircpass.length() ? true : false), m_WebircPass (webircpass) { } void I2PTunnelConnectionIRC::Write (const uint8_t * buf, size_t len) { m_OutPacket.str (""); 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_InPacket.clear (); m_InPacket.write ((const char *)buf, len); while (!m_InPacket.eof () && !m_InPacket.fail ()) { std::string line; std::getline (m_InPacket, line); if (line.length () == 0 && m_InPacket.eof ()) m_InPacket.str (""); auto pos = line.find ("USER"); if (!pos) // start of line { pos = line.find (" "); pos++; pos = line.find (" ", pos); pos++; auto nextpos = line.find (" ", pos); m_OutPacket << line.substr (0, pos); m_OutPacket << context.GetAddressBook ().ToAddress (m_From->GetIdentHash ()); m_OutPacket << line.substr (nextpos) << '\n'; } else m_OutPacket << line << '\n'; } I2PTunnelConnection::Write ((uint8_t *)m_OutPacket.str ().c_str (), m_OutPacket.str ().length ()); } /* This handler tries to establish 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), 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; std::shared_ptr m_Socket; }; void I2PClientTunnelHandler::Handle() { GetOwner()->CreateStream ( std::bind (&I2PClientTunnelHandler::HandleStreamRequestComplete, shared_from_this(), std::placeholders::_1), m_Address, m_DestinationPort); } void I2PClientTunnelHandler::HandleStreamRequestComplete (std::shared_ptr stream) { if (stream) { if (Kill()) return; LogPrint (eLogDebug, "I2PTunnel: New connection"); auto connection = std::make_shared(GetOwner(), m_Socket, stream); GetOwner()->AddHandler (connection); connection->I2PConnect (); Done(shared_from_this()); } else { LogPrint (eLogError, "I2PTunnel: Client Tunnel Issue when creating the stream, check the previous warnings for more info."); Terminate(); } } void I2PClientTunnelHandler::Terminate() { if (Kill()) return; if (m_Socket) { m_Socket->close(); m_Socket = nullptr; } Done(shared_from_this()); } I2PClientTunnel::I2PClientTunnel (const std::string& name, const std::string& destination, const std::string& address, uint16_t port, std::shared_ptr localDestination, uint16_t destinationPort): TCPIPAcceptor (address, port, localDestination), m_Name (name), m_Destination (destination), m_DestinationPort (destinationPort), m_KeepAliveInterval (0) { } void I2PClientTunnel::Start () { TCPIPAcceptor::Start (); GetAddress (); if (m_KeepAliveInterval) ScheduleKeepAliveTimer (); } void I2PClientTunnel::Stop () { TCPIPAcceptor::Stop(); m_Address = nullptr; if (m_KeepAliveTimer) m_KeepAliveTimer->cancel (); } void I2PClientTunnel::SetKeepAliveInterval (uint32_t keepAliveInterval) { m_KeepAliveInterval = keepAliveInterval; if (m_KeepAliveInterval) m_KeepAliveTimer.reset (new boost::asio::deadline_timer (GetLocalDestination ()->GetService ())); } /* HACK: maybe we should create a caching IdentHash provider in AddressBook */ std::shared_ptr I2PClientTunnel::GetAddress () { if (!m_Address) { m_Address = i2p::client::context.GetAddressBook ().GetAddress (m_Destination); if (!m_Address) LogPrint (eLogWarning, "I2PTunnel: Remote destination ", m_Destination, " not found"); } return m_Address; } std::shared_ptr I2PClientTunnel::CreateHandler(std::shared_ptr socket) { auto address = GetAddress (); if (address) return std::make_shared(this, address, m_DestinationPort, socket); else return nullptr; } void I2PClientTunnel::ScheduleKeepAliveTimer () { if (m_KeepAliveTimer) { m_KeepAliveTimer->expires_from_now (boost::posix_time::seconds (m_KeepAliveInterval)); m_KeepAliveTimer->async_wait (std::bind (&I2PClientTunnel::HandleKeepAliveTimer, this, std::placeholders::_1)); } } void I2PClientTunnel::HandleKeepAliveTimer (const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) { if (m_Address && m_Address->IsValid ()) { if (m_Address->IsIdentHash ()) GetLocalDestination ()->SendPing (m_Address->identHash); else GetLocalDestination ()->SendPing (m_Address->blindedPublicKey); } ScheduleKeepAliveTimer (); } } I2PServerTunnel::I2PServerTunnel (const std::string& name, const std::string& address, uint16_t port, std::shared_ptr localDestination, uint16_t inport, bool gzip): 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); } void I2PServerTunnel::Start () { m_Endpoint.port (m_Port); boost::system::error_code ec; auto addr = boost::asio::ip::make_address (m_Address, ec); if (!ec) { m_Endpoint.address (addr); Accept (); } else { auto resolver = std::make_shared(GetService ()); resolver->async_resolve (m_Address, "", std::bind (&I2PServerTunnel::HandleResolve, this, std::placeholders::_1, std::placeholders::_2, resolver)); } } void I2PServerTunnel::Stop () { if (m_PortDestination) m_PortDestination->ResetAcceptor (); auto localDestination = GetLocalDestination (); if (localDestination) localDestination->StopAcceptingStreams (); ClearHandlers (); } void I2PServerTunnel::HandleResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::results_type endpoints, 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); m_Endpoint.address (addr); Accept (); } else LogPrint (eLogError, "I2PTunnel: Unable to resolve server tunnel address ", m_Address, ": ", ecode.message ()); } void I2PServerTunnel::SetAccessList (const std::set& accessList) { m_AccessList = accessList; 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) m_PortDestination->SetAcceptor (std::bind (&I2PServerTunnel::HandleAccept, this, std::placeholders::_1)); auto localDestination = GetLocalDestination (); if (localDestination) { if (!localDestination->IsAcceptingStreams ()) // set it as default if not set yet localDestination->AcceptStreams (std::bind (&I2PServerTunnel::HandleAccept, this, std::placeholders::_1)); } else LogPrint (eLogError, "I2PTunnel: Local destination not set for server tunnel"); } void I2PServerTunnel::HandleAccept (std::shared_ptr stream) { if (stream) { if (m_IsAccessList) { if (!m_AccessList.count (stream->GetRemoteIdentity ()->GetIdentHash ())) { LogPrint (eLogWarning, "I2PTunnel: Address ", stream->GetRemoteIdentity ()->GetIdentHash ().ToBase32 (), " is not in white list. Incoming connection dropped"); stream->Close (); return; } } // new connection auto conn = CreateI2PConnection (stream); AddHandler (conn); if (m_LocalAddress) conn->Connect (*m_LocalAddress); else conn->Connect (m_IsUniqueLocal); } } std::shared_ptr I2PServerTunnel::CreateI2PConnection (std::shared_ptr stream) { return std::make_shared (this, stream, GetEndpoint (), true, m_SSLCtx); } 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): I2PServerTunnel (name, address, port, localDestination, inport, gzip), m_Host (host) { } 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 ()); } 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): I2PServerTunnel (name, address, port, localDestination, inport, gzip), m_WebircPass (webircpass) { } std::shared_ptr I2PServerTunnelIRC::CreateI2PConnection (std::shared_ptr stream) { return std::make_shared (this, stream, GetEndpoint (), m_WebircPass, GetSSLCtx ()); } } } i2pd-2.56.0/libi2pd_client/I2PTunnel.h000066400000000000000000000212141475272067700172770ustar00rootroot00000000000000/* * 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 I2PTUNNEL_H__ #define I2PTUNNEL_H__ #include #include #include #include #include #include #include #include #include "Identity.h" #include "Destination.h" #include "Streaming.h" #include "I2PService.h" #include "AddressBook.h" namespace i2p { namespace client { const size_t I2P_TUNNEL_CONNECTION_BUFFER_SIZE = 65536; const int I2P_TUNNEL_CONNECTION_MAX_IDLE = 3600; // in seconds const int I2P_TUNNEL_DESTINATION_REQUEST_TIMEOUT = 10; // in seconds // 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; 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 I2PTunnelConnection (I2PService * owner, std::shared_ptr socket, std::shared_ptr stream); // to I2P using simplified API I2PTunnelConnection (I2PService * owner, std::shared_ptr stream, const boost::asio::ip::tcp::endpoint& target, bool quiet = true, std::shared_ptr sslCtx = nullptr); // 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: void Terminate (); void Receive (); void StreamReceive (); 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 > GetSSL () const { return m_SSL; }; private: void HandleConnect (const boost::system::error_code& ecode); void HandleHandshake (const boost::system::error_code& ecode); void Established (); void HandleReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred); void HandleWrite (const boost::system::error_code& ecode); void HandleStreamReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred); private: uint8_t m_Buffer[I2P_TUNNEL_CONNECTION_BUFFER_SIZE], m_StreamBuffer[I2P_TUNNEL_CONNECTION_BUFFER_SIZE]; std::shared_ptr m_Socket; std::shared_ptr > m_SSL; std::shared_ptr m_Stream; boost::asio::ip::tcp::endpoint m_RemoteEndpoint; bool m_IsQuiet; // don't send destination }; class I2PClientTunnelConnectionHTTP: public I2PTunnelConnection { public: I2PClientTunnelConnectionHTTP (I2PService * owner, std::shared_ptr socket, std::shared_ptr stream): I2PTunnelConnection (owner, socket, stream), m_HeaderSent (false), m_ConnectionSent (false), m_ProxyConnectionSent (false) {}; protected: void Write (const uint8_t * buf, size_t len); private: std::stringstream m_InHeader, m_OutHeader; bool m_HeaderSent, m_ConnectionSent, m_ProxyConnectionSent; }; 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); protected: void Write (const uint8_t * buf, size_t len); void WriteToStream (const uint8_t * buf, size_t len); private: std::string m_Host, m_XI2P; std::stringstream m_InHeader, m_OutHeader; bool m_HeaderSent, m_ResponseHeaderSent; }; 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); protected: void Write (const uint8_t * buf, size_t len); private: std::shared_ptr m_From; std::stringstream m_OutPacket, m_InPacket; bool m_NeedsWebIrc; std::string m_WebircPass; }; 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); ~I2PClientTunnel () {} void Start (); void Stop (); const char* GetName() { return m_Name.c_str (); } void SetKeepAliveInterval (uint32_t keepAliveInterval); private: std::shared_ptr GetAddress (); void ScheduleKeepAliveTimer (); void HandleKeepAliveTimer (const boost::system::error_code& ecode); private: std::string m_Name, m_Destination; std::shared_ptr m_Address; uint16_t m_DestinationPort; uint32_t m_KeepAliveInterval; std::unique_ptr m_KeepAliveTimer; }; 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); void Start (); void Stop (); void SetAccessList (const std::set& accessList); 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; }; uint16_t GetLocalPort () const { return m_PortDestination->GetLocalPort (); }; const boost::asio::ip::tcp::endpoint& GetEndpoint () const { return m_Endpoint; } const char* GetName() { return m_Name.c_str (); } private: void HandleResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::results_type endpoints, std::shared_ptr resolver); void Accept (); void HandleAccept (std::shared_ptr stream); virtual std::shared_ptr CreateI2PConnection (std::shared_ptr stream); private: bool m_IsUniqueLocal; std::string m_Name, m_Address; uint16_t m_Port; 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, std::shared_ptr localDestination, const std::string& host, uint16_t 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; }; class I2PServerTunnelIRC: public I2PServerTunnel { public: I2PServerTunnelIRC (const std::string& name, const std::string& address, uint16_t port, std::shared_ptr localDestination, const std::string& webircpass, uint16_t 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); } } #endif i2pd-2.56.0/libi2pd_client/MatchedDestination.cpp000066400000000000000000000060031475272067700216200ustar00rootroot00000000000000/* * 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" namespace i2p { namespace client { MatchedTunnelDestination::MatchedTunnelDestination(const i2p::data::PrivateKeys & keys, const std::string & remoteName, const std::map * params) : RunnableClientDestination(keys, false, params), m_RemoteName(remoteName) {} void MatchedTunnelDestination::ResolveCurrentLeaseSet() { auto addr = i2p::client::context.GetAddressBook().GetAddress (m_RemoteName); if(addr && addr->IsIdentHash ()) { 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); } void MatchedTunnelDestination::HandleFoundCurrentLeaseSet(std::shared_ptr ls) { if(ls) { 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(); }); } } void MatchedTunnelDestination::Start() { ClientDestination::Start(); m_ResolveTimer = std::make_shared(GetService()); GetTunnelPool()->SetCustomPeerSelector(this); ResolveCurrentLeaseSet(); } void MatchedTunnelDestination::Stop() { ClientDestination::Stop(); if(m_ResolveTimer) m_ResolveTimer->cancel(); } 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))) 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) { 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"); } else LogPrint(eLogWarning, "Destination: Could not find proper IBGW for matched outbound tunnel"); } } return true; } } } i2pd-2.56.0/libi2pd_client/MatchedDestination.h000066400000000000000000000022321475272067700212650ustar00rootroot00000000000000/* * 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" #include namespace i2p { namespace client { /** * client tunnel that uses same OBEP as IBGW of each remote lease for a remote destination */ class MatchedTunnelDestination : public RunnableClientDestination, public i2p::tunnel::ITunnelPeerSelector { public: 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); 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; }; } } #endif i2pd-2.56.0/libi2pd_client/SAM.cpp000066400000000000000000001420411475272067700164740ustar00rootroot00000000000000/* * 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 #include #endif #include "Base.h" #include "Identity.h" #include "Log.h" #include "Destination.h" #include "ClientContext.h" #include "util.h" #include "SAM.h" namespace i2p { namespace client { SAMSocket::SAMSocket (SAMBridge& owner): m_Owner (owner), m_Socket(owner.GetService()), m_Timer (m_Owner.GetService ()), m_BufferOffset (0), m_SocketType (eSAMSocketTypeUnknown), m_IsSilent (false), m_IsAccepting (false), m_Stream (nullptr) { } SAMSocket::~SAMSocket () { m_Stream = nullptr; } void SAMSocket::Terminate (const char* reason) { if(m_Stream) { m_Stream->AsyncClose (); m_Stream = nullptr; } auto Session = m_Owner.FindSession(m_ID); switch (m_SocketType) { case eSAMSocketTypeSession: m_Owner.CloseSession (m_ID); break; case eSAMSocketTypeStream: { break; } case eSAMSocketTypeAcceptor: case eSAMSocketTypeForward: { if (Session) { if (m_IsAccepting && Session->GetLocalDestination ()) Session->GetLocalDestination ()->StopAcceptingStreams (); } break; } default: ; } m_SocketType = eSAMSocketTypeTerminated; if (m_Socket.is_open ()) { boost::system::error_code ec; m_Socket.shutdown (boost::asio::ip::tcp::socket::shutdown_both, ec); m_Socket.close (); } m_Owner.RemoveSocket(shared_from_this()); } 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"; } void SAMSocket::HandleHandshakeReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (ecode) { LogPrint (eLogError, "SAM: Handshake read error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate ("SAM: handshake read error"); } else { m_Buffer[bytes_transferred] = 0; char * eol = (char *)memchr (m_Buffer, '\n', bytes_transferred); if (eol) *eol = 0; LogPrint (eLogDebug, "SAM: Handshake ", m_Buffer); char * separator = strchr (m_Buffer, ' '); if (separator) { separator = strchr (separator + 1, ' '); if (separator) *separator = 0; } if (!strcmp (m_Buffer, SAM_HANDSHAKE)) { std::string maxver("3.1"); std::string minver("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; } // version negotiation std::string version; if (SAMVersionAcceptable(maxver)) { version = maxver; } else if (SAMVersionAcceptable(minver)) { version = minver; } else if (SAMVersionTooLow(minver) && SAMVersionTooHigh(maxver)) { version = "3.0"; } if (SAMVersionAcceptable(version)) { #ifdef _MSC_VER size_t l = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_HANDSHAKE_REPLY, version.c_str ()); #else size_t l = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_HANDSHAKE_REPLY, version.c_str ()); #endif boost::asio::async_write (m_Socket, boost::asio::buffer (m_Buffer, l), boost::asio::transfer_all (), std::bind(&SAMSocket::HandleHandshakeReplySent, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } else SendMessageReply (SAM_HANDSHAKE_NOVERSION, strlen (SAM_HANDSHAKE_NOVERSION), true); } else { 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 ()); if (ecode != boost::asio::error::operation_aborted) Terminate ("SAM: handshake reply send error"); } else { 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)); } } void SAMSocket::SendMessageReply (const char * msg, size_t len, bool close) { LogPrint (eLogDebug, "SAMSocket::SendMessageReply, close=",close?"true":"false", " reason: ", msg); if (!m_IsSilent || m_SocketType == eSAMSocketTypeForward) boost::asio::async_write (m_Socket, boost::asio::buffer (msg, len), boost::asio::transfer_all (), std::bind(&SAMSocket::HandleMessageReplySent, shared_from_this (), std::placeholders::_1, std::placeholders::_2, close)); else { if (close) Terminate ("SAMSocket::SendMessageReply(close=true)"); else Receive (); } } 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 ()); if (ecode != boost::asio::error::operation_aborted) Terminate ("SAM: reply send error"); } else { if (close) Terminate ("SAMSocket::HandleMessageReplySent(close=true)"); else Receive (); } } void SAMSocket::HandleMessage (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (ecode) { LogPrint (eLogError, "SAM: Read error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate ("SAM: read error"); } else if (m_SocketType == eSAMSocketTypeStream) HandleReceived (ecode, bytes_transferred); else { bytes_transferred += m_BufferOffset; m_BufferOffset = 0; m_Buffer[bytes_transferred] = 0; 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) { separator = strchr (separator + 1, ' '); if (separator) *separator = 0; else separator = eol; if (!strcmp (m_Buffer, SAM_SESSION_CREATE)) ProcessSessionCreate (separator + 1, bytes_transferred - (separator - m_Buffer) - 1); else if (!strcmp (m_Buffer, SAM_STREAM_CONNECT)) ProcessStreamConnect (separator + 1, bytes_transferred - (separator - m_Buffer) - 1, bytes_transferred - (eol - m_Buffer) - 1); else if (!strcmp (m_Buffer, SAM_STREAM_ACCEPT)) ProcessStreamAccept (separator + 1, 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)) { size_t len = bytes_transferred - (separator - m_Buffer) - 1; size_t processed = ProcessDatagramSend (separator + 1, len, eol + 1); if (processed < len) { m_BufferOffset = len - processed; if (processed > 0) memmove (m_Buffer, separator + 1 + processed, m_BufferOffset); else { // restore string back *separator = ' '; *eol = '\n'; } } // since it's SAM v1 reply is not expected Receive (); } else { LogPrint (eLogError, "SAM: Unexpected message ", m_Buffer); Terminate ("SAM: unexpected message"); } } else { LogPrint (eLogError, "SAM: Malformed message ", m_Buffer); Terminate ("malformed message"); } } else { LogPrint (eLogWarning, "SAM: Incomplete message ", bytes_transferred); m_BufferOffset = bytes_transferred; // try to receive remaining message Receive (); } } } 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); 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)) { // session exists SendMessageReply (SAM_SESSION_CREATE_DUPLICATED_ID, strlen(SAM_SESSION_CREATE_DUPLICATED_ID), true); return; } SAMSessionType type = eSAMSessionTypeUnknown; if (style == SAM_VALUE_STREAM) type = eSAMSessionTypeStream; else if (style == SAM_VALUE_DATAGRAM) type = eSAMSessionTypeDatagram; else if (style == SAM_VALUE_RAW) type = eSAMSessionTypeRaw; else if (style == SAM_VALUE_MASTER) type = eSAMSessionTypeMaster; if (type == eSAMSessionTypeUnknown) { // 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()) { // 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); if (e) { // not an ip address SendSessionI2PError("Invalid IP Address in HOST"); return; } auto port = std::stoi(params[SAM_PARAM_PORT]); if (port == -1) { SendSessionI2PError("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); if (session) { m_SocketType = eSAMSocketTypeSession; if (type == eSAMSessionTypeDatagram || type == eSAMSessionTypeRaw) { session->UDPEndpoint = forward; auto dest = session->GetLocalDestination ()->CreateDatagramDestination (); auto port = std::stoi(params[SAM_PARAM_PORT]); 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 ); } if (session->GetLocalDestination ()->IsReady ()) SendSessionCreateReplyOk (); else { m_Timer.expires_from_now (boost::posix_time::seconds(SAM_SESSION_READINESS_CHECK_INTERVAL)); m_Timer.async_wait (std::bind (&SAMSocket::HandleSessionReadinessCheckTimer, shared_from_this (), std::placeholders::_1)); } } else SendMessageReply (SAM_SESSION_CREATE_DUPLICATED_DEST, strlen(SAM_SESSION_CREATE_DUPLICATED_DEST), true); } void SAMSocket::HandleSessionReadinessCheckTimer (const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) { if (m_Socket.is_open ()) { auto session = m_Owner.FindSession(m_ID); if(session) { if (session->GetLocalDestination ()->IsReady ()) SendSessionCreateReplyOk (); else { m_Timer.expires_from_now (boost::posix_time::seconds(SAM_SESSION_READINESS_CHECK_INTERVAL)); m_Timer.async_wait (std::bind (&SAMSocket::HandleSessionReadinessCheckTimer, shared_from_this (), std::placeholders::_1)); } } } else Terminate ("SAM: session socket closed"); } } void SAMSocket::SendSessionCreateReplyOk () { auto session = m_Owner.FindSession(m_ID); if (session) { uint8_t buf[1024]; char priv[1024]; size_t l = session->GetLocalDestination ()->GetPrivateKeys ().ToBuffer (buf, 1024); size_t l1 = i2p::data::ByteStreamToBase64 (buf, l, priv, 1024); priv[l1] = 0; #ifdef _MSC_VER 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); #endif SendMessageReply (m_Buffer, l2, false); } } void SAMSocket::ProcessStreamConnect (char * buf, size_t len, size_t rem) { LogPrint (eLogDebug, "SAM: Stream connect: ", buf); if ( m_SocketType != eSAMSocketTypeUnknown) { SendSessionI2PError ("Socket already in use"); return; } std::map params; ExtractParams (buf, params); std::string& id = params[SAM_PARAM_ID]; std::string& destination = params[SAM_PARAM_DESTINATION]; std::string& silent = params[SAM_PARAM_SILENT]; if (silent == SAM_VALUE_TRUE) m_IsSilent = true; m_ID = id; auto session = m_Owner.FindSession (id); if (session) { if (rem > 0) // handle follow on data { memmove (m_Buffer, buf + len + 1, rem); // buf is a pointer to m_Buffer's content m_BufferOffset = rem; } 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) { 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, std::bind(&SAMSocket::HandleConnectLeaseSetRequestComplete, shared_from_this(), std::placeholders::_1)); } else SendMessageReply (SAM_STREAM_STATUS_INVALID_KEY, strlen(SAM_STREAM_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) { if (!session) session = m_Owner.FindSession(m_ID); if (session) { if (session->GetLocalDestination ()->SupportsEncryptionType (remote->GetEncryptionType ())) { m_SocketType = eSAMSocketTypeStream; m_Stream = session->GetLocalDestination ()->CreateStream (remote); if (m_Stream) { m_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"); } else SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, strlen(SAM_STREAM_STATUS_INVALID_ID), true); } void SAMSocket::HandleConnectLeaseSetRequestComplete (std::shared_ptr leaseSet) { if (leaseSet) Connect (leaseSet); else { LogPrint (eLogError, "SAM: Destination to connect not found"); SendStreamCantReachPeer ("LeaseSet not found"); } } void SAMSocket::ProcessStreamAccept (char * buf, size_t len) { LogPrint (eLogDebug, "SAM: Stream accept: ", buf); if ( m_SocketType != eSAMSocketTypeUnknown) { SendSessionI2PError ("Socket already in use"); return; } std::map params; ExtractParams (buf, params); std::string& id = params[SAM_PARAM_ID]; std::string& silent = params[SAM_PARAM_SILENT]; if (silent == SAM_VALUE_TRUE) m_IsSilent = true; m_ID = id; auto session = m_Owner.FindSession (id); if (session) { m_SocketType = eSAMSocketTypeAcceptor; if (!session->GetLocalDestination ()->IsAcceptingStreams ()) { m_IsAccepting = true; SendMessageReply (SAM_STREAM_STATUS_OK, strlen(SAM_STREAM_STATUS_OK), false); session->GetLocalDestination ()->AcceptOnce (std::bind (&SAMSocket::HandleI2PAccept, shared_from_this (), std::placeholders::_1)); } else { auto ts = i2p::util::GetSecondsSinceEpoch (); while (!session->acceptQueue.empty () && session->acceptQueue.front ().second + SAM_SESSION_MAX_ACCEPT_INTERVAL > ts) { auto socket = session->acceptQueue.front ().first; session->acceptQueue.pop_front (); if (socket) boost::asio::post (m_Owner.GetService (), std::bind(&SAMSocket::TerminateClose, socket)); } if (session->acceptQueue.size () < SAM_SESSION_MAX_ACCEPT_QUEUE_SIZE) { // already accepting, queue up SendMessageReply (SAM_STREAM_STATUS_OK, strlen(SAM_STREAM_STATUS_OK), false); session->acceptQueue.push_back (std::make_pair(shared_from_this(), ts)); } else { LogPrint (eLogInfo, "SAM: Session ", m_ID, " accept queue is full ", session->acceptQueue.size ()); SendStreamI2PError ("Already accepting"); } } } else SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, strlen(SAM_STREAM_STATUS_INVALID_ID), true); } 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); std::map params; ExtractParams (buf, params); size_t size = std::stoi(params[SAM_PARAM_SIZE]), offset = data - buf; if (offset + size <= len) { auto session = m_Owner.FindSession(m_ID); if (session) { auto d = session->GetLocalDestination ()->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 ()); } else LogPrint (eLogError, "SAM: Missing datagram destination"); } else LogPrint (eLogError, "SAM: Session is not created from DATAGRAM SEND"); } else { LogPrint (eLogWarning, "SAM: Sent datagram size ", size, " exceeds buffer ", len - offset); return 0; // try to receive more } return offset + size; } void SAMSocket::ProcessDestGenerate (char * buf, size_t len) { LogPrint (eLogDebug, "SAM: Dest generate"); std::map params; ExtractParams (buf, params); // extract signature type i2p::data::SigningKeyType signatureType = i2p::data::SIGNING_KEY_TYPE_DSA_SHA1; i2p::data::CryptoKeyType cryptoType = i2p::data::CRYPTO_KEY_TYPE_ELGAMAL; 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); } 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); #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 ()); #endif SendMessageReply (m_Buffer, l, false); } void SAMSocket::ProcessNamingLookup (char * buf, size_t len) { LogPrint (eLogDebug, "SAM: Naming lookup: ", buf); std::map params; ExtractParams (buf, params); std::string& name = params[SAM_PARAM_NAME]; std::shared_ptr identity; std::shared_ptr addr; auto session = m_Owner.FindSession(m_ID); auto dest = session == nullptr ? context.GetSharedLocalDestination() : session->GetLocalDestination (); 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))) { 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)); } else dest->RequestDestinationWithEncryptedLeaseSet (addr->blindedPublicKey, std::bind (&SAMSocket::HandleNamingLookupLeaseSetRequestComplete, shared_from_this (), std::placeholders::_1, name)); } else { 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 size_t len = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY_INVALID_KEY, name.c_str()); #endif SendMessageReply (m_Buffer, len, false); } } 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) { #ifdef _MSC_VER size_t len = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, reply, msg.c_str()); #else size_t len = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, reply, msg.c_str()); #endif SendMessageReply (m_Buffer, len, true); } void SAMSocket::SendSessionI2PError(const std::string & msg) { LogPrint (eLogError, "SAM: Session I2P error: ", msg); SendReplyWithMessage (SAM_SESSION_STATUS_I2P_ERROR, msg); } void SAMSocket::SendStreamI2PError(const std::string & msg) { LogPrint (eLogError, "SAM: Stream I2P error: ", msg); SendReplyWithMessage (SAM_STREAM_STATUS_I2P_ERROR, msg); } void SAMSocket::SendStreamCantReachPeer(const std::string & msg) { SendReplyWithMessage (SAM_STREAM_STATUS_CANT_REACH_PEER, msg); } void SAMSocket::HandleNamingLookupLeaseSetRequestComplete (std::shared_ptr leaseSet, std::string name) { if (leaseSet) { context.GetAddressBook ().InsertFullAddress (leaseSet->GetIdentity ()); SendNamingLookupReply (name, leaseSet->GetIdentity ()); } else { LogPrint (eLogError, "SAM: Naming lookup failed. LeaseSet for ", name, " not found"); #ifdef _MSC_VER size_t len = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY_INVALID_KEY, name.c_str()); #else size_t len = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY_INVALID_KEY, name.c_str()); #endif SendMessageReply (m_Buffer, len, false); } } void SAMSocket::SendNamingLookupReply (const std::string& name, 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 ()); #else size_t l = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY, name.c_str (), base64.c_str ()); #endif SendMessageReply (m_Buffer, l, false); } void SAMSocket::ExtractParams (char * buf, std::map& params) { char * separator; do { separator = strchr (buf, ' '); if (separator) *separator = 0; char * value = strchr (buf, '='); if (value) { *value = 0; value++; params[buf] = value; } buf = separator + 1; } while (separator); } void SAMSocket::Receive () { m_Socket.async_read_some (boost::asio::buffer(m_Buffer + m_BufferOffset, SAM_SOCKET_BUFFER_SIZE - m_BufferOffset), std::bind((m_SocketType == eSAMSocketTypeStream) ? &SAMSocket::HandleReceived : &SAMSocket::HandleMessage, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } void SAMSocket::HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (ecode) { 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; m_Stream->AsyncSend ((uint8_t *)m_Buffer, bytes_transferred, std::bind(&SAMSocket::HandleStreamSend, shared_from_this(), std::placeholders::_1)); } else { Terminate("No Stream Remaining"); } } } void SAMSocket::I2PReceive () { if (m_Stream) { if (m_Stream->GetStatus () == i2p::stream::eStreamStatusNew || m_Stream->GetStatus () == i2p::stream::eStreamStatusOpen) // regular { m_Stream->AsyncReceive (boost::asio::buffer (m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE), std::bind (&SAMSocket::HandleI2PReceive, shared_from_this(), std::placeholders::_1, std::placeholders::_2), SAM_SOCKET_CONNECTION_MAX_IDLE); } else // closed by peer { uint8_t * buff = new uint8_t[SAM_SOCKET_BUFFER_SIZE]; // get remaining 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 } 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)); } void SAMSocket::HandleI2PReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (ecode) { LogPrint (eLogError, "SAM: Stream read error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) { if (bytes_transferred > 0) { WriteI2PData(bytes_transferred); } else { auto s = shared_from_this (); boost::asio::post (m_Owner.GetService (), [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)"); }); } } else { if (m_SocketType != eSAMSocketTypeTerminated) { if (bytes_transferred > 0) { WriteI2PData(bytes_transferred); } else I2PReceive(); } } } void SAMSocket::HandleWriteI2PData (const boost::system::error_code& ecode, size_t bytes_transferred) { if (ecode) { LogPrint (eLogError, "SAM: Socket write error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate ("socket write error at HandleWriteI2PData"); } else { I2PReceive (); } } void SAMSocket::HandleI2PAccept (std::shared_ptr stream) { if (stream) { LogPrint (eLogDebug, "SAM: Incoming I2P connection for session ", m_ID); m_SocketType = eSAMSocketTypeStream; m_IsAccepting = false; m_Stream = stream; context.GetAddressBook ().InsertFullAddress (stream->GetRemoteIdentity ()); auto session = m_Owner.FindSession (m_ID); if (session && !session->acceptQueue.empty ()) { // 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) { socket->m_IsAccepting = true; session->GetLocalDestination ()->AcceptOnce (std::bind (&SAMSocket::HandleI2PAccept, socket, std::placeholders::_1)); } } } if (!m_IsSilent) { // get remote peer address auto ident_ptr = stream->GetRemoteIdentity(); const size_t ident_len = ident_ptr->GetFullLen(); uint8_t* ident = new uint8_t[ident_len]; // send remote peer address as base64 const size_t l = ident_ptr->ToBuffer (ident, ident_len); const size_t l1 = i2p::data::ByteStreamToBase64 (ident, l, (char *)m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE); delete[] ident; m_StreamBuffer[l1] = '\n'; HandleI2PReceive (boost::system::error_code (), l1 +1); // we send identity like it has been received from stream } else I2PReceive (); } else 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); auto base64 = from.ToBase64 (); auto session = m_Owner.FindSession(m_ID); if(session) { auto ep = session->UDPEndpoint; 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); } else { #ifdef _MSC_VER size_t l = sprintf_s ((char *)m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE, SAM_DATAGRAM_RECEIVED, base64.c_str (), (long unsigned int)len); #else 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_SOCKET_BUFFER_SIZE - l) { memcpy (m_StreamBuffer + l, buf, len); WriteI2PData(len + l); } else LogPrint (eLogWarning, "SAM: Received datagram size ", len," exceeds buffer"); } } } void SAMSocket::HandleI2PRawDatagramReceive (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) { LogPrint (eLogDebug, "SAM: Raw datagram received ", len); auto session = m_Owner.FindSession(m_ID); if(session) { auto ep = session->UDPEndpoint; if (ep) // udp forward enabled m_Owner.SendTo({ {buf, len} }, *ep); else { #ifdef _MSC_VER size_t l = sprintf_s ((char *)m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE, SAM_RAW_RECEIVED, (long unsigned int)len); #else size_t l = snprintf ((char *)m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE, SAM_RAW_RECEIVED, (long unsigned int)len); #endif if (len < SAM_SOCKET_BUFFER_SIZE - l) { memcpy (m_StreamBuffer + l, buf, len); WriteI2PData(len + l); } else LogPrint (eLogWarning, "SAM: Received raw datagram size ", len," exceeds buffer"); } } } 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) { } void SAMSession::CloseStreams () { for(const auto & itr : m_Bridge.ListSockets(Name)) { itr->Terminate(nullptr); } } 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 () { if (IsRunning ()) Stop (); } void SAMBridge::Start () { Accept (); ReceiveDatagram (); StartIOService (); } void SAMBridge::Stop () { try { m_Acceptor.cancel (); } catch (const std::exception& ex) { LogPrint (eLogError, "SAM: Runtime exception: ", ex.what ()); } decltype(m_Sessions) sessions; { std::unique_lock l(m_SessionsMutex); m_Sessions.swap (sessions); } for (auto& it: sessions) it.second->Close (); StopIOService (); } void SAMBridge::Accept () { auto newSocket = std::make_shared(*this); m_Acceptor.async_accept (newSocket->GetSocket(), 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) { boost::system::error_code ec; auto ep = socket->GetSocket ().remote_endpoint (ec); if (!ec) { LogPrint (eLogDebug, "SAM: New connection from ", ep); AddSocket (socket); socket->ReceiveHandshake (); } else LogPrint (eLogError, "SAM: Incoming connection error: ", ec.message ()); } else 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 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); } else // transient { // extract signature type i2p::data::SigningKeyType signatureType = i2p::data::SIGNING_KEY_TYPE_DSA_SHA1; i2p::data::CryptoKeyType cryptoType = i2p::data::CRYPTO_KEY_TYPE_ELGAMAL; if (params) { auto it = params->find (SAM_PARAM_SIGNATURE_TYPE); if (it != params->end ()) { if (!ResolveSignatureType (it->second, signatureType)) LogPrint (eLogWarning, "SAM: ", SAM_PARAM_SIGNATURE_TYPE, " is invalid ", it->second); } it = params->find (SAM_PARAM_CRYPTO_TYPE); if (it != params->end ()) { try { cryptoType = std::stoi(it->second); } catch (const std::exception& ex) { LogPrint (eLogWarning, "SAM: ", SAM_PARAM_CRYPTO_TYPE, "error: ", ex.what ()); } } } localDestination = m_IsSingleThread ? i2p::client::context.CreateNewLocalDestination (GetIOService (), true, signatureType, cryptoType, params) : i2p::client::context.CreateNewLocalDestination (true, signatureType, cryptoType, params); } if (localDestination) { localDestination->Acquire (); auto session = (type == eSAMSessionTypeMaster) ? std::make_shared(*this, id, localDestination) : std::make_shared(*this, id, type, localDestination); std::unique_lock l(m_SessionsMutex); auto ret = m_Sessions.insert (std::make_pair(id, session)); if (!ret.second) LogPrint (eLogWarning, "SAM: Session ", id, " already exists"); return ret.first->second; } 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; { std::unique_lock l(m_SessionsMutex); auto it = m_Sessions.find (id); if (it != m_Sessions.end ()) { session = it->second; m_Sessions.erase (it); } } if (session) { session->StopLocalDestination (); session->Close (); if (m_IsSingleThread) { auto timer = std::make_shared(GetService ()); timer->expires_from_now (boost::posix_time::seconds(5)); // postpone destination clean for 5 seconds timer->async_wait ([timer, session](const boost::system::error_code& ecode) { // session's destructor is called here }); } } } std::shared_ptr SAMBridge::FindSession (const std::string& id) const { std::unique_lock l(m_SessionsMutex); auto it = m_Sessions.find (id); if (it != m_Sessions.end ()) return it->second; return nullptr; } std::list > SAMBridge::ListSockets(const std::string & id) const { std::list > list; { std::unique_lock l(m_OpenSocketsMutex); for (const auto & itr : m_OpenSockets) if (itr->IsSession(id)) list.push_back(itr); } 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 () { m_DatagramSocket.async_receive_from ( boost::asio::buffer (m_DatagramReceiveBuffer, i2p::datagram::MAX_DATAGRAM_SIZE), m_SenderEndpoint, std::bind (&SAMBridge::HandleReceivedDatagram, this, std::placeholders::_1, std::placeholders::_2)); } void SAMBridge::HandleReceivedDatagram (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (!ecode) { 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) { sessionID++; char * destination = strchr (sessionID, ' '); if (destination) { *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"); } else LogPrint (eLogError, "SAM: Missing destination key"); } else LogPrint (eLogError, "SAM: Missing sessionID"); } else LogPrint(eLogError, "SAM: Invalid datagram"); 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; } } } i2pd-2.56.0/libi2pd_client/SAM.h000066400000000000000000000272221475272067700161440ustar00rootroot00000000000000/* * 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 SAM_H__ #define SAM_H__ #include #include #include #include #include #include #include #include #include #include "util.h" #include "Identity.h" #include "LeaseSet.h" #include "Streaming.h" #include "Destination.h" namespace i2p { namespace client { const size_t SAM_SOCKET_BUFFER_SIZE = 8192; const int SAM_SOCKET_CONNECTION_MAX_IDLE = 3600; // in seconds const int SAM_SESSION_READINESS_CHECK_INTERVAL = 3; // in seconds const size_t SAM_SESSION_MAX_ACCEPT_QUEUE_SIZE = 50; const size_t SAM_SESSION_MAX_ACCEPT_INTERVAL = 3; // in seconds const 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_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_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_DATAGRAM_RECEIVED[] = "DATAGRAM RECEIVED DESTINATION=%s SIZE=%lu\n"; const char SAM_RAW_RECEIVED[] = "RAW RECEIVED SIZE=%lu\n"; const char SAM_NAMING_REPLY_INVALID_KEY[] = "NAMING REPLY RESULT=INVALID_KEY NAME=%s\n"; const char SAM_NAMING_REPLY_KEY_NOT_FOUND[] = "NAMING REPLY RESULT=KEY_NOT_FOUND NAME=%s\n"; const char SAM_PARAM_MIN[] = "MIN"; const char SAM_PARAM_MAX[] = "MAX"; const char SAM_PARAM_STYLE[] = "STYLE"; const char SAM_PARAM_ID[] = "ID"; const char SAM_PARAM_SILENT[] = "SILENT"; const char SAM_PARAM_DESTINATION[] = "DESTINATION"; const char SAM_PARAM_NAME[] = "NAME"; 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"; enum SAMSocketType { eSAMSocketTypeUnknown, eSAMSocketTypeSession, eSAMSocketTypeStream, eSAMSocketTypeAcceptor, eSAMSocketTypeForward, eSAMSocketTypeTerminated }; class SAMBridge; struct SAMSession; class SAMSocket: public std::enable_shared_from_this { public: typedef boost::asio::ip::tcp::socket Socket_t; SAMSocket (SAMBridge& owner); ~SAMSocket (); Socket_t& 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); } void HandleHandshakeReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred); void HandleHandshakeReplySent (const boost::system::error_code& ecode, std::size_t bytes_transferred); void HandleMessage (const boost::system::error_code& ecode, std::size_t bytes_transferred); void SendMessageReply (const char * msg, size_t len, bool close); void HandleMessageReplySent (const boost::system::error_code& ecode, std::size_t bytes_transferred, bool close); void Receive (); void HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred); 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); 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 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 HandleSessionReadinessCheckTimer (const boost::system::error_code& ecode); void SendSessionCreateReplyOk (); void WriteI2PData(size_t sz); 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; boost::asio::deadline_timer m_Timer; char m_Buffer[SAM_SOCKET_BUFFER_SIZE + 1]; size_t m_BufferOffset; uint8_t m_StreamBuffer[SAM_SOCKET_BUFFER_SIZE]; SAMSocketType m_SocketType; std::string m_ID; // nickname bool m_IsSilent; bool m_IsAccepting; // for eSAMSocketTypeAcceptor 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 () {}; virtual std::shared_ptr GetLocalDestination () = 0; virtual void StopLocalDestination () = 0; virtual void Close () { CloseStreams (); }; 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 { public: SAMBridge (const std::string& address, uint16_t portTCP, uint16_t portUDP, bool singleThread); ~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 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; private: 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); private: bool m_IsSingleThread; 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: // for HTTP const decltype(m_Sessions)& GetSessions () const { return m_Sessions; }; }; } } #endif i2pd-2.56.0/libi2pd_client/SOCKS.cpp000066400000000000000000000623211475272067700167400ustar00rootroot00000000000000/* * 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 "SOCKS.h" #include "Identity.h" #include "Streaming.h" #include "Destination.h" #include "ClientContext.h" #include "I2PEndian.h" #include "I2PTunnel.h" #include "I2PService.h" #include "util.h" #include "Socks5.h" namespace i2p { 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 struct SOCKSDnsAddress { uint8_t size; char value[max_socks_hostname_size]; void FromString (const std::string& str) { size = str.length(); if (str.length() > max_socks_hostname_size) size = max_socks_hostname_size; memcpy(value,str.c_str(),size); } std::string ToString() { return std::string(value, size); } void push_back (char c) { value[size++] = c; } }; class SOCKSServer; class SOCKSHandler: public i2p::client::I2PServiceHandler, public std::enable_shared_from_this { private: enum state { GET_SOCKSV, GET_COMMAND, GET_PORT, GET_IPV4, GET4_IDENT, GET4A_HOST, GET5_AUTHNUM, GET5_AUTH, GET5_REQUESTV, GET5_GETRSV, GET5_GETADDRTYPE, GET5_IPV6, GET5_HOST_SIZE, GET5_HOST, GET5_USERPASSWD, GET5_USER_SIZE, GET5_USER, GET5_PASSWD_SIZE, GET5_PASSWD, READY, UPSTREAM_RESOLVE, UPSTREAM_CONNECT, UPSTREAM_HANDSHAKE }; enum authMethods { AUTH_NONE = 0, //No authentication, skip to next step AUTH_GSSAPI = 1, //GSSAPI authentication AUTH_USERPASSWD = 2, //Username and password AUTH_UNACCEPTABLE = 0xff //No acceptable method found }; enum addrTypes { ADDR_IPV4 = 1, //IPv4 address (4 octets) ADDR_DNS = 3, // DNS name (up to 255 octets) ADDR_IPV6 = 4 //IPV6 address (16 octets) }; enum errTypes { SOCKS5_OK = 0, // No error for SOCKS5 SOCKS5_GEN_FAIL = 1, // General server failure SOCKS5_RULE_DENIED = 2, // Connection disallowed by ruleset SOCKS5_NET_UNREACH = 3, // Network unreachable 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 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 SOCKS4_IDENTD_DIFFER = 93 // The ID reported by the application and by identd differ }; enum cmdTypes { CMD_CONNECT = 1, // TCP Connect CMD_BIND = 2, // TCP Bind CMD_UDP = 3 // UDP associate }; enum socksVersions { SOCKS4 = 4, // SOCKS4 SOCKS5 = 5 // SOCKS5 }; union address { uint32_t ip; SOCKSDnsAddress dns; uint8_t ipv6[16]; }; void EnterState(state nstate, uint8_t parseleft = 1); bool HandleData(uint8_t *sock_buff, std::size_t len); bool ValidateSOCKSRequest(); 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); bool Socks5ChooseAuth(); void Socks5UserPasswdResponse (); void SocksRequestFailed(errTypes error); void SocksRequestSuccess(); void SentSocksFailed(const boost::system::error_code & ecode); void SentSocksDone(const boost::system::error_code & ecode); void SentSocksResponse(const boost::system::error_code & ecode); 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); boost::asio::ip::tcp::resolver m_proxy_resolver; uint8_t m_sock_buff[socks_buffer_size]; std::shared_ptr m_sock, m_upstreamSock; #if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS) std::shared_ptr m_upstreamLocalSock; #endif std::shared_ptr m_stream; uint8_t *m_remaining_data; //Data left to be sent uint8_t *m_remaining_upstream_data; //upstream data left to be forwarded uint8_t m_response[7+max_socks_hostname_size]; 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 uint16_t m_port; uint8_t m_command; uint8_t m_parseleft; //Octets left to parse authMethods m_authchosen; //Authentication chosen addrTypes m_addrtype; //Address type chosen socksVersions m_socksv; //Socks version 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; public: 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), m_authchosen(AUTH_UNACCEPTABLE), m_addrtype(ADDR_IPV4), m_UseUpstreamProxy(useUpstream), m_UpstreamProxyAddress(upstreamAddr), m_UpstreamProxyPort(upstreamPort) { m_address.ip = 0; EnterState(GET_SOCKSV); } ~SOCKSHandler() { Terminate(); } void Handle() { AsyncSockRead(); } }; void SOCKSHandler::AsyncSockRead() { 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)); } else { LogPrint(eLogError,"SOCKS: No socket for read"); } } void SOCKSHandler::Terminate() { if (Kill()) return; if (m_sock) { LogPrint(eLogDebug, "SOCKS: Closing socket"); m_sock->close(); m_sock = nullptr; } if (m_upstreamSock) { 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"); m_stream.reset (); } Done(shared_from_this()); } boost::asio::const_buffer SOCKSHandler::GenerateSOCKS4Response(SOCKSHandler::errTypes error, uint32_t ip, uint16_t port) { assert(error >= SOCKS4_OK); m_response[0] = '\x00'; // version m_response[1] = error; // response code htobe16buf(m_response + 2, port); // port htobe32buf(m_response + 4, ip); // IP return boost::asio::const_buffer (m_response,8); } boost::asio::const_buffer SOCKSHandler::GenerateSOCKS5Response(SOCKSHandler::errTypes error, SOCKSHandler::addrTypes type, const SOCKSHandler::address &addr, uint16_t port) { size_t size = 6; // header + port assert(error <= SOCKS5_ADDR_UNSUP); m_response[0] = '\x05'; // version m_response[1] = error; // response code m_response[2] = '\x00'; // reserved 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); break; case ADDR_IPV6: size += 16; memcpy(m_response + 4, addr.ipv6, 16); htobe16buf(m_response + size - 2, port); 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); } break; } return boost::asio::const_buffer (m_response, size); } bool SOCKSHandler::Socks5ChooseAuth() { m_response[0] = '\x05'; // Version m_response[1] = m_authchosen; // Response code boost::asio::const_buffer 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)); 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)); 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); 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 response = GenerateSOCKS4Response(error, m_4aip, m_port); break; case SOCKS5: LogPrint(eLogWarning, "SOCKS: v5 request failed: ", error); response = GenerateSOCKS5Response(error, m_addrtype, m_address, m_port); break; } boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksFailed, 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 switch (m_socksv) { case SOCKS4: LogPrint(eLogInfo, "SOCKS: v4 connection success"); response = GenerateSOCKS4Response(SOCKS4_OK, m_4aip, m_port); break; case SOCKS5: 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 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)); } void SOCKSHandler::EnterState(SOCKSHandler::state nstate, uint8_t parseleft) { switch (nstate) { case GET_PORT: parseleft = 2; break; case GET_IPV4: m_addrtype = ADDR_IPV4; m_address.ip = 0; parseleft = 4; break; case GET4_IDENT: m_4aip = m_address.ip; break; case GET4A_HOST: case GET5_HOST: m_addrtype = ADDR_DNS; m_address.dns.size = 0; break; case GET5_IPV6: m_addrtype = ADDR_IPV6; parseleft = 16; break; default:; } m_parseleft = parseleft; m_state = nstate; } bool SOCKSHandler::ValidateSOCKSRequest() { if ( m_cmd != CMD_CONNECT ) { // 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! if ( m_addrtype != ADDR_DNS ) { switch (m_socksv) { case SOCKS5: LogPrint(eLogError, "SOCKS: v5 unsupported address type: ", m_addrtype); break; case SOCKS4: LogPrint(eLogError, "SOCKS: Request with v4a rejected because it's actually SOCKS4"); break; } SocksRequestFailed(SOCKS5_ADDR_UNSUP); return false; } return true; } bool SOCKSHandler::HandleData(uint8_t *sock_buff, std::size_t len) { assert(len); // This should always be called with a least a byte left to parse while (len > 0) { switch (m_state) { case GET_SOCKSV: m_socksv = (SOCKSHandler::socksVersions) *sock_buff; switch (*sock_buff) { case SOCKS4: EnterState(GET_COMMAND); //Initialize the parser at the right position break; case SOCKS5: EnterState(GET5_AUTHNUM); //Initialize the parser at the right position break; default: LogPrint(eLogError, "SOCKS: Rejected invalid version: ", ((int)*sock_buff)); Terminate(); return false; } break; case GET5_AUTHNUM: EnterState(GET5_AUTH, *sock_buff); break; case GET5_AUTH: 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); } break; case GET_COMMAND: switch (*sock_buff) { case CMD_CONNECT: case CMD_BIND: break; case CMD_UDP: if (m_socksv == SOCKS5) break; [[fallthrough]]; default: LogPrint(eLogError, "SOCKS: Invalid command: ", ((int)*sock_buff)); SocksRequestFailed(SOCKS5_GEN_FAIL); return false; } m_cmd = (SOCKSHandler::cmdTypes)*sock_buff; switch (m_socksv) { case SOCKS5: EnterState(GET5_GETRSV); break; case SOCKS4: EnterState(GET_PORT); break; } break; case GET_PORT: m_port = (m_port << 8)|((uint16_t)*sock_buff); m_parseleft--; if (m_parseleft == 0) { switch (m_socksv) { case SOCKS5: EnterState(READY); break; case SOCKS4: EnterState(GET_IPV4); break; } } break; case GET_IPV4: m_address.ip = (m_address.ip << 8)|((uint32_t)*sock_buff); m_parseleft--; if (m_parseleft == 0) { switch (m_socksv) { case SOCKS5: EnterState(GET_PORT); break; case SOCKS4: EnterState(GET4_IDENT); m_4aip = m_address.ip; break; } } break; case GET4_IDENT: if (!*sock_buff) { if( m_4aip == 0 || m_4aip > 255 ) EnterState(READY); else EnterState(GET4A_HOST); } break; case GET4A_HOST: if (!*sock_buff) { EnterState(READY); break; } if (m_address.dns.size >= max_socks_hostname_size) { LogPrint(eLogError, "SOCKS: v4a req failed: destination is too large"); SocksRequestFailed(SOCKS4_FAIL); return false; } m_address.dns.push_back(*sock_buff); break; case GET5_REQUESTV: if (*sock_buff != SOCKS5) { LogPrint(eLogError,"SOCKS: v5 rejected unknown request version: ", ((int)*sock_buff)); SocksRequestFailed(SOCKS5_GEN_FAIL); return false; } EnterState(GET_COMMAND); break; case GET5_GETRSV: if ( *sock_buff != 0 ) { LogPrint(eLogError, "SOCKS: v5 unknown reserved field: ", ((int)*sock_buff)); SocksRequestFailed(SOCKS5_GEN_FAIL); return false; } EnterState(GET5_GETADDRTYPE); break; case GET5_GETADDRTYPE: switch (*sock_buff) { case ADDR_IPV4: EnterState(GET_IPV4); break; case ADDR_IPV6: EnterState(GET5_IPV6); break; case ADDR_DNS : EnterState(GET5_HOST_SIZE); break; default: LogPrint(eLogError, "SOCKS: v5 unknown address type: ", ((int)*sock_buff)); SocksRequestFailed(SOCKS5_GEN_FAIL); return false; } break; case GET5_IPV6: m_address.ipv6[16-m_parseleft] = *sock_buff; m_parseleft--; if (m_parseleft == 0) EnterState(GET_PORT); break; case GET5_HOST_SIZE: EnterState(GET5_HOST, *sock_buff); break; case GET5_HOST: m_address.dns.push_back(*sock_buff); m_parseleft--; if (m_parseleft == 0) EnterState(GET_PORT); break; case GET5_USERPASSWD: if (*sock_buff != 1) { LogPrint(eLogError,"SOCKS: v5 rejected invalid username/password subnegotiation: ", ((int)*sock_buff)); SocksRequestFailed(SOCKS5_GEN_FAIL); return false; } EnterState(GET5_USER_SIZE); break; case GET5_USER_SIZE: if (*sock_buff) EnterState(GET5_USER, *sock_buff); else // empty user EnterState(GET5_PASSWD_SIZE); break; case GET5_USER: // skip user for now m_parseleft--; if (m_parseleft == 0) EnterState(GET5_PASSWD_SIZE); break; case GET5_PASSWD_SIZE: if (*sock_buff) EnterState(GET5_PASSWD, *sock_buff); else // empty password { Socks5UserPasswdResponse (); EnterState(GET5_REQUESTV); } break; case GET5_PASSWD: // skip passwd for now m_parseleft--; if (m_parseleft == 0) { Socks5UserPasswdResponse (); EnterState(GET5_REQUESTV); } break; default: LogPrint(eLogError, "SOCKS: Parse state?? ", m_state); Terminate(); return false; } sock_buff++; len--; if (m_state == READY) { m_remaining_data_len = len; m_remaining_data = sock_buff; return ValidateSOCKSRequest(); } } return true; } void SOCKSHandler::HandleSockRecv(const boost::system::error_code & ecode, std::size_t len) { LogPrint(eLogDebug, "SOCKS: Received ", len, " bytes"); if(ecode) { LogPrint(eLogWarning, "SOCKS: Recv got error: ", ecode); Terminate(); return; } if (HandleData(m_sock_buff, len)) { if (m_state == READY) { const std::string addr = m_address.dns.ToString(); LogPrint(eLogInfo, "SOCKS: Requested ", addr, ":" , m_port); const size_t addrlen = addr.size(); // does it end with .i2p? if ( addr.rfind(".i2p") == addrlen - 4) { // yes it does, make an i2p session GetOwner()->CreateStream ( std::bind (&SOCKSHandler::HandleStreamRequestComplete, shared_from_this(), std::placeholders::_1), m_address.dns.ToString(), m_port); } else if (m_UseUpstreamProxy) { // forward it to upstream proxy ForwardSOCKS(); } else { // no upstream proxy SocksRequestFailed(SOCKS5_ADDR_UNSUP); } } else AsyncSockRead(); } } void SOCKSHandler::SentSocksFailed(const boost::system::error_code & ecode) { if (ecode) LogPrint (eLogError, "SOCKS: Closing socket after sending failure because: ", ecode.message ()); Terminate(); } void SOCKSHandler::SentSocksDone(const boost::system::error_code & ecode) { if (!ecode) { if (Kill()) return; 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); Done(shared_from_this()); } else { LogPrint (eLogError, "SOCKS: Closing socket after completion reply because: ", ecode.message ()); Terminate(); } } void SOCKSHandler::SentSocksResponse(const boost::system::error_code & ecode) { if (ecode) { LogPrint (eLogError, "SOCKS: Closing socket after sending reply because: ", ecode.message ()); Terminate(); } } void SOCKSHandler::HandleStreamRequestComplete (std::shared_ptr stream) { if (stream) { m_stream = stream; SocksRequestSuccess(); } else { 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); } } template void SOCKSHandler::SocksUpstreamSuccess(std::shared_ptr& upstreamSock) { LogPrint(eLogInfo, "SOCKS: Upstream success"); boost::asio::const_buffer response(nullptr, 0); switch (m_socksv) { case SOCKS4: LogPrint(eLogInfo, "SOCKS: v4 connection success"); response = GenerateSOCKS4Response(SOCKS4_OK, m_4aip, m_port); break; case SOCKS5: LogPrint(eLogInfo, "SOCKS: v5 connection success"); //HACK only 16 bits passed in port as SOCKS5 doesn't allow for more response = GenerateSOCKS5Response(SOCKS5_OK, ADDR_DNS, m_address, m_port); break; } m_sock->send(response); auto forwarder = CreateSocketsPipe (GetOwner(), m_sock, upstreamSock); upstreamSock = nullptr; m_sock = nullptr; GetOwner()->AddHandler(forwarder); forwarder->Start(); Terminate(); } template void SOCKSHandler::SendUpstreamRequest(std::shared_ptr& upstreamSock) { 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"); } void SOCKSHandler::HandleUpstreamConnected(const boost::system::error_code & ecode, const boost::asio::ip::tcp::endpoint& ep) { if (ecode) { LogPrint(eLogWarning, "SOCKS: Could not connect to upstream proxy: ", ecode.message()); SocksRequestFailed(SOCKS5_NET_UNREACH); return; } LogPrint(eLogInfo, "SOCKS: Connected to upstream proxy"); SendUpstreamRequest(m_upstreamSock); } void SOCKSHandler::HandleUpstreamResolved(const boost::system::error_code & ecode, boost::asio::ip::tcp::resolver::results_type endpoints) { if (ecode) { // error resolving LogPrint(eLogWarning, "SOCKS: Upstream proxy", m_UpstreamProxyAddress, " not resolved: ", ecode.message()); SocksRequestFailed(SOCKS5_NET_UNREACH); return; } 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, 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, 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) { m_UseUpstreamProxy = false; if (outAddress.length() > 0 && outEnable) SetUpstreamProxy(outAddress, outPort); } std::shared_ptr SOCKSServer::CreateHandler(std::shared_ptr socket) { return std::make_shared (this, socket, m_UpstreamProxyAddress, m_UpstreamProxyPort, m_UseUpstreamProxy); } void SOCKSServer::SetUpstreamProxy(const std::string & addr, const uint16_t port) { m_UpstreamProxyAddress = addr; m_UpstreamProxyPort = port; m_UseUpstreamProxy = true; } } } i2pd-2.56.0/libi2pd_client/SOCKS.h000066400000000000000000000022071475272067700164020ustar00rootroot00000000000000/* * 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__ #include #include #include #include #include "I2PService.h" namespace i2p { 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, 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 (); } private: std::string m_Name; std::string m_UpstreamProxyAddress; uint16_t m_UpstreamProxyPort; bool m_UseUpstreamProxy; }; typedef SOCKSServer SOCKSProxy; } } #endif i2pd-2.56.0/libi2pd_client/UDPTunnel.cpp000066400000000000000000000343641475272067700177020ustar00rootroot00000000000000/* * 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); } } } i2pd-2.56.0/libi2pd_client/UDPTunnel.h000066400000000000000000000143731475272067700173450ustar00rootroot00000000000000/* * 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 i2pd-2.56.0/libi2pd_wrapper/000077500000000000000000000000001475272067700155505ustar00rootroot00000000000000i2pd-2.56.0/libi2pd_wrapper/api.go000066400000000000000000000011301475272067700166430ustar00rootroot00000000000000package 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" i2pd-2.56.0/libi2pd_wrapper/api.swigcxx000066400000000000000000000001751475272067700177420ustar00rootroot00000000000000// See swig.org for more interface options, // e.g. map std::string to Go string %{ #include "capi.h" %} %include "capi.h" i2pd-2.56.0/libi2pd_wrapper/capi.cpp000066400000000000000000000014371475272067700171750ustar00rootroot00000000000000/* * 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 i2pd-2.56.0/libi2pd_wrapper/capi.h000066400000000000000000000012371475272067700166400ustar00rootroot00000000000000/* * 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 i2pd-2.56.0/tests/000077500000000000000000000000001475272067700136255ustar00rootroot00000000000000i2pd-2.56.0/tests/.gitignore000066400000000000000000000003131475272067700156120ustar00rootroot00000000000000/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 i2pd-2.56.0/tests/CMakeLists.txt000066400000000000000000000062631475272067700163740ustar00rootroot00000000000000enable_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) i2pd-2.56.0/tests/Makefile000066400000000000000000000042561475272067700152740ustar00rootroot00000000000000SYS := $(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 all: $(TESTS) run $(LIBI2PD): @echo "Building libi2pd.a ..." && cd .. && $(MAKE) libi2pd.a test-http-%: test-http-%.cpp $(LIBI2PD) $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS) test-base-%: test-base-%.cpp $(LIBI2PD) $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS) 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) run: $(TESTS) @for TEST in $(TESTS); do echo Running $$TEST; ./$$TEST ; done clean: rm -f $(TESTS) i2pd-2.56.0/tests/test-aeadchacha20poly1305.cpp000066400000000000000000000045511475272067700207140ustar00rootroot00000000000000#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); } i2pd-2.56.0/tests/test-aes.cpp000066400000000000000000000040151475272067700160560ustar00rootroot00000000000000#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); } i2pd-2.56.0/tests/test-base-64.cpp000066400000000000000000000026671475272067700164620ustar00rootroot00000000000000#include #include #include "Base.h" using namespace i2p::data; int main() { const char *in = "test"; size_t in_len = strlen(in); char out[16]; /* bytes -> b64 */ assert(ByteStreamToBase64(NULL, 0, NULL, 0) == 0); assert(ByteStreamToBase64(NULL, 0, out, sizeof(out)) == 0); assert(Base64EncodingBufferSize(2) == 4); assert(Base64EncodingBufferSize(4) == 8); assert(Base64EncodingBufferSize(6) == 8); assert(Base64EncodingBufferSize(7) == 12); assert(Base64EncodingBufferSize(9) == 12); assert(Base64EncodingBufferSize(10) == 16); assert(Base64EncodingBufferSize(12) == 16); assert(Base64EncodingBufferSize(13) == 20); assert(ByteStreamToBase64((uint8_t *) in, in_len, out, sizeof(out)) == 8); assert(memcmp(out, "dGVzdA==", 8) == 0); /* b64 -> bytes */ assert(Base64ToByteStream(NULL, 0, NULL, 0) == 0); assert(Base64ToByteStream(NULL, 0, (uint8_t *) out, sizeof(out)) == 0); in = "dGVzdA=="; /* valid b64 */ assert(Base64ToByteStream(in, strlen(in), (uint8_t *) out, sizeof(out)) == 4); assert(memcmp(out, "test", 4) == 0); in = "dGVzdA="; /* invalid b64 : not padded */ assert(Base64ToByteStream(in, strlen(in), (uint8_t *) out, sizeof(out)) == 0); in = "dG/z.A=="; /* invalid b64 : char not from alphabet */ // assert(Base64ToByteStream(in, strlen(in), (uint8_t *) out, sizeof(out)) == 0); // ^^^ fails, current implementation not checks acceptable symbols return 0; } i2pd-2.56.0/tests/test-blinding.cpp000066400000000000000000000026171475272067700171020ustar00rootroot00000000000000#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); } i2pd-2.56.0/tests/test-eddsa.cpp000066400000000000000000000061751475272067700163770ustar00rootroot00000000000000#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)); } i2pd-2.56.0/tests/test-elligator.cpp000066400000000000000000000055431475272067700172770ustar00rootroot00000000000000#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)); } i2pd-2.56.0/tests/test-gost-sig.cpp000066400000000000000000000024601475272067700170440ustar00rootroot00000000000000#include #include #include #include "Gost.h" #include "Signature.h" const uint8_t example2[72] = { 0xfb,0xe2,0xe5,0xf0,0xee,0xe3,0xc8,0x20,0xfb,0xea,0xfa,0xeb,0xef,0x20,0xff,0xfb, 0xf0,0xe1,0xe0,0xf0,0xf5,0x20,0xe0,0xed,0x20,0xe8,0xec,0xe0,0xeb,0xe5,0xf0,0xf2, 0xf1,0x20,0xff,0xf0,0xee,0xec,0x20,0xf1,0x20,0xfa,0xf2,0xfe,0xe5,0xe2,0x20,0x2c, 0xe8,0xf6,0xf3,0xed,0xe2,0x20,0xe8,0xe6,0xee,0xe1,0xe8,0xf0,0xf2,0xd1,0x20,0x2c, 0xe8,0xf0,0xf2,0xe5,0xe2,0x20,0xe5,0xd1 }; int main () { uint8_t priv[64], pub[128], signature[128]; 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); 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); assert (verifier1.Verify (example2, 72, signature)); } i2pd-2.56.0/tests/test-gost.cpp000066400000000000000000000046621475272067700162720ustar00rootroot00000000000000#include #include #include #include "Gost.h" const uint8_t example1[63] = { 0x32,0x31,0x30,0x39,0x38,0x37,0x36,0x35,0x34,0x33,0x32,0x31,0x30,0x39,0x38,0x37, 0x36,0x35,0x34,0x33,0x32,0x31,0x30,0x39,0x38,0x37,0x36,0x35,0x34,0x33,0x32,0x31, 0x30,0x39,0x38,0x37,0x36,0x35,0x34,0x33,0x32,0x31,0x30,0x39,0x38,0x37,0x36,0x35, 0x34,0x33,0x32,0x31,0x30,0x39,0x38,0x37,0x36,0x35,0x34,0x33,0x32,0x31,0x30 }; const uint8_t example2[72] = { 0xfb,0xe2,0xe5,0xf0,0xee,0xe3,0xc8,0x20,0xfb,0xea,0xfa,0xeb,0xef,0x20,0xff,0xfb, 0xf0,0xe1,0xe0,0xf0,0xf5,0x20,0xe0,0xed,0x20,0xe8,0xec,0xe0,0xeb,0xe5,0xf0,0xf2, 0xf1,0x20,0xff,0xf0,0xee,0xec,0x20,0xf1,0x20,0xfa,0xf2,0xfe,0xe5,0xe2,0x20,0x2c, 0xe8,0xf6,0xf3,0xed,0xe2,0x20,0xe8,0xe6,0xee,0xe1,0xe8,0xf0,0xf2,0xd1,0x20,0x2c, 0xe8,0xf0,0xf2,0xe5,0xe2,0x20,0xe5,0xd1 }; const uint8_t example1_hash_512[64] = { 0x48,0x6f,0x64,0xc1,0x91,0x78,0x79,0x41,0x7f,0xef,0x08,0x2b,0x33,0x81,0xa4,0xe2, 0x11,0xc3,0x24,0xf0,0x74,0x65,0x4c,0x38,0x82,0x3a,0x7b,0x76,0xf8,0x30,0xad,0x00, 0xfa,0x1f,0xba,0xe4,0x2b,0x12,0x85,0xc0,0x35,0x2f,0x22,0x75,0x24,0xbc,0x9a,0xb1, 0x62,0x54,0x28,0x8d,0xd6,0x86,0x3d,0xcc,0xd5,0xb9,0xf5,0x4a,0x1a,0xd0,0x54,0x1b }; const uint8_t example1_hash_256[32] = { 0x00,0x55,0x7b,0xe5,0xe5,0x84,0xfd,0x52,0xa4,0x49,0xb1,0x6b,0x02,0x51,0xd0,0x5d, 0x27,0xf9,0x4a,0xb7,0x6c,0xba,0xa6,0xda,0x89,0x0b,0x59,0xd8,0xef,0x1e,0x15,0x9d }; const uint8_t example2_hash_512[64] = { 0x28,0xfb,0xc9,0xba,0xda,0x03,0x3b,0x14,0x60,0x64,0x2b,0xdc,0xdd,0xb9,0x0c,0x3f, 0xb3,0xe5,0x6c,0x49,0x7c,0xcd,0x0f,0x62,0xb8,0xa2,0xad,0x49,0x35,0xe8,0x5f,0x03, 0x76,0x13,0x96,0x6d,0xe4,0xee,0x00,0x53,0x1a,0xe6,0x0f,0x3b,0x5a,0x47,0xf8,0xda, 0xe0,0x69,0x15,0xd5,0xf2,0xf1,0x94,0x99,0x6f,0xca,0xbf,0x26,0x22,0xe6,0x88,0x1e }; const uint8_t example2_hash_256[32] = { 0x50,0x8f,0x7e,0x55,0x3c,0x06,0x50,0x1d,0x74,0x9a,0x66,0xfc,0x28,0xc6,0xca,0xc0, 0xb0,0x05,0x74,0x6d,0x97,0x53,0x7f,0xa8,0x5d,0x9e,0x40,0x90,0x4e,0xfe,0xd2,0x9d }; int main () { uint8_t digest[64]; i2p::crypto::GOSTR3411_2012_512 (example1, 63, digest); assert(memcmp (digest, example1_hash_512, 64) == 0); i2p::crypto::GOSTR3411_2012_256 (example1, 63, digest); assert(memcmp (digest, example1_hash_256, 32) == 0); i2p::crypto::GOSTR3411_2012_512 (example2, 72, digest); assert(memcmp (digest, example2_hash_512, 64) == 0); i2p::crypto::GOSTR3411_2012_256 (example2, 72, digest); assert(memcmp (digest, example2_hash_256, 32) == 0); } i2pd-2.56.0/tests/test-http-merge_chunked.cpp000066400000000000000000000006321475272067700210640ustar00rootroot00000000000000#include #include "HTTP.h" using namespace i2p::http; int main() { const char *buf = "4\r\n" "HTTP\r\n" "A\r\n" " response \r\n" "E\r\n" "with \r\n" "chunks.\r\n" "0\r\n" "\r\n" ; std::stringstream in(buf); std::stringstream out; assert(MergeChunkedResponse(in, out) == true); assert(out.str() == "HTTP response with \r\nchunks."); return 0; } i2pd-2.56.0/tests/test-http-req.cpp000066400000000000000000000044211475272067700170530ustar00rootroot00000000000000#include #include "HTTP.h" using namespace i2p::http; int main() { HTTPReq *req; int ret = 0, len = 0; const char *buf; /* test: parsing request with body */ buf = "GET / HTTP/1.0\r\n" "User-Agent: curl/7.26.0\r\n" "Host: inr.i2p\r\n" "Accept: */*\r\n" "\r\n" "test"; len = strlen(buf); req = new HTTPReq; assert((ret = req->parse(buf, len)) == len - 4); 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"); delete req; /* test: parsing request without body */ buf = "GET / HTTP/1.0\r\n" "\r\n"; len = strlen(buf); req = new HTTPReq; assert((ret = req->parse(buf, len)) == len); assert(req->version == "HTTP/1.0"); assert(req->method == "GET"); assert(req->uri == "/"); assert(req->GetNumHeaders () == 0); delete req; /* test: parsing request without body */ buf = "GET / HTTP/1.1\r\n" "\r\n"; len = strlen(buf); req = new HTTPReq; assert((ret = req->parse(buf, len)) > 0); delete req; /* test: parsing incomplete request */ buf = "GET / HTTP/1.0\r\n" ""; len = strlen(buf); req = new HTTPReq; assert((ret = req->parse(buf, len)) == 0); /* request not completed */ delete req; /* test: parsing slightly malformed request */ buf = "GET http://inr.i2p HTTP/1.1\r\n" "Host: stats.i2p\r\n" "Accept-Encoding: \r\n" "Accept: */*\r\n" "\r\n"; len = strlen(buf); req = new HTTPReq; 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") == ""); delete req; return 0; } /* vim: expandtab:ts=2 */ i2pd-2.56.0/tests/test-http-res.cpp000066400000000000000000000025201475272067700170530ustar00rootroot00000000000000#include #include "HTTP.h" using namespace i2p::http; int main() { HTTPRes *res; int ret = 0, len = 0; const char *buf; /* test: parsing valid response without body */ buf = "HTTP/1.1 304 Not Modified\r\n" "Date: Thu, 14 Apr 2016 00:00:00 GMT\r\n" "Server: nginx/1.2.1\r\n" "Content-Length: 536\r\n" "\r\n"; len = strlen(buf); res = new HTTPRes; assert((ret = res->parse(buf, len)) == len); assert(res->version == "HTTP/1.1"); assert(res->status == "Not Modified"); assert(res->code == 304); assert(res->headers.size() == 3); assert(res->headers.count("Date") == 1); assert(res->headers.count("Server") == 1); assert(res->headers.count("Content-Length") == 1); assert(res->headers.find("Date")->second == "Thu, 14 Apr 2016 00:00:00 GMT"); assert(res->headers.find("Server")->second == "nginx/1.2.1"); assert(res->headers.find("Content-Length")->second == "536"); assert(res->is_chunked() == false); assert(res->content_length() == 536); delete res; /* test: building request */ buf = "HTTP/1.0 304 Not Modified\r\n" "Content-Length: 0\r\n" "\r\n"; res = new HTTPRes; res->version = "HTTP/1.0"; res->code = 304; res->status = "Not Modified"; res->add_header("Content-Length", "0"); assert(res->to_string() == buf); return 0; } /* vim: expandtab:ts=2 */ i2pd-2.56.0/tests/test-http-url.cpp000066400000000000000000000075031475272067700170720ustar00rootroot00000000000000#include #include "HTTP.h" using namespace i2p::http; int main() { std::map params; URL *url; url = new URL; assert(url->parse("https://127.0.0.1:7070/asdasd?12345") == true); assert(url->schema == "https"); assert(url->user == ""); assert(url->pass == ""); 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; url = new URL; assert(url->parse("http://user:password@site.com:8080/asdasd?123456") == true); assert(url->schema == "http"); assert(url->user == "user"); assert(url->pass == "password"); assert(url->host == "site.com"); assert(url->port == 8080); assert(url->path == "/asdasd"); assert(url->hasquery == true); assert(url->query == "123456"); delete url; url = new URL; assert(url->parse("http://user:password@site.com/asdasd?name=value") == true); assert(url->schema == "http"); assert(url->user == "user"); assert(url->pass == "password"); assert(url->host == "site.com"); assert(url->port == 0); assert(url->path == "/asdasd"); assert(url->hasquery == true); assert(url->query == "name=value"); delete url; url = new URL; assert(url->parse("http://user:@site.com/asdasd?name=value1&name=value2") == true); assert(url->schema == "http"); assert(url->user == "user"); assert(url->pass == ""); 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; url = new URL; assert(url->parse("http://user@site.com/asdasd?name1=value1&name2&name3=value2") == true); assert(url->schema == "http"); assert(url->user == "user"); assert(url->pass == ""); 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); assert(params.count("name1") == 1); assert(params.count("name2") == 1); assert(params.count("name3") == 1); assert(params.find("name1")->second == "value1"); assert(params.find("name2")->second == ""); assert(params.find("name3")->second == "value2"); delete url; url = new URL; assert(url->parse("http://@site.com:800/asdasd?") == true); assert(url->schema == "http"); assert(url->user == ""); assert(url->pass == ""); assert(url->host == "site.com"); assert(url->port == 800); assert(url->path == "/asdasd"); assert(url->hasquery == true); assert(url->query == ""); delete url; url = new URL; assert(url->parse("http://@site.com:17") == true); assert(url->schema == "http"); assert(url->user == ""); assert(url->pass == ""); assert(url->host == "site.com"); assert(url->port == 17); assert(url->path == ""); assert(url->hasquery == false); assert(url->query == ""); delete url; url = new URL; assert(url->parse("http://user:password@site.com:err_port/asdasd") == false); assert(url->schema == "http"); assert(url->user == "user"); assert(url->pass == "password"); assert(url->host == "site.com"); assert(url->port == 0); assert(url->path == ""); assert(url->hasquery == false); assert(url->query == ""); delete url; url = new URL; assert(url->parse("http://user:password@site.com:84/asdasd/@17#frag") == true); assert(url->schema == "http"); assert(url->user == "user"); assert(url->pass == "password"); 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; return 0; } /* vim: expandtab:ts=2 */ i2pd-2.56.0/tests/test-http-url_decode.cpp000066400000000000000000000006511475272067700203720ustar00rootroot00000000000000#include #include "HTTP.h" using namespace i2p::http; int main() { std::string in("/%D1%81%D1%82%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D0%B0/"); std::string out = UrlDecode(in); assert(strcmp(out.c_str(), "/страница/") == 0); in = "/%00/"; out = UrlDecode(in, false); assert(strcmp(out.c_str(), "/%00/") == 0); out = UrlDecode(in, true); assert(strcmp(out.c_str(), "/\0/") == 0); return 0; }