diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..0f57a84a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,39 @@ +# editorconfig.org + +root = true + +[*] +# Unix style files +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[Makefile,Makefile.*] +indent_style = tab +indent_size = 4 + +[*.cmd] +indent_style = space +indent_size = 2 +end_of_line = crlf + +[*.{h,cpp}] +indent_style = tab +indent_size = 4 + +[*.rc] +indent_style = space +indent_size = 4 + +[*.{md,markdown}] +indent_style = space +indent_size = 2 +trim_trailing_whitespace = false + +[*.yml] +indent_style = space +indent_size = 2 + +[*.patch] +trim_trailing_whitespace = false diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..488400a3 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +/build/build_mingw.cmd eol=crlf \ No newline at end of file diff --git a/.github/workflows/build-deb.yml b/.github/workflows/build-deb.yml new file mode 100644 index 00000000..ebc3df4d --- /dev/null +++ b/.github/workflows/build-deb.yml @@ -0,0 +1,39 @@ +name: Build Debian packages + +on: [push, pull_request] + +jobs: + build: + name: ${{ matrix.dist }} + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + dist: ['buster', 'bullseye', 'bookworm'] + + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 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 "+${{ github.sha }}~${{ matrix.dist }}" -b --distribution ${{ matrix.dist }} "CI build" + extra-build-deps: devscripts git + + - name: Upload package + uses: actions/upload-artifact@v3 + with: + name: i2pd_${{ matrix.dist }} + path: debian/artifacts/i2pd_*.deb + + - name: Upload debugging symbols + uses: actions/upload-artifact@v3 + with: + name: i2pd-dbgsym_${{ matrix.dist }} + path: debian/artifacts/i2pd-dbgsym_*.deb diff --git a/.github/workflows/build-freebsd.yml b/.github/workflows/build-freebsd.yml new file mode 100644 index 00000000..76b496ad --- /dev/null +++ b/.github/workflows/build-freebsd.yml @@ -0,0 +1,32 @@ +name: Build on FreeBSD + +on: [push, pull_request] + +jobs: + build: + runs-on: macos-12 + name: with UPnP + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Test in FreeBSD + id: test + uses: vmactions/freebsd-vm@v0.3.0 + 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@v3 + with: + name: i2pd-freebsd + path: build/i2pd diff --git a/.github/workflows/build-osx.yml b/.github/workflows/build-osx.yml new file mode 100644 index 00000000..266f2c54 --- /dev/null +++ b/.github/workflows/build-osx.yml @@ -0,0 +1,26 @@ +name: Build on OSX + +on: [push, pull_request] + +jobs: + build: + name: With USE_UPNP=${{ matrix.with_upnp }} + runs-on: macOS-latest + + strategy: + fail-fast: true + matrix: + with_upnp: ['yes', 'no'] + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: install packages + run: | + find /usr/local/bin -lname '*/Library/Frameworks/Python.framework/*' -delete + brew update + brew install boost miniupnpc openssl@1.1 + + - name: build application + run: make HOMEBREW=1 USE_UPNP=${{ matrix.with_upnp }} PREFIX=$GITHUB_WORKSPACE/output -j3 diff --git a/.github/workflows/build-windows-msvc.yml b/.github/workflows/build-windows-msvc.yml new file mode 100644 index 00000000..172e0596 --- /dev/null +++ b/.github/workflows/build-windows-msvc.yml @@ -0,0 +1,52 @@ +name: Build on Windows with MSVC + +on: [push, pull_request] + +jobs: + build: + name: Build + runs-on: windows-latest + + strategy: + fail-fast: false + + steps: + - name: Checkout + uses: actions/checkout@v3 + 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 + uses: crazy-max/ghaction-chocolatey@v2 + with: + args: install boost-msvc-14.3 --version=1.81.0 + + - name: Install OpenSSL + uses: crazy-max/ghaction-chocolatey@v2 + with: + args: install openssl + + - name: Configure + working-directory: build + run: cmake -DWITH_STATIC=ON . + + - name: Build + working-directory: build + run: cmake --build . --config Debug -- -m + + - name: Upload artifacts + uses: actions/upload-artifact@v3 + with: + name: i2pd-msvc + path: build/Debug/i2pd.* + diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml new file mode 100644 index 00000000..66c846c1 --- /dev/null +++ b/.github/workflows/build-windows.yml @@ -0,0 +1,139 @@ +name: Build on Windows + +on: [push, pull_request] + +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@v3 + 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@v3 + 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@v3 + 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@v3 + 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@v3 + 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 + update: true + + - name: Build WinXP-capable CRT packages + run: | + git clone https://github.com/msys2/MINGW-packages + pushd MINGW-packages + pushd mingw-w64-headers-git + sed -i 's/0x601/0x501/' PKGBUILD + MINGW_ARCH=mingw32 makepkg-mingw -sCLf --noconfirm + pacman --noconfirm -U mingw-w64-i686-headers-git-*-any.pkg.tar.zst + popd + pushd mingw-w64-crt-git + MINGW_ARCH=mingw32 makepkg-mingw -sCLf --noconfirm + pacman --noconfirm -U mingw-w64-i686-crt-git-*-any.pkg.tar.zst + popd + pushd mingw-w64-winpthreads-git + MINGW_ARCH=mingw32 makepkg-mingw -sCLf --noconfirm + pacman --noconfirm -U mingw-w64-i686-libwinpthread-git-*-any.pkg.tar.zst mingw-w64-i686-winpthreads-git-*-any.pkg.tar.zst + popd + popd + + - 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@v3 + with: + name: i2pd-xp.exe + path: i2pd.exe diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..935c2f93 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,49 @@ +name: Build on Ubuntu + +on: [push, pull_request] + +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@v3 + + - 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@v3 + + - name: install packages + run: | + sudo apt-get update + sudo apt-get install build-essential cmake libboost-all-dev libminiupnpc-dev libssl-dev zlib1g-dev + + - name: build application + run: | + cd build + cmake -DWITH_UPNP=${{ matrix.with_upnp }} . + make -j3 diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 00000000..28e4e8b0 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,130 @@ +name: Build containers + +on: + push: + branches: + - openssl + - docker + 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@v3 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Login to DockerHub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Login to GitHub Container registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build container for ${{ matrix.archname }} + uses: docker/build-push-action@v3 + with: + context: ./contrib/docker + file: ./contrib/docker/Dockerfile + platforms: ${{ matrix.platform }} + push: true + tags: | + purplei2p/i2pd:latest-${{ matrix.archname }} + ghcr.io/purplei2p/i2pd:latest-${{ matrix.archname }} + provenance: false + + push: + name: Pushing merged manifest + runs-on: ubuntu-latest + + permissions: + packages: write + contents: read + + needs: build + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Login to DockerHub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Login to GitHub Container registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: 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 diff --git a/.gitignore b/.gitignore index 3319fa10..75bd6abb 100644 --- a/.gitignore +++ b/.gitignore @@ -3,11 +3,17 @@ router.info router.keys i2p -libi2pd.so netDb /i2pd /libi2pd.a /libi2pdclient.a +/libi2pdlang.a +/libi2pd.so +/libi2pdclient.so +/libi2pdlang.so +/libi2pd.dll +/libi2pdclient.dll +/libi2pdlang.dll *.exe @@ -254,6 +260,7 @@ docs/generated build/Makefile # debian stuff +debian/i2pd.1.gz .pc/ # qt diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index e69de29b..00000000 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index af47f458..00000000 --- a/.travis.yml +++ /dev/null @@ -1,54 +0,0 @@ -language: cpp -cache: - apt: true -os: -- linux -#- osx -dist: xenial -sudo: required -compiler: -- g++ -- clang++ -env: - global: - - MAKEFLAGS="-j 2" - matrix: - - BUILD_TYPE=make UPNP=ON MAKE_UPNP=yes - - BUILD_TYPE=make UPNP=OFF MAKE_UPNP=no - - BUILD_TYPE=cmake UPNP=ON MAKE_UPNP=yes - - BUILD_TYPE=cmake UPNP=OFF MAKE_UPNP=no -matrix: - exclude: - - os: osx - env: BUILD_TYPE=cmake UPNP=ON MAKE_UPNP=yes - - os: osx - env: BUILD_TYPE=cmake UPNP=OFF MAKE_UPNP=no - - os: linux - compiler: clang++ - env: BUILD_TYPE=make UPNP=ON MAKE_UPNP=yes - - os: linux - compiler: clang++ - env: BUILD_TYPE=make UPNP=OFF MAKE_UPNP=no -addons: - apt: - packages: - - build-essential - - cmake - - g++ - - clang - - libboost-chrono-dev - - libboost-date-time-dev - - libboost-filesystem-dev - - libboost-program-options-dev - - libboost-system-dev - - libboost-thread-dev - - libminiupnpc-dev - - libssl-dev -before_install: -- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update ; fi -- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install libressl miniupnpc ; fi -- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew outdated boost || brew upgrade boost ; fi -script: -- if [[ "$TRAVIS_OS_NAME" == "linux" && "$BUILD_TYPE" == "cmake" ]]; then cd build && cmake -DCMAKE_BUILD_TYPE=Release -DWITH_UPNP=${UPNP} && make ; fi -- if [[ "$TRAVIS_OS_NAME" == "linux" && "$BUILD_TYPE" == "make" ]]; then make USE_UPNP=${MAKE_UPNP} ; fi -- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then make HOMEBREW=1 USE_UPNP=${MAKE_UPNP} ; fi diff --git a/ChangeLog b/ChangeLog index beef7a5a..881e05d7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,469 @@ # for this file format description, # see https://github.com/olivierlacan/keep-a-changelog +## [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 @@ -8,11 +471,11 @@ - 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 +- 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 +- 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 @@ -20,11 +483,11 @@ - Reseed servers list ### Fixed - Don't connect through terminated SAM destination -- Differentiate UDP server sessions by port +- 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 +- AppArmor profile ## [2.32.1] - 2020-06-02 ### Added @@ -104,7 +567,7 @@ ### Added - Client auth flag for b33 address ### Changed -- Remove incoming NTCP2 session from pending list when established +- Remove incoming NTCP2 session from pending list when established - Handle errors for NTCP2 SessionConfrimed send ### Fixed - Failure to start on Windows XP @@ -408,7 +871,7 @@ ### Added - Datagram i2p tunnels - Unique local addresses for server tunnels -- Configurable list of reseed servers and initial addressbook +- Configurable list of reseed servers and initial addressbook - Configurable netid - Initial iOS support diff --git a/LICENSE b/LICENSE index 2cb10225..93280084 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2013-2015, The PurpleI2P Project +Copyright (c) 2013-2023, The PurpleI2P Project All rights reserved. diff --git a/Makefile b/Makefile index 35d76b82..0861ec0b 100644 --- a/Makefile +++ b/Makefile @@ -1,24 +1,44 @@ +.DEFAULT_GOAL := all + SYS := $(shell $(CXX) -dumpmachine) -SHLIB := libi2pd.so + +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_CLIENT := libi2pdclient.so +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 -GREP := grep -DEPS := obj/make.dep LIB_SRC_DIR := libi2pd LIB_CLIENT_SRC_DIR := libi2pd_client +WRAP_SRC_DIR := libi2pd_wrapper +LANG_SRC_DIR := i18n DAEMON_SRC_DIR := daemon +# import source files lists include filelist.mk -USE_AESNI := yes -USE_AVX := yes -USE_STATIC := no -USE_MESHNET := no -USE_UPNP := no -DEBUG := yes +USE_AESNI := $(or $(USE_AESNI),yes) +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 @@ -27,6 +47,10 @@ else LD_DEBUG = -s endif +ifneq (, $(DESTDIR)) + PREFIX = $(DESTDIR) +endif + ifneq (, $(findstring darwin, $(SYS))) DAEMON_SRC += $(DAEMON_SRC_DIR)/UnixDaemon.cpp ifeq ($(HOMEBREW),1) @@ -34,36 +58,51 @@ ifneq (, $(findstring darwin, $(SYS))) else include Makefile.osx endif +else ifneq (, $(findstring mingw, $(SYS))$(findstring windows-gnu, $(SYS))$(findstring cygwin, $(SYS))) + DAEMON_SRC += Win32/DaemonWin32.cpp Win32/Win32App.cpp Win32/Win32Service.cpp Win32/Win32NetState.cpp + include Makefile.mingw else ifneq (, $(findstring linux, $(SYS))$(findstring gnu, $(SYS))) DAEMON_SRC += $(DAEMON_SRC_DIR)/UnixDaemon.cpp include Makefile.linux else ifneq (, $(findstring freebsd, $(SYS))$(findstring openbsd, $(SYS))) DAEMON_SRC += $(DAEMON_SRC_DIR)/UnixDaemon.cpp include Makefile.bsd -else ifneq (, $(findstring mingw, $(SYS))$(findstring cygwin, $(SYS))) - DAEMON_SRC += Win32/DaemonWin32.cpp Win32/Win32Service.cpp Win32/Win32App.cpp Win32/Win32NetState.cpp - include Makefile.mingw else # not supported $(error Not supported platform) endif -ifeq ($(USE_MESHNET),yes) - NEEDED_CXXFLAGS += -DMESHNET +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 -NEEDED_CXXFLAGS += -I$(LIB_SRC_DIR) -I$(LIB_CLIENT_SRC_DIR) +LIB_OBJS += $(patsubst %.cpp,obj/%.o,$(LIB_SRC)) +LIB_CLIENT_OBJS += $(patsubst %.cpp,obj/%.o,$(LIB_CLIENT_SRC)) +LANG_OBJS += $(patsubst %.cpp,obj/%.o,$(LANG_SRC)) +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) -all: mk_obj_dir $(ARLIB) $(ARLIB_CLIENT) $(I2PD) +## 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 - @mkdir -p obj/Win32 @mkdir -p obj/$(LIB_SRC_DIR) @mkdir -p obj/$(LIB_CLIENT_SRC_DIR) + @mkdir -p obj/$(LANG_SRC_DIR) @mkdir -p obj/$(DAEMON_SRC_DIR) + @mkdir -p obj/$(WRAP_SRC_DIR) + @mkdir -p obj/Win32 -api: mk_obj_dir $(SHLIB) $(ARLIB) -api_client: mk_obj_dir $(SHLIB) $(ARLIB) $(SHLIB_CLIENT) $(ARLIB_CLIENT) +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. @@ -72,40 +111,53 @@ api_client: mk_obj_dir $(SHLIB) $(ARLIB) $(SHLIB_CLIENT) $(ARLIB_CLIENT) ## -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. -deps: mk_obj_dir - $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) -MM *.cpp > $(DEPS) - @sed -i -e '/\.o:/ s/^/obj\//' $(DEPS) - -obj/%.o: %.cpp - $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(CPU_FLAGS) -c -o $@ $< +obj/%.o: %.cpp | mk_obj_dir + $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(DEFINES) $(INCFLAGS) -c -o $@ $< # '-' is 'ignore if missing' on first run -include $(DEPS) -DAEMON_OBJS += $(patsubst %.cpp,obj/%.o,$(DAEMON_SRC)) -$(I2PD): $(DAEMON_OBJS) $(ARLIB) $(ARLIB_CLIENT) - $(CXX) -o $@ $^ $(LDFLAGS) $(LDLIBS) +$(I2PD): $(DAEMON_OBJS) $(ARLIB) $(ARLIB_CLIENT) $(ARLIB_LANG) + $(CXX) -o $@ $(DEFINES) $(LDFLAGS) $^ $(LDLIBS) -$(SHLIB): $(patsubst %.cpp,obj/%.o,$(LIB_SRC)) +$(SHLIB): $(LIB_OBJS) ifneq ($(USE_STATIC),yes) - $(CXX) $(LDFLAGS) $(LDLIBS) -shared -o $@ $^ + $(CXX) $(LDFLAGS) -shared -o $@ $^ $(LDLIBS) endif -$(SHLIB_CLIENT): $(patsubst %.cpp,obj/%.o,$(LIB_CLIENT_SRC)) - $(CXX) $(LDFLAGS) $(LDLIBS) -shared -o $@ $^ +$(SHLIB_CLIENT): $(LIB_CLIENT_OBJS) $(SHLIB) $(SHLIB_LANG) +ifneq ($(USE_STATIC),yes) + $(CXX) $(LDFLAGS) -shared -o $@ $^ $(LDLIBS) $(SHLIB) $(SHLIB_LANG) +endif -$(ARLIB): $(patsubst %.cpp,obj/%.o,$(LIB_SRC)) +$(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): $(patsubst %.cpp,obj/%.o,$(LIB_CLIENT_SRC)) +$(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) + $(RM) $(I2PD) $(SHLIB) $(ARLIB) $(SHLIB_CLIENT) $(ARLIB_CLIENT) $(SHLIB_LANG) $(ARLIB_LANG) $(SHLIB_WRAP) $(ARLIB_WRAP) -strip: $(I2PD) $(SHLIB_CLIENT) $(SHLIB) +strip: $(I2PD) $(SHLIB) $(SHLIB_CLIENT) $(SHLIB_LANG) strip $^ LATEST_TAG=$(shell git describe --tags --abbrev=0 openssl) @@ -123,11 +175,13 @@ doxygen: .PHONY: all .PHONY: clean -.PHONY: deps .PHONY: doxygen .PHONY: dist .PHONY: last-dist .PHONY: api .PHONY: api_client +.PHONY: client +.PHONY: lang .PHONY: mk_obj_dir .PHONY: install +.PHONY: strip diff --git a/Makefile.bsd b/Makefile.bsd index 39e5651a..00543193 100644 --- a/Makefile.bsd +++ b/Makefile.bsd @@ -6,7 +6,8 @@ CXXFLAGS ?= ${CXX_DEBUG} -Wall -Wextra -Wno-unused-parameter -pedantic -Wno-misl ## (e.g. -fstack-protector-strong -Wformat -Werror=format-security), we do not want to remove ## -std=c++11. If you want to remove this variable please do so in a way that allows setting ## custom FLAGS to work at build-time. -NEEDED_CXXFLAGS = -std=c++11 -D_GLIBCXX_USE_NANOSLEEP=1 +NEEDED_CXXFLAGS = -std=c++11 +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 = -lcrypto -lssl -lz -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread diff --git a/Makefile.homebrew b/Makefile.homebrew index 64301c02..88b2a9e2 100644 --- a/Makefile.homebrew +++ b/Makefile.homebrew @@ -35,20 +35,23 @@ endif # Seems like all recent Mac's have AES-NI, after firmware upgrade 2.2 # Found no good way to detect it from command line. TODO: Might be some osx sysinfo magic ifeq ($(USE_AESNI),yes) - CXXFLAGS += -maes -endif -ifeq ($(USE_AVX),1) - CXXFLAGS += -mavx + CXXFLAGS += -D__AES__ -maes endif install: all - install -d ${PREFIX}/bin ${PREFIX}/etc/i2pd ${PREFIX}/share/doc/i2pd ${PREFIX}/share/i2pd ${PREFIX}/share/man/man1 ${PREFIX}/var/lib/i2pd - install -m 755 ${I2PD} ${PREFIX}/bin/ + install -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 - @cp -R contrib/certificates ${PREFIX}/share/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 - @gzip debian/i2pd.1 && install debian/i2pd.1.gz ${PREFIX}/share/man/man1 - @ln -sf ${PREFIX}/share/i2pd/certificates ${PREFIX}/var/lib/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 \ No newline at end of file + @ln -sf ${PREFIX}/etc/i2pd/tunnels.conf ${PREFIX}/var/lib/i2pd/tunnels.conf diff --git a/Makefile.linux b/Makefile.linux index 3ef4793c..6c7a4619 100644 --- a/Makefile.linux +++ b/Makefile.linux @@ -1,5 +1,5 @@ # set defaults instead redefine -CXXFLAGS ?= ${CXX_DEBUG} -Wall -Wextra -Wno-unused-parameter -pedantic -Wno-misleading-indentation -Wno-psabi +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 @@ -20,7 +20,11 @@ else ifeq ($(shell expr match ${CXXVER} "4\.[8-9]"),3) # gcc 4.8 - 4.9 else ifeq ($(shell expr match ${CXXVER} "[5-6]"),1) # gcc 5 - 6 NEEDED_CXXFLAGS += -std=c++11 LDLIBS = -latomic -else ifeq ($(shell expr match ${CXXVER} "[1,7-9]"),1) # gcc >= 7 +else ifeq ($(shell expr match ${CXXVER} "[7-9]"),1) # gcc 7 - 9 + NEEDED_CXXFLAGS += -std=c++17 + LDLIBS = -latomic +else ifeq ($(shell expr match ${CXXVER} "1[0-9]"),2) # gcc 10+ +# NEEDED_CXXFLAGS += -std=c++20 NEEDED_CXXFLAGS += -std=c++17 LDLIBS = -latomic else # not supported @@ -33,7 +37,7 @@ 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 + LIBDIR := /usr/lib/$(SYS) LDLIBS += $(LIBDIR)/libboost_system.a LDLIBS += $(LIBDIR)/libboost_date_time.a LDLIBS += $(LIBDIR)/libboost_filesystem.a @@ -41,37 +45,43 @@ ifeq ($(USE_STATIC),yes) LDLIBS += $(LIBDIR)/libssl.a LDLIBS += $(LIBDIR)/libcrypto.a LDLIBS += $(LIBDIR)/libz.a - LDLIBS += -lpthread -static-libstdc++ -static-libgcc -lrt -ldl - USE_AESNI := no +ifeq ($(USE_UPNP),yes) + LDLIBS += $(LIBDIR)/libminiupnpc.a +endif + LDLIBS += -lpthread -ldl else LDLIBS += -lcrypto -lssl -lz -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread -endif - -# UPNP Support (miniupnpc 1.5 and higher) ifeq ($(USE_UPNP),yes) - CXXFLAGS += -DUSE_UPNP -ifeq ($(USE_STATIC),yes) - LDLIBS += $(LIBDIR)/libminiupnpc.a -else LDLIBS += -lminiupnpc endif endif +# UPNP Support (miniupnpc 1.5 and higher) +ifeq ($(USE_UPNP),yes) + DEFINES += -DUSE_UPNP +endif + ifeq ($(USE_AESNI),yes) -#check if AES-NI is supported by CPU -ifneq ($(shell $(GREP) -c aes /proc/cpuinfo),0) - machine := $(shell uname -m) - ifeq ($(machine), aarch64) - CXXFLAGS += -DARM64AES - else - CPU_FLAGS += -maes - endif +ifneq (, $(findstring i386, $(SYS))$(findstring i686, $(SYS))$(findstring x86_64, $(SYS))) # only x86-based CPU supports that + NEEDED_CXXFLAGS += -maes + DEFINES += -D__AES__ endif endif -ifeq ($(USE_AVX),yes) -#check if AVX supported by CPU -ifneq ($(shell $(GREP) -c avx /proc/cpuinfo),0) - CPU_FLAGS += -mavx -endif -endif +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 diff --git a/Makefile.mingw b/Makefile.mingw index 2782b715..6cd19080 100644 --- a/Makefile.mingw +++ b/Makefile.mingw @@ -1,26 +1,21 @@ -USE_WIN32_APP=yes -CXX = g++ -WINDRES = windres -CXXFLAGS := ${CXX_DEBUG} -D_MT -DWIN32 -D_WINDOWS -DWIN32_LEAN_AND_MEAN -INCFLAGS = -Idaemon -I. -LDFLAGS := ${LD_DEBUG} -Wl,-Bstatic -static-libgcc -static-libstdc++ +# Build application with GUI (tray, main window) +USE_WIN32_APP := yes -# detect proper flag for c++11 support by compilers -CXXVER := $(shell $(CXX) -dumpversion) -ifeq ($(shell expr match ${CXXVER} "[4]\.[7-9]\|4\.1[0-9]\|[5-6]"),4) # gcc 4.7 - 6 - NEEDED_CXXFLAGS += -std=c++11 -else ifeq ($(shell expr match ${CXXVER} "[1,7-9]"),1) # gcc >= 7 - NEEDED_CXXFLAGS += -std=c++17 -else # not supported -$(error Compiler too old) -endif +WINDRES = windres + +CXXFLAGS := $(CXX_DEBUG) -fPIC -msse +INCFLAGS := -I$(DAEMON_SRC_DIR) -IWin32 +LDFLAGS := ${LD_DEBUG} -static + +NEEDED_CXXFLAGS += -std=c++17 +DEFINES += -DWIN32_LEAN_AND_MEAN # Boost libraries suffix BOOST_SUFFIX = -mt # UPNP Support ifeq ($(USE_UPNP),yes) - CXXFLAGS += -DUSE_UPNP -DMINIUPNP_STATICLIB + DEFINES += -DUSE_UPNP -DMINIUPNP_STATICLIB LDLIBS = -lminiupnpc endif @@ -38,34 +33,27 @@ LDLIBS += \ -liphlpapi \ -lole32 \ -luuid \ - -lstdc++ \ -lpthread ifeq ($(USE_WIN32_APP), yes) - CXXFLAGS += -DWIN32_APP + DEFINES += -DWIN32_APP LDFLAGS += -mwindows DAEMON_RC += Win32/Resource.rc DAEMON_OBJS += $(patsubst %.rc,obj/%.o,$(DAEMON_RC)) endif ifeq ($(USE_WINXP_FLAGS), yes) - CXXFLAGS += -DWINVER=0x0501 -D_WIN32_WINNT=0x0501 + DEFINES += -DWINVER=0x0501 -D_WIN32_WINNT=0x0501 endif -# don't change following line to ifeq ($(USE_AESNI),yes) !!! -ifeq ($(USE_AESNI),1) - CPU_FLAGS += -maes -else - CPU_FLAGS += -msse -endif - -ifeq ($(USE_AVX),1) - CPU_FLAGS += -mavx +ifeq ($(USE_AESNI),yes) + NEEDED_CXXFLAGS += -maes + DEFINES += -D__AES__ endif ifeq ($(USE_ASLR),yes) LDFLAGS += -Wl,--nxcompat -Wl,--high-entropy-va -Wl,--dynamicbase,--export-all-symbols endif -obj/%.o : %.rc - $(WINDRES) -i $< -o $@ +obj/%.o : %.rc | mk_obj_dir + $(WINDRES) $(DEFINES) $(INCFLAGS) --preprocessor-arg=-MMD --preprocessor-arg=-MP --preprocessor-arg=-MF$@.d -i $< -o $@ diff --git a/Makefile.osx b/Makefile.osx index c6af4fc2..e069aaff 100644 --- a/Makefile.osx +++ b/Makefile.osx @@ -1,6 +1,7 @@ CXX = clang++ -CXXFLAGS := ${CXX_DEBUG} -Wall -std=c++11 -DMAC_OSX +CXXFLAGS := ${CXX_DEBUG} -Wall -std=c++11 INCFLAGS = -I/usr/local/include +DEFINES := -DMAC_OSX LDFLAGS := -Wl,-rpath,/usr/local/lib -L/usr/local/lib LDFLAGS += -Wl,-dead_strip LDFLAGS += -Wl,-dead_strip_dylibs @@ -14,7 +15,7 @@ endif ifeq ($(USE_UPNP),yes) LDFLAGS += -ldl - CXXFLAGS += -DUSE_UPNP + DEFINES += -DUSE_UPNP ifeq ($(USE_STATIC),yes) LDLIBS += /usr/local/lib/libminiupnpc.a else @@ -22,12 +23,12 @@ ifeq ($(USE_UPNP),yes) endif endif -ifeq ($(USE_AESNI),1) - CXXFLAGS += -maes -else - CXXFLAGS += -msse -endif +OSARCH = $(shell uname -p) -ifeq ($(USE_AVX),1) - CXXFLAGS += -mavx +ifneq ($(OSARCH),powerpc) + ifeq ($(USE_AESNI),yes) + CXXFLAGS += -D__AES__ -maes + else + CXXFLAGS += -msse + endif endif diff --git a/README.md b/README.md index ec7548f0..e7fb7318 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,9 @@ [![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 ==== @@ -53,6 +56,8 @@ 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: @@ -64,15 +69,15 @@ Build instructions: **Supported systems:** -* GNU/Linux - [![Build Status](https://travis-ci.org/PurpleI2P/i2pd.svg?branch=openssl)](https://travis-ci.org/PurpleI2P/i2pd) - * CentOS / Fedora / Mageia - [![Build Status](https://copr.fedorainfracloud.org/coprs/supervillain/i2pd/package/i2pd-git/status_image/last_build.png)](https://copr.fedorainfracloud.org/coprs/supervillain/i2pd/package/i2pd-git/) - * Alpine, ArchLinux, openSUSE, Gentoo, Debian, Ubuntu, etc. -* Windows - [![Build status](https://ci.appveyor.com/api/projects/status/1908qe4p48ff1x23?svg=true)](https://ci.appveyor.com/project/PurpleI2P/i2pd) -* Mac OS X - [![Build Status](https://travis-ci.org/PurpleI2P/i2pd.svg?branch=openssl)](https://travis-ci.org/PurpleI2P/i2pd) -* Docker image - [![Build Status](https://img.shields.io/docker/cloud/build/purplei2p/i2pd)](https://hub.docker.com/r/purplei2p/i2pd/builds/) -* Snap - [![Snap Status](https://build.snapcraft.io/badge/PurpleI2P/i2pd-snap.svg)](https://build.snapcraft.io/user/PurpleI2P/i2pd-snap) -* FreeBSD -* Android +* 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 @@ -81,15 +86,36 @@ 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 --------- -BTC: 3MDoGJW9TLMTCDGrR9bLgWXfm6sjmgy86f -LTC: LKQirrYrDeTuAPnpYq5y7LVKtywfkkHi59 -ETH: 0x9e5bac70d20d1079ceaa111127f4fb3bccce379d -DASH: Xw8YUrQpYzP9tZBmbjqxS3M97Q7v3vJKUF -ZEC: t1cTckLuXsr1dwVrK4NDzfhehss4NvMadAJ -GST: GbD2JSQHBHCKLa9WTHmigJRpyFgmBj4woG +**E-Mail**: ```i2porignal at yandex.ru``` + +**BTC**: ```3MDoGJW9TLMTCDGrR9bLgWXfm6sjmgy86f``` + +**LTC**: ```LKQirrYrDeTuAPnpYq5y7LVKtywfkkHi59``` + +**ETH**: ```0x9e5bac70d20d1079ceaa111127f4fb3bccce379d``` + +**GST**: ```GbD2JSQHBHCKLa9WTHmigJRpyFgmBj4woG``` + +**DASH**: ```Xw8YUrQpYzP9tZBmbjqxS3M97Q7v3vJKUF``` + +**ZEC**: ```t1cTckLuXsr1dwVrK4NDzfhehss4NvMadAJ``` + +**ANC**: ```AQJYweYYUqM1nVfLqfoSMpUMfzxvS4Xd7z``` + +**XMR**: ```497pJc7X4xqKvcLBLpSUtRgWqMMyo24u4btCos3cak6gbMkpobgSU6492ztUcUBghyeHpYeczB55s38NpuHoH5WGNSPDRMH``` License ------- diff --git a/Win32/DaemonWin32.cpp b/Win32/DaemonWin32.cpp index 1214ff68..48f65c27 100644 --- a/Win32/DaemonWin32.cpp +++ b/Win32/DaemonWin32.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -14,10 +14,10 @@ #include "Log.h" #ifdef _WIN32 -#include "Win32/Win32Service.h" +#include "Win32Service.h" #ifdef WIN32_APP #include -#include "Win32/Win32App.h" +#include "Win32App.h" #endif namespace i2p @@ -29,51 +29,30 @@ namespace util setlocale(LC_CTYPE, ""); SetConsoleCP(1251); SetConsoleOutputCP(1251); - setlocale(LC_ALL, "Russian"); + //setlocale(LC_ALL, "Russian"); setlocale(LC_TIME, "C"); i2p::log::SetThrowFunction ([](const std::string& s) { MessageBox(0, TEXT(s.c_str ()), TEXT("i2pd"), MB_ICONERROR | MB_TASKMODAL | MB_OK ); - }); + } + ); if (!Daemon_Singleton::init(argc, argv)) return false; - std::string serviceControl; i2p::config::GetOption("svcctl", serviceControl); - if (serviceControl == "install") - { - LogPrint(eLogInfo, "WinSVC: installing ", SERVICE_NAME, " as service"); - InstallService( - SERVICE_NAME, // Name of service - SERVICE_DISPLAY_NAME, // Name to display - SERVICE_START_TYPE, // Service start type - SERVICE_DEPENDENCIES, // Dependencies - SERVICE_ACCOUNT, // Service running account - SERVICE_PASSWORD // Password of the account - ); - return false; - } - else if (serviceControl == "remove") - { - LogPrint(eLogInfo, "WinSVC: uninstalling ", SERVICE_NAME, " service"); - UninstallService(SERVICE_NAME); - return false; - } - if (isDaemon) { LogPrint(eLogDebug, "Daemon: running as service"); I2PService service((PSTR)SERVICE_NAME); if (!I2PService::Run(service)) { - LogPrint(eLogError, "Daemon: Service failed to run w/err 0x%08lx\n", GetLastError()); + LogPrint(eLogCritical, "Daemon: Service failed to run w/err 0x%08lx\n", GetLastError()); return false; } return false; } - else - LogPrint(eLogDebug, "Daemon: running as user"); + return true; } @@ -82,13 +61,10 @@ namespace util setlocale(LC_CTYPE, ""); SetConsoleCP(1251); SetConsoleOutputCP(1251); - setlocale(LC_ALL, "Russian"); + //setlocale(LC_ALL, "Russian"); setlocale(LC_TIME, "C"); #ifdef WIN32_APP - if (!i2p::win32::StartWin32App ()) return false; - - // override log - i2p::config::SetOption("log", std::string ("file")); + if (!i2p::win32::StartWin32App (isDaemon)) return false; #endif bool ret = Daemon_Singleton::start(); if (ret && i2p::log::Logger().GetLogType() == eLogFile) diff --git a/Win32/Resource.rc b/Win32/Resource.rc index 5d394d1a..c9266b08 100644 --- a/Win32/Resource.rc +++ b/Win32/Resource.rc @@ -1,36 +1,36 @@ -#include "resource.h" - -#define APSTUDIO_READONLY_SYMBOLS -#include "winres.h" -#undef APSTUDIO_READONLY_SYMBOLS - -#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) -LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US -#pragma code_page(1252) - -#ifdef APSTUDIO_INVOKED -1 TEXTINCLUDE -BEGIN - "resource.h\0" -END - -2 TEXTINCLUDE -BEGIN - "#include ""winres.h""\r\n" - "\0" -END - -3 TEXTINCLUDE -BEGIN - "\r\n" - "\0" -END -#endif // APSTUDIO_INVOKED - -MAINICON ICON "mask.ico" -#endif // English (United States) resources - -#ifndef APSTUDIO_INVOKED -#include "Resource.rc2" -#endif // not APSTUDIO_INVOKED - +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +#include "winres.h" +#undef APSTUDIO_READONLY_SYMBOLS + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) + +#ifdef APSTUDIO_INVOKED +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END +#endif // APSTUDIO_INVOKED + +MAINICON ICON "mask.ico" +#endif // English (United States) resources + +#ifndef APSTUDIO_INVOKED +#include "Resource.rc2" +#endif // not APSTUDIO_INVOKED + diff --git a/Win32/Resource.rc2 b/Win32/Resource.rc2 index 6a4f481d..32744584 100644 --- a/Win32/Resource.rc2 +++ b/Win32/Resource.rc2 @@ -2,7 +2,7 @@ #error this file is not editable by Microsoft Visual C++ #endif //APSTUDIO_INVOKED -#include "../libi2pd/version.h" +#include "version.h" VS_VERSION_INFO VERSIONINFO FILEVERSION I2PD_VERSION_MAJOR,I2PD_VERSION_MINOR,I2PD_VERSION_MICRO,I2PD_VERSION_PATCH @@ -25,7 +25,7 @@ BEGIN VALUE "FileDescription", "C++ I2P daemon" VALUE "FileVersion", I2PD_VERSION VALUE "InternalName", CODENAME - VALUE "LegalCopyright", "Copyright (C) 2013-2017, The PurpleI2P Project" + VALUE "LegalCopyright", "Copyright (C) 2013-2023, The PurpleI2P Project" VALUE "OriginalFilename", "i2pd" VALUE "ProductName", "Purple I2P" VALUE "ProductVersion", I2P_VERSION diff --git a/Win32/Win32App.cpp b/Win32/Win32App.cpp index df345b11..9f750c4c 100644 --- a/Win32/Win32App.cpp +++ b/Win32/Win32App.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -31,6 +31,7 @@ #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) @@ -43,16 +44,15 @@ namespace i2p { namespace win32 { - static DWORD GracefulShutdownEndtime = 0; - - typedef DWORD (* IPN)(); - IPN GetTickCountLocal = (IPN)GetProcAddress (GetModuleHandle ("KERNEL32.dll"), "GetTickCount"); + 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_APP, "Show app"); + 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()) @@ -83,18 +83,19 @@ namespace win32 DestroyMenu(hPopup); } - static void AddTrayIcon (HWND hWnd) + 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"); - strcpy (nid.szInfo, "i2pd is starting"); + if (notify) strcpy (nid.szInfo, "i2pd is starting"); Shell_NotifyIcon(NIM_ADD, &nid ); } @@ -133,7 +134,7 @@ namespace win32 transfer >>= 10; auto mbytes = transfer & 0x03ff; transfer >>= 10; - auto gbytes = transfer & 0x03ff; + auto gbytes = transfer; if (gbytes) s << gbytes << " GB, "; @@ -144,32 +145,53 @@ namespace win32 s << bytes << " Bytes\n"; } + static void ShowNetworkStatus (std::stringstream& s, RouterStatus status, bool testing) + { + 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 (i2p::context.GetError () != eRouterErrorNone) + { + switch (i2p::context.GetError ()) + { + case eRouterErrorClockSkew: + s << " - Clock skew"; + break; + case eRouterErrorOffline: + s << " - Offline"; + break; + case eRouterErrorSymmetricNAT: + s << " - Symmetric NAT"; + break; + default: ; + } + } + } + static void PrintMainWindowText (std::stringstream& s) { s << "\n"; s << "Status: "; - switch (i2p::context.GetStatus()) + ShowNetworkStatus (s, i2p::context.GetStatus (), i2p::context.GetTesting ()); + if (i2p::context.SupportsV6 ()) { - case eRouterStatusOK: s << "OK"; break; - case eRouterStatusTesting: s << "Testing"; break; - case eRouterStatusFirewalled: s << "Firewalled"; break; - case eRouterStatusError: - { - switch (i2p::context.GetError()) - { - case eRouterErrorClockSkew: s << "Clock skew"; break; - default: s << "Error"; - } - break; - } - default: s << "Unknown"; + s << " / "; + ShowNetworkStatus (s, i2p::context.GetStatusV6 (), i2p::context.GetTestingV6 ()); } s << "; "; s << "Success Rate: " << i2p::tunnel::tunnels.GetTunnelCreationSuccessRate() << "%\n"; s << "Uptime: "; ShowUptime(s, i2p::context.GetUptime ()); - if (GracefulShutdownEndtime != 0) + if (g_GracefulShutdownEndtime != 0) { - DWORD GracefulTimeLeft = (GracefulShutdownEndtime - GetTickCountLocal()) / 1000; + DWORD GracefulTimeLeft = (g_GracefulShutdownEndtime - GetTickCount()) / 1000; s << "Graceful shutdown, time left: "; ShowUptime(s, GracefulTimeLeft); } else @@ -198,7 +220,7 @@ namespace win32 case WM_CREATE: { s_uTaskbarRestart = RegisterWindowMessage(TEXT("TaskbarCreated")); - AddTrayIcon (hWnd); + AddTrayIcon (hWnd, true); break; } case WM_CLOSE: @@ -247,7 +269,7 @@ namespace win32 i2p::context.SetAcceptsTunnels (false); SetTimer (hWnd, IDT_GRACEFUL_SHUTDOWN_TIMER, 10*60*1000, nullptr); // 10 minutes SetTimer (hWnd, IDT_GRACEFUL_TUNNELCHECK_TIMER, 1000, nullptr); // check tunnels every second - GracefulShutdownEndtime = GetTickCountLocal() + 10*60*1000; + g_GracefulShutdownEndtime = GetTickCount() + 10*60*1000; i2p::util::DaemonWin32::Instance ().isGraceful = true; return 0; } @@ -256,7 +278,7 @@ namespace win32 i2p::context.SetAcceptsTunnels (true); KillTimer (hWnd, IDT_GRACEFUL_SHUTDOWN_TIMER); KillTimer (hWnd, IDT_GRACEFUL_TUNNELCHECK_TIMER); - GracefulShutdownEndtime = 0; + g_GracefulShutdownEndtime = 0; i2p::util::DaemonWin32::Instance ().isGraceful = false; return 0; } @@ -283,6 +305,12 @@ namespace win32 SetTimer(hWnd, FRAME_UPDATE_TIMER, 3000, NULL); return 0; } + case ID_DATADIR: + { + std::string datadir(i2p::fs::GetUTF8DataDir()); + ShellExecute(NULL, "explore", datadir.c_str(), NULL, NULL, SW_SHOWNORMAL); + return 0; + } } break; } @@ -321,6 +349,9 @@ namespace win32 } } } +#if (__cplusplus >= 201703L) // C++ 17 or higher + [[fallthrough]]; +#endif } case WM_TRAYICON: { @@ -343,7 +374,7 @@ namespace win32 { case IDT_GRACEFUL_SHUTDOWN_TIMER: { - GracefulShutdownEndtime = 0; + g_GracefulShutdownEndtime = 0; PostMessage (hWnd, WM_CLOSE, 0, 0); // exit return 0; } @@ -383,15 +414,16 @@ namespace win32 default: { if (uMsg == s_uTaskbarRestart) - AddTrayIcon (hWnd); + AddTrayIcon (hWnd, false); break; } } return DefWindowProc( hWnd, uMsg, wParam, lParam); } - bool StartWin32App () + bool StartWin32App (bool isWinService) { + g_isWinService = isWinService; if (FindWindow (I2PD_WIN32_CLASSNAME, TEXT("i2pd"))) { MessageBox(NULL, TEXT("I2Pd is running already"), TEXT("Warning"), MB_OK); @@ -420,7 +452,9 @@ namespace win32 MessageBox(NULL, "Failed to create main window", TEXT("Warning!"), MB_ICONERROR | MB_OK | MB_TOPMOST); return false; } - SubscribeToEvents(); + // COM requires message loop to work, which is not implemented in service mode + if (!g_isWinService) + SubscribeToEvents(); return true; } @@ -440,7 +474,8 @@ namespace win32 HWND hWnd = FindWindow (I2PD_WIN32_CLASSNAME, TEXT("i2pd")); if (hWnd) PostMessage (hWnd, WM_COMMAND, MAKEWPARAM(ID_EXIT, 0), 0); - // UnSubscribeFromEvents(); // TODO: understand why unsubscribing crashes app + else if(!g_isWinService) + UnSubscribeFromEvents(); UnregisterClass (I2PD_WIN32_CLASSNAME, GetModuleHandle(NULL)); } diff --git a/Win32/Win32App.h b/Win32/Win32App.h index d242f7d3..614de738 100644 --- a/Win32/Win32App.h +++ b/Win32/Win32App.h @@ -15,7 +15,9 @@ namespace i2p { namespace win32 { - bool StartWin32App (); + extern DWORD g_GracefulShutdownEndtime; + + bool StartWin32App (bool isWinService); void StopWin32App (); int RunWin32App (); bool GracefulShutdown (); diff --git a/Win32/Win32NetState.cpp b/Win32/Win32NetState.cpp index dd4dd08c..794dc4b9 100644 --- a/Win32/Win32NetState.cpp +++ b/Win32/Win32NetState.cpp @@ -15,6 +15,7 @@ IUnknown *pUnknown = nullptr; INetworkListManager *pNetworkListManager = nullptr; IConnectionPointContainer *pCPContainer = nullptr; IConnectionPoint *pConnectPoint = nullptr; +CNetworkListManagerEvent *pNetEvent = nullptr; DWORD Cookie = 0; void SubscribeToEvents() @@ -29,10 +30,14 @@ void SubscribeToEvents() if (SUCCEEDED(Result)) { VARIANT_BOOL IsConnect = VARIANT_FALSE; +#if defined(_MSC_VER) + Result = pNetworkListManager->get_IsConnectedToInternet(&IsConnect); +#else Result = pNetworkListManager->IsConnectedToInternet(&IsConnect); +#endif if (SUCCEEDED(Result)) { i2p::transport::transports.SetOnline (true); - LogPrint(eLogInfo, "NetState: current state: ", IsConnect == VARIANT_TRUE ? "connected" : "disconnected"); + LogPrint(eLogInfo, "NetState: Current state: ", IsConnect == VARIANT_TRUE ? "connected" : "disconnected"); } Result = pNetworkListManager->QueryInterface(IID_IConnectionPointContainer, (void **)&pCPContainer); @@ -41,8 +46,8 @@ void SubscribeToEvents() Result = pCPContainer->FindConnectionPoint(IID_INetworkListManagerEvents, &pConnectPoint); if(SUCCEEDED(Result)) { - CNetworkListManagerEvent *NetEvent = new CNetworkListManagerEvent; - Result = pConnectPoint->Advise((IUnknown *)NetEvent, &Cookie); + pNetEvent = new CNetworkListManagerEvent; + Result = pConnectPoint->Advise((IUnknown *)pNetEvent, &Cookie); if (SUCCEEDED(Result)) LogPrint(eLogInfo, "NetState: Successfully subscribed to NetworkListManagerEvent messages"); else @@ -59,6 +64,7 @@ void SubscribeToEvents() void UnSubscribeFromEvents() { + LogPrint(eLogInfo, "NetState: Unsubscribing from NetworkListManagerEvents"); try { if (pConnectPoint) { @@ -66,6 +72,9 @@ void UnSubscribeFromEvents() pConnectPoint->Release(); } + if (pNetEvent) + pNetEvent->Release(); + if (pCPContainer) pCPContainer->Release(); @@ -79,7 +88,7 @@ void UnSubscribeFromEvents() } catch (std::exception& ex) { - LogPrint (eLogError, "NetState: received exception: ", ex.what ()); + LogPrint (eLogError, "NetState: Received exception: ", ex.what ()); } } diff --git a/Win32/Win32NetState.h b/Win32/Win32NetState.h index c2ddaee4..1414a324 100644 --- a/Win32/Win32NetState.h +++ b/Win32/Win32NetState.h @@ -19,21 +19,18 @@ class CNetworkListManagerEvent : public INetworkListManagerEvents { public: CNetworkListManagerEvent() : m_ref(1) { } - ~CNetworkListManagerEvent() { } HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject) { - HRESULT Result = S_OK; if (IsEqualIID(riid, IID_IUnknown)) { *ppvObject = (IUnknown *)this; } else if (IsEqualIID(riid ,IID_INetworkListManagerEvents)) { *ppvObject = (INetworkListManagerEvents *)this; } else { - Result = E_NOINTERFACE; + return E_NOINTERFACE; } AddRef(); - - return Result; + return S_OK; } ULONG STDMETHODCALLTYPE AddRef() diff --git a/Win32/Win32Service.cpp b/Win32/Win32Service.cpp index 7a6d7abf..057a1edd 100644 --- a/Win32/Win32Service.cpp +++ b/Win32/Win32Service.cpp @@ -1,18 +1,13 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * * See full license text in LICENSE file at top of project tree */ -#ifdef _WIN32 -#define _CRT_SECURE_NO_WARNINGS // to use freopen -#endif - #include "Win32Service.h" #include -//#include #include #include "Daemon.h" @@ -26,7 +21,7 @@ BOOL I2PService::isService() HWINSTA hWinStation = GetProcessWindowStation(); if (hWinStation != NULL) { - USEROBJECTFLAGS uof = { 0 }; + USEROBJECTFLAGS uof = { FALSE, FALSE, 0 }; if (GetUserObjectInformation(hWinStation, UOI_FLAGS, &uof, sizeof(USEROBJECTFLAGS), NULL) && ((uof.dwFlags & WSF_VISIBLE) == 0)) { bIsService = TRUE; @@ -124,24 +119,20 @@ void I2PService::Start(DWORD dwArgc, PSTR *pszArgv) } catch (DWORD dwError) { - LogPrint(eLogError, "Win32Service Start", dwError); + LogPrint(eLogCritical, "Win32Service: Start error: ", dwError); SetServiceStatus(SERVICE_STOPPED, dwError); } catch (...) { - LogPrint(eLogError, "Win32Service failed to start.", EVENTLOG_ERROR_TYPE); + LogPrint(eLogCritical, "Win32Service: failed to start: ", EVENTLOG_ERROR_TYPE); SetServiceStatus(SERVICE_STOPPED); } } void I2PService::OnStart(DWORD dwArgc, PSTR *pszArgv) { - LogPrint(eLogInfo, "Win32Service in OnStart", EVENTLOG_INFORMATION_TYPE); + LogPrint(eLogInfo, "Win32Service: in OnStart (", EVENTLOG_INFORMATION_TYPE, ")"); Daemon.start(); - //i2p::util::config::OptionParser(dwArgc, pszArgv); - //i2p::util::filesystem::ReadConfigFile(i2p::util::config::mapArgs, i2p::util::config::mapMultiArgs); - //i2p::context.OverrideNTCPAddress(i2p::util::config::GetCharArg("-host", "127.0.0.1"), - // i2p::util::config::GetArg("-port", 17070)); _worker = new std::thread(std::bind(&I2PService::WorkerThread, this)); } @@ -166,12 +157,12 @@ void I2PService::Stop() } catch (DWORD dwError) { - LogPrint(eLogInfo, "Win32Service Stop", dwError); + LogPrint(eLogInfo, "Win32Service: Stop error: ", dwError); SetServiceStatus(dwOriginalState); } catch (...) { - LogPrint(eLogError, "Win32Service failed to stop.", EVENTLOG_ERROR_TYPE); + LogPrint(eLogCritical, "Win32Service: Failed to stop: ", EVENTLOG_ERROR_TYPE); SetServiceStatus(dwOriginalState); } } @@ -179,7 +170,7 @@ void I2PService::Stop() void I2PService::OnStop() { // Log a service stop message to the Application log. - LogPrint(eLogInfo, "Win32Service in OnStop", EVENTLOG_INFORMATION_TYPE); + LogPrint(eLogInfo, "Win32Service: in OnStop (", EVENTLOG_INFORMATION_TYPE, ")"); Daemon.stop(); m_fStopping = TRUE; if (WaitForSingleObject(m_hStoppedEvent, INFINITE) != WAIT_OBJECT_0) @@ -200,12 +191,12 @@ void I2PService::Pause() } catch (DWORD dwError) { - LogPrint(eLogError, "Win32Service Pause", dwError); + LogPrint(eLogCritical, "Win32Service: Pause error: ", dwError); SetServiceStatus(SERVICE_RUNNING); } catch (...) { - LogPrint(eLogError, "Win32Service failed to pause.", EVENTLOG_ERROR_TYPE); + LogPrint(eLogCritical, "Win32Service: Failed to pause: ", EVENTLOG_ERROR_TYPE); SetServiceStatus(SERVICE_RUNNING); } } @@ -224,12 +215,12 @@ void I2PService::Continue() } catch (DWORD dwError) { - LogPrint(eLogError, "Win32Service Continue", dwError); + LogPrint(eLogCritical, "Win32Service: Continue error: ", dwError); SetServiceStatus(SERVICE_PAUSED); } catch (...) { - LogPrint(eLogError, "Win32Service failed to resume.", EVENTLOG_ERROR_TYPE); + LogPrint(eLogCritical, "Win32Service: Failed to resume: ", EVENTLOG_ERROR_TYPE); SetServiceStatus(SERVICE_PAUSED); } } @@ -247,11 +238,11 @@ void I2PService::Shutdown() } catch (DWORD dwError) { - LogPrint(eLogError, "Win32Service Shutdown", dwError); + LogPrint(eLogCritical, "Win32Service: Shutdown error: ", dwError); } catch (...) { - LogPrint(eLogError, "Win32Service failed to shut down.", EVENTLOG_ERROR_TYPE); + LogPrint(eLogCritical, "Win32Service: Failed to shut down: ", EVENTLOG_ERROR_TYPE); } } @@ -290,125 +281,3 @@ void FreeHandles(SC_HANDLE schSCManager, SC_HANDLE schService) schService = NULL; } } - -void InstallService(PCSTR pszServiceName, PCSTR pszDisplayName, DWORD dwStartType, PCSTR pszDependencies, PCSTR pszAccount, PCSTR pszPassword) -{ - printf("Try to install Win32Service (%s).\n", pszServiceName); - - char szPath[MAX_PATH]; - SC_HANDLE schSCManager = NULL; - SC_HANDLE schService = NULL; - - if (GetModuleFileName(NULL, szPath, ARRAYSIZE(szPath)) == 0) - { - printf("GetModuleFileName failed w/err 0x%08lx\n", GetLastError()); - FreeHandles(schSCManager, schService); - return; - } - char SvcOpt[] = " --daemon"; - strncat(szPath, SvcOpt, strlen(SvcOpt)); - - // Open the local default service control manager database - schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT | SC_MANAGER_CREATE_SERVICE); - if (schSCManager == NULL) - { - printf("OpenSCManager failed w/err 0x%08lx\n", GetLastError()); - FreeHandles(schSCManager, schService); - return; - } - - // Install the service into SCM by calling CreateService - schService = CreateService( - schSCManager, // SCManager database - pszServiceName, // Name of service - pszDisplayName, // Name to display - SERVICE_QUERY_STATUS, // Desired access - SERVICE_WIN32_OWN_PROCESS, // Service type - dwStartType, // Service start type - SERVICE_ERROR_NORMAL, // Error control type - szPath, // Service's binary - NULL, // No load ordering group - NULL, // No tag identifier - pszDependencies, // Dependencies - pszAccount, // Service running account - pszPassword // Password of the account - ); - - if (schService == NULL) - { - printf("CreateService failed w/err 0x%08lx\n", GetLastError()); - FreeHandles(schSCManager, schService); - return; - } - - printf("Win32Service is installed as %s.\n", pszServiceName); - - // Centralized cleanup for all allocated resources. - FreeHandles(schSCManager, schService); -} - -void UninstallService(PCSTR pszServiceName) -{ - printf("Try to uninstall Win32Service (%s).\n", pszServiceName); - - SC_HANDLE schSCManager = NULL; - SC_HANDLE schService = NULL; - SERVICE_STATUS ssSvcStatus = {}; - - // Open the local default service control manager database - schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT); - if (schSCManager == NULL) - { - printf("OpenSCManager failed w/err 0x%08lx\n", GetLastError()); - FreeHandles(schSCManager, schService); - return; - } - - // Open the service with delete, stop, and query status permissions - schService = OpenService(schSCManager, pszServiceName, SERVICE_STOP | SERVICE_QUERY_STATUS | DELETE); - if (schService == NULL) - { - printf("OpenService failed w/err 0x%08lx\n", GetLastError()); - FreeHandles(schSCManager, schService); - return; - } - - // Try to stop the service - if (ControlService(schService, SERVICE_CONTROL_STOP, &ssSvcStatus)) - { - printf("Stopping %s.\n", pszServiceName); - Sleep(1000); - - while (QueryServiceStatus(schService, &ssSvcStatus)) - { - if (ssSvcStatus.dwCurrentState == SERVICE_STOP_PENDING) - { - printf("."); - Sleep(1000); - } - else break; - } - - if (ssSvcStatus.dwCurrentState == SERVICE_STOPPED) - { - printf("\n%s is stopped.\n", pszServiceName); - } - else - { - printf("\n%s failed to stop.\n", pszServiceName); - } - } - - // Now remove the service by calling DeleteService. - if (!DeleteService(schService)) - { - printf("DeleteService failed w/err 0x%08lx\n", GetLastError()); - FreeHandles(schSCManager, schService); - return; - } - - printf("%s is removed.\n", pszServiceName); - - // Centralized cleanup for all allocated resources. - FreeHandles(schSCManager, schService); -} diff --git a/Win32/Win32Service.h b/Win32/Win32Service.h index 40fff787..5cedbed6 100644 --- a/Win32/Win32Service.h +++ b/Win32/Win32Service.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -12,25 +12,7 @@ #include #include -#ifdef _WIN32 -// Internal name of the service -#define SERVICE_NAME "i2pdService" - -// Displayed name of the service -#define SERVICE_DISPLAY_NAME "i2pd router service" - -// Service start options. -#define SERVICE_START_TYPE SERVICE_DEMAND_START - -// List of service dependencies - "dep1\0dep2\0\0" -#define SERVICE_DEPENDENCIES "" - -// The name of the account under which the service should run -#define SERVICE_ACCOUNT "NT AUTHORITY\\LocalService" - -// The password to the service account name -#define SERVICE_PASSWORD NULL -#endif +#define SERVICE_NAME "i2pdService" class I2PService { @@ -78,15 +60,4 @@ class I2PService std::thread* _worker; }; -void InstallService( - PCSTR pszServiceName, - PCSTR pszDisplayName, - DWORD dwStartType, - PCSTR pszDependencies, - PCSTR pszAccount, - PCSTR pszPassword -); - -void UninstallService(PCSTR pszServiceName); - #endif // WIN_32_SERVICE_H__ diff --git a/android/.gitignore b/android/.gitignore deleted file mode 100644 index 57a0a6cd..00000000 --- a/android/.gitignore +++ /dev/null @@ -1,18 +0,0 @@ -gen -tests -bin -libs -log* -obj -.cxx -.gradle -.idea -.externalNativeBuild -ant.properties -local.properties -build.sh -android.iml -build -*.iml -*.local -*.jks diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml deleted file mode 100755 index 88985138..00000000 --- a/android/AndroidManifest.xml +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/android/README.md b/android/README.md deleted file mode 100644 index e0850b15..00000000 --- a/android/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# how to compile? -## Install the gradle + NDK or use android-studio -[https://gradle.org/install/](https://gradle.org/install/) - -## Install the depencies -``` -git clone https://github.com/PurpleI2P/Boost-for-Android-Prebuilt.git -b boost-1_72_0 -git clone https://github.com/PurpleI2P/android-ifaddrs.git -git clone https://github.com/PurpleI2P/OpenSSL-for-Android-Prebuilt.git -git clone https://github.com/PurpleI2P/MiniUPnP-for-Android-Prebuilt.git -``` -## Set libs in jni/Application.mk on 24 line: -``` -# change to your own -I2PD_LIBS_PATH = /home/user/i2pd/android/ -``` - -## compile apk file -gradle clean assembleRelease diff --git a/android/assets/addressbook/addresses.csv b/android/assets/addressbook/addresses.csv deleted file mode 100644 index 97a43dfc..00000000 --- a/android/assets/addressbook/addresses.csv +++ /dev/null @@ -1,693 +0,0 @@ -00.i2p,zmzpltxslembpaupg3srh4bbhv5txgh5jmms6sfj4hzsvlv3xugq -0ipfs.i2p,cdii3ou5mve5sfxyirs6kogt4tbvivk2d6o25awbcbazjrlhjeza -0xcc.i2p,gawouxh2sg32cluwlqsnpy3dwedvoqtfroi4evvdvm2pfv7tdadq -1.fcp.freenet.i2p,cuxbeputgxn75ak4nr7ltp7fjktnzl5sul3wstwnsoytbbpb4ixq -102chan.i2p,xxu3lso4h2rh6wmrxiou3ax7r7la7x6dhoepnku3jvrlwp35pefq -1st.i2p,rduua7bhest6rwsmmyttzssfdw3p4eu6bgl3mb4hin32qo3x5zfq -2.fcp.freenet.i2p,ndsznnipoeyapnsg3gj3yi2dzsqduxwalmujm5mzjm7e6x374tta -333.i2p,ctvfe2fimcsdfxmzmd42brnbf7ceenwrbroyjx3wzah5eudjyyza -55cancri.i2p,b4iqenefh2fr4xtuq6civfc6nhnia6e2yo36pf7vcgdvrwmh7xua -adab.i2p,pxjr6f2cig6v7v7ekam3smdnkqgmgseyy5cdwrozdyejm7jknkha -alice.i2p,iq26r2ls2qlkhbn62cvgb6a4iib7m5lkoulohdua5z6uvzlovjtq -always.i2p,wp43sdtuxum6gxbjvyeor35r5yvgtkp3dcu7dv47lx22zeb3relq -amazone.i2p,e6kq73lsxaeyiwpmykdbdo3uy4ppj64bl7y3viegp6mqrilqybqa -amiga.i2p,edy2xappzjjh7bxqounevji4wd2binqkv7gft4usrkan45xhbk5q -amobius.i2p,rj6432agdprun5baai2hj62xfhb4l75uvzl55dhj6z5zzoxv3htq -anarchistfaq.i2p,xosberjz2geveh5dcstztq5kwew6xx2brrqaorkjf2323bjzcd3q -animal.i2p,5iedafy32swqq4t2wcmjb4fvg3onscng7ct7wb237jkvrclaftla -anodex.i2p,25cb5kixhxm6i6c6wequrhi65mez4duc4l5qk6ictbik3tnxlu6a -anoncoin.i2p,nmi3loretkk4zbili32t2e5wyznwoxcsgzmd2z4ll3msgndyqpfa -anongw.i2p,owrnciwubb3f3dctvlmnaknb6tjdxtlzvv7klocb45mmhievdjhq -anonsfw.i2p,ir6hzi66izmvqx3usjl6br3nndkpazonlckrzt3gtltqcy5ralyq -anonymnet.i2p,77ouyl2ane7ffgydosd4ye42g67aomtc4jrusmi76lds5qonlffa -anonynanny.i2p,l2lnhq2dynnmf3m46tcbpcmbbn4kifjgt26go6n2hlapy4drhyja -anonyradio.i2p,cbobsax3rhoyjbk7ii2nd2fnl5bxh3x7bbearokyxgvmudn7o5bq -antipiracyagency.i2p,by4kcmklz7xnkai6ndfio47kts3rndm6wwleegtxghllimikdapq -antipiratbyran.i2p,y2qbhrvuciifbszaqqwxd5t75bomp7kzdqx4yxsrkaq542t75k3a -aosp.i2p,ly7raldsh2na2cgw5yvueyvqqjgx3vbqinecjrqdldgya76i2p2q -arc2.i2p,rnmosuwvtftfcrk5sk7zoyhyadh2g4dhe2mif5ml7qjisgkyw2na -archaicbinarybbs.i2p,t7o2tw36cffedgfr6kahewpkrntofnliuapji2e4rucl3os55epa -archiv.tutorials.i2p,lldr2miowq6353fxy44pnxfk37d6yn2f6kaivzecbmvvnnf5exyq -archive.i2p,x54d5st3dl6mwgfxj6raiekqkypo5pdvuex3n62szwju7hgefiyq -archive.syndie.i2p,abbyu5n3mh3nj7pe3b6byldrxswvva5ttxcafsnnseidanurq3kq -ardor-wallet.i2p,tm23k5ny3umhf6vf3kghnnwacli5zywq5wrr3xcqowbcofuyr4gq -ardvark.i2p,jcmw2sol3hruwc6rfinonx4e23pjkukkg7lg7xt7xb2gpiyyraiq -arf.i2p,o46lsq4u7udxg3qqlidrmpj4lb4nr7ldxmbb2x53nftndaeyxqeq -arkan.i2p,7o5y2lyyrjx5tf6l4fyumywui7msjv5azaaheatvw5sqj7mxbuvq -asciiwhite.i2p,itbzny5ktuenhjwjfqx3jravolhlj5wullhhr2m4qr6k2emnm5dq -aspnet.i2p,tsb7zqru57p4q2a7cto2lko4w5cg4lieglwm6t27c44fkphqmf2a -asylum.i2p,p45ejjw4p2q6nq3mzi6cm6ep35grtzshboidj2lojmrmic22noha -auchan.i2p,6vxz4yp3vhjwbkmxajj7wiikxafwujig63gkhjknbq6xh4rqpm5a -aum.i2p,ohdfneqxapfd3fwfbum4tut7z6k3rnr7rrguoxdrrfe2tln2kpbq -awup.i2p,v6g32duzrkacnrezfbll3pza5u37h7lnukr2wbsk6rqen6prhbga -b.i2p,272kt3gcx6wjurunzaiiwld7s5p4mpjewfubzmlcvw2vie62ckpq -bacardi.i2p,hivhnx2v47vh234c7coi2urj5cyvbl4bu3ypjr7snklortyqeljq -backup.i2p,kepphem42whle3rkfv26wcksmnegdbg6rdp6t3oobdkc2fmzrdkq -badfish.i2p,f6v26gyr4eipy3a7pi2voulw5qvob6dg7zij6xpo2ywbi5tvbu6a -badtoyz.i2p,3qz6ubtwlt2c4iasofjirkckq43u5fgkzyg7mlutcsym5gzhijna -barry.i2p,4kyahq53ol52n23l44tefgeaxqpp3cbb632t5k3umdvqcooevdzq -bash.i2p,s3wouoilbl3mrefxjhp4qoyujgok34e7y6vmpbu6hx4342ivqo4q -bdl.i2p,kp6fnuulenbjm7r26pfbmjcq3u7c7kvxeajodvgr5flcnskdgi5a -bdsm.i2p,pa7fxql5jljegg7j5tglhnnaod2sptq3gxvdn3ji6muqyhgn3poq -betaguru.i2p,d7cduwwhrcc2voameqfkvd66u3advu4jw2p6pysgax35vq6ovriq -beyond.i2p,uaicfqlrpjtitqbqkpfujanj5dollzfzee5glsuls67ekw6hlpoa -bible.i2p,pypz7ca24n3lyp4tm3kvncg3ltp3gd5pgnacc6zltoeffiyyegda -bible4u.i2p,xs6lr2g5jiaajtb3nkno2zmy34eipitrggooxb7wtey7uko7bqmq -bigbrother.i2p,tnxiifs6uticzyg6ac4lhv2l5luwi6xra7yngocro56ive5e4jsq -bitlox.i2p,lqw5khxcdntlv3u4vhn53upcqirplvnc4etjlmoytrzs66ytettq -bittorrent.i2p,pgax2vz572i4zsp6u6paox5xubmjrkqohq6g4hvlp6ruzzy56l5q -bk1k.i2p,nlyegmtyfffo5jfgg5h4dxxnlmqko2g36gpaye5a7vd3is35xxfq -bl.i2p,e73d6uhnfbylza6wqkhxejmqeyfb7thkzw35gn5ojmna64jzyk2a -black.i2p,sjwueu62qpe6dtv5b322k3f23fl4uz3w6qe6wcrwauiwpnymypfq -blackbox.i2p,7josyf7zjieoib3ovmr5a4dh5w64kmfh45lv5h436eljtgfegtqa -blackexchange.i2p,ztgr5kghkyn43fhhkuycroxgfti6cojo3vg4wdd3usqonyvrla5q -blog.curiosity.i2p,yiz6jec5k7ccxdgnh7msqa4ze52bqqmf6rpq6bqdyojra2erd4ta -blog.polecat.i2p,orlccceubewvxo3fbdyydq6e4uuidbs4xd5u2gyqbculnowo3ehq -blog.tinlans.i2p,ylkch2nkrwehakx4z6wiyjbeqwlgasknukdkex6r6yq4xusrjnda -bluebeam.i2p,lvxp3cbcfwtol57d5pmrsck32t7ndutlxubjb4smaf32bynhlk6a -blueheron.i2p,anfb5jrhixjmvkyxctqwkezqer7dbob22wge2bh6wsewbhgnftfa -bnc.i2p,fr4zbcygmx2vdct6nrabakfys4b4derm6jqu2ovppkgqillvlqxa -bob.i2p,i76m7dwm5hnapljendbie6fc5y3mjlkdlduo3tvbwiwmvhxbpyaa -bobcat.i2p,ftuukjtcquuvppt726w37boit7gp5hf2yxwfop35prx3grzzzxlq -bobthebuilder.i2p,qlahgthqhr4uojkkwahnper2cl3ro5f5gtzy5t4lzapbzo4osy6q -boerse.i2p,7633w56hd53sesr6b532r5qlbdnvyl5bnvama6ign6xryaxol4rq -bofh.i2p,auvuinzogu6gc4pwsgbjijuszxgcjygciu2wy53pfz7mo5nfpc5a -boing.i2p,bgsq33bh74j66hn4oh7oovlvuhhdyw22lq2qi2fnv3jyh2ryap3a -books.manveru.i2p,eb2tisc2vr5jvjqrixrozcujiucwxg4m722stxwho5666ipl67zq -bote.i2p,bhjhc3lsdqzoyhxwzyrd63kvyg4br6n2337d74blyintae66mr2a -bozo.i2p,7a2d23h6htprhzrol36vgwgklsbqrnuya4tbaaaspmaeaodt57iq -brittanyworld.i2p,e76umhhic3474sdxiuax25ixyfg7y3z7oojj4fmxvhgv3ruet6aa -bt.i2p,uhkuu54pg47zey76h45tnvsdtpkf5bthbtrjgnaloi5m54h4hlaq -bugfuzz.i2p,ubszn4gsf22vga67rvzzlg4qj2bfcq6o52fmxz46xruawqm6z7rq -burntout.i2p,lkep3fd7tjvxrs25crr2c3jy7xm4s7bqiua5r327zgpw37sgyerq -bytepay.i2p,7amc4ztwkzu3cgsaaaw3223ohuihn5hlsqc6gpf2rxdyptdkyugq -ca.i2pd.i2p,u5safmawcxj5vlrdtqrsqbsndkr5cfenpicgg5euu4xqm73yicba -cases.i2p,kmpmk2fmineaiwublteqlifg4fkmewnhmxqlcgg7qwecz6daj43a -cathugger.i2p,vq43xjjcnejqpzfprws5qzrea2siieshu4tglpdepql2w3w3bpba -cbs.i2p,u3lp7wazvq6opodzwjg5sc5w5kwxehmxd4wcdpt4s4j2k4dx4apq -cerapadus.i2p,zroed2cxga5zeuu6rcvmp2yfi77nzduw7yhdplbeuqkuyxwbrzaq -cerebrum.i2p,u5gtsfn267udwfh2uq35jiabkufifvcbgv456zz34cydutsiw2eq -cgan.i2p,43z65gdr52xe3fxmkumwp3dzhedu4tu4rdtzr24hz5b4awcpfbqa -chat.i2p,ollpwnp6yidc3obbb3famgt6rw5jg5w3k3a6z7hhaegj6gcohiuq -chess.fillament.i2p,tv6wbanei647yf5bie4dhg2wmybkjurezlpdfwftc5ajqlfswwya -chess.i2p,sbnoqznp5yzxals3vs6nzyqaj2fetvonys4e3b3x4ktmfeus54sa -china.i2p,wit6f2zx6dtuqqze6nhbykrds3idppfirxvhf2f7ydqoqf4xdzeq -chitanka.i2p,u4s3jneepk3akoez46kqiwikoezi6zyj2ibjkjyi4uuvsbcojzba -ciaran.i2p,2r3645eete6xwbfu62ogonudcrcgqq25sbnij5v4geru74yrscna -ciphercraft.i2p,7s5pkqbpbfdkxtwuu2e2iwstbikyewvvscy76lij4x5pfbygbjca -closedshop.i2p,6fg67mbw2okopzyonsck4bsy3cy7l2fame56uiysr2cezhjhzdbq -cneal.i2p,g4za73ffigv3ht4jnhzy4dae52djjq7lqcguqsfg3w5cxzqm7nba -co.i2p,3mvo5eifcwplcsoubtvqkzdahwo2sdhfygfdde7lj2glybk4q22q -codevoid.i2p,2mukrqwtinsw27uoejtrz74zxtilyhnnfdyso7j3yo6vaa6nzlaa -colombo-bt.i2p,cyr75zgiu2uuzap5zeosforbgvpfbqos2g6spe4qfulvzpyhnzxa -complication.i2p,x2av6rwj5e5tp64yhdmifdyleo4wblw4ncrrcrabxwscuevpdv7a -comwiz.i2p,6p7zqfotzbd66etl5xqy3p6xvr5ijucru3am2xqa7wmnj6vf3djq -confessions.i2p,lh5vitshufxpmyr44zgyymebo5elc42eda7pxvn5lmtes47c7rxa -connelly.i2p,5yrris3nigb3fapvzrlrcaew6cdmzdknzvgrc7y2jpn3ntqurweq -costeira.i2p,abhty5xlmnyab2kqdxcd56352kcescxoux3p6dbqdrghggyygnxa -cowsay.i2p,q4ghzfpah4ffvm3bhc6fdkrznk5f6jxfjm2daytlparznai5d54q -crstrack.i2p,mm3zx3besctrx6peq5wzzueil237jdgscuvn5ugwilxrwzyuajja -crypthost.i2p,zywhrxtnkjc3rxxvxbocom7ml4hnutomgtuvqrwyf3rhuupnq5ca -crypto.i2p,vffax5jzewwv6pfim55hvhqyynafkygdalvzoqd74lkib3hla3ta -cryptostorm.i2p,mlu7mswyirjf53usqq7gyamvqc6rqihezgdbevov3dkxmkfo57aq -curiosity.i2p,eomeif4xrykxlzhawc3icdilje5iammijos6tyizwhrfh3j7qdvq -cvs.i2p,yd6k7dzpsa2tnlzx4q7xqkmd4qsjk5xk5hbiqpiarwbeyvxaxgba -danwin1210.i2p,eoqdf4no5dxn4tw5n256kkd4lzz3uk4p47np4mepsykpsdzrnvba -darknetnow.i2p,gkx3o5fy7mv7l4psqqnhp35d5iun7rt3soci6ylf3rgb7a5a655q -darknut.i2p,2mk37gtvpk2i63o6vl7vna4dr46rqexxetupgn5efuuins7x3qya -darkrealm.i2p,gbh4eerxdsph7etxsxznfhvmuiz54trlkenakqep343u4xcoekzq -darrob.i2p,hz2xhtpeo6btgiwi6od4qj2575ml5o2246rd5orarruyjhd63zja -dashninja.i2p,dzjzoefy7fx57h5xkdknikvfv3ckbxu2bx5wryn6taud343g2jma -davidkra.i2p,nq7ca2egm563nir3xegfv52ocgmxstpz56droji4jgnzfoosk45a -dcherukhin.i2p,qa4boq364ndjdgow4kadycr5vvch7hofzblcqangh3nobzvyew7a -de-ebook-archiv.i2p,6mhurvyn6b6j6xa4a3wpuz7ovpsejbuncvyl6rnhepasfgdgmn7q -de-ebooks.i2p,epqdyuuhtydkg5muwwq47n7jvr66pq4jheve7ky5euls6klzwuyq -dead.i2p,7ko27dxvicr2sezvykkrfiktlghx5y5onup3f2bas5ipocy6ibvq -deadgod.i2p,63bveyh7wefb44hlia7wtxxb3jal3r67thd6jekmwrtq4ulaaksa -debian-multimedia.i2p,cylxxz2y35x6cvyrl57wu3brckurtexatyi2i5awz3eeamqwjspq -decadence.i2p,pw5ys7k2grjb5myydpv6ohikm6nna7y6u2dro44i4rucgulu3ikq -deepwebradio.i2p,2nait2gdeozkgf6gyhzjfij6mwldwkxxwcvtxobb4b5q5cvtm5la -def2.i2p,cepsrw27kdegwo7ihzouwvgcvw2obswwjs23ollgj7hk2yrce3da -def3.i2p,xbf3ots2purqun7orn72ypkpjmrzbfrkj3u654zfe77hbrbow6la -def4.i2p,yyzdq4fwwmnlojp23drfpfqujln2vcjozjrfzfeuriuqzdq7g4mq -deploy.i2p,ujzspsqkbz5z272eozsrdv4ukl434h3fuliwrfxxnab74jmd7e6a -det.i2p,y6d4fs3rpqrctuv77ltfajf5m4tl4kzcu7rtwhxgiohylfxxow4q -detonate.i2p,nykapdsjjswdkjov7x3jzslhg4ig3cpkhmshxqzijuhbisx25jja -dev.i2p,cfscxpnm3w3qxnlv3oikewxm4qrot4u6dwp52ec2iuo6m7xb5mna -di.i2p,3irnooyt5spqiem66upksabez4f3yyrvvjwkmwyzlbealg64mgxa -diasporg.i2p,edvccoobtjukjgw2os5eetywanbb2mpag5aknkrpia5qx2koksua -diftracker.i2p,m4mer767ipj7mq6l7gdrmrq37yzvsj3kzezd7n7nsfuctntjseka -dm.i2p,heysbdivyeugdbggpscco5wje3dsvwgcpp5ot4sopooebnmiqvtq -docs.i2p,ato242wckzs4eaawlr5matzxudt6t5enw73e4p6r3wajwkxsm3za -docs.i2p2.i2p,las5l45ulwwf5i72nht6vk33sfkidcpr2okpf5b6mvgbk3a2ujna -downloads.legion.i2p,xpmxdpuuptlekyhs7mmdwkvry7h2jbvpqpzsijqe3a5ctxgodesq -dox.i2p,vk27cjdrtegfdnrjqutebgxkpyrfj42trdfbsupl5zn2kp34wb3a -dropbox.i2p,omax2s5n4mzvymidpuxp2yqknf23asvu54uon6cxl6gdrlblnuiq -duck.i2p,3u2mqm3mvcyc27yliky3xnr4khpgfd4eeadhwwjneaqhj25a65ua -dumpteam.i2p,2fwlpuouwxlk2nj4xklvm43m52tqyhqnu2fcfiuv7clvf3wd5nwa -dust.i2p,u6xgh6zhhhvdvefbqksfljfs3nyjvqcrmyamp5bryz5f4injmniq -dvdr-core.i2p,fg6l2ej6qrk5rkyfzdptxx5xkcm4kvdla4gg2tun7z7fm5cxxw5q -dyad.i2p,7n2ljphvp2dep7imoujvydxp4myuxfld3axwfgcny5xc5x6jj6ka -e-reading.i2p,z54dnry6rxtmzcg7e6y3qtsig5yf5fmehuvakcg5wnuahx3iafuq -easygpg2.i2p,bwxry5alzx5ihgrd3glah4eotddblzhalvpheppnw4zcajzqoora -eboochka.i2p,ou7g64d5in4sugv5fgmmzwnunuw5hloixio7puthmrvrkwrp6egq -ebooks.i2p,bvpy6xf6ivyws6mshhqmdmr36pruh2hvoceznzeag52mpu647nzq -echelon.i2p,afvtspvugtd32rsalxircjglh3fhcjzk7gxrm3gw4s2yrpvzk6wq -echo.baffled.i2p,bfr3lyicr72psxvt2umqfb562rtex66w6q3hi3tktzkoyane2iha -eco.i2p,2dq2o5h6c6a674qaduipp55mid5iktumjbswuwmpsrcqaeowdvwa -eddysblog.i2p,ieac3ub4g5sy3wuhsbqfembnpp7f3a37xgcx537ytzsmgfzexnbq -edge.i2p,aknsl5wmzjmwyc4wxutfdwy2w5vgd3vcx52mqx647hcgvyurmqta -eepdot.i2p,t6edyotbxmxvy56fofdvmragvsj65te2gkhvzv5qnblicutyvgoa -eepshare-project.i2p,sn26kom4qyuzouppv4lwnk6bqabdydcegtrilybviibwiq2s4nfq -eepsites.i2p,isskhl4ak3g7qevrarlmblddgr4ugnn3ckalwpjcvxafk5rjgypq -elf.i2p,duz6ey27ohpcp3llylklzdb63lylolzcixad6bh7rt5tkq42qqpa -elgoog.i2p,z6hrgkg2ajmuzlrddjlffrgctx7x7fkipm6c4hdzmohyn5wkr4ya -ems.i2p,734zw4jsegdf55zl3z6s22tqkbxcghu4qvk6q2wevjfmx7xhbn6q -epub-eepsite.i2p,yxvzjwd4vin6pnjauekdufh7lxaijal3kqe2bhakuf47g5zkb6xa -es.hiddenanswers.i2p,cw7ge5ey4ekp5iep2kaw6j54boebtqytpcbnvio2bfpccd5ejzfa -eschaton.i2p,xe75f5hzmrq6rkhsef2geslmi2v2yfngdiysmlmxvh7b4pyyjk4q -esuwiki.i2p,cwxuiwcpymb72vm5vluba66ofhugyf5qeevvwo7e2fqrxl243coa -evil.i2p,ljfl7cujtmxfffcydq77pgkqfxhgbikbc6qxjgkvcpn4wzd73a4a -evilchat.i2p,s5b7l3hzs3ea535vqc5qe2ufnutyxzd63ke5hdvnhz24ltp3pjla -evilgit.i2p,mx5vyoqhg77yuhthwznsxrepjsemq4uwitx4lxdzetk36ryl5rla -exch.i2p,vsyjsbbf2pyggtilpqwqnhgcc7mymjxblamarmxe5hmbxaxvcndq -exchange.gostcoin.i2p,n33uthzyqsbozl2qh5zii2bq2nnvbz6g6c4ew3mwp6uukk6u7wva -exchanged.i2p,ylmulgfskl6uiwac4hw4ecwqdzd3oxtwaemzj25zc6k5q4rkexra -exitpoint.i2p,5zmjurq3enudcenegnxu5hqmfmayz4lxvnik6ulch4xssa2ithta -exotrack.i2p,blbgywsjubw3d2zih2giokakhe3o2cko7jtte4risb3hohbcoyva -explorer.gostcoin.i2p,ktoacmumifddtqdw6ewns3szxths2hq2fat2o7xnwq4y3auga3za -fa.i2p,6n6p3aj6xqhevfojj36dixwbl4reopkhymxmatz7ai5sroh75rka -falafel.i2p,djpn5cbcgmpumwcriuzqistbae66txca2j4apjd2xesfgb7r5zmq -false.i2p,77mpz4z6s4eenjexleclqb36uxvqjtztqikjfqa4sovojh6gwwha -false2.i2p,j5i2tfumh3ti5sdtafwzzbpupmlcbg5drysfay2kxbdpsaljrosa -fantasy-worlds.i2p,62a4xcyyhvfrcq2bkckb7ia37fmrssrgx467tlkxp32fjpq577wq -fcp.entropy.i2p,de6h6ti5z3mcbdcwucu45vplikqyoeddsu3rqy7s2zy5i47j3peq -fcp.i2p,ndsznnipoeyapnsg3gj3yi2dzsqduxwalmujm5mzjm7e6x374tta -fedo.i2p,zoamh7e3k2vf2g6pfy46ho4taujk2f4mxqqsv3gbg554fxbvyfqq -feedspace.i2p,kvtnpx4jylgeyojfhix4x462sqn5uork3roml4sfzotkxx62i4wa -ferret.i2p,kkqie5qmja7bkf3iad4zxhrdarwj7kbrx2m3etn5kmba3shgwj4q -fido.r4sas.i2p,i522xmu63hfbaw2k54cthffcoqmeao6urjyq3jg4hddf6wf57p3q -fifi4all.i2p,v2stz6bsot7sbjzix5tky5dm5ej7gidmjnkvzqjju5xvz5sz6fwa -files.hypercubus.i2p,qfglq25jwieszgyt7muz6dambzqsrmjhhszygzzx2ttubc77sffa -files.i2p,w2sy74xe6oqnuz6sfh5fhkzu7boholgzd5f3anhj47srxwpj2vaa -files.nickster.i2p,yil7dp2hg5pbqyovsiwb2ig6zjsq4tize3fnwemmqdrr6j5itdtq -fillament.i2p,udj2kiino4cylstsj4edpz2jsls77e32jvffn2a4knjn4222s2oq -firerabbit.i2p,awqh7n3wskzl3epyvkdwgarmfybsncm7vye6psg4tpkmplh3mj2q -flibs.i2p,ocdm33e3h5tdml3yyholj4objdwsrhlugfqjnqgdkslmgdzb6b3a -flibusta.i2p,zmw2cyw2vj7f6obx3msmdvdepdhnw2ctc4okza2zjxlukkdfckhq -flipkick.i2p,aso5rzc4ym6g2bcbxjy2n573bmbenkjawva2jg7fhyqhwtwgu6lq -flock.i2p,hflpi33ko5bi2655lx6bpzstdnjqgzrz23inovqjx5zpntyzyb3q -floureszination.i2p,vitpvfb25sikuk3crgcvtcdi7hajxnnq2t6weay3no7ulur2wwwq -forum.fr.i2p,onvelkowkbuwrglhw2cnocggvbdudi75sll5mfirde3cbopjqivq -forum.i2p,33pebl3dijgihcdxxuxm27m3m4rgldi5didiqmjqjtg4q6fla6ya -forum.rus.i2p,zd37rfivydhkiyvau27qxwzmerlzbqtthsa5ohtcww62zrygjaga -forums.i2p,tmlxlzag7lmkgwf6g2msygby3qttxvm6ixlfkq6s6cpgwubp33ya -fproxy.i2p,keknios3gm6kh6onez6x2bm2t7stv54oanvltuagphgdfjdw5e2a -fproxy.tino.i2p,fpaituvuvyxp6xdjnv3i27alnj2ifzcvqdweqb6yj5uybotzvyha -fproxy2.i2p,r4lgw4wmza25g7j5fjocjbwzwthfg4ymcbm52ref3hh2hogskcza -fr.i2p,ia6xlsnygorllplx2owokahtrkospukvsmysz7i7bzw3vejc4hdq -freeciv.nightblade.i2p,rluupsgxbvw5t7jno3apyzlrdirjkljft4gdoy4mxxh4fmd4xzta -freedomarchives.i2p,4ck6oliqfjz3sccpya2q4rh5xkj5xdxkqs76ieml37537nfhwd2q -freedomforum.i2p,abzmusjcm3p3llj4z7b5kkkexpsxcnsylikokouk5txfim3evqua -freefallheavens.i2p,giqnkltyugfmsb4ot5ywpvf3ievuswfurk6bjie4hxi2hh2axajq -freenet.eco.i2p,2kf7ovb35ztqkrurkm76y34jfpwi6go25xj7peznnmxrl7aieo7a -freshcoffee.i2p,sscuukigp6alcb3ylhkcugoejjfw5jqgtqbsbafw4hyku42lgc3q -frooze.i2p,m6ofa5dmyse4b4jg7kfmluuuc4pw5jqu6zh4qnboin4vropxepja -frosk.i2p,63naq7zb3hvbcppj2ng7qwf6ztusp4kwpyrzbt4ptafcdbu4pfjq -frostmirror.i2p,ycz3imuz6yte2zhlapmsm3bsvc46senvc2jxzwsbfdct5c72qulq -fs.i2p,ah4r4vzunzfa67atljlbrdgtg3zak5esh7ablpm6xno6fhqij35q -fsoc.i2p,vaqc4jm2trq7lx2kkglve7rkzxhhaptcwwl32uicx4ehf5k3hx6q -galen.i2p,4weo7zkxscxbcouiqx4mlnb35uwl2lromikzk33er3fljktyvi2q -gaming.i2p,rfxberwod6st2zc6gblqswxjl57nucgc3xrbwss43pe3dvqqzj4q -garden.i2p,qkk2dqx6nocycgt3vinsoc76cxkb4jreybcpgz3fcps2dbe4rowq -gaytorrents.i2p,fnggbr2t2aulr6rvlo4aehotx6wecfob7u3k2nxsnvtm4xex424q -general.i2p,5fklrsztdqpl3hkkwwrrw2rdowrq7wwhwb6h7avvk4fhansp4vvq -gernika.i2p,wpzqv3lxpecdsvcaadvbmrhhwlc7kp4n2mijdv2qjw3zr3ye232a -ginnegappen.i2p,kbhfkzx5jeqhfgss4xixnf4cb3jpuo432l3hxc32feelcmnr3yja -git-ssh.crypthost.i2p,llcp7jvz3hgtt3yzkdgjolwobisgvhv4xqa5a4oddejllyozur5a -git.crypthost.i2p,7frihhdcisdcyrzdbax6jzvx5gvtgwsm7m6kcem2tlaw4jtahbqa -git.psi.i2p,em763732l4b7b7zhaolctpt6wewwr7zw3nsxfchr6qmceizzmgpa -git.repo.i2p,vsd2vtgtuua2vwqsal2mpmxm2b2cpn3qzmqjoeumrrw2p4aot7uq -git.volatile.i2p,gwqdodo2stgwgwusekxpkh3hbtph5jjc3kovmov2e2fbfdxg3woq -glog.i2p,ciaqmqmd2wnws3hcpyboqymauyz4dbwmkb3gm2eckklgvdca4rgq -gloinsblog.i2p,zqazjq6ttjtbf2psrtmmjthjeuxaubi742ujrk2eptcsaoam4k7a -go.i2p,ll6q4lsirhwkln4dqxwqkh2xu4mu3jiy546b4uhe4fypyb4vvx2q -gonzo2000.i2p,nogsv7okydhbvrewv6hb4xdojncvhkusnyib4lglluc4uw67a37a -google.i2p,4p3ajq4cotnflmuv7fhef3ptop5qpm3uzzgp5bahxif3nc4w3ffq -gostcoin.i2p,4gzcllfxktrqzv3uys5k4vgkzbth4gqednwhfpt755yivm3davuq -gott.i2p,dqows7dpftxxl2bd4bgcpkck6knrysdun6mtqy4ms5dxobbvg3ja -greenflog.i2p,zny5ftmhzxulxzyczmeat53qjnue2xtqv2clisc7dg76lwfceecq -gstbtc.i2p,n33uthzyqsbozl2qh5zii2bq2nnvbz6g6c4ew3mwp6uukk6u7wva -gusion.i2p,4qyfdhizjixe2psu7wcvqufix5wlijocehpb2futurcmlhlktrta -guttersnipe.i2p,kizkhzes2bzp45widihremo6geepfk7dl6juourkvzuvlc6y3spq -hack8.i2p,un63fgjgi3auvi7zscznwqfol7ka4johgthvqf635mg3fefsjgpq -hagen.i2p,e2t6rqd2ysbvs53t5nnaf7drllkgk6kfriq3lfuz6mip6xfg644q -heisenberg.i2p,jz4quyw7zt63tmw65jfp76fblwadjss4iyi4puqdg3dye7oaqlvq -heligoland.i2p,gzrjm62ektpqjfsem3r3kwvg6zpjvvhvpjvwfxkm2ay4zu7sp6oq -hidden.i2p,iqodhhqo473qv5gwhjcs2bsrbhlqtpzgpnuumpastfiyhuwb2kyq -hiddenanswers.i2p,kj2kbzt27naifij4ki6bklsa2qfewxnkzbkgvximr4ecm7y4ojdq -hiddenbooru.i2p,zma5du344hy2ip5xcu6xmt4c7dgibnlv5jm4c2fre5nxv44sln3q -hiddenchan.i2p,6y4tltjdgqwfdcz6tqwc7dxhhuradop2vejatisu64nwjzh5tuwa -hiddengate.i2p,rvblcu54jvkkfffp3fobhunsvpgfc6546crcgzielzwe2s5m5hbq -home.duck.i2p,jsh7yfvm2t5urdcnmfzdy4n6vegqskdtlwem53chgxli4ipfmuma -hopekiller.i2p,kcaelbgsvrkiwpx36b4wxofebrl3njx7rgm5amzfmqwbomt44cxa -hotline.i2p,6cczi27iuxkm3aivazaemzltdqgh42ljzurqp43uclbz2lid2uqq -hq.postman.i2p,27ivgyi2xhbwjyqmnx3ufjvc2slg6mv7767hxct74cfwzksjemaq -http.entropy.i2p,ytu7kz5bdoc26nkpw2hajwt3q7n5rcbg2eokyefhmkxmmslimbdq -human.i2p,nrtcelq3humyfvoxmzmngpka6tmyifweouku5mbi5av4lc43hzaa -i2host.i2p,awdf3nnmxxup5q2i6dobhozgcbir7fxpccejwruqcde2ptld443q -i2jump.i2p,633kqgmwzzu6vhkevwvbf2pfyejt3gkes34i6upa4og57fgdfcxa -i2p-bt.postman.i2p,jeudwnx7mekjcowpqo6xpkwn7263c57y5piurrjrdzinjziu4fla -i2p-epub-eepsite.i2p,yxvzjwd4vin6pnjauekdufh7lxaijal3kqe2bhakuf47g5zkb6xa -i2p-javadocs.i2p,icgmr6hhjudl4yxhtuq4pxvss2pzypwddzowajgs5rdz6f55novq -i2p-projekt.i2p,udhdrtrcetjm5sxzskjyr5ztpeszydbh4dpl3pl4utgqqw2v4jna -i2pbote.i2p,tjgidoycrw6s3guetge3kvrvynppqjmvqsosmtbmgqasa6vmsf6a -i2pbuggenie.i2p,bioq5jbcnfopqwvk7qssaxcl7avzeta6mu72jmxjeowflpcrhf6q -i2pchan.i2p,tduxyvfs7fzi26znvph3mu2d2ewaess7emomfci22wvownajphuq -i2pd.i2p,4bpcp4fmvyr46vb4kqjvtxlst6puz4r3dld24umooiy5mesxzspa -i2pdocs.str4d.i2p,yfvbtrhjac3jutdsqzugog6mbz3jtyhpwovrt2mqc5mzv534y7cq -i2peek-a-boo.i2p,qgv64klyy4tgk4ranaznet5sjgi7ccsrawtjx3j5tvekvvfl67aa -i2pforum.i2p,tmipbl5d7ctnz3cib4yd2yivlrssrtpmuuzyqdpqkelzmnqllhda -i2pjump.i2p,2mwcgdjvfvd3xwumzqzqntual3l57h3zo7lwdmkjboeraudpkyka -i2plugins.i2p,bb63kmnmbpitsdu45ez54kmogvvljn3yudksurcxiyq7dn5abt7a -i2pmetrics.i2p,v65p4czypwxrn35zlrfkar2w77vr42acd7gbszegsrqq4u7sip5a -i2pnews.i2p,tc73n4kivdroccekirco7rhgxdg5f3cjvbaapabupeyzrqwv5guq -i2podisy.i2p,3c2jzypzjpxuq2ncr3wn3swn5d4isxlulqgccb6oq5f6zylcrvcq -i2push.i2p,mabdiml4busx53hjh4el5wlyn4go5mgji2dxsfyelagi4v5mzjxq -i2pwiki.i2p,nrbnshsndzb6homcipymkkngngw4s6twediqottzqdfyvrvjw3pq -iamevil.i2p,au7jhslyt4cxkjp365bvqvend3hhykrrhbohtjqlgoqrlijbezja -icu812.i2p,bxgqwfsnr3bgnr6adn62anjcin5nuthqglotb3wn3dgynsfofeva -id3nt.i2p,ufuqdzsxltiz224vq5gnuslt3a3t72dhy5kq6i2xway53m6pzv6q -identiguy.i2p,3mzmrus2oron5fxptw7hw2puho3bnqmw2hqy7nw64dsrrjwdilva -ilcosmista.i2p,6u2rfuq3cyeb7ytjzjxgbfa73ipzpzen5wx3tihyast2f2oeo24q -ilita.i2p,isxls447iuumsb35pq5r3di6xrxr2igugvshqwhi5hj5gvhwvqba -illuminati.i2p,syi6jakreatlm2z22u76izyqvbm4yi4yj7hr7jb63lgru5yhwwla -imhotep.i2p,qegmmhy52bdes2wqot4kfyqyg7xnxm5jzbafdb42rfoafadj2q7a -in.i2p,r5vbv2akbp6txy5amkftia757klgdy44s6cglqhmstpg65xycyjq -infosecurity.i2p,v3gkh5kqzawn2l3uzhw6xnszsh6w3nztjmlwil7p4kyrwrsm2dba -infoserver.i2p,jd3agbakybnhfvkeoxrx7t33iln6suzomv3kxkxf77j7rkonch6q -inproxy.tino.i2p,ex5yf6eqqmjkrzxnkn6cgvefgne24qxsskqnpmarmajoit43pgma -inr.i2p,joajgazyztfssty4w2on5oaqksz6tqoxbduy553y34mf4byv6gpq -instantexchange.i2p,5wiyndm44bysev22kxvczxt37p6o6qroiqykytrvn2yzi55aqfxq -investigaciones.i2p,n7hqd4asxrdwf3zwo7rzv27y2qkcfmakmz6mjar6aw6hlc4c7mha -invisible-internet.i2p,jnpykdpp46zenz4p64eb3opadl5g42dls3rurk2cvq6a3g3rvbvq -io.i2p,tx22i6crnorzuti3x6va4mijsbhoqswy2cfdxjbvprgsq4eerg7q -irc.00.i2p,bvcja52pppgfspp2ueuipoysjnvvoyblz2h6smpxcmanjquogirq -irc.arcturus.i2p,5nywlbn35p2nwsymwpfmicu6fxono6g64vwusxbsvmm2qwz6vupq -irc.baffled.i2p,5zmtoopscym6qagkvpgyn7jnkp6dwnfai745xevkxlou77c2fsjq -irc.carambar.i2p,hxzbpivxqxy6nuae4t6fnkhcgnhs4c72vt6mmsqfmfhrkn2ca6gq -irc.cerapadus.i2p,e4ckznxcxvgyikzjmjsu72i2dbj2d76ogexyukklbjvpcnhp6zzq -irc.dg.i2p,fvp3pkcw4uvijqabwtekcdilklp73gyasuek67wdcs2mucep4caq -irc.duck.i2p,chdpmm4gxffyn24xx5dhxvfd5httu42i5gtoe6cctjlsf4mbofeq -irc.echelon.i2p,ez2czsvej5p3z5bquue5q3thujcodfze7ptybctqhnqc7hms5uzq -irc.freshcoffee.i2p,ubiu2ehtfnrleemgpzsqkahwnvzuaifqa3u4wmaz5maaisd5ycfa -irc.i2p,l3ohmm4ccxvyuxuajeaddiptci5lsrnxtvtyq7iohphrt3oj2evq -irc.ilita.i2p,5xeoyfvtddmo5k3kxzv7b3d5risil6333ntqrr3yvx3yubz5tk3a -irc.ircbnc.i2p,4rqcsqd7xif6r4v55blqvmqu5er6due4eyene3mjorfkts4o3rxa -irc.killyourtv.i2p,wre4majmg2vnbi6id27et7yw6lnpf56wkbm6ftnlwpvxnktq73hq -irc.nickster.i2p,dhq3fhd5scw3jqhj5ge7kqfpprfolcgxfjbaw24obohaiqjtdu7a -irc.orz.i2p,7gifacog4aoons3syybojbbnyqqaaqijhngrehn2xlq3eucuyjcq -irc.postman.i2p,mpvr7qmek2yz2ekegp5rur573z7e77vp3xqt2lfbco5i6nkfppcq -irc.r4sas.i2p,hodhusp73gltozgrnianlbploon3rrvhrzfn5mf2g46o7aaau5la -ircssl.cerapadus.i2p,4x2i745i4w52ss3he2kse6tzwt64pr62yvrcb72lgvrb63fup6ea -irongeeks.i2p,ecduxoion5uc5hnvzjxff6iiwhdwph6gse3dknyvlo7e6gaeho7a -iscofsi.i2p,enjgdxs4um2dmhdb2ajff2egrdijkjji3g47m6unb74swbrqsddq -isotoxin.i2p,wue3ycaccf4texikza3fh6p5yrmtgnooisuypnepo5mo67lmpcqq -itemname.i2p,o35ut7hgywy35okvgkjkv3ufzv2ejv4luap4oytwbyy2jqy6u4vq -ivorytower.i2p,fpwrfvidfexsz7dspofkwtkmmizm7lyralfz5kvykffk7gubvxsq -j.i2p,kjxvohlsf5sdrzxzfcrmvquccnoevi6ytbl63mstsru5wt2dx3ea -jabber-2.i2p,pvnmzgemetkwcuvt45omgowmeznwk5xw3nc3ygeoz7yekqxy57na -jabber.duck.i2p,rhdzvvzraqzzm67zpyegb7knpfrjeffitixqzeyymdoz56uh2rtq -jake.i2p,v2axvy6pqefnla7gun5fmqs4lqe4xfyqovgzcundhxrpcdvfd7cq -jar.i2p,2fthkmujup3xiiu3yple24n6g4emzdiiimbuqwvpdddtsr3c4nrq -jazzy.i2p,ha5c3zafwkt6mwqwjcf4oqwvbwz473652ljjadiwrj4gfkfkjofa -jdot.i2p,kw4jr5qw4bhnj33avkwankjdh3zi7wtahlmgkjwvsv2isskkzgpq -jhor.i2p,c6rnm7oemydhuwzmhwwwxphkzanez5rnn7fkcs3lpgu6gkgtssoa -jikx.i2p,aazr55itvyns4lwppvx5njyx5tjdwemw4w6jbmpegdunznod2ieq -jisko.i2p,jxgfvr663uhr6m65hrgkscshysfshkq32ywdubc4ed7zda3e2pca -jmg.i2p,oglpnq7zungdukmk6gk5fzj5jp6wibuoihqgks453wztrwos4ggq -jnymo.i2p,nbfplxgykyfutyadlfko2rmizdsxox2pee2ahboj5mju4s3putda -jrandom.dev.i2p,htynimemonyzqmn76gworxyfkmqtsa7zcprbrd3i5cxqqm75tuzq -jrandom.i2p,dqows7dpftxxl2bd4bgcpkck6knrysdun6mtqy4ms5dxobbvg3ja -jrrecology.i2p,qxi24gpbum3w3kesuxvheyu3p5u5o6tuvoypaolub2gnvbld57xq -jwebcache.i2p,xdffxnxtjd6ji2zig3cgva7igvl2tiapyjoc7ylbzwqhxudbmvfa -k1773r.i2p,zam7u6vslhemddz347uusuzjdk5wma4h5hcmcqlng4ybbpdbjhnq -kaji.i2p,z5ic7gvm2k4doczphtrnrspl2w5sfbss2de4z3ihjijhtjw67ydq -kaji2.i2p,4lscgc6napekfx7ay5fdcjofeja4fnl7tqcd3fek63t4saavur2a -keys.echelon.i2p,mwfpkdmjur5ytq4og36ym3ychinv36b2a57f4rmgqmtrwepq3fva -keys.i2p,6qv4x7ltaxckd4vbay5s4ntqqflq4efk6oke2d5yzicqrmk443ba -keyserver.sigterm.i2p,isoxvnflrdn7cm76yjlfg5tbcugoito2hur7eidbqmo33xmwz5ga -killyourtv.i2p,aululz24ugumppq56jsaw3d7mkbmcgo7dl2lgeanvpniyk2cbrda -knijka.i2p,knjkodsakcxihwk5w5new76hibywia5zqcgoqgjttzsausnd22oa -knotwork.i2p,2yocdbcjiyfaqgxb4l6oenrrrrie6nydgmbnbfulqg7cik6bozxq -kohaar.i2p,qchpjehbhqjbxdo7w3m55jbkrtsneb7oqoxcr24qttiq6j5g3z5q -krabs.i2p,3yamyk5bgfgovg6zpvtvpdjk37ivjj2wog2w7wha5agzgxxkqaca -kuroneko.i2p,wbit2huhhwlyqp2j4undccuyrodh6qcmzdeyuaoy5o4ym7g5gdgq -kycklingar.i2p,gctswdhp4447yibxfbqg3uq2bvx63qjeqnaoaux75zw73leakyva -lazyguy.i2p,ia6xlsnygorllplx2owokahtrkospukvsmysz7i7bzw3vejc4hdq -legion.i2p,5oirascyhwfy2tr2horw6mixozsre7z6s7jfq7qbnj523q3bkebq -legwork.i2p,cuss2sgthm5wfipnnztrjdvtaczb22hnmr2ohnaqqqz3jf6ubf3a -lenta.i2p,nevfjzoo3eeef3lbj2nqsuwj5qh3veiztiw6gzeu2eokcowns3ra -libertor.i2p,7gajvk4dnnob6wlkoo2zcws7nor3gunvoi7ofalcps5lc76wruuq -library.i2p,brqqaq44vbeagesj5o3sxcnkc5yivkwouafyxa77ciu7l644ei2a -lifebox.i2p,pyqjnycm55cuxow22voqj62qysrjdnb6nbyladaiaiirqi7vp2yq -linuxagent.i2p,ap5riaikrjq2uv5qvy7klzhhqywvqi7wqscyipsewcun7w2eynlq -lists.i2p2.i2p,vmfwbic2brek2ez223j6fc6bl5mmouzqvbsch45msvyyzih3iqua -lm.i2p,yeyar743vuwmm6fpgf3x6bzmj7fxb5uxhuoxx4ea76wqssdi4f3q -lodikon.i2p,u3f67staiwhqxpacya3clmvurdwd2kp7qcthzhstqnhrmlwc2g4a -lolicatgirls.i2p,a4lzmjyba7aq7hl6okqpds7znnwymolqnr7xhvno2wraqb7uhfla -lolifox.i2p,7fd2clkiotjnaoeigdtxlkkb24eik675ovezjf67x26ysham4zca -longhorn.i2p,pohcihzxzttjclrazhs3p76wt3ih737egb5bovqb6ym3du6z3o7a -lp.i2p,jiklbujn3cbfikf4pca526jgmorx6mxhil3twqmfoteaplx6ddwq -lucky.i2p,wx36m3wnpt2y6bngdpg3ifrarvtkpwnluarx377bllpgvkuhybaa -luckypunk.i2p,y4t6cujjxnnrtln3rgmfbgbh46hic7wkef57krd7opitbgngohka -lunokhod.i2p,3yc6sp7xic4grmpfecbwuij6z3dp5kdgoo362pszaco7io42mnwa -m16.i2p,ucsr3eveuc4mx5y6gxnoaywd4ojvbel5q3ynns6s5yfw3vusmfva -mac7.i2p,3yjowssqzydciwa5zx55kazgf7q7w6g7rkocr7bngy35ii44t34q -madman2003.i2p,a2sam2xbhxbzmeyobphbxrkdwlppoerewq5qvibbyk3ftsr643qq -magix.i2p,cgfnyxv62msfynsfbv3kju22j2mt6tfnopshhmrcmpcrxyts6xwq -magnets.i2p,snz46nez6hrrpg6336neinflw56l3vwatk6bzzytwu77xmsfsoca -manas.i2p,6qolj62ikkoq6wdn3hbvcbdmlvf2rcyv432kgi5uy7mvrczmjtba -manveru.i2p,pbmbofs76wpjnxi55eqtwg4y6ltyij72o4fm4sxfjol3y57ze5sq -marcos.i2p,vpo36bsil2voqaou53zshuegssqaroa5mbrzxfmhjywlbojckalq -marshmallow.i2p,svdqd6j3y3gwryufcl4fkzpmcujgvrvphvk2oy4r7m75xs327e2q -marxists.i2p,lepah55qyp2fhuwxlz7bwrhzckn4gkuofivnofoeuyfpmke5x2hq -mattermost.i2p,x5oovnhnuli5fnwtgkbd5z5jvrvdvprqyuofywx6uoxkk4bie6ya -me.i2p,dbpegthe42sx2yendpesxgispuohjixm4bds7ts5gjxzni5nu6na -meeh.i2p,4oes3rlgrpbkmzv4lqcfili23h3cvpwslqcfjlk6vvguxyggspwa -mesh.firerabbit.i2p,3x5wokr4bjy5z3ynji4fyhvwzv4fvgry3xafi5df5h75doezjytq -messageinabottle.i2p,avfhe3kvrrv7utxn2vre65lg7damxzzsewq3vukwie4llitd254a -metrics.i2p,z45ieamhex2ihqv7oowk5fz4qq47rbvxhhhbaaiinpajbhuevtpq -mhatta.i2p,o4rsxdeepfrnncsnjq675xogp5v5qkbfgbt6ooqeyfvlifobrjxq -microbleu.i2p,mtapervgibruizniems2yyr47pin2wpysyh7m632rigl26vjc6qa -microsoft.i2p,hvaqr5idszdyrjph34amb4mjosqd3ynggoxlnj7ciqhnx7q6plza -mindisl0st.i2p,u7rnqhvsuyxd3fabm4kyzn7brgz3i3cporj2emk2jmbpcmltyf7a -mindspore.i2p,uuh5dd3y2rqa7x2jpggm4p2pg6znarm5uanwsvybe4tk36ymwr4q -modulus.i2p,ctz3o6hdefrzwt3hlg6rjhdcbjk6irppbndq32u6jnn4lz72f62a -monerotools.i2p,5bal7dngxde2ddmhuzbtfken6w5nmxmixtjlrlmxt3wbhnemv73q -monerujo.i2p,puri6y5dtwh6zr4u77ep6ozatun6iz7v4wai2dzxppz7654corlq -morph.i2p,iovyp2dao5rta6g5v6hke2s4ugx2btkpcljddak2yhxfrx3l4dqa -mosbot.i2p,5bhmrp43mjwlzf4x64xgdrkwmw4luvng6eq5waa663a7vnkp732a -mosfet.i2p,s5ynkgagndmpxpf2kmnenv4x72io664gzd2x3qef54ilammnte3q -moxonom.i2p,gcjdrvnlobgexh7ebv276pwmnoj3yoyaqm3w4vmmdha4lgxfinqq -mp3.aum.i2p,n7bmu5dwux7f6gedmdik6zrm77bnls4lkzo2vo3bf4bwegk7vkjq -mp3.tc.i2p,w3ied5s7ldjcvnhxu2gyofe3oogzbplkyxshzfkhspiy2526snsa -mpaa.i2p,m6cqnglo7xlytwxkdsmwf3d23d6lq5r446c3tktb2tdmuah36zya -mrbamboo.i2p,tmpmkx6wlbbrgsnexrqlrib7laoegpbfeop7bnyezegii7hecpxa -mrflibble.i2p,u7k2qcmkrril6yvudvwxjqz7k3dzgp3jdejjjeapej7liselj3eq -mrplod.i2p,fjn5hxtybxyfyvdf6u5v5seg2sjd47hb5by6sa6ais4w3xnrxwyq -mtn.i2p,xisk3h6sku3iqj52uriogaajmnku7pwjux7wa4omx2zloamuw6eq -mtn.i2p-projekt.i2p,f52x5fp6uhq53f5zle5d6rq5un34xgmxgazvilvmzcby37xcmsfa -mtn.i2p2.i2p,l6kuhtmgvbp57d7jwalj5nksi6nr4gfzbz4oit62lxgipb3llt5a -mtn.meeh.i2p,h7ylrsuzzynrxp3jql7anoozyqblavj7eqces6o3wngvuuxhs2la -mudgaard.i2p,yz32lk42gtoesknesfolq3tt4erxxcejcote5pontaeqev3bj2kq -mush.zeit.i2p,dk3sg23kljawxqp3cb6xz5mnzjlyckzvq5jhqs5gnvdsv7wqn6ha -music.i2p,akamh76yi6p7xxbvl3qv3yhaockne57yfuh77acogbgpjmwypvia -mysterious.i2p,p66g2a4nzfkvidd3l7nwphcnfa3ttyu5kiolcb4czec2rn2kvwsq -mywastedlife.i2p,ceumy3puvvsrru5bmfmtgsajsx5qyehqac7l7a23xpwtfs2bvcgq -nacl.i2p,bm2fib3tumer72lopjh4nmqomwvqu2sdfyb2hmr6lnk7jbw3vvia -nano.i2p,ex5ssv7s3hj6jp7hvadxfw3wvbjbvnczxr4pbk7qw26ihiorjmba -nassai.i2p,v653cocvn3i6bgjdm3ciwbdnu32supglv6gn4fh23bohemsp545q -neodome.i2p,5hkhjehj3ct2pvcah7dcylwef2oti3xij5myxbv3pd7rocio5vkq -news-i2pn.i2p,wwcqkwfo5yhe6uribv5tzylk25j5hkdk6gdnyftzd3k7dawlzwca -news.neodome.i2p,trhwcnygfkeqjj6g4xhmrdp4gsjqsye47lsxshbmwbten4ywt5oq -news.underscore.i2p,rl7t3kspoktuatjcu7gf7xleu7y6biibs4fspzo24kll6n7hbq4q -newsbyte.i2p,gsk3rgsejxxrfabjxu5w5plplxsu47aoeoke22vvhlwwllzosnxq -nibble.i2p,jmdxcpdzqafedn3clc4y7u6o56qocfiffrzbzncmtggqtio5qjpa -nic.i2p,vzu5ymab6klevpcdudv4ypisjqaznmt44e6lcg7dwiuza4saibxq -nickster.i2p,zkwsa6kvq2wdhovw5g5wqakpb7rlaylyhfriwmurots5pvwbqauq -nickster2.i2p,eofzi7npzpk4p5gb4qper4hmwgxo6kepo3dheeblakewedxj2bwq -nickyb.i2p,gmpxk4tje7mnud32kg2kjmf36f6cpwqakzc2dxuzjnnz4qr5w4sa -nightblade.i2p,p4gkon7ytswxrbwkl7vruw6mg7kfw5aofovqjgt4c7tnqmbq6lha -ninja.i2p,q6dg6hlb3egzdqz352ri5rc4fx4gcrdeu3tpiyfxlv73yfjgrhya -nm.i2p,3itdpqzyn3ii7sivppo4sxxwhvgtpskzkbokrdibim6gqpvlw5ya -nntp.baffled.i2p,kc6muo2tih5mttbpzecteegvtonuysjidk3emcy4cm4yifzild2a -nntp.duck.i2p,gvzzor4utsqxswvf6jaglfks7yxudlz2s326ftrk56i4lpd2s47q -nntp.fr.i2p,npoztnqadfnu4vrokoh6rusoi3yne47s6jurc3lzhcrzzia5eqva -nntp.i2p,wwdzmeyler4djegvyt2bxmkwrckfgg3epkkwowyb75s47he6df6q -no.i2p,lpsg4x4gdrf7antxcdy47cl6abcqei5ommgzt55retq7go5ku3ba -noname56.i2p,oiyoslismzyxuw7ehxoigmtkdj35idim6flmlplddxuiiif6msfa -nop.i2p,ssag45lathm4gqp46si7c4w4tioyvjpcza5uvz5x2zuljnplylca -normal.i2p,j5fex634r2altzb3kjvu35qekt2r3hgsqzg5qxoy7dp53heu5pma -normanabcd.i2p,si2vh43gvxjnw2shwr24j76xyanow4oa6gbu4idookbraoxl3s3a -nothingburger.i2p,tesfpn757ysc7nih7mxher2b3jstkc3l5fhfcyb5kxhzhvv52trq -nothingspecial.i2p,wzrwqrp52bilqijrlboclynuev4kzpjzfzlvzl5aqxqt5fdnpbga -novospice.i2p,ukqap24nwac4gns77s4zy7j5cagt7l7syb5zo7eukfg3zn5gg5qq -nsa.i2p,nsetvbclpomqxfcit4mghn6z7vdhnza6jdzczby4crnto32uykga -nvspc.i2p,anlncoi2fzbsadbujidqmtji7hshfw3nrkqvbgdleepbxx3d5xra -nxt-wallet.i2p,33pp74k4ivy67z332qpyl3qlcqmi6gxqumrow4bldkblxxlxqq5a -obmen.i2p,vodkv54jaetjw7q2t2iethc4cbi4gjdrmw2ovfmr43mcybt7ekxa -obscuratus.i2p,i4j37hcmfssokfb6w3npup77v6v4awdxzxa65ranu34urjs4cota -ogg.aum.i2p,wchgsx6d6p3czloeqvna2db5jr7odw4v4kqrn4gr4qiipfyrbh5q -ogg.baffled.i2p,tfbvj2xal6lcuxv3hzuw7cw4g3whguombcv2zuotzvul4qtrimgq -ol.i2p,bnb46culzbssz6aipcjkuytanflz6dtndyhmlaxn3pfiv6zqrohq -onboard.i2p,qwlgxrmv62mhdu6bgkh4ufnxowxsatfb6tbs2zr666qyunwqnecq -onelon.i2p,irkvgdnlc6tidoqomre4qr7q4w4qcjfyvbovatgyolk6d4uvcyha -onhere.i2p,vwjowg5exhxxsmt4uhjeumuecf5tvticndq2qilfnhzrdumcnuva -oniichan.i2p,nnkikjorplul4dlytwfovkne66lwo7ln26xzuq33isvixw3wu3yq -onionforum.i2p,yadam2bp6hccgy7uvcigf5cabknovj5hrplcqxnufcu4ey33pu5q -ooo.i2p,iqp5wt326fyai5jajsa3vkkk5uk56ofn4anocgpe5iwlpisq6l7a -opal.i2p,li5kue3hfaqhhvaoxiw2ollhhkw765myhwcijgock5rs4erdqdaa -open4you.i2p,ice6ax5qrzwfwzsy64bctffj6zlzpuzdr5np65zsxlbt7hztyc6a -opendiftracker.i2p,bikpeyxci4zuyy36eau5ycw665dplun4yxamn7vmsastejdqtfoq -openforums.i2p,lho7cvuuzddql24utu7x6mzfsdmxqq7virxp5bcqsxzry2vmwj5q -opentracker.dg2.i2p,w7tpbzncbcocrqtwwm3nezhnnsw4ozadvi2hmvzdhrqzfxfum7wa -orion.i2p,5vntdqqckjex274sma3uqckwqep2czxs5zew25zlntwoofxk3sga -orz.i2p,oxomqkekybmyk6befjlouesit5mhstonzvzd2xnvsk7i6uyrqsfq -os3.i2p,s7x4ww5osrrfein3xgwyq67wnk6lgliw4mzt7shtu66wrb2zdojq -osiristomb.i2p,t3slf77axkv3qm7c3gzpv3jgmkraoqqe2bojr6h66eipibofsyzq -ot.knotwork.i2p,cxhvvfkbp2qbv5qojph7zb46molpe2ffanghnerjag3xdmy6ltxq -outproxy-tor.meeh.i2p,77igjr2pbg73ox5ngqy5ohzvrnur3ezqcogtl4vpuqtrcl3irsqq -outproxy.h2ik.i2p,nwgvfpfarpnyjjl4pwsxr2zdsppcx5we3kos2vlwicbiukopgaza -outproxyng.h2ik.i2p,v32zse2zczzgegelwxbx7n5i2lm2xhh2avltg76h6fz5tb53sfxq -overchan.oniichan.i2p,g7c54d4b7yva4ktpbaabqeu2yx6axalh4gevb44afpbwm23xuuya -p4bl0.i2p,lkgdfm4w6e2kkjhcdzr4ahhz26s3aunhrn6t2or436o73qh4z7ga -pants.i2p,xez3clscjfafkqwk6f473ccp3yvac4kh6rdp6dptwxa2lhixizgq -papel.i2p,mxskjqntn2d34q4ovsnd5mud7cgde734tdjldd3lt4hczh2645zq -pasta-nojs.i2p,dkkl3ab6iovxfqnp44wsjgqaabznvu7u3hugpzyagbeqlxgvx3la -paste.crypthost.i2p,2zaj4u4s4l3lgas2h5p6c6pvzr2dckylkrh5ngabursj4oh25ozq -paste.i2p2.i2p,b2gizskfea4sjxlw6ru2tb6kdrj47dsjc77cijsf5mzh4ogbmfvq -paste.r4sas.i2p,csen43keji3qiw6uobsgzysxyjd225g6446ylq5uuz6ur2glkzaa -pastebin.i2p,mnicncxrg2qqi55qftigiitaheugnj4rpysbk7zabdrirgktelqa -pastethis.i2p,erkqiwnjl7vtysqd3wvddv6tfvnhswarqkbn4blhdlhfxn7cf2ha -pdforge.i2p,wzeg3ehf6d2mqjqji3sd3rns776thvhe2vam2r6gjlmsqis2dctq -perv.i2p,f3k3wm4ae7t7ottfjd4hu6is7zsls73izl2gm2qynzficxcdsiwq -pgp.duck.i2p,wujajyxj3cgsfsbtr3g7g7npv5ft3de6pcstxlav26zq6cxdjmha -pharos.i2p,vathk2pyvaskeie63yyg4tshjkx5xt6zfvhwhgr3de67q46ob3sa -pharoz.i2p,vathk2pyvaskeie63yyg4tshjkx5xt6zfvhwhgr3de67q46ob3sa -phonebooth.i2p,noxia7rv6uvamoy2fkcgyj4ssjpdt4io6lzgx6jl6wujpufxedrq -photo.i2p,fqhuy77ugd5htnubzkyy5guvwboqn6goahtmn2g7feewvdj7k3iq -piespy.i2p,vzusfjzcu5ntnvobcvyzc4dcu4j6ommtnpmba2puk3kexgdzrl7a -pisekot.i2p,7yzdwhy723fodqz4onp6k3nyvixra2sa6dl45tcblhmyoa7i36nq -pizdabol.i2p,5vik2232yfwyltuwzq7ht2yocla46q76ioacin2bfofgy63hz6wa -planet.i2p,y45f23mb2apgywmftrjmfg35oynzfwjed7rxs2mh76pbdeh4fatq -plugins.i2p,wwgtflbaa7od2fxbw4u7q7uugmdclxf56alddvizugwcz5edjgia -polecat.i2p,het5jrdn35nhkanxmom5mjyggyvmn2wdj2agyqlrv4mhzhtmavwq -politguy.i2p,6dkkh3wnlwlr6k7wnlp4dbtf7pebjrph5afra2vqgfjnbihdglkq -pomoyka.i2p,omt56v4jxa4hurbwk44vqbbcwn3eavuynyc24c25cy7grucjh24q -pool.gostcoin.i2p,m4f4k3eeaj7otbc254ccj7d5hivguqgnohwelkibr4ddk43qhywa -pop.mail.i2p,bup6pmac7adgzkb5r6eknk2juczkxigolkwqkbmenawkes5s5qfq -pop.postman.i2p,ipkiowj7x4yjj7jc35yay3c6gauynkkl64gzzyxra3wmyhtfxlya -pravtor.i2p,2sr27o5x2v2pyqro7wl5nl6krrsbizwrzsky5y7pkohwh24gn6xq -pris.i2p,ahiwycgzuutdxvfqu3wseqffdnhy675nes57s4it2uysy5pxmz6a -project-future.i2p,ivqynpfwxzl746gxf376lxqvgktql2lqshzwnwjk2twut6xq7xta -projectmayhem2012-086.i2p,ehkjj4ptsagxlo27wpv4a5dk4zxqf4kg4p6fh35xrlz4y6mhe4eq -protokol.i2p,f4xre35ehc5l6ianjvt3zcktxkjlyp2iwdje65qnu2j6vurhy6nq -proxynet.i2p,7gar5a3n4hzvsgi73iizo65mjza4kujf7feopfxuwu5p6wtwog5a -psi.i2p,avviiexdngd32ccoy4kuckvc3mkf53ycvzbz6vz75vzhv4tbpk5a -psy.i2p,s3elzoj3wo6v6wqu5ehd56vevpz2vrhhjc5m6mxoazicrl43y62q -psyco.i2p,eoilbrgyaiikxzdtmk2zeoalteupjrvcu3ui23p4wvfqo25bb73q -pt.hiddenanswers.i2p,o5jlxbbnx3byzgmihqye3kysop5jgl3unsrkmurbtr2nrnl2y74a -ptm.i2p,7dna5745ynxgogpjermnq26hwrqyjdlsibpjfmjxlwig247bjisa -ptt.i2p,q7r32j7lc3xgrcw2ym33wv4lfgqbez7vtm4lts7n34qfe3iygeha -pull.git.repo.i2p,3so7htzxzz6h46qvjm3fbd735zl3lrblerlj2xxybhobublcv67q -push.git.repo.i2p,jef4g5vxnqybm4zpouum3lzbl6ti6456q57nbyj5kfyldkempm3a -pycache.awup.i2p,w45lkxdnqhil4sgzanmxce62sv3q4szeowcjb2e72a5y5vbhm4ra -r4sas.i2p,2gafixvoztrndawkmhfxamci5lgd3urwnilxqmlo6ittu552cndq -radio.r4sas.i2p,cv72xsje5ihg6e24atitmhyk2cbml6eggi6b6fjfh2vgw62gdpla -ragnarok.i2p,jpzw6kbuzz3ll2mfi3emcaan4gidyt7ysdhu62r5k5xawrva7kca -ransack.i2p,mqamk4cfykdvhw5kjez2gnvse56gmnqxn7vkvvbuor4k4j2lbbnq -rasputin-sucks.i2p,fdozdbyak4rul4jwpqfisbkcx4xbrkuvf2o5r6fd3xryyrjgvjiq -rebel.i2p,nch2arl45crkyk6bklyk2hrdwjf5nztyxdtoshy6llhwqgxho5jq -red.i2p,fzbdltgsg7jrpz7gmjfvhpcdnw5yrglwspnxqp4zoym3bglntzfa -redpanda.i2p,3wcnp6afz4cikqzdu2ktb5wfz7hb3ejdbpn7ocpy7fmeqyzbaiea -redzara.i2p,ty7bt62rw5ryvk44dd3v5sua6c7wnbpxxqb6v4dohajmwmezi7va -reefer.i2p,4cde25mrrnt5n4nvp5tl62gej33nekfvq2viubmx4xdakhm5pfaa -relatelist.i2p,utrer5zgnou72hs4eztmk37pmzdtfw3d6s23wwl7nk3lkqpzbdiq -repo.i2p,uxe3lqueuuyklel23sf5h25zwgqgjwsofrqchhnptd5y6pedzbxa -repo.r4sas.i2p,ymzx5zgt6qzdg6nhxnecdgbqjd34ery6mpqolnbyo5kcwxadnodq -reseed.i2p,j7xszhsjy7orrnbdys7yykrssv5imkn4eid7n5ikcnxuhpaaw6cq -retrobbs-nntp.i2p,fkyzl24oxcxvjzkx74t3533x7qjketzmvzk6bwn3d6hj5t7hlw6q -retrobbs.i2p,mnn77stihntxdoade3ca2vcf456w6vhhvdsfepdvq5qggikvprxq -retrobbs2.i2p,ejff7jtyaus37slkwgeqrrcmyhpj26carp7n27f5h6s5vlbeiy6q -revo-ua.i2p,hpojpumki22xjwhmhe6zkiy44oanyn7u4ctcfe3in2ibwm5l32hq -riaa.i2p,lfbezn7amkzhswnx7lb4lxihyggl2kuqo5c7vwkcv6bwqmr4cuoa -rideronthestorm.i2p,xrdc2qc7quhumhglpbcuiqxr42nuffv4xj4a73jbr4ygepitibqq -romster.i2p,eaf2stdqdbepylt53egvixdi34g2usvgi7a4oixsja6atkran43a -rootd.i2p,mzbe5wofwn7eaqq4yefrmxizqaxoslwqxrv5qcv2opx5lnhg64dq -rospravosudie.i2p,z55khrnlj6bzhs5zielutm6ae6t2bbhfuiujwlrp3teubqyc4w7q -rotten.i2p,j4bm3rvezlejnb44elniagi5v2gazh7jaqrzhbod2pbxmgeb2frq -rpi.i2p,56p5qxsrvo5ereibevetw2qbj5bronmos7wxunku27g2s4kpbnlq -rslight.i2p,bitag46q3465nylvzuikfwjcj7ewi4gjkjtvuxhn73f6vsxffyiq -rsync.thetower.i2p,w4brpcdod7wnfqhwqrxyt4sbf2acouqfk5wyosfpq4mxq4s35kqa -ru.hiddenanswers.i2p,o6rmndvggfwnuvxwyq54y667fmmurgveerlzufyrhub6w3vkagva -ru.i2p,m7fqktjgtmsb3x7bvfrdx4tf7htnhytnz5qi2ujjcnph33u3hnja -rufurus.i2p,7msryymfdta3ssyz34qur6gi4jyfkvca5iyfmnceviipwu7g2wca -rus.i2p,gh6655arkncnbrzq5tmq4xpn36734d4tdza6flbw5xppye2dt6ga -ruslibgen.i2p,kk566cv37hivbjafiij5ryoui2ebxnm7b25gb3troniixopaj6nq -rutor.i2p,tro5tvvtd2qg34naxhvqp4236it36jjaipbda5vnjmggp55navdq -salt.i2p,6aflphlze6btsbez5cm4x53ydrmwhqrkxsud535d3qjh4wq62rxq -sasquotch.i2p,p6535uyfk2y6etc3t47vd3oqxydznqior5jxcvq5bdxe5kw5th6q -schwarzwald.i2p,4gokilzy73mmudufy3pohgatm42fcstx7uzg5hjvnfyphxpnphuq -sciencebooks.i2p,ypftjpgck75swz3bnsu4nw7rmrlr2vqsn4mwivwt3zcc3rxln5cq -scp.duck.i2p,ghbpsolpnveizxu4wbs7jbs2vj3kntnsexfcdleyhpqdhfpxleda -search.i2p,nz4qj6xaw5fda3rsmsax6yjthqy4c7uak2j3dzcehtkgyso4q46q -secretchat.i2p,cl3j2zxhpw6u6jevny45i557ojhwfxn4g375nnuqhy6lp27mry2q -secure.thetinhat.i2p,4q3qyzgz3ub5npbmt3vqqege5lg4zy62rhbgage4lpvnujwfpala -seeker.i2p,ipll7sit24oyhnwawpvokz5u7dabq6klveuqpx3sbi6o5qemy2bq -seomon.i2p,5mvpsy4h45w4fx7upen7ay3vkrs5klphz5nptmtcqvc3fsajsm4q -septu.i2p,5lqvih7yzbqacfi63hwnmih57dxopu5g2o5o4e2aorq7bt4ooyra -serien.i2p,3z5k3anbbk32thinvwcy4g5al7dmb75fagcm3zgh4rzrt3maphda -ses.i2p,5qfoz6qfgbo7z5sdi26naxstpi2xiltamkcdbhmj6y6q2bo4inja -shiftfox.i2p,wpvnuzslu7hjy4gujvnphtyckchdoxccrlhbyomsmjizykczyseq -shoieq3.i2p,3fjk4nfk3mccch4hdreghnyijcvovsi3yucjz3qzj5sxngqk5j6q -shoronil.i2p,7shqzgmb6tabiwrnwlasruq7pswy2d3emvfhaitehkqgod7i62sa -short.i2p,z5mt5rvnanlex6r3x3jnjhzzfqpv36r4ylesynigytegjmebauba -sion.i2p,lcbmmw2tvplvqh2dq5lmpxl3vnd5o4j3bdul5moa23deakjrso5q -sirup.i2p,aohdp4yajnkitrtw7v2mo3sp7swuqhjfwlsi5xwd7dudzftumsma -site.games.i2p,zeuczucfxeev3k7tvqlfcdpfbnqggheiknyyb5r2q4utn3d2auja -skank.i2p,qiii4iqrj3fwv4ucaji2oykcvsob75jviycv3ghw7dhzxg2kq53q -slack.i2p,gfcsh2yrb2tx7hyvmobriv52skz7qoobn7n7y7n6xaehhh4rpbja -slacker.i2p,wq7m2wdguzweleb666ygv3bmfhha63zj74rub76vfesbyhsyk6iq -smeghead.i2p,ojf4czveeuekxqkjvkszvv7eiop5dg7x2p6rgfzl4ng4xrjk6lja -smtp.mail.i2p,kdn7zx7fgoe4bn5abaaj5cb3e4ql22fklb5veui5yajpj4cxapya -smtp.postman.i2p,jj7pt6chsziz6oxxnzpqj7mzhxm2xfhcrbh7dl3tegifb577vx5q -socks1.tor.i2p,sifawcdexgdmoc3krv46pvvz74nzd6fkju2vzykjxsx3egqsb6wq -sonax.i2p,jmuxdhlok5ggojehesfjlit2e2q3fhzwwfxjndts7vzdshucbjjq -sponge.i2p,o5hu7phy7udffuhts6w5wn5mw3sepwe3hyvw6kthti33wa2xn5tq -squid.i2p,r4ll5zkbokgxlttqc2lrojvvey5yar4xr5prnndvnmggnqzjaeoq -squid2.i2p,hum4wlwizbsckbudcklflei66qxhpxsdkyo4l2rn256smmjleila -sqz.i2p,3jvbwc7sy4lnhj25nj7yepx7omli4ulqirnawv3mz6qlhgokjgzq -ssh.i2p,xpvdadaouc4qr75pteymyozc7mcsynjfkuqqkkla542lpcsqionq -stasher.i2p,6ilgpudnba4kroleunc2weh5txgoxys5yucij5gla6pjyki4oewa -stats.i2p,7tbay5p4kzeekxvyvbf6v7eauazemsnnl2aoyqhg5jzpr5eke7tq -status.str4d.i2p,ycyyjo3psqbo45nuz243xvgvwnmzlanzqbzxv3kh6gyjztv7425q -sto-man.i2p,rg4eilfpe24ws6nctix63qw2dlvd2tqgwdcgdxzji6l5bc4dc7aa -str4d.i2p,wrrwzdgsppwl2g2bdohhajz3dh45ui6u3y7yuop5ivvfzxtwnipa -stream.i2p,prmbv3xm63ksoetnhbzqg4nzu2lhqdnqytgsydb7u3quxfrg7rna -streams.darkrealm.i2p,ud3gcmvysjch4lbjr2khmhqpf7r2x5if4q43xkqdptl4k7lc4muq -striker.i2p,4gswsrfpbd44hwjoj33jbqfbwzxfkwpuplb3ydq5zm7nfu2pxvdq -subrosa.i2p,g3lnglrnoual7wyabnwwv37uwhadgbxiqz36pf3f5cwfuxsx4mxq -subterra.i2p,vdmhe4u26unzgd7ysq6w36ubjncms5wzbhzr2gq576sq4xut5zwq -sugadude.i2p,yzjn76iyqard64wgggfrnywkxi7tbfkw7mjhpviqz3p2dguey4yq -suicidal.i2p,yfamynllow5xiqbbca7eh5xn733wtnuti5bi4ovc7dwycntqmiuq -sungo.i2p,h67s3jw56rwfyoxqxj3fngrluybsgxc2meendngkehzqowxnpj3q -surrender.adab.i2p,jgz7xglgfgnjfklrytyn427np2ubipztlm5bxrtbiucayglukrta -susi.i2p,qc6g2qfi2ccw7vjwpst6rwuofgzbeoewsb2usv7rubutf4gzqveq -syncline.i2p,5kcqmhislu3lmr7llgmdl72yu3efhyriljdc6wp774ftpwlcs5ra -syndie-project.i2p,xa63tpfoaqt3zru2ehxjjfbpadwj4ha6qsdvtcqtyr3b7hmt4iaq -syndie.echelon.i2p,vwrl2qmcif722fdkn3ldxcgz76df5cq4qypbndzthxwgmykyewta -syndie.i2p,7lm3yzpuejhpl4tt4l7o4ndqlu7hgijohofh7oaydx7q7kelenbq -syndiemedia.i2p,4lrbbblclodhobn3jadt5bf2yab2pxzoz4ey4a2cvrl44tdv3jma -tabak.i2p,y5o2vwb6kart7ivpnbpk4yte3i7kf2dsx7fy3i6w7htqtxhmbzia -tahoeserve.i2p,yhs7tsjeznxdenmdho5gjmk755wtredfzipb5t272oi5otipfkoa -tc.i2p,qkv2yk6rof3rh7n3eelg5niujae6cmdzcpqbv3wsttedxtqqqj7a -telegram.i2p,i6jow7hymogz2s42xq62gqgej2zdm4xtnmpc6vjcwktdxpdoupja -templar.i2p,zxeralsujowfpyi2ynyjooxy222pzz4apc2qcwrfx5ikhf64et7q -terror.i2p,wsijm6aqz4qtuyn2jedpx6imar5uq4yuhjdgtfqumxbqww47vbnq -thebland.i2p,oiviukgwapzxsrwxsoucpqa47s3wt6nfuhfjxvgbqsyrze2mwrda -thebreton.i2p,woutbsflcrlgppx4y7ag2kawlqijyenvlwrhbbvbkoaksuhf2hkq -thedarkside.i2p,fxt3z33nzkrg5kjrk7bp5vvmu7w2vsn4i6jo6cily3hsm6u664ca -theland.i2p,26ppxbseda6xmim37ksarccdb4q5ctdagfmt2u5aba6xjh452zsa -thetower.i2p,3xqa5nype64y6fxgqjq6r5w2qpiqftoraj2niebumseat4cj654a -thornworld.i2p,vinz4ygmodxarocntyjlfwk2wjpvzndlf4hxss2w2t3fk52oplva -tino.i2p,e4bfnhvaofu4s67ztcgiskos2mqyhskid64dvlqexxs2c2bno3iq -tinyurl.i2p,mc4oxv3v7dnyzpvok7v5qxkwtgjprgyz6w7x3tag4fipsen6rdwa -tome.i2p,qktkxwawgixrm5lzofnj5n24zspbnzxy4pvjm7uvaxvmgwrsuvgq -tor-gw.meeh.i2p,ounrqi7cfemnt66yhnhigt2u27fkctbvct527cp2522ozy3btjza -tor-www-proxy.i2p,xov45rvjks5fe4ofmpblkj23bnwxgslbypbgvchbr7yul2ujej2q -torapa.i2p,eejqjtpko6mdd4opvntbpsuandstrebxpbymfhix7avp5obrw5ta -torrentfinder.i2p,mpc73okj7wq2xl6clofl64cn6v7vrvhpmi6d524nrsvbeuvjxalq -torrfreedom.i2p,nfrjvknwcw47itotkzmk6mdlxmxfxsxhbhlr5ozhlsuavcogv4hq -trac.i2p,kyioa2lgdi2za2fwfwajnb3ljz6zwlx7yzjdpnxnch5uw3iqn6ca -trac.i2p2.i2p,i43xzkihpdq34f2jlmtgiyyay5quafg5rebog7tk7xil2c6kbyoa -tracker-fr.i2p,qfrvqrfoqkistgzo2oxpfduz4ktkhtqopleozs3emblmm36fepea -tracker.awup.i2p,dl47cno335ltvqm6noi5zcij5hpvbj7vjkzuofu262efvu6yp6cq -tracker.crypthost.i2p,ri5a27ioqd4vkik72fawbcryglkmwyy4726uu5j3eg6zqh2jswfq -tracker.fr.i2p,rzwqr7pfibq5wlcq4a7akm6ohfyhz7hchmy4wz5t55lhd7dwao5q -tracker.i2p,lsjcplya2b4hhmezz2jy5gqh6zlk3nskisjkhhwapy3jjly4ds5q -tracker.lodikon.i2p,q2a7tqlyddbyhxhtuia4bmtqpohpp266wsnrkm6cgoahdqrjo3ra -tracker.mastertracker.i2p,tiwurhqvaaguwpz2shdahqmcfze5ejre52ed2rmoadnjkkilskda -tracker.postman.i2p,jfcylf4j3gfmqogkltwy7v5m47wp4h7ffrnfsva6grfdavdn7ueq -tracker.psi.i2p,vmow3h54yljn7zvzbqepdddt5fmygijujycod2q6yznpy2rrzuwa -tracker.thebland.i2p,s5ikrdyjwbcgxmqetxb3nyheizftms7euacuub2hic7defkh3xhq -tracker.welterde.i2p,cfmqlafjfmgkzbt4r3jsfyhgsr5abgxryl6fnz3d3y5a365di5aa -tracker2.postman.i2p,ahsplxkbhemefwvvml7qovzl5a2b5xo5i7lyai7ntdunvcyfdtna -traditio.i2p,wkpjjloylf6jopu2itgpktr45t2xvpjijxilxd5tq4i7wkqgwhhq -trevorreznik.i2p,wc2z6o5fxm2saqzpfcawr63lejwccvzkysmgtfudkrigqopzfdma -true.i2p,pdilhl5vmefyzrrnmak5bnmxqxk2pmw7rpy4f7wbaeppqu2vvugq -trwcln.i2p,evml6jiiujhulsgxkdu3wcmkwbokxlv4is6w5qj46tp3ajz3hqzq -trypanom.i2p,tgv5acj4khwvr6t44cmryohybd2e5o2kndysnzae6qwcr4hzda3q -ts.i2p,nebcjgfx3f7q4wzihqmguwcdeopaf7f6wyk2dojw4bcuku472zxq -ttc.i2p,wb4tsfyvfv4idgrultsq6o7inza4fxkc7dijsfpncbx7zko4cdlq -ttp.i2p,uuczclxejmetohwf2vqewovx3qcumdfh5zecjb3xkcdmk6e5j72a -tumbach.i2p,u6pciacxnpbsq7nwc3tgutywochfd6aysgayijr7jxzoysgxklvq -tutorials.i2p,zy37tq6ynucp3ufoyeegswqjaeofmj57cpm5ecd7nbanh2h6f2ja -ugha.i2p,z3f3owc72awbywk4p6qb5l2mxgitvs6ejztggbpn2a3ddmymfjda -uk.i2p,vydbychnep3mzkzhg43ptewp242issy47whamfbxodc4ma6wc63a -underground.i2p,dlnuthb6tpw3kchlb7xoztyspy4ehlggjhl44l64vbcrulrfeica -underscore.i2p,3gmezyig6gvsjbpkq2kihoskpuqpkfrajmhhm7hpyrjuvtasgepa -unqueued.i2p,3gvn4kwd7z74jxc2sn4ucx52dpvpscxbzjluux3ul4t3eu5g64xq -up.i2p,25it5olgdo7pht25z6buzd32sw7jvc65oziqeuocfozfhgua655q -update.dg.i2p,iqj6ysfh3wl26m4buvyna73yhduifv523l7bwuexxak4mgldexja -update.killyourtv.i2p,gqdfg25jlqtm35qnmt4b7r53d6u2vep4ob23fwd42iyy4j6cvdqq -update.postman.i2p,u5rbu6yohfafplp6lgbbmmcuip34s7g3zqdd63cp27dl3nbd7gtq -utansans.i2p,u2oyre7ygqv4qs5xjjijfg3x7ddwtod6nqwgbomuuzljzvnq4rda -v2mail.i2p,4gg7fykcqe7oaqt4w5fmlarnia7vtmwkv3h45zzgoj6o6crryg5a -vadino.i2p,aalttzlt3z25ktokesceweabm5yyhhvml2z3rfotndgpfyh6myra -visibility.i2p,pwgma3snbsgkddxgb54mrxxkt3l4jzchrtp52vxmw7rbkjygylxq -volatile.i2p,q6rve733tvhgyys57jfw4fymqf3xsnza6dqailcdjcq7w4fa5m3a -vpnbest.i2p,ov5f74ndsy5rfkuyps56waf42vxncufqu5rzm3vsnxkdtogccaea -vudu.i2p,3zlwci7pvgep2igygzyjej24ue7mjsktlhaff6crpsr75yquak2q -w.i2p,j2xorlcb3qxubnthzqu7lt4fvxqn63it4ikwmze55yjkzeeampuq -wa11ed.city.i2p,7mxwtmala3ycg2sybjwwfil7s6dqck2fbemeutghhwu73rznmqoa -wahoo.i2p,vqe5vkpe5wbda7lwekcd2jaj44ar3rawgv54u5rcolezbg5f5vwa -wallet.gostcoin.i2p,reuvum7lgetglafn72chypesvto773oy53zumagrpigkckybrwda -wallsgetbombed.i2p,tzhea5d65fllm4263wztghgw4ijdgibsca5xsecp6lk4xlsbdeuq -web.telegram.i2p,re6cgwg2yrkgaixlqvt5ufajbb3w42fsldlq7k5brpvnd5gp6x5a -wiht.i2p,yojmpj3sh76g3i6ogzgsf7eouipdgdij5o2blcpdgmu5oyjk5xca -wiki.fr.i2p,lrqa7hw52uxjb5q3pedmjs6hzos5zrod4y6a4e25hu7vcjhohvxq -wiki.ilita.i2p,r233yskmowqe4od4he4b37wydr5fqzvj3z77v5fdei2etp2kg34a -wintermute.i2p,4gvlfrdy2rkmem33c342tjntpvqik65wekcvm4275qbkuwotoila -wspucktracker.i2p,ubd2txda3kllumx7ftg4unzgqy536cn6dd2ax6mlhodczfas7rgq -www.aum.i2p,3xolizygkzkqrldncjqsb734szznw2u36lliceuacqnbs2n65aeq -www.baffled.i2p,lqrsfslwu4xnubkk2hofhmuvvr4dia2zevxefinbzdsjurvehtqq -www.fr.i2p,rmkgvlfwo3vkb3xrr6epoypxasdzzuilv3sckcqbo6c4os5jo2ea -www.i2p,ojxyenivrrqvycgbxbm3phgisu5abspzq4g2us4fjlwz4tx222va -www.i2p2.i2p,rjxwbsw4zjhv4zsplma6jmf5nr24e4ymvvbycd3swgiinbvg7oga -www.imule.i2p,657xcllunctawyjtar5kgh3wpt6z4l7ba6mmam5rf7hev5w2lsvq -www.infoserver.i2p,fq7xhxkdcauhwn4loufcadiiy24zbei25elnup33a3gfrdzrtlyq -www.janonymous.i2p,vosqx5qw22hwrzcgsm4ib7hymf5ryovsbtaexqrzmnzshy5bhakq -www.mail.i2p,nctas6ioo7aaekfstv3o45yh6ywzwa3vznrdae52ouupzke5pyba -www.nntp.i2p,kly3o7zmetuwyz7xonnhttw4lj2244pkbibjz26uflyfte3b3dka -www.postman.i2p,rb3srw2gaooyw63q62cp4udrxxa6molr2irbkgrloveylpkkblhq -www.syndie.i2p,vojgy5ep4wffmtpjmpnbpa4gq64bgn4yicuw6qmhbm6nqa2ysrva -www1.squid.i2p,vbh3bltd2duwbukafgj6f6vfi6aigwso7snucp5zohnf66a2hkpa -xc.i2p,mt45a2z3sb2iyy2mwauj4rwa2lwu4peanfy6gx6ybidwnbasusyq -xeha.i2p,oartgetziabrdemxctowp7bbeggc7ktmj7tr4qgk5y5jcz4prbtq -xilog.i2p,eoc5i5q52hutnmsmq56edvooulutaxfikddgdz27otmgtsxmiloq -xmpp.crypthost.i2p,ittkqpjuliwsdewdugkhvgzstejr2jp5tzou7p332lxx4xw7srba -xmpp.rpi.i2p,3yv65pfwiwfuv4ciwtx34clqps6o2mc3vtyltcbqdkcki6untbca -xn--l2bl5aw.i2p,d2epikjh5crt2l5xjmtceqw2ho44hzp6x3u7hgjrd4mi4wywikwa -xolotl.i2p,rwr6rrlmrotxfkxt22mah42cycliy2g5k7hgxyxkpcyyxkd2bgwq -xotc.i2p,gqgvzum3xdgtaahkjfw3layb33vjrucmw5btyhrppm463cz3c5oq -z-lab.i2p,s6g2pz3mrwzsl4ts65ox3scqawfj7mzvd7hn2ekiiycawopkriba -zab.i2p,n4xen5sohufgjhv327ex4qra77f4tpqohlcyoa3atoboknzqazeq -zcash.i2p,zcashmliuw3yd2ptfyd5sadatcpyxj4ldiqahtjzg73cgoevxp4q -zener.i2p,mcbyglflte3dhwhqyafsfpnqtcapqkv2sepqd62wzd7fo2dzz4ca -zerobin.i2p,3564erslxzaoucqasxsjerk4jz2xril7j2cbzd4p7flpb4ut67hq -zeroman.i2p,gq77fmto535koofcd53f6yzcc5y57ccrxg3pb6twhcodc7v5dutq -zeronet.i2p,fe6pk5sibhkr64veqxkfochdfptehyxrrbs3edwjs5ckjbjn4bna -znc.i2p,uw2yt6njjl676fupd72hiezwmd4ouuywowrph6fvhkzhlnvp7jwa -znc.str4d.i2p,ufkajv3stxpxlwgwwb2ae6oixdjircnbwog77qxpxv7nt67rpcxq -zzz.i2p,ukeu3k5oycgaauneqgtnvselmt4yemvoilkln7jpvamvfx7dnkdq diff --git a/android/assets/certificates b/android/assets/certificates deleted file mode 120000 index 01b21e5d..00000000 --- a/android/assets/certificates +++ /dev/null @@ -1 +0,0 @@ -../../contrib/certificates \ No newline at end of file diff --git a/android/assets/i2pd.conf b/android/assets/i2pd.conf deleted file mode 100644 index 14254248..00000000 --- a/android/assets/i2pd.conf +++ /dev/null @@ -1,92 +0,0 @@ -## Configuration file for a typical i2pd user -## See https://i2pd.readthedocs.io/en/latest/user-guide/configuration/ -## for more options you can use in this file. - -#logfile = /sdcard/i2pd/i2pd.log -loglevel = none -#tunnelsdir = /sdcard/i2pd/tunnels.d - -# host = 1.2.3.4 -# port = 4567 - -ipv4 = true -ipv6 = false - -# ntcp = true -# ntcpproxy = http://127.0.0.1:8118 -# ssu = true - -bandwidth = L -# share = 100 - -# notransit = true -# floodfill = true - -[ntcp2] -enabled = true - -[http] -enabled = true -address = 127.0.0.1 -port = 7070 -# auth = true -# user = i2pd -# pass = changeme - -[httpproxy] -enabled = true -address = 127.0.0.1 -port = 4444 -inbound.length = 1 -inbound.quantity = 5 -outbound.length = 1 -outbound.quantity = 5 -signaturetype=7 -i2cp.leaseSetType=3 -i2cp.leaseSetEncType=0,4 -keys = proxy-keys.dat -# addresshelper = true -# outproxy = http://false.i2p -## httpproxy section also accepts I2CP parameters, like "inbound.length" etc. - -[socksproxy] -enabled = true -address = 127.0.0.1 -port = 4447 -keys = proxy-keys.dat -# outproxy.enabled = false -# outproxy = 127.0.0.1 -# outproxyport = 9050 -## socksproxy section also accepts I2CP parameters, like "inbound.length" etc. - -[sam] -enabled = false -# address = 127.0.0.1 -# port = 7656 - -[precomputation] -elgamal = true - -[upnp] -enabled = true -# name = I2Pd - -[reseed] -verify = true -## Path to local reseed data file (.su3) for manual reseeding -# file = /path/to/i2pseeds.su3 -## or HTTPS URL to reseed from -# file = https://legit-website.com/i2pseeds.su3 -## Path to local ZIP file or HTTPS URL to reseed from -# zipfile = /path/to/netDb.zip -## If you run i2pd behind a proxy server, set proxy server for reseeding here -## Should be http://address:port or socks://address:port -# proxy = http://127.0.0.1:8118 -## Minimum number of known routers, below which i2pd triggers reseeding. 25 by default -# threshold = 25 - -[limits] -transittunnels = 50 - -[persist] -profiles = false diff --git a/android/assets/subscriptions.txt b/android/assets/subscriptions.txt deleted file mode 100644 index 8f4afb03..00000000 --- a/android/assets/subscriptions.txt +++ /dev/null @@ -1,3 +0,0 @@ -http://inr.i2p/export/alive-hosts.txt -http://stats.i2p/cgi-bin/newhosts.txt -http://i2p-projekt.i2p/hosts.txt diff --git a/android/assets/tunnels.conf b/android/assets/tunnels.conf deleted file mode 100644 index c39a0220..00000000 --- a/android/assets/tunnels.conf +++ /dev/null @@ -1,33 +0,0 @@ -#[IRC-IRC2P] -#type = client -#address = 127.0.0.1 -#port = 6668 -#destination = irc.postman.i2p -#destinationport = 6667 -#keys = irc-keys.dat - -#[IRC-ILITA] -#type = client -#address = 127.0.0.1 -#port = 6669 -#destination = irc.ilita.i2p -#destinationport = 6667 -#keys = irc-keys.dat - -#[SMTP] -#type = client -#address = 127.0.0.1 -#port = 7659 -#destination = smtp.postman.i2p -#destinationport = 25 -#keys = smtp-keys.dat - -#[POP3] -#type = client -#address = 127.0.0.1 -#port = 7660 -#destination = pop.postman.i2p -#destinationport = 110 -#keys = pop3-keys.dat - -# see more examples at https://i2pd.readthedocs.io/en/latest/user-guide/tunnels/ diff --git a/android/assets/tunnels.d b/android/assets/tunnels.d deleted file mode 120000 index ff262031..00000000 --- a/android/assets/tunnels.d +++ /dev/null @@ -1 +0,0 @@ -../../contrib/tunnels.d \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle deleted file mode 100644 index ace2047a..00000000 --- a/android/build.gradle +++ /dev/null @@ -1,104 +0,0 @@ -buildscript { - repositories { - mavenCentral() - jcenter() - google() - } - dependencies { - classpath 'com.android.tools.build:gradle:3.4.2' - } -} - -apply plugin: 'com.android.application' - -repositories { - jcenter() - maven { - url 'https://maven.google.com' - } - google() -} - -dependencies { - implementation 'androidx.core:core:1.0.2' -} - -android { - compileSdkVersion 29 - buildToolsVersion "28.0.3" - defaultConfig { - applicationId "org.purplei2p.i2pd" - targetSdkVersion 29 - minSdkVersion 14 - versionCode 2330 - versionName "2.33.0" - setProperty("archivesBaseName", archivesBaseName + "-" + versionName) - ndk { - abiFilters 'armeabi-v7a' - abiFilters 'x86' - //abiFilters 'arm64-v8a' - //abiFilters 'x86_64' - } - externalNativeBuild { - ndkBuild { - arguments "-j3" - } - } - } - sourceSets { - main { - manifest.srcFile 'AndroidManifest.xml' - java.srcDirs = ['src'] - res.srcDirs = ['res'] - jniLibs.srcDirs = ['libs'] - assets.srcDirs = ['assets'] - } - } - splits { - abi { - // change that to true if you need splitted apk - enable true - reset() - //include "armeabi-v7a", "arm64-v8a", "x86", "x86_64" - include "armeabi-v7a", "x86" - universalApk true - } - } - signingConfigs { - orignal { - storeFile file("i2pdapk.jks") - storePassword "android" - keyAlias "i2pdapk" - keyPassword "android" - } - } - buildTypes { - release { - minifyEnabled false - signingConfig signingConfigs.orignal - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt' - } - } - externalNativeBuild { - ndkBuild { - path './jni/Android.mk' - } - } - compileOptions { - sourceCompatibility = '1.8' - targetCompatibility = '1.8' - } -} - -ext.abiCodes = ['armeabi-v7a':1, 'x86':2, 'arm64-v8a':3, 'x86_64':4] -import com.android.build.OutputFile - -android.applicationVariants.all { variant -> - variant.outputs.each { output -> - def baseAbiVersionCode = project.ext.abiCodes.get(output.getFilter(OutputFile.ABI)) - - if (baseAbiVersionCode != null) { - output.versionCodeOverride = baseAbiVersionCode + variant.versionCode - } - } -} diff --git a/android/build.xml b/android/build.xml deleted file mode 100644 index ed8196c3..00000000 --- a/android/build.xml +++ /dev/null @@ -1,96 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/android/gradle.properties b/android/gradle.properties deleted file mode 100644 index 6678daaf..00000000 --- a/android/gradle.properties +++ /dev/null @@ -1,3 +0,0 @@ -android.enableJetifier=true -android.useAndroidX=true -org.gradle.parallel=true \ No newline at end of file diff --git a/android/gradle/wrapper/gradle-wrapper.jar b/android/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index f6b961fd..00000000 Binary files a/android/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 3b803211..00000000 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,6 +0,0 @@ -#Tue Aug 20 14:39:08 MSK 2019 -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip diff --git a/android/gradlew b/android/gradlew deleted file mode 100755 index cccdd3d5..00000000 --- a/android/gradlew +++ /dev/null @@ -1,172 +0,0 @@ -#!/usr/bin/env sh - -############################################################################## -## -## Gradle start up script for UN*X -## -############################################################################## - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" - -warn () { - echo "$*" -} - -die () { - echo - echo "$*" - echo - exit 1 -} - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=$((i+1)) - done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac -fi - -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=$(save "$@") - -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" - -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi - -exec "$JAVACMD" "$@" diff --git a/android/gradlew.bat b/android/gradlew.bat deleted file mode 100644 index f9553162..00000000 --- a/android/gradlew.bat +++ /dev/null @@ -1,84 +0,0 @@ -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/android/jni/Android.mk b/android/jni/Android.mk deleted file mode 100755 index 07dc7080..00000000 --- a/android/jni/Android.mk +++ /dev/null @@ -1,73 +0,0 @@ -LOCAL_PATH := $(call my-dir) -include $(CLEAR_VARS) -LOCAL_MODULE := i2pd -LOCAL_CPP_FEATURES := rtti exceptions -LOCAL_C_INCLUDES += $(IFADDRS_PATH) $(LIB_SRC_PATH) $(LIB_CLIENT_SRC_PATH) $(DAEMON_SRC_PATH) -LOCAL_STATIC_LIBRARIES := \ - boost_system \ - boost_date_time \ - boost_filesystem \ - boost_program_options \ - crypto ssl \ - miniupnpc -LOCAL_LDLIBS := -lz - -LOCAL_SRC_FILES := DaemonAndroid.cpp i2pd_android.cpp $(IFADDRS_PATH)/ifaddrs.c \ - $(wildcard $(LIB_SRC_PATH)/*.cpp)\ - $(wildcard $(LIB_CLIENT_SRC_PATH)/*.cpp)\ - $(DAEMON_SRC_PATH)/Daemon.cpp \ - $(DAEMON_SRC_PATH)/UPnP.cpp \ - $(DAEMON_SRC_PATH)/HTTPServer.cpp \ - $(DAEMON_SRC_PATH)/I2PControl.cpp - -include $(BUILD_SHARED_LIBRARY) - -LOCAL_PATH := $(call my-dir) -include $(CLEAR_VARS) -LOCAL_MODULE := boost_system -LOCAL_SRC_FILES := $(BOOST_PATH)/boost-1_72_0/$(TARGET_ARCH_ABI)/lib/libboost_system.a -LOCAL_EXPORT_C_INCLUDES := $(BOOST_PATH)/boost-1_72_0/include -include $(PREBUILT_STATIC_LIBRARY) - -LOCAL_PATH := $(call my-dir) -include $(CLEAR_VARS) -LOCAL_MODULE := boost_date_time -LOCAL_SRC_FILES := $(BOOST_PATH)/boost-1_72_0/$(TARGET_ARCH_ABI)/lib/libboost_date_time.a -LOCAL_EXPORT_C_INCLUDES := $(BOOST_PATH)/boost-1_72_0/include -include $(PREBUILT_STATIC_LIBRARY) - -LOCAL_PATH := $(call my-dir) -include $(CLEAR_VARS) -LOCAL_MODULE := boost_filesystem -LOCAL_SRC_FILES := $(BOOST_PATH)/boost-1_72_0/$(TARGET_ARCH_ABI)/lib/libboost_filesystem.a -LOCAL_EXPORT_C_INCLUDES := $(BOOST_PATH)/boost-1_72_0/include -include $(PREBUILT_STATIC_LIBRARY) - -LOCAL_PATH := $(call my-dir) -include $(CLEAR_VARS) -LOCAL_MODULE := boost_program_options -LOCAL_SRC_FILES := $(BOOST_PATH)/boost-1_72_0/$(TARGET_ARCH_ABI)/lib/libboost_program_options.a -LOCAL_EXPORT_C_INCLUDES := $(BOOST_PATH)/boost-1_72_0/include -include $(PREBUILT_STATIC_LIBRARY) - -LOCAL_PATH := $(call my-dir) -include $(CLEAR_VARS) -LOCAL_MODULE := crypto -LOCAL_SRC_FILES := $(OPENSSL_PATH)/openssl-1.1.1d-clang/$(TARGET_ARCH_ABI)/lib/libcrypto.a -LOCAL_EXPORT_C_INCLUDES := $(OPENSSL_PATH)/openssl-1.1.1d-clang/include -include $(PREBUILT_STATIC_LIBRARY) - -LOCAL_PATH := $(call my-dir) -include $(CLEAR_VARS) -LOCAL_MODULE := ssl -LOCAL_SRC_FILES := $(OPENSSL_PATH)/openssl-1.1.1d-clang/$(TARGET_ARCH_ABI)/lib/libssl.a -LOCAL_EXPORT_C_INCLUDES := $(OPENSSL_PATH)/openssl-1.1.1d-clang/include -LOCAL_STATIC_LIBRARIES := crypto -include $(PREBUILT_STATIC_LIBRARY) - -LOCAL_PATH := $(call my-dir) -include $(CLEAR_VARS) -LOCAL_MODULE := miniupnpc -LOCAL_SRC_FILES := $(MINIUPNP_PATH)/miniupnpc-2.1/$(TARGET_ARCH_ABI)/lib/libminiupnpc.a -LOCAL_EXPORT_C_INCLUDES := $(MINIUPNP_PATH)/miniupnpc-2.1/include -include $(PREBUILT_STATIC_LIBRARY) diff --git a/android/jni/Application.mk b/android/jni/Application.mk deleted file mode 100755 index 0c291661..00000000 --- a/android/jni/Application.mk +++ /dev/null @@ -1,36 +0,0 @@ -#APP_ABI := armeabi-v7a x86 -#APP_PLATFORM := android-14 - -# ABI arm64-v8a and x86_64 supported only from platform-21 -#APP_ABI := arm64-v8a x86_64 -#APP_PLATFORM := android-21 - -NDK_TOOLCHAIN_VERSION := clang -#APP_STL := c++_shared -APP_STL := c++_static - -# Enable c++17 extensions in source code -APP_CPPFLAGS += -std=c++17 -fexceptions -frtti - -APP_CPPFLAGS += -DANDROID -D__ANDROID__ -DUSE_UPNP -ifeq ($(TARGET_ARCH_ABI),armeabi-v7a) -APP_CPPFLAGS += -DANDROID_ARM7A -endif - -# git clone https://github.com/PurpleI2P/Boost-for-Android-Prebuilt.git -b boost-1_72_0 -# git clone https://github.com/PurpleI2P/OpenSSL-for-Android-Prebuilt.git -# git clone https://github.com/PurpleI2P/MiniUPnP-for-Android-Prebuilt.git -# git clone https://github.com/PurpleI2P/android-ifaddrs.git -# change to your own -I2PD_LIBS_PATH = /path/to/libraries -BOOST_PATH = $(I2PD_LIBS_PATH)/Boost-for-Android-Prebuilt -OPENSSL_PATH = $(I2PD_LIBS_PATH)/OpenSSL-for-Android-Prebuilt -MINIUPNP_PATH = $(I2PD_LIBS_PATH)/MiniUPnP-for-Android-Prebuilt -IFADDRS_PATH = $(I2PD_LIBS_PATH)/android-ifaddrs - -# don't change me -I2PD_SRC_PATH = $(PWD)/.. - -LIB_SRC_PATH = $(I2PD_SRC_PATH)/libi2pd -LIB_CLIENT_SRC_PATH = $(I2PD_SRC_PATH)/libi2pd_client -DAEMON_SRC_PATH = $(I2PD_SRC_PATH)/daemon diff --git a/android/jni/DaemonAndroid.cpp b/android/jni/DaemonAndroid.cpp deleted file mode 100644 index 39c06ea0..00000000 --- a/android/jni/DaemonAndroid.cpp +++ /dev/null @@ -1,222 +0,0 @@ -/* -* Copyright (c) 2013-2020, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - -#include -#include -#include -#include -#include -#include -//#include "mainwindow.h" -#include "FS.h" -#include "DaemonAndroid.h" -#include "Daemon.h" - -namespace i2p -{ -namespace android -{ -/* Worker::Worker (DaemonAndroidImpl& daemon): - m_Daemon (daemon) - { - } - - void Worker::startDaemon() - { - Log.d(TAG"Performing daemon start..."); - m_Daemon.start(); - Log.d(TAG"Daemon started."); - emit resultReady(); - } - void Worker::restartDaemon() - { - Log.d(TAG"Performing daemon restart..."); - m_Daemon.restart(); - Log.d(TAG"Daemon restarted."); - emit resultReady(); - } - void Worker::stopDaemon() { - Log.d(TAG"Performing daemon stop..."); - m_Daemon.stop(); - Log.d(TAG"Daemon stopped."); - emit resultReady(); - } - - Controller::Controller(DaemonAndroidImpl& daemon): - m_Daemon (daemon) - { - Worker *worker = new Worker (m_Daemon); - worker->moveToThread(&workerThread); - connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater); - connect(this, &Controller::startDaemon, worker, &Worker::startDaemon); - connect(this, &Controller::stopDaemon, worker, &Worker::stopDaemon); - connect(this, &Controller::restartDaemon, worker, &Worker::restartDaemon); - connect(worker, &Worker::resultReady, this, &Controller::handleResults); - workerThread.start(); - } - Controller::~Controller() - { - Log.d(TAG"Closing and waiting for daemon worker thread..."); - workerThread.quit(); - workerThread.wait(); - Log.d(TAG"Waiting for daemon worker thread finished."); - if(m_Daemon.isRunning()) - { - Log.d(TAG"Stopping the daemon..."); - m_Daemon.stop(); - Log.d(TAG"Stopped the daemon."); - } - } -*/ - std::string dataDir = ""; - - DaemonAndroidImpl::DaemonAndroidImpl () - //: - /*mutex(nullptr), */ - //m_IsRunning(false), - //m_RunningChangedCallback(nullptr) - { - } - - DaemonAndroidImpl::~DaemonAndroidImpl () - { - //delete mutex; - } - - bool DaemonAndroidImpl::init(int argc, char* argv[]) - { - //mutex=new QMutex(QMutex::Recursive); - //setRunningCallback(0); - //m_IsRunning=false; - - // make sure assets are ready before proceed - i2p::fs::DetectDataDir(dataDir, false); - int numAttempts = 0; - do - { - if (i2p::fs::Exists (i2p::fs::DataDirPath("assets.ready"))) break; // assets ready - numAttempts++; - std::this_thread::sleep_for (std::chrono::seconds(1)); // otherwise wait for 1 more second - } - while (numAttempts <= 10); // 10 seconds max - return Daemon.init(argc,argv); - } - - void DaemonAndroidImpl::start() - { - //QMutexLocker locker(mutex); - //setRunning(true); - Daemon.start(); - } - - void DaemonAndroidImpl::stop() - { - //QMutexLocker locker(mutex); - Daemon.stop(); - //setRunning(false); - } - - void DaemonAndroidImpl::restart() - { - //QMutexLocker locker(mutex); - stop(); - start(); - } - /* - void DaemonAndroidImpl::setRunningCallback(runningChangedCallback cb) - { - m_RunningChangedCallback = cb; - } - - bool DaemonAndroidImpl::isRunning() - { - return m_IsRunning; - } - - void DaemonAndroidImpl::setRunning(bool newValue) - { - bool oldValue = m_IsRunning; - if(oldValue!=newValue) - { - m_IsRunning = newValue; - if(m_RunningChangedCallback) - m_RunningChangedCallback(); - } - } -*/ - static DaemonAndroidImpl daemon; - static char* argv[1]={strdup("tmp")}; - /** - * returns error details if failed - * returns "ok" if daemon initialized and started okay - */ - std::string start(/*int argc, char* argv[]*/) - { - try - { - //int result; - - { - //Log.d(TAG"Initialising the daemon..."); - bool daemonInitSuccess = daemon.init(1,argv); - if(!daemonInitSuccess) - { - //QMessageBox::critical(0, "Error", "Daemon init failed"); - return "Daemon init failed"; - } - //Log.d(TAG"Initialised, creating the main window..."); - //MainWindow w; - //Log.d(TAG"Before main window.show()..."); - //w.show (); - - { - //i2p::qt::Controller daemonQtController(daemon); - //Log.d(TAG"Starting the daemon..."); - //emit daemonQtController.startDaemon(); - //daemon.start (); - //Log.d(TAG"Starting GUI event loop..."); - //result = app.exec(); - //daemon.stop (); - daemon.start(); - } - } - - //QMessageBox::information(&w, "Debug", "demon stopped"); - //Log.d(TAG"Exiting the application"); - //return result; - } - catch (boost::exception& ex) - { - std::stringstream ss; - ss << boost::diagnostic_information(ex); - return ss.str(); - } - catch (std::exception& ex) - { - std::stringstream ss; - ss << ex.what(); - return ss.str(); - } - catch(...) - { - return "unknown exception"; - } - return "ok"; - } - - void stop() - { - daemon.stop(); - } - - void SetDataDir(std::string jdataDir) - { - dataDir = jdataDir; - } -} -} diff --git a/android/jni/DaemonAndroid.h b/android/jni/DaemonAndroid.h deleted file mode 100644 index 912f6f49..00000000 --- a/android/jni/DaemonAndroid.h +++ /dev/null @@ -1,97 +0,0 @@ -/* -* Copyright (c) 2013-2020, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - -#ifndef DAEMON_ANDROID_H -#define DAEMON_ANDROID_H - -#include - -namespace i2p -{ -namespace android -{ - class DaemonAndroidImpl - { - public: - - DaemonAndroidImpl (); - ~DaemonAndroidImpl (); - - //typedef void (*runningChangedCallback)(); - - /** - * @return success - */ - bool init(int argc, char* argv[]); - void start(); - void stop(); - void restart(); - //void setRunningCallback(runningChangedCallback cb); - //bool isRunning(); - private: - //void setRunning(bool running); - private: - //QMutex* mutex; - //bool m_IsRunning; - //runningChangedCallback m_RunningChangedCallback; - }; - - /** - * returns "ok" if daemon init failed - * returns errinfo if daemon initialized and started okay - */ - std::string start(); - - // stops the daemon - void stop(); - - // set datadir received from jni - void SetDataDir(std::string jdataDir); - /* - class Worker : public QObject - { - Q_OBJECT - public: - - Worker (DaemonAndroidImpl& daemon); - - private: - - DaemonAndroidImpl& m_Daemon; - - public slots: - void startDaemon(); - void restartDaemon(); - void stopDaemon(); - - signals: - void resultReady(); - }; - - class Controller : public QObject - { - Q_OBJECT - QThread workerThread; - public: - Controller(DaemonAndroidImpl& daemon); - ~Controller(); - private: - DaemonAndroidImpl& m_Daemon; - - public slots: - void handleResults(){} - signals: - void startDaemon(); - void stopDaemon(); - void restartDaemon(); - }; - */ -} -} - -#endif // DAEMON_ANDROID_H diff --git a/android/jni/i2pd_android.cpp b/android/jni/i2pd_android.cpp deleted file mode 100755 index c6e309dd..00000000 --- a/android/jni/i2pd_android.cpp +++ /dev/null @@ -1,114 +0,0 @@ -/* -* Copyright (c) 2013-2020, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - -#include -#include "org_purplei2p_i2pd_I2PD_JNI.h" -#include "DaemonAndroid.h" -#include "RouterContext.h" -#include "ClientContext.h" -#include "Transports.h" -#include "Tunnel.h" - -JNIEXPORT jstring JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_getABICompiledWith - (JNIEnv *env, jclass clazz) { -#if defined(__arm__) - #if defined(__ARM_ARCH_7A__) - #if defined(__ARM_NEON__) - #if defined(__ARM_PCS_VFP) - #define ABI "armeabi-v7a/NEON (hard-float)" - #else - #define ABI "armeabi-v7a/NEON" - #endif - #else - #if defined(__ARM_PCS_VFP) - #define ABI "armeabi-v7a (hard-float)" - #else - #define ABI "armeabi-v7a" - #endif - #endif - #else - #define ABI "armeabi" - #endif - #elif defined(__i386__) - #define ABI "x86" - #elif defined(__x86_64__) - #define ABI "x86_64" - #elif defined(__mips64) /* mips64el-* toolchain defines __mips__ too */ - #define ABI "mips64" - #elif defined(__mips__) - #define ABI "mips" - #elif defined(__aarch64__) - #define ABI "arm64-v8a" - #else - #define ABI "unknown" -#endif - - return env->NewStringUTF(ABI); -} - -JNIEXPORT jstring JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_startDaemon - (JNIEnv *env, jclass clazz) { - return env->NewStringUTF(i2p::android::start().c_str()); -} - -JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_stopDaemon - (JNIEnv *env, jclass clazz) { - i2p::android::stop(); -} - -JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_stopAcceptingTunnels - (JNIEnv *env, jclass clazz) { - i2p::context.SetAcceptsTunnels (false); -} - -JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_startAcceptingTunnels - (JNIEnv *env, jclass clazz) { - i2p::context.SetAcceptsTunnels (true); -} - -JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_reloadTunnelsConfigs - (JNIEnv *env, jclass clazz) { - i2p::client::context.ReloadConfig(); -} - -JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_onNetworkStateChanged - (JNIEnv *env, jclass clazz, jboolean isConnected) { - bool isConnectedBool = (bool) isConnected; - i2p::transport::transports.SetOnline (isConnectedBool); -} - -JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_setDataDir - (JNIEnv *env, jclass clazz, jstring jdataDir) { - - /* - // Method 1: convert UTF-16 jstring to std::string (https://stackoverflow.com/a/41820336) - const jclass stringClass = env->GetObjectClass(jdataDir); - const jmethodID getBytes = env->GetMethodID(stringClass, "getBytes", "(Ljava/lang/String;)[B"); - const jbyteArray stringJbytes = (jbyteArray) env->CallObjectMethod(jdataDir, getBytes, env->NewStringUTF("UTF-8")); - - size_t length = (size_t) env->GetArrayLength(stringJbytes); - jbyte* pBytes = env->GetByteArrayElements(stringJbytes, NULL); - - std::string dataDir = std::string((char *)pBytes, length); - env->ReleaseByteArrayElements(stringJbytes, pBytes, JNI_ABORT); - - env->DeleteLocalRef(stringJbytes); - env->DeleteLocalRef(stringClass); */ - - // Method 2: get string chars and make char array. - auto dataDir = env->GetStringUTFChars(jdataDir, NULL); - env->ReleaseStringUTFChars(jdataDir, dataDir); - - // Set DataDir - i2p::android::SetDataDir(dataDir); -} - -JNIEXPORT jint JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_GetTransitTunnelsCount - (JNIEnv *env, jclass clazz) { - return i2p::tunnel::tunnels.CountTransitTunnels(); -} diff --git a/android/jni/org_purplei2p_i2pd_I2PD_JNI.h b/android/jni/org_purplei2p_i2pd_I2PD_JNI.h deleted file mode 100644 index 68935ad1..00000000 --- a/android/jni/org_purplei2p_i2pd_I2PD_JNI.h +++ /dev/null @@ -1,53 +0,0 @@ -/* -* Copyright (c) 2013-2020, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - -/* DO NOT EDIT THIS FILE - it is machine generated */ -#include -/* Header for class org_purplei2p_i2pd_I2PD_JNI */ - -#ifndef _Included_org_purplei2p_i2pd_I2PD_JNI -#define _Included_org_purplei2p_i2pd_I2PD_JNI -#ifdef __cplusplus -extern "C" { -#endif -/* - * Class: org_purplei2p_i2pd_I2PD_JNI - * Method: stringFromJNI - * Signature: ()Ljava/lang/String; - */ -JNIEXPORT jstring JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_getABICompiledWith - (JNIEnv *, jclass); - -JNIEXPORT jstring JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_startDaemon - (JNIEnv *, jclass); - -JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_stopDaemon - (JNIEnv *, jclass); - -JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_stopAcceptingTunnels - (JNIEnv *, jclass); - -JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_startAcceptingTunnels - (JNIEnv *, jclass); - -JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_reloadTunnelsConfigs - (JNIEnv *, jclass); - -JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_onNetworkStateChanged - (JNIEnv * env, jclass clazz, jboolean isConnected); - -JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_setDataDir - (JNIEnv *env, jclass clazz, jstring jdataDir); - -JNIEXPORT jint JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_GetTransitTunnelsCount - (JNIEnv *, jclass); - -#ifdef __cplusplus -} -#endif -#endif diff --git a/android/proguard-project.txt b/android/proguard-project.txt deleted file mode 100644 index f2fe1559..00000000 --- a/android/proguard-project.txt +++ /dev/null @@ -1,20 +0,0 @@ -# To enable ProGuard in your project, edit project.properties -# to define the proguard.config property as described in that file. -# -# Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in ${sdk.dir}/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the ProGuard -# include property in project.properties. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# Add any project specific keep options here: - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} diff --git a/android/project.properties b/android/project.properties deleted file mode 100644 index 82181932..00000000 --- a/android/project.properties +++ /dev/null @@ -1,14 +0,0 @@ -# This file is automatically generated by Android Tools. -# Do not modify this file -- YOUR CHANGES WILL BE ERASED! -# -# This file must be checked in Version Control Systems. -# -# To customize properties used by the Ant build system edit -# "ant.properties", and override values to adapt the script to your -# project structure. -# -# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): -#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt - -# Project target. -target=android-29 diff --git a/android/res/drawable/icon.png b/android/res/drawable/icon.png deleted file mode 100644 index 9a2f7404..00000000 Binary files a/android/res/drawable/icon.png and /dev/null differ diff --git a/android/res/drawable/itoopie_notification_icon.png b/android/res/drawable/itoopie_notification_icon.png deleted file mode 100644 index fa99e7fc..00000000 Binary files a/android/res/drawable/itoopie_notification_icon.png and /dev/null differ diff --git a/android/res/layout/activity_perms_asker.xml b/android/res/layout/activity_perms_asker.xml deleted file mode 100644 index 778c9ef5..00000000 --- a/android/res/layout/activity_perms_asker.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - \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()) + if (i2p::tunnel::tunnels.CountTransitTunnels()) { - s << "Transit tunnels:
\r\n
\r\n"; + s << "" << tr("Transit Tunnels") << ":
\r\n"; + s << ""; for (const auto& it: i2p::tunnel::tunnels.GetTransitTunnels ()) { - s << "
\r\n"; if (std::dynamic_pointer_cast(it)) - s << it->GetTunnelID () << " ⇒ "; + s << "
\r\n"; } - s << "\r\n"; + s << "
ID" << tr("Amount") << "
" << it->GetTunnelID () << ""; else if (std::dynamic_pointer_cast(it)) - s << " ⇒ " << it->GetTunnelID (); + s << "
" << it->GetTunnelID () << ""; else - s << " ⇒ " << it->GetTunnelID () << " ⇒ "; - s << " " << it->GetNumTransmittedBytes () << "\r\n"; + s << "
" << it->GetTunnelID () << ""; + ShowTraffic(s, it->GetNumTransmittedBytes ()); + s << "
\r\n"; } else { - s << "Transit tunnels: no transit tunnels currently built.
\r\n"; + s << "" << tr("Transit Tunnels") << ": " << tr(/* Message on transit tunnels page */ "no transit tunnels currently built") << ".
\r\n"; } } template - static void ShowNTCPTransports (std::stringstream& s, const Sessions& sessions, const std::string name) + static void ShowTransportSessions (std::stringstream& s, const Sessions& sessions, const std::string name) { - std::stringstream tmp_s, tmp_s6; uint16_t cnt = 0, cnt6 = 0; - for (const auto& it: sessions ) + 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) { - if (it.second && it.second->IsEstablished () && !it.second->GetSocket ().remote_endpoint ().address ().is_v6 ()) + 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.second->IsOutgoing ()) tmp_s << " ⇒ "; - tmp_s << i2p::data::GetIdentHashAbbreviation (it.second->GetRemoteIdentity ()->GetIdentHash ()) << ": " - << it.second->GetSocket ().remote_endpoint().address ().to_string (); - if (!it.second->IsOutgoing ()) tmp_s << " ⇒ "; - tmp_s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; + 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.second && it.second->IsEstablished () && it.second->GetSocket ().remote_endpoint ().address ().is_v6 ()) + if (it && it->IsEstablished () && endpoint.address ().is_v6 ()) { tmp_s6 << "
\r\n"; - if (it.second->IsOutgoing ()) tmp_s6 << " ⇒ "; - tmp_s6 << i2p::data::GetIdentHashAbbreviation (it.second->GetRemoteIdentity ()->GetIdentHash ()) << ": " - << "[" << it.second->GetSocket ().remote_endpoint().address ().to_string () << "]"; - if (!it.second->IsOutgoing ()) tmp_s6 << " ⇒ "; - tmp_s6 << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; + 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++; } @@ -731,53 +896,20 @@ namespace http { void ShowTransports (std::stringstream& s) { - s << "Transports:
\r\n"; + s << "" << tr("Transports") << ":
\r\n"; auto ntcp2Server = i2p::transport::transports.GetNTCP2Server (); if (ntcp2Server) { auto sessions = ntcp2Server->GetNTCP2Sessions (); if (!sessions.empty ()) - ShowNTCPTransports (s, sessions, "NTCP2"); + ShowTransportSessions (s, sessions, "NTCP2"); } - auto ssuServer = i2p::transport::transports.GetSSUServer (); - if (ssuServer) + auto ssu2Server = i2p::transport::transports.GetSSU2Server (); + if (ssu2Server) { - auto sessions = ssuServer->GetSessions (); + auto sessions = ssu2Server->GetSSU2Sessions (); if (!sessions.empty ()) - { - s << "
\r\n\r\n
"; - for (const auto& it: sessions) - { - s << "
\r\n"; - auto endpoint = it.second->GetRemoteEndpoint (); - if (it.second->IsOutgoing ()) s << " ⇒ "; - s << endpoint.address ().to_string () << ":" << endpoint.port (); - if (!it.second->IsOutgoing ()) s << " ⇒ "; - s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; - if (it.second->GetRelayTag ()) - s << " [itag:" << it.second->GetRelayTag () << "]"; - s << "
\r\n" << std::endl; - } - s << "
\r\n
\r\n"; - } - auto sessions6 = ssuServer->GetSessionsV6 (); - if (!sessions6.empty ()) - { - s << "
\r\n\r\n
"; - for (const auto& it: sessions6) - { - s << "
\r\n"; - auto endpoint = it.second->GetRemoteEndpoint (); - if (it.second->IsOutgoing ()) s << " ⇒ "; - s << "[" << endpoint.address ().to_string () << "]:" << endpoint.port (); - if (!it.second->IsOutgoing ()) s << " ⇒ "; - s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; - if (it.second->GetRelayTag ()) - s << " [itag:" << it.second->GetRelayTag () << "]"; - s << "
\r\n" << std::endl; - } - s << "
\r\n
\r\n"; - } + ShowTransportSessions (s, sessions, "SSU2"); } } @@ -787,46 +919,46 @@ namespace http { auto sam = i2p::client::context.GetSAMBridge (); if (!sam) { - ShowError(s, "SAM disabled"); + ShowError(s, tr("SAM disabled")); return; } - if(sam->GetSessions ().size ()) + if (sam->GetSessions ().size ()) { - s << "SAM Sessions:
\r\n
\r\n"; + s << "" << tr("SAM sessions") << ":
\r\n
\r\n"; for (auto& it: sam->GetSessions ()) { - auto& name = it.second->localDestination->GetNickname (); + auto& name = it.second->GetLocalDestination ()->GetNickname (); s << "\r\n" << std::endl; } s << "
\r\n"; } else - s << "SAM Sessions: no sessions currently running.
\r\n"; + s << "" << tr("SAM sessions") << ": " << tr(/* Message on SAM sessions page */ "no sessions currently running") << ".
\r\n"; } - static void ShowSAMSession (std::stringstream& s, const std::string& id) + void ShowSAMSession (std::stringstream& s, const std::string& id) { auto sam = i2p::client::context.GetSAMBridge (); if (!sam) { - ShowError(s, "SAM disabled"); + ShowError(s, tr("SAM disabled")); return; } auto session = sam->FindSession (id); if (!session) { - ShowError(s, "SAM session not found"); + ShowError(s, tr("SAM session not found")); return; } std::string webroot; i2p::config::GetOption("http.webroot", webroot); - s << "SAM Session:
\r\n
\r\n"; - auto& ident = session->localDestination->GetIdentHash(); + s << "" << tr("SAM Session") << ":
\r\n
\r\n"; + auto& ident = session->GetLocalDestination ()->GetIdentHash(); s << "\r\n"; s << "
\r\n"; - s << "Streams:
\r\n
\r\n"; + s << "" << tr("Streams") << ":
\r\n
\r\n"; for (const auto& it: sam->ListSockets(id)) { s << "
"; @@ -835,6 +967,7 @@ namespace http { 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() << "]"; @@ -846,38 +979,46 @@ namespace http { void ShowI2PTunnels (std::stringstream& s) { std::string webroot; i2p::config::GetOption("http.webroot", webroot); - s << "Client Tunnels:
\r\n
\r\n"; - for (auto& it: i2p::client::context.GetClientTunnels ()) - { - auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); - s << "
"; - s << it.second->GetName () << " ⇐ "; - s << i2p::client::context.GetAddressBook ().ToAddress(ident); - s << "
\r\n"<< std::endl; - } + + auto& clientTunnels = i2p::client::context.GetClientTunnels (); auto httpProxy = i2p::client::context.GetHttpProxy (); - if (httpProxy) - { - auto& ident = httpProxy->GetLocalDestination ()->GetIdentHash(); - s << "
"; - s << "HTTP Proxy" << " ⇐ "; - s << i2p::client::context.GetAddressBook ().ToAddress(ident); - s << "
\r\n"<< std::endl; - } auto socksProxy = i2p::client::context.GetSocksProxy (); - if (socksProxy) + if (!clientTunnels.empty () || httpProxy || socksProxy) { - auto& ident = socksProxy->GetLocalDestination ()->GetIdentHash(); - s << "
"; - s << "SOCKS Proxy" << " ⇐ "; - s << i2p::client::context.GetAddressBook ().ToAddress(ident); - s << "
\r\n"<< std::endl; + s << "" << tr("Client Tunnels") << ":
\r\n
\r\n"; + if (!clientTunnels.empty ()) + { + for (auto& it: clientTunnels) + { + auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); + s << "
"; + s << it.second->GetName () << " ⇐ "; + s << i2p::client::context.GetAddressBook ().ToAddress(ident); + s << "
\r\n"<< std::endl; + } + } + if (httpProxy) + { + auto& ident = httpProxy->GetLocalDestination ()->GetIdentHash(); + s << "
"; + s << "HTTP " << tr("Proxy") << " ⇐ "; + s << i2p::client::context.GetAddressBook ().ToAddress(ident); + s << "
\r\n"<< std::endl; + } + if (socksProxy) + { + auto& ident = socksProxy->GetLocalDestination ()->GetIdentHash(); + s << "
"; + s << "SOCKS " << tr("Proxy") << " ⇐ "; + s << i2p::client::context.GetAddressBook ().ToAddress(ident); + s << "
\r\n"<< std::endl; + } + s << "
\r\n"; } - s << "
\r\n"; auto& serverTunnels = i2p::client::context.GetServerTunnels (); if (!serverTunnels.empty ()) { - s << "
\r\nServer Tunnels:
\r\n
\r\n"; + s << "
\r\n" << tr("Server Tunnels") << ":
\r\n
\r\n"; for (auto& it: serverTunnels) { auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); @@ -893,7 +1034,7 @@ namespace http { auto& clientForwards = i2p::client::context.GetClientForwards (); if (!clientForwards.empty ()) { - s << "
\r\nClient Forwards:
\r\n
\r\n"; + s << "
\r\n" << tr("Client Forwards") << ":
\r\n
\r\n"; for (auto& it: clientForwards) { auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); @@ -907,11 +1048,11 @@ namespace http { auto& serverForwards = i2p::client::context.GetServerForwards (); if (!serverForwards.empty ()) { - s << "
\r\nServer Forwards:
\r\n
\r\n"; + s << "
\r\n" << tr("Server Forwards") << ":
\r\n
\r\n"; for (auto& it: serverForwards) { auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); - s << ""; + s << "
"; s << it.second->GetName () << " ⇐ "; s << i2p::client::context.GetAddressBook ().ToAddress(ident); s << "
\r\n"<< std::endl; @@ -920,16 +1061,6 @@ namespace http { } } - 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; - } - HTTPConnection::HTTPConnection (std::string hostname, std::shared_ptr socket): m_Socket (socket), m_BufferLen (0), expected_host(hostname) { @@ -999,7 +1130,7 @@ namespace http { if (expected == provided) return true; } - LogPrint(eLogWarning, "HTTPServer: auth failure from ", m_Socket->remote_endpoint().address ()); + LogPrint(eLogWarning, "HTTPServer: Auth failure from ", m_Socket->remote_endpoint().address ()); return false; } @@ -1009,7 +1140,7 @@ namespace http { std::string content; HTTPRes res; - LogPrint(eLogDebug, "HTTPServer: request: ", req.uri); + LogPrint(eLogDebug, "HTTPServer: Request: ", req.uri); if (needAuth && !CheckAuth(req)) { res.code = 401; @@ -1017,6 +1148,7 @@ namespace http { SendReply(res, content); return; } + bool strictheaders; i2p::config::GetOption("http.strictheaders", strictheaders); if (strictheaders) @@ -1039,7 +1171,8 @@ namespace http { return; } } - // Html5 head start + + // HTML head start ShowPageHead (s); if (req.uri.find("page=") != std::string::npos) { HandlePage (req, res, s); @@ -1056,6 +1189,8 @@ namespace http { SendReply (res, content); } + std::map HTTPConnection::m_Tokens; + uint32_t HTTPConnection::CreateToken () { uint32_t token; @@ -1113,7 +1248,7 @@ namespace http { ShowLeasesSets(s); else { res.code = 400; - ShowError(s, "Unknown page: " + page); + ShowError(s, tr("Unknown page") + ": " + page); return; } } @@ -1127,19 +1262,19 @@ namespace http { url.parse_query(params); std::string webroot; i2p::config::GetOption("http.webroot", webroot); - std::string redirect = "5; url=" + webroot + "?page=commands"; + 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, "Invalid token"); + 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_CONFIG) + else if (cmd == HTTP_COMMAND_RELOAD_TUNNELS_CONFIG) i2p::client::context.ReloadConfig (); else if (cmd == HTTP_COMMAND_ENABLE_TRANSIT) i2p::context.SetAcceptsTunnels (true); @@ -1157,7 +1292,7 @@ namespace http { else if (cmd == HTTP_COMMAND_SHUTDOWN_CANCEL) { i2p::context.SetAcceptsTunnels (true); -#if ((!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) || defined(ANDROID_BINARY)) +#if ((!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) || defined(ANDROID_BINARY)) Daemon.gracefulShutdownInterval = 0; #elif defined(WIN32_APP) i2p::win32::StopGracefulShutdown (); @@ -1189,46 +1324,137 @@ namespace http { { if (dest) { - if(dest->DeleteStream (streamID)) - s << "SUCCESS: Stream closed

\r\n"; + if (dest->DeleteStream (streamID)) + s << "" << tr("SUCCESS") << ": " << tr("Stream closed") << "
\r\n
\r\n"; else - s << "ERROR: Stream not found or already was closed

\r\n"; + s << "" << tr("ERROR") << ": " << tr("Stream not found or already was closed") << "
\r\n
\r\n"; } else - s << "ERROR: Destination not found

\r\n"; + s << "" << tr("ERROR") << ": " << tr("Destination not found") << "
\r\n
\r\n"; } else - s << "ERROR: StreamID can be null

\r\n"; + s << "" << tr("ERROR") << ": " << tr("StreamID can't be null") << "
\r\n
\r\n"; - s << "Return to destination page
\r\n"; - s << "

You will be redirected back in 5 seconds"; - redirect = "5; url=" + webroot + "?page=local_destination&b32=" + b32; + 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 <= 65535) - SetMaxNumTransitTunnels (limit); + if (limit > 0 && limit <= TRANSIT_TUNNELS_LIMIT) + i2p::tunnel::tunnels.SetMaxNumTransitTunnels (limit); else { - s << "ERROR: Transit tunnels count must not exceed 65535

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

You will be redirected back in 5 seconds"; + 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, "Unknown command: " + cmd); + ShowError(s, tr("Unknown command") + ": " + cmd); return; } - s << "SUCCESS: Command accepted

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

You will be redirected in 5 seconds"; + 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()); } @@ -1274,19 +1500,27 @@ namespace http { pass[i] = alnum[random[i] % (sizeof(alnum) - 1)]; } i2p::config::SetOption("http.pass", pass); - LogPrint(eLogInfo, "HTTPServer: password set to ", pass); + LogPrint(eLogInfo, "HTTPServer: Password set to ", pass); } m_IsRunning = true; m_Thread.reset (new std::thread (std::bind (&HTTPServer::Run, this))); m_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) { @@ -1297,6 +1531,8 @@ namespace http { void HTTPServer::Run () { + i2p::util::SetThreadName("Webconsole"); + while (m_IsRunning) { try @@ -1305,7 +1541,7 @@ namespace http { } catch (std::exception& ex) { - LogPrint (eLogError, "HTTPServer: runtime exception: ", ex.what ()); + LogPrint (eLogError, "HTTPServer: Runtime exception: ", ex.what ()); } } } @@ -1320,15 +1556,13 @@ namespace http { void HTTPServer::HandleAccept(const boost::system::error_code& ecode, std::shared_ptr newSocket) { - if (ecode) + if (!ecode) + CreateConnection(newSocket); + else { - if(newSocket) newSocket->close(); - LogPrint(eLogError, "HTTP Server: error handling accept ", ecode.message()); - if(ecode != boost::asio::error::operation_aborted) - Accept(); - return; + if (newSocket) newSocket->close(); + LogPrint(eLogError, "HTTP Server: Error handling accept: ", ecode.message()); } - CreateConnection(newSocket); Accept (); } diff --git a/daemon/HTTPServer.h b/daemon/HTTPServer.h index a977e3e8..f41d925d 100644 --- a/daemon/HTTPServer.h +++ b/daemon/HTTPServer.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -24,6 +24,8 @@ namespace http { const size_t HTTP_CONNECTION_BUFFER_SIZE = 8192; const int TOKEN_EXPIRATION_TIMEOUT = 30; // in seconds + const int COMMAND_REDIRECT_TIMEOUT = 5; // in seconds + const int TRANSIT_TUNNELS_LIMIT = 65535; class HTTPConnection: public std::enable_shared_from_this { @@ -98,6 +100,8 @@ namespace http 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 diff --git a/daemon/HTTPServerResources.h b/daemon/HTTPServerResources.h new file mode 100644 index 00000000..1e5b6f75 --- /dev/null +++ b/daemon/HTTPServerResources.h @@ -0,0 +1,97 @@ +/* +* Copyright (c) 2013-2023, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#ifndef HTTP_SERVER_RESOURCES_H__ +#define HTTP_SERVER_RESOURCES_H__ + +namespace i2p +{ +namespace http +{ + const std::string itoopieFavicon = + "data:image/png;base64," + "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACx" + "jwv8YQUAAAAJcEhZcwAALiIAAC4iAari3ZIAAAAHdElNRQfgCQsUNSZrkhi1AAAAGXRFWHRTb2Z0" + "d2FyZQBwYWludC5uZXQgNC4wLjEyQwRr7AAAAoJJREFUOE9jwAUqi4Q1oEwwcDTV1+5sETaBclGB" + "vb09C5QJB6kWpvFQJoOCeLC5kmjEHCgXE2SlyETLi3h6QrkM4VL+ssWSCZUgtopITLKqaOotRTEn" + "cbAkLqAkGtOqLBLVAWLXyWSVFkkmRiqLxuaqiWb/VBYJMAYrwgckJY25VEUzniqKhjU2y+RtCRSP" + "6lUXy/1jIBV5tlYxZUaFVMq2NInwIi9hO8fSfOEAqDZUoCwal6MulvOvyS7gi69K4j9zxZT/m0ps" + "/28ptvvvquXXryIa7QYMMdTwqi0WNtVi0GIDseXl7TnUxFKfnGlxAGp0+D8j2eH/8Ub7/9e7nf7X" + "+Af/B7rwt6pI0h0l0WhQADOC9DBkhSirpImHNVZKp24ukkyoshGLnN8d5fA/y13t/44Kq/8hlnL/" + "z7fZ/58f6vcxSNpbVUVFhV1RLNBVTsQzVYZPSwhsCAhkiIfpNMrkbO6TLf071Sfk/5ZSi/+7q6z/" + "P5ns+v9mj/P/CpuI/20y+aeNGYxZoVoYGmsF3aFMBAAZlCwftnF9ke3//bU2//fXWP8/UGv731Am" + "+V+DdNblSqnUYqhSTKAiYSOqJBrVqiaa+S3UNPr/gmyH/xuKXf63hnn/B8bIP0UxHfEyyeSNQKVM" + "EB1AEB2twhcTLp+gIBJUoyKasEpVJHmqskh8qryovUG/ffCHHRU2q/Tk/YuB6eGPsbExa7ZkpLu1" + "oLEcVDtuUCgV1w60rQzElpRUE1EVSX0BYidHiInXF4nagNhYQW60EF+ApH1ktni0A1SIITSUgVlZ" + "JHYnlIsfzJjIp9xZKswL5YKBHL+coKJoRDaUSzoozxHVrygQU4JykQADAwAT5b1NHtwZugAAAABJ" + "RU5ErkJggg=="; + + // bundled style sheet + const std::string internalCSS = + "\r\n"; + + // for external style sheet + std::string externalCSS; + +} // http +} // i2p + +#endif /* HTTP_SERVER_RESOURCES_H__ */ diff --git a/daemon/I2PControl.cpp b/daemon/I2PControl.cpp index e8c6e031..da2443fd 100644 --- a/daemon/I2PControl.cpp +++ b/daemon/I2PControl.cpp @@ -1,26 +1,30 @@ +/* +* Copyright (c) 2013-2022, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include #include #include #include -#include + +// Use global placeholders from boost introduced when local_time.hpp is loaded +#define BOOST_BIND_GLOBAL_PLACEHOLDERS + #include #include -#include #include +#include -#include "Crypto.h" #include "FS.h" #include "Log.h" #include "Config.h" #include "NetDb.hpp" -#include "RouterContext.h" -#include "Daemon.h" #include "Tunnel.h" -#include "Timestamp.h" -#include "Transports.h" -#include "version.h" -#include "util.h" -#include "ClientContext.h" +#include "Daemon.h" #include "I2PControl.h" namespace i2p @@ -44,58 +48,31 @@ namespace client 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"); + 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); + LogPrint(eLogDebug, "I2PControl: Using cert from ", i2pcp_crt); } m_SSLContext.set_options (boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2 | boost::asio::ssl::context::single_dh_use); m_SSLContext.use_certificate_file (i2pcp_crt, boost::asio::ssl::context::pem); m_SSLContext.use_private_key_file (i2pcp_key, boost::asio::ssl::context::pem); // handlers - m_MethodHandlers["Authenticate"] = &I2PControlService::AuthenticateHandler; - m_MethodHandlers["Echo"] = &I2PControlService::EchoHandler; - m_MethodHandlers["I2PControl"] = &I2PControlService::I2PControlHandler; - m_MethodHandlers["RouterInfo"] = &I2PControlService::RouterInfoHandler; - m_MethodHandlers["RouterManager"] = &I2PControlService::RouterManagerHandler; - m_MethodHandlers["NetworkSetting"] = &I2PControlService::NetworkSettingHandler; - m_MethodHandlers["ClientServicesInfo"] = &I2PControlService::ClientServicesInfoHandler; + 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; - // RouterInfo - m_RouterInfoHandlers["i2p.router.uptime"] = &I2PControlService::UptimeHandler; - m_RouterInfoHandlers["i2p.router.version"] = &I2PControlService::VersionHandler; - m_RouterInfoHandlers["i2p.router.status"] = &I2PControlService::StatusHandler; - m_RouterInfoHandlers["i2p.router.netdb.knownpeers"] = &I2PControlService::NetDbKnownPeersHandler; - m_RouterInfoHandlers["i2p.router.netdb.activepeers"] = &I2PControlService::NetDbActivePeersHandler; - m_RouterInfoHandlers["i2p.router.net.bw.inbound.1s"] = &I2PControlService::InboundBandwidth1S; - m_RouterInfoHandlers["i2p.router.net.bw.outbound.1s"] = &I2PControlService::OutboundBandwidth1S; - m_RouterInfoHandlers["i2p.router.net.status"] = &I2PControlService::NetStatusHandler; - m_RouterInfoHandlers["i2p.router.net.tunnels.participating"] = &I2PControlService::TunnelsParticipatingHandler; - m_RouterInfoHandlers["i2p.router.net.tunnels.successrate"] = -&I2PControlService::TunnelsSuccessRateHandler; - m_RouterInfoHandlers["i2p.router.net.total.received.bytes"] = &I2PControlService::NetTotalReceivedBytes; - m_RouterInfoHandlers["i2p.router.net.total.sent.bytes"] = &I2PControlService::NetTotalSentBytes; - // RouterManager m_RouterManagerHandlers["Reseed"] = &I2PControlService::ReseedHandler; m_RouterManagerHandlers["Shutdown"] = &I2PControlService::ShutdownHandler; m_RouterManagerHandlers["ShutdownGraceful"] = &I2PControlService::ShutdownGracefulHandler; - - // NetworkSetting - m_NetworkSettingHandlers["i2p.router.net.bw.in"] = &I2PControlService::InboundBandwidthLimit; - m_NetworkSettingHandlers["i2p.router.net.bw.out"] = &I2PControlService::OutboundBandwidthLimit; - - // ClientServicesInfo - m_ClientServicesInfoHandlers["I2PTunnel"] = &I2PControlService::I2PTunnelInfoHandler; - m_ClientServicesInfoHandlers["HTTPProxy"] = &I2PControlService::HTTPProxyInfoHandler; - m_ClientServicesInfoHandlers["SOCKS"] = &I2PControlService::SOCKSInfoHandler; - m_ClientServicesInfoHandlers["SAM"] = &I2PControlService::SAMInfoHandler; - m_ClientServicesInfoHandlers["BOB"] = &I2PControlService::BOBInfoHandler; - m_ClientServicesInfoHandlers["I2CP"] = &I2PControlService::I2CPInfoHandler; } I2PControlService::~I2PControlService () @@ -131,12 +108,14 @@ namespace client void I2PControlService::Run () { + i2p::util::SetThreadName("I2PC"); + while (m_IsRunning) { try { m_Service.run (); } catch (std::exception& ex) { - LogPrint (eLogError, "I2PControl: runtime exception: ", ex.what ()); + LogPrint (eLogError, "I2PControl: Runtime exception: ", ex.what ()); } } } @@ -154,10 +133,10 @@ namespace client Accept (); if (ecode) { - LogPrint (eLogError, "I2PControl: accept error: ", ecode.message ()); + LogPrint (eLogError, "I2PControl: Accept error: ", ecode.message ()); return; } - LogPrint (eLogDebug, "I2PControl: new request from ", socket->lowest_layer ().remote_endpoint ()); + LogPrint (eLogDebug, "I2PControl: New request from ", socket->lowest_layer ().remote_endpoint ()); Handshake (socket); } @@ -170,7 +149,7 @@ namespace client void I2PControlService::HandleHandshake (const boost::system::error_code& ecode, std::shared_ptr socket) { if (ecode) { - LogPrint (eLogError, "I2PControl: handshake error: ", ecode.message ()); + LogPrint (eLogError, "I2PControl: Handshake error: ", ecode.message ()); return; } //std::this_thread::sleep_for (std::chrono::milliseconds(5)); @@ -196,7 +175,7 @@ namespace client { if (ecode) { - LogPrint (eLogError, "I2PControl: read error: ", ecode.message ()); + LogPrint (eLogError, "I2PControl: Read error: ", ecode.message ()); return; } else @@ -219,7 +198,7 @@ namespace client } if (ss.eof ()) { - LogPrint (eLogError, "I2PControl: malformed request, HTTP header expected"); + LogPrint (eLogError, "I2PControl: Malformed request, HTTP header expected"); return; // TODO: } std::streamoff rem = contentLength + ss.tellg () - bytes_transferred; // more bytes to read @@ -244,7 +223,7 @@ namespace client } else { - LogPrint (eLogWarning, "I2PControl: unknown method ", method); + LogPrint (eLogWarning, "I2PControl: Unknown method ", method); response << "{\"id\":null,\"error\":"; response << "{\"code\":-32601,\"message\":\"Method not found\"},"; response << "\"jsonrpc\":\"2.0\"}"; @@ -253,7 +232,7 @@ namespace client } catch (std::exception& ex) { - LogPrint (eLogError, "I2PControl: exception when handle request: ", ex.what ()); + LogPrint (eLogError, "I2PControl: Exception when handle request: ", ex.what ()); std::ostringstream response; response << "{\"id\":null,\"error\":"; response << "{\"code\":-32700,\"message\":\"" << ex.what () << "\"},"; @@ -262,37 +241,11 @@ namespace client } catch (...) { - LogPrint (eLogError, "I2PControl: handle request unknown exception"); + LogPrint (eLogError, "I2PControl: Handle request unknown exception"); } } } - void I2PControlService::InsertParam (std::ostringstream& ss, const std::string& name, int value) const - { - ss << "\"" << name << "\":" << value; - } - - void I2PControlService::InsertParam (std::ostringstream& ss, const std::string& name, const std::string& value) const - { - ss << "\"" << name << "\":"; - if (value.length () > 0) - ss << "\"" << value << "\""; - else - ss << "null"; - } - - void I2PControlService::InsertParam (std::ostringstream& ss, const std::string& name, double value) const - { - ss << "\"" << name << "\":" << std::fixed << std::setprecision(2) << value; - } - - void I2PControlService::InsertParam (std::ostringstream& ss, const std::string& name, const boost::property_tree::ptree& value) const - { - std::ostringstream buf; - boost::property_tree::write_json (buf, value, false); - ss << "\"" << name << "\":" << buf.str(); - } - void I2PControlService::SendResponse (std::shared_ptr socket, std::shared_ptr buf, std::ostringstream& response, bool isHtml) { @@ -323,7 +276,7 @@ namespace client std::shared_ptr socket, std::shared_ptr buf) { if (ecode) { - LogPrint (eLogError, "I2PControl: write error: ", ecode.message ()); + LogPrint (eLogError, "I2PControl: Write error: ", ecode.message ()); } } @@ -373,96 +326,11 @@ namespace client void I2PControlService::PasswordHandler (const std::string& value) { - LogPrint (eLogWarning, "I2PControl: new password=", value, ", to make it persistent you should update your config!"); + LogPrint (eLogWarning, "I2PControl: New password=", value, ", to make it persistent you should update your config!"); m_Password = value; m_Tokens.clear (); } -// RouterInfo - - void I2PControlService::RouterInfoHandler (const boost::property_tree::ptree& params, std::ostringstream& results) - { - bool first = true; - for (auto it = params.begin (); it != params.end (); it++) - { - LogPrint (eLogDebug, "I2PControl: RouterInfo request: ", it->first); - auto it1 = m_RouterInfoHandlers.find (it->first); - if (it1 != m_RouterInfoHandlers.end ()) - { - if (!first) results << ","; - else first = false; - (this->*(it1->second))(results); - } - else - LogPrint (eLogError, "I2PControl: RouterInfo unknown request ", it->first); - } - } - - void I2PControlService::UptimeHandler (std::ostringstream& results) - { - InsertParam (results, "i2p.router.uptime", (int)i2p::context.GetUptime ()*1000); - } - - void I2PControlService::VersionHandler (std::ostringstream& results) - { - InsertParam (results, "i2p.router.version", VERSION); - } - - void I2PControlService::StatusHandler (std::ostringstream& results) - { - auto dest = i2p::client::context.GetSharedLocalDestination (); - InsertParam (results, "i2p.router.status", (dest && dest->IsReady ()) ? "1" : "0"); - } - - void I2PControlService::NetDbKnownPeersHandler (std::ostringstream& results) - { - InsertParam (results, "i2p.router.netdb.knownpeers", i2p::data::netdb.GetNumRouters ()); - } - - void I2PControlService::NetDbActivePeersHandler (std::ostringstream& results) - { - InsertParam (results, "i2p.router.netdb.activepeers", (int)i2p::transport::transports.GetPeers ().size ()); - } - - void I2PControlService::NetStatusHandler (std::ostringstream& results) - { - InsertParam (results, "i2p.router.net.status", (int)i2p::context.GetStatus ()); - } - - void I2PControlService::TunnelsParticipatingHandler (std::ostringstream& results) - { - int transit = i2p::tunnel::tunnels.GetTransitTunnels ().size (); - InsertParam (results, "i2p.router.net.tunnels.participating", transit); - } - - void I2PControlService::TunnelsSuccessRateHandler (std::ostringstream& results) - { - int rate = i2p::tunnel::tunnels.GetTunnelCreationSuccessRate (); - InsertParam (results, "i2p.router.net.tunnels.successrate", rate); - } - - void I2PControlService::InboundBandwidth1S (std::ostringstream& results) - { - double bw = i2p::transport::transports.GetInBandwidth (); - InsertParam (results, "i2p.router.net.bw.inbound.1s", bw); - } - - void I2PControlService::OutboundBandwidth1S (std::ostringstream& results) - { - double bw = i2p::transport::transports.GetOutBandwidth (); - InsertParam (results, "i2p.router.net.bw.outbound.1s", bw); - } - - void I2PControlService::NetTotalReceivedBytes (std::ostringstream& results) - { - InsertParam (results, "i2p.router.net.total.received.bytes", (double)i2p::transport::transports.GetTotalReceivedBytes ()); - } - - void I2PControlService::NetTotalSentBytes (std::ostringstream& results) - { - InsertParam (results, "i2p.router.net.total.sent.bytes", (double)i2p::transport::transports.GetTotalSentBytes ()); - } - // RouterManager @@ -488,7 +356,7 @@ namespace client m_ShutdownTimer.expires_from_now (boost::posix_time::seconds(1)); // 1 second to make sure response has been sent m_ShutdownTimer.async_wait ( [](const boost::system::error_code& ecode) - { + { Daemon.running = 0; }); } @@ -502,7 +370,7 @@ namespace client m_ShutdownTimer.expires_from_now (boost::posix_time::seconds(timeout + 1)); // + 1 second m_ShutdownTimer.async_wait ( [](const boost::system::error_code& ecode) - { + { Daemon.running = 0; }); } @@ -514,37 +382,6 @@ namespace client i2p::data::netdb.Reseed (); } -// network setting - void I2PControlService::NetworkSettingHandler (const boost::property_tree::ptree& params, std::ostringstream& results) - { - for (auto it = params.begin (); it != params.end (); it++) - { - LogPrint (eLogDebug, "I2PControl: NetworkSetting request: ", it->first); - auto it1 = m_NetworkSettingHandlers.find (it->first); - if (it1 != m_NetworkSettingHandlers.end ()) { - if (it != params.begin ()) results << ","; - (this->*(it1->second))(it->second.data (), results); - } else - LogPrint (eLogError, "I2PControl: NetworkSetting unknown request: ", it->first); - } - } - - void I2PControlService::InboundBandwidthLimit (const std::string& value, std::ostringstream& results) - { - if (value != "null") - i2p::context.SetBandwidth (std::atoi(value.c_str())); - int bw = i2p::context.GetBandwidthLimit(); - InsertParam (results, "i2p.router.net.bw.in", bw); - } - - void I2PControlService::OutboundBandwidthLimit (const std::string& value, std::ostringstream& results) - { - if (value != "null") - i2p::context.SetBandwidth (std::atoi(value.c_str())); - int bw = i2p::context.GetBandwidthLimit(); - InsertParam (results, "i2p.router.net.bw.out", bw); - } - // certificate void I2PControlService::CreateCertificate (const char *crt_path, const char *key_path) { @@ -571,11 +408,11 @@ namespace client // save cert if ((f = fopen (crt_path, "wb")) != NULL) { - LogPrint (eLogInfo, "I2PControl: saving new cert to ", crt_path); + LogPrint (eLogInfo, "I2PControl: Saving new cert to ", crt_path); PEM_write_X509 (f, x509); fclose (f); } else { - LogPrint (eLogError, "I2PControl: can't write cert: ", strerror(errno)); + LogPrint (eLogError, "I2PControl: Can't write cert: ", strerror(errno)); } // save key @@ -584,187 +421,14 @@ namespace client PEM_write_PrivateKey (f, pkey, NULL, NULL, 0, NULL, NULL); fclose (f); } else { - LogPrint (eLogError, "I2PControl: can't write key: ", strerror(errno)); + LogPrint (eLogError, "I2PControl: Can't write key: ", strerror(errno)); } X509_free (x509); } else { - LogPrint (eLogError, "I2PControl: can't create RSA key for certificate"); + LogPrint (eLogError, "I2PControl: Can't create RSA key for certificate"); } EVP_PKEY_free (pkey); } - -// ClientServicesInfo - - void I2PControlService::ClientServicesInfoHandler (const boost::property_tree::ptree& params, std::ostringstream& results) - { - for (auto it = params.begin (); it != params.end (); it++) - { - LogPrint (eLogDebug, "I2PControl: ClientServicesInfo request: ", it->first); - auto it1 = m_ClientServicesInfoHandlers.find (it->first); - if (it1 != m_ClientServicesInfoHandlers.end ()) - { - if (it != params.begin ()) results << ","; - (this->*(it1->second))(results); - } - else - LogPrint (eLogError, "I2PControl: ClientServicesInfo unknown request ", it->first); - } - } - - void I2PControlService::I2PTunnelInfoHandler (std::ostringstream& results) - { - boost::property_tree::ptree pt; - boost::property_tree::ptree client_tunnels, server_tunnels; - - for (auto& it: i2p::client::context.GetClientTunnels ()) - { - auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); - boost::property_tree::ptree ct; - ct.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); - client_tunnels.add_child(it.second->GetName (), ct); - } - - auto& serverTunnels = i2p::client::context.GetServerTunnels (); - if (!serverTunnels.empty ()) { - for (auto& it: serverTunnels) - { - auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); - boost::property_tree::ptree st; - st.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); - st.put("port", it.second->GetLocalPort ()); - server_tunnels.add_child(it.second->GetName (), st); - } - } - - auto& clientForwards = i2p::client::context.GetClientForwards (); - if (!clientForwards.empty ()) - { - for (auto& it: clientForwards) - { - auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); - boost::property_tree::ptree ct; - ct.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); - client_tunnels.add_child(it.second->GetName (), ct); - } - } - - auto& serverForwards = i2p::client::context.GetServerForwards (); - if (!serverForwards.empty ()) - { - for (auto& it: serverForwards) - { - auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); - boost::property_tree::ptree st; - st.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); - server_tunnels.add_child(it.second->GetName (), st); - } - } - - pt.add_child("client", client_tunnels); - pt.add_child("server", server_tunnels); - - InsertParam (results, "I2PTunnel", pt); - } - - void I2PControlService::HTTPProxyInfoHandler (std::ostringstream& results) - { - boost::property_tree::ptree pt; - - auto httpProxy = i2p::client::context.GetHttpProxy (); - if (httpProxy) - { - auto& ident = httpProxy->GetLocalDestination ()->GetIdentHash(); - pt.put("enabled", true); - pt.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); - } - else - pt.put("enabled", false); - - InsertParam (results, "HTTPProxy", pt); - } - - void I2PControlService::SOCKSInfoHandler (std::ostringstream& results) - { - boost::property_tree::ptree pt; - - auto socksProxy = i2p::client::context.GetSocksProxy (); - if (socksProxy) - { - auto& ident = socksProxy->GetLocalDestination ()->GetIdentHash(); - pt.put("enabled", true); - pt.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); - } - else - pt.put("enabled", false); - - InsertParam (results, "SOCKS", pt); - } - - void I2PControlService::SAMInfoHandler (std::ostringstream& results) - { - boost::property_tree::ptree pt; - auto sam = i2p::client::context.GetSAMBridge (); - if (sam) - { - pt.put("enabled", true); - boost::property_tree::ptree sam_sessions; - for (auto& it: sam->GetSessions ()) - { - boost::property_tree::ptree sam_session, sam_session_sockets; - auto& name = it.second->localDestination->GetNickname (); - auto& ident = it.second->localDestination->GetIdentHash(); - sam_session.put("name", name); - sam_session.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); - - for (const auto& socket: sam->ListSockets(it.first)) - { - boost::property_tree::ptree stream; - stream.put("type", socket->GetSocketType ()); - stream.put("peer", socket->GetSocket ().remote_endpoint()); - - sam_session_sockets.push_back(std::make_pair("", stream)); - } - sam_session.add_child("sockets", sam_session_sockets); - sam_sessions.add_child(it.first, sam_session); - } - - pt.add_child("sessions", sam_sessions); - } - else - pt.put("enabled", false); - - InsertParam (results, "SAM", pt); - } - - void I2PControlService::BOBInfoHandler (std::ostringstream& results) - { - boost::property_tree::ptree pt; - auto bob = i2p::client::context.GetBOBCommandChannel (); - if (bob) - { - /* TODO more info */ - pt.put("enabled", true); - } - else - pt.put("enabled", false); - - InsertParam (results, "BOB", pt); - } - - void I2PControlService::I2CPInfoHandler (std::ostringstream& results) - { - boost::property_tree::ptree pt; - auto i2cp = i2p::client::context.GetI2CPServer (); - if (i2cp) - { - /* TODO more info */ - pt.put("enabled", true); - } - else - pt.put("enabled", false); - - InsertParam (results, "I2CP", pt); - } } } diff --git a/daemon/I2PControl.h b/daemon/I2PControl.h index d731c24e..af152631 100644 --- a/daemon/I2PControl.h +++ b/daemon/I2PControl.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -20,6 +20,7 @@ #include #include #include +#include "I2PControlHandlers.h" namespace i2p { @@ -32,7 +33,7 @@ namespace client const char I2P_CONTROL_CERTIFICATE_COMMON_NAME[] = "i2pd.i2pcontrol"; const char I2P_CONTROL_CERTIFICATE_ORGANIZATION[] = "Purple I2P"; - class I2PControlService + class I2PControlService: public I2PControlHandlers { typedef boost::asio::ssl::stream ssl_socket; @@ -63,61 +64,24 @@ namespace client private: - void InsertParam (std::ostringstream& ss, const std::string& name, int value) const; - void InsertParam (std::ostringstream& ss, const std::string& name, double value) const; - void InsertParam (std::ostringstream& ss, const std::string& name, const std::string& value) const; - void InsertParam (std::ostringstream& ss, const std::string& name, const boost::property_tree::ptree& value) const; - // methods typedef void (I2PControlService::*MethodHandler)(const boost::property_tree::ptree& params, std::ostringstream& results); void AuthenticateHandler (const boost::property_tree::ptree& params, std::ostringstream& results); void EchoHandler (const boost::property_tree::ptree& params, std::ostringstream& results); void I2PControlHandler (const boost::property_tree::ptree& params, std::ostringstream& results); - void RouterInfoHandler (const boost::property_tree::ptree& params, std::ostringstream& results); void RouterManagerHandler (const boost::property_tree::ptree& params, std::ostringstream& results); - void NetworkSettingHandler (const boost::property_tree::ptree& params, std::ostringstream& results); - void ClientServicesInfoHandler (const boost::property_tree::ptree& params, std::ostringstream& results); // I2PControl typedef void (I2PControlService::*I2PControlRequestHandler)(const std::string& value); void PasswordHandler (const std::string& value); - // RouterInfo - typedef void (I2PControlService::*RouterInfoRequestHandler)(std::ostringstream& results); - void UptimeHandler (std::ostringstream& results); - void VersionHandler (std::ostringstream& results); - void StatusHandler (std::ostringstream& results); - void NetDbKnownPeersHandler (std::ostringstream& results); - void NetDbActivePeersHandler (std::ostringstream& results); - void NetStatusHandler (std::ostringstream& results); - void TunnelsParticipatingHandler (std::ostringstream& results); - void TunnelsSuccessRateHandler (std::ostringstream& results); - void InboundBandwidth1S (std::ostringstream& results); - void OutboundBandwidth1S (std::ostringstream& results); - void NetTotalReceivedBytes (std::ostringstream& results); - void NetTotalSentBytes (std::ostringstream& results); - // RouterManager typedef void (I2PControlService::*RouterManagerRequestHandler)(std::ostringstream& results); void ShutdownHandler (std::ostringstream& results); void ShutdownGracefulHandler (std::ostringstream& results); void ReseedHandler (std::ostringstream& results); - // NetworkSetting - typedef void (I2PControlService::*NetworkSettingRequestHandler)(const std::string& value, std::ostringstream& results); - void InboundBandwidthLimit (const std::string& value, std::ostringstream& results); - void OutboundBandwidthLimit (const std::string& value, std::ostringstream& results); - - // ClientServicesInfo - typedef void (I2PControlService::*ClientServicesInfoRequestHandler)(std::ostringstream& results); - void I2PTunnelInfoHandler (std::ostringstream& results); - void HTTPProxyInfoHandler (std::ostringstream& results); - void SOCKSInfoHandler (std::ostringstream& results); - void SAMInfoHandler (std::ostringstream& results); - void BOBInfoHandler (std::ostringstream& results); - void I2CPInfoHandler (std::ostringstream& results); - private: std::string m_Password; @@ -132,10 +96,7 @@ namespace client std::map m_MethodHandlers; std::map m_I2PControlHandlers; - std::map m_RouterInfoHandlers; std::map m_RouterManagerHandlers; - std::map m_NetworkSettingHandlers; - std::map m_ClientServicesInfoHandlers; }; } } diff --git a/daemon/I2PControlHandlers.cpp b/daemon/I2PControlHandlers.cpp new file mode 100644 index 00000000..f3ea7f61 --- /dev/null +++ b/daemon/I2PControlHandlers.cpp @@ -0,0 +1,390 @@ +/* +* Copyright (c) 2013-2022, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#include +#define BOOST_BIND_GLOBAL_PLACEHOLDERS +#include +#include + +#include "Log.h" +#include "RouterContext.h" +#include "NetDb.hpp" +#include "Tunnel.h" +#include "Transports.h" +#include "version.h" +#include "ClientContext.h" +#include "I2PControlHandlers.h" + +namespace i2p +{ +namespace client +{ + I2PControlHandlers::I2PControlHandlers () + { + // RouterInfo + m_RouterInfoHandlers["i2p.router.uptime"] = &I2PControlHandlers::UptimeHandler; + m_RouterInfoHandlers["i2p.router.version"] = &I2PControlHandlers::VersionHandler; + m_RouterInfoHandlers["i2p.router.status"] = &I2PControlHandlers::StatusHandler; + m_RouterInfoHandlers["i2p.router.netdb.knownpeers"] = &I2PControlHandlers::NetDbKnownPeersHandler; + m_RouterInfoHandlers["i2p.router.netdb.activepeers"] = &I2PControlHandlers::NetDbActivePeersHandler; + m_RouterInfoHandlers["i2p.router.net.bw.inbound.1s"] = &I2PControlHandlers::InboundBandwidth1S; + m_RouterInfoHandlers["i2p.router.net.bw.inbound.15s"] = &I2PControlHandlers::InboundBandwidth15S; + m_RouterInfoHandlers["i2p.router.net.bw.outbound.1s"] = &I2PControlHandlers::OutboundBandwidth1S; + m_RouterInfoHandlers["i2p.router.net.bw.outbound.15s"] = &I2PControlHandlers::OutboundBandwidth15S; + m_RouterInfoHandlers["i2p.router.net.status"] = &I2PControlHandlers::NetStatusHandler; + m_RouterInfoHandlers["i2p.router.net.tunnels.participating"] = &I2PControlHandlers::TunnelsParticipatingHandler; + m_RouterInfoHandlers["i2p.router.net.tunnels.successrate"] = &I2PControlHandlers::TunnelsSuccessRateHandler; + m_RouterInfoHandlers["i2p.router.net.total.received.bytes"] = &I2PControlHandlers::NetTotalReceivedBytes; + m_RouterInfoHandlers["i2p.router.net.total.sent.bytes"] = &I2PControlHandlers::NetTotalSentBytes; + + // NetworkSetting + m_NetworkSettingHandlers["i2p.router.net.bw.in"] = &I2PControlHandlers::InboundBandwidthLimit; + m_NetworkSettingHandlers["i2p.router.net.bw.out"] = &I2PControlHandlers::OutboundBandwidthLimit; + + // ClientServicesInfo + m_ClientServicesInfoHandlers["I2PTunnel"] = &I2PControlHandlers::I2PTunnelInfoHandler; + m_ClientServicesInfoHandlers["HTTPProxy"] = &I2PControlHandlers::HTTPProxyInfoHandler; + m_ClientServicesInfoHandlers["SOCKS"] = &I2PControlHandlers::SOCKSInfoHandler; + m_ClientServicesInfoHandlers["SAM"] = &I2PControlHandlers::SAMInfoHandler; + m_ClientServicesInfoHandlers["BOB"] = &I2PControlHandlers::BOBInfoHandler; + m_ClientServicesInfoHandlers["I2CP"] = &I2PControlHandlers::I2CPInfoHandler; + } + + void I2PControlHandlers::InsertParam (std::ostringstream& ss, const std::string& name, int value) const + { + ss << "\"" << name << "\":" << value; + } + + void I2PControlHandlers::InsertParam (std::ostringstream& ss, const std::string& name, const std::string& value, bool quotes) const + { + ss << "\"" << name << "\":"; + if (value.length () > 0) + { + if (quotes) + ss << "\"" << value << "\""; + else + ss << value; + } + else + ss << "null"; + } + + void I2PControlHandlers::InsertParam (std::ostringstream& ss, const std::string& name, double value) const + { + ss << "\"" << name << "\":" << std::fixed << std::setprecision(2) << value; + } + + void I2PControlHandlers::InsertParam (std::ostringstream& ss, const std::string& name, const boost::property_tree::ptree& value) const + { + std::ostringstream buf; + boost::property_tree::write_json (buf, value, false); + ss << "\"" << name << "\":" << buf.str(); + } + +// RouterInfo + + void I2PControlHandlers::RouterInfoHandler (const boost::property_tree::ptree& params, std::ostringstream& results) + { + bool first = true; + for (auto it = params.begin (); it != params.end (); it++) + { + LogPrint (eLogDebug, "I2PControl: RouterInfo request: ", it->first); + auto it1 = m_RouterInfoHandlers.find (it->first); + if (it1 != m_RouterInfoHandlers.end ()) + { + if (!first) results << ","; + else first = false; + (this->*(it1->second))(results); + } + else + LogPrint (eLogError, "I2PControl: RouterInfo unknown request ", it->first); + } + } + + void I2PControlHandlers::UptimeHandler (std::ostringstream& results) + { + InsertParam (results, "i2p.router.uptime", std::to_string (i2p::context.GetUptime ()*1000LL), false); + } + + void I2PControlHandlers::VersionHandler (std::ostringstream& results) + { + InsertParam (results, "i2p.router.version", VERSION); + } + + void I2PControlHandlers::StatusHandler (std::ostringstream& results) + { + auto dest = i2p::client::context.GetSharedLocalDestination (); + InsertParam (results, "i2p.router.status", (dest && dest->IsReady ()) ? "1" : "0"); + } + + void I2PControlHandlers::NetDbKnownPeersHandler (std::ostringstream& results) + { + InsertParam (results, "i2p.router.netdb.knownpeers", i2p::data::netdb.GetNumRouters ()); + } + + void I2PControlHandlers::NetDbActivePeersHandler (std::ostringstream& results) + { + InsertParam (results, "i2p.router.netdb.activepeers", (int)i2p::transport::transports.GetPeers ().size ()); + } + + void I2PControlHandlers::NetStatusHandler (std::ostringstream& results) + { + InsertParam (results, "i2p.router.net.status", (int)i2p::context.GetStatus ()); + } + + void I2PControlHandlers::TunnelsParticipatingHandler (std::ostringstream& results) + { + int transit = i2p::tunnel::tunnels.GetTransitTunnels ().size (); + InsertParam (results, "i2p.router.net.tunnels.participating", transit); + } + + void I2PControlHandlers::TunnelsSuccessRateHandler (std::ostringstream& results) + { + int rate = i2p::tunnel::tunnels.GetTunnelCreationSuccessRate (); + InsertParam (results, "i2p.router.net.tunnels.successrate", rate); + } + + void I2PControlHandlers::InboundBandwidth1S (std::ostringstream& results) + { + double bw = i2p::transport::transports.GetInBandwidth (); + InsertParam (results, "i2p.router.net.bw.inbound.1s", bw); + } + + void I2PControlHandlers::InboundBandwidth15S (std::ostringstream& results) + { + double bw = i2p::transport::transports.GetInBandwidth15s (); + InsertParam (results, "i2p.router.net.bw.inbound.15s", bw); + } + + void I2PControlHandlers::OutboundBandwidth1S (std::ostringstream& results) + { + double bw = i2p::transport::transports.GetOutBandwidth (); + InsertParam (results, "i2p.router.net.bw.outbound.1s", bw); + } + + void I2PControlHandlers::OutboundBandwidth15S (std::ostringstream& results) + { + double bw = i2p::transport::transports.GetOutBandwidth15s (); + InsertParam (results, "i2p.router.net.bw.outbound.15s", bw); + } + + void I2PControlHandlers::NetTotalReceivedBytes (std::ostringstream& results) + { + InsertParam (results, "i2p.router.net.total.received.bytes", (double)i2p::transport::transports.GetTotalReceivedBytes ()); + } + + void I2PControlHandlers::NetTotalSentBytes (std::ostringstream& results) + { + InsertParam (results, "i2p.router.net.total.sent.bytes", (double)i2p::transport::transports.GetTotalSentBytes ()); + } + +// network setting + void I2PControlHandlers::NetworkSettingHandler (const boost::property_tree::ptree& params, std::ostringstream& results) + { + for (auto it = params.begin (); it != params.end (); it++) + { + LogPrint (eLogDebug, "I2PControl: NetworkSetting request: ", it->first); + auto it1 = m_NetworkSettingHandlers.find (it->first); + if (it1 != m_NetworkSettingHandlers.end ()) { + if (it != params.begin ()) results << ","; + (this->*(it1->second))(it->second.data (), results); + } else + LogPrint (eLogError, "I2PControl: NetworkSetting unknown request: ", it->first); + } + } + + void I2PControlHandlers::InboundBandwidthLimit (const std::string& value, std::ostringstream& results) + { + if (value != "null") + i2p::context.SetBandwidth (std::atoi(value.c_str())); + int bw = i2p::context.GetBandwidthLimit(); + InsertParam (results, "i2p.router.net.bw.in", bw); + } + + void I2PControlHandlers::OutboundBandwidthLimit (const std::string& value, std::ostringstream& results) + { + if (value != "null") + i2p::context.SetBandwidth (std::atoi(value.c_str())); + int bw = i2p::context.GetBandwidthLimit(); + InsertParam (results, "i2p.router.net.bw.out", bw); + } + +// ClientServicesInfo + + void I2PControlHandlers::ClientServicesInfoHandler (const boost::property_tree::ptree& params, std::ostringstream& results) + { + for (auto it = params.begin (); it != params.end (); it++) + { + LogPrint (eLogDebug, "I2PControl: ClientServicesInfo request: ", it->first); + auto it1 = m_ClientServicesInfoHandlers.find (it->first); + if (it1 != m_ClientServicesInfoHandlers.end ()) + { + if (it != params.begin ()) results << ","; + (this->*(it1->second))(results); + } + else + LogPrint (eLogError, "I2PControl: ClientServicesInfo unknown request ", it->first); + } + } + + void I2PControlHandlers::I2PTunnelInfoHandler (std::ostringstream& results) + { + boost::property_tree::ptree pt; + boost::property_tree::ptree client_tunnels, server_tunnels; + + for (auto& it: i2p::client::context.GetClientTunnels ()) + { + auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); + boost::property_tree::ptree ct; + ct.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); + client_tunnels.add_child(it.second->GetName (), ct); + } + + auto& serverTunnels = i2p::client::context.GetServerTunnels (); + if (!serverTunnels.empty ()) { + for (auto& it: serverTunnels) + { + auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); + boost::property_tree::ptree st; + st.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); + st.put("port", it.second->GetLocalPort ()); + server_tunnels.add_child(it.second->GetName (), st); + } + } + + auto& clientForwards = i2p::client::context.GetClientForwards (); + if (!clientForwards.empty ()) + { + for (auto& it: clientForwards) + { + auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); + boost::property_tree::ptree ct; + ct.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); + client_tunnels.add_child(it.second->GetName (), ct); + } + } + + auto& serverForwards = i2p::client::context.GetServerForwards (); + if (!serverForwards.empty ()) + { + for (auto& it: serverForwards) + { + auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); + boost::property_tree::ptree st; + st.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); + server_tunnels.add_child(it.second->GetName (), st); + } + } + + pt.add_child("client", client_tunnels); + pt.add_child("server", server_tunnels); + + InsertParam (results, "I2PTunnel", pt); + } + + void I2PControlHandlers::HTTPProxyInfoHandler (std::ostringstream& results) + { + boost::property_tree::ptree pt; + + auto httpProxy = i2p::client::context.GetHttpProxy (); + if (httpProxy) + { + auto& ident = httpProxy->GetLocalDestination ()->GetIdentHash(); + pt.put("enabled", true); + pt.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); + } + else + pt.put("enabled", false); + + InsertParam (results, "HTTPProxy", pt); + } + + void I2PControlHandlers::SOCKSInfoHandler (std::ostringstream& results) + { + boost::property_tree::ptree pt; + + auto socksProxy = i2p::client::context.GetSocksProxy (); + if (socksProxy) + { + auto& ident = socksProxy->GetLocalDestination ()->GetIdentHash(); + pt.put("enabled", true); + pt.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); + } + else + pt.put("enabled", false); + + InsertParam (results, "SOCKS", pt); + } + + void I2PControlHandlers::SAMInfoHandler (std::ostringstream& results) + { + boost::property_tree::ptree pt; + auto sam = i2p::client::context.GetSAMBridge (); + if (sam) + { + pt.put("enabled", true); + boost::property_tree::ptree sam_sessions; + for (auto& it: sam->GetSessions ()) + { + boost::property_tree::ptree sam_session, sam_session_sockets; + auto& name = it.second->GetLocalDestination ()->GetNickname (); + auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); + sam_session.put("name", name); + sam_session.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); + + for (const auto& socket: sam->ListSockets(it.first)) + { + boost::property_tree::ptree stream; + stream.put("type", socket->GetSocketType ()); + stream.put("peer", socket->GetSocket ().remote_endpoint()); + + sam_session_sockets.push_back(std::make_pair("", stream)); + } + sam_session.add_child("sockets", sam_session_sockets); + sam_sessions.add_child(it.first, sam_session); + } + + pt.add_child("sessions", sam_sessions); + } + else + pt.put("enabled", false); + + InsertParam (results, "SAM", pt); + } + + void I2PControlHandlers::BOBInfoHandler (std::ostringstream& results) + { + boost::property_tree::ptree pt; + auto bob = i2p::client::context.GetBOBCommandChannel (); + if (bob) + { + /* TODO more info */ + pt.put("enabled", true); + } + else + pt.put("enabled", false); + + InsertParam (results, "BOB", pt); + } + + void I2PControlHandlers::I2CPInfoHandler (std::ostringstream& results) + { + boost::property_tree::ptree pt; + auto i2cp = i2p::client::context.GetI2CPServer (); + if (i2cp) + { + /* TODO more info */ + pt.put("enabled", true); + } + else + pt.put("enabled", false); + + InsertParam (results, "I2CP", pt); + } +} +} diff --git a/daemon/I2PControlHandlers.h b/daemon/I2PControlHandlers.h new file mode 100644 index 00000000..d106f288 --- /dev/null +++ b/daemon/I2PControlHandlers.h @@ -0,0 +1,82 @@ +/* +* Copyright (c) 2013-2022, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#ifndef I2P_CONTROL_HANDLERS_H__ +#define I2P_CONTROL_HANDLERS_H__ + +#include +#include +#include +#include + +namespace i2p +{ +namespace client +{ + class I2PControlHandlers + { + public: + + I2PControlHandlers (); + + // methods + // TODO: make protected + void RouterInfoHandler (const boost::property_tree::ptree& params, std::ostringstream& results); + void NetworkSettingHandler (const boost::property_tree::ptree& params, std::ostringstream& results); + void ClientServicesInfoHandler (const boost::property_tree::ptree& params, std::ostringstream& results); + + protected: + + void InsertParam (std::ostringstream& ss, const std::string& name, int value) const; + void InsertParam (std::ostringstream& ss, const std::string& name, double value) const; + void InsertParam (std::ostringstream& ss, const std::string& name, const std::string& value, bool quotes = true) const; + void InsertParam (std::ostringstream& ss, const std::string& name, const boost::property_tree::ptree& value) const; + + private: + + // RouterInfo + typedef void (I2PControlHandlers::*RouterInfoRequestHandler)(std::ostringstream& results); + void UptimeHandler (std::ostringstream& results); + void VersionHandler (std::ostringstream& results); + void StatusHandler (std::ostringstream& results); + void NetDbKnownPeersHandler (std::ostringstream& results); + void NetDbActivePeersHandler (std::ostringstream& results); + void NetStatusHandler (std::ostringstream& results); + void TunnelsParticipatingHandler (std::ostringstream& results); + void TunnelsSuccessRateHandler (std::ostringstream& results); + void InboundBandwidth1S (std::ostringstream& results); + void InboundBandwidth15S (std::ostringstream& results); + void OutboundBandwidth1S (std::ostringstream& results); + void OutboundBandwidth15S (std::ostringstream& results); + void NetTotalReceivedBytes (std::ostringstream& results); + void NetTotalSentBytes (std::ostringstream& results); + + // NetworkSetting + typedef void (I2PControlHandlers::*NetworkSettingRequestHandler)(const std::string& value, std::ostringstream& results); + void InboundBandwidthLimit (const std::string& value, std::ostringstream& results); + void OutboundBandwidthLimit (const std::string& value, std::ostringstream& results); + + // ClientServicesInfo + typedef void (I2PControlHandlers::*ClientServicesInfoRequestHandler)(std::ostringstream& results); + void I2PTunnelInfoHandler (std::ostringstream& results); + void HTTPProxyInfoHandler (std::ostringstream& results); + void SOCKSInfoHandler (std::ostringstream& results); + void SAMInfoHandler (std::ostringstream& results); + void BOBInfoHandler (std::ostringstream& results); + void I2CPInfoHandler (std::ostringstream& results); + + private: + + std::map m_RouterInfoHandlers; + std::map m_NetworkSettingHandlers; + std::map m_ClientServicesInfoHandlers; + }; +} +} + +#endif diff --git a/daemon/UPnP.cpp b/daemon/UPnP.cpp index 92a41011..dbaf864a 100644 --- a/daemon/UPnP.cpp +++ b/daemon/UPnP.cpp @@ -29,7 +29,7 @@ namespace transport { if (m_IsRunning) { - LogPrint(eLogInfo, "UPnP: stopping"); + LogPrint(eLogInfo, "UPnP: Stopping"); m_IsRunning = false; m_Timer.cancel (); m_Service.stop (); @@ -46,7 +46,7 @@ namespace transport void UPnP::Start() { m_IsRunning = true; - LogPrint(eLogInfo, "UPnP: starting"); + LogPrint(eLogInfo, "UPnP: Starting"); m_Service.post (std::bind (&UPnP::Discover, this)); std::unique_lock l(m_StartedMutex); m_Thread.reset (new std::thread (std::bind (&UPnP::Run, this))); @@ -60,6 +60,8 @@ namespace transport void UPnP::Run () { + i2p::util::SetThreadName("UPnP"); + while (m_IsRunning) { try @@ -70,7 +72,7 @@ namespace transport } catch (std::exception& ex) { - LogPrint (eLogError, "UPnP: runtime exception: ", ex.what ()); + LogPrint (eLogError, "UPnP: Runtime exception: ", ex.what ()); PortMapping (); } } @@ -79,10 +81,10 @@ namespace transport void UPnP::Discover () { bool isError; - int err; + int err; #if ((MINIUPNPC_API_VERSION >= 8) || defined (UPNPDISCOVER_SUCCESS)) - err = UPNPDISCOVER_SUCCESS; + err = UPNPDISCOVER_SUCCESS; #if (MINIUPNPC_API_VERSION >= 14) m_Devlist = upnpDiscover (UPNP_RESPONSE_TIMEOUT, NULL, NULL, 0, 0, 2, &err); @@ -91,9 +93,9 @@ namespace transport #endif isError = err != UPNPDISCOVER_SUCCESS; -#else // MINIUPNPC_API_VERSION >= 8 - err = 0; - m_Devlist = upnpDiscover (UPNP_RESPONSE_TIMEOUT, NULL, NULL, 0); +#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 { @@ -104,33 +106,33 @@ namespace transport if (isError) { - LogPrint (eLogError, "UPnP: unable to discover Internet Gateway Devices: error ", err); + LogPrint (eLogError, "UPnP: Unable to discover Internet Gateway Devices: error ", err); return; } err = UPNP_GetValidIGD (m_Devlist, &m_upnpUrls, &m_upnpData, m_NetworkAddr, sizeof (m_NetworkAddr)); - m_upnpUrlsInitialized=err!=0; + m_upnpUrlsInitialized=err!=0; if (err == UPNP_IGD_VALID_CONNECTED) { - err = UPNP_GetExternalIPAddress (m_upnpUrls.controlURL, m_upnpData.first.servicetype, m_externalIPAddress); + 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); + LogPrint (eLogError, "UPnP: Unable to get external address: error ", err); return; } else { - LogPrint (eLogError, "UPnP: found Internet Gateway Device ", m_upnpUrls.controlURL); + LogPrint (eLogError, "UPnP: Found Internet Gateway Device ", m_upnpUrls.controlURL); if (!m_externalIPAddress[0]) { - LogPrint (eLogError, "UPnP: found Internet Gateway Device doesn't know our external address"); + LogPrint (eLogError, "UPnP: Found Internet Gateway Device doesn't know our external address"); return; } } } else { - LogPrint (eLogError, "UPnP: unable to find valid Internet Gateway Device: error ", err); + LogPrint (eLogError, "UPnP: Unable to find valid Internet Gateway Device: error ", err); return; } @@ -157,10 +159,11 @@ namespace transport void UPnP::PortMapping () { - const auto& a = context.GetRouterInfo().GetAddresses(); - for (const auto& address : a) + auto a = context.GetRouterInfo().GetAddresses(); + if (!a) return; + for (const auto& address : *a) { - if (!address->host.is_v6 () && address->port) + if (address && !address->host.is_v6 () && address->port) TryPortMapping (address); } m_Timer.expires_from_now (boost::posix_time::minutes(20)); // every 20 minutes @@ -181,7 +184,7 @@ namespace transport err = CheckMapping (strPort.c_str (), strType.c_str ()); if (err != UPNPCOMMAND_SUCCESS) // if mapping not found { - LogPrint (eLogDebug, "UPnP: possibly port ", strPort, " is not forwarded: return code ", err); + 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); @@ -190,42 +193,43 @@ namespace transport #endif if (err != UPNPCOMMAND_SUCCESS) { - LogPrint (eLogError, "UPnP: port forwarding to ", m_NetworkAddr, ":", strPort, " failed: return code ", err); + 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 ,")"); + 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"); + LogPrint (eLogDebug, "UPnP: External forward from ", m_NetworkAddr, ":", strPort, " exists on current Internet Gateway Device"); return; } } void UPnP::CloseMapping () { - const auto& a = context.GetRouterInfo().GetAddresses(); - for (const auto& address : a) + auto a = context.GetRouterInfo().GetAddresses(); + if (!a) return; + for (const auto& address : *a) { - if (!address->host.is_v6 () && address->port) + if (address && !address->host.is_v6 () && address->port) CloseMapping (address); } } void UPnP::CloseMapping (std::shared_ptr address) { - if(!m_upnpUrlsInitialized) { - return; - } + 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) + 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); @@ -236,20 +240,20 @@ namespace transport { freeUPNPDevlist (m_Devlist); m_Devlist = 0; - if(m_upnpUrlsInitialized){ - FreeUPNPUrls (&m_upnpUrls); - m_upnpUrlsInitialized=false; - } - } + if(m_upnpUrlsInitialized){ + FreeUPNPUrls (&m_upnpUrls); + m_upnpUrlsInitialized=false; + } + } std::string UPnP::GetProto (std::shared_ptr address) { switch (address->transportStyle) { - case i2p::data::RouterInfo::eTransportNTCP: + case i2p::data::RouterInfo::eTransportNTCP2: return "TCP"; break; - case i2p::data::RouterInfo::eTransportSSU: + case i2p::data::RouterInfo::eTransportSSU2: default: return "UDP"; } diff --git a/daemon/UPnP.h b/daemon/UPnP.h index e8220e24..59f3b785 100644 --- a/daemon/UPnP.h +++ b/daemon/UPnP.h @@ -51,7 +51,7 @@ namespace transport private: void Discover (); - int CheckMapping (const char* port, const char* type); + int CheckMapping (const char* port, const char* type); void PortMapping (); void TryPortMapping (std::shared_ptr address); void CloseMapping (); @@ -80,7 +80,7 @@ namespace transport } } -#else // USE_UPNP +#else // USE_UPNP namespace i2p { namespace transport { /* class stub */ diff --git a/daemon/UnixDaemon.cpp b/daemon/UnixDaemon.cpp index ffc5f1c0..bf4a7662 100644 --- a/daemon/UnixDaemon.cpp +++ b/daemon/UnixDaemon.cpp @@ -24,6 +24,7 @@ #include "Tunnel.h" #include "RouterContext.h" #include "ClientContext.h" +#include "Transports.h" void handle_signal(int sig) { @@ -54,6 +55,14 @@ void handle_signal(int sig) case SIGPIPE: LogPrint(eLogInfo, "SIGPIPE received"); break; + case SIGTSTP: + LogPrint(eLogInfo, "Daemon: Got SIGTSTP, disconnecting from network..."); + i2p::transport::transports.SetOnline(false); + break; + case SIGCONT: + LogPrint(eLogInfo, "Daemon: Got SIGCONT, restoring connection to network..."); + i2p::transport::transports.SetOnline(true); + break; } } @@ -72,7 +81,8 @@ namespace i2p if (pid < 0) // error { - LogPrint(eLogError, "Daemon: could not fork: ", strerror(errno)); + LogPrint(eLogError, "Daemon: Could not fork: ", strerror(errno)); + std::cerr << "i2pd: Could not fork: " << strerror(errno) << std::endl; return false; } @@ -81,13 +91,15 @@ namespace i2p int sid = setsid(); if (sid < 0) { - LogPrint(eLogError, "Daemon: could not create process group."); + 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)); + LogPrint(eLogError, "Daemon: Could not chdir: ", strerror(errno)); + std::cerr << "i2pd: Could not chdir: " << strerror(errno) << std::endl; return false; } @@ -102,14 +114,14 @@ namespace i2p uint16_t nfiles; i2p::config::GetOption("limits.openfiles", nfiles); getrlimit(RLIMIT_NOFILE, &limit); if (nfiles == 0) { - LogPrint(eLogInfo, "Daemon: using system limit in ", limit.rlim_cur, " max open files"); + LogPrint(eLogInfo, "Daemon: Using system limit in ", limit.rlim_cur, " max open files"); } else if (nfiles <= limit.rlim_max) { limit.rlim_cur = nfiles; if (setrlimit(RLIMIT_NOFILE, &limit) == 0) { - LogPrint(eLogInfo, "Daemon: set max number of open files to ", + LogPrint(eLogInfo, "Daemon: Set max number of open files to ", nfiles, " (system limit is ", limit.rlim_max, ")"); } else { - LogPrint(eLogError, "Daemon: can't set max number of open files: ", strerror(errno)); + LogPrint(eLogError, "Daemon: Can't set max number of open files: ", strerror(errno)); } } else { LogPrint(eLogError, "Daemon: limits.openfiles exceeds system limit: ", limit.rlim_max); @@ -122,11 +134,11 @@ namespace i2p if (cfsize <= limit.rlim_max) { limit.rlim_cur = cfsize; if (setrlimit(RLIMIT_CORE, &limit) != 0) { - LogPrint(eLogError, "Daemon: can't set max size of coredump: ", strerror(errno)); + LogPrint(eLogError, "Daemon: Can't set max size of coredump: ", strerror(errno)); } else if (cfsize == 0) { LogPrint(eLogInfo, "Daemon: coredumps disabled"); } else { - LogPrint(eLogInfo, "Daemon: set max size of core files to ", cfsize / 1024, "Kb"); + LogPrint(eLogInfo, "Daemon: Set max size of core files to ", cfsize / 1024, "Kb"); } } else { LogPrint(eLogError, "Daemon: limits.coresize exceeds system limit: ", limit.rlim_max); @@ -143,14 +155,16 @@ namespace i2p pidFH = open(pidfile.c_str(), O_RDWR | O_CREAT, 0600); if (pidFH < 0) { - LogPrint(eLogError, "Daemon: could not create pid file ", pidfile, ": ", strerror(errno)); + 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) { - LogPrint(eLogError, "Daemon: could not lock pid file ", pidfile, ": ", strerror(errno)); + LogPrint(eLogError, "Daemon: Could not lock pid file ", pidfile, ": ", strerror(errno)); + std::cerr << "i2pd: Could not lock pid file " << pidfile << ": " << strerror(errno) << std::endl; return false; } #endif @@ -159,12 +173,16 @@ namespace i2p ftruncate(pidFH, 0); if (write(pidFH, pid, strlen(pid)) < 0) { - LogPrint(eLogError, "Daemon: could not write pidfile: ", strerror(errno)); + LogPrint(eLogCritical, "Daemon: Could not write pidfile ", pidfile, ": ", strerror(errno)); + std::cerr << "i2pd: Could not write pidfile " << pidfile << ": " << strerror(errno) << std::endl; return false; } } 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; @@ -176,6 +194,11 @@ namespace i2p sigaction(SIGTERM, &sa, 0); sigaction(SIGINT, &sa, 0); sigaction(SIGPIPE, &sa, 0); + if (handleTSTP) + { + sigaction(SIGTSTP, &sa, 0); + sigaction(SIGCONT, &sa, 0); + } return Daemon_Singleton::start(); } diff --git a/debian/changelog b/debian/changelog index bac3f3b3..91b0abdf 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,123 @@ +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 diff --git a/debian/compat b/debian/compat index f11c82a4..48082f72 100644 --- a/debian/compat +++ b/debian/compat @@ -1 +1 @@ -9 \ No newline at end of file +12 diff --git a/debian/control b/debian/control index 843a153c..48f5e680 100644 --- a/debian/control +++ b/debian/control @@ -2,31 +2,17 @@ Source: i2pd Section: net Priority: optional Maintainer: r4sas -Build-Depends: debhelper (>= 9), dpkg-dev (>= 1.17.2~), gcc (>= 4.7) | clang (>= 3.3), libboost-system-dev (>= 1.46), libboost-date-time-dev (>= 1.46), libboost-filesystem-dev (>= 1.46), libboost-program-options-dev (>= 1.46), libminiupnpc-dev, libssl-dev, zlib1g-dev -Standards-Version: 3.9.6 +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: adduser +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. - . - This package contains the full-featured C++ implementation of I2P router. - -Package: i2pd-dbg -Architecture: any -Priority: extra -Section: debug -Depends: i2pd (= ${binary:Version}), ${misc:Depends} -Description: i2pd debugging symbols - I2P (Invisible Internet Protocol) is a universal anonymous network layer. All - communications over I2P are anonymous and end-to-end encrypted, participants - don't reveal their real IP addresses. - . - This package contains symbols required for debugging. diff --git a/debian/copyright b/debian/copyright index 9f18f53a..352e260b 100644 --- a/debian/copyright +++ b/debian/copyright @@ -3,25 +3,18 @@ Upstream-Name: i2pd Source: https://github.com/PurpleI2P Files: * -Copyright: 2013-2020 PurpleI2P +Copyright: 2013-2023 PurpleI2P License: BSD-3-clause -Files: qt/i2pd_qt/android/src/org/kde/necessitas/ministro/IMinistro.aidl - qt/i2pd_qt/android/src/org/kde/necessitas/ministro/IMinistroCallback.aidl - qt/i2pd_qt/android/src/org/qtproject/qt5/android/bindings/QtActivity.java - qt/i2pd_qt/android/src/org/qtproject/qt5/android/bindings/QtApplication.java -Copyright: 2011-2013 BogDan Vatra -License: BSD-2-Clause - Files: debian/* Copyright: 2013-2015 Kill Your TV 2014-2016 hagen - 2016-2020 R4SAS + 2016-2023 R4SAS 2017-2020 Yangfl License: GPL-2+ License: BSD-3-clause - Copyright (c) 2013-2017, The PurpleI2P Project + Copyright (c) 2013-2023, The PurpleI2P Project . All rights reserved. . @@ -49,28 +42,6 @@ License: BSD-3-clause TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -License: BSD-2-Clause - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - 1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - . - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE HOLDERS OR - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - License: GPL-2+ This package is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/debian/docs b/debian/docs index dfa6f572..b43bf86b 100644 --- a/debian/docs +++ b/debian/docs @@ -1,5 +1 @@ README.md -contrib/i2pd.conf -contrib/subscriptions.txt -contrib/tunnels.conf -contrib/tunnels.d diff --git a/debian/i2pd.default b/debian/i2pd.default index bd1d073f..90392ede 100644 --- a/debian/i2pd.default +++ b/debian/i2pd.default @@ -8,4 +8,4 @@ I2PD_ENABLED="yes" DAEMON_OPTS="" # If you have problems with hunging i2pd, you can try enable this -ulimit -n 4096 +ulimit -n 8192 diff --git a/debian/i2pd.dirs b/debian/i2pd.dirs deleted file mode 100644 index 3b643352..00000000 --- a/debian/i2pd.dirs +++ /dev/null @@ -1,2 +0,0 @@ -etc/i2pd -var/lib/i2pd diff --git a/debian/i2pd.install b/debian/i2pd.install index d20b2c17..93eee7a1 100644 --- a/debian/i2pd.install +++ b/debian/i2pd.install @@ -1,7 +1,6 @@ i2pd usr/sbin/ -contrib/i2pd.conf etc/i2pd/ +contrib/i2pd.conf etc/i2pd/ contrib/tunnels.conf etc/i2pd/ -contrib/subscriptions.txt etc/i2pd/ contrib/certificates/ usr/share/i2pd/ contrib/tunnels.d/README etc/i2pd/tunnels.conf.d/ contrib/apparmor/usr.sbin.i2pd etc/apparmor.d diff --git a/debian/i2pd.links b/debian/i2pd.links index a149967f..16558791 100644 --- a/debian/i2pd.links +++ b/debian/i2pd.links @@ -1,5 +1,4 @@ -etc/i2pd/i2pd.conf var/lib/i2pd/i2pd.conf +etc/i2pd/i2pd.conf var/lib/i2pd/i2pd.conf etc/i2pd/tunnels.conf var/lib/i2pd/tunnels.conf -etc/i2pd/subscriptions.txt var/lib/i2pd/subscriptions.txt etc/i2pd/tunnels.conf.d var/lib/i2pd/tunnels.d usr/share/i2pd/certificates var/lib/i2pd/certificates diff --git a/debian/patches/01-tune-build-opts.patch b/debian/patches/01-tune-build-opts.patch deleted file mode 100644 index e06a8621..00000000 --- a/debian/patches/01-tune-build-opts.patch +++ /dev/null @@ -1,15 +0,0 @@ -Index: i2pd/Makefile -=================================================================== ---- i2pd.orig/Makefile -+++ i2pd/Makefile -@@ -13,8 +13,8 @@ DAEMON_SRC_DIR := daemon - - include filelist.mk - --USE_AESNI := yes --USE_AVX := yes -+USE_AESNI := no -+USE_AVX := no - USE_STATIC := no - USE_MESHNET := no - USE_UPNP := no diff --git a/debian/patches/01-upnp.patch b/debian/patches/01-upnp.patch new file mode 100644 index 00000000..bec8f2b0 --- /dev/null +++ b/debian/patches/01-upnp.patch @@ -0,0 +1,17 @@ +Description: Enable UPnP usage in package +Author: r4sas + +Reviewed-By: r4sas +Last-Update: 2022-03-23 + +--- i2pd.orig/Makefile ++++ i2pd/Makefile +@@ -31,7 +31,7 @@ include filelist.mk + + USE_AESNI := $(or $(USE_AESNI),yes) + USE_STATIC := $(or $(USE_STATIC),no) +-USE_UPNP := $(or $(USE_UPNP),no) ++USE_UPNP := $(or $(USE_UPNP),yes) + DEBUG := $(or $(DEBUG),yes) + + # for debugging purposes only, when commit hash needed in trunk builds in i2pd version string diff --git a/debian/patches/02-fix-1210.patch b/debian/patches/02-fix-1210.patch deleted file mode 100644 index 9ad9bb0f..00000000 --- a/debian/patches/02-fix-1210.patch +++ /dev/null @@ -1,27 +0,0 @@ -Description: fix #1210 - Disables two options, which not presented in old systemd versions -Author: r4sas - -Bug: https://github.com/PurpleI2P/i2pd/issues/1210 -Reviewed-By: r4sas -Last-Update: 2020-05-25 - -Index: i2pd/contrib/i2pd.service -=================================================================== ---- i2pd.orig/contrib/i2pd.service -+++ i2pd/contrib/i2pd.service -@@ -6,10 +6,10 @@ After=network.target - [Service] - User=i2pd - Group=i2pd --RuntimeDirectory=i2pd --RuntimeDirectoryMode=0700 --LogsDirectory=i2pd --LogsDirectoryMode=0700 -+#RuntimeDirectory=i2pd -+#RuntimeDirectoryMode=0700 -+#LogsDirectory=i2pd -+#LogsDirectoryMode=0700 - Type=forking - ExecStart=/usr/sbin/i2pd --conf=/etc/i2pd/i2pd.conf --tunconf=/etc/i2pd/tunnels.conf --tunnelsdir=/etc/i2pd/tunnels.conf.d --pidfile=/run/i2pd/i2pd.pid --logfile=/var/log/i2pd/i2pd.log --daemon --service - ExecReload=/bin/sh -c "kill -HUP $MAINPID" diff --git a/debian/patches/series b/debian/patches/series index 002802b5..f97fdf65 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -1,2 +1 @@ -01-tune-build-opts.patch -02-fix-1210.patch +01-upnp.patch diff --git a/debian/postinst b/debian/postinst index 9c9e74ae..720753fd 100755 --- a/debian/postinst +++ b/debian/postinst @@ -12,7 +12,6 @@ case "$1" in # Create user and group as a system user. if getent passwd $I2PDUSER > /dev/null 2>&1; then groupadd -f $I2PDUSER || true - usermod -s "/bin/false" -e 1 $I2PDUSER > /dev/null || true else adduser --system --quiet --group --home $I2PDHOME $I2PDUSER fi @@ -23,7 +22,7 @@ case "$1" in chmod 640 $LOGFILE chown -f ${I2PDUSER}:adm $LOGFILE mkdir -p -m0750 $I2PDHOME - chown -f -R -P ${I2PDUSER}:${I2PDUSER} ${I2PDHOME} + chown -f -P ${I2PDUSER}:${I2PDUSER} ${I2PDHOME} ;; abort-upgrade|abort-remove|abort-deconfigure) echo "Aborting upgrade" diff --git a/debian/rules b/debian/rules index 77ecd7b7..fc769066 100755 --- a/debian/rules +++ b/debian/rules @@ -1,22 +1,13 @@ #!/usr/bin/make -f -# -*- makefile -*- - -# Uncomment this to turn on verbose mode. #export DH_VERBOSE=1 +export DEB_BUILD_MAINT_OPTIONS = hardening=+all -DEB_BUILD_MAINT_OPTIONS=hardening=+bindnow -#DPKG_EXPORT_BUILDFLAGS = 1 -#include /usr/share/dpkg/buildflags.mk -#CXXFLAGS+=$(CPPFLAGS) -#PREFIX=/usr +include /usr/share/dpkg/architecture.mk + +export DEB_CXXFLAGS_MAINT_APPEND = -Wall -pedantic +export DEB_LDFLAGS_MAINT_APPEND = %: - dh $@ --parallel -# dh_apparmor --profile-name=usr.sbin.i2pd -pi2pd + dh $@ -override_dh_strip: - dh_strip --dbg-package=i2pd-dbg - -## uncomment this if you have "missing info" problem when building package -#override_dh_shlibdeps: -# dh_shlibdeps --dpkg-shlibdeps-params=--ignore-missing-info +override_dh_auto_install: diff --git a/debian/watch b/debian/watch index 55cda021..2ec6c29b 100644 --- a/debian/watch +++ b/debian/watch @@ -1,3 +1,4 @@ -version=3 -opts=filenamemangle=s/.+\/v?(\d\S*)\.tar\.gz/i2pd-$1\.tar\.gz/ \ - https://github.com/PurpleI2P/i2pd/tags .*/v?(\d\S*)\.tar\.gz +version=4 +opts="filenamemangle=s%(?:.*?)?v?(\d[\d.]*)\.tar\.gz%i2pd-$1.tar.gz%" \ + https://github.com/PurpleI2P/i2pd/tags \ + (?:.*?/)?(\d[\d.]*)\.tar\.gz debian uupdate diff --git a/filelist.mk b/filelist.mk index 7d13fb2f..d8f503e6 100644 --- a/filelist.mk +++ b/filelist.mk @@ -19,4 +19,8 @@ LIB_CLIENT_SRC = $(wildcard $(LIB_CLIENT_SRC_DIR)/*.cpp) #DAEMON_SRC = \ # HTTPServer.cpp I2PControl.cpp UPnP.cpp Daemon.cpp i2pd.cpp +LANG_SRC = $(wildcard $(LANG_SRC_DIR)/*.cpp) + +WRAP_LIB_SRC = $(wildcard $(WRAP_SRC_DIR)/*.cpp) + DAEMON_SRC = $(wildcard $(DAEMON_SRC_DIR)/*.cpp) diff --git a/i18n/Afrikaans.cpp b/i18n/Afrikaans.cpp new file mode 100644 index 00000000..b582a06a --- /dev/null +++ b/i18n/Afrikaans.cpp @@ -0,0 +1,81 @@ +/* +* Copyright (c) 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 +#include "I18N.h" + +// Afrikaans localization file + +namespace i2p +{ +namespace i18n +{ +namespace afrikaans // language namespace +{ + // language name in lowercase + static std::string language = "afrikaans"; + + // See for language plural forms here: + // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html + static int plural (int n) { + return n != 1 ? 1 : 0; + } + + static std::map strings + { + {"failed", "Het misluk"}, + {"unknown", "onbekend"}, + {"Tunnels", "Tonnels"}, + {"I2P tunnels", "I2P tonnels"}, + {"SAM sessions", "SAM sessies"}, + {"OK", "LEKKER"}, + {"Testing", "Besig om te toets"}, + {"Firewalled", "Vuurmuur'd"}, + {"Unknown", "Onbekend"}, + {"Error", "Fout"}, + {"Offline", "Aflyn"}, + {"Uptime", "Optyd"}, + {"Network status", "Netwerk status"}, + {"Network status v6", "Netwerk status v6"}, + {"Family", "Familie"}, + {"Received", "Ontvang"}, + {"Sent", "Gestuur"}, + {"Hidden content. Press on text to see.", "Hidden content. Druk om te sien."}, + {"Router Ident", "Router Ident"}, + {"Router Family", "Router Familie"}, + {"Enabled", "Geaktiveer"}, + {"Disabled", "Gedeaktiveer"}, + {"Change", "Verander"}, + {"Change language", "Verander taal"}, + {"Description", "Beskrywing"}, + {"Submit", "Stuur"}, + {"Proxy error", "Proxy-fout"}, + {"Host", "Gasheer"}, + {"", ""}, + }; + + static std::map> plurals + { + {"%d days", {"%d dag", "%d dae"}}, + {"%d hours", {"%d uur", "%d ure"}}, + {"%d minutes", {"%d minuut", "%d minute"}}, + {"%d seconds", {"%d seconde", "%d sekondes"}}, + {"", {"", ""}}, + }; + + std::shared_ptr GetLocale() + { + return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); + } + +} // language +} // i18n +} // i2p diff --git a/i18n/Armenian.cpp b/i18n/Armenian.cpp new file mode 100644 index 00000000..b99e5032 --- /dev/null +++ b/i18n/Armenian.cpp @@ -0,0 +1,204 @@ +/* +* 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 +*/ + +#include +#include +#include +#include +#include "I18N.h" + +// Armenian localization file + +namespace i2p +{ +namespace i18n +{ +namespace armenian // language namespace +{ + // language name in lowercase + static std::string language = "armenian"; + + // See for language plural forms here: + // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html + static int plural (int n) { + return n != 1 ? 1 : 0; + } + + static std::map strings + { + {"%.2f KiB", "%.2f ԿիԲ"}, + {"%.2f MiB", "%.2f ՄիԲ"}, + {"%.2f GiB", "%.2f ԳիԲ"}, + {"building", "կառուցվում է"}, + {"failed", "Անհաջող"}, + {"expiring", "Լրանում է"}, + {"established", "կարգավոյված է"}, + {"unknown", "անհայտ"}, + {"exploratory", "հետազոտոկան"}, + {"Purple I2P Webconsole", "Վեբ-կոնսոլ Purple I2P"}, + {"i2pd webconsole", "Վեբ-կոնսոլ i2pd"}, + {"Main page", "Գլխավոր էջ"}, + {"Router commands", "Երթուղիչի հրահանգներ"}, + {"Local Destinations", "Տեղական վերջնակետերը"}, + {"LeaseSets", "ԼիզՍեթեր"}, + {"Tunnels", "Թունելներ"}, + {"Transit Tunnels", "Տարանցիկ թունելներ"}, + {"Transports", "Տրանսպորտ"}, + {"I2P tunnels", "I2P թունելներ"}, + {"SAM sessions", "SAM նստաշրջաններ"}, + {"ERROR", "ՍԽԱԼ"}, + {"OK", "ԼԱՎ"}, + {"Testing", "Փորձարկում"}, + {"Firewalled", "Արգելափակված է դրսից"}, + {"Unknown", "Անհայտ"}, + {"Proxy", "Պրոկսի"}, + {"Mesh", "MESH-ցանց"}, + {"Clock skew", "Ոչ ճշգրիտ ժամանակ"}, + {"Offline", "Օֆլայն"}, + {"Symmetric NAT", "Սիմետրիկ NAT"}, + {"Full cone NAT", "Full cone NAT"}, + {"Uptime", "Առկայություն"}, + {"Network status", "Ցանցի կարգավիճակ"}, + {"Network status v6", "Ցանցի կարգավիճակ v6"}, + {"Stopping in", "Դադարում"}, + {"Family", "Խմբատեսակ"}, + {"Tunnel creation success rate", "Հաջողությամբ կառուցված թունելներ"}, + {"Received", "Ստացվել է"}, + {"%.2f KiB/s", "%.2f ԿիԲ/վ"}, + {"Sent", "Ուղարկվել է"}, + {"Transit", "Տարանցում"}, + {"Data path", "Տվյալների ուղին"}, + {"Hidden content. Press on text to see.", "Թաքցված բովանդակություն: Տեսնելու համար սեղմեկ տեքստին:"}, + {"Router Ident", "Երթուղիչի նույնականացուցիչ"}, + {"Router Family", "Երթուղիչի խումբը"}, + {"Router Caps", "Երթուղիչի հատկություններ"}, + {"Version", "Տարբերակ"}, + {"Our external address", "Մեր արտաքին հասցեն"}, + {"supported", "համատեղելի է"}, + {"Routers", "Երթուղիչներ"}, + {"Floodfills", "Floodfills-ներ"}, + {"Client Tunnels", "Oգտատիրական թունելներ"}, + {"Services", "Ծառայություններ"}, + {"Enabled", "Միացված է"}, + {"Disabled", "Անջատված է"}, + {"Encrypted B33 address", "Գաղտնագրված B33 հասցեներ"}, + {"Address registration line", "Հասցեի գրանցման տող"}, + {"Domain", "Տիրույթ"}, + {"Generate", "Գեներացնել"}, + {"Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", " Նշում. արդյունքի տողը կարող է օգտագործվել միայն 2LD տիրույթներ գրանցելու համար (example.i2p): Ենթատիրույթներ գրանցելու համար խնդրում ենք օգտագործել i2pd-tools գործիքակազմը:"}, + {"Address", "Հասցե"}, + {"Type", "Տեսակը"}, + {"EncType", "Գաղտնագրի տեսակը"}, + {"Inbound tunnels", "Մուտքային թունելներ"}, + {"%dms", "%dմլվ"}, + {"Outbound tunnels", "Ելքային թունելներ"}, + {"Tags", "Թեգեր"}, + {"Incoming", "Մուտքային"}, + {"Outgoing", "ելքային"}, + {"Destination", "Նշանակման վայր"}, + {"Amount", "Քանակ"}, + {"Incoming Tags", "Մուտքային պիտակներ"}, + {"Tags sessions", "Նստաշրջանի պիտակներ"}, + {"Status", "Կարգավիճակ"}, + {"Local Destination", "Տեղական նշանակման կետ"}, + {"Streams", "Հոսքեր"}, + {"Close stream", "Փակել հոսքը"}, + {"I2CP session not found", "I2CP նստաշրջանը գոյություն չունի"}, + {"I2CP is not enabled", "I2CP միացված է"}, + {"Invalid", "Անվավեր"}, + {"Store type", "Պահեստավորման տեսակը"}, + {"Expires", "Սպառվում է"}, + {"Non Expired Leases", "Չսպառված Lease-եր"}, + {"Gateway", "Դարպաս"}, + {"TunnelID", "Թունելի ID"}, + {"EndDate", "Ավարտ"}, + {"Queue size", "Հերթի չափսը"}, + {"Run peer test", "Գործարկել փորձարկումը"}, + {"Decline transit tunnels", "Մերժել տարանցիկ թունելներ"}, + {"Accept transit tunnels", "Ընդունել տարանցիկ թունելներ"}, + {"Cancel graceful shutdown", "Չեղարկել սահուն անջատումը"}, + {"Start graceful shutdown", "Սկսել սահուն անջատումը"}, + {"Force shutdown", "Հարկադիր անջատում"}, + {"Reload external CSS styles", "Վերաբեռնեք CSS ոճաթերթը"}, + {"Note: any action done here are not persistent and not changes your config files.", " Նշում․ այստեղ կատարված ցանկացած գործողություն մշտական ​​չէ և չի փոխում ձեր կազմաձևման ֆայլերը։"}, + {"Logging level", "Գրառման աստիճանը"}, + {"Transit tunnels limit", "Տարանցիկ թունելների սահմանափակում"}, + {"Change", "Փոփոխել"}, + {"Change language", "Փոփոխել լեզուն"}, + {"no transit tunnels currently built", "ընթացիկ կառուցված տարանցիկ թունելներ գոյություն չունեն"}, + {"SAM disabled", "SAM-ն անջատված է"}, + {"no sessions currently running", "ներկայումս գործող նստաշրջաններ գոյություն չունեն"}, + {"SAM session not found", "SAM նստաշրջան գոյություն չունի"}, + {"SAM Session", "SAM նստաշրջան"}, + {"Server Tunnels", "Սերվերային թունելներ"}, + {"Client Forwards", "Օգտատիրական փոխանցումներ"}, + {"Server Forwards", "Սերվերային փոխանցումներ"}, + {"Unknown page", "Անհայտ էջ"}, + {"Invalid token", "Սխալ տոկեն"}, + {"SUCCESS", "ՀԱՋՈՂՎԱԾ"}, + {"Stream closed", "Հոսքն անջատված է"}, + {"Stream not found or already was closed", "Հոսքը գոյություն չունի կամ արդեն ավարտված է"}, + {"Destination not found", "Հասցեի վայրը չի գտնվել"}, + {"StreamID can't be null", "StreamID-ն չի կարող լինել դատարկ"}, + {"Return to destination page", "Վերադառնալ նախորդ էջի հասցե"}, + {"Back to commands list", "Վերադառնալ հրահանգների ցուցակ"}, + {"Description", "Նկարագրություն"}, + {"A bit information about service on domain", "Մի փոքր տեղեկատվություն տիրոիյթում գտնվող ծառայության մասին"}, + {"Submit", "Ուղարկվել"}, + {"Domain can't end with .b32.i2p", "Տիրույթը չպետք է վերջանա .b32.i2p-ով"}, + {"Domain must end with .i2p", "Տիրույթը պետք է վերջանա .i2p-ով"}, + {"Such destination is not found", "Այդիպսի հասցե գոյություն չունի"}, + {"Unknown command", "Անհայտ հրահանգ"}, + {"Command accepted", "Հրարահանգն ընդունված է"}, + {"Proxy error", "Պրոկսի սխալ"}, + {"Proxy info", "Պրոկսի տեղեկություն"}, + {"Proxy error: Host not found", "Պրոկսի սխալ՝ նման հոսթ գոյություն չունի"}, + {"Remote host not found in router's addressbook", "Դեպի հոսթ կատարված հարցումը գոյություն չունի երթուղիչի հասցեագրքում"}, + {"You may try to find this host on jump services below", "Ստորև Դուք կարող եք գտնել այս հոսթը jump ծառայությունների միջոցով"}, + {"Invalid request", "Սխալ հարցում"}, + {"Proxy unable to parse your request", "Պրոկսին չի կարող հասկանալ Ձեր հարցումը"}, + {"Invalid request URI", "Սխալ ձևավորված URI հարցում"}, + {"Can't detect destination host from request", "Չհաջողվեց հայնտաբերեկ վայրի հասցեն նշված հարցմամբ"}, + {"Outproxy failure", "Սխալ արտաքին պրոքսի"}, + {"Bad outproxy settings", "Սխալ արտաքին պրոկսի կարգավորումներ"}, + {"Host %s is not inside I2P network, but outproxy is not enabled", "Հոսթ %s Հարցումը I2P ցանցից դուրս է, բայց արտաքին պրոքսին միացված չէ"}, + {"Unknown outproxy URL", "Արտաքին պրոքսիի անհայտ URL"}, + {"Cannot resolve upstream proxy", "Չհաջողվեց որոշել վերադաս պրոկսին"}, + {"Hostname is too long", "Հոսթի անունը չափազանց երկար է"}, + {"Cannot connect to upstream SOCKS proxy", "Չհաջողվեց միանալ վերադաս SOCKS պրոկսի սերվերին"}, + {"Cannot negotiate with SOCKS proxy", "Չհաջողվեց պայմանավորվել վերադաս SOCKS պրոկսիի հետ"}, + {"CONNECT error", "Սխալ CONNECT հարցում"}, + {"Failed to connect", "Միանալ չhաջողվեց"}, + {"SOCKS proxy error", "Սխալ SOCKS պրոկսի"}, + {"Failed to send request to upstream", "Չհաջողվեց հարցումն ուղարկել վերադաս պրոկսիին"}, + {"No reply from SOCKS proxy", "Բացակայում է պատասխանը SOCKS պրոկսի սերվերի կողմից"}, + {"Cannot connect", "Հնարավոր չե միանալ"}, + {"HTTP out proxy not implemented", "Արտաքին HTTP պրոկսին դեռ իրականացված չէ"}, + {"Cannot connect to upstream HTTP proxy", "Չհաջողվեց միանալ վերադաս HTTP պրոկսի սերվերին"}, + {"Host is down", "Հոսթն անհասանելի է"}, + {"Can't create connection to requested host, it may be down. Please try again later.", "Հոսթի հետ կապը հաստատել չհաջողվեց, հնարավոր է այն անջատված է, փորձեք միանալ քիչ ուշ:"}, + {"", ""}, + }; + + static std::map> plurals + { + {"%d days", {"%d օր", "%d օր"}}, + {"%d hours", {"%d ժամ", "%d ժամ"}}, + {"%d minutes", {"%d րոպե", "%d րոպե"}}, + {"%d seconds", {"%d վարկյան", "%d վարկյան"}}, + {"", {"", ""}}, + }; + + std::shared_ptr GetLocale() + { + return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); + } + +} // language +} // i18n +} // i2p diff --git a/i18n/Chinese.cpp b/i18n/Chinese.cpp new file mode 100644 index 00000000..5ecfe067 --- /dev/null +++ b/i18n/Chinese.cpp @@ -0,0 +1,219 @@ +/* +* Copyright (c) 2022-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 +#include +#include "I18N.h" + +// Simplified Chinese localization file + +namespace i2p +{ +namespace i18n +{ +namespace chinese // language namespace +{ + // language name in lowercase + static std::string language = "chinese"; + + // See for language plural forms here: + // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html + static int plural (int n) { + return 0; + } + + static std::map strings + { + {"%.2f KiB", "%.2f KiB"}, + {"%.2f MiB", "%.2f MiB"}, + {"%.2f GiB", "%.2f GiB"}, + {"building", "正在构建"}, + {"failed", "连接失败"}, + {"expiring", "即将过期"}, + {"established", "连接成功"}, + {"unknown", "未知"}, + {"exploratory", "探索"}, + {"Purple I2P Webconsole", "Purple I2P 网页控制台"}, + {"i2pd webconsole", "i2pd 网页控制台"}, + {"Main page", "主页"}, + {"Router commands", "路由命令"}, + {"Local Destinations", "本地目标"}, + {"LeaseSets", "租约集"}, + {"Tunnels", "隧道"}, + {"Transit Tunnels", "中转隧道"}, + {"Transports", "传输"}, + {"I2P tunnels", "I2P 隧道"}, + {"SAM sessions", "SAM 会话"}, + {"ERROR", "错误"}, + {"OK", "良好"}, + {"Testing", "测试中"}, + {"Firewalled", "受到防火墙限制"}, + {"Unknown", "未知"}, + {"Proxy", "代理"}, + {"Mesh", "自组网"}, + {"Clock skew", "时钟偏移"}, + {"Offline", "离线"}, + {"Symmetric NAT", "对称 NAT"}, + {"Full cone NAT", "全锥型NAT"}, + {"No Descriptors", "无描述符"}, + {"Uptime", "运行时间"}, + {"Network status", "IPv4 网络状态"}, + {"Network status v6", "IPv6 网络状态"}, + {"Stopping in", "距停止还有:"}, + {"Family", "家族"}, + {"Tunnel creation success rate", "隧道创建成功率"}, + {"Received", "已接收"}, + {"%.2f KiB/s", "%.2f KiB/s"}, + {"Sent", "已发送"}, + {"Transit", "中转"}, + {"Data path", "数据文件路径"}, + {"Hidden content. Press on text to see.", "隐藏内容 请点击此处查看。"}, + {"Router Ident", "路由身份"}, + {"Router Family", "路由器家族"}, + {"Router Caps", "路由器类型"}, + {"Version", "版本"}, + {"Our external address", "外部地址"}, + {"supported", "支持"}, + {"Routers", "路由节点"}, + {"Floodfills", "洪泛节点"}, + {"Client Tunnels", "客户端隧道"}, + {"Services", "服务"}, + {"Enabled", "启用"}, + {"Disabled", "禁用"}, + {"Encrypted B33 address", "加密的 B33 地址"}, + {"Address registration line", "地址域名注册"}, + {"Domain", "域名"}, + {"Generate", "生成"}, + {"Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", "注意: 结果字符串只能用于注册二级域名(例如:example.i2p)。若需注册三级域名,请使用 i2pd-tools。"}, + {"Address", "地址"}, + {"Type", "类型"}, + {"EncType", "加密类型"}, + {"Inbound tunnels", "入站隧道"}, + {"%dms", "%dms"}, + {"Outbound tunnels", "出站隧道"}, + {"Tags", "标签"}, + {"Incoming", "传入"}, + {"Outgoing", "传出"}, + {"Destination", "目标"}, + {"Amount", "数量"}, + {"Incoming Tags", "传入标签"}, + {"Tags sessions", "标签会话"}, + {"Status", "状态"}, + {"Local Destination", "本地目标"}, + {"Streams", "流"}, + {"Close stream", "断开流"}, + {"Such destination is not found", "找不到此目标"}, + {"I2CP session not found", "未找到 I2CP 会话"}, + {"I2CP is not enabled", "I2CP 未启用"}, + {"Invalid", "无效"}, + {"Store type", "存储类型"}, + {"Expires", "过期时间"}, + {"Non Expired Leases", "未到期的租约"}, + {"Gateway", "网关"}, + {"TunnelID", "隧道 ID"}, + {"EndDate", "结束日期"}, + {"floodfill mode is disabled", "洪泛已禁用"}, + {"Queue size", "队列大小"}, + {"Run peer test", "运行节点测试"}, + {"Reload tunnels configuration", "重新载入隧道配置"}, + {"Decline transit tunnels", "拒绝中转隧道"}, + {"Accept transit tunnels", "允许中转隧道"}, + {"Cancel graceful shutdown", "取消平滑关闭"}, + {"Start graceful shutdown", "平滑关闭"}, + {"Force shutdown", "强制停止"}, + {"Reload external CSS styles", "重载外部 CSS 样式"}, + {"Note: any action done here are not persistent and not changes your config files.", "注意: 此处完成的任何操作都不是永久的,不会更改您的配置文件。"}, + {"Logging level", "日志级别"}, + {"Transit tunnels limit", "中转隧道限制"}, + {"Change", "修改"}, + {"Change language", "更改语言"}, + {"no transit tunnels currently built", "目前未构建中转隧道"}, + {"SAM disabled", "SAM 已禁用"}, + {"no sessions currently running", "没有正在运行的会话"}, + {"SAM session not found", "未找到 SAM 会话"}, + {"SAM Session", "SAM 会话"}, + {"Server Tunnels", "服务器隧道"}, + {"Client Forwards", "客户端转发"}, + {"Server Forwards", "服务器转发"}, + {"Unknown page", "未知页面"}, + {"Invalid token", "无效令牌"}, + {"SUCCESS", "成功"}, + {"Stream closed", "流已关闭"}, + {"Stream not found or already was closed", "流未找到或已关闭"}, + {"Destination not found", "找不到目标"}, + {"StreamID can't be null", "StreamID 不能为空"}, + {"Return to destination page", "返回目标页面"}, + {"You will be redirected in %d seconds", "您将在%d秒内被重定向"}, + {"Transit tunnels count must not exceed %d", "中转隧道数量限制为 %d"}, + {"Back to commands list", "返回命令列表"}, + {"Register at reg.i2p", "在 reg.i2p 注册域名"}, + {"Description", "描述"}, + {"A bit information about service on domain", "在此域名上运行的服务的一些信息"}, + {"Submit", "提交"}, + {"Domain can't end with .b32.i2p", "域名不能以 .b32.i2p 结尾"}, + {"Domain must end with .i2p", "域名必须以 .i2p 结尾"}, + {"Unknown command", "未知指令"}, + {"Command accepted", "已接受指令"}, + {"Proxy error", "代理错误"}, + {"Proxy info", "代理信息"}, + {"Proxy error: Host not found", "代理错误:未找到主机"}, + {"Remote host not found in router's addressbook", "在路由地址簿中未找到远程主机"}, + {"You may try to find this host on jump services below", "您可以尝试在下方的跳转服务中找到此主机"}, + {"Invalid request", "无效请求"}, + {"Proxy unable to parse your request", "代理无法解析您的请求"}, + {"Addresshelper is not supported", "不支持地址助手"}, + {"Host %s is already in router's addressbook. Be careful: source of this URL may be harmful! Click here to update record: Continue.", "主机 %s 已在路由地址簿中请注意:此地址的来源可能是有害的!点击此处更新记录:继续"}, + {"Addresshelper forced update rejected", "地址助手强制更新被拒绝"}, + {"To add host %s in router's addressbook, click here: Continue.", "若要在路由器地址簿中添加主机 %s 请点击这里: 继续"}, + {"Addresshelper request", "请求地址助手"}, + {"Host %s added to router's addressbook from helper. Click here to proceed: Continue.", "主机 %s 已通过地址助手添加到路由地址簿中。点击此处继续:继续"}, + {"Addresshelper adding", "正在添加地址助手"}, + {"Host %s is already in router's addressbook. Click here to update record: Continue.", "主机 %s 已在路由地址簿中。点击此处更新记录:继续"}, + {"Addresshelper update", "更新地址助手"}, + {"Invalid request URI", "无效的 URI 请求"}, + {"Can't detect destination host from request", "无法从请求中检测到目标主机"}, + {"Outproxy failure", "出口代理故障"}, + {"Bad outproxy settings", "错误的出口代理设置"}, + {"Host %s is not inside I2P network, but outproxy is not enabled", "主机 %s 不在 I2P 网络内,但出口代理未启用"}, + {"Unknown outproxy URL", "未知的出口代理地址"}, + {"Cannot resolve upstream proxy", "无法解析上游代理"}, + {"Hostname is too long", "主机名过长"}, + {"Cannot connect to upstream SOCKS proxy", "无法连接到上游 SOCKS 代理"}, + {"Cannot negotiate with SOCKS proxy", "无法与 SOCKS 代理协商"}, + {"CONNECT error", "连接错误"}, + {"Failed to connect", "连接失败"}, + {"SOCKS proxy error", "SOCKS 代理错误"}, + {"Failed to send request to upstream", "向上游发送请求失败"}, + {"No reply from SOCKS proxy", "没有来自 SOCKS 代理的回复"}, + {"Cannot connect", "无法连接"}, + {"HTTP out proxy not implemented", "HTTP 出口代理未实现"}, + {"Cannot connect to upstream HTTP proxy", "无法连接到上游 HTTP 代理"}, + {"Host is down", "主机已关闭"}, + {"Can't create connection to requested host, it may be down. Please try again later.", "无法创建到目标主机的连接。主机可能已下线,请稍后再试。"}, + {"", ""}, + }; + + static std::map> plurals + { + {"%d days", {"%d 天"}}, + {"%d hours", {"%d 小时"}}, + {"%d minutes", {"%d 分钟"}}, + {"%d seconds", {"%d 秒"}}, + {"", {""}}, + }; + + std::shared_ptr GetLocale() + { + return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); + } + +} // language +} // i18n +} // i2p diff --git a/i18n/Czech.cpp b/i18n/Czech.cpp new file mode 100644 index 00000000..09a56d50 --- /dev/null +++ b/i18n/Czech.cpp @@ -0,0 +1,204 @@ +/* +* Copyright (c) 2022-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 +#include +#include "I18N.h" + +// Czech localization file + +namespace i2p +{ +namespace i18n +{ +namespace czech // language namespace +{ + // language name in lowercase + static std::string language = "czech"; + + // See for language plural forms here: + // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html + static int plural (int n) { + return (n == 1) ? 0 : (n >= 2 && n <= 4) ? 1 : 2; + } + + static std::map strings + { + {"%.2f KiB", "%.2f KiB"}, + {"%.2f MiB", "%.2f MiB"}, + {"%.2f GiB", "%.2f GiB"}, + {"building", "vytváří se"}, + {"failed", "selhalo"}, + {"expiring", "končící"}, + {"established", "vytvořeno"}, + {"unknown", "neznámý"}, + {"exploratory", "průzkumné"}, + {"Purple I2P Webconsole", "Purple I2P Webkonsole"}, + {"i2pd webconsole", "i2pd webkonsole"}, + {"Main page", "Hlavní stránka"}, + {"Router commands", "Router příkazy"}, + {"Local Destinations", "Lokální destinace"}, + {"LeaseSets", "LeaseSety"}, + {"Tunnels", "Tunely"}, + {"Transit Tunnels", "Transitní tunely"}, + {"Transports", "Transporty"}, + {"I2P tunnels", "I2P tunely"}, + {"SAM sessions", "SAM relace"}, + {"ERROR", "CHYBA"}, + {"OK", "OK"}, + {"Testing", "Testuji"}, + {"Firewalled", "Za Firewallem"}, + {"Unknown", "Neznámý"}, + {"Proxy", "Proxy"}, + {"Mesh", "Síť"}, + {"Clock skew", "Časová nesrovnalost"}, + {"Offline", "Offline"}, + {"Symmetric NAT", "Symetrický NAT"}, + {"Uptime", "Doba provozu"}, + {"Network status", "Status sítě"}, + {"Network status v6", "Status sítě v6"}, + {"Stopping in", "Zastavuji za"}, + {"Family", "Rodina"}, + {"Tunnel creation success rate", "Úspěšnost vytváření tunelů"}, + {"Received", "Přijato"}, + {"%.2f KiB/s", "%.2f KiB/s"}, + {"Sent", "Odesláno"}, + {"Transit", "Tranzit"}, + {"Data path", "Cesta k data souborům"}, + {"Hidden content. Press on text to see.", "Skrytý kontent. Pro zobrazení, klikni na text."}, + {"Router Ident", "Routerová Identita"}, + {"Router Family", "Rodina routerů"}, + {"Router Caps", "Omezení Routerů"}, + {"Version", "Verze"}, + {"Our external address", "Naše externí adresa"}, + {"supported", "podporováno"}, + {"Routers", "Routery"}, + {"Floodfills", "Floodfilly"}, + {"Client Tunnels", "Klientské tunely"}, + {"Services", "Služby"}, + {"Enabled", "Zapnuto"}, + {"Disabled", "Vypnuto"}, + {"Encrypted B33 address", "Šifrovaná adresa B33"}, + {"Address registration line", "Registrační řádek adresy"}, + {"Domain", "Doména"}, + {"Generate", "Vygenerovat"}, + {"Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", "Poznámka: výsledný řetězec může být použit pouze pro registraci 2LD domén (example.i2p). Pro registraci subdomén použijte prosím i2pd-tools."}, + {"Address", "Adresa"}, + {"Type", "Typ"}, + {"EncType", "EncType"}, + {"Inbound tunnels", "Příchozí tunely"}, + {"%dms", "%dms"}, + {"Outbound tunnels", "Odchozí tunely"}, + {"Tags", "Štítky"}, + {"Incoming", "Příchozí"}, + {"Outgoing", "Odchozí"}, + {"Destination", "Destinace"}, + {"Amount", "Množství"}, + {"Incoming Tags", "Příchozí štítky"}, + {"Tags sessions", "Relace štítků"}, + {"Status", "Status"}, + {"Local Destination", "Lokální Destinace"}, + {"Streams", "Toky"}, + {"Close stream", "Uzavřít tok"}, + {"I2CP session not found", "I2CP relace nenalezena"}, + {"I2CP is not enabled", "I2CP není zapnuto"}, + {"Invalid", "Neplatný"}, + {"Store type", "Druh uložení"}, + {"Expires", "Vyprší"}, + {"Non Expired Leases", "Nevypršené Leasy"}, + {"Gateway", "Brána"}, + {"TunnelID", "ID tunelu"}, + {"EndDate", "Datum ukončení"}, + {"Queue size", "Velikost fronty"}, + {"Run peer test", "Spustit peer test"}, + {"Decline transit tunnels", "Odmítnout tranzitní tunely"}, + {"Accept transit tunnels", "Přijmout tranzitní tunely"}, + {"Cancel graceful shutdown", "Zrušit hladké vypnutí"}, + {"Start graceful shutdown", "Zahájit hladké vypnutí"}, + {"Force shutdown", "Vynutit vypnutí"}, + {"Reload external CSS styles", "Znovu načíst externí CSS"}, + {"Note: any action done here are not persistent and not changes your config files.", "Poznámka: žádná vykonaná akce zde není trvalá a nemění konfigurační soubory."}, + {"Logging level", "Úroveň logování"}, + {"Transit tunnels limit", "Limit tranzitních tunelů"}, + {"Change", "Změnit"}, + {"Change language", "Změnit jazyk"}, + {"no transit tunnels currently built", "Žádný tranzitní tunel není momentálně vytvořen"}, + {"SAM disabled", "SAM vypnutý"}, + {"no sessions currently running", "Momentálně nejsou spuštěné žádné relace"}, + {"SAM session not found", "SAM relace nenalezena"}, + {"SAM Session", "SAM Relace"}, + {"Server Tunnels", "Server Tunely"}, + {"Client Forwards", "Přesměrování Klienta"}, + {"Server Forwards", "Přesměrování Serveru"}, + {"Unknown page", "Neznámá stránka"}, + {"Invalid token", "Neplatný token"}, + {"SUCCESS", "ÚSPĚCH"}, + {"Stream closed", "Tok uzavřen"}, + {"Stream not found or already was closed", "Tok nenalezen nebo byl již uzavřen"}, + {"Destination not found", "Destinace nenalezena"}, + {"StreamID can't be null", "StreamID nemůže být null"}, + {"Return to destination page", "Zpět na stránku destinací"}, + {"Back to commands list", "Zpět na list příkazů"}, + {"Register at reg.i2p", "Zaregistrovat na reg.i2p"}, + {"Description", "Popis"}, + {"A bit information about service on domain", "Trochu informací o službě na doméně"}, + {"Submit", "Odeslat"}, + {"Domain can't end with .b32.i2p", "Doména nesmí končit na .b32.i2p"}, + {"Domain must end with .i2p", "Doména musí končit s .i2p"}, + {"Such destination is not found", "Takováto destinace nebyla nalezena"}, + {"Unknown command", "Neznámý příkaz"}, + {"Command accepted", "Příkaz přijat"}, + {"Proxy error", "Chyba proxy serveru"}, + {"Proxy info", "Proxy informace"}, + {"Proxy error: Host not found", "Chyba proxy serveru: Hostitel nenalezen"}, + {"Remote host not found in router's addressbook", "Vzdálený hostitel nebyl nalezen v adresáři routeru"}, + {"You may try to find this host on jump services below", "Můžete se pokusit najít tohoto hostitele na startovacích službách níže"}, + {"Invalid request", "Neplatný požadavek"}, + {"Proxy unable to parse your request", "Proxy server nemohl zpracovat váš požadavek"}, + {"Invalid request URI", "Neplatný URI požadavek"}, + {"Can't detect destination host from request", "Nelze zjistit cílového hostitele z požadavku"}, + {"Outproxy failure", "Outproxy selhání"}, + {"Bad outproxy settings", "Špatné outproxy nastavení"}, + {"Host %s is not inside I2P network, but outproxy is not enabled", "Hostitel %s není uvnitř I2P sítě a outproxy není nastavena"}, + {"Unknown outproxy URL", "Neznámá outproxy URL"}, + {"Cannot resolve upstream proxy", "Nelze rozluštit upstream proxy server"}, + {"Hostname is too long", "Název hostitele je příliš dlouhý"}, + {"Cannot connect to upstream SOCKS proxy", "Nelze se připojit k upstream SOCKS proxy serveru"}, + {"Cannot negotiate with SOCKS proxy", "Nelze vyjednávat se SOCKS proxy serverem"}, + {"CONNECT error", "Chyba PŘIPOJENÍ"}, + {"Failed to connect", "Připojení se nezdařilo"}, + {"SOCKS proxy error", "Chyba SOCKS proxy serveru"}, + {"Failed to send request to upstream", "Odeslání žádosti upstream serveru se nezdařilo"}, + {"No reply from SOCKS proxy", "Žádná odpověď od SOCKS proxy serveru"}, + {"Cannot connect", "Nelze se připojit"}, + {"HTTP out proxy not implemented", "HTTP out proxy není implementován"}, + {"Cannot connect to upstream HTTP proxy", "Nelze se připojit k upstream HTTP proxy serveru"}, + {"Host is down", "Hostitel je nedostupný"}, + {"Can't create connection to requested host, it may be down. Please try again later.", "Připojení k požadovanému hostiteli nelze vytvořit, může být nedostupný. Zkuste to, prosím, znovu později."}, + {"", ""}, + }; + + static std::map> plurals + { + {"%d days", {"%d den", "%d dny", "%d dní", "%d dní"}}, + {"%d hours", {"%d hodina", "%d hodiny", "%d hodin", "%d hodin"}}, + {"%d minutes", {"%d minuta", "%d minuty", "%d minut", "%d minut"}}, + {"%d seconds", {"%d vteřina", "%d vteřiny", "%d vteřin", "%d vteřin"}}, + {"", {"", "", "", ""}}, + }; + + std::shared_ptr GetLocale() + { + return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); + } + +} // language +} // i18n +} // i2p diff --git a/i18n/English.cpp b/i18n/English.cpp new file mode 100644 index 00000000..2670e984 --- /dev/null +++ b/i18n/English.cpp @@ -0,0 +1,50 @@ +/* +* Copyright (c) 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 +#include "I18N.h" + +// English localization file +// This is an example translation file without strings in it. + +namespace i2p +{ +namespace i18n +{ +namespace english // language namespace +{ + // language name in lowercase + static std::string language = "english"; + + // See for language plural forms here: + // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html + static int plural (int n) { + return n != 1 ? 1 : 0; + } + + static std::map strings + { + {"", ""}, + }; + + static std::map> plurals + { + {"", {"", ""}}, + }; + + std::shared_ptr GetLocale() + { + return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); + } + +} // language +} // i18n +} // i2p diff --git a/i18n/French.cpp b/i18n/French.cpp new file mode 100644 index 00000000..8bec887c --- /dev/null +++ b/i18n/French.cpp @@ -0,0 +1,223 @@ +/* +* Copyright (c) 2022-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 +#include +#include "I18N.h" + +// French localization file + +namespace i2p +{ +namespace i18n +{ +namespace french // language namespace +{ + // language name in lowercase + static std::string language = "french"; + + // See for language plural forms here: + // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html + static int plural (int n) { + return n != 1 ? 1 : 0; + } + + static std::map strings + { + {"%.2f KiB", "%.2f Kio"}, + {"%.2f MiB", "%.2f Mio"}, + {"%.2f GiB", "%.2f Gio"}, + {"building", "En construction"}, + {"failed", "échoué"}, + {"expiring", "expiré"}, + {"established", "établi"}, + {"unknown", "inconnu"}, + {"exploratory", "exploratoire"}, + {"Purple I2P Webconsole", "Console web Purple I2P"}, + {"i2pd webconsole", "Console web i2pd"}, + {"Main page", "Page principale"}, + {"Router commands", "Commandes du routeur"}, + {"Local Destinations", "Destinations locales"}, + {"LeaseSets", "Jeu de baux"}, + {"Tunnels", "Tunnels"}, + {"Transit Tunnels", "Tunnels transitoires"}, + {"Transports", "Transports"}, + {"I2P tunnels", "Tunnels I2P"}, + {"SAM sessions", "Sessions SAM"}, + {"ERROR", "ERREUR"}, + {"OK", "OK"}, + {"Testing", "Test en cours"}, + {"Firewalled", "Derrière un pare-feu"}, + {"Unknown", "Inconnu"}, + {"Proxy", "Proxy"}, + {"Mesh", "Maillé"}, + {"Clock skew", "Horloge décalée"}, + {"Offline", "Hors ligne"}, + {"Symmetric NAT", "NAT symétrique"}, + {"Full cone NAT", "NAT à cône complet"}, + {"No Descriptors", "Aucuns Descripteurs"}, + {"Uptime", "Temps de fonctionnement"}, + {"Network status", "État du réseau"}, + {"Network status v6", "État du réseau v6"}, + {"Stopping in", "Arrêt dans"}, + {"Family", "Famille"}, + {"Tunnel creation success rate", "Taux de succès de création de tunnels"}, + {"Total tunnel creation success rate", "Taux de réussite de création de tunnel"}, + {"Received", "Reçu"}, + {"%.2f KiB/s", "%.2f Kio/s"}, + {"Sent", "Envoyé"}, + {"Transit", "Transité"}, + {"Data path", "Emplacement des données"}, + {"Hidden content. Press on text to see.", "Contenu caché. Cliquez sur le texte pour afficher."}, + {"Router Ident", "Identifiant du routeur"}, + {"Router Family", "Famille du routeur"}, + {"Router Caps", "Limiteurs du routeur"}, + {"Version", "Version"}, + {"Our external address", "Notre adresse externe"}, + {"supported", "supporté"}, + {"Routers", "Routeurs"}, + {"Floodfills", "Remplisseurs"}, + {"Client Tunnels", "Tunnels clients"}, + {"Services", "Services"}, + {"Enabled", "Activé"}, + {"Disabled", "Désactivé"}, + {"Encrypted B33 address", "Adresse B33 chiffrée"}, + {"Address registration line", "Ligne d'inscription de l'adresse"}, + {"Domain", "Domaine"}, + {"Generate", "Générer"}, + {"Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", "Note : La chaîne résultante peut seulement être utilisée pour enregistrer les domaines 2LD (exemple.i2p). Pour enregistrer des sous-domaines, veuillez utiliser i2pd-tools."}, + {"Address", "Adresse"}, + {"Type", "Type"}, + {"EncType", "EncType"}, + {"Expire LeaseSet", "Expiration du LeaseSet"}, + {"Inbound tunnels", "Tunnels entrants"}, + {"%dms", "%dms"}, + {"Outbound tunnels", "Tunnels sortants"}, + {"Tags", "Balises"}, + {"Incoming", "Entrant"}, + {"Outgoing", "Sortant"}, + {"Destination", "Destination"}, + {"Amount", "Quantité"}, + {"Incoming Tags", "Balises entrantes"}, + {"Tags sessions", "Sessions des balises"}, + {"Status", "Statut"}, + {"Local Destination", "Destination locale"}, + {"Streams", "Flux"}, + {"Close stream", "Fermer le flux"}, + {"Such destination is not found", "Cette destination est introuvable"}, + {"I2CP session not found", "Session I2CP introuvable"}, + {"I2CP is not enabled", "I2CP est désactivé"}, + {"Invalid", "Invalide"}, + {"Store type", "Type de stockage"}, + {"Expires", "Expire"}, + {"Non Expired Leases", "Baux non expirés"}, + {"Gateway", "Passerelle"}, + {"TunnelID", "ID du tunnel"}, + {"EndDate", "Date de fin"}, + {"floodfill mode is disabled", "le mode de remplissage est désactivé"}, + {"Queue size", "Longueur de la file"}, + {"Run peer test", "Lancer test des pairs"}, + {"Reload tunnels configuration", "Recharger la configuration des tunnels"}, + {"Decline transit tunnels", "Refuser les tunnels transitoires"}, + {"Accept transit tunnels", "Accepter les tunnels transitoires"}, + {"Cancel graceful shutdown", "Annuler l'arrêt gracieux"}, + {"Start graceful shutdown", "Démarrer l'arrêt gracieux"}, + {"Force shutdown", "Forcer l'arrêt"}, + {"Reload external CSS styles", "Rafraîchir les styles CSS externes"}, + {"Note: any action done here are not persistent and not changes your config files.", "Note : Toute action effectuée ici n'est pas permanente et ne modifie pas vos fichiers de configuration."}, + {"Logging level", "Niveau de journalisation"}, + {"Transit tunnels limit", "Limite sur les tunnels transitoires"}, + {"Change", "Changer"}, + {"Change language", "Changer la langue"}, + {"no transit tunnels currently built", "aucun tunnel transitoire présentement établi"}, + {"SAM disabled", "SAM désactivé"}, + {"no sessions currently running", "aucune session présentement en cours"}, + {"SAM session not found", "session SAM introuvable"}, + {"SAM Session", "Session SAM"}, + {"Server Tunnels", "Tunnels serveurs"}, + {"Client Forwards", "Transmission du client"}, + {"Server Forwards", "Transmission du serveur"}, + {"Unknown page", "Page inconnue"}, + {"Invalid token", "Jeton invalide"}, + {"SUCCESS", "SUCCÈS"}, + {"Stream closed", "Flux fermé"}, + {"Stream not found or already was closed", "Flux introuvable ou déjà fermé"}, + {"Destination not found", "Destination introuvable"}, + {"StreamID can't be null", "StreamID ne peut pas être vide"}, + {"Return to destination page", "Retourner à la page de destination"}, + {"You will be redirected in %d seconds", "Vous serez redirigé dans %d secondes"}, + {"LeaseSet expiration time updated", "Temps d'expiration du LeaseSet mis à jour"}, + {"LeaseSet is not found or already expired", "Le LeaseSet est introuvable ou a déjà expirée"}, + {"Transit tunnels count must not exceed %d", "Le nombre de tunnels de transit ne doit pas excéder %d"}, + {"Back to commands list", "Retour à la liste des commandes"}, + {"Register at reg.i2p", "Inscription à reg.i2p"}, + {"Description", "Description"}, + {"A bit information about service on domain", "Un peu d'information à propos des services disponibles dans le domaine"}, + {"Submit", "Soumettre"}, + {"Domain can't end with .b32.i2p", "Le domaine ne peut pas terminer par .b32.i2p"}, + {"Domain must end with .i2p", "Le domaine doit terminer par .i2p"}, + {"Unknown command", "Commande inconnue"}, + {"Command accepted", "Commande acceptée"}, + {"Proxy error", "Erreur de proxy"}, + {"Proxy info", "Information sur le proxy"}, + {"Proxy error: Host not found", "Erreur de proxy : Hôte introuvable"}, + {"Remote host not found in router's addressbook", "Hôte distant introuvable dans le carnet d'adresse du routeur"}, + {"You may try to find this host on jump services below", "Vous pouvez essayer de trouver cet hôte sur des services de redirection ci-dessous"}, + {"Invalid request", "Requête invalide"}, + {"Proxy unable to parse your request", "Proxy incapable de comprendre votre requête"}, + {"Addresshelper is not supported", "Assistant d'adresse non supporté"}, + {"Host %s is already in router's addressbook. Be careful: source of this URL may be harmful! Click here to update record: Continue.", "L'hôte %s est déjà dans le carnet d'adresses du routeur. Attention : la source de cette URL peut être nuisible ! Cliquez ici pour mettre à jour l'enregistrement : Continuer."}, + {"Addresshelper forced update rejected", "Mise à jour forcée des assistants d'adresses rejetée"}, + {"To add host %s in router's addressbook, click here: Continue.", "Pour ajouter l'hôte %s au carnet d'adresses du routeur, cliquez ici : Continuer."}, + {"Addresshelper request", "Demande à l'assistant d'adresse"}, + {"Host %s added to router's addressbook from helper. Click here to proceed: Continue.", "L'hôte %s a été ajouté au carnet d'adresses du routeur depuis l'assistant. Cliquez ici pour continuer : Continuer."}, + {"Addresshelper adding", "Ajout de l'assistant d'adresse"}, + {"Host %s is already in router's addressbook. Click here to update record: Continue.", "L'hôte %s est déjà dans le carnet d'adresses du routeur. Cliquez ici pour mettre à jour le dossier : Continuer."}, + {"Addresshelper update", "Mise à jour de l'assistant d'adresse"}, + {"Invalid request URI", "URI de la requête invalide"}, + {"Can't detect destination host from request", "Impossible de détecter l'hôte de destination à partir de la requête"}, + {"Outproxy failure", "Échec de proxy de sortie"}, + {"Bad outproxy settings", "Mauvaise configuration du proxy de sortie"}, + {"Host %s is not inside I2P network, but outproxy is not enabled", "Hôte %s pas dans le réseau I2P, mais le proxy de sortie n'est pas activé"}, + {"Unknown outproxy URL", "URL du proxy de sortie inconnu"}, + {"Cannot resolve upstream proxy", "Impossible de résoudre l'adresse du proxy en amont"}, + {"Hostname is too long", "Nom d'hôte trop long"}, + {"Cannot connect to upstream SOCKS proxy", "Impossible de se connecter au proxy SOCKS en amont"}, + {"Cannot negotiate with SOCKS proxy", "Impossible de négocier avec le proxy SOCKS"}, + {"CONNECT error", "Erreur de connexion"}, + {"Failed to connect", "Échec de connexion"}, + {"SOCKS proxy error", "Erreur de proxy SOCKS"}, + {"Failed to send request to upstream", "Erreur lors de l'envoie de la requête en amont"}, + {"No reply from SOCKS proxy", "Pas de réponse du proxy SOCKS"}, + {"Cannot connect", "Impossible de connecter"}, + {"HTTP out proxy not implemented", "Proxy de sortie HTTP non implémenté"}, + {"Cannot connect to upstream HTTP proxy", "Impossible de se connecter au proxy HTTP en amont"}, + {"Host is down", "Hôte hors service"}, + {"Can't create connection to requested host, it may be down. Please try again later.", "Impossible d'établir une connexion avec l'hôte, il est peut-être hors service. Veuillez réessayer plus tard."}, + {"", ""}, + }; + + static std::map> plurals + { + {"%d days", {"%d jour", "%d jours"}}, + {"%d hours", {"%d heure", "%d heures"}}, + {"%d minutes", {"%d minute", "%d minutes"}}, + {"%d seconds", {"%d seconde", "%d secondes"}}, + {"", {"", ""}}, + }; + + std::shared_ptr GetLocale() + { + return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); + } + +} // language +} // i18n +} // i2p diff --git a/i18n/German.cpp b/i18n/German.cpp new file mode 100644 index 00000000..02662e8e --- /dev/null +++ b/i18n/German.cpp @@ -0,0 +1,218 @@ +/* +* Copyright (c) 2022-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 +#include +#include "I18N.h" + +// German localization file + +namespace i2p +{ +namespace i18n +{ +namespace german // language namespace +{ + // language name in lowercase + static std::string language = "german"; + + // See for language plural forms here: + // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html + static int plural (int n) { + return n != 1 ? 1 : 0; + } + + static std::map strings + { + {"%.2f KiB", "%.2f KiB"}, + {"%.2f MiB", "%.2f MiB"}, + {"%.2f GiB", "%.2f GiB"}, + {"building", "In Bau"}, + {"failed", "fehlgeschlagen"}, + {"expiring", "läuft ab"}, + {"established", "hergestellt"}, + {"unknown", "Unbekannt"}, + {"exploratory", "erforschend"}, + {"Purple I2P Webconsole", "Purple I2P-Webkonsole"}, + {"i2pd webconsole", "i2pd-Webkonsole"}, + {"Main page", "Startseite"}, + {"Router commands", "Routerbefehle"}, + {"Local Destinations", "Lokale Ziele"}, + {"LeaseSets", "LeaseSets"}, + {"Tunnels", "Tunnel"}, + {"Transit Tunnels", "Transittunnel"}, + {"Transports", "Transporte"}, + {"I2P tunnels", "I2P Tunnel"}, + {"SAM sessions", "SAM Sitzungen"}, + {"ERROR", "FEHLER"}, + {"OK", "OK"}, + {"Testing", "Testen"}, + {"Firewalled", "Hinter einer Firewall"}, + {"Unknown", "Unbekannt"}, + {"Proxy", "Proxy"}, + {"Mesh", "Mesh"}, + {"Clock skew", "Zeitabweichung"}, + {"Offline", "Offline"}, + {"Symmetric NAT", "Symmetrisches NAT"}, + {"No Descriptors", "Keine Beschreibungen"}, + {"Uptime", "Laufzeit"}, + {"Network status", "Netzwerkstatus"}, + {"Network status v6", "Netzwerkstatus v6"}, + {"Stopping in", "Stoppt in"}, + {"Family", "Familie"}, + {"Tunnel creation success rate", "Erfolgsrate der Tunnelerstellung"}, + {"Received", "Eingegangen"}, + {"%.2f KiB/s", "%.2f KiB/s"}, + {"Sent", "Gesendet"}, + {"Transit", "Transit"}, + {"Data path", "Datenpfad"}, + {"Hidden content. Press on text to see.", "Versteckter Inhalt. Klicke hier, um ihn zu sehen."}, + {"Router Ident", "Routeridentität"}, + {"Router Family", "Routerfamilie"}, + {"Router Caps", "Routerattribute"}, + {"Version", "Version"}, + {"Our external address", "Unsere externe Adresse"}, + {"supported", "unterstützt"}, + {"Routers", "Router"}, + {"Floodfills", "Floodfills"}, + {"Client Tunnels", "Clienttunnel"}, + {"Services", "Services"}, + {"Enabled", "Aktiviert"}, + {"Disabled", "Deaktiviert"}, + {"Encrypted B33 address", "Verschlüsselte B33-Adresse"}, + {"Address registration line", "Adressregistrierungszeile"}, + {"Domain", "Domain"}, + {"Generate", "Generieren"}, + {"Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", "Hinweis: Der resultierende String kann nur für die Registrierung einer 2LD-Domain (beispiel.i2p) benutzt werden. Für die Registrierung von Subdomains kann i2pd-tools verwendet werden."}, + {"Address", "Adresse"}, + {"Type", "Typ"}, + {"EncType", "Verschlüsselungstyp"}, + {"Inbound tunnels", "Eingehende Tunnel"}, + {"%dms", "%dms"}, + {"Outbound tunnels", "Ausgehende Tunnel"}, + {"Tags", "Tags"}, + {"Incoming", "Eingehend"}, + {"Outgoing", "Ausgehend"}, + {"Destination", "Ziel"}, + {"Amount", "Anzahl"}, + {"Incoming Tags", "Eingehende Tags"}, + {"Tags sessions", "Tags-Sitzungen"}, + {"Status", "Status"}, + {"Local Destination", "Lokales Ziel"}, + {"Streams", "Streams"}, + {"Close stream", "Stream schließen"}, + {"I2CP session not found", "I2CP-Sitzung nicht gefunden"}, + {"I2CP is not enabled", "I2CP ist nicht aktiviert"}, + {"Invalid", "Ungültig"}, + {"Store type", "Speichertyp"}, + {"Expires", "Ablaufdatum"}, + {"Non Expired Leases", "Nicht abgelaufene Leases"}, + {"Gateway", "Gateway"}, + {"TunnelID", "TunnelID"}, + {"EndDate", "Enddatum"}, + {"floodfill mode is disabled", "Floodfill Modus ist deaktiviert"}, + {"Queue size", "Größe der Warteschlange"}, + {"Run peer test", "Peer-Test durchführen"}, + {"Reload tunnels configuration", "Tunnel Konfiguration neu laden"}, + {"Decline transit tunnels", "Transittunnel ablehnen"}, + {"Accept transit tunnels", "Transittunnel akzeptieren"}, + {"Cancel graceful shutdown", "Beende das kontrollierte Herunterfahren"}, + {"Start graceful shutdown", "Starte das kontrollierte Herunterfahren"}, + {"Force shutdown", "Herunterfahren erzwingen"}, + {"Reload external CSS styles", "Lade externe CSS-Stile neu"}, + {"Note: any action done here are not persistent and not changes your config files.", "Hinweis: Alle hier durchgeführten Aktionen sind nicht dauerhaft und ändern die Konfigurationsdateien nicht."}, + {"Logging level", "Protokollierungslevel"}, + {"Transit tunnels limit", "Limit für Transittunnel"}, + {"Change", "Ändern"}, + {"Change language", "Sprache ändern"}, + {"no transit tunnels currently built", "derzeit keine Transittunnel aufgebaut"}, + {"SAM disabled", "SAM deaktiviert"}, + {"no sessions currently running", "Derzeit keine laufenden Sitzungen"}, + {"SAM session not found", "SAM-Sitzung nicht gefunden"}, + {"SAM Session", "SAM-Sitzung"}, + {"Server Tunnels", "Servertunnel"}, + {"Client Forwards", "Client-Weiterleitungen"}, + {"Server Forwards", "Server-Weiterleitungen"}, + {"Unknown page", "Unbekannte Seite"}, + {"Invalid token", "Ungültiger Token"}, + {"SUCCESS", "ERFOLGREICH"}, + {"Stream closed", "Stream geschlossen"}, + {"Stream not found or already was closed", "Stream nicht gefunden oder bereits geschlossen"}, + {"Destination not found", "Ziel nicht gefunden"}, + {"StreamID can't be null", "StreamID kann nicht null sein"}, + {"Return to destination page", "Zurück zur Ziel-Seite"}, + {"You will be redirected in %d seconds", "Du wirst umgeleitet in %d Sekunden"}, + {"Transit tunnels count must not exceed %d", "Die Anzahl der Transittunnel darf nicht über %d gehen"}, + {"Back to commands list", "Zurück zur Befehlsliste"}, + {"Register at reg.i2p", "Auf reg.i2p registrieren"}, + {"Description", "Beschreibung"}, + {"A bit information about service on domain", "Ein paar Informationen über den Service auf der Domain"}, + {"Submit", "Absenden"}, + {"Domain can't end with .b32.i2p", "Domain kann nicht auf .b32.i2p enden"}, + {"Domain must end with .i2p", "Domain muss auf .i2p enden"}, + {"Such destination is not found", "Ein solches Ziel konnte nicht gefunden werden"}, + {"Unknown command", "Unbekannter Befehl"}, + {"Command accepted", "Befehl akzeptiert"}, + {"Proxy error", "Proxy-Fehler"}, + {"Proxy info", "Proxy-Info"}, + {"Proxy error: Host not found", "Proxy-Fehler: Host nicht gefunden"}, + {"Remote host not found in router's addressbook", "Remote-Host nicht im Router-Adressbuch gefunden"}, + {"You may try to find this host on jump services below", "Vielleicht kannst du diesen Host auf einem der nachfolgenden Jump-Services finden"}, + {"Invalid request", "Ungültige Anfrage"}, + {"Proxy unable to parse your request", "Proxy konnte die Anfrage nicht verarbeiten"}, + {"Addresshelper is not supported", "Adresshelfer wird nicht unterstützt"}, + {"Host %s is already in router's addressbook. Be careful: source of this URL may be harmful! Click here to update record: Continue.", "Host %s ist bereits im Adressbuch des Routers. Vorsicht: Die Quelle dieser URL kann schädlich sein! Klicken Sie hier, um den Datensatz zu aktualisieren: Weiter."}, + {"Addresshelper forced update rejected", "Adresshelfer gezwungene Aktualisierung abgelehnt"}, + {"To add host %s in router's addressbook, click here: Continue.", "Um den Host %s im Adressbuch des Routers hinzuzufügen, klicken Sie hier: Weiter."}, + {"Addresshelper request", "Adresshelfer gefunden"}, + {"Host %s added to router's addressbook from helper. Click here to proceed: Continue.", "Host %s wurde vom Helfer zum Adressbuch des Routers hinzugefügt. Klicken Sie hier, um fortzufahren: Weiter."}, + {"Addresshelper adding", "Adresshelfer hinzufügen"}, + {"Host %s is already in router's addressbook. Click here to update record: Continue.", "Host %s ist bereits im Adressbuch des Routers. Klicken Sie hier, um den Eintrag zu aktualisieren: Weiter."}, + {"Addresshelper update", "Adresshelfer aktualisieren"}, + {"Invalid request URI", "Ungültige Anfrage-URI"}, + {"Can't detect destination host from request", "Kann den Ziel-Host von der Anfrage nicht erkennen"}, + {"Outproxy failure", "Outproxy-Fehler"}, + {"Bad outproxy settings", "Ungültige Outproxy-Einstellungen"}, + {"Host %s is not inside I2P network, but outproxy is not enabled", "Host %s außerhalb des I2P-Netzwerks, aber Outproxy ist nicht aktiviert"}, + {"Unknown outproxy URL", "Unbekannte Outproxy-URL"}, + {"Cannot resolve upstream proxy", "Kann den Upstream-Proxy nicht auflösen"}, + {"Hostname is too long", "Hostname zu lang"}, + {"Cannot connect to upstream SOCKS proxy", "Kann keine Verbindung zum Upstream-SOCKS-Proxy herstellen"}, + {"Cannot negotiate with SOCKS proxy", "Kann nicht mit SOCKS-Proxy verhandeln"}, + {"CONNECT error", "CONNECT-Fehler"}, + {"Failed to connect", "Verbindung konnte nicht hergestellt werden"}, + {"SOCKS proxy error", "SOCKS-Proxy-Fehler"}, + {"Failed to send request to upstream", "Anfrage an den Upstream zu senden ist gescheitert"}, + {"No reply from SOCKS proxy", "Keine Antwort vom SOCKS-Proxy"}, + {"Cannot connect", "Kann nicht verbinden"}, + {"HTTP out proxy not implemented", "HTTP-Outproxy nicht implementiert"}, + {"Cannot connect to upstream HTTP proxy", "Kann nicht zu Upstream-HTTP-Proxy verbinden"}, + {"Host is down", "Host ist offline"}, + {"Can't create connection to requested host, it may be down. Please try again later.", "Konnte keine Verbindung zum angefragten Host aufbauen, vielleicht ist er offline. Versuche es später noch mal."}, + {"", ""}, + }; + + static std::map> plurals + { + {"%d days", {"%d Tag", "%d Tage"}}, + {"%d hours", {"%d Stunde", "%d Stunden"}}, + {"%d minutes", {"%d Minute", "%d Minuten"}}, + {"%d seconds", {"%d Sekunde", "%d Sekunden"}}, + {"", {"", ""}}, + }; + + std::shared_ptr GetLocale() + { + return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); + } + +} // language +} // i18n +} // i2p diff --git a/i18n/I18N.cpp b/i18n/I18N.cpp new file mode 100644 index 00000000..cf4873eb --- /dev/null +++ b/i18n/I18N.cpp @@ -0,0 +1,43 @@ +/* +* 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 +*/ + +#include +#include "ClientContext.h" +#include "I18N_langs.h" +#include "I18N.h" + +namespace i2p +{ +namespace i18n +{ + void SetLanguage(const std::string &lang) + { + const auto it = i2p::i18n::languages.find(lang); + if (it == i2p::i18n::languages.end()) // fallback + { + i2p::client::context.SetLanguage (i2p::i18n::english::GetLocale()); + setlocale(LC_NUMERIC, "english"); + } + else + { + i2p::client::context.SetLanguage (it->second.LocaleFunc()); + setlocale(LC_NUMERIC, lang.c_str()); // set decimal point based on language + } + } + + std::string translate (const std::string& arg) + { + return i2p::client::context.GetLanguage ()->GetString (arg); + } + + std::string translate (const std::string& arg, const std::string& arg2, const int& n) + { + return i2p::client::context.GetLanguage ()->GetPlural (arg, arg2, n); + } +} // i18n +} // i2p diff --git a/i18n/I18N.h b/i18n/I18N.h new file mode 100644 index 00000000..6ec5b16e --- /dev/null +++ b/i18n/I18N.h @@ -0,0 +1,135 @@ +/* +* Copyright (c) 2021-2023, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#ifndef __I18N_H__ +#define __I18N_H__ + +#include +#include +#include +#include + +namespace i2p +{ +namespace i18n +{ + class Locale + { + public: + Locale ( + const std::string& language, + const std::map& strings, + const std::map>& plurals, + std::function formula + ): m_Language (language), m_Strings (strings), m_Plurals (plurals), m_Formula (formula) { }; + + // Get activated language name for webconsole + std::string GetLanguage() const + { + return m_Language; + } + + std::string GetString (const std::string& arg) const + { + const auto it = m_Strings.find(arg); + if (it == m_Strings.end()) + { + return arg; + } + else + { + return it->second; + } + } + + std::string GetPlural (const std::string& arg, const std::string& arg2, const int& n) const + { + const auto it = m_Plurals.find(arg2); + if (it == m_Plurals.end()) // not found, fallback to english + { + return n == 1 ? arg : arg2; + } + else + { + int form = m_Formula(n); + return it->second[form]; + } + } + + private: + const std::string m_Language; + const std::map m_Strings; + const std::map> m_Plurals; + std::function m_Formula; + }; + + void SetLanguage(const std::string &lang); + std::string translate (const std::string& arg); + std::string translate (const std::string& arg, const std::string& arg2, const int& n); +} // i18n +} // i2p + +/** + * @brief Get translation of string + * @param arg String with message + */ +template +std::string tr (TValue&& arg) +{ + return i2p::i18n::translate(std::forward(arg)); +} + +/** + * @brief Get translation of string and format it + * @param arg String with message + * @param args Array of arguments for string formatting +*/ +template +std::string tr (TValue&& arg, TArgs&&... args) +{ + std::string tr_str = i2p::i18n::translate(std::forward(arg)); + + size_t size = std::snprintf(NULL, 0, tr_str.c_str(), std::forward(args)...); + std::string str(size, 0); + std::snprintf(&str.front(), size + 1, tr_str.c_str(), std::forward(args)...); + + return str; +} + +/** + * @brief Get translation of string with plural forms + * @param arg String with message in singular form + * @param arg2 String with message in plural form + * @param n Integer, used for selection of form + */ +template +std::string ntr (TValue&& arg, TValue2&& arg2, int& n) +{ + return i2p::i18n::translate(std::forward(arg), std::forward(arg2), std::forward(n)); +} + +/** + * @brief Get translation of string with plural forms and format it + * @param arg String with message in singular form + * @param arg2 String with message in plural form + * @param n Integer, used for selection of form + * @param args Array of arguments for string formatting + */ +template +std::string ntr (TValue&& arg, TValue2&& arg2, int& n, TArgs&&... args) +{ + std::string tr_str = i2p::i18n::translate(std::forward(arg), std::forward(arg2), std::forward(n)); + + size_t size = std::snprintf(NULL, 0, tr_str.c_str(), std::forward(args)...); + std::string str(size, 0); + std::snprintf(&str.front(), size + 1, tr_str.c_str(), std::forward(args)...); + + return str; +} + +#endif // __I18N_H__ diff --git a/i18n/I18N_langs.h b/i18n/I18N_langs.h new file mode 100644 index 00000000..6426e2ce --- /dev/null +++ b/i18n/I18N_langs.h @@ -0,0 +1,71 @@ +/* +* Copyright (c) 2021-2023, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#ifndef __I18N_LANGS_H__ +#define __I18N_LANGS_H__ + +#include "I18N.h" + +namespace i2p +{ +namespace i18n +{ + struct langData + { + std::string LocaleName; // localized name + std::string ShortCode; // short language code, like "en" + std::function (void)> LocaleFunc; + }; + + // Add localization here with language name as namespace + namespace afrikaans { std::shared_ptr GetLocale (); } + namespace armenian { std::shared_ptr GetLocale (); } + namespace chinese { std::shared_ptr GetLocale (); } + namespace czech { std::shared_ptr GetLocale (); } + namespace english { std::shared_ptr GetLocale (); } + namespace french { std::shared_ptr GetLocale (); } + namespace german { std::shared_ptr GetLocale (); } + namespace italian { std::shared_ptr GetLocale (); } + namespace polish { std::shared_ptr GetLocale (); } + namespace portuguese { std::shared_ptr GetLocale (); } + namespace russian { std::shared_ptr GetLocale (); } + namespace spanish { std::shared_ptr GetLocale (); } + namespace swedish { std::shared_ptr GetLocale (); } + namespace turkish { std::shared_ptr GetLocale (); } + namespace turkmen { std::shared_ptr GetLocale (); } + namespace ukrainian { std::shared_ptr GetLocale (); } + namespace uzbek { std::shared_ptr GetLocale (); } + + /** + * That map contains international language name lower-case, name in it's language and it's code + */ + static std::map languages + { + { "afrikaans", {"Afrikaans", "af", i2p::i18n::afrikaans::GetLocale} }, + { "armenian", {"hայերէն", "hy", i2p::i18n::armenian::GetLocale} }, + { "chinese", {"简体字", "zh-CN", i2p::i18n::chinese::GetLocale} }, + { "czech", {"čeština", "cs", i2p::i18n::czech::GetLocale} }, + { "english", {"English", "en", i2p::i18n::english::GetLocale} }, + { "french", {"Français", "fr", i2p::i18n::french::GetLocale} }, + { "german", {"Deutsch", "de", i2p::i18n::german::GetLocale} }, + { "italian", {"Italiano", "it", i2p::i18n::italian::GetLocale} }, + { "polish", {"Polski", "pl", i2p::i18n::polish::GetLocale} }, + { "portuguese", {"Português", "pt", i2p::i18n::portuguese::GetLocale} }, + { "russian", {"Русский язык", "ru", i2p::i18n::russian::GetLocale} }, + { "spanish", {"Español", "es", i2p::i18n::spanish::GetLocale} }, + { "swedish", {"Svenska", "sv", i2p::i18n::swedish::GetLocale} }, + { "turkish", {"Türk dili", "tr", i2p::i18n::turkish::GetLocale} }, + { "turkmen", {"Türkmen dili", "tk", i2p::i18n::turkmen::GetLocale} }, + { "ukrainian", {"Украї́нська мо́ва", "uk", i2p::i18n::ukrainian::GetLocale} }, + { "uzbek", {"Oʻzbek", "uz", i2p::i18n::uzbek::GetLocale} }, + }; + +} // i18n +} // i2p + +#endif // __I18N_LANGS_H__ diff --git a/i18n/Italian.cpp b/i18n/Italian.cpp new file mode 100644 index 00000000..386372d5 --- /dev/null +++ b/i18n/Italian.cpp @@ -0,0 +1,219 @@ +/* +* Copyright (c) 2022-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 +#include +#include "I18N.h" + +// Italian localization file + +namespace i2p +{ +namespace i18n +{ +namespace italian // language namespace +{ + // language name in lowercase + static std::string language = "italian"; + + // See for language plural forms here: + // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html + static int plural (int n) { + return n != 1 ? 1 : 0; + } + + static std::map strings + { + {"%.2f KiB", "%.2f KiB"}, + {"%.2f MiB", "%.2f MiB"}, + {"%.2f GiB", "%.2f GiB"}, + {"building", "in costruzione"}, + {"failed", "fallito"}, + {"expiring", "in scadenza"}, + {"established", "stabilita"}, + {"unknown", "sconosciuto"}, + {"exploratory", "esplorativo"}, + {"Purple I2P Webconsole", "Terminale web Purple I2P"}, + {"i2pd webconsole", "Terminal web i2pd"}, + {"Main page", "Pagina principale"}, + {"Router commands", "Comandi router"}, + {"Local Destinations", "Destinazioni locali"}, + {"LeaseSets", "LeaseSets"}, + {"Tunnels", "Tunnel"}, + {"Transit Tunnels", "Tunnel di transito"}, + {"Transports", "Trasporti"}, + {"I2P tunnels", "Tunnel I2P"}, + {"SAM sessions", "Sessioni SAM"}, + {"ERROR", "ERRORE"}, + {"OK", "OK"}, + {"Testing", "Testando"}, + {"Firewalled", "Protetto da firewall"}, + {"Unknown", "Sconosciuto"}, + {"Proxy", "Proxy"}, + {"Mesh", "Mesh"}, + {"Clock skew", "Orologio disallineato"}, + {"Offline", "Disconnesso"}, + {"Symmetric NAT", "NAT simmetrico"}, + {"Full cone NAT", "Cono completo NAT"}, + {"No Descriptors", "Nessun descrittore"}, + {"Uptime", "In funzione da"}, + {"Network status", "Stato della rete"}, + {"Network status v6", "Stato della rete v6"}, + {"Stopping in", "Arresto in"}, + {"Family", "Famiglia"}, + {"Tunnel creation success rate", "Percentuale di tunnel creati con successo"}, + {"Received", "Ricevuti"}, + {"%.2f KiB/s", "%.2f KiB/s"}, + {"Sent", "Inviati"}, + {"Transit", "Transitati"}, + {"Data path", "Percorso dati"}, + {"Hidden content. Press on text to see.", "Contenuto nascosto. Premi sul testo per vedere."}, + {"Router Ident", "Identificativo del router"}, + {"Router Family", "Famiglia del router"}, + {"Router Caps", "Limiti del router"}, + {"Version", "Versione"}, + {"Our external address", "Il nostro indirizzo esterno"}, + {"supported", "supportato"}, + {"Routers", "Router"}, + {"Floodfills", "Floodfill"}, + {"Client Tunnels", "Tunnel client"}, + {"Services", "Servizi"}, + {"Enabled", "Abilitato"}, + {"Disabled", "Disabilitato"}, + {"Encrypted B33 address", "Indirizzo criptato B33"}, + {"Address registration line", "Linea di registrazione indirizzo"}, + {"Domain", "Dominio"}, + {"Generate", "Genera"}, + {"Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", "Nota: la stringa risultante può essere utilizzata solo per registrare domini 2LD (example.i2p). Per registrare i sottodomini, si prega di utilizzare i2pd-tools."}, + {"Address", "Indirizzo"}, + {"Type", "Tipologia"}, + {"EncType", "Tipo di crittografia"}, + {"Inbound tunnels", "Tunnel in entrata"}, + {"%dms", "%dms"}, + {"Outbound tunnels", "Tunnel in uscita"}, + {"Tags", "Tag"}, + {"Incoming", "In entrata"}, + {"Outgoing", "In uscita"}, + {"Destination", "Destinazione"}, + {"Amount", "Quantità"}, + {"Incoming Tags", "Tag in entrata"}, + {"Tags sessions", "Sessioni dei tag"}, + {"Status", "Stato"}, + {"Local Destination", "Destinazione locale"}, + {"Streams", "Flussi"}, + {"Close stream", "Interrompi il flusso"}, + {"I2CP session not found", "Sessione I2CP non trovata"}, + {"I2CP is not enabled", "I2CP non è abilitato"}, + {"Invalid", "Invalido"}, + {"Store type", "Tipologia di archivio"}, + {"Expires", "Scade"}, + {"Non Expired Leases", "Lease non scaduti"}, + {"Gateway", "Gateway"}, + {"TunnelID", "TunnelID"}, + {"EndDate", "Data di fine"}, + {"floodfill mode is disabled", "la modalità floodfill è disabilitata"}, + {"Queue size", "Dimensione della coda"}, + {"Run peer test", "Esegui il test dei peer"}, + {"Reload tunnels configuration", "Ricarica la configurazione dei tunnel"}, + {"Decline transit tunnels", "Rifiuta tunnel di transito"}, + {"Accept transit tunnels", "Accetta tunnel di transito"}, + {"Cancel graceful shutdown", "Annulla l'interruzione controllata"}, + {"Start graceful shutdown", "Avvia l'interruzione controllata"}, + {"Force shutdown", "Forza l'arresto"}, + {"Reload external CSS styles", "Ricarica gli stili CSS esterni"}, + {"Note: any action done here are not persistent and not changes your config files.", "Nota: qualsiasi azione effettuata qui non è persistente e non modifica i file di configurazione."}, + {"Logging level", "Livello di log"}, + {"Transit tunnels limit", "Limite di tunnel di transito"}, + {"Change", "Modifica"}, + {"Change language", "Modifica linguaggio"}, + {"no transit tunnels currently built", "Attualmente non ci sono tunnel di transito instaurati"}, + {"SAM disabled", "SAM disabilitato"}, + {"no sessions currently running", "Attualmente non ci sono sessioni attive"}, + {"SAM session not found", "Sessione SAM non trovata"}, + {"SAM Session", "Sessione SAM"}, + {"Server Tunnels", "Tunnel server"}, + {"Client Forwards", "Client di inoltro"}, + {"Server Forwards", "Server di inoltro"}, + {"Unknown page", "Pagina sconosciuta"}, + {"Invalid token", "Token non valido"}, + {"SUCCESS", "SUCCESSO"}, + {"Stream closed", "Flusso terminato"}, + {"Stream not found or already was closed", "Il flusso non è stato trovato oppure è già stato terminato"}, + {"Destination not found", "Destinazione non trovata"}, + {"StreamID can't be null", "Lo StreamID non può essere null"}, + {"Return to destination page", "Ritorna alla pagina di destinazione"}, + {"You will be redirected in %d seconds", "Sarai reindirizzato tra %d secondi"}, + {"Transit tunnels count must not exceed %d", "Il conteggio dei tunnel di transito non deve superare %d"}, + {"Back to commands list", "Ritorna alla lista dei comandi"}, + {"Register at reg.i2p", "Registra a reg.i2p"}, + {"Description", "Descrizione"}, + {"A bit information about service on domain", "Alcune informazioni riguardo il servizio sul dominio"}, + {"Submit", "Invia"}, + {"Domain can't end with .b32.i2p", "I domini non possono terminare con .b32.i2p"}, + {"Domain must end with .i2p", "I domini devono terminare con .i2p"}, + {"Such destination is not found", "Questa destinazione non è stata trovata"}, + {"Unknown command", "Comando sconosciuto"}, + {"Command accepted", "Comando accettato"}, + {"Proxy error", "Errore del proxy"}, + {"Proxy info", "Informazioni del proxy"}, + {"Proxy error: Host not found", "Errore del proxy: Host non trovato"}, + {"Remote host not found in router's addressbook", "L'host remoto non è stato trovato nella rubrica del router"}, + {"You may try to find this host on jump services below", "Si può provare a trovare questo host sui servizi di salto qui sotto"}, + {"Invalid request", "Richiesta non valida"}, + {"Proxy unable to parse your request", "Il proxy non è in grado di elaborare la tua richiesta"}, + {"Addresshelper is not supported", "Addresshelper non è supportato"}, + {"Host %s is already in router's addressbook. Be careful: source of this URL may be harmful! Click here to update record: Continue.", "L'host %s è già nella rubrica del router. Attenzione: la fonte di questo URL potrebbe essere dannosa! Fai clic qui per aggiornare il record: Continua."}, + {"Addresshelper forced update rejected", "Aggiornamento forzato dell'helper degli indirizzi rifiutato"}, + {"To add host %s in router's addressbook, click here: Continue.", "Per aggiungere host %s nella rubrica del router, clicca qui: Continua."}, + {"Addresshelper request", "Richiesta di indirizzo helper"}, + {"Host %s added to router's addressbook from helper. Click here to proceed: Continue.", "L'host %s viene aggiunto alla rubrica del router dall'helper. Fai clic qui per procedere: Continua."}, + {"Addresshelper adding", "Aggiunta di Addresshelper"}, + {"Host %s is already in router's addressbook. Click here to update record: Continue.", "L'host %s è già nella rubrica del router. Clicca qui per aggiornare il record: Continua."}, + {"Addresshelper update", "Aggiornamento dell'helper degli indirizzi"}, + {"Invalid request URI", "URI della richiesta non valido"}, + {"Can't detect destination host from request", "Impossibile determinare l'host di destinazione dalla richiesta"}, + {"Outproxy failure", "Fallimento del proxy di uscita"}, + {"Bad outproxy settings", "Impostazioni errate del proxy di uscita"}, + {"Host %s is not inside I2P network, but outproxy is not enabled", "Host %s non all'interno della rete I2P, ma il proxy di uscita non è abilitato"}, + {"Unknown outproxy URL", "URL del proxy di uscita sconosciuto"}, + {"Cannot resolve upstream proxy", "Impossibile identificare il flusso a monte del proxy"}, + {"Hostname is too long", "Il nome dell'host è troppo lungo"}, + {"Cannot connect to upstream SOCKS proxy", "Impossibile connettersi al flusso a monte del proxy SOCKS"}, + {"Cannot negotiate with SOCKS proxy", "Impossibile negoziare con il proxy SOCKS"}, + {"CONNECT error", "Errore di connessione"}, + {"Failed to connect", "Connessione fallita"}, + {"SOCKS proxy error", "Errore del proxy SOCKS"}, + {"Failed to send request to upstream", "Invio della richiesta a monte non riuscito"}, + {"No reply from SOCKS proxy", "Nessuna risposta dal proxy SOCKS"}, + {"Cannot connect", "Impossibile connettersi"}, + {"HTTP out proxy not implemented", "Proxy HTTP di uscita non implementato"}, + {"Cannot connect to upstream HTTP proxy", "Impossibile connettersi al flusso a monte del proxy HTTP"}, + {"Host is down", "L'host è offline"}, + {"Can't create connection to requested host, it may be down. Please try again later.", "Impossibile creare la connessione all'host richiesto, probabilmente è offline. Riprova più tardi."}, + {"", ""}, + }; + + static std::map> plurals + { + {"%d days", {"%d giorno", "%d giorni"}}, + {"%d hours", {"%d ora", "%d ore"}}, + {"%d minutes", {"%d minuto", "%d minuti"}}, + {"%d seconds", {"%d secondo", "%d secondi"}}, + {"", {"", ""}}, + }; + + std::shared_ptr GetLocale() + { + return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); + } + +} // language +} // i18n +} // i2p diff --git a/i18n/Polish.cpp b/i18n/Polish.cpp new file mode 100644 index 00000000..26661231 --- /dev/null +++ b/i18n/Polish.cpp @@ -0,0 +1,59 @@ +/* +* Copyright (c) 2023, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#include +#include +#include +#include +#include "I18N.h" + +// Polish localization file + +namespace i2p +{ +namespace i18n +{ +namespace polish // language namespace +{ + // language name in lowercase + static std::string language = "polish"; + + // See for language plural forms here: + // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html + static int plural (int n) { + return (n == 1 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2); + } + + static std::map strings + { + {"building", "Kompilowanie"}, + {"failed", "nieudane"}, + {"expiring", "wygasający"}, + {"established", "ustanowiony"}, + {"Main page", "Strona główna"}, + {"Router commands", "Komendy routera"}, + {"Tunnels", "Tunele"}, + {"OK", "Ok"}, + {"Uptime", "Czas pracy"}, + {"Sent", "Wysłane"}, + {"", ""}, + }; + + static std::map> plurals + { + {"", {"", "", ""}}, + }; + + std::shared_ptr GetLocale() + { + return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); + } + +} // language +} // i18n +} // i2p diff --git a/i18n/Portuguese.cpp b/i18n/Portuguese.cpp new file mode 100644 index 00000000..8ba7f0cd --- /dev/null +++ b/i18n/Portuguese.cpp @@ -0,0 +1,219 @@ +/* +* Copyright (c) 2023, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#include +#include +#include +#include +#include "I18N.h" + +// Portuguese localization file + +namespace i2p +{ +namespace i18n +{ +namespace portuguese // language namespace +{ + // language name in lowercase + static std::string language = "portuguese"; + + // See for language plural forms here: + // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html + static int plural (int n) { + return n != 1 ? 1 : 0; + } + + static std::map strings + { + {"%.2f KiB", "%.2f KiB"}, + {"%.2f MiB", "%.2f MiB"}, + {"%.2f GiB", "%.2f GiB"}, + {"building", "construindo"}, + {"failed", "falhou"}, + {"expiring", "expirando"}, + {"established", "estabelecido"}, + {"unknown", "desconhecido"}, + {"exploratory", "exploratório"}, + {"Purple I2P Webconsole", "Webconsole Purple I2P"}, + {"i2pd webconsole", "webconsole i2pd"}, + {"Main page", "Página Principal"}, + {"Router commands", "Comandos do Roteador"}, + {"Local Destinations", "Destinos Locais"}, + {"LeaseSets", "LeaseSets"}, + {"Tunnels", "Túneis"}, + {"Transit Tunnels", "Túneis de Trânsito"}, + {"Transports", "Transportes"}, + {"I2P tunnels", "Túneis I2P"}, + {"SAM sessions", "Sessões do SAM"}, + {"ERROR", "ERRO"}, + {"OK", "OK"}, + {"Testing", "Testando"}, + {"Firewalled", "Sob Firewall"}, + {"Unknown", "Desconhecido"}, + {"Proxy", "Proxy"}, + {"Mesh", "Malha"}, + {"Clock skew", "Defasagem do Relógio"}, + {"Offline", "Desligado"}, + {"Symmetric NAT", "NAT Simétrico"}, + {"Full cone NAT", "Full cone NAT"}, + {"No Descriptors", "Sem Descritores"}, + {"Uptime", "Tempo Ativo"}, + {"Network status", "Estado da rede"}, + {"Network status v6", "Estado da rede v6"}, + {"Stopping in", "Parando em"}, + {"Family", "Família"}, + {"Tunnel creation success rate", "Taxa de sucesso na criação de túneis"}, + {"Received", "Recebido"}, + {"%.2f KiB/s", "%.2f KiB/s"}, + {"Sent", "Enviado"}, + {"Transit", "Trânsito"}, + {"Data path", "Diretório dos dados"}, + {"Hidden content. Press on text to see.", "Conteúdo oculto. Clique no texto para revelar."}, + {"Router Ident", "Identidade do Roteador"}, + {"Router Family", "Família do Roteador"}, + {"Router Caps", "Limites do Roteador"}, + {"Version", "Versão"}, + {"Our external address", "Nosso endereço externo"}, + {"supported", "suportado"}, + {"Routers", "Roteadores"}, + {"Floodfills", "Modo Inundação"}, + {"Client Tunnels", "Túneis de Clientes"}, + {"Services", "Serviços"}, + {"Enabled", "Ativado"}, + {"Disabled", "Desativado"}, + {"Encrypted B33 address", "Endereço B33 criptografado"}, + {"Address registration line", "Linha de cadastro de endereço"}, + {"Domain", "Domínio"}, + {"Generate", "Gerar"}, + {"Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", " Nota: A string resultante só pode ser usada para registrar domínios 2LD (exemplo.i2p). Para registrar subdomínios por favor utilize o i2pd-tools."}, + {"Address", "Endereço"}, + {"Type", "Tipo"}, + {"EncType", "Tipo de Criptografia"}, + {"Inbound tunnels", "Túneis de Entrada"}, + {"%dms", "%dms"}, + {"Outbound tunnels", "Túneis de Saída"}, + {"Tags", "Etiquetas"}, + {"Incoming", "Entradas"}, + {"Outgoing", "Saídas"}, + {"Destination", "Destinos"}, + {"Amount", "Quantidade"}, + {"Incoming Tags", "Etiquetas de Entrada"}, + {"Tags sessions", "Sessões de etiquetas"}, + {"Status", "Estado"}, + {"Local Destination", "Destinos Locais"}, + {"Streams", "Fluxos"}, + {"Close stream", "Fechar fluxo"}, + {"I2CP session not found", "Sessão do I2CP não encontrada"}, + {"I2CP is not enabled", "I2CP não está ativado"}, + {"Invalid", "Inválido"}, + {"Store type", "Tipo de armazenamento"}, + {"Expires", "Expira em"}, + {"Non Expired Leases", "Sessões não expiradas"}, + {"Gateway", "Gateway"}, + {"TunnelID", "TunnelID"}, + {"EndDate", "Data final"}, + {"floodfill mode is disabled", "Mode de inundação está desativado"}, + {"Queue size", "Tamanho da fila"}, + {"Run peer test", "Executar teste de peers"}, + {"Reload tunnels configuration", "Recarregar a configuração dos túneis"}, + {"Decline transit tunnels", "Negar túneis de trânsito"}, + {"Accept transit tunnels", "Aceitar túneis de trânsito"}, + {"Cancel graceful shutdown", "Cancelar desligamento gracioso"}, + {"Start graceful shutdown", "Iniciar desligamento gracioso"}, + {"Force shutdown", "Forçar desligamento"}, + {"Reload external CSS styles", "Recarregar estilos CSS externos"}, + {"Note: any action done here are not persistent and not changes your config files.", " Nota: Qualquer ação feita aqui não será permanente e não altera os seus arquivos de configuração."}, + {"Logging level", "Nível de registro"}, + {"Transit tunnels limit", "Limite de túneis de trânsito"}, + {"Change", "Mudar"}, + {"Change language", "Trocar idioma"}, + {"no transit tunnels currently built", "Nenhum túnel de trânsito construido no momento"}, + {"SAM disabled", "SAM desativado"}, + {"no sessions currently running", "Nenhuma sessão funcionando no momento"}, + {"SAM session not found", "Nenhuma sessão do SAM encontrada"}, + {"SAM Session", "Sessão do SAM"}, + {"Server Tunnels", "Túneis de Servidor"}, + {"Client Forwards", "Túneis de Cliente"}, + {"Server Forwards", "Encaminhamentos de Servidor"}, + {"Unknown page", "Página desconhecida"}, + {"Invalid token", "Token Inválido"}, + {"SUCCESS", "SUCESSO"}, + {"Stream closed", "Fluxo fechado"}, + {"Stream not found or already was closed", "Fluxo não encontrado ou já encerrado"}, + {"Destination not found", "Destino não encontrado"}, + {"StreamID can't be null", "StreamID não pode ser nulo"}, + {"Return to destination page", "Retornar para à página de destino"}, + {"You will be redirected in %d seconds", "Você será redirecionado em %d segundos"}, + {"Transit tunnels count must not exceed %d", "A contagem de túneis de trânsito não deve exceder %d"}, + {"Back to commands list", "Voltar para a lista de comandos"}, + {"Register at reg.i2p", "Registrar na reg.i2p"}, + {"Description", "Descrição"}, + {"A bit information about service on domain", "Algumas informações sobre o serviço no domínio"}, + {"Submit", "Enviar"}, + {"Domain can't end with .b32.i2p", "O domínio não pode terminar com .b32.i2p"}, + {"Domain must end with .i2p", "O domínio não pode terminar com .i2p"}, + {"Such destination is not found", "Tal destino não foi encontrado"}, + {"Unknown command", "Comando desconhecido"}, + {"Command accepted", "Comando aceito"}, + {"Proxy error", "Erro no proxy"}, + {"Proxy info", "Informações do proxy"}, + {"Proxy error: Host not found", "Erro no proxy: Host não encontrado"}, + {"Remote host not found in router's addressbook", "O host remoto não foi encontrado no livro de endereços do roteador"}, + {"You may try to find this host on jump services below", "Você pode tentar encontrar este host nos jump services abaixo"}, + {"Invalid request", "Requisição inválida"}, + {"Proxy unable to parse your request", "O proxy foi incapaz de processar a sua requisição"}, + {"Addresshelper is not supported", "O Auxiliar de Endereços não é suportado"}, + {"Host %s is already in router's addressbook. Be careful: source of this URL may be harmful! Click here to update record: Continue.", "O host %s já está no catálogo de endereços do roteador. Cuidado: a fonte desta URL pode ser perigosa! Clique aqui para atualizar o registro: Continuar."}, + {"Addresshelper forced update rejected", "A atualização forçada do Auxiliar de Endereços foi rejeitada"}, + {"To add host %s in router's addressbook, click here: Continue.", "Para adicionar o host %s ao catálogo de endereços do roteador, clique aqui: Continuar ."}, + {"Addresshelper request", "Requisição do Auxiliar de Endereços"}, + {"Host %s added to router's addressbook from helper. Click here to proceed: Continue.", "O host %s foi adicionado ao catálogo de endereços do roteador por um auxiliar. Clique aqui para proceder: Continuar ."}, + {"Addresshelper adding", "Auxiliar de Endereço adicionando"}, + {"Host %s is already in router's addressbook. Click here to update record: Continue.", "O host %s já está no catálogo de endereços do roteador . Clique aqui para atualizar o registro: Continuar."}, + {"Addresshelper update", "Atualização do Auxiliar de Endereços"}, + {"Invalid request URI", "A URI de requisição é inválida"}, + {"Can't detect destination host from request", "Incapaz de detectar o host de destino da requisição"}, + {"Outproxy failure", "Falha no outproxy"}, + {"Bad outproxy settings", "Configurações ruins de outproxy"}, + {"Host %s is not inside I2P network, but outproxy is not enabled", "O host %s não está dentro da rede I2P, mas o outproxy não está ativado"}, + {"Unknown outproxy URL", "URL de outproxy desconhecida"}, + {"Cannot resolve upstream proxy", "Não é possível resolver o proxy de entrada"}, + {"Hostname is too long", "O hostname é muito longo"}, + {"Cannot connect to upstream SOCKS proxy", "Não é possível se conectar ao proxy SOCKS de entrada"}, + {"Cannot negotiate with SOCKS proxy", "Não é possível negociar com o proxy SOCKS"}, + {"CONNECT error", "Erro de CONEXÃO"}, + {"Failed to connect", "Falha ao conectar"}, + {"SOCKS proxy error", "Erro no proxy SOCKS"}, + {"Failed to send request to upstream", "Falha ao enviar requisição para o fluxo de entrada"}, + {"No reply from SOCKS proxy", "Sem resposta do proxy SOCKS"}, + {"Cannot connect", "Impossível conectar"}, + {"HTTP out proxy not implemented", "proxy de saída HTTP não implementado"}, + {"Cannot connect to upstream HTTP proxy", "Não é possível conectar ao proxy HTTP de entrada"}, + {"Host is down", "Host está desligado"}, + {"Can't create connection to requested host, it may be down. Please try again later.", "Não é possível se conectar ao host requisitado, talvez ele esteja for do ar. Por favor, tente novamente mais tarde."}, + {"", ""}, + }; + + static std::map> plurals + { + {"%d days", {"%d Dia", "%d Dias"}}, + {"%d hours", {"%d hora", "%d horas"}}, + {"%d minutes", {"%d minuto", "%d minutos"}}, + {"%d seconds", {"%d Segundo", "%d segundos"}}, + {"", {"", ""}}, + }; + + std::shared_ptr GetLocale() + { + return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); + } + +} // language +} // i18n +} // i2p diff --git a/i18n/Russian.cpp b/i18n/Russian.cpp new file mode 100644 index 00000000..15952710 --- /dev/null +++ b/i18n/Russian.cpp @@ -0,0 +1,223 @@ +/* +* 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 +*/ + +#include +#include +#include +#include +#include "I18N.h" + +// Russian localization file + +namespace i2p +{ +namespace i18n +{ +namespace russian // language namespace +{ + // language name in lowercase + static std::string language = "russian"; + + // See for language plural forms here: + // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html + static int plural (int n) { + return n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2; + } + + static std::map strings + { + {"%.2f KiB", "%.2f КиБ"}, + {"%.2f MiB", "%.2f МиБ"}, + {"%.2f GiB", "%.2f ГиБ"}, + {"building", "строится"}, + {"failed", "неудачный"}, + {"expiring", "истекает"}, + {"established", "работает"}, + {"unknown", "неизвестно"}, + {"exploratory", "исследовательский"}, + {"Purple I2P Webconsole", "Веб-консоль Purple I2P"}, + {"i2pd webconsole", "Веб-консоль i2pd"}, + {"Main page", "Главная"}, + {"Router commands", "Команды роутера"}, + {"Local Destinations", "Локальные назначения"}, + {"LeaseSets", "Лизсеты"}, + {"Tunnels", "Туннели"}, + {"Transit Tunnels", "Транзитные туннели"}, + {"Transports", "Транспорты"}, + {"I2P tunnels", "I2P туннели"}, + {"SAM sessions", "SAM сессии"}, + {"ERROR", "ОШИБКА"}, + {"OK", "OK"}, + {"Testing", "Тестирование"}, + {"Firewalled", "Заблокировано извне"}, + {"Unknown", "Неизвестно"}, + {"Proxy", "Прокси"}, + {"Mesh", "MESH-сеть"}, + {"Clock skew", "Не точное время"}, + {"Offline", "Оффлайн"}, + {"Symmetric NAT", "Симметричный NAT"}, + {"Full cone NAT", "Full cone NAT"}, + {"No Descriptors", "Нет дескрипторов"}, + {"Uptime", "В сети"}, + {"Network status", "Сетевой статус"}, + {"Network status v6", "Сетевой статус v6"}, + {"Stopping in", "Остановка через"}, + {"Family", "Семейство"}, + {"Tunnel creation success rate", "Успешно построенных туннелей"}, + {"Total tunnel creation success rate", "Общий процент успешно построенных туннелей"}, + {"Received", "Получено"}, + {"%.2f KiB/s", "%.2f КиБ/с"}, + {"Sent", "Отправлено"}, + {"Transit", "Транзит"}, + {"Data path", "Путь к данным"}, + {"Hidden content. Press on text to see.", "Скрытый контент. Нажмите на текст чтобы отобразить."}, + {"Router Ident", "Идентификатор роутера"}, + {"Router Family", "Семейство роутера"}, + {"Router Caps", "Флаги роутера"}, + {"Version", "Версия"}, + {"Our external address", "Наш внешний адрес"}, + {"supported", "поддерживается"}, + {"Routers", "Роутеры"}, + {"Floodfills", "Флудфилы"}, + {"Client Tunnels", "Клиентские туннели"}, + {"Services", "Сервисы"}, + {"Enabled", "Включено"}, + {"Disabled", "Выключено"}, + {"Encrypted B33 address", "Шифрованные B33 адреса"}, + {"Address registration line", "Строка регистрации адреса"}, + {"Domain", "Домен"}, + {"Generate", "Сгенерировать"}, + {"Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", "Примечание: полученная строка может быть использована только для регистрации доменов второго уровня (example.i2p). Для регистрации поддоменов используйте i2pd-tools."}, + {"Address", "Адрес"}, + {"Type", "Тип"}, + {"EncType", "ТипШифр"}, + {"Expire LeaseSet", "Просрочить Лизсет"}, + {"Inbound tunnels", "Входящие туннели"}, + {"%dms", "%dмс"}, + {"Outbound tunnels", "Исходящие туннели"}, + {"Tags", "Теги"}, + {"Incoming", "Входящие"}, + {"Outgoing", "Исходящие"}, + {"Destination", "Назначение"}, + {"Amount", "Количество"}, + {"Incoming Tags", "Входящие теги"}, + {"Tags sessions", "Сессии тегов"}, + {"Status", "Статус"}, + {"Local Destination", "Локальное назначение"}, + {"Streams", "Стримы"}, + {"Close stream", "Закрыть стрим"}, + {"Such destination is not found", "Такая точка назначения не найдена"}, + {"I2CP session not found", "I2CP сессия не найдена"}, + {"I2CP is not enabled", "I2CP не включен"}, + {"Invalid", "Некорректный"}, + {"Store type", "Тип хранилища"}, + {"Expires", "Истекает"}, + {"Non Expired Leases", "Не истекшие Lease-ы"}, + {"Gateway", "Шлюз"}, + {"TunnelID", "ID туннеля"}, + {"EndDate", "Заканчивается"}, + {"floodfill mode is disabled", "режим флудфила отключен"}, + {"Queue size", "Размер очереди"}, + {"Run peer test", "Запустить тестирование"}, + {"Reload tunnels configuration", "Перезагрузить конфигурацию туннелей"}, + {"Decline transit tunnels", "Отклонять транзитные туннели"}, + {"Accept transit tunnels", "Принимать транзитные туннели"}, + {"Cancel graceful shutdown", "Отменить плавную остановку"}, + {"Start graceful shutdown", "Запустить плавную остановку"}, + {"Force shutdown", "Принудительная остановка"}, + {"Reload external CSS styles", "Перезагрузить внешние CSS стили"}, + {"Note: any action done here are not persistent and not changes your config files.", "Примечание: любое действие произведенное здесь не является постоянным и не изменяет ваши конфигурационные файлы."}, + {"Logging level", "Уровень логирования"}, + {"Transit tunnels limit", "Лимит транзитных туннелей"}, + {"Change", "Изменить"}, + {"Change language", "Изменение языка"}, + {"no transit tunnels currently built", "нет построенных транзитных туннелей"}, + {"SAM disabled", "SAM выключен"}, + {"no sessions currently running", "нет запущенных сессий"}, + {"SAM session not found", "SAM сессия не найдена"}, + {"SAM Session", "SAM сессия"}, + {"Server Tunnels", "Серверные туннели"}, + {"Client Forwards", "Клиентские перенаправления"}, + {"Server Forwards", "Серверные перенаправления"}, + {"Unknown page", "Неизвестная страница"}, + {"Invalid token", "Неверный токен"}, + {"SUCCESS", "УСПЕШНО"}, + {"Stream closed", "Стрим закрыт"}, + {"Stream not found or already was closed", "Стрим не найден или уже закрыт"}, + {"Destination not found", "Точка назначения не найдена"}, + {"StreamID can't be null", "StreamID не может быть пустым"}, + {"Return to destination page", "Вернуться на страницу точки назначения"}, + {"You will be redirected in %d seconds", "Вы будете переадресованы через %d секунд"}, + {"LeaseSet expiration time updated", "Время действия LeaseSet обновлено"}, + {"LeaseSet is not found or already expired", "Лизсет не найден или время действия уже истекло"}, + {"Transit tunnels count must not exceed %d", "Число транзитных туннелей не должно превышать %d"}, + {"Back to commands list", "Вернуться к списку команд"}, + {"Register at reg.i2p", "Зарегистрировать на reg.i2p"}, + {"Description", "Описание"}, + {"A bit information about service on domain", "Немного информации о сервисе на домене"}, + {"Submit", "Отправить"}, + {"Domain can't end with .b32.i2p", "Домен не может заканчиваться на .b32.i2p"}, + {"Domain must end with .i2p", "Домен должен заканчиваться на .i2p"}, + {"Unknown command", "Неизвестная команда"}, + {"Command accepted", "Команда принята"}, + {"Proxy error", "Ошибка прокси"}, + {"Proxy info", "Информация прокси"}, + {"Proxy error: Host not found", "Ошибка прокси: Узел не найден"}, + {"Remote host not found in router's addressbook", "Запрошенный узел не найден в адресной книге роутера"}, + {"You may try to find this host on jump services below", "Вы можете попробовать найти узел через джамп сервисы ниже"}, + {"Invalid request", "Некорректный запрос"}, + {"Proxy unable to parse your request", "Прокси не может разобрать ваш запрос"}, + {"Addresshelper is not supported", "Addresshelper не поддерживается"}, + {"Host %s is already in router's addressbook. Be careful: source of this URL may be harmful! Click here to update record: Continue.", "Узел %s уже в адресной книге роутера. Будьте осторожны: источник данной ссылки может быть вредоносным! Нажмите здесь, чтобы обновить запись: Продолжить."}, + {"Addresshelper forced update rejected", "Принудительное обновление через Addresshelper отклонено"}, + {"To add host %s in router's addressbook, click here: Continue.", "Чтобы добавить узел %s в адресную книгу роутера, нажмите здесь: Продолжить."}, + {"Addresshelper request", "Запрос добавления Addresshelper"}, + {"Host %s added to router's addressbook from helper. Click here to proceed: Continue.", "Узел %s добавлен в адресную книгу роутера через хелпер. Нажмите здесь, чтобы продолжить: Продолжить."}, + {"Addresshelper adding", "Добавление Addresshelper"}, + {"Host %s is already in router's addressbook. Click here to update record: Continue.", "Узел %s уже в адресной книге роутера. Нажмите здесь, чтобы обновить запись: Продолжить."}, + {"Addresshelper update", "Обновление записи через Addresshelper"}, + {"Invalid request URI", "Некорректный URI запроса"}, + {"Can't detect destination host from request", "Не удалось определить адрес назначения из запроса"}, + {"Outproxy failure", "Ошибка внешнего прокси"}, + {"Bad outproxy settings", "Некорректные настройки внешнего прокси"}, + {"Host %s is not inside I2P network, but outproxy is not enabled", "Узел %s не в I2P сети, но внешний прокси не включен"}, + {"Unknown outproxy URL", "Неизвестный URL внешнего прокси"}, + {"Cannot resolve upstream proxy", "Не удается определить вышестоящий прокси"}, + {"Hostname is too long", "Имя хоста слишком длинное"}, + {"Cannot connect to upstream SOCKS proxy", "Не удалось подключиться к вышестоящему SOCKS прокси серверу"}, + {"Cannot negotiate with SOCKS proxy", "Не удается договориться с вышестоящим SOCKS прокси"}, + {"CONNECT error", "Ошибка CONNECT запроса"}, + {"Failed to connect", "Не удалось соединиться"}, + {"SOCKS proxy error", "Ошибка SOCKS прокси"}, + {"Failed to send request to upstream", "Не удалось отправить запрос вышестоящему прокси серверу"}, + {"No reply from SOCKS proxy", "Нет ответа от SOCKS прокси сервера"}, + {"Cannot connect", "Не удалось подключиться"}, + {"HTTP out proxy not implemented", "Поддержка внешнего HTTP прокси сервера не реализована"}, + {"Cannot connect to upstream HTTP proxy", "Не удалось подключиться к вышестоящему HTTP прокси серверу"}, + {"Host is down", "Узел недоступен"}, + {"Can't create connection to requested host, it may be down. Please try again later.", "Не удалось установить соединение к запрошенному узлу, возможно он не в сети. Попробуйте повторить запрос позже."}, + {"", ""}, + }; + + static std::map> plurals + { + {"%d days", {"%d день", "%d дня", "%d дней"}}, + {"%d hours", {"%d час", "%d часа", "%d часов"}}, + {"%d minutes", {"%d минуту", "%d минуты", "%d минут"}}, + {"%d seconds", {"%d секунду", "%d секунды", "%d секунд"}}, + {"", {"", "", ""}}, + }; + + std::shared_ptr GetLocale() + { + return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); + } + +} // language +} // i18n +} // i2p diff --git a/i18n/Spanish.cpp b/i18n/Spanish.cpp new file mode 100644 index 00000000..a5ecc30a --- /dev/null +++ b/i18n/Spanish.cpp @@ -0,0 +1,204 @@ +/* +* Copyright (c) 2022-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 +#include +#include "I18N.h" + +// Spanish localization file + +namespace i2p +{ +namespace i18n +{ +namespace spanish // language namespace +{ + // language name in lowercase + static std::string language = "spanish"; + + // See for language plural forms here: + // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html + static int plural (int n) { + return n != 1 ? 1 : 0; + } + + static std::map strings + { + {"%.2f KiB", "%.2f KiB"}, + {"%.2f MiB", "%.2f MiB"}, + {"%.2f GiB", "%.2f GiB"}, + {"building", "pendiente"}, + {"failed", "fallido"}, + {"expiring", "expiró"}, + {"established", "establecido"}, + {"unknown", "desconocido"}, + {"exploratory", "exploratorio"}, + {"Purple I2P Webconsole", "Consola web de Purple I2P"}, + {"i2pd webconsole", "Consola web de i2pd"}, + {"Main page", "Inicio"}, + {"Router commands", "Comandos de enrutador"}, + {"Local Destinations", "Destinos locales"}, + {"LeaseSets", "LeaseSets"}, + {"Tunnels", "Túneles"}, + {"Transit Tunnels", "Túneles de Tránsito"}, + {"Transports", "Transportes"}, + {"I2P tunnels", "Túneles I2P"}, + {"SAM sessions", "Sesiones SAM"}, + {"ERROR", "ERROR"}, + {"OK", "VALE"}, + {"Testing", "Probando"}, + {"Firewalled", "Con cortafuegos"}, + {"Unknown", "Desconocido"}, + {"Proxy", "Proxy"}, + {"Mesh", "Malla"}, + {"Clock skew", "Reloj desfasado"}, + {"Offline", "Desconectado"}, + {"Symmetric NAT", "NAT simétrico"}, + {"Uptime", "Tiempo en línea"}, + {"Network status", "Estado de red"}, + {"Network status v6", "Estado de red v6"}, + {"Stopping in", "Parando en"}, + {"Family", "Familia"}, + {"Tunnel creation success rate", "Tasa de éxito de creación de túneles"}, + {"Received", "Recibido"}, + {"%.2f KiB/s", "%.2f KiB/s"}, + {"Sent", "Enviado"}, + {"Transit", "Tránsito"}, + {"Data path", "Ruta de datos"}, + {"Hidden content. Press on text to see.", "Contenido oculto. Presione para ver."}, + {"Router Ident", "Ident del Enrutador"}, + {"Router Family", "Familia de enrutador"}, + {"Router Caps", "Atributos del Enrutador"}, + {"Version", "Versión"}, + {"Our external address", "Nuestra dirección externa"}, + {"supported", "soportado"}, + {"Routers", "Enrutadores"}, + {"Floodfills", "Inundaciones"}, + {"Client Tunnels", "Túneles de cliente"}, + {"Services", "Servicios"}, + {"Enabled", "Activado"}, + {"Disabled", "Desactivado"}, + {"Encrypted B33 address", "Dirección encriptada B33"}, + {"Address registration line", "Línea para registrar direcciones"}, + {"Domain", "Dominio"}, + {"Generate", "Generar"}, + {"Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", "Nota: la cadena resultante solo se puede usar para registrar dominios 2LD (ejemplo.i2p). Para registrar subdominios, por favor utilice i2pd-tools."}, + {"Address", "Dirección"}, + {"Type", "Tipo"}, + {"EncType", "TipoEncrip"}, + {"Inbound tunnels", "Túneles entrantes"}, + {"%dms", "%dms"}, + {"Outbound tunnels", "Túneles salientes"}, + {"Tags", "Etiquetas"}, + {"Incoming", "Entrante"}, + {"Outgoing", "Saliente"}, + {"Destination", "Destino"}, + {"Amount", "Cantidad"}, + {"Incoming Tags", "Etiquetas entrantes"}, + {"Tags sessions", "Sesiones de etiquetas"}, + {"Status", "Estado"}, + {"Local Destination", "Destino Local"}, + {"Streams", "Flujos"}, + {"Close stream", "Cerrar flujo"}, + {"I2CP session not found", "Sesión I2CP no encontrada"}, + {"I2CP is not enabled", "I2CP no está activado"}, + {"Invalid", "Inválido"}, + {"Store type", "Tipo de almacenamiento"}, + {"Expires", "Caduca"}, + {"Non Expired Leases", "Sesiones No Expiradas"}, + {"Gateway", "Puerta de enlace"}, + {"TunnelID", "TunnelID"}, + {"EndDate", "FechaVenc"}, + {"Queue size", "Tamaño de cola"}, + {"Run peer test", "Ejecutar prueba de par"}, + {"Decline transit tunnels", "Rechazar túneles de tránsito"}, + {"Accept transit tunnels", "Aceptar túneles de tránsito"}, + {"Cancel graceful shutdown", "Cancelar apagado con gracia"}, + {"Start graceful shutdown", "Iniciar apagado con gracia"}, + {"Force shutdown", "Forzar apagado"}, + {"Reload external CSS styles", "Recargar estilos CSS externos"}, + {"Note: any action done here are not persistent and not changes your config files.", "Nota: cualquier acción hecha aquí no es persistente y no cambia tus archivos de configuración."}, + {"Logging level", "Nivel de registro de errores"}, + {"Transit tunnels limit", "Límite de túneles de tránsito"}, + {"Change", "Cambiar"}, + {"Change language", "Cambiar idioma"}, + {"no transit tunnels currently built", "no hay túneles de tránsito actualmente construidos"}, + {"SAM disabled", "SAM desactivado"}, + {"no sessions currently running", "no hay sesiones ejecutándose ahora"}, + {"SAM session not found", "Sesión SAM no encontrada"}, + {"SAM Session", "Sesión SAM"}, + {"Server Tunnels", "Túneles de Servidor"}, + {"Client Forwards", "Redirecciones de Cliente"}, + {"Server Forwards", "Redirecciones de Servidor"}, + {"Unknown page", "Página desconocida"}, + {"Invalid token", "Token inválido"}, + {"SUCCESS", "ÉXITO"}, + {"Stream closed", "Transmisión cerrada"}, + {"Stream not found or already was closed", "No se encontró la transmisión o ya se cerró"}, + {"Destination not found", "Destino no encontrado"}, + {"StreamID can't be null", "StreamID no puede ser nulo"}, + {"Return to destination page", "Volver a la página de destino"}, + {"Back to commands list", "Volver a lista de comandos"}, + {"Register at reg.i2p", "Registrar en reg.i2p"}, + {"Description", "Descripción"}, + {"A bit information about service on domain", "Un poco de información sobre el servicio en el dominio"}, + {"Submit", "Enviar"}, + {"Domain can't end with .b32.i2p", "El dominio no puede terminar con .b32.i2p"}, + {"Domain must end with .i2p", "El dominio debe terminar con .i2p"}, + {"Such destination is not found", "No se encontró el destino"}, + {"Unknown command", "Comando desconocido"}, + {"Command accepted", "Comando aceptado"}, + {"Proxy error", "Error de proxy"}, + {"Proxy info", "Información del proxy"}, + {"Proxy error: Host not found", "Error de proxy: Host no encontrado"}, + {"Remote host not found in router's addressbook", "Servidor remoto no encontrado en la libreta de direcciones del enrutador"}, + {"You may try to find this host on jump services below", "Puede intentar encontrar este dominio en los siguientes servicios de salto"}, + {"Invalid request", "Solicitud inválida"}, + {"Proxy unable to parse your request", "Proxy no puede procesar su solicitud"}, + {"Invalid request URI", "URI de solicitud inválida"}, + {"Can't detect destination host from request", "No se puede detectar el host de destino de la solicitud"}, + {"Outproxy failure", "Fallo en el proxy saliente"}, + {"Bad outproxy settings", "Configuración de outproxy incorrecta"}, + {"Host %s is not inside I2P network, but outproxy is not enabled", "Dominio %s no está dentro de la red I2P, pero el proxy de salida no está activado"}, + {"Unknown outproxy URL", "URL de proxy outproxy desconocido"}, + {"Cannot resolve upstream proxy", "No se puede resolver el proxy de upstream"}, + {"Hostname is too long", "Nombre de dominio muy largo"}, + {"Cannot connect to upstream SOCKS proxy", "No se puede conectar al proxy SOCKS principal"}, + {"Cannot negotiate with SOCKS proxy", "No se puede negociar con el proxy SOCKS"}, + {"CONNECT error", "Error de CONNECT"}, + {"Failed to connect", "Error al conectar"}, + {"SOCKS proxy error", "Error de proxy SOCKS"}, + {"Failed to send request to upstream", "No se pudo enviar petición al principal"}, + {"No reply from SOCKS proxy", "Sin respuesta del proxy SOCKS"}, + {"Cannot connect", "No se puede conectar"}, + {"HTTP out proxy not implemented", "Proxy externo HTTP no implementado"}, + {"Cannot connect to upstream HTTP proxy", "No se puede conectar al proxy HTTP principal"}, + {"Host is down", "Servidor caído"}, + {"Can't create connection to requested host, it may be down. Please try again later.", "No se puede crear la conexión al servidor solicitado, puede estar caído. Intente de nuevo más tarde."}, + {"", ""}, + }; + + static std::map> plurals + { + {"%d days", {"%d día", "%d días"}}, + {"%d hours", {"%d hora", "%d horas"}}, + {"%d minutes", {"%d minuto", "%d minutos"}}, + {"%d seconds", {"%d segundo", "%d segundos"}}, + {"", {"", ""}}, + }; + + std::shared_ptr GetLocale() + { + return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); + } + +} // language +} // i18n +} // i2p diff --git a/i18n/Swedish.cpp b/i18n/Swedish.cpp new file mode 100644 index 00000000..05ed1e18 --- /dev/null +++ b/i18n/Swedish.cpp @@ -0,0 +1,220 @@ +/* +* Copyright (c) 2023, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#include +#include +#include +#include +#include "I18N.h" + +// Swedish localization file + +namespace i2p +{ +namespace i18n +{ +namespace swedish // language namespace +{ + // language name in lowercase + static std::string language = "swedish"; + + // See for language plural forms here: + // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html + static int plural (int n) { + return n != 1 ? 1 : 0; + } + + static std::map strings + { + {"%.2f KiB", "%.2f KiB"}, + {"%.2f MiB", "%.2f MiB"}, + {"%.2f GiB", "%.2f GiB"}, + {"building", "bygger"}, + {"failed", "misslyckad"}, + {"expiring", "utgår"}, + {"established", "upprättad"}, + {"unknown", "okänt"}, + {"exploratory", "utforskande"}, + {"Purple I2P Webconsole", "Purple I2P Webbkonsoll"}, + {"i2pd webconsole", "i2pd-Webbkonsoll"}, + {"Main page", "Huvudsida"}, + {"Router commands", "Routerkommandon"}, + {"Local Destinations", "Lokala Platser"}, + {"LeaseSets", "Hyresuppsättningar"}, + {"Tunnels", "Tunnlar"}, + {"Transit Tunnels", "Förmedlande Tunnlar"}, + {"Transports", "Transporter"}, + {"I2P tunnels", "I2P-tunnlar"}, + {"SAM sessions", "SAM-perioder"}, + {"ERROR", "FEL"}, + {"OK", "OK"}, + {"Testing", "Prövar"}, + {"Firewalled", "Bakom Brandvägg"}, + {"Unknown", "Okänt"}, + {"Proxy", "Proxy"}, + {"Mesh", "Mesh"}, + {"Clock skew", "Tidsförskjutning"}, + {"Offline", "Nedkopplad"}, + {"Symmetric NAT", "Symmetrisk NAT"}, + {"Full cone NAT", "Full kon NAT"}, + {"No Descriptors", "Inga Beskrivningar"}, + {"Uptime", "Upptid"}, + {"Network status", "Nätverkstillstånd"}, + {"Network status v6", "Nätverkstillstånd v6"}, + {"Stopping in", "Avstängd om"}, + {"Family", "Familj"}, + {"Tunnel creation success rate", "Andel framgångsrika tunnlar"}, + {"Received", "Mottaget"}, + {"%.2f KiB/s", "%.2f KiB/s"}, + {"Sent", "Skickat"}, + {"Transit", "Förmedlat"}, + {"Data path", "Sökväg"}, + {"Hidden content. Press on text to see.", "Dolt innehåll. Tryck för att visa."}, + {"Router Ident", "Routeridentitet"}, + {"Router Family", "Routerfamilj"}, + {"Router Caps", "Routerbegränsningar"}, + {"Version", "Version"}, + {"Our external address", "Vår externa adress"}, + {"supported", "stöds"}, + {"Routers", "Routrar"}, + {"Floodfills", "Översvämningsfyllare"}, + {"Client Tunnels", "Klienttunnlar"}, + {"Services", "Tjänster"}, + {"Enabled", "Påslaget"}, + {"Disabled", "Avslaget"}, + {"Encrypted B33 address", "Krypterad B33-Adress"}, + {"Address registration line", "Adressregistreringsrad"}, + {"Domain", "Domän"}, + {"Generate", "Skapa"}, + {"Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", "Uppmärksamma: den resulterande strängen kan enbart användas för att registrera 2LD-domäner (exempel.i2p). För att registrera underdomäner, vänligen använd i2pd-tools."}, + {"Address", "Adress"}, + {"Type", "Typ"}, + {"EncType", "EncTyp"}, + {"Inbound tunnels", "Ingående Tunnlar"}, + {"%dms", "%dms"}, + {"Outbound tunnels", "Utgående Tunnlar"}, + {"Tags", "Taggar"}, + {"Incoming", "Ingående"}, + {"Outgoing", "Utgående"}, + {"Destination", "Plats"}, + {"Amount", "Mängd"}, + {"Incoming Tags", "Ingående Taggar"}, + {"Tags sessions", "Tagg-perioder"}, + {"Status", "Tillstånd"}, + {"Local Destination", "Lokal Plats"}, + {"Streams", "Strömmar"}, + {"Close stream", "Stäng strömmen"}, + {"Such destination is not found", "En sådan plats hittas ej"}, + {"I2CP session not found", "I2CP-period hittades inte"}, + {"I2CP is not enabled", "I2CP är inte påslaget"}, + {"Invalid", "Ogiltig"}, + {"Store type", "Lagringstyp"}, + {"Expires", "Utgångsdatum"}, + {"Non Expired Leases", "Ickeutgångna Hyresuppsättningar"}, + {"Gateway", "Gateway"}, + {"TunnelID", "TunnelID"}, + {"EndDate", "EndDate"}, + {"floodfill mode is disabled", "Floodfill läget är inaktiverat"}, + {"Queue size", "Köstorlek"}, + {"Run peer test", "Utför utsiktstest"}, + {"Reload tunnels configuration", "Ladda om tunnelkonfiguration"}, + {"Decline transit tunnels", "Avvisa förmedlande tunnlar"}, + {"Accept transit tunnels", "Tillåt förmedlande tunnlar"}, + {"Cancel graceful shutdown", "Avbryt välvillig avstängning"}, + {"Start graceful shutdown", "Påbörja välvillig avstängning"}, + {"Force shutdown", "Tvingad avstängning"}, + {"Reload external CSS styles", "Ladda om externa CSS-stilar"}, + {"Note: any action done here are not persistent and not changes your config files.", "Uppmärksamma: inga ändringar här är beständiga eller påverkar dina inställningsfiler."}, + {"Logging level", "Protokollförningsnivå"}, + {"Transit tunnels limit", "Begränsa förmedlande tunnlar"}, + {"Change", "Ändra"}, + {"Change language", "Ändra språk"}, + {"no transit tunnels currently built", "inga förmedlande tunnlar har byggts"}, + {"SAM disabled", "SAM avslaget"}, + {"no sessions currently running", "inga perioder igång"}, + {"SAM session not found", "SAM-perioder hittades ej"}, + {"SAM Session", "SAM-period"}, + {"Server Tunnels", "Värdtunnlar"}, + {"Client Forwards", "Klientförpassningar"}, + {"Server Forwards", "Värdförpassningar"}, + {"Unknown page", "Okänd sida"}, + {"Invalid token", "Ogiltig polett"}, + {"SUCCESS", "FRAMGÅNG"}, + {"Stream closed", "Ström stängd"}, + {"Stream not found or already was closed", "Strömmen hittades inte eller var redan avslutad"}, + {"Destination not found", "Plats hittades ej"}, + {"StreamID can't be null", "Ström-ID kan inte vara null"}, + {"Return to destination page", "Återvänd till platssidan"}, + {"You will be redirected in %d seconds", "Du omdirigeras inom %d sekunder"}, + {"Transit tunnels count must not exceed %d", "Förmedlande tunnlar får inte överstiga %d"}, + {"Back to commands list", "Tillbaka till kommandolistan"}, + {"Register at reg.i2p", "Registrera vid reg.i2p"}, + {"Description", "Beskrivning"}, + {"A bit information about service on domain", "Ett stycke information om domänens tjänst"}, + {"Submit", "Skicka"}, + {"Domain can't end with .b32.i2p", "Domänen får inte sluta med .b32.i2p"}, + {"Domain must end with .i2p", "Domänen måste sluta med .i2p"}, + {"Unknown command", "Okänt kommando"}, + {"Command accepted", "Kommando accepterades"}, + {"Proxy error", "Proxyfel"}, + {"Proxy info", "Proxyinfo"}, + {"Proxy error: Host not found", "Proxyfel: Värden hittades ej"}, + {"Remote host not found in router's addressbook", "Främmande värd hittades inte i routerns adressbok"}, + {"You may try to find this host on jump services below", "Du kan försöka att hitta värden genom hopptjänsterna nedan"}, + {"Invalid request", "Ogiltig förfrågan"}, + {"Proxy unable to parse your request", "Proxyt kan inte behandla din förfrågan"}, + {"Addresshelper is not supported", "Adresshjälparen stöds ej"}, + {"Host %s is already in router's addressbook. Be careful: source of this URL may be harmful! Click here to update record: Continue.", "Värd %s är redan i routerns adressbok. Var försiktig: källan till denna URL kan vara skadlig! Klicka här för att uppdatera registreringen: Fortsätt."}, + {"Addresshelper forced update rejected", "Tvingad uppdatering av adresshjälparen nekad"}, + {"To add host %s in router's addressbook, click here: Continue.", "För att lägga till värd %s i routerns adressbok, klicka här: Fortsätt."}, + {"Addresshelper request", "Adresshjälpare förfrågan"}, + {"Host %s added to router's addressbook from helper. Click here to proceed: Continue.", "Värd %s tillagd i routerns adressbok från hjälparen. Klicka här för att fortsätta: Fortsätt."}, + {"Addresshelper adding", "Adresshjälpare tilläggning"}, + {"Host %s is already in router's addressbook. Click here to update record: Continue.", "Värd %s är redan i routerns adressbok. Klicka här för att uppdatera registreringen: Fortsätt."}, + {"Addresshelper update", "Adresshjälpare uppdatering"}, + {"Invalid request URI", "Ogiltig förfrågnings-URI"}, + {"Can't detect destination host from request", "Kan inte upptäcka platsvärden från förfrågan"}, + {"Outproxy failure", "Utproxyfel"}, + {"Bad outproxy settings", "Ogiltig utproxyinställning"}, + {"Host %s is not inside I2P network, but outproxy is not enabled", "Värd %s är inte inom I2P-näverket, men utproxy är inte påslaget"}, + {"Unknown outproxy URL", "okänt Utproxy-URL"}, + {"Cannot resolve upstream proxy", "Hittar inte uppströmsproxyt"}, + {"Hostname is too long", "Värdnamnet är för långt"}, + {"Cannot connect to upstream SOCKS proxy", "Kan inte ansluta till uppström SOCKS-proxy"}, + {"Cannot negotiate with SOCKS proxy", "Kan inte förhandla med SOCKSproxyt"}, + {"CONNECT error", "CONNECT-fel"}, + {"Failed to connect", "Anslutningen misslyckades"}, + {"SOCKS proxy error", "SOCKSproxyfel"}, + {"Failed to send request to upstream", "Förfrågan uppströms kunde ej skickas"}, + {"No reply from SOCKS proxy", "Fick inget svar från SOCKSproxyt"}, + {"Cannot connect", "Kan inte ansluta"}, + {"HTTP out proxy not implemented", "HTTP-Utproxy ej implementerat"}, + {"Cannot connect to upstream HTTP proxy", "Kan inte ansluta till uppströms HTTP-proxy"}, + {"Host is down", "Värden är nere"}, + {"Can't create connection to requested host, it may be down. Please try again later.", "Kan inte ansluta till värden, den kan vara nere. Vänligen försök senare."}, + {"", ""}, + }; + + static std::map> plurals + { + {"%d days", {"%d dag", "%d dagar"}}, + {"%d hours", {"%d timme", "%d timmar"}}, + {"%d minutes", {"%d minut", "%d minuter"}}, + {"%d seconds", {"%d sekund", "%d sekunder"}}, + {"", {"", ""}}, + }; + + std::shared_ptr GetLocale() + { + return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); + } + +} // language +} // i18n +} // i2p + diff --git a/i18n/Turkish.cpp b/i18n/Turkish.cpp new file mode 100644 index 00000000..d4398ebe --- /dev/null +++ b/i18n/Turkish.cpp @@ -0,0 +1,114 @@ +/* +* Copyright (c) 2023, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#include +#include +#include +#include +#include "I18N.h" + +// Turkish localization file + +namespace i2p +{ +namespace i18n +{ +namespace turkish // language namespace +{ + // language name in lowercase + static std::string language = "turkish"; + + // See for language plural forms here: + // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html + static int plural (int n) { + return n != 1 ? 1 : 0; + } + + static std::map strings + { + {"%.2f KiB", "%.2f KiB"}, + {"%.2f MiB", "%.2f MiB"}, + {"%.2f GiB", "%.2f GiB"}, + {"building", "kuruluyor"}, + {"failed", "başarısız"}, + {"expiring", "süresi geçiyor"}, + {"established", "kurulmuş"}, + {"unknown", "bilinmeyen"}, + {"Purple I2P Webconsole", "Mor I2P Webkonsolu"}, + {"i2pd webconsole", "i2pd webkonsolu"}, + {"Main page", "Ana sayfa"}, + {"Router commands", "Router komutları"}, + {"Local Destinations", "Yerel Hedefler"}, + {"Tunnels", "Tüneller"}, + {"Transit Tunnels", "Transit Tünelleri"}, + {"Transports", "Taşıma"}, + {"I2P tunnels", "I2P tünelleri"}, + {"SAM sessions", "SAM oturumları"}, + {"ERROR", "HATA"}, + {"OK", "TAMAM"}, + {"Testing", "Test ediliyor"}, + {"Firewalled", "Güvenlik Duvarı Kısıtlaması"}, + {"Unknown", "Bilinmeyen"}, + {"Proxy", "Proxy"}, + {"Clock skew", "Saat sorunu"}, + {"Offline", "Çevrimdışı"}, + {"Symmetric NAT", "Simetrik NAT"}, + {"Full cone NAT", "Full cone NAT"}, + {"No Descriptors", "Tanımlayıcı Yok"}, + {"Uptime", "Bağlantı süresi"}, + {"Network status", "Ağ durumu"}, + {"Network status v6", "Ağ durumu v6"}, + {"Family", "Aile"}, + {"Tunnel creation success rate", "Tünel oluşturma başarı oranı"}, + {"Received", "Alındı"}, + {"%.2f KiB/s", "%.2f KiB/s"}, + {"Sent", "Gönderildi"}, + {"Transit", "Transit"}, + {"Data path", "Veri yolu"}, + {"Hidden content. Press on text to see.", "Gizlenmiş içerik. Görmek için yazıya tıklayınız."}, + {"Router Family", "Router Familyası"}, + {"Decline transit tunnels", "Transit tünellerini reddet"}, + {"Accept transit tunnels", "Transit tünellerini kabul et"}, + {"Cancel graceful shutdown", "Düzgün durdurmayı iptal Et"}, + {"Start graceful shutdown", "Düzgün durdurmayı başlat"}, + {"Force shutdown", "Durdurmaya zorla"}, + {"Reload external CSS styles", "Harici CSS stilini yeniden yükle"}, + {"Note: any action done here are not persistent and not changes your config files.", "Not: burada yapılan ayarların hiçbiri kalıcı değildir ve ayar dosyalarınızı değiştirmez."}, + {"Logging level", "Kayıt tutma seviyesi"}, + {"Transit tunnels limit", "Transit tünel limiti"}, + {"Change", "Değiştir"}, + {"Change language", "Dil değiştir"}, + {"no transit tunnels currently built", "kurulmuş bir transit tüneli bulunmamakta"}, + {"SAM disabled", "SAM devre dışı"}, + {"no sessions currently running", "hiçbir oturum şu anda çalışmıyor"}, + {"SAM session not found", "SAM oturumu bulunamadı"}, + {"SAM Session", "SAM oturumu"}, + {"Server Tunnels", "Sunucu Tünelleri"}, + {"Unknown page", "Bilinmeyen sayfa"}, + {"Invalid token", "Geçersiz token"}, + {"SUCCESS", "BAŞARILI"}, + {"", ""}, + }; + + static std::map> plurals + { + {"%d days", {"%d gün", "%d gün"}}, + {"%d hours", {"%d saat", "%d saat"}}, + {"%d minutes", {"%d dakika", "%d dakika"}}, + {"%d seconds", {"%d saniye", "%d saniye"}}, + {"", {"", ""}}, + }; + + std::shared_ptr GetLocale() + { + return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); + } + +} // language +} // i18n +} // i2p diff --git a/i18n/Turkmen.cpp b/i18n/Turkmen.cpp new file mode 100644 index 00000000..35ee0f89 --- /dev/null +++ b/i18n/Turkmen.cpp @@ -0,0 +1,204 @@ +/* +* 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 +*/ + +#include +#include +#include +#include +#include "I18N.h" + +// Turkmen localization file + +namespace i2p +{ +namespace i18n +{ +namespace turkmen // language namespace +{ + // language name in lowercase + static std::string language = "turkmen"; + + // See for language plural forms here: + // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html + static int plural (int n) { + return n != 1 ? 1 : 0; + } + + static std::map strings + { + {"%.2f KiB", "%.2f KiB"}, + {"%.2f MiB", "%.2f MiB"}, + {"%.2f GiB", "%.2f GiB"}, + {"building", "bina"}, + {"failed", "şowsuz"}, + {"expiring", "möhleti gutarýar"}, + {"established", "işleýär"}, + {"unknown", "näbelli"}, + {"exploratory", "gözleg"}, + {"Purple I2P Webconsole", "Web konsoly Purple I2P"}, + {"i2pd webconsole", "Web konsoly i2pd"}, + {"Main page", "Esasy sahypa"}, + {"Router commands", "Marşrutizator buýruklary"}, + {"Local Destinations", "Ýerli ýerler"}, + {"LeaseSets", "Lizset"}, + {"Tunnels", "Tuneller"}, + {"Transit Tunnels", "Tranzit Tunelleri"}, + {"Transports", "Daşamak"}, + {"I2P tunnels", "I2P tuneller"}, + {"SAM sessions", "SAM Sessiýasy"}, + {"ERROR", "Ýalňyşlyk"}, + {"OK", "OK"}, + {"Testing", "Synag etmek"}, + {"Firewalled", "Daşynda petiklendi"}, + {"Unknown", "Näbelli"}, + {"Proxy", "Proksi"}, + {"Mesh", "MESH-tor"}, + {"Clock skew", "Takyk wagt däl"}, + {"Offline", "Awtonom"}, + {"Symmetric NAT", "Simmetriklik NAT"}, + {"Uptime", "Onlaýn onlaýn sözlügi"}, + {"Network status", "Tor ýagdaýy"}, + {"Network status v6", "Tor ýagdaýy v6"}, + {"Stopping in", "Soň duruň"}, + {"Family", "Maşgala"}, + {"Tunnel creation success rate", "Gurlan teneller üstünlikli gurlan teneller"}, + {"Received", "Alnan"}, + {"%.2f KiB/s", "%.2f KiB/s"}, + {"Sent", "Ýerleşdirildi"}, + {"Transit", "Tranzit"}, + {"Data path", "Maglumat ýoly"}, + {"Hidden content. Press on text to see.", "Gizlin mazmun. Görkezmek üçin tekste basyň."}, + {"Router Ident", "Marşrutly kesgitleýji"}, + {"Router Family", "Marşrutler maşgalasy"}, + {"Router Caps", "Baýdaklar marşruteri"}, + {"Version", "Wersiýasy"}, + {"Our external address", "Daşarky salgymyz"}, + {"supported", "goldanýar"}, + {"Routers", "Marşrutizatorlar"}, + {"Floodfills", "Fludfillar"}, + {"Client Tunnels", "Müşderi tunelleri"}, + {"Services", "Hyzmatlar"}, + {"Enabled", "Goşuldy"}, + {"Disabled", "Öçürildi"}, + {"Encrypted B33 address", "Şifrlenen B33 salgylar"}, + {"Address registration line", "Hasaba alyş salgysy"}, + {"Domain", "Domen"}, + {"Generate", "Öndürmek"}, + {"Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", "Bellik: Alnan setir diňe ikinji derejeli domenleri bellige almak üçin ulanylyp bilner (example.i2p). Subýutmalary hasaba almak üçin i2pd ulanyň-tools."}, + {"Address", "Salgysy"}, + {"Type", "Görnüş"}, + {"EncType", "Şifrlemek görnüşi"}, + {"Inbound tunnels", "Gelýän tuneller"}, + {"%dms", "%dms"}, + {"Outbound tunnels", "Çykýan tuneller"}, + {"Tags", "Bellikler"}, + {"Incoming", "Gelýän"}, + {"Outgoing", "Çykýan"}, + {"Destination", "Maksat"}, + {"Amount", "Sany"}, + {"Incoming Tags", "Gelýän bellikler"}, + {"Tags sessions", "Sapaklar bellikler"}, + {"Status", "Ýagdaýy"}, + {"Local Destination", "Ýerli maksat"}, + {"Streams", "Strimlary"}, + {"Close stream", "Yap strim"}, + {"I2CP session not found", "I2CP Sessiýa tapylmady"}, + {"I2CP is not enabled", "I2CP goşulmaýar"}, + {"Invalid", "Nädogry"}, + {"Store type", "Ammar görnüşi"}, + {"Expires", "Möhleti gutarýar"}, + {"Non Expired Leases", "Möhleti gutarmady Lizsetlary"}, + {"Gateway", "Derweze"}, + {"TunnelID", "Tuneliň ID"}, + {"EndDate", "Gutarýar"}, + {"Queue size", "Nobatyň ululygy"}, + {"Run peer test", "Synag başlaň"}, + {"Decline transit tunnels", "Tranzit tunellerini ret ediň"}, + {"Accept transit tunnels", "Tranzit tunellerini alyň"}, + {"Cancel graceful shutdown", "Tekiz durmagy ýatyryň"}, + {"Start graceful shutdown", "Tekiz durmak"}, + {"Force shutdown", "Mejbury duralga"}, + {"Reload external CSS styles", "Daşarky CSS stillerini täzeden ýükläň"}, + {"Note: any action done here are not persistent and not changes your config files.", "Bellik: Bu ýerde öndürilen islendik çäre hemişelik däl we konfigurasiýa faýllaryňyzy üýtgetmeýär."}, + {"Logging level", "Giriş derejesi"}, + {"Transit tunnels limit", "Tranzit tunelleriniň çägi"}, + {"Change", "Üýtgetmek"}, + {"Change language", "Dil üýtgetmek"}, + {"no transit tunnels currently built", "gurlan tranzit tunelleri ýok"}, + {"SAM disabled", "SAM öçürilen"}, + {"no sessions currently running", "başlamagyň sessiýalary ýok"}, + {"SAM session not found", "SAM Sessiýa tapylmady"}, + {"SAM Session", "SAM Sessiýa"}, + {"Server Tunnels", "Serwer tunelleri"}, + {"Client Forwards", "Müşderi gönükdirýär"}, + {"Server Forwards", "Serweriň täzeden düzlüleri"}, + {"Unknown page", "Näbelli sahypa"}, + {"Invalid token", "Nädogry token"}, + {"SUCCESS", "Üstünlikli"}, + {"Stream closed", "Strim ýapyk"}, + {"Stream not found or already was closed", "Strim tapylmady ýa-da eýýäm ýapyldy"}, + {"Destination not found", "Niýetlenen ýeri tapylmady"}, + {"StreamID can't be null", "StreamID boş bolup bilmez"}, + {"Return to destination page", "Barmaly nokadynyň nokadyna gaýdyp geliň"}, + {"Back to commands list", "Topar sanawyna dolan"}, + {"Register at reg.i2p", "Reg.i2P-de hasaba duruň"}, + {"Description", "Beýany"}, + {"A bit information about service on domain", "Domendäki hyzmat barada käbir maglumatlar"}, + {"Submit", "Iber"}, + {"Domain can't end with .b32.i2p", "Domain .b32.i2p bilen gutaryp bilmez"}, + {"Domain must end with .i2p", "Domeni .i2p bilen gutarmaly"}, + {"Such destination is not found", "Bu barmaly ýer tapylmady"}, + {"Unknown command", "Näbelli topar"}, + {"Command accepted", "Topar kabul edilýär"}, + {"Proxy error", "Proksi ýalňyşlygy"}, + {"Proxy info", "Proksi maglumat"}, + {"Proxy error: Host not found", "Proksi ýalňyşlygy: Host tapylmady"}, + {"Remote host not found in router's addressbook", "Uzakdaky öý eýesi marşruteriň salgy kitabynda tapylmady"}, + {"You may try to find this host on jump services below", "Aşakdaky böküş hyzmatlarynda bu öý eýesini tapmaga synanyşyp bilersiňiz"}, + {"Invalid request", "Nädogry haýyş"}, + {"Proxy unable to parse your request", "Proksi haýyşyňyzy derňäp bilmeýär"}, + {"Invalid request URI", "Nädogry haýyş URI"}, + {"Can't detect destination host from request", "Haýyşdan barmaly ýerini tapyp bilemok"}, + {"Outproxy failure", "Daşarky proksi ýalňyşlyk"}, + {"Bad outproxy settings", "Daşarky Daşarky proksi sazlamalary nädogry"}, + {"Host %s is not inside I2P network, but outproxy is not enabled", "Adres %s I2P torunda däl, ýöne daşarky proksi goşulmaýar"}, + {"Unknown outproxy URL", "Näbelli daşarky proksi URL"}, + {"Cannot resolve upstream proxy", "Has ýokary proksi kesgitläp bilmeýär"}, + {"Hostname is too long", "Hoster eýesi ady gaty uzyn"}, + {"Cannot connect to upstream SOCKS proxy", "Ýokary jorap SOCKS proksi bilen birigip bolmaýar"}, + {"Cannot negotiate with SOCKS proxy", "Iň ýokary jorap SOCKS proksi bilen ylalaşyp bilmeýärler"}, + {"CONNECT error", "Bagyr haýyşy säwligi"}, + {"Failed to connect", "Birikdirip bilmedi"}, + {"SOCKS proxy error", "SOCKS proksi ýalňyşlygy"}, + {"Failed to send request to upstream", "Öý eýesi proksi üçin haýyş iberip bilmedi"}, + {"No reply from SOCKS proxy", "Jorap SOCKS proksi serwerinden hiç hili jogap ýok"}, + {"Cannot connect", "Birikdirip bilmedi"}, + {"HTTP out proxy not implemented", "Daşarky HTTP proksi serwerini goldamak amala aşyrylmaýar"}, + {"Cannot connect to upstream HTTP proxy", "Ýokary jorap HTTP proksi bilen birigip bolmaýar"}, + {"Host is down", "Salgy elýeterli däl"}, + {"Can't create connection to requested host, it may be down. Please try again later.", "Talap edilýän salgyda birikmäni gurup bilmedim, onlaýn bolup bilmez. Soňra haýyşy soň gaýtalamaga synanyşyň."}, + {"", ""}, + }; + + static std::map> plurals + { + {"%d days", {"%d gün", "%d gün"}}, + {"%d hours", {"%d sagat", "%d sagat"}}, + {"%d minutes", {"%d minut", "%d minut"}}, + {"%d seconds", {"%d sekunt", "%d sekunt"}}, + {"", {"", ""}}, + }; + + std::shared_ptr GetLocale() + { + return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); + } + +} // language +} // i18n +} // i2p diff --git a/i18n/Ukrainian.cpp b/i18n/Ukrainian.cpp new file mode 100644 index 00000000..c1e222ea --- /dev/null +++ b/i18n/Ukrainian.cpp @@ -0,0 +1,219 @@ +/* +* 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 +*/ + +#include +#include +#include +#include +#include "I18N.h" + +// Ukrainian localization file + +namespace i2p +{ +namespace i18n +{ +namespace ukrainian // language namespace +{ + // language name in lowercase + static std::string language = "ukrainian"; + + // See for language plural forms here: + // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html + static int plural (int n) { + return n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2; + } + + static std::map strings + { + {"%.2f KiB", "%.2f КіБ"}, + {"%.2f MiB", "%.2f МіБ"}, + {"%.2f GiB", "%.2f ГіБ"}, + {"building", "будується"}, + {"failed", "невдалий"}, + {"expiring", "завершується"}, + {"established", "працює"}, + {"unknown", "невідомо"}, + {"exploratory", "дослідницький"}, + {"Purple I2P Webconsole", "Веб-консоль Purple I2P"}, + {"i2pd webconsole", "Веб-консоль i2pd"}, + {"Main page", "Головна"}, + {"Router commands", "Команди маршрутизатора"}, + {"Local Destinations", "Локальні Призначення"}, + {"LeaseSets", "Лізсети"}, + {"Tunnels", "Тунелі"}, + {"Transit Tunnels", "Транзитні Тунелі"}, + {"Transports", "Транспорти"}, + {"I2P tunnels", "I2P тунелі"}, + {"SAM sessions", "SAM сесії"}, + {"ERROR", "ПОМИЛКА"}, + {"OK", "OK"}, + {"Testing", "Тестування"}, + {"Firewalled", "Заблоковано ззовні"}, + {"Unknown", "Невідомо"}, + {"Proxy", "Проксі"}, + {"Mesh", "MESH-мережа"}, + {"Clock skew", "Неточний час"}, + {"Offline", "Офлайн"}, + {"Symmetric NAT", "Симетричний NAT"}, + {"Full cone NAT", "Повний NAT"}, + {"No Descriptors", "Немає Описів"}, + {"Uptime", "У мережі"}, + {"Network status", "Мережевий статус"}, + {"Network status v6", "Мережевий статус v6"}, + {"Stopping in", "Зупинка через"}, + {"Family", "Сімейство"}, + {"Tunnel creation success rate", "Успішно побудованих тунелів"}, + {"Received", "Отримано"}, + {"%.2f KiB/s", "%.2f КіБ/с"}, + {"Sent", "Відправлено"}, + {"Transit", "Транзит"}, + {"Data path", "Шлях до даних"}, + {"Hidden content. Press on text to see.", "Прихований вміст. Щоб відобразити, натисніть на текст."}, + {"Router Ident", "Ідентифікатор маршрутизатора"}, + {"Router Family", "Сімейство маршрутизатора"}, + {"Router Caps", "Прапорці маршрутизатора"}, + {"Version", "Версія"}, + {"Our external address", "Наша зовнішня адреса"}, + {"supported", "підтримується"}, + {"Routers", "Маршрутизатори"}, + {"Floodfills", "Флудфіли"}, + {"Client Tunnels", "Клієнтські Тунелі"}, + {"Services", "Сервіси"}, + {"Enabled", "Увімкнуто"}, + {"Disabled", "Вимкнуто"}, + {"Encrypted B33 address", "Шифровані B33 адреси"}, + {"Address registration line", "Рядок реєстрації адреси"}, + {"Domain", "Домен"}, + {"Generate", "Згенерувати"}, + {"Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", "Примітка: отриманий рядок може бути використаний тільки для реєстрації доменів другого рівня (example.i2p). Для реєстрації піддоменів використовуйте i2pd-tools."}, + {"Address", "Адреса"}, + {"Type", "Тип"}, + {"EncType", "ТипШифр"}, + {"Inbound tunnels", "Вхідні тунелі"}, + {"%dms", "%dмс"}, + {"Outbound tunnels", "Вихідні тунелі"}, + {"Tags", "Теги"}, + {"Incoming", "Вхідні"}, + {"Outgoing", "Вихідні"}, + {"Destination", "Призначення"}, + {"Amount", "Кількість"}, + {"Incoming Tags", "Вхідні Теги"}, + {"Tags sessions", "Сесії Тегів"}, + {"Status", "Статус"}, + {"Local Destination", "Локальні Призначення"}, + {"Streams", "Потоки"}, + {"Close stream", "Закрити потік"}, + {"I2CP session not found", "I2CP сесія не знайдена"}, + {"I2CP is not enabled", "I2CP не увікнуто"}, + {"Invalid", "Некоректний"}, + {"Store type", "Тип сховища"}, + {"Expires", "Завершується"}, + {"Non Expired Leases", "Не завершені Lease-и"}, + {"Gateway", "Шлюз"}, + {"TunnelID", "ID тунеля"}, + {"EndDate", "Закінчується"}, + {"floodfill mode is disabled", "режим floodfill вимкнено"}, + {"Queue size", "Розмір черги"}, + {"Run peer test", "Запустити тестування"}, + {"Reload tunnels configuration", "Перезавантажити налаштування тунелів"}, + {"Decline transit tunnels", "Відхиляти транзитні тунелі"}, + {"Accept transit tunnels", "Ухвалювати транзитні тунелі"}, + {"Cancel graceful shutdown", "Скасувати плавну зупинку"}, + {"Start graceful shutdown", "Запустити плавну зупинку"}, + {"Force shutdown", "Примусова зупинка"}, + {"Reload external CSS styles", "Перезавантажити зовнішні стилі CSS"}, + {"Note: any action done here are not persistent and not changes your config files.", "Примітка: будь-яка зроблена тут дія не є постійною та не змінює ваші конфігураційні файли."}, + {"Logging level", "Рівень логування"}, + {"Transit tunnels limit", "Обмеження транзитних тунелів"}, + {"Change", "Змінити"}, + {"Change language", "Змінити мову"}, + {"no transit tunnels currently built", "немає побудованих транзитних тунелів"}, + {"SAM disabled", "SAM вимкнуто"}, + {"no sessions currently running", "немає запущених сесій"}, + {"SAM session not found", "SAM сесія не знайдена"}, + {"SAM Session", "SAM сесія"}, + {"Server Tunnels", "Серверні Тунелі"}, + {"Client Forwards", "Клієнтські Переспрямування"}, + {"Server Forwards", "Серверні Переспрямування"}, + {"Unknown page", "Невідома сторінка"}, + {"Invalid token", "Невірний токен"}, + {"SUCCESS", "УСПІШНО"}, + {"Stream closed", "Потік зачинений"}, + {"Stream not found or already was closed", "Потік не знайдений або вже зачинений"}, + {"Destination not found", "Точка призначення не знайдена"}, + {"StreamID can't be null", "Ідентифікатор потоку не може бути порожнім"}, + {"Return to destination page", "Повернутися на сторінку точки призначення"}, + {"You will be redirected in %d seconds", "Ви будете переадресовані через %d секунд"}, + {"Transit tunnels count must not exceed %d", "Кількість транзитних тунелів не повинна перевищувати %d"}, + {"Back to commands list", "Повернутися до списку команд"}, + {"Register at reg.i2p", "Зареєструвати на reg.i2p"}, + {"Description", "Опис"}, + {"A bit information about service on domain", "Трохи інформації про сервіс на домені"}, + {"Submit", "Надіслати"}, + {"Domain can't end with .b32.i2p", "Домен не може закінчуватися на .b32.i2p"}, + {"Domain must end with .i2p", "Домен повинен закінчуватися на .i2p"}, + {"Such destination is not found", "Така точка призначення не знайдена"}, + {"Unknown command", "Невідома команда"}, + {"Command accepted", "Команда прийнята"}, + {"Proxy error", "Помилка проксі"}, + {"Proxy info", "Інформація проксі"}, + {"Proxy error: Host not found", "Помилка проксі: Адреса не знайдена"}, + {"Remote host not found in router's addressbook", "Віддалена адреса не знайдена в адресній книзі маршрутизатора"}, + {"You may try to find this host on jump services below", "Ви можете спробувати знайти дану адресу на джамп сервісах нижче"}, + {"Invalid request", "Некоректний запит"}, + {"Proxy unable to parse your request", "Проксі не може розібрати ваш запит"}, + {"Addresshelper is not supported", "Адресна книга не підтримується"}, + {"Host %s is already in router's addressbook. Be careful: source of this URL may be harmful! Click here to update record: Continue.", "Хост %s вже в адресній книзі маршрутизатора. Будьте обережні: джерело цієї адреси може зашкодити! Натисніть тут, щоб оновити запис: Продовжити."}, + {"Addresshelper forced update rejected", "Адресна книга відхилила примусове оновлення"}, + {"To add host %s in router's addressbook, click here: Continue.", "Щоб додати хост %s в адресі маршрутизатора, натисніть тут: Продовжити."}, + {"Addresshelper request", "Запит на адресну сторінку"}, + {"Host %s added to router's addressbook from helper. Click here to proceed: Continue.", "Хост %s доданий в адресну книгу маршрутизатора від помічника. Натисніть тут, щоб продовжити: Продовжити."}, + {"Addresshelper adding", "Адреса додана"}, + {"Host %s is already in router's addressbook. Click here to update record: Continue.", "Хост %s вже в адресній книзі маршрутизатора. Натисніть тут, щоб оновити запис: Продовжити."}, + {"Addresshelper update", "Оновлення адресної книги"}, + {"Invalid request URI", "Некоректний URI запиту"}, + {"Can't detect destination host from request", "Не вдалось визначити адресу призначення з запиту"}, + {"Outproxy failure", "Помилка зовнішнього проксі"}, + {"Bad outproxy settings", "Некоректні налаштування зовнішнього проксі"}, + {"Host %s is not inside I2P network, but outproxy is not enabled", "Адрес %s не в I2P мережі, але зовнішній проксі не включений"}, + {"Unknown outproxy URL", "Невідомий URL зовнішнього проксі"}, + {"Cannot resolve upstream proxy", "Не вдається визначити висхідний проксі"}, + {"Hostname is too long", "Ім'я вузла надто довге"}, + {"Cannot connect to upstream SOCKS proxy", "Не вдалося підключитися до висхідного SOCKS проксі сервера"}, + {"Cannot negotiate with SOCKS proxy", "Не вдається домовитися з висхідним SOCKS проксі"}, + {"CONNECT error", "Помилка CONNECT запиту"}, + {"Failed to connect", "Не вдалося підключитися"}, + {"SOCKS proxy error", "Помилка SOCKS проксі"}, + {"Failed to send request to upstream", "Не вдалося відправити запит висхідному проксі"}, + {"No reply from SOCKS proxy", "Немає відповіді від SOCKS проксі сервера"}, + {"Cannot connect", "Не вдалося підключитися"}, + {"HTTP out proxy not implemented", "Підтримка зовнішнього HTTP проксі сервера не реалізована"}, + {"Cannot connect to upstream HTTP proxy", "Не вдалося підключитися до висхідного HTTP проксі сервера"}, + {"Host is down", "Вузол недоступний"}, + {"Can't create connection to requested host, it may be down. Please try again later.", "Не вдалося встановити з'єднання до запитаного вузла, можливо він не в мережі. Спробуйте повторити запит пізніше."}, + {"", ""}, + }; + + static std::map> plurals + { + {"%d days", {"%d день", "%d дня", "%d днів"}}, + {"%d hours", {"%d годину", "%d години", "%d годин"}}, + {"%d minutes", {"%d хвилину", "%d хвилини", "%d хвилин"}}, + {"%d seconds", {"%d секунду", "%d секунди", "%d секунд"}}, + {"", {"", "", ""}}, + }; + + std::shared_ptr GetLocale() + { + return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); + } + +} // language +} // i18n +} // i2p diff --git a/i18n/Uzbek.cpp b/i18n/Uzbek.cpp new file mode 100644 index 00000000..9d798be4 --- /dev/null +++ b/i18n/Uzbek.cpp @@ -0,0 +1,219 @@ +/* +* 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 +*/ + +#include +#include +#include +#include +#include "I18N.h" + +// Ukrainian localization file + +namespace i2p +{ +namespace i18n +{ +namespace uzbek // language namespace +{ + // language name in lowercase + static std::string language = "uzbek"; + + // See for language plural forms here: + // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html + static int plural (int n) { + return n > 1 ? 1 : 0; + } + + static std::map strings + { + {"%.2f KiB", "%.2f KiB"}, + {"%.2f MiB", "%.2f MiB"}, + {"%.2f GiB", "%.2f GiB"}, + {"building", "yaratilmoqda"}, + {"failed", "muvaffaqiyatsiz"}, + {"expiring", "muddati tugaydi"}, + {"established", "aloqa o'rnatildi"}, + {"unknown", "noma'lum"}, + {"exploratory", "tadqiqiy"}, + {"Purple I2P Webconsole", "Veb-konsoli Purple I2P"}, + {"i2pd webconsole", "i2pd veb-konsoli"}, + {"Main page", "Asosiy sahifa"}, + {"Router commands", "Router buyruqlari"}, + {"Local Destinations", "Mahalliy joylanishlar"}, + {"LeaseSets", "LeaseSets"}, + {"Tunnels", "Tunnellar"}, + {"Transit Tunnels", "Tranzit Tunellari"}, + {"Transports", "Transportlar"}, + {"I2P tunnels", "I2P tunnellari"}, + {"SAM sessions", "SAM sessiyalari"}, + {"ERROR", "XATO"}, + {"OK", "OK"}, + {"Testing", "Testlash"}, + {"Firewalled", "Xavfsizlik devori bilan himoyalangan"}, + {"Unknown", "Notanish"}, + {"Proxy", "Proksi"}, + {"Mesh", "Mesh To'r"}, + {"Clock skew", "Aniq vaqt emas"}, + {"Offline", "Oflayn"}, + {"Symmetric NAT", "Simmetrik NAT"}, + {"Full cone NAT", "Full cone NAT"}, + {"No Descriptors", "Deskriptorlar yo'q"}, + {"Uptime", "Ish vaqti"}, + {"Network status", "Tarmoq holati"}, + {"Network status v6", "Tarmoq holati v6"}, + {"Stopping in", "Ichida to'xtatish"}, + {"Family", "Oila"}, + {"Tunnel creation success rate", "Tunnel yaratish muvaffaqiyat darajasi"}, + {"Received", "Qabul qilindi"}, + {"%.2f KiB/s", "%.2f KiB/s"}, + {"Sent", "Yuborilgan"}, + {"Transit", "Tranzit"}, + {"Data path", "Ma'lumotlar joylanishi"}, + {"Hidden content. Press on text to see.", "Yashirin tarkib. Ko'rish uchun matn ustida bosing."}, + {"Router Ident", "Router identifikatori"}, + {"Router Family", "Router oilasi"}, + {"Router Caps", "Router Bayroqlari"}, + {"Version", "Versiya"}, + {"Our external address", "Bizning tashqi manzilimiz"}, + {"supported", "qo'llab-quvvatlanadi"}, + {"Routers", "Routerlar"}, + {"Floodfills", "Floodfills"}, + {"Client Tunnels", "Mijoz Tunellari"}, + {"Services", "Xizmatlar"}, + {"Enabled", "Yoqilgan"}, + {"Disabled", "O'chirilgan"}, + {"Encrypted B33 address", "Shifrlangan B33 manzil"}, + {"Address registration line", "Manzilni ro'yxatga olish liniyasi"}, + {"Domain", "Domen"}, + {"Generate", "Yaratish"}, + {"Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", "Eslatma: natija satridan faqat 2LD domenlarini ro'yxatdan o'tkazish uchun foydalanish mumkin (example.i2p). Subdomenlarni ro'yxatdan o'tkazish uchun 'i2pd-tools'dan foydalaning."}, + {"Address", "Manzil"}, + {"Type", "Turi"}, + {"EncType", "ShifrlashTuri"}, + {"Inbound tunnels", "Kirish tunnellari"}, + {"%dms", "%dms"}, + {"Outbound tunnels", "Chiquvchi tunnellar"}, + {"Tags", "Teglar"}, + {"Incoming", "Kiruvchi"}, + {"Outgoing", "Chiquvchi"}, + {"Destination", "Manzilgoh"}, + {"Amount", "Soni"}, + {"Incoming Tags", "Kiruvchi teglar"}, + {"Tags sessions", "Teglar sessiyalari"}, + {"Status", "Holat"}, + {"Local Destination", "Mahalliy joylanish"}, + {"Streams", "Strim"}, + {"Close stream", "Strimni o'chirish"}, + {"I2CP session not found", "I2CP sessiyasi topilmadi"}, + {"I2CP is not enabled", "I2CP yoqilmagan"}, + {"Invalid", "Noto'g'ri"}, + {"Store type", "Saqlash turi"}, + {"Expires", "Muddati tugaydi"}, + {"Non Expired Leases", "Muddati O'tmagan Leases"}, + {"Gateway", "Kirish yo'li"}, + {"TunnelID", "TunnelID"}, + {"EndDate", "Tugash Sanasi"}, + {"floodfill mode is disabled", "floodfill rejimi o'chirilgan"}, + {"Queue size", "Navbat hajmi"}, + {"Run peer test", "Sinovni boshlang"}, + {"Reload tunnels configuration", "Tunnel konfiguratsiyasini qayta yuklash"}, + {"Decline transit tunnels", "Tranzit tunnellarini rad etish"}, + {"Accept transit tunnels", "Tranzit tunnellarni qabul qilish"}, + {"Cancel graceful shutdown", "Yumshoq to'xtashni bekor qilish"}, + {"Start graceful shutdown", "Yumshoq to'xtashni boshlash"}, + {"Force shutdown", "Majburiy to'xtatish"}, + {"Reload external CSS styles", "Tashqi CSS uslublarini qayta yuklang"}, + {"Note: any action done here are not persistent and not changes your config files.", "Eslatma: shu yerda qilingan har qanday harakat doimiy emas va konfiguratsiya fayllarini o'zgartirmaydi."}, + {"Logging level", "Jurnal darajasi"}, + {"Transit tunnels limit", "Tranzit tunellarning chegarasi"}, + {"Change", "O'zgartirish"}, + {"Change language", "Tilni o'zgartirish"}, + {"no transit tunnels currently built", "qurilgan tranzit tunnellari yo'q"}, + {"SAM disabled", "SAM o'chirilgan"}, + {"no sessions currently running", "hech qanday ishlaydigan sessiyalar yo'q"}, + {"SAM session not found", "SAM sessiyasi topilmadi"}, + {"SAM Session", "SAM sessiyasi"}, + {"Server Tunnels", "Server Tunellari"}, + {"Client Forwards", "Mijozlarni Yo'naltirish"}, + {"Server Forwards", "Serverni Yo'naltirish"}, + {"Unknown page", "Noma'lum sahifa"}, + {"Invalid token", "Noto‘g‘ri belgi"}, + {"SUCCESS", "Muvaffaqiyat"}, + {"Stream closed", "Strim yopiq"}, + {"Stream not found or already was closed", "Strim topilmadi yoki allaqachon yopilgan"}, + {"Destination not found", "Yo'nalish topilmadi"}, + {"StreamID can't be null", "StreamID bo'sh bo'lishi mumkin emas"}, + {"Return to destination page", "Manzilgoh sahifasiga qaytish"}, + {"You will be redirected in %d seconds", "Siz %d soniyadan so‘ng boshqa yo‘nalishga yo‘naltirilasiz"}, + {"Transit tunnels count must not exceed %d", "Tranzit tunnellar soni %d dan oshmasligi kerak"}, + {"Back to commands list", "Buyruqlar ro'yxatiga qaytish"}, + {"Register at reg.i2p", "Reg.i2p-da ro'yxatdan o'ting"}, + {"Description", "Tavsif"}, + {"A bit information about service on domain", "Domen xizmatlari haqida bir oz ma'lumot"}, + {"Submit", "Yuborish"}, + {"Domain can't end with .b32.i2p", "Domen .b32.i2p bilan tugashi mumkin emas"}, + {"Domain must end with .i2p", "Domen .i2p bilan tugashi kerak"}, + {"Such destination is not found", "Bunday yo'nalish topilmadi"}, + {"Unknown command", "Noma'lum buyruq"}, + {"Command accepted", "Buyruq qabul qilindi"}, + {"Proxy error", "Proksi xatosi"}, + {"Proxy info", "Proksi ma'lumotlari"}, + {"Proxy error: Host not found", "Proksi xatosi: Xost topilmadi"}, + {"Remote host not found in router's addressbook", "Masofaviy xost yo'riqnoma manzillar kitobida topilmadi"}, + {"You may try to find this host on jump services below", "Siz xost quyida o'tish xizmatlari orqali topishga harakat qilishingiz mumkin"}, + {"Invalid request", "Noto‘g‘ri so‘rov"}, + {"Proxy unable to parse your request", "Proksi sizning so'rovingizni aniqlab ololmayapti"}, + {"Addresshelper is not supported", "Addresshelper qo'llab-quvvatlanmaydi"}, + {"Host %s is already in router's addressbook. Be careful: source of this URL may be harmful! Click here to update record: Continue.", "%s xosti allaqachon routerning manzillar kitobida. Ehtiyot bo'ling: bu URL manbasi zararli bo'lishi mumkin! Yozuvni yangilash uchun bu yerni bosing: Davom etish."}, + {"Addresshelper forced update rejected", "Addresshelperni majburiy yangilash rad etildi"}, + {"To add host %s in router's addressbook, click here: Continue.", "Routerning manzillar kitobiga %s xostini qo'shish uchun bu yerni bosing: Davom etish."}, + {"Addresshelper request", "Addresshelper so'rovi"}, + {"Host %s added to router's addressbook from helper. Click here to proceed: Continue.", "Yordamchidan router manzillar kitobiga %s xost qo‘shildi. Davom etish uchun bu yerga bosing: Davom etish."}, + {"Addresshelper adding", "Addresshelperni qo'shish"}, + {"Host %s is already in router's addressbook. Click here to update record: Continue.", "%s xosti allaqachon routerning manzillar kitobida. Yozuvni yangilash uchun shu yerni bosing: Davom etish."}, + {"Addresshelper update", "Addresshelperni yangilash"}, + {"Invalid request URI", "Noto'g'ri URI so'rovi"}, + {"Can't detect destination host from request", "So‘rov orqali manzil xostini aniqlab bo'lmayapti"}, + {"Outproxy failure", "Tashqi proksi muvaffaqiyatsizligi"}, + {"Bad outproxy settings", "Noto'g'ri tashqi proksi-server sozlamalari"}, + {"Host %s is not inside I2P network, but outproxy is not enabled", "Xost %s I2P tarmog'ida emas, lekin tashqi proksi yoqilmagan"}, + {"Unknown outproxy URL", "Noma'lum outproxy URL"}, + {"Cannot resolve upstream proxy", "Yuqoridagi 'proxy-server'ni aniqlab olib bolmayapti"}, + {"Hostname is too long", "Xost nomi juda uzun"}, + {"Cannot connect to upstream SOCKS proxy", "Yuqori 'SOCKS proxy'ga ulanib bo'lmayapti"}, + {"Cannot negotiate with SOCKS proxy", "'SOCKS proxy' bilan muzokara olib bo'lmaydi"}, + {"CONNECT error", "CONNECT xatosi"}, + {"Failed to connect", "Ulanib bo'lmayapti"}, + {"SOCKS proxy error", "'SOCKS proxy' xatosi"}, + {"Failed to send request to upstream", "Yuqori proksi-serveriga so'rovni uborib bo'lmadi"}, + {"No reply from SOCKS proxy", "'SOCKS proxy'dan javob yo'q"}, + {"Cannot connect", "Ulanib bo'lmaydi"}, + {"HTTP out proxy not implemented", "Tashqi HTTP proksi-serverni qo'llab-quvvatlash amalga oshirilmagan"}, + {"Cannot connect to upstream HTTP proxy", "Yuqori 'HTTP proxy'ga ulanib bo'lmayapti"}, + {"Host is down", "Xost ishlamayapti"}, + {"Can't create connection to requested host, it may be down. Please try again later.", "Talab qilingan xost bilan aloqa o'rnatilmadi, u ishlamay qolishi mumkin. Iltimos keyinroq qayta urinib ko'ring."}, + {"", ""}, + }; + + static std::map> plurals + { + {"%d days", {"%d kun", "%d kun"}}, + {"%d hours", {"%d soat", "%d soat"}}, + {"%d minutes", {"%d daqiqa", "%d daqiqa"}}, + {"%d seconds", {"%d soniya", "%d soniya"}}, + {"", {"", ""}}, + }; + + std::shared_ptr GetLocale() + { + return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); + } + +} // language +} // i18n +} // i2p diff --git a/libi2pd/Base.cpp b/libi2pd/Base.cpp index 921c20af..e979c5b4 100644 --- a/libi2pd/Base.cpp +++ b/libi2pd/Base.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -185,10 +185,10 @@ namespace data if (InCount && !m) outCount = 3 * n; else - { - outCount = 0; return 0; - } + + if(*InBuffer == P64) + return 0; ps = (unsigned char *)(InBuffer + InCount - 1); while ( *ps-- == P64 ) @@ -196,7 +196,7 @@ namespace data ps = (unsigned char *)InBuffer; if (outCount > len) - return -1; + return 0; pd = OutBuffer; auto endOfOutBuffer = OutBuffer + outCount; @@ -272,7 +272,7 @@ namespace data size_t Base32ToByteStream (const char * inBuf, size_t len, uint8_t * outBuf, size_t outLen) { - int tmp = 0, bits = 0; + unsigned int tmp = 0, bits = 0; size_t ret = 0; for (size_t i = 0; i < len; i++) { @@ -301,7 +301,7 @@ namespace data size_t ByteStreamToBase32 (const uint8_t * inBuf, size_t len, char * outBuf, size_t outLen) { size_t ret = 0, pos = 1; - int bits = 8, tmp = inBuf[0]; + unsigned int bits = 8, tmp = inBuf[0]; while (ret < outLen && (bits > 0 || pos < len)) { if (bits < 5) diff --git a/libi2pd/Base.h b/libi2pd/Base.h index 073d9b40..79152e02 100644 --- a/libi2pd/Base.h +++ b/libi2pd/Base.h @@ -24,8 +24,8 @@ namespace data { size_t ByteStreamToBase32 (const uint8_t * InBuf, size_t len, char * outBuf, size_t outLen); /** - Compute the size for a buffer to contain encoded base64 given that the size of the input is input_size bytes - */ + * Compute the size for a buffer to contain encoded base64 given that the size of the input is input_size bytes + */ size_t Base64EncodingBufferSize(const size_t input_size); std::string ToBase64Standard (const std::string& in); // using standard table, for Proxy-Authorization diff --git a/libi2pd/Blinding.cpp b/libi2pd/Blinding.cpp index 6770d223..ced086e1 100644 --- a/libi2pd/Blinding.cpp +++ b/libi2pd/Blinding.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -99,7 +99,7 @@ namespace data static size_t BlindECDSA (i2p::data::SigningKeyType sigType, const uint8_t * key, const uint8_t * seed, Fn blind, Args&&...args) // blind is BlindEncodedPublicKeyECDSA or BlindEncodedPrivateKeyECDSA { - size_t publicKeyLength = 0; + size_t publicKeyLength = 0; EC_GROUP * group = nullptr; switch (sigType) { @@ -122,7 +122,7 @@ namespace data break; } default: - LogPrint (eLogError, "Blinding: signature type ", (int)sigType, " is not ECDSA"); + LogPrint (eLogError, "Blinding: Signature type ", (int)sigType, " is not ECDSA"); } if (group) { @@ -135,7 +135,7 @@ namespace data //---------------------------------------------------------- const uint8_t B33_TWO_BYTES_SIGTYPE_FLAG = 0x01; - const uint8_t B33_PER_SECRET_FLAG = 0x02; // not used for now + // const uint8_t B33_PER_SECRET_FLAG = 0x02; // not used for now const uint8_t B33_PER_CLIENT_AUTH_FLAG = 0x04; BlindedPublicKey::BlindedPublicKey (std::shared_ptr identity, bool clientAuth): @@ -146,7 +146,10 @@ namespace data m_PublicKey.resize (len); memcpy (m_PublicKey.data (), identity->GetSigningPublicKeyBuffer (), len); m_SigType = identity->GetSigningKeyType (); - m_BlindedSigType = m_SigType; + if (m_SigType == i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519) + m_BlindedSigType = i2p::data::SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519; // 7 -> 11 + else + m_BlindedSigType = m_SigType; } BlindedPublicKey::BlindedPublicKey (const std::string& b33): @@ -156,7 +159,7 @@ namespace data size_t l = i2p::data::Base32ToByteStream (b33.c_str (), b33.length (), addr, 40); if (l < 32) { - LogPrint (eLogError, "Blinding: malformed b33 ", b33); + LogPrint (eLogError, "Blinding: Malformed b33 ", b33); return; } uint32_t checksum = crc32 (0, addr + 3, l - 3); @@ -186,10 +189,10 @@ namespace data memcpy (m_PublicKey.data (), addr + offset, len); } else - LogPrint (eLogError, "Blinding: public key in b33 address is too short for signature type ", (int)m_SigType); + LogPrint (eLogError, "Blinding: Public key in b33 address is too short for signature type ", (int)m_SigType); } else - LogPrint (eLogError, "Blinding: unknown signature type ", (int)m_SigType, " in b33"); + LogPrint (eLogError, "Blinding: Unknown signature type ", (int)m_SigType, " in b33"); } std::string BlindedPublicKey::ToB33 () const @@ -256,7 +259,7 @@ namespace data publicKeyLength = i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH; break; default: - LogPrint (eLogError, "Blinding: can't blind signature type ", (int)m_SigType); + LogPrint (eLogError, "Blinding: Can't blind signature type ", (int)m_SigType); } return publicKeyLength; } @@ -277,8 +280,16 @@ namespace data i2p::crypto::GetEd25519 ()->BlindPrivateKey (priv, seed, blindedPriv, blindedPub); publicKeyLength = i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH; break; + case i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519: + { + uint8_t exp[64]; + i2p::crypto::Ed25519::ExpandPrivateKey (priv, exp); + i2p::crypto::GetEd25519 ()->BlindPrivateKey (exp, seed, blindedPriv, blindedPub); + publicKeyLength = i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH; + break; + } default: - LogPrint (eLogError, "Blinding: can't blind signature type ", (int)m_SigType); + LogPrint (eLogError, "Blinding: Can't blind signature type ", (int)m_SigType); } return publicKeyLength; } @@ -316,7 +327,7 @@ namespace data SHA256_Final ((uint8_t *)hash, &ctx); } else - LogPrint (eLogError, "Blinding: blinded key type ", (int)m_BlindedSigType, " is not supported"); + LogPrint (eLogError, "Blinding: Blinded key type ", (int)m_BlindedSigType, " is not supported"); return hash; } diff --git a/libi2pd/Blinding.h b/libi2pd/Blinding.h index 2f670882..c78db003 100644 --- a/libi2pd/Blinding.h +++ b/libi2pd/Blinding.h @@ -28,8 +28,8 @@ namespace data const uint8_t * GetPublicKey () const { return m_PublicKey.data (); }; size_t GetPublicKeyLen () const { return m_PublicKey.size (); }; - SigningKeyType GetSigType () const { return m_SigType; }; - SigningKeyType GetBlindedSigType () const { return m_BlindedSigType; }; + SigningKeyType GetSigType () const { return m_SigType; }; + SigningKeyType GetBlindedSigType () const { return m_BlindedSigType; }; bool IsValid () const { return GetSigType (); }; // signature type 0 means invalid void GetSubcredential (const uint8_t * blinded, size_t len, uint8_t * subcredential) const; // 32 bytes diff --git a/libi2pd/BloomFilter.cpp b/libi2pd/BloomFilter.cpp deleted file mode 100644 index de077e60..00000000 --- a/libi2pd/BloomFilter.cpp +++ /dev/null @@ -1,77 +0,0 @@ -/* -* Copyright (c) 2013-2020, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - -#include "BloomFilter.h" -#include "I2PEndian.h" -#include -#include - -namespace i2p -{ -namespace util -{ - - /** @brief decaying bloom filter implementation */ - class DecayingBloomFilter : public IBloomFilter - { - public: - - DecayingBloomFilter(const std::size_t size) - { - m_Size = size; - m_Data = new uint8_t[size]; - } - - /** @brief implements IBloomFilter::~IBloomFilter */ - ~DecayingBloomFilter() - { - delete [] m_Data; - } - - /** @brief implements IBloomFilter::Add */ - bool Add(const uint8_t * data, std::size_t len) - { - std::size_t idx; - uint8_t mask; - Get(data, len, idx, mask); - if(m_Data[idx] & mask) return false; // filter hit - m_Data[idx] |= mask; - return true; - } - - /** @brief implements IBloomFilter::Decay */ - void Decay() - { - // reset bloom filter buffer - memset(m_Data, 0, m_Size); - } - - private: - /** @brief get bit index for for data */ - void Get(const uint8_t * data, std::size_t len, std::size_t & idx, uint8_t & bm) - { - bm = 1; - uint8_t digest[32]; - // TODO: use blake2 because it's faster - SHA256(data, len, digest); - uint64_t i = buf64toh(digest); - idx = i % m_Size; - bm <<= (i % 8); - } - - uint8_t * m_Data; - std::size_t m_Size; - }; - - - BloomFilterPtr BloomFilter(std::size_t capacity) - { - return std::make_shared(capacity); - } -} -} diff --git a/libi2pd/BloomFilter.h b/libi2pd/BloomFilter.h deleted file mode 100644 index ade854e4..00000000 --- a/libi2pd/BloomFilter.h +++ /dev/null @@ -1,39 +0,0 @@ -/* -* Copyright (c) 2013-2020, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - -#ifndef BLOOM_FILTER_H_ -#define BLOOM_FILTER_H_ -#include -#include - -namespace i2p -{ -namespace util -{ - - /** @brief interface for bloom filter */ - struct IBloomFilter - { - - /** @brief destructor */ - virtual ~IBloomFilter() {}; - /** @brief add entry to bloom filter, return false if filter hit otherwise return true */ - virtual bool Add(const uint8_t * data, std::size_t len) = 0; - /** @brief optionally decay old entries */ - virtual void Decay() = 0; - }; - - typedef std::shared_ptr BloomFilterPtr; - - /** @brief create bloom filter */ - BloomFilterPtr BloomFilter(std::size_t capacity = 1024 * 8); - -} -} - -#endif diff --git a/libi2pd/CPU.cpp b/libi2pd/CPU.cpp index e7eff473..d42f1ddd 100644 --- a/libi2pd/CPU.cpp +++ b/libi2pd/CPU.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -7,57 +7,52 @@ */ #include "CPU.h" -#if defined(__x86_64__) || defined(__i386__) -#include -#endif #include "Log.h" -#ifndef bit_AES -#define bit_AES (1 << 25) -#endif -#ifndef bit_AVX -#define bit_AVX (1 << 28) -#endif +#if defined(_MSC_VER) +#include +#ifndef bit_AES + #define bit_AES (1 << 25) +#endif +#endif namespace i2p { namespace cpu { bool aesni = false; - bool avx = false; - void Detect() + inline bool cpu_support_aes() { -#if defined(__AES__) || defined(__AVX__) +#if (defined(_M_AMD64) || defined(__x86_64__)) || (defined(_M_IX86) || defined(__i386__)) +#if defined(_MSC_VER) + int cpu_info[4]; + __cpuid(cpu_info, 1); + return ((cpu_info[2] & bit_AES) != 0); +#elif defined(__clang__) +#if __clang_major__ >= 6 + __builtin_cpu_init(); +#endif + return __builtin_cpu_supports("aes"); +#elif defined(__GNUC__) + __builtin_cpu_init(); + return __builtin_cpu_supports("aes"); +#else + return false; +#endif +#else + return false; +#endif + } -#if defined(__x86_64__) || defined(__i386__) - int info[4]; - __cpuid(0, info[0], info[1], info[2], info[3]); - if (info[0] >= 0x00000001) { - __cpuid(0x00000001, info[0], info[1], info[2], info[3]); -#ifdef __AES__ - aesni = info[2] & bit_AES; // AESNI -#endif // __AES__ -#ifdef __AVX__ - avx = info[2] & bit_AVX; // AVX -#endif // __AVX__ + void Detect(bool AesSwitch, bool force) + { + if ((cpu_support_aes() && AesSwitch) || (AesSwitch && force)) { + aesni = true; } -#endif // defined(__x86_64__) || defined(__i386__) -#ifdef __AES__ - if(aesni) - { - LogPrint(eLogInfo, "AESNI enabled"); - } -#endif // __AES__ -#ifdef __AVX__ - if(avx) - { - LogPrint(eLogInfo, "AVX enabled"); - } -#endif // __AVX__ -#endif // defined(__AES__) || defined(__AVX__) + LogPrint(eLogInfo, "AESNI ", (aesni ? "enabled" : "disabled")); } } } diff --git a/libi2pd/CPU.h b/libi2pd/CPU.h index 9677b293..a0695884 100644 --- a/libi2pd/CPU.h +++ b/libi2pd/CPU.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -14,9 +14,8 @@ namespace i2p namespace cpu { extern bool aesni; - extern bool avx; - void Detect(); + void Detect(bool AesSwitch, bool force); } } diff --git a/libi2pd/Config.cpp b/libi2pd/Config.cpp index 1c565083..5551b010 100644 --- a/libi2pd/Config.cpp +++ b/libi2pd/Config.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -19,6 +19,7 @@ #include "Identity.h" #include "Config.h" #include "version.h" +#include "Log.h" using namespace boost::program_options; @@ -36,6 +37,7 @@ namespace config { ("conf", value()->default_value(""), "Path to main i2pd config file (default: try ~/.i2pd/i2pd.conf or /var/lib/i2pd/i2pd.conf)") ("tunconf", value()->default_value(""), "Path to config with tunnels list and options (default: try ~/.i2pd/tunnels.conf or /var/lib/i2pd/tunnels.conf)") ("tunnelsdir", value()->default_value(""), "Path to extra tunnels' configs folder (default: ~/.i2pd/tunnels.d or /var/lib/i2pd/tunnels.d") + ("certsdir", value()->default_value(""), "Path to certificates used for verifying .su3, families (default: ~/.i2pd/certificates or /var/lib/i2pd/certificates") ("pidfile", value()->default_value(""), "Path to pidfile (default: ~/i2pd/i2pd.pid or /var/lib/i2pd/i2pd.pid)") ("log", value()->default_value(""), "Logs destination: stdout, file, syslog (stdout if not set)") ("logfile", value()->default_value(""), "Path to logfile (stdout if not set, autodetect if daemon)") @@ -43,26 +45,29 @@ namespace config { ("logclftime", bool_switch()->default_value(false), "Write full CLF-formatted date and time to log (default: disabled, write only time)") ("family", value()->default_value(""), "Specify a family, router belongs to") ("datadir", value()->default_value(""), "Path to storage of i2pd data (RI, keys, peer profiles, ...)") - ("host", value()->default_value("0.0.0.0"), "External IP") + ("host", value()->default_value(""), "External IP") ("ifname", value()->default_value(""), "Network interface to bind to") ("ifname4", value()->default_value(""), "Network interface to bind to for ipv4") ("ifname6", value()->default_value(""), "Network interface to bind to for ipv6") - ("nat", value()->default_value(true), "Should we assume we are behind NAT? (default: enabled)") + ("nat", bool_switch()->default_value(true), "Should we assume we are behind NAT? (default: enabled)") ("port", value()->default_value(0), "Port to listen for incoming connections (default: auto)") - ("ipv4", value()->default_value(true), "Enable communication through ipv4 (default: enabled)") + ("ipv4", bool_switch()->default_value(true), "Enable communication through ipv4 (default: enabled)") + ("address4", value()->default_value(""), "Local address to bind ipv4 transport sockets to") ("ipv6", bool_switch()->default_value(false), "Enable communication through ipv6 (default: disabled)") + ("address6", value()->default_value(""), "Local address to bind ipv6 transport sockets to") + ("reservedrange", bool_switch()->default_value(true), "Check remote RI for being in blacklist of reserved IP ranges (default: enabled)") ("netid", value()->default_value(I2PD_NET_ID), "Specify NetID. Main I2P is 2") ("daemon", bool_switch()->default_value(false), "Router will go to background after start (default: disabled)") ("service", bool_switch()->default_value(false), "Router will use system folders like '/var/lib/i2pd' (default: disabled)") ("notransit", bool_switch()->default_value(false), "Router will not accept transit tunnels at startup (default: disabled)") ("floodfill", bool_switch()->default_value(false), "Router will be floodfill (default: disabled)") - ("bandwidth", value()->default_value(""), "Bandwidth limit: integer in KBps or letters: L (32), O (256), P (2048), X (>9000)") + ("bandwidth", value()->default_value(""), "Transit traffic bandwidth limit: integer in KBps or letters: L (32), O (256), P (2048), X (>9000)") ("share", value()->default_value(100), "Limit of transit traffic from max bandwidth in percents. (default: 100)") - ("ntcp", value()->default_value(false), "Ignored. Always false") - ("ssu", value()->default_value(true), "Enable SSU transport (default: enabled)") + ("ntcp", bool_switch()->default_value(false), "Ignored. Always false") + ("ssu", bool_switch()->default_value(false), "Ignored. Always false") ("ntcpproxy", value()->default_value(""), "Ignored") #ifdef _WIN32 - ("svcctl", value()->default_value(""), "Windows service management ('install' or 'remove')") + ("svcctl", value()->default_value(""), "Ignored") ("insomnia", bool_switch()->default_value(false), "Prevent system from sleeping (default: disabled)") ("close", value()->default_value("ask"), "Action on close: minimize, exit, ask") #endif @@ -72,10 +77,11 @@ namespace config { limits.add_options() ("limits.coresize", value()->default_value(0), "Maximum size of corefile in Kb (0 - use system limit)") ("limits.openfiles", value()->default_value(0), "Maximum number of open files (0 - use system default)") - ("limits.transittunnels", value()->default_value(2500), "Maximum active transit sessions (default:2500)") - ("limits.ntcpsoft", value()->default_value(0), "Threshold to start probabilistic backoff with ntcp sessions (default: use system limit)") - ("limits.ntcphard", value()->default_value(0), "Maximum number of ntcp sessions (default: use system limit)") - ("limits.ntcpthreads", value()->default_value(1), "Maximum number of threads used by NTCP DH worker (default: 1)") + ("limits.transittunnels", value()->default_value(5000), "Maximum active transit tunnels (default:5000)") + ("limits.zombies", value()->default_value(0), "Minimum percentage of successfully created tunnels under which tunnel cleanup is paused (default [%]: 0.00)") + ("limits.ntcpsoft", value()->default_value(0), "Ignored") + ("limits.ntcphard", value()->default_value(0), "Ignored") + ("limits.ntcpthreads", value()->default_value(1), "Ignored") ; options_description httpserver("HTTP Server options"); @@ -89,6 +95,8 @@ namespace config { ("http.strictheaders", value()->default_value(true), "Enable strict host checking on WebUI") ("http.hostname", value()->default_value("localhost"), "Expected hostname for WebUI") ("http.webroot", value()->default_value("/"), "WebUI root path (default: / )") + ("http.lang", value()->default_value("english"), "WebUI language (default: english )") + ("http.showTotalTCSR", value()->default_value(false), "Show additional value with total TCSR since router's start (default: false)") ; options_description httpproxy("HTTP Proxy options"); @@ -103,12 +111,15 @@ namespace config { ("httpproxy.outbound.length", value()->default_value("3"), "HTTP proxy outbound tunnel length") ("httpproxy.inbound.quantity", value()->default_value("5"), "HTTP proxy inbound tunnels quantity") ("httpproxy.outbound.quantity", value()->default_value("5"), "HTTP proxy outbound tunnels quantity") + ("httpproxy.inbound.lengthVariance", value()->default_value("0"), "HTTP proxy inbound tunnels length variance") + ("httpproxy.outbound.lengthVariance", value()->default_value("0"), "HTTP proxy outbound tunnels length variance") ("httpproxy.latency.min", value()->default_value("0"), "HTTP proxy min latency for tunnels") ("httpproxy.latency.max", value()->default_value("0"), "HTTP proxy max latency for tunnels") ("httpproxy.outproxy", value()->default_value(""), "HTTP proxy upstream out proxy url") ("httpproxy.addresshelper", value()->default_value(true), "Enable or disable addresshelper") - ("httpproxy.i2cp.leaseSetType", value()->default_value("1"), "Local destination's LeaseSet type") - ("httpproxy.i2cp.leaseSetEncType", value()->default_value("0"), "Local destination's LeaseSet encryption type") + ("httpproxy.i2cp.leaseSetType", value()->default_value("3"), "Local destination's LeaseSet type") + ("httpproxy.i2cp.leaseSetEncType", value()->default_value("0,4"), "Local destination's LeaseSet encryption type") + ("httpproxy.i2cp.leaseSetPrivKey", value()->default_value(""), "LeaseSet private key") ; options_description socksproxy("SOCKS Proxy options"); @@ -123,20 +134,24 @@ namespace config { ("socksproxy.outbound.length", value()->default_value("3"), "SOCKS proxy outbound tunnel length") ("socksproxy.inbound.quantity", value()->default_value("5"), "SOCKS proxy inbound tunnels quantity") ("socksproxy.outbound.quantity", value()->default_value("5"), "SOCKS proxy outbound tunnels quantity") + ("socksproxy.inbound.lengthVariance", value()->default_value("0"), "SOCKS proxy inbound tunnels length variance") + ("socksproxy.outbound.lengthVariance", value()->default_value("0"), "SOCKS proxy outbound tunnels length variance") ("socksproxy.latency.min", value()->default_value("0"), "SOCKS proxy min latency for tunnels") ("socksproxy.latency.max", value()->default_value("0"), "SOCKS proxy max latency for tunnels") ("socksproxy.outproxy.enabled", value()->default_value(false), "Enable or disable SOCKS outproxy") ("socksproxy.outproxy", value()->default_value("127.0.0.1"), "Upstream outproxy address for SOCKS Proxy") ("socksproxy.outproxyport", value()->default_value(9050), "Upstream outproxy port for SOCKS Proxy") - ("socksproxy.i2cp.leaseSetType", value()->default_value("1"), "Local destination's LeaseSet type") - ("socksproxy.i2cp.leaseSetEncType", value()->default_value("0"), "Local destination's LeaseSet encryption type") + ("socksproxy.i2cp.leaseSetType", value()->default_value("3"), "Local destination's LeaseSet type") + ("socksproxy.i2cp.leaseSetEncType", value()->default_value("0,4"), "Local destination's LeaseSet encryption type") + ("socksproxy.i2cp.leaseSetPrivKey", value()->default_value(""), "LeaseSet private key") ; options_description sam("SAM bridge options"); sam.add_options() ("sam.enabled", value()->default_value(true), "Enable or disable SAM Application bridge") ("sam.address", value()->default_value("127.0.0.1"), "SAM listen address") - ("sam.port", value()->default_value(7656), "SAM listen port") + ("sam.port", value()->default_value(7656), "SAM listen TCP port") + ("sam.portudp", value()->default_value(0), "SAM listen UDP port") ("sam.singlethread", value()->default_value(true), "Sessions run in the SAM bridge's thread") ; @@ -178,7 +193,7 @@ namespace config { options_description precomputation("Precomputation options"); precomputation.add_options() ("precomputation.elgamal", -#if defined(__x86_64__) +#if (defined(_M_AMD64) || defined(__x86_64__)) value()->default_value(false), #else value()->default_value(true), @@ -195,29 +210,43 @@ namespace config { ("reseed.zipfile", value()->default_value(""), "Path to local .zip file to reseed from") ("reseed.proxy", value()->default_value(""), "url for reseed proxy, supports http/socks") ("reseed.urls", value()->default_value( - "https://reseed.i2p-projekt.de/," - "https://reseed.diva.exchange/," - "https://reseed.i2p2.no/," + "https://reseed2.i2p.net/," + "https://reseed.diva.exchange/," "https://reseed-fr.i2pd.xyz/," "https://reseed.memcpy.io/," "https://reseed.onion.im/," "https://i2pseed.creativecowpat.net:8443/," - "https://reseed.i2pgit.org/," - "https://i2p.novg.net/" + "https://reseed.i2pgit.org/," + "https://banana.incognet.io/," + "https://reseed-pl.i2pd.xyz/," + "https://www2.mk16.de/," + "https://i2p.ghativega.in/," + "https://i2p.novg.net/" ), "Reseed URLs, separated by comma") + ("reseed.yggurls", value()->default_value( + "http://[324:71e:281a:9ed3::ace]:7070/," + "http://[301:65b9:c7cd:9a36::1]:18801/," + "http://[320:8936:ec1a:31f1::216]/," + "http://[306:3834:97b9:a00a::1]/," + "http://[316:f9e0:f22e:a74f::216]/" + ), "Reseed URLs through the Yggdrasil, separated by comma") ; options_description addressbook("AddressBook options"); addressbook.add_options() + ("addressbook.enabled", value()->default_value(true), "Enable address book lookups and subscritions (default: enabled)") ("addressbook.defaulturl", value()->default_value( - "http://joajgazyztfssty4w2on5oaqksz6tqoxbduy553y34mf4byv6gpq.b32.i2p/export/alive-hosts.txt" + "http://shx5vqsw7usdaunyzr2qmes2fq37oumybpudrd4jjj4e4vk4uusa.b32.i2p/hosts.txt" ), "AddressBook subscription URL for initial setup") - ("addressbook.subscriptions", value()->default_value(""), "AddressBook subscriptions URLs, separated by comma"); + ("addressbook.subscriptions", value()->default_value( + "http://reg.i2p/hosts.txt" + ), "AddressBook subscriptions URLs, separated by comma") + ("addressbook.hostsfile", value()->default_value(""), "File to dump addresses in hosts.txt format"); options_description trust("Trust options"); trust.add_options() ("trust.enabled", value()->default_value(false), "Enable explicit trust options") - ("trust.family", value()->default_value(""), "Router Familiy to trust for first hops") + ("trust.family", value()->default_value(""), "Router Family to trust for first hops") ("trust.routers", value()->default_value(""), "Only Connect to these routers") ("trust.hidden", value()->default_value(false), "Should we hide our router from other routers?") ; @@ -243,20 +272,31 @@ namespace config { ("ntcp2.enabled", value()->default_value(true), "Enable NTCP2 (default: enabled)") ("ntcp2.published", value()->default_value(true), "Publish NTCP2 (default: enabled)") ("ntcp2.port", value()->default_value(0), "Port to listen for incoming NTCP2 connections (default: auto)") - ("ntcp2.addressv6", value()->default_value("::"), "Address to bind NTCP2 on") + ("ntcp2.addressv6", value()->default_value("::"), "Address to publish NTCP2 with") ("ntcp2.proxy", value()->default_value(""), "Proxy URL for NTCP2 transport") ; + options_description ssu2("SSU2 Options"); + ssu2.add_options() + ("ssu2.enabled", value()->default_value(true), "Enable SSU2 (default: enabled)") + ("ssu2.published", value()->default_value(true), "Publish SSU2 (default: enabled)") + ("ssu2.port", value()->default_value(0), "Port to listen for incoming SSU2 packets (default: auto)") + ("ssu2.mtu4", value()->default_value(0), "MTU for ipv4 address (default: detect)") + ("ssu2.mtu6", value()->default_value(0), "MTU for ipv6 address (default: detect)") + ("ssu2.proxy", value()->default_value(""), "Socks5 proxy URL for SSU2 transport") + ; + options_description nettime("Time sync options"); nettime.add_options() - ("nettime.enabled", value()->default_value(false), "Disable time sync (default: disabled)") + ("nettime.enabled", value()->default_value(false), "Enable NTP time sync (default: disabled)") ("nettime.ntpservers", value()->default_value( "0.pool.ntp.org," "1.pool.ntp.org," "2.pool.ntp.org," "3.pool.ntp.org" - ), "Comma separated list of NTCP servers") + ), "Comma separated list of NTP servers") ("nettime.ntpsyncinterval", value()->default_value(72), "NTP sync interval in hours (default: 72)") + ("nettime.frompeers", value()->default_value(true), "Sync clock from transport peers (default: enabled)") ; options_description persist("Network information persisting options"); @@ -265,6 +305,26 @@ namespace config { ("persist.addressbook", value()->default_value(true), "Persist full addresses (default: true)") ; + options_description cpuext("CPU encryption extensions options"); + cpuext.add_options() + ("cpuext.aesni", bool_switch()->default_value(true), "Use auto detection for AESNI CPU extensions. If false, AESNI will be not used") + ("cpuext.avx", bool_switch()->default_value(false), "Deprecated option") + ("cpuext.force", bool_switch()->default_value(false), "Force usage of CPU extensions. Useful when cpuinfo is not available on virtual machines") + ; + + options_description meshnets("Meshnet transports options"); + meshnets.add_options() + ("meshnets.yggdrasil", bool_switch()->default_value(false), "Support transports through the Yggdrasil (default: false)") + ("meshnets.yggaddress", value()->default_value(""), "Yggdrasil address to publish") + ; + +#ifdef __linux__ + options_description unix_specific("UNIX-specific options"); + unix_specific.add_options() + ("unix.handle_sigtstp", bool_switch()->default_value(false), "Handle SIGTSTP and SIGCONT signals (default: disabled)") + ; +#endif + m_OptionsDesc .add(general) .add(limits) @@ -283,8 +343,14 @@ namespace config { .add(websocket) // deprecated .add(exploratory) .add(ntcp2) + .add(ssu2) .add(nettime) .add(persist) + .add(cpuext) + .add(meshnets) +#ifdef __linux__ + .add(unix_specific) +#endif ; } @@ -293,7 +359,7 @@ namespace config { try { auto style = boost::program_options::command_line_style::unix_style - | boost::program_options::command_line_style::allow_long_disguise; + | boost::program_options::command_line_style::allow_long_disguise; style &= ~ boost::program_options::command_line_style::allow_guessing; if (ignoreUnknown) store(command_line_parser(argc, argv).options(m_OptionsDesc).style (style).allow_unregistered().run(), m_Options); @@ -302,6 +368,7 @@ namespace config { } catch (boost::program_options::error& e) { + ThrowFatal ("Error while parsing arguments: ", e.what()); std::cerr << "args: " << e.what() << std::endl; exit(EXIT_FAILURE); } @@ -339,6 +406,7 @@ namespace config { if (!config.is_open()) { + ThrowFatal ("Missing or unreadable config file: ", path); std::cerr << "missing/unreadable config file: " << path << std::endl; exit(EXIT_FAILURE); } @@ -349,6 +417,7 @@ namespace config { } catch (boost::program_options::error& e) { + ThrowFatal ("Error while parsing config file: ", e.what()); std::cerr << e.what() << std::endl; exit(EXIT_FAILURE); }; diff --git a/libi2pd/Config.h b/libi2pd/Config.h index dac5fc80..79463e65 100644 --- a/libi2pd/Config.h +++ b/libi2pd/Config.h @@ -29,16 +29,16 @@ namespace config { extern boost::program_options::variables_map m_Options; /** - * @brief Initialize list of acceptable parameters + * @brief Initialize list of acceptable parameters * * Should be called before any Parse* functions. */ void Init(); /** - * @brief Parse cmdline parameters, and show help if requested - * @param argc Cmdline arguments count, should be passed from main(). - * @param argv Cmdline parameters array, should be passed from main() + * @brief Parse cmdline parameters, and show help if requested + * @param argc Cmdline arguments count, should be passed from main(). + * @param argv Cmdline parameters array, should be passed from main() * * If --help is given in parameters, shows its list with description * and terminates the program with exitcode 0. @@ -52,8 +52,8 @@ namespace config { void ParseCmdline(int argc, char* argv[], bool ignoreUnknown = false); /** - * @brief Load and parse given config file - * @param path Path to config file + * @brief Load and parse given config file + * @param path Path to config file * * If error occurred when opening file path is points to, * we show the error message and terminate program. @@ -67,14 +67,14 @@ namespace config { void ParseConfig(const std::string& path); /** - * @brief Used to combine options from cmdline, config and default values + * @brief Used to combine options from cmdline, config and default values */ void Finalize(); /** - * @brief Accessor to parameters by name - * @param name Name of the requested parameter - * @param value Variable where to store option + * @brief Accessor to parameters by name + * @param name Name of the requested parameter + * @param value Variable where to store option * @return this function returns false if parameter not found * * Example: uint16_t port; GetOption("sam.port", port); @@ -98,9 +98,9 @@ namespace config { bool GetOptionAsAny(const std::string& name, boost::any& value); /** - * @brief Set value of given parameter - * @param name Name of settable parameter - * @param value New parameter value + * @brief Set value of given parameter + * @param name Name of settable parameter + * @param value New parameter value * @return true if value set up successful, false otherwise * * Example: uint16_t port = 2827; SetOption("bob.port", port); @@ -116,8 +116,8 @@ namespace config { } /** - * @brief Check is value explicitly given or default - * @param name Name of checked parameter + * @brief Check is value explicitly given or default + * @param name Name of checked parameter * @return true if value set to default, false otherwise */ bool IsDefault(const char *name); diff --git a/libi2pd/Crypto.cpp b/libi2pd/Crypto.cpp index 2f10e91d..b5bc33d1 100644 --- a/libi2pd/Crypto.cpp +++ b/libi2pd/Crypto.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -28,6 +28,12 @@ #include "I2PEndian.h" #include "Log.h" +#if defined(__AES__) && !defined(_MSC_VER) && ((defined(_M_AMD64) || defined(__x86_64__)) || (defined(_M_IX86) || defined(__i386__))) + #define SUPPORTS_AES 1 +#else + #define SUPPORTS_AES 0 +#endif + namespace i2p { namespace crypto @@ -119,7 +125,7 @@ namespace crypto ~CryptoConstants () { - BN_free (elgp); BN_free (elgg); BN_free (dsap); BN_free (dsaq); BN_free (dsag); BN_free (rsae); + BN_free (elgp); BN_free (elgg); BN_free (dsap); BN_free (dsaq); BN_free (dsag); BN_free (rsae); } }; @@ -159,8 +165,10 @@ namespace crypto // DH/ElGamal +#if !defined(__x86_64__) const int ELGAMAL_SHORT_EXPONENT_NUM_BITS = 226; const int ELGAMAL_SHORT_EXPONENT_NUM_BYTES = ELGAMAL_SHORT_EXPONENT_NUM_BITS/8+1; +#endif const int ELGAMAL_FULL_EXPONENT_NUM_BITS = 2048; const int ELGAMAL_FULL_EXPONENT_NUM_BYTES = ELGAMAL_FULL_EXPONENT_NUM_BITS/8; @@ -239,55 +247,6 @@ namespace crypto static BIGNUM * (* g_ElggTable)[255] = nullptr; -// DH - - DHKeys::DHKeys () - { - m_DH = DH_new (); - DH_set0_pqg (m_DH, BN_dup (elgp), NULL, BN_dup (elgg)); - DH_set0_key (m_DH, NULL, NULL); - } - - DHKeys::~DHKeys () - { - DH_free (m_DH); - } - - void DHKeys::GenerateKeys () - { - BIGNUM * priv_key = NULL, * pub_key = NULL; -#if !defined(__x86_64__) // use short exponent for non x64 - priv_key = BN_new (); - BN_rand (priv_key, ELGAMAL_SHORT_EXPONENT_NUM_BITS, 0, 1); -#endif - if (g_ElggTable) - { -#if defined(__x86_64__) - priv_key = BN_new (); - BN_rand (priv_key, ELGAMAL_FULL_EXPONENT_NUM_BITS, 0, 1); -#endif - auto ctx = BN_CTX_new (); - pub_key = ElggPow (priv_key, g_ElggTable, ctx); - DH_set0_key (m_DH, pub_key, priv_key); - BN_CTX_free (ctx); - } - else - { - DH_set0_key (m_DH, NULL, priv_key); - DH_generate_key (m_DH); - DH_get0_key (m_DH, (const BIGNUM **)&pub_key, (const BIGNUM **)&priv_key); - } - - bn2buf (pub_key, m_PublicKey, 256); - } - - void DHKeys::Agree (const uint8_t * pub, uint8_t * shared) - { - BIGNUM * pk = BN_bin2bn (pub, 256, NULL); - DH_compute_key (shared, pk, m_DH); - BN_free (pk); - } - // x25519 X25519Keys::X25519Keys () { @@ -351,11 +310,13 @@ namespace crypto #endif } - void X25519Keys::Agree (const uint8_t * pub, uint8_t * shared) + bool X25519Keys::Agree (const uint8_t * pub, uint8_t * shared) { + if (!pub || (pub[31] & 0x80)) return false; // not x25519 key #if OPENSSL_X25519 EVP_PKEY_derive_init (m_Ctx); auto pkey = EVP_PKEY_new_raw_public_key (EVP_PKEY_X25519, NULL, pub, 32); + if (!pkey) return false; EVP_PKEY_derive_set_peer (m_Ctx, pkey); size_t len = 32; EVP_PKEY_derive (m_Ctx, shared, &len); @@ -363,6 +324,7 @@ namespace crypto #else GetEd25519 ()->ScalarMul (pub, m_PrivateKey, shared, m_Ctx); #endif + return true; } void X25519Keys::GetPrivateKey (uint8_t * priv) const @@ -386,7 +348,7 @@ namespace crypto { size_t len = 32; EVP_PKEY_get_raw_public_key (m_Pkey, m_PublicKey, &len); - } + } #else memcpy (m_PrivateKey, priv, 32); if (calculatePublic) @@ -395,8 +357,9 @@ namespace crypto } // ElGamal - void ElGamalEncrypt (const uint8_t * key, const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding) + void ElGamalEncrypt (const uint8_t * key, const uint8_t * data, uint8_t * encrypted) { + BN_CTX * ctx = BN_CTX_new (); BN_CTX_start (ctx); // everything, but a, because a might come from table BIGNUM * k = BN_CTX_get (ctx); @@ -404,7 +367,7 @@ namespace crypto BIGNUM * b1 = BN_CTX_get (ctx); BIGNUM * b = BN_CTX_get (ctx); // select random k -#if defined(__x86_64__) +#if (defined(_M_AMD64) || defined(__x86_64__)) BN_rand (k, ELGAMAL_FULL_EXPONENT_NUM_BITS, -1, 1); // full exponent for x64 #else BN_rand (k, ELGAMAL_SHORT_EXPONENT_NUM_BITS, -1, 1); // short exponent of 226 bits @@ -432,37 +395,32 @@ namespace crypto BN_bin2bn (m, 255, b); BN_mod_mul (b, b1, b, elgp, ctx); // copy a and b - if (zeroPadding) - { - encrypted[0] = 0; - bn2buf (a, encrypted + 1, 256); - encrypted[257] = 0; - bn2buf (b, encrypted + 258, 256); - } - else - { - bn2buf (a, encrypted, 256); - bn2buf (b, encrypted + 256, 256); - } + encrypted[0] = 0; + bn2buf (a, encrypted + 1, 256); + encrypted[257] = 0; + bn2buf (b, encrypted + 258, 256); + BN_free (a); BN_CTX_end (ctx); + BN_CTX_free (ctx); } - bool ElGamalDecrypt (const uint8_t * key, const uint8_t * encrypted, - uint8_t * data, BN_CTX * ctx, bool zeroPadding) + bool ElGamalDecrypt (const uint8_t * key, const uint8_t * encrypted, uint8_t * data) { + BN_CTX * ctx = BN_CTX_new (); BN_CTX_start (ctx); BIGNUM * x = BN_CTX_get (ctx), * a = BN_CTX_get (ctx), * b = BN_CTX_get (ctx); BN_bin2bn (key, 256, x); BN_sub (x, elgp, x); BN_sub_word (x, 1); // x = elgp - x- 1 - BN_bin2bn (zeroPadding ? encrypted + 1 : encrypted, 256, a); - BN_bin2bn (zeroPadding ? encrypted + 258 : encrypted + 256, 256, b); + BN_bin2bn (encrypted + 1, 256, a); + BN_bin2bn (encrypted + 258, 256, b); // m = b*(a^x mod p) mod p BN_mod_exp (x, a, x, elgp, ctx); BN_mod_mul (b, b, x, elgp, ctx); uint8_t m[255]; bn2buf (b, m, 255); BN_CTX_end (ctx); + BN_CTX_free (ctx); uint8_t hash[32]; SHA256 (m + 33, 222, hash); if (memcmp (m + 1, hash, 32)) @@ -476,7 +434,7 @@ namespace crypto void GenerateElGamalKeyPair (uint8_t * priv, uint8_t * pub) { -#if defined(__x86_64__) || defined(__i386__) || defined(_MSC_VER) +#if (defined(_M_AMD64) || defined(__x86_64__)) || (defined(_M_IX86) || defined(__i386__)) || defined(_MSC_VER) RAND_bytes (priv, 256); #else // lower 226 bits (28 bytes and 2 bits) only. short exponent @@ -496,8 +454,9 @@ namespace crypto } // ECIES - void ECIESEncrypt (const EC_GROUP * curve, const EC_POINT * key, const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding) + void ECIESEncrypt (const EC_GROUP * curve, const EC_POINT * key, const uint8_t * data, uint8_t * encrypted) { + BN_CTX * ctx = BN_CTX_new (); BN_CTX_start (ctx); BIGNUM * q = BN_CTX_get (ctx); EC_GROUP_get_order(curve, q, ctx); @@ -509,20 +468,11 @@ namespace crypto EC_POINT_mul (curve, p, k, nullptr, nullptr, ctx); BIGNUM * x = BN_CTX_get (ctx), * y = BN_CTX_get (ctx); EC_POINT_get_affine_coordinates_GFp (curve, p, x, y, nullptr); - if (zeroPadding) - { - encrypted[0] = 0; - bn2buf (x, encrypted + 1, len); - bn2buf (y, encrypted + 1 + len, len); - RAND_bytes (encrypted + 1 + 2*len, 256 - 2*len); - } - else - { - bn2buf (x, encrypted, len); - bn2buf (y, encrypted + len, len); - RAND_bytes (encrypted + 2*len, 256 - 2*len); - } - // ecryption key and iv + encrypted[0] = 0; + bn2buf (x, encrypted + 1, len); + bn2buf (y, encrypted + 1 + len, len); + RAND_bytes (encrypted + 1 + 2*len, 256 - 2*len); + // encryption key and iv EC_POINT_mul (curve, p, nullptr, key, k, ctx); EC_POINT_get_affine_coordinates_GFp (curve, p, x, y, nullptr); uint8_t keyBuf[64], iv[64], shared[32]; @@ -538,36 +488,25 @@ namespace crypto CBCEncryption encryption; encryption.SetKey (shared); encryption.SetIV (iv); - if (zeroPadding) - { - encrypted[257] = 0; - encryption.Encrypt (m, 256, encrypted + 258); - } - else - encryption.Encrypt (m, 256, encrypted + 256); + encrypted[257] = 0; + encryption.Encrypt (m, 256, encrypted + 258); EC_POINT_free (p); BN_CTX_end (ctx); + BN_CTX_free (ctx); } - bool ECIESDecrypt (const EC_GROUP * curve, const BIGNUM * key, const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding) + bool ECIESDecrypt (const EC_GROUP * curve, const BIGNUM * key, const uint8_t * encrypted, uint8_t * data) { bool ret = true; + BN_CTX * ctx = BN_CTX_new (); BN_CTX_start (ctx); BIGNUM * q = BN_CTX_get (ctx); EC_GROUP_get_order(curve, q, ctx); int len = BN_num_bytes (q); // point for shared secret BIGNUM * x = BN_CTX_get (ctx), * y = BN_CTX_get (ctx); - if (zeroPadding) - { - BN_bin2bn (encrypted + 1, len, x); - BN_bin2bn (encrypted + 1 + len, len, y); - } - else - { - BN_bin2bn (encrypted, len, x); - BN_bin2bn (encrypted + len, len, y); - } + BN_bin2bn (encrypted + 1, len, x); + BN_bin2bn (encrypted + 1 + len, len, y); auto p = EC_POINT_new (curve); if (EC_POINT_set_affine_coordinates_GFp (curve, p, x, y, nullptr)) { @@ -584,10 +523,7 @@ namespace crypto CBCDecryption decryption; decryption.SetKey (shared); decryption.SetIV (iv); - if (zeroPadding) - decryption.Decrypt (encrypted + 258, 256, m); - else - decryption.Decrypt (encrypted + 256, 256, m); + decryption.Decrypt (encrypted + 258, 256, m); // verify and copy uint8_t hash[32]; SHA256 (m + 33, 222, hash); @@ -607,6 +543,7 @@ namespace crypto EC_POINT_free (p); BN_CTX_end (ctx); + BN_CTX_free (ctx); return ret; } @@ -623,111 +560,33 @@ namespace crypto BN_CTX_free (ctx); } -// HMAC - const uint64_t IPAD = 0x3636363636363636; - const uint64_t OPAD = 0x5C5C5C5C5C5C5C5C; - - - static const uint64_t ipads[] = { IPAD, IPAD, IPAD, IPAD }; - static const uint64_t opads[] = { OPAD, OPAD, OPAD, OPAD }; - - void HMACMD5Digest (uint8_t * msg, size_t len, const MACKey& key, uint8_t * digest) - // key is 32 bytes - // digest is 16 bytes - // block size is 64 bytes - { - uint64_t buf[256]; - uint64_t hash[12]; // 96 bytes -#ifdef __AVX__ - if(i2p::cpu::avx) - { - __asm__ - ( - "vmovups %[key], %%ymm0 \n" - "vmovups %[ipad], %%ymm1 \n" - "vmovups %%ymm1, 32(%[buf]) \n" - "vxorps %%ymm0, %%ymm1, %%ymm1 \n" - "vmovups %%ymm1, (%[buf]) \n" - "vmovups %[opad], %%ymm1 \n" - "vmovups %%ymm1, 32(%[hash]) \n" - "vxorps %%ymm0, %%ymm1, %%ymm1 \n" - "vmovups %%ymm1, (%[hash]) \n" - "vzeroall \n" // end of AVX - "movups %%xmm0, 80(%[hash]) \n" // zero last 16 bytes - : - : [key]"m"(*(const uint8_t *)key), [ipad]"m"(*ipads), [opad]"m"(*opads), - [buf]"r"(buf), [hash]"r"(hash) - : "memory", "%xmm0" // TODO: change to %ymm0 later - ); - } - else -#endif - { - // ikeypad - buf[0] = key.GetLL ()[0] ^ IPAD; - buf[1] = key.GetLL ()[1] ^ IPAD; - buf[2] = key.GetLL ()[2] ^ IPAD; - buf[3] = key.GetLL ()[3] ^ IPAD; - buf[4] = IPAD; - buf[5] = IPAD; - buf[6] = IPAD; - buf[7] = IPAD; - // okeypad - hash[0] = key.GetLL ()[0] ^ OPAD; - hash[1] = key.GetLL ()[1] ^ OPAD; - hash[2] = key.GetLL ()[2] ^ OPAD; - hash[3] = key.GetLL ()[3] ^ OPAD; - hash[4] = OPAD; - hash[5] = OPAD; - hash[6] = OPAD; - hash[7] = OPAD; - // fill last 16 bytes with zeros (first hash size assumed 32 bytes in I2P) - memset (hash + 10, 0, 16); - } - - // concatenate with msg - memcpy (buf + 8, msg, len); - // calculate first hash - MD5((uint8_t *)buf, len + 64, (uint8_t *)(hash + 8)); // 16 bytes - - // calculate digest - MD5((uint8_t *)hash, 96, digest); - } - // AES -#ifdef __AES__ - #ifdef ARM64AES - void init_aesenc(void){ - // TODO: Implementation - } - - #endif - +#if SUPPORTS_AES #define KeyExpansion256(round0,round1) \ - "pshufd $0xff, %%xmm2, %%xmm2 \n" \ - "movaps %%xmm1, %%xmm4 \n" \ - "pslldq $4, %%xmm4 \n" \ + "pshufd $0xff, %%xmm2, %%xmm2 \n" \ + "movaps %%xmm1, %%xmm4 \n" \ + "pslldq $4, %%xmm4 \n" \ "pxor %%xmm4, %%xmm1 \n" \ - "pslldq $4, %%xmm4 \n" \ + "pslldq $4, %%xmm4 \n" \ "pxor %%xmm4, %%xmm1 \n" \ - "pslldq $4, %%xmm4 \n" \ + "pslldq $4, %%xmm4 \n" \ "pxor %%xmm4, %%xmm1 \n" \ "pxor %%xmm2, %%xmm1 \n" \ - "movaps %%xmm1, "#round0"(%[sched]) \n" \ + "movaps %%xmm1, "#round0"(%[sched]) \n" \ "aeskeygenassist $0, %%xmm1, %%xmm4 \n" \ - "pshufd $0xaa, %%xmm4, %%xmm2 \n" \ - "movaps %%xmm3, %%xmm4 \n" \ - "pslldq $4, %%xmm4 \n" \ + "pshufd $0xaa, %%xmm4, %%xmm2 \n" \ + "movaps %%xmm3, %%xmm4 \n" \ + "pslldq $4, %%xmm4 \n" \ "pxor %%xmm4, %%xmm3 \n" \ - "pslldq $4, %%xmm4 \n" \ + "pslldq $4, %%xmm4 \n" \ "pxor %%xmm4, %%xmm3 \n" \ - "pslldq $4, %%xmm4 \n" \ + "pslldq $4, %%xmm4 \n" \ "pxor %%xmm4, %%xmm3 \n" \ "pxor %%xmm2, %%xmm3 \n" \ - "movaps %%xmm3, "#round1"(%[sched]) \n" + "movaps %%xmm3, "#round1"(%[sched]) \n" #endif -#ifdef __AES__ +#if SUPPORTS_AES void ECBCryptoAESNI::ExpandKey (const AESKey& key) { __asm__ @@ -750,16 +609,16 @@ namespace crypto KeyExpansion256(192,208) "aeskeygenassist $64, %%xmm3, %%xmm2 \n" // key expansion final - "pshufd $0xff, %%xmm2, %%xmm2 \n" - "movaps %%xmm1, %%xmm4 \n" - "pslldq $4, %%xmm4 \n" + "pshufd $0xff, %%xmm2, %%xmm2 \n" + "movaps %%xmm1, %%xmm4 \n" + "pslldq $4, %%xmm4 \n" "pxor %%xmm4, %%xmm1 \n" - "pslldq $4, %%xmm4 \n" + "pslldq $4, %%xmm4 \n" "pxor %%xmm4, %%xmm1 \n" - "pslldq $4, %%xmm4 \n" + "pslldq $4, %%xmm4 \n" "pxor %%xmm4, %%xmm1 \n" "pxor %%xmm2, %%xmm1 \n" - "movups %%xmm1, 224(%[sched]) \n" + "movups %%xmm1, 224(%[sched]) \n" : // output : [key]"r"((const uint8_t *)key), [sched]"r"(GetKeySchedule ()) // input : "%xmm1", "%xmm2", "%xmm3", "%xmm4", "memory" // clogged @@ -768,7 +627,7 @@ namespace crypto #endif -#ifdef __AES__ +#if SUPPORTS_AES #define EncryptAES256(sched) \ "pxor (%["#sched"]), %%xmm0 \n" \ "aesenc 16(%["#sched"]), %%xmm0 \n" \ @@ -789,16 +648,18 @@ namespace crypto void ECBEncryption::Encrypt (const ChipherBlock * in, ChipherBlock * out) { -#ifdef __AES__ +#if SUPPORTS_AES if(i2p::cpu::aesni) { __asm__ - ( - "movups (%[in]), %%xmm0 \n" - EncryptAES256(sched) - "movups %%xmm0, (%[out]) \n" - : : [sched]"r"(GetKeySchedule ()), [in]"r"(in), [out]"r"(out) : "%xmm0", "memory" - ); + ( + "movups (%[in]), %%xmm0 \n" + EncryptAES256(sched) + "movups %%xmm0, (%[out]) \n" + : + : [sched]"r"(GetKeySchedule ()), [in]"r"(in), [out]"r"(out) + : "%xmm0", "memory" + ); } else #endif @@ -807,7 +668,7 @@ namespace crypto } } -#ifdef __AES__ +#if SUPPORTS_AES #define DecryptAES256(sched) \ "pxor 224(%["#sched"]), %%xmm0 \n" \ "aesdec 208(%["#sched"]), %%xmm0 \n" \ @@ -828,16 +689,18 @@ namespace crypto void ECBDecryption::Decrypt (const ChipherBlock * in, ChipherBlock * out) { -#ifdef __AES__ +#if SUPPORTS_AES if(i2p::cpu::aesni) { __asm__ - ( - "movups (%[in]), %%xmm0 \n" - DecryptAES256(sched) - "movups %%xmm0, (%[out]) \n" - : : [sched]"r"(GetKeySchedule ()), [in]"r"(in), [out]"r"(out) : "%xmm0", "memory" - ); + ( + "movups (%[in]), %%xmm0 \n" + DecryptAES256(sched) + "movups %%xmm0, (%[out]) \n" + : + : [sched]"r"(GetKeySchedule ()), [in]"r"(in), [out]"r"(out) + : "%xmm0", "memory" + ); } else #endif @@ -846,16 +709,16 @@ namespace crypto } } -#ifdef __AES__ +#if SUPPORTS_AES #define CallAESIMC(offset) \ - "movaps "#offset"(%[shed]), %%xmm0 \n" \ + "movaps "#offset"(%[shed]), %%xmm0 \n" \ "aesimc %%xmm0, %%xmm0 \n" \ "movaps %%xmm0, "#offset"(%[shed]) \n" #endif void ECBEncryption::SetKey (const AESKey& key) { -#ifdef __AES__ +#if SUPPORTS_AES if(i2p::cpu::aesni) { ExpandKey (key); @@ -869,28 +732,30 @@ namespace crypto void ECBDecryption::SetKey (const AESKey& key) { -#ifdef __AES__ +#if SUPPORTS_AES if(i2p::cpu::aesni) { ExpandKey (key); // expand encryption key first - // then invert it using aesimc + // then invert it using aesimc __asm__ - ( - CallAESIMC(16) - CallAESIMC(32) - CallAESIMC(48) - CallAESIMC(64) - CallAESIMC(80) - CallAESIMC(96) - CallAESIMC(112) - CallAESIMC(128) - CallAESIMC(144) - CallAESIMC(160) - CallAESIMC(176) - CallAESIMC(192) - CallAESIMC(208) - : : [shed]"r"(GetKeySchedule ()) : "%xmm0", "memory" - ); + ( + CallAESIMC(16) + CallAESIMC(32) + CallAESIMC(48) + CallAESIMC(64) + CallAESIMC(80) + CallAESIMC(96) + CallAESIMC(112) + CallAESIMC(128) + CallAESIMC(144) + CallAESIMC(160) + CallAESIMC(176) + CallAESIMC(192) + CallAESIMC(208) + : + : [shed]"r"(GetKeySchedule ()) + : "%xmm0", "memory" + ); } else #endif @@ -901,28 +766,28 @@ namespace crypto void CBCEncryption::Encrypt (int numBlocks, const ChipherBlock * in, ChipherBlock * out) { -#ifdef __AES__ +#if SUPPORTS_AES if(i2p::cpu::aesni) { __asm__ - ( - "movups (%[iv]), %%xmm1 \n" - "1: \n" - "movups (%[in]), %%xmm0 \n" - "pxor %%xmm1, %%xmm0 \n" - EncryptAES256(sched) - "movaps %%xmm0, %%xmm1 \n" - "movups %%xmm0, (%[out]) \n" - "add $16, %[in] \n" - "add $16, %[out] \n" - "dec %[num] \n" - "jnz 1b \n" - "movups %%xmm1, (%[iv]) \n" - : - : [iv]"r"((uint8_t *)m_LastBlock), [sched]"r"(m_ECBEncryption.GetKeySchedule ()), - [in]"r"(in), [out]"r"(out), [num]"r"(numBlocks) - : "%xmm0", "%xmm1", "cc", "memory" - ); + ( + "movups (%[iv]), %%xmm1 \n" + "1: \n" + "movups (%[in]), %%xmm0 \n" + "pxor %%xmm1, %%xmm0 \n" + EncryptAES256(sched) + "movaps %%xmm0, %%xmm1 \n" + "movups %%xmm0, (%[out]) \n" + "add $16, %[in] \n" + "add $16, %[out] \n" + "dec %[num] \n" + "jnz 1b \n" + "movups %%xmm1, (%[iv]) \n" + : + : [iv]"r"((uint8_t *)m_LastBlock), [sched]"r"(m_ECBEncryption.GetKeySchedule ()), + [in]"r"(in), [out]"r"(out), [num]"r"(numBlocks) + : "%xmm0", "%xmm1", "cc", "memory" + ); } else #endif @@ -946,22 +811,22 @@ namespace crypto void CBCEncryption::Encrypt (const uint8_t * in, uint8_t * out) { -#ifdef __AES__ +#if SUPPORTS_AES if(i2p::cpu::aesni) { __asm__ - ( - "movups (%[iv]), %%xmm1 \n" - "movups (%[in]), %%xmm0 \n" - "pxor %%xmm1, %%xmm0 \n" - EncryptAES256(sched) - "movups %%xmm0, (%[out]) \n" - "movups %%xmm0, (%[iv]) \n" - : - : [iv]"r"((uint8_t *)m_LastBlock), [sched]"r"(m_ECBEncryption.GetKeySchedule ()), - [in]"r"(in), [out]"r"(out) - : "%xmm0", "%xmm1", "memory" - ); + ( + "movups (%[iv]), %%xmm1 \n" + "movups (%[in]), %%xmm0 \n" + "pxor %%xmm1, %%xmm0 \n" + EncryptAES256(sched) + "movups %%xmm0, (%[out]) \n" + "movups %%xmm0, (%[iv]) \n" + : + : [iv]"r"((uint8_t *)m_LastBlock), [sched]"r"(m_ECBEncryption.GetKeySchedule ()), + [in]"r"(in), [out]"r"(out) + : "%xmm0", "%xmm1", "memory" + ); } else #endif @@ -970,29 +835,29 @@ namespace crypto void CBCDecryption::Decrypt (int numBlocks, const ChipherBlock * in, ChipherBlock * out) { -#ifdef __AES__ +#if SUPPORTS_AES if(i2p::cpu::aesni) { __asm__ - ( - "movups (%[iv]), %%xmm1 \n" - "1: \n" - "movups (%[in]), %%xmm0 \n" - "movaps %%xmm0, %%xmm2 \n" - DecryptAES256(sched) - "pxor %%xmm1, %%xmm0 \n" - "movups %%xmm0, (%[out]) \n" - "movaps %%xmm2, %%xmm1 \n" - "add $16, %[in] \n" - "add $16, %[out] \n" - "dec %[num] \n" - "jnz 1b \n" - "movups %%xmm1, (%[iv]) \n" - : - : [iv]"r"((uint8_t *)m_IV), [sched]"r"(m_ECBDecryption.GetKeySchedule ()), - [in]"r"(in), [out]"r"(out), [num]"r"(numBlocks) - : "%xmm0", "%xmm1", "%xmm2", "cc", "memory" - ); + ( + "movups (%[iv]), %%xmm1 \n" + "1: \n" + "movups (%[in]), %%xmm0 \n" + "movaps %%xmm0, %%xmm2 \n" + DecryptAES256(sched) + "pxor %%xmm1, %%xmm0 \n" + "movups %%xmm0, (%[out]) \n" + "movaps %%xmm2, %%xmm1 \n" + "add $16, %[in] \n" + "add $16, %[out] \n" + "dec %[num] \n" + "jnz 1b \n" + "movups %%xmm1, (%[iv]) \n" + : + : [iv]"r"((uint8_t *)m_IV), [sched]"r"(m_ECBDecryption.GetKeySchedule ()), + [in]"r"(in), [out]"r"(out), [num]"r"(numBlocks) + : "%xmm0", "%xmm1", "%xmm2", "cc", "memory" + ); } else #endif @@ -1016,22 +881,22 @@ namespace crypto void CBCDecryption::Decrypt (const uint8_t * in, uint8_t * out) { -#ifdef __AES__ +#if SUPPORTS_AES if(i2p::cpu::aesni) { __asm__ - ( - "movups (%[iv]), %%xmm1 \n" - "movups (%[in]), %%xmm0 \n" - "movups %%xmm0, (%[iv]) \n" - DecryptAES256(sched) - "pxor %%xmm1, %%xmm0 \n" - "movups %%xmm0, (%[out]) \n" - : - : [iv]"r"((uint8_t *)m_IV), [sched]"r"(m_ECBDecryption.GetKeySchedule ()), - [in]"r"(in), [out]"r"(out) - : "%xmm0", "%xmm1", "memory" - ); + ( + "movups (%[iv]), %%xmm1 \n" + "movups (%[in]), %%xmm0 \n" + "movups %%xmm0, (%[iv]) \n" + DecryptAES256(sched) + "pxor %%xmm1, %%xmm0 \n" + "movups %%xmm0, (%[out]) \n" + : + : [iv]"r"((uint8_t *)m_IV), [sched]"r"(m_ECBDecryption.GetKeySchedule ()), + [in]"r"(in), [out]"r"(out) + : "%xmm0", "%xmm1", "memory" + ); } else #endif @@ -1040,34 +905,34 @@ namespace crypto void TunnelEncryption::Encrypt (const uint8_t * in, uint8_t * out) { -#ifdef __AES__ +#if SUPPORTS_AES if(i2p::cpu::aesni) { __asm__ - ( - // encrypt IV - "movups (%[in]), %%xmm0 \n" - EncryptAES256(sched_iv) - "movaps %%xmm0, %%xmm1 \n" - // double IV encryption - EncryptAES256(sched_iv) - "movups %%xmm0, (%[out]) \n" - // encrypt data, IV is xmm1 - "1: \n" - "add $16, %[in] \n" - "add $16, %[out] \n" - "movups (%[in]), %%xmm0 \n" - "pxor %%xmm1, %%xmm0 \n" - EncryptAES256(sched_l) - "movaps %%xmm0, %%xmm1 \n" - "movups %%xmm0, (%[out]) \n" - "dec %[num] \n" - "jnz 1b \n" - : - : [sched_iv]"r"(m_IVEncryption.GetKeySchedule ()), [sched_l]"r"(m_LayerEncryption.ECB().GetKeySchedule ()), - [in]"r"(in), [out]"r"(out), [num]"r"(63) // 63 blocks = 1008 bytes - : "%xmm0", "%xmm1", "cc", "memory" - ); + ( + // encrypt IV + "movups (%[in]), %%xmm0 \n" + EncryptAES256(sched_iv) + "movaps %%xmm0, %%xmm1 \n" + // double IV encryption + EncryptAES256(sched_iv) + "movups %%xmm0, (%[out]) \n" + // encrypt data, IV is xmm1 + "1: \n" + "add $16, %[in] \n" + "add $16, %[out] \n" + "movups (%[in]), %%xmm0 \n" + "pxor %%xmm1, %%xmm0 \n" + EncryptAES256(sched_l) + "movaps %%xmm0, %%xmm1 \n" + "movups %%xmm0, (%[out]) \n" + "dec %[num] \n" + "jnz 1b \n" + : + : [sched_iv]"r"(m_IVEncryption.GetKeySchedule ()), [sched_l]"r"(m_LayerEncryption.ECB().GetKeySchedule ()), + [in]"r"(in), [out]"r"(out), [num]"r"(63) // 63 blocks = 1008 bytes + : "%xmm0", "%xmm1", "cc", "memory" + ); } else #endif @@ -1081,35 +946,35 @@ namespace crypto void TunnelDecryption::Decrypt (const uint8_t * in, uint8_t * out) { -#ifdef __AES__ +#if SUPPORTS_AES if(i2p::cpu::aesni) { __asm__ - ( - // decrypt IV - "movups (%[in]), %%xmm0 \n" - DecryptAES256(sched_iv) - "movaps %%xmm0, %%xmm1 \n" - // double IV encryption - DecryptAES256(sched_iv) - "movups %%xmm0, (%[out]) \n" - // decrypt data, IV is xmm1 - "1: \n" - "add $16, %[in] \n" - "add $16, %[out] \n" - "movups (%[in]), %%xmm0 \n" - "movaps %%xmm0, %%xmm2 \n" - DecryptAES256(sched_l) - "pxor %%xmm1, %%xmm0 \n" - "movups %%xmm0, (%[out]) \n" - "movaps %%xmm2, %%xmm1 \n" - "dec %[num] \n" - "jnz 1b \n" - : - : [sched_iv]"r"(m_IVDecryption.GetKeySchedule ()), [sched_l]"r"(m_LayerDecryption.ECB().GetKeySchedule ()), - [in]"r"(in), [out]"r"(out), [num]"r"(63) // 63 blocks = 1008 bytes - : "%xmm0", "%xmm1", "%xmm2", "cc", "memory" - ); + ( + // decrypt IV + "movups (%[in]), %%xmm0 \n" + DecryptAES256(sched_iv) + "movaps %%xmm0, %%xmm1 \n" + // double IV encryption + DecryptAES256(sched_iv) + "movups %%xmm0, (%[out]) \n" + // decrypt data, IV is xmm1 + "1: \n" + "add $16, %[in] \n" + "add $16, %[out] \n" + "movups (%[in]), %%xmm0 \n" + "movaps %%xmm0, %%xmm2 \n" + DecryptAES256(sched_l) + "pxor %%xmm1, %%xmm0 \n" + "movups %%xmm0, (%[out]) \n" + "movaps %%xmm2, %%xmm1 \n" + "dec %[num] \n" + "jnz 1b \n" + : + : [sched_iv]"r"(m_IVDecryption.GetKeySchedule ()), [sched_l]"r"(m_LayerDecryption.ECB().GetKeySchedule ()), + [in]"r"(in), [out]"r"(out), [num]"r"(63) // 63 blocks = 1008 bytes + : "%xmm0", "%xmm1", "%xmm2", "cc", "memory" + ); } else #endif @@ -1138,7 +1003,7 @@ namespace crypto EVP_EncryptInit_ex(ctx, NULL, NULL, key, nonce); EVP_EncryptUpdate(ctx, NULL, &outlen, ad, adLen); EVP_EncryptUpdate(ctx, buf, &outlen, msg, msgLen); - EVP_EncryptFinal_ex(ctx, buf, &outlen); + EVP_EncryptFinal_ex(ctx, buf + outlen, &outlen); EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, 16, buf + msgLen); } else @@ -1306,7 +1171,7 @@ namespace crypto EVP_PKEY_CTX_set1_hkdf_key (pctx, tempKey, len); } if (info.length () > 0) - EVP_PKEY_CTX_add1_hkdf_info (pctx, info.c_str (), info.length ()); + EVP_PKEY_CTX_add1_hkdf_info (pctx, (const uint8_t *)info.c_str (), info.length ()); EVP_PKEY_derive (pctx, out, &outLen); EVP_PKEY_CTX_free (pctx); #else @@ -1323,9 +1188,104 @@ namespace crypto #endif } +// Noise + + void NoiseSymmetricState::MixHash (const uint8_t * buf, size_t len) + { + SHA256_CTX ctx; + SHA256_Init (&ctx); + SHA256_Update (&ctx, m_H, 32); + SHA256_Update (&ctx, buf, len); + SHA256_Final (m_H, &ctx); + } + + void NoiseSymmetricState::MixHash (const std::vector >& bufs) + { + SHA256_CTX ctx; + SHA256_Init (&ctx); + SHA256_Update (&ctx, m_H, 32); + for (const auto& it: bufs) + SHA256_Update (&ctx, it.first, it.second); + SHA256_Final (m_H, &ctx); + } + + void NoiseSymmetricState::MixKey (const uint8_t * sharedSecret) + { + HKDF (m_CK, sharedSecret, 32, "", m_CK); + // new ck is m_CK[0:31], key is m_CK[32:63] + } + + static void InitNoiseState (NoiseSymmetricState& state, const uint8_t * ck, + const uint8_t * hh, const uint8_t * pub) + { + // pub is Bob's public static key, hh = SHA256(h) + memcpy (state.m_CK, ck, 32); + SHA256_CTX ctx; + SHA256_Init (&ctx); + SHA256_Update (&ctx, hh, 32); + SHA256_Update (&ctx, pub, 32); + SHA256_Final (state.m_H, &ctx); // h = MixHash(pub) = SHA256(hh || pub) + } + + void InitNoiseNState (NoiseSymmetricState& state, const uint8_t * pub) + { + static const char protocolName[] = "Noise_N_25519_ChaChaPoly_SHA256"; // 31 chars + static const uint8_t hh[32] = + { + 0x69, 0x4d, 0x52, 0x44, 0x5a, 0x27, 0xd9, 0xad, 0xfa, 0xd2, 0x9c, 0x76, 0x32, 0x39, 0x5d, 0xc1, + 0xe4, 0x35, 0x4c, 0x69, 0xb4, 0xf9, 0x2e, 0xac, 0x8a, 0x1e, 0xe4, 0x6a, 0x9e, 0xd2, 0x15, 0x54 + }; // hh = SHA256(protocol_name || 0) + InitNoiseState (state, (const uint8_t *)protocolName, hh, pub); // ck = protocol_name || 0 + } + + void InitNoiseXKState (NoiseSymmetricState& state, const uint8_t * pub) + { + static const uint8_t protocolNameHash[32] = + { + 0x72, 0xe8, 0x42, 0xc5, 0x45, 0xe1, 0x80, 0x80, 0xd3, 0x9c, 0x44, 0x93, 0xbb, 0x91, 0xd7, 0xed, + 0xf2, 0x28, 0x98, 0x17, 0x71, 0x21, 0x8c, 0x1f, 0x62, 0x4e, 0x20, 0x6f, 0x28, 0xd3, 0x2f, 0x71 + }; // SHA256 ("Noise_XKaesobfse+hs2+hs3_25519_ChaChaPoly_SHA256") + static const uint8_t hh[32] = + { + 0x49, 0xff, 0x48, 0x3f, 0xc4, 0x04, 0xb9, 0xb2, 0x6b, 0x11, 0x94, 0x36, 0x72, 0xff, 0x05, 0xb5, + 0x61, 0x27, 0x03, 0x31, 0xba, 0x89, 0xb8, 0xfc, 0x33, 0x15, 0x93, 0x87, 0x57, 0xdd, 0x3d, 0x1e + }; // SHA256 (protocolNameHash) + InitNoiseState (state, protocolNameHash, hh, pub); + } + + void InitNoiseXKState1 (NoiseSymmetricState& state, const uint8_t * pub) + { + static const uint8_t protocolNameHash[32] = + { + 0xb1, 0x37, 0x22, 0x81, 0x74, 0x23, 0xa8, 0xfd, 0xf4, 0x2d, 0xf2, 0xe6, 0x0e, 0xd1, 0xed, 0xf4, + 0x1b, 0x93, 0x07, 0x1d, 0xb1, 0xec, 0x24, 0xa3, 0x67, 0xf7, 0x84, 0xec, 0x27, 0x0d, 0x81, 0x32 + }; // SHA256 ("Noise_XKchaobfse+hs1+hs2+hs3_25519_ChaChaPoly_SHA256") + static const uint8_t hh[32] = + { + 0xdc, 0x85, 0xe6, 0xaf, 0x7b, 0x02, 0x65, 0x0c, 0xf1, 0xf9, 0x0d, 0x71, 0xfb, 0xc6, 0xd4, 0x53, + 0xa7, 0xcf, 0x6d, 0xbf, 0xbd, 0x52, 0x5e, 0xa5, 0xb5, 0x79, 0x1c, 0x47, 0xb3, 0x5e, 0xbc, 0x33 + }; // SHA256 (protocolNameHash) + InitNoiseState (state, protocolNameHash, hh, pub); + } + + void InitNoiseIKState (NoiseSymmetricState& state, const uint8_t * pub) + { + static const uint8_t protocolNameHash[32] = + { + 0x4c, 0xaf, 0x11, 0xef, 0x2c, 0x8e, 0x36, 0x56, 0x4c, 0x53, 0xe8, 0x88, 0x85, 0x06, 0x4d, 0xba, + 0xac, 0xbe, 0x00, 0x54, 0xad, 0x17, 0x8f, 0x80, 0x79, 0xa6, 0x46, 0x82, 0x7e, 0x6e, 0xe4, 0x0c + }; // SHA256("Noise_IKelg2+hs2_25519_ChaChaPoly_SHA256"), 40 bytes + static const uint8_t hh[32] = + { + 0x9c, 0xcf, 0x85, 0x2c, 0xc9, 0x3b, 0xb9, 0x50, 0x44, 0x41, 0xe9, 0x50, 0xe0, 0x1d, 0x52, 0x32, + 0x2e, 0x0d, 0x47, 0xad, 0xd1, 0xe9, 0xa5, 0x55, 0xf7, 0x55, 0xb5, 0x69, 0xae, 0x18, 0x3b, 0x5c + }; // SHA256 (protocolNameHash) + InitNoiseState (state, protocolNameHash, hh, pub); + } + // init and terminate -/* std::vector > m_OpenSSLMutexes; +/* std::vector > m_OpenSSLMutexes; static void OpensslLockingCallback(int mode, int type, const char * file, int line) { if (type > 0 && (size_t)type < m_OpenSSLMutexes.size ()) @@ -1337,10 +1297,9 @@ namespace crypto } }*/ - - void InitCrypto (bool precomputation) + void InitCrypto (bool precomputation, bool aesni, bool force) { - i2p::cpu::Detect (); + i2p::cpu::Detect (aesni, force); #if LEGACY_OPENSSL SSL_library_init (); #endif @@ -1350,7 +1309,7 @@ namespace crypto CRYPTO_set_locking_callback (OpensslLockingCallback);*/ if (precomputation) { -#if defined(__x86_64__) +#if (defined(_M_AMD64) || defined(__x86_64__)) g_ElggTable = new BIGNUM * [ELGAMAL_FULL_EXPONENT_NUM_BYTES][255]; PrecalculateElggTable (g_ElggTable, ELGAMAL_FULL_EXPONENT_NUM_BYTES); #else @@ -1365,7 +1324,7 @@ namespace crypto if (g_ElggTable) { DestroyElggTable (g_ElggTable, -#if defined(__x86_64__) +#if (defined(_M_AMD64) || defined(__x86_64__)) ELGAMAL_FULL_EXPONENT_NUM_BYTES #else ELGAMAL_SHORT_EXPONENT_NUM_BYTES diff --git a/libi2pd/Crypto.h b/libi2pd/Crypto.h index ab84e56a..1419293b 100644 --- a/libi2pd/Crypto.h +++ b/libi2pd/Crypto.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -29,21 +29,25 @@ #include "CPU.h" // recognize openssl version and features -#if ((OPENSSL_VERSION_NUMBER < 0x010100000) || defined(LIBRESSL_VERSION_NUMBER)) // 1.0.2 and below or LibreSSL -# define LEGACY_OPENSSL 1 -# define X509_getm_notBefore X509_get_notBefore -# define X509_getm_notAfter X509_get_notAfter +#if (defined(LIBRESSL_VERSION_NUMBER) && (LIBRESSL_VERSION_NUMBER >= 0x3050200fL)) // LibreSSL 3.5.2 and above +# define LEGACY_OPENSSL 0 +#elif ((OPENSSL_VERSION_NUMBER < 0x010100000) || defined(LIBRESSL_VERSION_NUMBER)) // 1.0.2 and below or LibreSSL +# define LEGACY_OPENSSL 1 +# define X509_getm_notBefore X509_get_notBefore +# define X509_getm_notAfter X509_get_notAfter #else -# define LEGACY_OPENSSL 0 -# if (OPENSSL_VERSION_NUMBER >= 0x010101000) // 1.1.1 -# define OPENSSL_HKDF 1 -# define OPENSSL_EDDSA 1 -# define OPENSSL_X25519 1 -# define OPENSSL_SIPHASH 1 -# endif -# if !defined OPENSSL_NO_CHACHA && !defined OPENSSL_NO_POLY1305 // some builds might not include them -# define OPENSSL_AEAD_CHACHA20_POLY1305 1 -# endif +# define LEGACY_OPENSSL 0 +# if (OPENSSL_VERSION_NUMBER >= 0x010101000) // 1.1.1 +# define OPENSSL_HKDF 1 +# define OPENSSL_EDDSA 1 +# define OPENSSL_X25519 1 +# if (OPENSSL_VERSION_NUMBER != 0x030000000) // 3.0.0, regression in SipHash +# define OPENSSL_SIPHASH 1 +# endif +# endif +# if !defined OPENSSL_NO_CHACHA && !defined OPENSSL_NO_POLY1305 // some builds might not include them +# define OPENSSL_AEAD_CHACHA20_POLY1305 1 +# endif #endif namespace i2p @@ -58,24 +62,6 @@ namespace crypto // RSA const BIGNUM * GetRSAE (); - // DH - class DHKeys - { - public: - - DHKeys (); - ~DHKeys (); - - void GenerateKeys (); - const uint8_t * GetPublicKey () const { return m_PublicKey; }; - void Agree (const uint8_t * pub, uint8_t * shared); - - private: - - DH * m_DH; - uint8_t m_PublicKey[256]; - }; - // x25519 class X25519Keys { @@ -89,11 +75,11 @@ namespace crypto const uint8_t * GetPublicKey () const { return m_PublicKey; }; void GetPrivateKey (uint8_t * priv) const; void SetPrivateKey (const uint8_t * priv, bool calculatePublic = false); - void Agree (const uint8_t * pub, uint8_t * shared); + bool Agree (const uint8_t * pub, uint8_t * shared); bool IsElligatorIneligible () const { return m_IsElligatorIneligible; } void SetElligatorIneligible () { m_IsElligatorIneligible = true; } - + private: uint8_t m_PublicKey[32]; @@ -104,23 +90,19 @@ namespace crypto BN_CTX * m_Ctx; uint8_t m_PrivateKey[32]; #endif - bool m_IsElligatorIneligible = false; // true if definitly ineligible + bool m_IsElligatorIneligible = false; // true if definitely ineligible }; // ElGamal - void ElGamalEncrypt (const uint8_t * key, const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding = false); - bool ElGamalDecrypt (const uint8_t * key, const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding = false); + void ElGamalEncrypt (const uint8_t * key, const uint8_t * data, uint8_t * encrypted); // 222 bytes data, 514 bytes encrypted + bool ElGamalDecrypt (const uint8_t * key, const uint8_t * encrypted, uint8_t * data); // 514 bytes encrypted, 222 data void GenerateElGamalKeyPair (uint8_t * priv, uint8_t * pub); // ECIES - void ECIESEncrypt (const EC_GROUP * curve, const EC_POINT * key, const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding = false); // 222 bytes data, 514 bytes encrypted with zeropadding, 512 without - bool ECIESDecrypt (const EC_GROUP * curve, const BIGNUM * key, const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding = false); + void ECIESEncrypt (const EC_GROUP * curve, const EC_POINT * key, const uint8_t * data, uint8_t * encrypted); // 222 bytes data, 514 bytes encrypted + bool ECIESDecrypt (const EC_GROUP * curve, const BIGNUM * key, const uint8_t * encrypted, uint8_t * data); // 514 bytes encrypted, 222 data void GenerateECIESKeyPair (const EC_GROUP * curve, BIGNUM *& priv, EC_POINT *& pub); - // HMAC - typedef i2p::data::Tag<32> MACKey; - void HMACMD5Digest (uint8_t * msg, size_t len, const MACKey& key, uint8_t * digest); - // AES struct ChipherBlock { @@ -169,9 +151,6 @@ namespace crypto #ifdef __AES__ - #ifdef ARM64AES - void init_aesenc(void) __attribute__((constructor)); - #endif class ECBCryptoAESNI { public: @@ -311,8 +290,24 @@ namespace crypto void HKDF (const uint8_t * salt, const uint8_t * key, size_t keyLen, const std::string& info, uint8_t * out, size_t outLen = 64); // salt - 32, out - 32 or 64, info <= 32 +// Noise + + struct NoiseSymmetricState + { + uint8_t m_H[32] /*h*/, m_CK[64] /*[ck, k]*/; + + void MixHash (const uint8_t * buf, size_t len); + void MixHash (const std::vector >& bufs); + void MixKey (const uint8_t * sharedSecret); + }; + + void InitNoiseNState (NoiseSymmetricState& state, const uint8_t * pub); // Noise_N (tunnels, router) + void InitNoiseXKState (NoiseSymmetricState& state, const uint8_t * pub); // Noise_XK (NTCP2) + void InitNoiseXKState1 (NoiseSymmetricState& state, const uint8_t * pub); // Noise_XK (SSU2) + void InitNoiseIKState (NoiseSymmetricState& state, const uint8_t * pub); // Noise_IK (ratchets) + // init and terminate - void InitCrypto (bool precomputation); + void InitCrypto (bool precomputation, bool aesni, bool force); void TerminateCrypto (); } } @@ -368,7 +363,7 @@ inline int DH_set0_pqg(DH *dh, BIGNUM *p, BIGNUM *q, BIGNUM *g) if (dh->p) BN_free (dh->p); if (dh->q) BN_free (dh->q); if (dh->g) BN_free (dh->g); - dh->p = p; dh->q = q; dh->g = g; return 1; + dh->p = p; dh->q = q; dh->g = g; return 1; } inline int DH_set0_key(DH *dh, BIGNUM *pub_key, BIGNUM *priv_key) { diff --git a/libi2pd/CryptoKey.cpp b/libi2pd/CryptoKey.cpp index 4518a347..ad986129 100644 --- a/libi2pd/CryptoKey.cpp +++ b/libi2pd/CryptoKey.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -20,10 +20,9 @@ namespace crypto memcpy (m_PublicKey, pub, 256); } - void ElGamalEncryptor::Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding) + void ElGamalEncryptor::Encrypt (const uint8_t * data, uint8_t * encrypted) { - if (!ctx) return; - ElGamalEncrypt (m_PublicKey, data, encrypted, ctx, zeroPadding); + ElGamalEncrypt (m_PublicKey, data, encrypted); } ElGamalDecryptor::ElGamalDecryptor (const uint8_t * priv) @@ -31,10 +30,9 @@ namespace crypto memcpy (m_PrivateKey, priv, 256); } - bool ElGamalDecryptor::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding) + bool ElGamalDecryptor::Decrypt (const uint8_t * encrypted, uint8_t * data) { - if (!ctx) return false; - return ElGamalDecrypt (m_PrivateKey, encrypted, data, ctx, zeroPadding); + return ElGamalDecrypt (m_PrivateKey, encrypted, data); } ECIESP256Encryptor::ECIESP256Encryptor (const uint8_t * pub) @@ -54,10 +52,10 @@ namespace crypto if (m_PublicKey) EC_POINT_free (m_PublicKey); } - void ECIESP256Encryptor::Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding) + void ECIESP256Encryptor::Encrypt (const uint8_t * data, uint8_t * encrypted) { if (m_Curve && m_PublicKey) - ECIESEncrypt (m_Curve, m_PublicKey, data, encrypted, ctx, zeroPadding); + ECIESEncrypt (m_Curve, m_PublicKey, data, encrypted); } ECIESP256Decryptor::ECIESP256Decryptor (const uint8_t * priv) @@ -72,10 +70,10 @@ namespace crypto if (m_PrivateKey) BN_free (m_PrivateKey); } - bool ECIESP256Decryptor::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding) + bool ECIESP256Decryptor::Decrypt (const uint8_t * encrypted, uint8_t * data) { if (m_Curve && m_PrivateKey) - return ECIESDecrypt (m_Curve, m_PrivateKey, encrypted, data, ctx, zeroPadding); + return ECIESDecrypt (m_Curve, m_PrivateKey, encrypted, data); return false; } @@ -114,10 +112,10 @@ namespace crypto if (m_PublicKey) EC_POINT_free (m_PublicKey); } - void ECIESGOSTR3410Encryptor::Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding) + void ECIESGOSTR3410Encryptor::Encrypt (const uint8_t * data, uint8_t * encrypted) { if (m_PublicKey) - ECIESEncrypt (GetGOSTR3410Curve (eGOSTR3410CryptoProA)->GetGroup (), m_PublicKey, data, encrypted, ctx, zeroPadding); + ECIESEncrypt (GetGOSTR3410Curve (eGOSTR3410CryptoProA)->GetGroup (), m_PublicKey, data, encrypted); } ECIESGOSTR3410Decryptor::ECIESGOSTR3410Decryptor (const uint8_t * priv) @@ -130,10 +128,10 @@ namespace crypto if (m_PrivateKey) BN_free (m_PrivateKey); } - bool ECIESGOSTR3410Decryptor::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding) + bool ECIESGOSTR3410Decryptor::Decrypt (const uint8_t * encrypted, uint8_t * data) { if (m_PrivateKey) - return ECIESDecrypt (GetGOSTR3410Curve (eGOSTR3410CryptoProA)->GetGroup (), m_PrivateKey, encrypted, data, ctx, zeroPadding); + return ECIESDecrypt (GetGOSTR3410Curve (eGOSTR3410CryptoProA)->GetGroup (), m_PrivateKey, encrypted, data); return false; } @@ -161,7 +159,7 @@ namespace crypto memcpy (m_PublicKey, pub, 32); } - void ECIESX25519AEADRatchetEncryptor::Encrypt (const uint8_t *, uint8_t * pub, BN_CTX *, bool) + void ECIESX25519AEADRatchetEncryptor::Encrypt (const uint8_t *, uint8_t * pub) { memcpy (pub, m_PublicKey, 32); } @@ -171,10 +169,9 @@ namespace crypto m_StaticKeys.SetPrivateKey (priv, calculatePublic); } - bool ECIESX25519AEADRatchetDecryptor::Decrypt (const uint8_t * epub, uint8_t * sharedSecret, BN_CTX * ctx, bool zeroPadding) + bool ECIESX25519AEADRatchetDecryptor::Decrypt (const uint8_t * epub, uint8_t * sharedSecret) { - m_StaticKeys.Agree (epub, sharedSecret); - return true; + return m_StaticKeys.Agree (epub, sharedSecret); } void CreateECIESX25519AEADRatchetRandomKeys (uint8_t * priv, uint8_t * pub) diff --git a/libi2pd/CryptoKey.h b/libi2pd/CryptoKey.h index fb69558f..a7d86d09 100644 --- a/libi2pd/CryptoKey.h +++ b/libi2pd/CryptoKey.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -21,7 +21,7 @@ namespace crypto public: virtual ~CryptoKeyEncryptor () {}; - virtual void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding) = 0; // 222 bytes data, 512/514 bytes encrypted + virtual void Encrypt (const uint8_t * data, uint8_t * encrypted) = 0; }; class CryptoKeyDecryptor @@ -29,7 +29,7 @@ namespace crypto public: virtual ~CryptoKeyDecryptor () {}; - virtual bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding) = 0; // 512/514 bytes encrypted, 222 bytes data + virtual bool Decrypt (const uint8_t * encrypted, uint8_t * data) = 0; virtual size_t GetPublicKeyLen () const = 0; // we need it to set key in LS2 }; @@ -39,7 +39,7 @@ namespace crypto public: ElGamalEncryptor (const uint8_t * pub); - void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding); + void Encrypt (const uint8_t * data, uint8_t * encrypted) override; // 222 bytes data, 514 bytes encrypted private: @@ -51,8 +51,8 @@ namespace crypto public: ElGamalDecryptor (const uint8_t * priv); - bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding); - size_t GetPublicKeyLen () const { return 256; }; + bool Decrypt (const uint8_t * encrypted, uint8_t * data) override; // 514 bytes encrypted, 222 bytes data + size_t GetPublicKeyLen () const override { return 256; }; private: @@ -67,7 +67,7 @@ namespace crypto ECIESP256Encryptor (const uint8_t * pub); ~ECIESP256Encryptor (); - void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding); + void Encrypt (const uint8_t * data, uint8_t * encrypted) override; private: @@ -82,8 +82,8 @@ namespace crypto ECIESP256Decryptor (const uint8_t * priv); ~ECIESP256Decryptor (); - bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding); - size_t GetPublicKeyLen () const { return 64; }; + bool Decrypt (const uint8_t * encrypted, uint8_t * data) override; + size_t GetPublicKeyLen () const override { return 64; }; private: @@ -101,7 +101,7 @@ namespace crypto ECIESGOSTR3410Encryptor (const uint8_t * pub); ~ECIESGOSTR3410Encryptor (); - void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding); + void Encrypt (const uint8_t * data, uint8_t * encrypted) override; private: @@ -115,8 +115,8 @@ namespace crypto ECIESGOSTR3410Decryptor (const uint8_t * priv); ~ECIESGOSTR3410Decryptor (); - bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding); - size_t GetPublicKeyLen () const { return 64; }; + bool Decrypt (const uint8_t * encrypted, uint8_t * data) override; + size_t GetPublicKeyLen () const override { return 64; }; private: @@ -133,7 +133,7 @@ namespace crypto ECIESX25519AEADRatchetEncryptor (const uint8_t * pub); ~ECIESX25519AEADRatchetEncryptor () {}; - void Encrypt (const uint8_t *, uint8_t * pub, BN_CTX *, bool); + void Encrypt (const uint8_t *, uint8_t * pub) override; // copies m_PublicKey to pub private: @@ -147,9 +147,9 @@ namespace crypto ECIESX25519AEADRatchetDecryptor (const uint8_t * priv, bool calculatePublic = false); ~ECIESX25519AEADRatchetDecryptor () {}; - bool Decrypt (const uint8_t * epub, uint8_t * sharedSecret, BN_CTX * ctx, bool zeroPadding); + bool Decrypt (const uint8_t * epub, uint8_t * sharedSecret) override; // agree with static and return in sharedSecret (32 bytes) - size_t GetPublicKeyLen () const { return 32; }; + size_t GetPublicKeyLen () const override { return 32; }; const uint8_t * GetPubicKey () const { return m_StaticKeys.GetPublicKey (); }; private: diff --git a/libi2pd/CryptoWorker.h b/libi2pd/CryptoWorker.h deleted file mode 100644 index 27b012e7..00000000 --- a/libi2pd/CryptoWorker.h +++ /dev/null @@ -1,88 +0,0 @@ -/* -* Copyright (c) 2013-2020, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - -#ifndef CRYPTO_WORKER_H_ -#define CRYPTO_WORKER_H_ - -#include -#include -#include -#include -#include -#include - -namespace i2p -{ -namespace worker -{ - template - struct ThreadPool - { - typedef std::function ResultFunc; - typedef std::function WorkFunc; - typedef std::pair, WorkFunc> Job; - typedef std::mutex mtx_t; - typedef std::unique_lock lock_t; - typedef std::condition_variable cond_t; - ThreadPool(int workers) - { - stop = false; - if(workers > 0) - { - while(workers--) - { - threads.emplace_back([this] { - for (;;) - { - Job job; - { - lock_t lock(this->queue_mutex); - this->condition.wait( - lock, [this] { return this->stop || !this->jobs.empty(); }); - if (this->stop && this->jobs.empty()) return; - job = std::move(this->jobs.front()); - this->jobs.pop_front(); - } - ResultFunc result = job.second(); - job.first->GetService().post(result); - } - }); - } - } - }; - - void Offer(const Job & job) - { - { - lock_t lock(queue_mutex); - if (stop) return; - jobs.emplace_back(job); - } - condition.notify_one(); - } - - ~ThreadPool() - { - { - lock_t lock(queue_mutex); - stop = true; - } - condition.notify_all(); - for(auto &t: threads) t.join(); - } - - std::vector threads; - std::deque jobs; - mtx_t queue_mutex; - cond_t condition; - bool stop; - }; -} -} - -#endif diff --git a/libi2pd/Datagram.cpp b/libi2pd/Datagram.cpp index 559b0a8b..64738ebe 100644 --- a/libi2pd/Datagram.cpp +++ b/libi2pd/Datagram.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -21,6 +21,9 @@ namespace datagram DatagramDestination::DatagramDestination (std::shared_ptr owner, bool gzip): m_Owner (owner), m_Receiver (nullptr), m_RawReceiver (nullptr), m_Gzip (gzip) { + if (m_Gzip) + m_Deflator.reset (new i2p::data::GzipDeflator); + auto identityLen = m_Owner->GetIdentity ()->GetFullLen (); m_From.resize (identityLen); m_Owner->GetIdentity ()->ToBuffer (m_From.data (), identityLen); @@ -49,8 +52,8 @@ namespace datagram std::shared_ptr DatagramDestination::GetSession(const i2p::data::IdentHash & ident) { return ObtainSession(ident); - } - + } + void DatagramDestination::SendDatagram (std::shared_ptr session, const uint8_t * payload, size_t len, uint16_t fromPort, uint16_t toPort) { if (session) @@ -67,21 +70,21 @@ namespace datagram auto msg = CreateDataMessage ({{m_From.data (), m_From.size ()}, {m_Signature.data (), m_Signature.size ()}, {payload, len}}, fromPort, toPort, false, !session->IsRatchets ()); // datagram session->SendMsg(msg); - } - } + } + } void DatagramDestination::SendRawDatagram (std::shared_ptr session, const uint8_t * payload, size_t len, uint16_t fromPort, uint16_t toPort) { if (session) session->SendMsg(CreateDataMessage ({{payload, len}}, fromPort, toPort, true, !session->IsRatchets ())); // raw } - + void DatagramDestination::FlushSendQueue (std::shared_ptr session) { if (session) session->FlushSendQueue (); - } - + } + void DatagramDestination::HandleDatagram (uint16_t fromPort, uint16_t toPort,uint8_t * const &buf, size_t len) { i2p::data::IdentityEx identity; @@ -152,11 +155,16 @@ namespace datagram const std::vector >& payloads, uint16_t fromPort, uint16_t toPort, bool isRaw, bool checksum) { + size_t size; auto msg = m_I2NPMsgsPool.AcquireShared (); uint8_t * buf = msg->GetPayload (); buf += 4; // reserve for length - size_t size = m_Gzip ? m_Deflator.Deflate (payloads, buf, msg->maxLen - msg->len) : - i2p::data::GzipNoCompression (payloads, buf, msg->maxLen - msg->len); + + if (m_Gzip && m_Deflator) + size = m_Deflator->Deflate (payloads, buf, msg->maxLen - msg->len); + else + size = i2p::data::GzipNoCompression (payloads, buf, msg->maxLen - msg->len); + if (size) { htobe32buf (msg->GetPayload (), size); // length @@ -242,7 +250,7 @@ namespace datagram if (msg || m_SendQueue.empty ()) m_SendQueue.push_back(msg); // flush queue right away if full - if (!msg || m_SendQueue.size() >= DATAGRAM_SEND_QUEUE_MAX_SIZE) + if (!msg || m_SendQueue.size() >= DATAGRAM_SEND_QUEUE_MAX_SIZE) FlushSendQueue(); } @@ -286,100 +294,103 @@ namespace datagram m_RemoteLeaseSet = m_LocalDestination->FindLeaseSet(m_RemoteIdent); if (!m_RemoteLeaseSet) { - if(!m_RequestingLS) + if(!m_RequestingLS) { m_RequestingLS = true; m_LocalDestination->RequestDestination(m_RemoteIdent, std::bind(&DatagramSession::HandleLeaseSetUpdated, this, std::placeholders::_1)); } return nullptr; - } - } + } + } - if (!m_RoutingSession || !m_RoutingSession->GetOwner ()) + if (!m_RoutingSession || m_RoutingSession->IsTerminated () || !m_RoutingSession->IsReadyToSend ()) { bool found = false; for (auto& it: m_PendingRoutingSessions) - if (it->GetOwner ()) // found established session + if (it->GetOwner () && m_RoutingSession->IsReadyToSend ()) // found established session { m_RoutingSession = it; m_PendingRoutingSessions.clear (); found = true; break; - } + } if (!found) - { + { m_RoutingSession = m_LocalDestination->GetRoutingSession(m_RemoteLeaseSet, true); - if (!m_RoutingSession->GetOwner ()) + if (!m_RoutingSession->GetOwner () || !m_RoutingSession->IsReadyToSend ()) m_PendingRoutingSessions.push_back (m_RoutingSession); - } - } - + } + } + auto path = m_RoutingSession->GetSharedRoutingPath(); if (path && m_RoutingSession->IsRatchets () && - m_LastUse > m_RoutingSession->GetLastActivityTimestamp ()*1000 + DATAGRAM_SESSION_PATH_TIMEOUT) - { + m_LastUse > m_RoutingSession->GetLastActivityTimestamp ()*1000 + DATAGRAM_SESSION_PATH_TIMEOUT) + { m_RoutingSession->SetSharedRoutingPath (nullptr); path = nullptr; } - - if (path) + + if (path) { if (path->outboundTunnel && !path->outboundTunnel->IsEstablished ()) - { + { // bad outbound tunnel, switch outbound tunnel path->outboundTunnel = m_LocalDestination->GetTunnelPool()->GetNextOutboundTunnel(path->outboundTunnel); if (!path->outboundTunnel) m_RoutingSession->SetSharedRoutingPath (nullptr); - } - - if (path->remoteLease && path->remoteLease->ExpiresWithin(DATAGRAM_SESSION_LEASE_HANDOVER_WINDOW)) + } + + if (path->remoteLease && path->remoteLease->ExpiresWithin(DATAGRAM_SESSION_LEASE_HANDOVER_WINDOW)) { // bad lease, switch to next one - if (m_RemoteLeaseSet) + if (m_RemoteLeaseSet) { auto ls = m_RemoteLeaseSet->GetNonExpiredLeasesExcluding( - [&](const i2p::data::Lease& l) -> bool + [&](const i2p::data::Lease& l) -> bool { return l.tunnelID == path->remoteLease->tunnelID; }); auto sz = ls.size(); - if (sz) + if (sz) { auto idx = rand() % sz; path->remoteLease = ls[idx]; } else m_RoutingSession->SetSharedRoutingPath (nullptr); - } - else - { + } + else + { // no remote lease set? LogPrint(eLogWarning, "DatagramSession: no cached remote lease set for ", m_RemoteIdent.ToBase32()); m_RoutingSession->SetSharedRoutingPath (nullptr); - } + } } - } - else + } + else { // no current path, make one path = std::make_shared(); - path->outboundTunnel = m_LocalDestination->GetTunnelPool()->GetNextOutboundTunnel(); - if (!path->outboundTunnel) return nullptr; - - if (m_RemoteLeaseSet) + + if (m_RemoteLeaseSet) { // pick random next good lease auto ls = m_RemoteLeaseSet->GetNonExpiredLeases(); auto sz = ls.size(); - if (sz) + if (sz) { auto idx = rand() % sz; path->remoteLease = ls[idx]; } else return nullptr; - } - else + + auto leaseRouter = i2p::data::netdb.FindRouter (path->remoteLease->tunnelGateway); + path->outboundTunnel = m_LocalDestination->GetTunnelPool()->GetNextOutboundTunnel(nullptr, + leaseRouter ? leaseRouter->GetCompatibleTransports (false) : (i2p::data::RouterInfo::CompatibleTransports)i2p::data::RouterInfo::eAllTransports); + if (!path->outboundTunnel) return nullptr; + } + else { // no remote lease set currently, bail LogPrint(eLogWarning, "DatagramSession: no remote lease set found for ", m_RemoteIdent.ToBase32()); @@ -414,7 +425,7 @@ namespace datagram if (m) send.push_back(i2p::tunnel::TunnelMessageBlock{i2p::tunnel::eDeliveryTypeTunnel,routingPath->remoteLease->tunnelGateway, routingPath->remoteLease->tunnelID, m}); } - routingPath->outboundTunnel->SendTunnelDataMsg(send); + routingPath->outboundTunnel->SendTunnelDataMsgs(send); } m_SendQueue.clear(); } diff --git a/libi2pd/Datagram.h b/libi2pd/Datagram.h index 5dd6c8b6..8f3d5ceb 100644 --- a/libi2pd/Datagram.h +++ b/libi2pd/Datagram.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -15,6 +15,7 @@ #include #include #include "Base.h" +#include "Gzip.h" #include "Identity.h" #include "LeaseSet.h" #include "I2NPProtocol.h" @@ -117,12 +118,12 @@ namespace datagram void SendDatagramTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash & ident, uint16_t fromPort = 0, uint16_t toPort = 0); void SendRawDatagramTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash & ident, uint16_t fromPort = 0, uint16_t toPort = 0); // TODO: implement calls from other thread from SAM - + std::shared_ptr GetSession(const i2p::data::IdentHash & ident); void SendDatagram (std::shared_ptr session, const uint8_t * payload, size_t len, uint16_t fromPort, uint16_t toPort); void SendRawDatagram (std::shared_ptr session, const uint8_t * payload, size_t len, uint16_t fromPort, uint16_t toPort); void FlushSendQueue (std::shared_ptr session); - + void HandleDataMessagePayload (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len, bool isRaw = false); void SetReceiver (const Receiver& receiver) { m_Receiver = receiver; }; @@ -164,7 +165,7 @@ namespace datagram std::map m_ReceiversByPorts; i2p::data::GzipInflator m_Inflator; - i2p::data::GzipDeflator m_Deflator; + std::unique_ptr m_Deflator; std::vector m_From, m_Signature; i2p::util::MemoryPool > m_I2NPMsgsPool; }; diff --git a/libi2pd/Destination.cpp b/libi2pd/Destination.cpp index 669fd791..b9555abe 100644 --- a/libi2pd/Destination.cpp +++ b/libi2pd/Destination.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -13,7 +13,6 @@ #include #include #include "Crypto.h" -#include "Config.h" #include "Log.h" #include "FS.h" #include "Timestamp.h" @@ -35,6 +34,8 @@ namespace client int inQty = DEFAULT_INBOUND_TUNNELS_QUANTITY; int outLen = DEFAULT_OUTBOUND_TUNNEL_LENGTH; int outQty = DEFAULT_OUTBOUND_TUNNELS_QUANTITY; + int inVar = DEFAULT_INBOUND_TUNNELS_LENGTH_VARIANCE; + int outVar = DEFAULT_OUTBOUND_TUNNELS_LENGTH_VARIANCE; int numTags = DEFAULT_TAGS_TO_SEND; std::shared_ptr > explicitPeers; try @@ -53,10 +54,16 @@ namespace client it = params->find (I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY); if (it != params->end ()) outQty = std::stoi(it->second); + it = params->find (I2CP_PARAM_INBOUND_TUNNELS_LENGTH_VARIANCE); + if (it != params->end ()) + inVar = std::stoi(it->second); + it = params->find (I2CP_PARAM_OUTBOUND_TUNNELS_LENGTH_VARIANCE); + if (it != params->end ()) + outVar = std::stoi(it->second); it = params->find (I2CP_PARAM_TAGS_TO_SEND); if (it != params->end ()) numTags = std::stoi(it->second); - LogPrint (eLogInfo, "Destination: parameters for tunnel set to: ", inQty, " inbound (", inLen, " hops), ", outQty, " outbound (", outLen, " hops), ", numTags, " tags"); + LogPrint (eLogInfo, "Destination: Parameters for tunnel set to: ", inQty, " inbound (", inLen, " hops), ", outQty, " outbound (", outLen, " hops), ", numTags, " tags"); it = params->find (I2CP_PARAM_RATCHET_INBOUND_TAGS); if (it != params->end ()) SetNumRatchetInboundTags (std::stoi(it->second)); @@ -82,6 +89,12 @@ namespace client if (it != params->end ()) m_Nickname = it->second; // otherwise we set default nickname in Start when we know local address } + it = params->find (I2CP_PARAM_DONT_PUBLISH_LEASESET); + if (it != params->end ()) + { + // oveeride isPublic + m_IsPublic = (it->second != "true"); + } it = params->find (I2CP_PARAM_LEASESET_TYPE); if (it != params->end ()) m_LeaseSetType = std::stoi(it->second); @@ -95,7 +108,7 @@ namespace client if (authType >= i2p::data::ENCRYPTED_LEASESET_AUTH_TYPE_NONE && authType <= i2p::data::ENCRYPTED_LEASESET_AUTH_TYPE_PSK) m_AuthType = authType; else - LogPrint (eLogError, "Destination: Unknown auth type ", authType); + LogPrint (eLogError, "Destination: Unknown auth type: ", authType); } } it = params->find (I2CP_PARAM_LEASESET_PRIV_KEY); @@ -104,7 +117,7 @@ namespace client m_LeaseSetPrivKey.reset (new i2p::data::Tag<32>()); if (m_LeaseSetPrivKey->FromBase64 (it->second) != 32) { - LogPrint(eLogError, "Destination: invalid value i2cp.leaseSetPrivKey ", it->second); + LogPrint(eLogCritical, "Destination: Invalid value i2cp.leaseSetPrivKey: ", it->second); m_LeaseSetPrivKey.reset (nullptr); } } @@ -112,10 +125,10 @@ namespace client } catch (std::exception & ex) { - LogPrint(eLogError, "Destination: unable to parse parameters for destination: ", ex.what()); + LogPrint(eLogError, "Destination: Unable to parse parameters for destination: ", ex.what()); } SetNumTags (numTags); - m_Pool = i2p::tunnel::tunnels.CreateTunnelPool (inLen, outLen, inQty, outQty); + m_Pool = i2p::tunnel::tunnels.CreateTunnelPool (inLen, outLen, inQty, outQty, inVar, outVar); if (explicitPeers) m_Pool->SetExplicitPeers (explicitPeers); if(params) @@ -128,7 +141,7 @@ namespace client auto minlatency = std::stoi(itr->second); if ( minlatency > 0 && maxlatency > 0 ) { // set tunnel pool latency - LogPrint(eLogInfo, "Destination: requiring tunnel latency [", minlatency, "ms, ", maxlatency, "ms]"); + LogPrint(eLogInfo, "Destination: Requiring tunnel latency [", minlatency, "ms, ", maxlatency, "ms]"); m_Pool->RequireLatency(minlatency, maxlatency); } } @@ -243,23 +256,12 @@ namespace client } else { - LogPrint (eLogWarning, "Destination: remote LeaseSet expired"); + LogPrint (eLogWarning, "Destination: Remote LeaseSet expired"); std::lock_guard lock(m_RemoteLeaseSetsMutex); m_RemoteLeaseSets.erase (ident); return nullptr; } } - else - { - auto ls = i2p::data::netdb.FindLeaseSet (ident); - if (ls && !ls->IsExpired ()) - { - ls->PopulateLeases (); // since we don't store them in netdb - std::lock_guard _lock(m_RemoteLeaseSetsMutex); - m_RemoteLeaseSets[ident] = ls; - return ls; - } - } return nullptr; } @@ -300,7 +302,11 @@ namespace client { int numTunnels = m_Pool->GetNumInboundTunnels () + 2; // 2 backup tunnels if (numTunnels > i2p::data::MAX_NUM_LEASES) numTunnels = i2p::data::MAX_NUM_LEASES; // 16 tunnels maximum - CreateNewLeaseSet (m_Pool->GetInboundTunnels (numTunnels)); + auto tunnels = m_Pool->GetInboundTunnels (numTunnels); + if (!tunnels.empty ()) + CreateNewLeaseSet (tunnels); + else + LogPrint (eLogInfo, "Destination: No inbound tunnels for LeaseSet"); } bool LeaseSetDestination::SubmitSessionKey (const uint8_t * key, const uint8_t * tag) @@ -319,6 +325,22 @@ namespace client return true; } + void LeaseSetDestination::SubmitECIESx25519Key (const uint8_t * key, uint64_t tag) + { + struct + { + uint8_t k[32]; + uint64_t t; + } data; + memcpy (data.k, key, 32); + data.t = tag; + auto s = shared_from_this (); + m_Service.post ([s,data](void) + { + s->AddECIESx25519Key (data.k, data.t); + }); + } + void LeaseSetDestination::ProcessGarlicMessage (std::shared_ptr msg) { m_Service.post (std::bind (&LeaseSetDestination::HandleGarlicMessage, shared_from_this (), msg)); @@ -333,10 +355,11 @@ namespace client void LeaseSetDestination::HandleI2NPMessage (const uint8_t * buf, size_t len) { I2NPMessageType typeID = (I2NPMessageType)(buf[I2NP_HEADER_TYPEID_OFFSET]); - LeaseSetDestination::HandleCloveI2NPMessage (typeID, buf + I2NP_HEADER_SIZE, GetI2NPMessageLength(buf, len) - I2NP_HEADER_SIZE); + uint32_t msgID = bufbe32toh (buf + I2NP_HEADER_MSGID_OFFSET); + LeaseSetDestination::HandleCloveI2NPMessage (typeID, buf + I2NP_HEADER_SIZE, GetI2NPMessageLength(buf, len) - I2NP_HEADER_SIZE, msgID); } - bool LeaseSetDestination::HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len) + bool LeaseSetDestination::HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID) { switch (typeID) { @@ -353,6 +376,9 @@ namespace client case eI2NPDatabaseSearchReply: HandleDatabaseSearchReplyMessage (payload, len); break; + case eI2NPShortTunnelBuildReply: // might come as garlic encrypted + i2p::HandleI2NPMessage (CreateI2NPMessage (typeID, payload, len, msgID)); + break; default: LogPrint (eLogWarning, "Destination: Unexpected I2NP message type ", typeID); return false; @@ -362,6 +388,11 @@ namespace client void LeaseSetDestination::HandleDatabaseStoreMessage (const uint8_t * buf, size_t len) { + if (len < DATABASE_STORE_HEADER_SIZE) + { + LogPrint (eLogError, "Destination: Database store msg is too short ", len); + return; + } uint32_t replyToken = bufbe32toh (buf + DATABASE_STORE_REPLY_TOKEN_OFFSET); size_t offset = DATABASE_STORE_HEADER_SIZE; if (replyToken) @@ -369,6 +400,11 @@ namespace client LogPrint (eLogInfo, "Destination: Reply token is ignored for DatabaseStore"); offset += 36; } + if (offset > len || len > i2p::data::MAX_LS_BUFFER_SIZE + offset) + { + LogPrint (eLogError, "Destination: Database store message is too long ", len); + return; + } i2p::data::IdentHash key (buf + DATABASE_STORE_KEY_OFFSET); std::shared_ptr leaseSet; switch (buf[DATABASE_STORE_TYPE_OFFSET]) @@ -379,13 +415,14 @@ namespace client LogPrint (eLogDebug, "Destination: Remote LeaseSet"); std::lock_guard lock(m_RemoteLeaseSetsMutex); auto it = m_RemoteLeaseSets.find (key); - if (it != m_RemoteLeaseSets.end ()) + if (it != m_RemoteLeaseSets.end () && + it->second->GetStoreType () == buf[DATABASE_STORE_TYPE_OFFSET]) // update only if same type { leaseSet = it->second; if (leaseSet->IsNewer (buf + offset, len - offset)) { leaseSet->Update (buf + offset, len - offset); - if (leaseSet->IsValid () && leaseSet->GetIdentHash () == key) + if (leaseSet->IsValid () && leaseSet->GetIdentHash () == key && !leaseSet->IsExpired ()) LogPrint (eLogDebug, "Destination: Remote LeaseSet updated"); else { @@ -399,11 +436,12 @@ namespace client } else { + // add or replace if (buf[DATABASE_STORE_TYPE_OFFSET] == i2p::data::NETDB_STORE_TYPE_LEASESET) leaseSet = std::make_shared (buf + offset, len - offset); // LeaseSet else leaseSet = std::make_shared (buf[DATABASE_STORE_TYPE_OFFSET], buf + offset, len - offset, true, GetPreferredCryptoType () ); // LeaseSet2 - if (leaseSet->IsValid () && leaseSet->GetIdentHash () == key) + if (leaseSet->IsValid () && leaseSet->GetIdentHash () == key && !leaseSet->IsExpired ()) { if (leaseSet->GetIdentHash () != GetIdentHash ()) { @@ -428,12 +466,15 @@ namespace client { auto ls2 = std::make_shared (buf + offset, len - offset, it2->second->requestedBlindedKey, m_LeaseSetPrivKey ? ((const uint8_t *)*m_LeaseSetPrivKey) : nullptr , GetPreferredCryptoType ()); - if (ls2->IsValid ()) + if (ls2->IsValid () && !ls2->IsExpired ()) { + leaseSet = ls2; + std::lock_guard lock(m_RemoteLeaseSetsMutex); m_RemoteLeaseSets[ls2->GetIdentHash ()] = ls2; // ident is not key m_RemoteLeaseSets[key] = ls2; // also store as key for next lookup - leaseSet = ls2; } + else + LogPrint (eLogError, "Destination: New remote encrypted LeaseSet2 failed"); } else LogPrint (eLogInfo, "Destination: Couldn't find request for encrypted LeaseSet2"); @@ -469,7 +510,7 @@ namespace client i2p::data::IdentHash peerHash (buf + 33 + i*32); if (!request->excluded.count (peerHash) && !i2p::data::netdb.FindRouter (peerHash)) { - LogPrint (eLogInfo, "Destination: Found new floodfill, request it"); + LogPrint (eLogInfo, "Destination: Found new floodfill, request it"); i2p::data::netdb.RequestDestination (peerHash, nullptr, false); // through exploratory } } @@ -537,16 +578,9 @@ namespace client shared_from_this (), std::placeholders::_1)); return; } - auto outbound = m_Pool->GetNextOutboundTunnel (); - if (!outbound) + if (!m_Pool->GetInboundTunnels ().size () || !m_Pool->GetOutboundTunnels ().size ()) { - LogPrint (eLogError, "Destination: Can't publish LeaseSet. No outbound tunnels"); - return; - } - auto inbound = m_Pool->GetNextInboundTunnel (); - if (!inbound) - { - LogPrint (eLogError, "Destination: Can't publish LeaseSet. No inbound tunnels"); + LogPrint (eLogError, "Destination: Can't publish LeaseSet. Destination is not ready"); return; } auto floodfill = i2p::data::netdb.GetClosestFloodfill (leaseSet->GetIdentHash (), m_ExcludedFloodfills); @@ -556,14 +590,41 @@ namespace client m_ExcludedFloodfills.clear (); return; } + auto outbound = m_Pool->GetNextOutboundTunnel (nullptr, floodfill->GetCompatibleTransports (false)); + auto inbound = m_Pool->GetNextInboundTunnel (nullptr, floodfill->GetCompatibleTransports (true)); + if (!outbound || !inbound) + { + LogPrint (eLogInfo, "Destination: No compatible tunnels with ", floodfill->GetIdentHash ().ToBase64 (), ". Trying another floodfill"); + m_ExcludedFloodfills.insert (floodfill->GetIdentHash ()); + floodfill = i2p::data::netdb.GetClosestFloodfill (leaseSet->GetIdentHash (), m_ExcludedFloodfills); + if (floodfill) + { + outbound = m_Pool->GetNextOutboundTunnel (nullptr, floodfill->GetCompatibleTransports (false)); + if (outbound) + { + inbound = m_Pool->GetNextInboundTunnel (nullptr, floodfill->GetCompatibleTransports (true)); + if (!inbound) + LogPrint (eLogError, "Destination: Can't publish LeaseSet. No inbound tunnels"); + } + else + LogPrint (eLogError, "Destination: Can't publish LeaseSet. No outbound tunnels"); + } + else + LogPrint (eLogError, "Destination: Can't publish LeaseSet, no more floodfills found"); + if (!floodfill || !outbound || !inbound) + { + m_ExcludedFloodfills.clear (); + return; + } + } m_ExcludedFloodfills.insert (floodfill->GetIdentHash ()); LogPrint (eLogDebug, "Destination: Publish LeaseSet of ", GetIdentHash ().ToBase32 ()); RAND_bytes ((uint8_t *)&m_PublishReplyToken, 4); - auto msg = WrapMessage (floodfill, i2p::CreateDatabaseStoreMsg (leaseSet, m_PublishReplyToken, inbound)); + auto msg = WrapMessageForRouter (floodfill, i2p::CreateDatabaseStoreMsg (leaseSet, m_PublishReplyToken, inbound)); m_PublishConfirmationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_CONFIRMATION_TIMEOUT)); m_PublishConfirmationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishConfirmationTimer, shared_from_this (), std::placeholders::_1)); - outbound->SendTunnelDataMsg (floodfill->GetIdentHash (), 0, msg); + outbound->SendTunnelDataMsgTo (floodfill->GetIdentHash (), 0, msg); m_LastSubmissionTime = ts; } @@ -600,7 +661,7 @@ namespace client auto ls = GetLeaseSetMt (); if (!ls) { - LogPrint (eLogWarning, "Destination: couldn't verify LeaseSet for ", GetIdentHash().ToBase32()); + LogPrint (eLogWarning, "Destination: Couldn't verify LeaseSet for ", GetIdentHash().ToBase32()); return; } auto s = shared_from_this (); @@ -612,7 +673,7 @@ namespace client if (*ls == *leaseSet) { // we got latest LeasetSet - LogPrint (eLogDebug, "Destination: published LeaseSet verified for ", s->GetIdentHash().ToBase32()); + LogPrint (eLogDebug, "Destination: Published LeaseSet verified for ", s->GetIdentHash().ToBase32()); s->m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_REGULAR_VERIFICATION_INTERNAL)); s->m_PublishVerificationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishVerificationTimer, s, std::placeholders::_1)); return; @@ -621,7 +682,7 @@ namespace client LogPrint (eLogDebug, "Destination: LeaseSet is different than just published for ", s->GetIdentHash().ToBase32()); } else - LogPrint (eLogWarning, "Destination: couldn't find published LeaseSet for ", s->GetIdentHash().ToBase32()); + LogPrint (eLogWarning, "Destination: Couldn't find published LeaseSet for ", s->GetIdentHash().ToBase32()); // we have to publish again s->Publish (); }); @@ -704,9 +765,17 @@ namespace client request->requestTime = ts; if (!SendLeaseSetRequest (dest, floodfill, request)) { - // request failed - m_LeaseSetRequests.erase (ret.first); - if (requestComplete) requestComplete (nullptr); + // try another + LogPrint (eLogWarning, "Destination: Couldn't send LeaseSet request to ", floodfill->GetIdentHash ().ToBase64 (), ". Trying another"); + request->excluded.insert (floodfill->GetIdentHash ()); + floodfill = i2p::data::netdb.GetClosestFloodfill (dest, request->excluded); + if (!SendLeaseSetRequest (dest, floodfill, request)) + { + // request failed + LogPrint (eLogWarning, "Destination: LeaseSet request for ", dest.ToBase32 (), " was not sent"); + m_LeaseSetRequests.erase (ret.first); + if (requestComplete) requestComplete (nullptr); + } } } else // duplicate @@ -733,31 +802,29 @@ namespace client std::shared_ptr nextFloodfill, std::shared_ptr request) { if (!request->replyTunnel || !request->replyTunnel->IsEstablished ()) - request->replyTunnel = m_Pool->GetNextInboundTunnel (); - if (!request->replyTunnel) LogPrint (eLogError, "Destination: Can't send LeaseSet request, no inbound tunnels found"); + request->replyTunnel = m_Pool->GetNextInboundTunnel (nullptr, nextFloodfill->GetCompatibleTransports (false)); // outbound from floodfill + if (!request->replyTunnel) LogPrint (eLogWarning, "Destination: Can't send LeaseSet request, no compatible inbound tunnels found"); if (!request->outboundTunnel || !request->outboundTunnel->IsEstablished ()) - request->outboundTunnel = m_Pool->GetNextOutboundTunnel (); - if (!request->outboundTunnel) LogPrint (eLogError, "Destination: Can't send LeaseSet request, no outbound tunnels found"); + request->outboundTunnel = m_Pool->GetNextOutboundTunnel (nullptr, nextFloodfill->GetCompatibleTransports (true)); // inbound from floodfill + if (!request->outboundTunnel) LogPrint (eLogWarning, "Destination: Can't send LeaseSet request, no compatible outbound tunnels found"); if (request->replyTunnel && request->outboundTunnel) { request->excluded.insert (nextFloodfill->GetIdentHash ()); request->requestTimeoutTimer.cancel (); - bool isECIES = SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET) && + bool isECIES = SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) && nextFloodfill->GetVersion () >= MAKE_VERSION_NUMBER(0, 9, 46); // >= 0.9.46; uint8_t replyKey[32], replyTag[32]; RAND_bytes (replyKey, 32); // random session key RAND_bytes (replyTag, isECIES ? 8 : 32); // random session tag - if (isECIES) + if (isECIES) AddECIESx25519Key (replyKey, replyTag); - else + else AddSessionKey (replyKey, replyTag); - - auto msg = WrapMessage (nextFloodfill, - CreateLeaseSetDatabaseLookupMsg (dest, request->excluded, - request->replyTunnel, replyKey, replyTag, isECIES)); - request->outboundTunnel->SendTunnelDataMsg ( + auto msg = WrapMessageForRouter (nextFloodfill, CreateLeaseSetDatabaseLookupMsg (dest, + request->excluded, request->replyTunnel, replyKey, replyTag, isECIES)); + request->outboundTunnel->SendTunnelDataMsgs ( { i2p::tunnel::TunnelMessageBlock { @@ -843,16 +910,16 @@ namespace client i2p::data::CryptoKeyType LeaseSetDestination::GetPreferredCryptoType () const { - if (SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET)) - return i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET; + if (SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) + return i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD; return i2p::data::CRYPTO_KEY_TYPE_ELGAMAL; } ClientDestination::ClientDestination (boost::asio::io_service& service, const i2p::data::PrivateKeys& keys, bool isPublic, const std::map * params): - LeaseSetDestination (service, isPublic, params), - m_Keys (keys), m_StreamingAckDelay (DEFAULT_INITIAL_ACK_DELAY), - m_IsStreamingAnswerPings (DEFAULT_ANSWER_PINGS), + LeaseSetDestination (service, isPublic, params), + m_Keys (keys), m_StreamingAckDelay (DEFAULT_INITIAL_ACK_DELAY), + m_IsStreamingAnswerPings (DEFAULT_ANSWER_PINGS), m_LastPort (0), m_DatagramDestination (nullptr), m_RefCounter (0), m_ReadyChecker(service) { @@ -894,22 +961,22 @@ namespace client for (auto& it: encryptionKeyTypes) { auto encryptionKey = new EncryptionKey (it); - if (isPublic) + if (IsPublic ()) PersistTemporaryKeys (encryptionKey, isSingleKey); else encryptionKey->GenerateKeys (); encryptionKey->CreateDecryptor (); - if (it == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET) - { + if (it == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) + { m_ECIESx25519EncryptionKey.reset (encryptionKey); if (GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_LEASESET) - SetLeaseSetType (i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2); // Rathets must use LeaseSet2 - } + SetLeaseSetType (i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2); // Rathets must use LeaseSet2 + } else m_StandardEncryptionKey.reset (encryptionKey); } - if (isPublic) + if (IsPublic ()) LogPrint (eLogInfo, "Destination: Local address ", GetIdentHash().ToBase32 (), " created"); try @@ -922,8 +989,8 @@ namespace client m_StreamingAckDelay = std::stoi(it->second); it = params->find (I2CP_PARAM_STREAMING_ANSWER_PINGS); if (it != params->end ()) - i2p::config::GetOption (it->second, m_IsStreamingAnswerPings); - + m_IsStreamingAnswerPings = std::stoi (it->second); // 1 for true + if (GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2) { // authentication for encrypted LeaseSet @@ -936,12 +1003,12 @@ namespace client else if (authType == i2p::data::ENCRYPTED_LEASESET_AUTH_TYPE_PSK) ReadAuthKey (I2CP_PARAM_LEASESET_CLIENT_PSK, params); else - LogPrint (eLogError, "Destination: Unexpected auth type ", authType); + LogPrint (eLogError, "Destination: Unexpected auth type: ", authType); if (m_AuthKeys->size ()) LogPrint (eLogInfo, "Destination: ", m_AuthKeys->size (), " auth keys read"); else { - LogPrint (eLogError, "Destination: No auth keys read for auth type ", authType); + LogPrint (eLogCritical, "Destination: No auth keys read for auth type: ", authType); m_AuthKeys = nullptr; } } @@ -950,7 +1017,7 @@ namespace client } catch (std::exception & ex) { - LogPrint(eLogError, "Destination: unable to parse parameters for destination: ", ex.what()); + LogPrint(eLogCritical, "Destination: Unable to parse parameters for destination: ", ex.what()); } } @@ -969,22 +1036,30 @@ namespace client void ClientDestination::Stop () { + LogPrint(eLogDebug, "Destination: Stopping destination ", GetIdentHash().ToBase32(), ".b32.i2p"); LeaseSetDestination::Stop (); m_ReadyChecker.cancel(); + LogPrint(eLogDebug, "Destination: -> Stopping Streaming Destination"); m_StreamingDestination->Stop (); //m_StreamingDestination->SetOwner (nullptr); m_StreamingDestination = nullptr; + + LogPrint(eLogDebug, "Destination: -> Stopping Streaming Destination by ports"); for (auto& it: m_StreamingDestinationsByPorts) { it.second->Stop (); //it.second->SetOwner (nullptr); } m_StreamingDestinationsByPorts.clear (); + m_LastStreamingDestination = nullptr; + if (m_DatagramDestination) { + LogPrint(eLogDebug, "Destination: -> Stopping Datagram Destination"); delete m_DatagramDestination; m_DatagramDestination = nullptr; } + LogPrint(eLogDebug, "Destination: -> Stopping done"); } void ClientDestination::HandleDataMessage (const uint8_t * buf, size_t len) @@ -1004,9 +1079,15 @@ namespace client case PROTOCOL_TYPE_STREAMING: { // streaming protocol - auto dest = GetStreamingDestination (toPort); - if (dest) - dest->HandleDataMessagePayload (buf, length); + if (toPort != m_LastPort || !m_LastStreamingDestination) + { + m_LastStreamingDestination = GetStreamingDestination (toPort); + if (!m_LastStreamingDestination) + m_LastStreamingDestination = m_StreamingDestination; // if no destination on port use default + m_LastPort = toPort; + } + if (m_LastStreamingDestination) + m_LastStreamingDestination->HandleDataMessagePayload (buf, length); else LogPrint (eLogError, "Destination: Missing streaming destination"); } @@ -1026,20 +1107,26 @@ namespace client LogPrint (eLogError, "Destination: Missing raw datagram destination"); break; default: - LogPrint (eLogError, "Destination: Data: unexpected protocol ", buf[9]); + LogPrint (eLogError, "Destination: Data: Unexpected protocol ", buf[9]); } } - void ClientDestination::CreateStream (StreamRequestComplete streamRequestComplete, const i2p::data::IdentHash& dest, int port) + void ClientDestination::CreateStream (StreamRequestComplete streamRequestComplete, const i2p::data::IdentHash& dest, uint16_t port) { if (!streamRequestComplete) { - LogPrint (eLogError, "Destination: request callback is not specified in CreateStream"); + LogPrint (eLogError, "Destination: Request callback is not specified in CreateStream"); return; } auto leaseSet = FindLeaseSet (dest); if (leaseSet) - streamRequestComplete(CreateStream (leaseSet, port)); + { + auto stream = CreateStream (leaseSet, port); + GetService ().post ([streamRequestComplete, stream]() + { + streamRequestComplete(stream); + }); + } else { auto s = GetSharedFromThis (); @@ -1054,11 +1141,11 @@ namespace client } } - void ClientDestination::CreateStream (StreamRequestComplete streamRequestComplete, std::shared_ptr dest, int port) + void ClientDestination::CreateStream (StreamRequestComplete streamRequestComplete, std::shared_ptr dest, uint16_t port) { if (!streamRequestComplete) { - LogPrint (eLogError, "Destination: request callback is not specified in CreateStream"); + LogPrint (eLogError, "Destination: Request callback is not specified in CreateStream"); return; } auto s = GetSharedFromThis (); @@ -1072,7 +1159,42 @@ namespace client }); } - std::shared_ptr ClientDestination::CreateStream (std::shared_ptr remote, int port) + template + std::shared_ptr ClientDestination::CreateStreamSync (const Dest& dest, uint16_t port) + { + volatile bool done = false; + std::shared_ptr stream; + std::condition_variable streamRequestComplete; + std::mutex streamRequestCompleteMutex; + CreateStream ( + [&done, &streamRequestComplete, &streamRequestCompleteMutex, &stream](std::shared_ptr s) + { + stream = s; + std::unique_lock l(streamRequestCompleteMutex); + streamRequestComplete.notify_all (); + done = true; + }, + dest, port); + while (!done) + { + std::unique_lock l(streamRequestCompleteMutex); + if (!done) + streamRequestComplete.wait (l); + } + return stream; + } + + std::shared_ptr ClientDestination::CreateStream (const i2p::data::IdentHash& dest, uint16_t port) + { + return CreateStreamSync (dest, port); + } + + std::shared_ptr ClientDestination::CreateStream (std::shared_ptr dest, uint16_t port) + { + return CreateStreamSync (dest, port); + } + + std::shared_ptr ClientDestination::CreateStream (std::shared_ptr remote, uint16_t port) { if (m_StreamingDestination) return m_StreamingDestination->CreateNewOutgoingStream (remote, port); @@ -1080,7 +1202,36 @@ namespace client return nullptr; } - std::shared_ptr ClientDestination::GetStreamingDestination (int port) const + void ClientDestination::SendPing (const i2p::data::IdentHash& to) + { + if (m_StreamingDestination) + { + auto leaseSet = FindLeaseSet (to); + if (leaseSet) + m_StreamingDestination->SendPing (leaseSet); + else + { + auto s = m_StreamingDestination; + RequestDestination (to, + [s](std::shared_ptr ls) + { + if (ls) s->SendPing (ls); + }); + } + } + } + + void ClientDestination::SendPing (std::shared_ptr to) + { + auto s = m_StreamingDestination; + RequestDestinationWithEncryptedLeaseSet (to, + [s](std::shared_ptr ls) + { + if (ls) s->SendPing (ls); + }); + } + + std::shared_ptr ClientDestination::GetStreamingDestination (uint16_t port) const { if (port) { @@ -1088,8 +1239,9 @@ namespace client if (it != m_StreamingDestinationsByPorts.end ()) return it->second; } - // if port is zero or not found, use default destination - return m_StreamingDestination; + else // if port is zero, use default destination + return m_StreamingDestination; + return nullptr; } void ClientDestination::AcceptStreams (const i2p::stream::StreamingDestination::Acceptor& acceptor) @@ -1117,7 +1269,7 @@ namespace client m_StreamingDestination->AcceptOnce (acceptor); } - std::shared_ptr ClientDestination::CreateStreamingDestination (int port, bool gzip) + std::shared_ptr ClientDestination::CreateStreamingDestination (uint16_t port, bool gzip) { auto dest = std::make_shared (GetSharedFromThis (), port, gzip); if (port) @@ -1127,6 +1279,21 @@ namespace client return dest; } + std::shared_ptr ClientDestination::RemoveStreamingDestination (uint16_t port) + { + if (port) + { + auto it = m_StreamingDestinationsByPorts.find (port); + if (it != m_StreamingDestinationsByPorts.end ()) + { + auto ret = it->second; + m_StreamingDestinationsByPorts.erase (it); + return ret; + } + } + return nullptr; + } + i2p::datagram::DatagramDestination * ClientDestination::CreateDatagramDestination (bool gzip) { if (m_DatagramDestination == nullptr) @@ -1173,10 +1340,10 @@ namespace client f1.write ((char *)keys->priv, 256); return; } - LogPrint(eLogError, "Destinations: Can't save keys to ", path); + LogPrint(eLogCritical, "Destinations: Can't save keys to ", path); } - void ClientDestination::CreateNewLeaseSet (std::vector > tunnels) + void ClientDestination::CreateNewLeaseSet (const std::vector >& tunnels) { std::shared_ptr leaseSet; if (GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_LEASESET) @@ -1214,26 +1381,26 @@ namespace client if (m_DatagramDestination) m_DatagramDestination->CleanUp (); } - bool ClientDestination::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, i2p::data::CryptoKeyType preferredCrypto) const + bool ClientDestination::Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const { - if (preferredCrypto == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET) + if (preferredCrypto == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) if (m_ECIESx25519EncryptionKey && m_ECIESx25519EncryptionKey->decryptor) - return m_ECIESx25519EncryptionKey->decryptor->Decrypt (encrypted, data, ctx, true); + return m_ECIESx25519EncryptionKey->decryptor->Decrypt (encrypted, data); if (m_StandardEncryptionKey && m_StandardEncryptionKey->decryptor) - return m_StandardEncryptionKey->decryptor->Decrypt (encrypted, data, ctx, true); + return m_StandardEncryptionKey->decryptor->Decrypt (encrypted, data); else - LogPrint (eLogError, "Destinations: decryptor is not set"); + LogPrint (eLogError, "Destinations: Decryptor is not set"); return false; } bool ClientDestination::SupportsEncryptionType (i2p::data::CryptoKeyType keyType) const { - return keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET ? (bool)m_ECIESx25519EncryptionKey : (bool)m_StandardEncryptionKey; + return keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD ? (bool)m_ECIESx25519EncryptionKey : (bool)m_StandardEncryptionKey; } const uint8_t * ClientDestination::GetEncryptionPublicKey (i2p::data::CryptoKeyType keyType) const { - if (keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET) + if (keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) return m_ECIESx25519EncryptionKey ? m_ECIESx25519EncryptionKey->pub : nullptr; return m_StandardEncryptionKey ? m_StandardEncryptionKey->pub : nullptr; } @@ -1250,7 +1417,7 @@ namespace client if (pubKey.FromBase64 (it.second.substr (pos+1))) m_AuthKeys->push_back (pubKey); else - LogPrint (eLogError, "Destination: Unexpected auth key ", it.second.substr (pos+1)); + LogPrint (eLogCritical, "Destination: Unexpected auth key: ", it.second.substr (pos+1)); } } } diff --git a/libi2pd/Destination.h b/libi2pd/Destination.h index a7567534..3b395f4d 100644 --- a/libi2pd/Destination.h +++ b/libi2pd/Destination.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -53,6 +54,10 @@ namespace client const int DEFAULT_INBOUND_TUNNELS_QUANTITY = 5; const char I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY[] = "outbound.quantity"; const int DEFAULT_OUTBOUND_TUNNELS_QUANTITY = 5; + const char I2CP_PARAM_INBOUND_TUNNELS_LENGTH_VARIANCE[] = "inbound.lengthVariance"; + const int DEFAULT_INBOUND_TUNNELS_LENGTH_VARIANCE = 0; + const char I2CP_PARAM_OUTBOUND_TUNNELS_LENGTH_VARIANCE[] = "outbound.lengthVariance"; + const int DEFAULT_OUTBOUND_TUNNELS_LENGTH_VARIANCE = 0; const char I2CP_PARAM_EXPLICIT_PEERS[] = "explicitPeers"; const int STREAM_REQUEST_TIMEOUT = 60; //in seconds const char I2CP_PARAM_TAGS_TO_SEND[] = "crypto.tagsToSend"; @@ -61,8 +66,9 @@ namespace client const char I2CP_PARAM_RATCHET_OUTBOUND_TAGS[] = "crypto.ratchet.outboundTags"; // not used yet const char I2CP_PARAM_INBOUND_NICKNAME[] = "inbound.nickname"; const char I2CP_PARAM_OUTBOUND_NICKNAME[] = "outbound.nickname"; + const char I2CP_PARAM_DONT_PUBLISH_LEASESET[] = "i2cp.dontPublishLeaseSet"; const char I2CP_PARAM_LEASESET_TYPE[] = "i2cp.leaseSetType"; - const int DEFAULT_LEASESET_TYPE = 1; + const int DEFAULT_LEASESET_TYPE = 3; const char I2CP_PARAM_LEASESET_ENCRYPTION_TYPE[] = "i2cp.leaseSetEncType"; const char I2CP_PARAM_LEASESET_PRIV_KEY[] = "i2cp.leaseSetPrivKey"; // PSK decryption key, base64 const char I2CP_PARAM_LEASESET_AUTH_TYPE[] = "i2cp.leaseSetAuthType"; @@ -79,7 +85,7 @@ namespace client const char I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY[] = "i2p.streaming.initialAckDelay"; const int DEFAULT_INITIAL_ACK_DELAY = 200; // milliseconds const char I2CP_PARAM_STREAMING_ANSWER_PINGS[] = "i2p.streaming.answerPings"; - const int DEFAULT_ANSWER_PINGS = true; + const int DEFAULT_ANSWER_PINGS = true; typedef std::function stream)> StreamRequestComplete; @@ -133,25 +139,28 @@ namespace client // override GarlicDestination bool SubmitSessionKey (const uint8_t * key, const uint8_t * tag); + void SubmitECIESx25519Key (const uint8_t * key, uint64_t tag); void ProcessGarlicMessage (std::shared_ptr msg); void ProcessDeliveryStatusMessage (std::shared_ptr msg); void SetLeaseSetUpdated (); + bool IsPublic () const { return m_IsPublic; }; + void SetPublic (bool pub) { m_IsPublic = pub; }; + protected: // implements GarlicDestination void HandleI2NPMessage (const uint8_t * buf, size_t len); - bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len); + bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID); void SetLeaseSet (std::shared_ptr newLeaseSet); int GetLeaseSetType () const { return m_LeaseSetType; }; void SetLeaseSetType (int leaseSetType) { m_LeaseSetType = leaseSetType; }; int GetAuthType () const { return m_AuthType; }; - bool IsPublic () const { return m_IsPublic; }; virtual void CleanupDestination () {}; // additional clean up in derived classes // I2CP virtual void HandleDataMessage (const uint8_t * buf, size_t len) = 0; - virtual void CreateNewLeaseSet (std::vector > tunnels) = 0; + virtual void CreateNewLeaseSet (const std::vector >& tunnels) = 0; private: @@ -176,8 +185,8 @@ namespace client boost::asio::io_service& m_Service; mutable std::mutex m_RemoteLeaseSetsMutex; - std::map > m_RemoteLeaseSets; - std::map > m_LeaseSetRequests; + std::unordered_map > m_RemoteLeaseSets; + std::unordered_map > m_LeaseSetRequests; std::shared_ptr m_Pool; std::mutex m_LeaseSetMutex; @@ -233,25 +242,30 @@ namespace client int GetRefCounter () const { return m_RefCounter; }; // streaming - std::shared_ptr CreateStreamingDestination (int port, bool gzip = true); // additional - std::shared_ptr GetStreamingDestination (int port = 0) const; + std::shared_ptr CreateStreamingDestination (uint16_t port, bool gzip = true); // additional + std::shared_ptr GetStreamingDestination (uint16_t port = 0) const; + std::shared_ptr RemoveStreamingDestination (uint16_t port); // following methods operate with default streaming destination - void CreateStream (StreamRequestComplete streamRequestComplete, const i2p::data::IdentHash& dest, int port = 0); - void CreateStream (StreamRequestComplete streamRequestComplete, std::shared_ptr dest, int port = 0); - std::shared_ptr CreateStream (std::shared_ptr remote, int port = 0); + void CreateStream (StreamRequestComplete streamRequestComplete, const i2p::data::IdentHash& dest, uint16_t port = 0); + void CreateStream (StreamRequestComplete streamRequestComplete, std::shared_ptr dest, uint16_t port = 0); + std::shared_ptr CreateStream (const i2p::data::IdentHash& dest, uint16_t port = 0); // sync + std::shared_ptr CreateStream (std::shared_ptr dest, uint16_t port = 0); // sync + std::shared_ptr CreateStream (std::shared_ptr remote, uint16_t port = 0); + void SendPing (const i2p::data::IdentHash& to); + void SendPing (std::shared_ptr to); void AcceptStreams (const i2p::stream::StreamingDestination::Acceptor& acceptor); void StopAcceptingStreams (); bool IsAcceptingStreams () const; void AcceptOnce (const i2p::stream::StreamingDestination::Acceptor& acceptor); int GetStreamingAckDelay () const { return m_StreamingAckDelay; } bool IsStreamingAnswerPings () const { return m_IsStreamingAnswerPings; } - + // datagram i2p::datagram::DatagramDestination * GetDatagramDestination () const { return m_DatagramDestination; }; i2p::datagram::DatagramDestination * CreateDatagramDestination (bool gzip = true); // implements LocalDestination - bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, i2p::data::CryptoKeyType preferredCrypto) const; + bool Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const; std::shared_ptr GetIdentity () const { return m_Keys.GetPublic (); }; bool SupportsEncryptionType (i2p::data::CryptoKeyType keyType) const; const uint8_t * GetEncryptionPublicKey (i2p::data::CryptoKeyType keyType) const; @@ -261,7 +275,7 @@ namespace client void CleanupDestination (); // I2CP void HandleDataMessage (const uint8_t * buf, size_t len); - void CreateNewLeaseSet (std::vector > tunnels); + void CreateNewLeaseSet (const std::vector >& tunnels); private: @@ -271,6 +285,9 @@ namespace client void PersistTemporaryKeys (EncryptionKey * keys, bool isSingleKey); void ReadAuthKey (const std::string& group, const std::map * params); + template + std::shared_ptr CreateStreamSync (const Dest& dest, uint16_t port); + private: i2p::data::PrivateKeys m_Keys; @@ -281,6 +298,7 @@ namespace client bool m_IsStreamingAnswerPings; std::shared_ptr m_StreamingDestination; // default std::map > m_StreamingDestinationsByPorts; + std::shared_ptr m_LastStreamingDestination; uint16_t m_LastPort; // for server tunnels i2p::datagram::DatagramDestination * m_DatagramDestination; int m_RefCounter; // how many clients(tunnels) use this destination diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index aef273ed..3b3ed485 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -9,6 +9,7 @@ #include #include #include "Log.h" +#include "util.h" #include "Crypto.h" #include "Elligator.h" #include "Tag.h" @@ -30,29 +31,29 @@ namespace garlic uint8_t keydata[64]; i2p::crypto::HKDF (rootKey, k, 32, "KDFDHRatchetStep", keydata); // keydata = HKDF(rootKey, k, "KDFDHRatchetStep", 64) memcpy (m_NextRootKey, keydata, 32); // nextRootKey = keydata[0:31] - i2p::crypto::HKDF (keydata + 32, nullptr, 0, "TagAndKeyGenKeys", m_KeyData.buf); + i2p::crypto::HKDF (keydata + 32, nullptr, 0, "TagAndKeyGenKeys", m_SessionTagKeyData); // [sessTag_ck, symmKey_ck] = HKDF(keydata[32:63], ZEROLEN, "TagAndKeyGenKeys", 64) - memcpy (m_SymmKeyCK, m_KeyData.buf + 32, 32); + memcpy (m_SymmKeyCK, (const uint8_t *)m_SessionTagKeyData + 32, 32); m_NextSymmKeyIndex = 0; } void RatchetTagSet::NextSessionTagRatchet () { - i2p::crypto::HKDF (m_KeyData.GetSessTagCK (), nullptr, 0, "STInitialization", m_KeyData.buf); // [sessTag_ck, sesstag_constant] = HKDF(sessTag_ck, ZEROLEN, "STInitialization", 64) - memcpy (m_SessTagConstant, m_KeyData.GetSessTagConstant (), 32); + i2p::crypto::HKDF (m_SessionTagKeyData, nullptr, 0, "STInitialization", m_SessionTagKeyData); // [sessTag_ck, sesstag_constant] = HKDF(sessTag_ck, ZEROLEN, "STInitialization", 64) + memcpy (m_SessTagConstant, (const uint8_t *)m_SessionTagKeyData + 32, 32); // SESSTAG_CONSTANT = keydata[32:63] m_NextIndex = 0; } uint64_t RatchetTagSet::GetNextSessionTag () { - i2p::crypto::HKDF (m_KeyData.GetSessTagCK (), m_SessTagConstant, 32, "SessionTagKeyGen", m_KeyData.buf); // [sessTag_ck, tag] = HKDF(sessTag_chainkey, SESSTAG_CONSTANT, "SessionTagKeyGen", 64) m_NextIndex++; if (m_NextIndex >= 65535) { LogPrint (eLogError, "Garlic: Tagset ", GetTagSetID (), " is empty"); return 0; } - return m_KeyData.GetTag (); + i2p::crypto::HKDF (m_SessionTagKeyData, m_SessTagConstant, 32, "SessionTagKeyGen", m_SessionTagKeyData); // [sessTag_ck, tag] = HKDF(sessTag_chainkey, SESSTAG_CONSTANT, "SessionTagKeyGen", 64) + return m_SessionTagKeyData.GetLL ()[4]; // tag = keydata[32:39] } void RatchetTagSet::GetSymmKey (int index, uint8_t * key) @@ -89,102 +90,87 @@ namespace garlic } void RatchetTagSet::DeleteSymmKey (int index) - { + { m_ItermediateSymmKeys.erase (index); } - - void RatchetTagSet::Expire () + + void ReceiveRatchetTagSet::Expire () { if (!m_ExpirationTimestamp) m_ExpirationTimestamp = i2p::util::GetSecondsSinceEpoch () + ECIESX25519_PREVIOUS_TAGSET_EXPIRATION_TIMEOUT; } - bool RatchetTagSet::HandleNextMessage (uint8_t * buf, size_t len, int index) + bool ReceiveRatchetTagSet::IsExpired (uint64_t ts) const + { + return m_ExpirationTimestamp && ts > m_ExpirationTimestamp; + } + + bool ReceiveRatchetTagSet::IsIndexExpired (int index) const + { + return index < m_TrimBehindIndex; + } + + bool ReceiveRatchetTagSet::HandleNextMessage (uint8_t * buf, size_t len, int index) { auto session = GetSession (); if (!session) return false; return session->HandleNextMessage (buf, len, shared_from_this (), index); - } - - DatabaseLookupTagSet::DatabaseLookupTagSet (GarlicDestination * destination, const uint8_t * key): - RatchetTagSet (nullptr), m_Destination (destination) - { - memcpy (m_Key, key, 32); - Expire (); } - - bool DatabaseLookupTagSet::HandleNextMessage (uint8_t * buf, size_t len, int index) + + SymmetricKeyTagSet::SymmetricKeyTagSet (GarlicDestination * destination, const uint8_t * key): + ReceiveRatchetTagSet (nullptr), m_Destination (destination) + { + memcpy (m_Key, key, 32); + Expire (); + } + + bool SymmetricKeyTagSet::HandleNextMessage (uint8_t * buf, size_t len, int index) { if (len < 24) return false; uint8_t nonce[12]; memset (nonce, 0, 12); // n = 0 - size_t offset = 8; // first 8 bytes is reply tag used as AD + size_t offset = 8; // first 8 bytes is reply tag used as AD len -= 16; // poly1305 if (!i2p::crypto::AEADChaCha20Poly1305 (buf + offset, len - offset, buf, 8, m_Key, nonce, buf + offset, len - offset, false)) // decrypt { - LogPrint (eLogWarning, "Garlic: Lookup reply AEAD decryption failed"); + LogPrint (eLogWarning, "Garlic: Symmetric key tagset AEAD decryption failed"); return false; } // we assume 1 I2NP block with delivery type local - if (offset + 3 > len) - { - LogPrint (eLogWarning, "Garlic: Lookup reply is too short ", len); + if (offset + 3 > len) + { + LogPrint (eLogWarning, "Garlic: Symmetric key tagset is too short ", len); return false; - } + } if (buf[offset] != eECIESx25519BlkGalicClove) { - LogPrint (eLogWarning, "Garlic: Lookup reply unexpected block ", (int)buf[offset]); + LogPrint (eLogWarning, "Garlic: Symmetric key tagset unexpected block ", (int)buf[offset]); return false; - } + } offset++; auto size = bufbe16toh (buf + offset); offset += 2; - if (offset + size > len) + if (offset + size > len) { - LogPrint (eLogWarning, "Garlic: Lookup reply block is too long ", size); + LogPrint (eLogWarning, "Garlic: Symmetric key tagset block is too long ", size); return false; - } + } if (m_Destination) - m_Destination->HandleECIESx25519GarlicClove (buf + offset, size); + m_Destination->HandleECIESx25519GarlicClove (buf + offset, size); return true; - } - - ECIESX25519AEADRatchetSession::ECIESX25519AEADRatchetSession (GarlicDestination * owner, bool attachLeaseSet): - GarlicRoutingSession (owner, attachLeaseSet) + } + + ECIESX25519AEADRatchetSession::ECIESX25519AEADRatchetSession (GarlicDestination * owner, bool attachLeaseSetNS): + GarlicRoutingSession (owner, true) { + if (!attachLeaseSetNS) SetLeaseSetUpdateStatus (eLeaseSetUpToDate); RAND_bytes (m_PaddingSizes, 32); m_NextPaddingSize = 0; - ResetKeys (); } ECIESX25519AEADRatchetSession::~ECIESX25519AEADRatchetSession () { } - void ECIESX25519AEADRatchetSession::ResetKeys () - { - static const uint8_t protocolNameHash[32] = - { - 0x4c, 0xaf, 0x11, 0xef, 0x2c, 0x8e, 0x36, 0x56, 0x4c, 0x53, 0xe8, 0x88, 0x85, 0x06, 0x4d, 0xba, - 0xac, 0xbe, 0x00, 0x54, 0xad, 0x17, 0x8f, 0x80, 0x79, 0xa6, 0x46, 0x82, 0x7e, 0x6e, 0xe4, 0x0c - }; // SHA256("Noise_IKelg2+hs2_25519_ChaChaPoly_SHA256"), 40 bytes - static const uint8_t hh[32] = - { - 0x9c, 0xcf, 0x85, 0x2c, 0xc9, 0x3b, 0xb9, 0x50, 0x44, 0x41, 0xe9, 0x50, 0xe0, 0x1d, 0x52, 0x32, - 0x2e, 0x0d, 0x47, 0xad, 0xd1, 0xe9, 0xa5, 0x55, 0xf7, 0x55, 0xb5, 0x69, 0xae, 0x18, 0x3b, 0x5c - }; // SHA256 (protocolNameHash) - memcpy (m_CK, protocolNameHash, 32); - memcpy (m_H, hh, 32); - } - - void ECIESX25519AEADRatchetSession::MixHash (const uint8_t * buf, size_t len) - { - SHA256_CTX ctx; - SHA256_Init (&ctx); - SHA256_Update (&ctx, m_H, 32); - SHA256_Update (&ctx, buf, len); - SHA256_Final (m_H, &ctx); - } - void ECIESX25519AEADRatchetSession::CreateNonce (uint64_t seqn, uint8_t * nonce) { memset (nonce, 0, 4); @@ -195,11 +181,11 @@ namespace garlic { bool ineligible = false; while (!ineligible) - { + { m_EphemeralKeys = i2p::transport::transports.GetNextX25519KeysPair (); ineligible = m_EphemeralKeys->IsElligatorIneligible (); if (!ineligible) // we haven't tried it yet - { + { if (i2p::crypto::GetElligator ()->Encode (m_EphemeralKeys->GetPublicKey (), buf)) return true; // success // otherwise return back @@ -208,9 +194,9 @@ namespace garlic } else i2p::transport::transports.ReuseX25519KeysPair (m_EphemeralKeys); - } + } // we still didn't find elligator eligible pair - for (int i = 0; i < 10; i++) + for (int i = 0; i < 25; i++) { // create new m_EphemeralKeys = std::make_shared(); @@ -222,22 +208,19 @@ namespace garlic // let NTCP2 use it m_EphemeralKeys->SetElligatorIneligible (); i2p::transport::transports.ReuseX25519KeysPair (m_EphemeralKeys); - } + } } LogPrint (eLogError, "Garlic: Can't generate elligator eligible x25519 keys"); return false; } - std::shared_ptr ECIESX25519AEADRatchetSession::CreateNewSessionTagset () + void ECIESX25519AEADRatchetSession::InitNewSessionTagset (std::shared_ptr tagsetNsr) const { uint8_t tagsetKey[32]; i2p::crypto::HKDF (m_CK, nullptr, 0, "SessionReplyTags", tagsetKey, 32); // tagsetKey = HKDF(chainKey, ZEROLEN, "SessionReplyTags", 32) // Session Tag Ratchet - auto tagsetNsr = (m_State == eSessionStateNewSessionReceived) ? std::make_shared(shared_from_this ()): - std::make_shared(shared_from_this ()); tagsetNsr->DHInitialize (m_CK, tagsetKey); // tagset_nsr = DH_INITIALIZE(chainKey, tagsetKey) tagsetNsr->NextSessionTagRatchet (); - return tagsetNsr; } bool ECIESX25519AEADRatchetSession::HandleNewIncomingSession (const uint8_t * buf, size_t len) @@ -245,7 +228,7 @@ namespace garlic if (!GetOwner ()) return false; // we are Bob // KDF1 - MixHash (GetOwner ()->GetEncryptionPublicKey (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET), 32); // h = SHA256(h || bpk) + i2p::crypto::InitNoiseIKState (GetNoiseState (), GetOwner ()->GetEncryptionPublicKey (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)); // bpk if (!i2p::crypto::GetElligator ()->Decode (buf, m_Aepk)) { @@ -256,8 +239,12 @@ namespace garlic MixHash (m_Aepk, 32); // h = SHA256(h || aepk) uint8_t sharedSecret[32]; - GetOwner ()->Decrypt (m_Aepk, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET); // x25519(bsk, aepk) - i2p::crypto::HKDF (m_CK, sharedSecret, 32, "", m_CK); // [chainKey, key] = HKDF(chainKey, sharedSecret, "", 64) + if (!GetOwner ()->Decrypt (m_Aepk, sharedSecret, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) // x25519(bsk, aepk) + { + LogPrint (eLogWarning, "Garlic: Incorrect Alice ephemeral key"); + return false; + } + MixKey (sharedSecret); // decrypt flags/static uint8_t nonce[12], fs[32]; @@ -276,8 +263,12 @@ namespace garlic { // static key, fs is apk memcpy (m_RemoteStaticKey, fs, 32); - GetOwner ()->Decrypt (fs, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET); // x25519(bsk, apk) - i2p::crypto::HKDF (m_CK, sharedSecret, 32, "", m_CK); // [chainKey, key] = HKDF(chainKey, sharedSecret, "", 64) + if (!GetOwner ()->Decrypt (fs, sharedSecret, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) // x25519(bsk, apk) + { + LogPrint (eLogWarning, "Garlic: Incorrect Alice static key"); + return false; + } + MixKey (sharedSecret); } else // all zeros flags CreateNonce (1, nonce); @@ -289,16 +280,19 @@ namespace garlic LogPrint (eLogWarning, "Garlic: Payload section AEAD verification failed"); return false; } - if (isStatic) MixHash (buf, len); // h = SHA256(h || ciphertext) - m_State = eSessionStateNewSessionReceived; - GetOwner ()->AddECIESx25519Session (m_RemoteStaticKey, shared_from_this ()); + m_State = eSessionStateNewSessionReceived; + if (isStatic) + { + MixHash (buf, len); // h = SHA256(h || ciphertext) + GetOwner ()->AddECIESx25519Session (m_RemoteStaticKey, shared_from_this ()); + } HandlePayload (payload.data (), len - 16, nullptr, 0); return true; } - void ECIESX25519AEADRatchetSession::HandlePayload (const uint8_t * buf, size_t len, const std::shared_ptr& receiveTagset, int index) + void ECIESX25519AEADRatchetSession::HandlePayload (const uint8_t * buf, size_t len, const std::shared_ptr& receiveTagset, int index) { size_t offset = 0; while (offset < len) @@ -320,12 +314,15 @@ namespace garlic GetOwner ()->HandleECIESx25519GarlicClove (buf + offset, size); break; case eECIESx25519BlkNextKey: - LogPrint (eLogDebug, "Garlic: next key"); - HandleNextKey (buf + offset, size, receiveTagset); + LogPrint (eLogDebug, "Garlic: Next key"); + if (receiveTagset) + HandleNextKey (buf + offset, size, receiveTagset); + else + LogPrint (eLogError, "Garlic: Unexpected next key block"); break; case eECIESx25519BlkAck: { - LogPrint (eLogDebug, "Garlic: ack"); + LogPrint (eLogDebug, "Garlic: Ack"); int numAcks = size >> 2; // /4 auto offset1 = offset; for (auto i = 0; i < numAcks; i++) @@ -337,24 +334,25 @@ namespace garlic } case eECIESx25519BlkAckRequest: { - LogPrint (eLogDebug, "Garlic: ack request"); - m_AckRequests.push_back ({receiveTagset->GetTagSetID (), index}); + LogPrint (eLogDebug, "Garlic: Ack request"); + if (receiveTagset) + m_AckRequests.push_back ({receiveTagset->GetTagSetID (), index}); break; } case eECIESx25519BlkTermination: - LogPrint (eLogDebug, "Garlic: termination"); + LogPrint (eLogDebug, "Garlic: Termination"); if (GetOwner ()) GetOwner ()->RemoveECIESx25519Session (m_RemoteStaticKey); if (receiveTagset) receiveTagset->Expire (); break; case eECIESx25519BlkDateTime: - LogPrint (eLogDebug, "Garlic: datetime"); + LogPrint (eLogDebug, "Garlic: Datetime"); break; case eECIESx25519BlkOptions: - LogPrint (eLogDebug, "Garlic: options"); + LogPrint (eLogDebug, "Garlic: Options"); break; case eECIESx25519BlkPadding: - LogPrint (eLogDebug, "Garlic: padding"); + LogPrint (eLogDebug, "Garlic: Padding"); break; default: LogPrint (eLogWarning, "Garlic: Unknown block type ", (int)blk); @@ -363,7 +361,7 @@ namespace garlic } } - void ECIESX25519AEADRatchetSession::HandleNextKey (const uint8_t * buf, size_t len, const std::shared_ptr& receiveTagset) + void ECIESX25519AEADRatchetSession::HandleNextKey (const uint8_t * buf, size_t len, const std::shared_ptr& receiveTagset) { uint8_t flag = buf[0]; buf++; // flag if (flag & ECIESX25519_NEXT_KEY_REVERSE_KEY_FLAG) @@ -378,13 +376,13 @@ namespace garlic uint8_t sharedSecret[32], tagsetKey[32]; m_NextSendRatchet->key->Agree (m_NextSendRatchet->remote, sharedSecret); i2p::crypto::HKDF (sharedSecret, nullptr, 0, "XDHRatchetTagSet", tagsetKey, 32); // tagsetKey = HKDF(sharedSecret, ZEROLEN, "XDHRatchetTagSet", 32) - auto newTagset = std::make_shared (shared_from_this ()); + auto newTagset = std::make_shared (); newTagset->SetTagSetID (1 + m_NextSendRatchet->keyID + keyID); newTagset->DHInitialize (m_SendTagset->GetNextRootKey (), tagsetKey); newTagset->NextSessionTagRatchet (); m_SendTagset = newTagset; m_SendForwardKey = false; - LogPrint (eLogDebug, "Garlic: next send tagset ", newTagset->GetTagSetID (), " created"); + LogPrint (eLogDebug, "Garlic: Next send tagset ", newTagset->GetTagSetID (), " created"); } else LogPrint (eLogDebug, "Garlic: Unexpected next key ", keyID); @@ -420,14 +418,14 @@ namespace garlic uint8_t sharedSecret[32], tagsetKey[32]; m_NextReceiveRatchet->key->Agree (m_NextReceiveRatchet->remote, sharedSecret); i2p::crypto::HKDF (sharedSecret, nullptr, 0, "XDHRatchetTagSet", tagsetKey, 32); // tagsetKey = HKDF(sharedSecret, ZEROLEN, "XDHRatchetTagSet", 32) - auto newTagset = std::make_shared(shared_from_this ()); + auto newTagset = std::make_shared(shared_from_this ()); newTagset->SetTagSetID (tagsetID); newTagset->DHInitialize (receiveTagset->GetNextRootKey (), tagsetKey); newTagset->NextSessionTagRatchet (); GenerateMoreReceiveTags (newTagset, (GetOwner () && GetOwner ()->GetNumRatchetInboundTags () > 0) ? GetOwner ()->GetNumRatchetInboundTags () : ECIESX25519_MAX_NUM_GENERATED_TAGS); receiveTagset->Expire (); - LogPrint (eLogDebug, "Garlic: next receive tagset ", tagsetID, " created"); + LogPrint (eLogDebug, "Garlic: Next receive tagset ", tagsetID, " created"); } } @@ -449,12 +447,11 @@ namespace garlic m_NextSendRatchet->key = i2p::transport::transports.GetNextX25519KeysPair (); m_SendForwardKey = true; - LogPrint (eLogDebug, "Garlic: new send ratchet ", m_NextSendRatchet->newKey ? "new" : "old", " key ", m_NextSendRatchet->keyID, " created"); + LogPrint (eLogDebug, "Garlic: New send ratchet ", m_NextSendRatchet->newKey ? "new" : "old", " key ", m_NextSendRatchet->keyID, " created"); } - bool ECIESX25519AEADRatchetSession::NewOutgoingSessionMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen) + bool ECIESX25519AEADRatchetSession::NewOutgoingSessionMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen, bool isStatic) { - ResetKeys (); // we are Alice, bpk is m_RemoteStaticKey size_t offset = 0; if (!GenerateEphemeralKeysAndEncode (out + offset)) @@ -465,43 +462,69 @@ namespace garlic offset += 32; // KDF1 - MixHash (m_RemoteStaticKey, 32); // h = SHA256(h || bpk) + i2p::crypto::InitNoiseIKState (GetNoiseState (), m_RemoteStaticKey); // bpk MixHash (m_EphemeralKeys->GetPublicKey (), 32); // h = SHA256(h || aepk) uint8_t sharedSecret[32]; - m_EphemeralKeys->Agree (m_RemoteStaticKey, sharedSecret); // x25519(aesk, bpk) - i2p::crypto::HKDF (m_CK, sharedSecret, 32, "", m_CK); // [chainKey, key] = HKDF(chainKey, sharedSecret, "", 64) - // encrypt static key section - uint8_t nonce[12]; - CreateNonce (0, nonce); - if (!i2p::crypto::AEADChaCha20Poly1305 (GetOwner ()->GetEncryptionPublicKey (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET), 32, m_H, 32, m_CK + 32, nonce, out + offset, 48, true)) // encrypt + if (!m_EphemeralKeys->Agree (m_RemoteStaticKey, sharedSecret)) // x25519(aesk, bpk) { - LogPrint (eLogWarning, "Garlic: Static section AEAD encryption failed "); + LogPrint (eLogWarning, "Garlic: Incorrect Bob static key"); return false; } + MixKey (sharedSecret); + // encrypt flags/static key section + uint8_t nonce[12]; + CreateNonce (0, nonce); + const uint8_t * fs; + if (isStatic) + fs = GetOwner ()->GetEncryptionPublicKey (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); + else + { + memset (out + offset, 0, 32); // all zeros flags section + fs = out + offset; + } + if (!i2p::crypto::AEADChaCha20Poly1305 (fs, 32, m_H, 32, m_CK + 32, nonce, out + offset, 48, true)) // encrypt + { + LogPrint (eLogWarning, "Garlic: Flags/static section AEAD encryption failed "); + return false; + } + MixHash (out + offset, 48); // h = SHA256(h || ciphertext) offset += 48; // KDF2 - GetOwner ()->Decrypt (m_RemoteStaticKey, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET); // x25519 (ask, bpk) - i2p::crypto::HKDF (m_CK, sharedSecret, 32, "", m_CK); // [chainKey, key] = HKDF(chainKey, sharedSecret, "", 64) + if (isStatic) + { + GetOwner ()->Decrypt (m_RemoteStaticKey, sharedSecret, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); // x25519 (ask, bpk) + MixKey (sharedSecret); + } + else + CreateNonce (1, nonce); // encrypt payload if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, m_H, 32, m_CK + 32, nonce, out + offset, len + 16, true)) // encrypt { LogPrint (eLogWarning, "Garlic: Payload section AEAD encryption failed"); return false; } - MixHash (out + offset, len + 16); // h = SHA256(h || ciphertext) m_State = eSessionStateNewSessionSent; - if (GetOwner ()) - GenerateMoreReceiveTags (CreateNewSessionTagset (), ECIESX25519_NSR_NUM_GENERATED_TAGS); - + if (isStatic) + { + MixHash (out + offset, len + 16); // h = SHA256(h || ciphertext) + if (GetOwner ()) + { + auto tagsetNsr = std::make_shared(shared_from_this (), true); + InitNewSessionTagset (tagsetNsr); + tagsetNsr->Expire (); // let non-replied session expire + GenerateMoreReceiveTags (tagsetNsr, ECIESX25519_NSR_NUM_GENERATED_TAGS); + } + } return true; } bool ECIESX25519AEADRatchetSession::NewSessionReplyMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen) { // we are Bob - m_NSRSendTagset = CreateNewSessionTagset (); + m_NSRSendTagset = std::make_shared(); + InitNewSessionTagset (m_NSRSendTagset); uint64_t tag = m_NSRSendTagset->GetNextSessionTag (); size_t offset = 0; @@ -512,17 +535,25 @@ namespace garlic LogPrint (eLogError, "Garlic: Can't encode elligator"); return false; } - memcpy (m_NSREncodedKey, out + offset, 56); // for possible next NSR + memcpy (m_NSREncodedKey, out + offset, 32); // for possible next NSR memcpy (m_NSRH, m_H, 32); offset += 32; // KDF for Reply Key Section MixHash ((const uint8_t *)&tag, 8); // h = SHA256(h || tag) MixHash (m_EphemeralKeys->GetPublicKey (), 32); // h = SHA256(h || bepk) uint8_t sharedSecret[32]; - m_EphemeralKeys->Agree (m_Aepk, sharedSecret); // sharedSecret = x25519(besk, aepk) - i2p::crypto::HKDF (m_CK, sharedSecret, 32, "", m_CK, 32); // chainKey = HKDF(chainKey, sharedSecret, "", 32) - m_EphemeralKeys->Agree (m_RemoteStaticKey, sharedSecret); // sharedSecret = x25519(besk, apk) - i2p::crypto::HKDF (m_CK, sharedSecret, 32, "", m_CK); // [chainKey, key] = HKDF(chainKey, sharedSecret, "", 64) + if (!m_EphemeralKeys->Agree (m_Aepk, sharedSecret)) // sharedSecret = x25519(besk, aepk) + { + LogPrint (eLogWarning, "Garlic: Incorrect Alice ephemeral key"); + return false; + } + MixKey (sharedSecret); + if (!m_EphemeralKeys->Agree (m_RemoteStaticKey, sharedSecret)) // sharedSecret = x25519(besk, apk) + { + LogPrint (eLogWarning, "Garlic: Incorrect Alice static key"); + return false; + } + MixKey (sharedSecret); uint8_t nonce[12]; CreateNonce (0, nonce); // calculate hash for zero length @@ -537,10 +568,10 @@ namespace garlic uint8_t keydata[64]; i2p::crypto::HKDF (m_CK, nullptr, 0, "", keydata); // keydata = HKDF(chainKey, ZEROLEN, "", 64) // k_ab = keydata[0:31], k_ba = keydata[32:63] - auto receiveTagset = std::make_shared(shared_from_this ()); + auto receiveTagset = std::make_shared(shared_from_this()); receiveTagset->DHInitialize (m_CK, keydata); // tagset_ab = DH_INITIALIZE(chainKey, k_ab) receiveTagset->NextSessionTagRatchet (); - m_SendTagset = std::make_shared(shared_from_this ()); + m_SendTagset = std::make_shared(); m_SendTagset->DHInitialize (m_CK, keydata + 32); // tagset_ba = DH_INITIALIZE(chainKey, k_ba) m_SendTagset->NextSessionTagRatchet (); GenerateMoreReceiveTags (receiveTagset, (GetOwner () && GetOwner ()->GetNumRatchetInboundTags () > 0) ? @@ -554,7 +585,7 @@ namespace garlic } m_State = eSessionStateNewSessionReplySent; m_SessionCreatedTimestamp = i2p::util::GetSecondsSinceEpoch (); - + return true; } @@ -588,7 +619,7 @@ namespace garlic bool ECIESX25519AEADRatchetSession::HandleNewOutgoingSessionReply (uint8_t * buf, size_t len) { // we are Alice - LogPrint (eLogDebug, "Garlic: reply received"); + LogPrint (eLogDebug, "Garlic: Reply received"); const uint8_t * tag = buf; buf += 8; len -= 8; // tag uint8_t bepk[32]; // Bob's ephemeral key @@ -599,18 +630,19 @@ namespace garlic } buf += 32; len -= 32; // KDF for Reply Key Section - uint8_t h[32]; memcpy (h, m_H, 32); // save m_H + i2p::util::SaveStateHelper s(GetNoiseState ()); // restore noise state on exit MixHash (tag, 8); // h = SHA256(h || tag) MixHash (bepk, 32); // h = SHA256(h || bepk) uint8_t sharedSecret[32]; - if (m_State == eSessionStateNewSessionSent) + if (!m_EphemeralKeys->Agree (bepk, sharedSecret)) // sharedSecret = x25519(aesk, bepk) { - // only fist time, we assume ephemeral keys the same - m_EphemeralKeys->Agree (bepk, sharedSecret); // sharedSecret = x25519(aesk, bepk) - i2p::crypto::HKDF (m_CK, sharedSecret, 32, "", m_CK, 32); // chainKey = HKDF(chainKey, sharedSecret, "", 32) - GetOwner ()->Decrypt (bepk, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET); // x25519 (ask, bepk) - i2p::crypto::HKDF (m_CK, sharedSecret, 32, "", m_CK); // [chainKey, key] = HKDF(chainKey, sharedSecret, "", 64) + LogPrint (eLogWarning, "Garlic: Incorrect Bob ephemeral key"); + return false; } + MixKey (sharedSecret); + GetOwner ()->Decrypt (bepk, sharedSecret, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); // x25519 (ask, bepk) + MixKey (sharedSecret); + uint8_t nonce[12]; CreateNonce (0, nonce); // calculate hash for zero length @@ -626,11 +658,12 @@ namespace garlic i2p::crypto::HKDF (m_CK, nullptr, 0, "", keydata); // keydata = HKDF(chainKey, ZEROLEN, "", 64) if (m_State == eSessionStateNewSessionSent) { + // only first time, then we keep using existing tagsets // k_ab = keydata[0:31], k_ba = keydata[32:63] - m_SendTagset = std::make_shared(shared_from_this ()); + m_SendTagset = std::make_shared(); m_SendTagset->DHInitialize (m_CK, keydata); // tagset_ab = DH_INITIALIZE(chainKey, k_ab) m_SendTagset->NextSessionTagRatchet (); - auto receiveTagset = std::make_shared(shared_from_this ()); + auto receiveTagset = std::make_shared(shared_from_this ()); receiveTagset->DHInitialize (m_CK, keydata + 32); // tagset_ba = DH_INITIALIZE(chainKey, k_ba) receiveTagset->NextSessionTagRatchet (); GenerateMoreReceiveTags (receiveTagset, (GetOwner () && GetOwner ()->GetNumRatchetInboundTags () > 0) ? @@ -647,11 +680,10 @@ namespace garlic if (m_State == eSessionStateNewSessionSent) { m_State = eSessionStateEstablished; - m_EphemeralKeys = nullptr; + //m_EphemeralKeys = nullptr; // TODO: delete after a while m_SessionCreatedTimestamp = i2p::util::GetSecondsSinceEpoch (); GetOwner ()->AddECIESx25519Session (m_RemoteStaticKey, shared_from_this ()); } - memcpy (m_H, h, 32); // restore m_H HandlePayload (buf, len - 16, nullptr, 0); // we have received reply to NS with LeaseSet in it @@ -667,6 +699,13 @@ namespace garlic auto index = m_SendTagset->GetNextIndex (); CreateNonce (index, nonce); // tag's index uint64_t tag = m_SendTagset->GetNextSessionTag (); + if (!tag) + { + LogPrint (eLogError, "Garlic: Can't create new ECIES-X25519-AEAD-Ratchet tag for send tagset"); + if (GetOwner ()) + GetOwner ()->RemoveECIESx25519Session (m_RemoteStaticKey); + return false; + } memcpy (out, &tag, 8); // ad = The session tag, 8 bytes // ciphertext = ENCRYPT(k, n, payload, ad) @@ -683,7 +722,7 @@ namespace garlic } bool ECIESX25519AEADRatchetSession::HandleExistingSessionMessage (uint8_t * buf, size_t len, - std::shared_ptr receiveTagset, int index) + std::shared_ptr receiveTagset, int index) { uint8_t nonce[12]; CreateNonce (index, nonce); // tag's index @@ -698,32 +737,31 @@ namespace garlic } HandlePayload (payload, len - 16, receiveTagset, index); if (GetOwner ()) - { + { int moreTags = 0; if (GetOwner ()->GetNumRatchetInboundTags () > 0) // override in settings? { if (receiveTagset->GetNextIndex () - index < GetOwner ()->GetNumRatchetInboundTags ()/2) moreTags = GetOwner ()->GetNumRatchetInboundTags (); + index -= GetOwner ()->GetNumRatchetInboundTags (); // trim behind } else - { + { moreTags = ECIESX25519_MIN_NUM_GENERATED_TAGS + (index >> 2); // N/4 if (moreTags > ECIESX25519_MAX_NUM_GENERATED_TAGS) moreTags = ECIESX25519_MAX_NUM_GENERATED_TAGS; moreTags -= (receiveTagset->GetNextIndex () - index); - } + index -= ECIESX25519_MAX_NUM_GENERATED_TAGS; // trim behind + } if (moreTags > 0) - { GenerateMoreReceiveTags (receiveTagset, moreTags); - index -= (moreTags >> 1); // /2 - if (index > 0) - receiveTagset->SetTrimBehind (index); - } - } + if (index > 0) + receiveTagset->SetTrimBehind (index); + } return true; } bool ECIESX25519AEADRatchetSession::HandleNextMessage (uint8_t * buf, size_t len, - std::shared_ptr receiveTagset, int index) + std::shared_ptr receiveTagset, int index) { m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch (); switch (m_State) @@ -736,16 +774,19 @@ namespace garlic [[fallthrough]]; #endif case eSessionStateEstablished: - if (HandleExistingSessionMessage (buf, len, receiveTagset, index)) return true; - // check NSR just in case - LogPrint (eLogDebug, "Garlic: check for out of order NSR with index ", index); - if (receiveTagset->GetNextIndex () - index < ECIESX25519_NSR_NUM_GENERATED_TAGS/2) - GenerateMoreReceiveTags (receiveTagset, ECIESX25519_NSR_NUM_GENERATED_TAGS); - return HandleNewOutgoingSessionReply (buf, len); + if (receiveTagset->IsNS ()) + { + // our of sequence NSR + LogPrint (eLogDebug, "Garlic: Check for out of order NSR with index ", index); + if (receiveTagset->GetNextIndex () - index < ECIESX25519_NSR_NUM_GENERATED_TAGS/2) + GenerateMoreReceiveTags (receiveTagset, ECIESX25519_NSR_NUM_GENERATED_TAGS); + return HandleNewOutgoingSessionReply (buf, len); + } + else + return HandleExistingSessionMessage (buf, len, receiveTagset, index); case eSessionStateNew: return HandleNewIncomingSession (buf, len); case eSessionStateNewSessionSent: - receiveTagset->Expire (); // NSR tagset return HandleNewOutgoingSessionReply (buf, len); default: return false; @@ -755,8 +796,9 @@ namespace garlic std::shared_ptr ECIESX25519AEADRatchetSession::WrapSingleMessage (std::shared_ptr msg) { - auto payload = CreatePayload (msg, m_State != eSessionStateEstablished); - size_t len = payload.size (); + uint8_t * payload = GetOwner ()->GetPayloadBuffer (); + if (!payload) return nullptr; + size_t len = CreatePayload (msg, m_State != eSessionStateEstablished, payload); if (!len) return nullptr; auto m = NewI2NPMessage (len + 100); // 96 + 4 m->Align (12); // in order to get buf aligned to 16 (12 + 4) @@ -765,25 +807,30 @@ namespace garlic switch (m_State) { case eSessionStateEstablished: - if (!NewExistingSessionMessage (payload.data (), payload.size (), buf, m->maxLen)) + if (!NewExistingSessionMessage (payload, len, buf, m->maxLen)) return nullptr; len += 24; break; case eSessionStateNew: - if (!NewOutgoingSessionMessage (payload.data (), payload.size (), buf, m->maxLen)) + if (!NewOutgoingSessionMessage (payload, len, buf, m->maxLen)) return nullptr; len += 96; break; case eSessionStateNewSessionReceived: - if (!NewSessionReplyMessage (payload.data (), payload.size (), buf, m->maxLen)) + if (!NewSessionReplyMessage (payload, len, buf, m->maxLen)) return nullptr; len += 72; break; case eSessionStateNewSessionReplySent: - if (!NextNewSessionReplyMessage (payload.data (), payload.size (), buf, m->maxLen)) + if (!NextNewSessionReplyMessage (payload, len, buf, m->maxLen)) return nullptr; len += 72; break; + case eSessionStateOneTime: + if (!NewOutgoingSessionMessage (payload, len, buf, m->maxLen, false)) + return nullptr; + len += 96; + break; default: return nullptr; } @@ -794,20 +841,29 @@ namespace garlic return m; } - std::vector ECIESX25519AEADRatchetSession::CreatePayload (std::shared_ptr msg, bool first) + std::shared_ptr ECIESX25519AEADRatchetSession::WrapOneTimeMessage (std::shared_ptr msg) + { + m_State = eSessionStateOneTime; + return WrapSingleMessage (msg); + } + + size_t ECIESX25519AEADRatchetSession::CreatePayload (std::shared_ptr msg, bool first, uint8_t * payload) { uint64_t ts = i2p::util::GetMillisecondsSinceEpoch (); size_t payloadLen = 0; if (first) payloadLen += 7;// datatime if (msg) - { + { payloadLen += msg->GetPayloadLength () + 13; if (m_Destination) payloadLen += 32; - } - auto leaseSet = (GetLeaseSetUpdateStatus () == eLeaseSetUpdated || - (GetLeaseSetUpdateStatus () == eLeaseSetSubmitted && - ts > GetLeaseSetSubmissionTime () + LEASET_CONFIRMATION_TIMEOUT)) ? - GetOwner ()->GetLeaseSet () : nullptr; + } + if (GetLeaseSetUpdateStatus () == eLeaseSetSubmitted && ts > GetLeaseSetSubmissionTime () + LEASET_CONFIRMATION_TIMEOUT) + { + // resubmit non-confirmed LeaseSet + SetLeaseSetUpdateStatus (eLeaseSetUpdated); + SetSharedRoutingPath (nullptr); // invalidate path since leaseset was not confirmed + } + auto leaseSet = (GetLeaseSetUpdateStatus () == eLeaseSetUpdated) ? GetOwner ()->GetLeaseSet () : nullptr; if (leaseSet) { payloadLen += leaseSet->GetBufferLen () + DATABASE_STORE_HEADER_SIZE + 13; @@ -841,9 +897,9 @@ namespace garlic paddingSize = m_PaddingSizes[m_NextPaddingSize++] & 0x0F; // 0 - 15 if (m_NextPaddingSize >= 32) { - RAND_bytes (m_PaddingSizes, 32); + RAND_bytes (m_PaddingSizes, 32); m_NextPaddingSize = 0; - } + } if (delta > 3) { delta -= 3; @@ -853,89 +909,93 @@ namespace garlic payloadLen += paddingSize + 3; } } - std::vector v(payloadLen); if (payloadLen) - { + { + if (payloadLen > I2NP_MAX_MESSAGE_SIZE) + { + LogPrint (eLogError, "Garlic: Payload length ", payloadLen, " is too long"); + return 0; + } m_LastSentTimestamp = ts; size_t offset = 0; // DateTime if (first) { - v[offset] = eECIESx25519BlkDateTime; offset++; - htobe16buf (v.data () + offset, 4); offset += 2; - htobe32buf (v.data () + offset, ts/1000); offset += 4; // in seconds + payload[offset] = eECIESx25519BlkDateTime; offset++; + htobe16buf (payload + offset, 4); offset += 2; + htobe32buf (payload + offset, ts/1000); offset += 4; // in seconds } // LeaseSet if (leaseSet) { - offset += CreateLeaseSetClove (leaseSet, ts, v.data () + offset, payloadLen - offset); + offset += CreateLeaseSetClove (leaseSet, ts, payload + offset, payloadLen - offset); if (!first) { // ack request - v[offset] = eECIESx25519BlkAckRequest; offset++; - htobe16buf (v.data () + offset, 1); offset += 2; - v[offset] = 0; offset++; // flags + payload[offset] = eECIESx25519BlkAckRequest; offset++; + htobe16buf (payload + offset, 1); offset += 2; + payload[offset] = 0; offset++; // flags } } // msg - if (msg && m_Destination) - offset += CreateGarlicClove (msg, v.data () + offset, payloadLen - offset); + if (msg) + offset += CreateGarlicClove (msg, payload + offset, payloadLen - offset); // ack if (m_AckRequests.size () > 0) { - v[offset] = eECIESx25519BlkAck; offset++; - htobe16buf (v.data () + offset, m_AckRequests.size () * 4); offset += 2; + payload[offset] = eECIESx25519BlkAck; offset++; + htobe16buf (payload + offset, m_AckRequests.size () * 4); offset += 2; for (auto& it: m_AckRequests) { - htobe16buf (v.data () + offset, it.first); offset += 2; - htobe16buf (v.data () + offset, it.second); offset += 2; + htobe16buf (payload + offset, it.first); offset += 2; + htobe16buf (payload + offset, it.second); offset += 2; } m_AckRequests.clear (); } // next keys if (m_SendReverseKey) { - v[offset] = eECIESx25519BlkNextKey; offset++; - htobe16buf (v.data () + offset, m_NextReceiveRatchet->newKey ? 35 : 3); offset += 2; - v[offset] = ECIESX25519_NEXT_KEY_REVERSE_KEY_FLAG; + payload[offset] = eECIESx25519BlkNextKey; offset++; + htobe16buf (payload + offset, m_NextReceiveRatchet->newKey ? 35 : 3); offset += 2; + payload[offset] = ECIESX25519_NEXT_KEY_REVERSE_KEY_FLAG; int keyID = m_NextReceiveRatchet->keyID - 1; if (m_NextReceiveRatchet->newKey) { - v[offset] |= ECIESX25519_NEXT_KEY_KEY_PRESENT_FLAG; + payload[offset] |= ECIESX25519_NEXT_KEY_KEY_PRESENT_FLAG; keyID++; } offset++; // flag - htobe16buf (v.data () + offset, keyID); offset += 2; // keyid + htobe16buf (payload + offset, keyID); offset += 2; // keyid if (m_NextReceiveRatchet->newKey) { - memcpy (v.data () + offset, m_NextReceiveRatchet->key->GetPublicKey (), 32); + memcpy (payload + offset, m_NextReceiveRatchet->key->GetPublicKey (), 32); offset += 32; // public key } m_SendReverseKey = false; } if (m_SendForwardKey) { - v[offset] = eECIESx25519BlkNextKey; offset++; - htobe16buf (v.data () + offset, m_NextSendRatchet->newKey ? 35 : 3); offset += 2; - v[offset] = m_NextSendRatchet->newKey ? ECIESX25519_NEXT_KEY_KEY_PRESENT_FLAG : ECIESX25519_NEXT_KEY_REQUEST_REVERSE_KEY_FLAG; - if (!m_NextSendRatchet->keyID) v[offset] |= ECIESX25519_NEXT_KEY_REQUEST_REVERSE_KEY_FLAG; // for first key only + payload[offset] = eECIESx25519BlkNextKey; offset++; + htobe16buf (payload + offset, m_NextSendRatchet->newKey ? 35 : 3); offset += 2; + payload[offset] = m_NextSendRatchet->newKey ? ECIESX25519_NEXT_KEY_KEY_PRESENT_FLAG : ECIESX25519_NEXT_KEY_REQUEST_REVERSE_KEY_FLAG; + if (!m_NextSendRatchet->keyID) payload[offset] |= ECIESX25519_NEXT_KEY_REQUEST_REVERSE_KEY_FLAG; // for first key only offset++; // flag - htobe16buf (v.data () + offset, m_NextSendRatchet->keyID); offset += 2; // keyid + htobe16buf (payload + offset, m_NextSendRatchet->keyID); offset += 2; // keyid if (m_NextSendRatchet->newKey) { - memcpy (v.data () + offset, m_NextSendRatchet->key->GetPublicKey (), 32); + memcpy (payload + offset, m_NextSendRatchet->key->GetPublicKey (), 32); offset += 32; // public key } } // padding if (paddingSize) { - v[offset] = eECIESx25519BlkPadding; offset++; - htobe16buf (v.data () + offset, paddingSize); offset += 2; - memset (v.data () + offset, 0, paddingSize); offset += paddingSize; + payload[offset] = eECIESx25519BlkPadding; offset++; + htobe16buf (payload + offset, paddingSize); offset += 2; + memset (payload + offset, 0, paddingSize); offset += paddingSize; } - } - return v; + } + return payloadLen; } size_t ECIESX25519AEADRatchetSession::CreateGarlicClove (std::shared_ptr msg, uint8_t * buf, size_t len) @@ -988,51 +1048,164 @@ namespace garlic return cloveSize + 3; } - void ECIESX25519AEADRatchetSession::GenerateMoreReceiveTags (std::shared_ptr receiveTagset, int numTags) + void ECIESX25519AEADRatchetSession::GenerateMoreReceiveTags (std::shared_ptr receiveTagset, int numTags) { - for (int i = 0; i < numTags; i++) - GetOwner ()->AddECIESx25519SessionNextTag (receiveTagset); + if (GetOwner ()) + { + for (int i = 0; i < numTags; i++) + { + auto tag = GetOwner ()->AddECIESx25519SessionNextTag (receiveTagset); + if (!tag) + { + LogPrint (eLogError, "Garlic: Can't create new ECIES-X25519-AEAD-Ratchet tag for receive tagset"); + break; + } + } + } } bool ECIESX25519AEADRatchetSession::CheckExpired (uint64_t ts) { CleanupUnconfirmedLeaseSet (ts); - return ts > m_LastActivityTimestamp + ECIESX25519_EXPIRATION_TIMEOUT; + return ts > m_LastActivityTimestamp + ECIESX25519_RECEIVE_EXPIRATION_TIMEOUT && // seconds + ts*1000 > m_LastSentTimestamp + ECIESX25519_SEND_EXPIRATION_TIMEOUT*1000; // milliseconds } - std::shared_ptr WrapECIESX25519AEADRatchetMessage (std::shared_ptr msg, const uint8_t * key, uint64_t tag) + RouterIncomingRatchetSession::RouterIncomingRatchetSession (const i2p::crypto::NoiseSymmetricState& initState): + ECIESX25519AEADRatchetSession (&i2p::context, false) { - auto m = NewI2NPMessage (); - m->Align (12); // in order to get buf aligned to 16 (12 + 4) - uint8_t * buf = m->GetPayload () + 4; // 4 bytes for length + SetLeaseSetUpdateStatus (eLeaseSetDoNotSend); + SetNoiseState (initState); + } + + bool RouterIncomingRatchetSession::HandleNextMessage (const uint8_t * buf, size_t len) + { + if (!GetOwner ()) return false; + m_CurrentNoiseState = GetNoiseState (); + // we are Bob + m_CurrentNoiseState.MixHash (buf, 32); + uint8_t sharedSecret[32]; + if (!GetOwner ()->Decrypt (buf, sharedSecret, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) // x25519(bsk, aepk) + { + LogPrint (eLogWarning, "Garlic: Incorrect N ephemeral public key"); + return false; + } + m_CurrentNoiseState.MixKey (sharedSecret); + buf += 32; len -= 32; uint8_t nonce[12]; - memset (nonce, 0, 12); // n = 0 - size_t offset = 0; - memcpy (buf + offset, &tag, 8); offset += 8; - auto payload = buf + offset; - uint16_t cloveSize = msg->GetPayloadLength () + 9 + 1; - size_t len = cloveSize + 3; + CreateNonce (0, nonce); + std::vector payload (len - 16); + if (!i2p::crypto::AEADChaCha20Poly1305 (buf, len - 16, m_CurrentNoiseState.m_H, 32, + m_CurrentNoiseState.m_CK + 32, nonce, payload.data (), len - 16, false)) // decrypt + { + LogPrint (eLogWarning, "Garlic: Payload for router AEAD verification failed"); + return false; + } + HandlePayload (payload.data (), len - 16, nullptr, 0); + return true; + } + + static size_t CreateGarlicPayload (std::shared_ptr msg, uint8_t * payload, + bool datetime, size_t optimalSize) + { + size_t len = 0; + if (datetime) + { + // DateTime + payload[0] = eECIESx25519BlkDateTime; + htobe16buf (payload + 1, 4); + htobe32buf (payload + 3, i2p::util::GetSecondsSinceEpoch ()); + len = 7; + } + // I2NP + payload += len; + uint16_t cloveSize = msg->GetPayloadLength () + 10; payload[0] = eECIESx25519BlkGalicClove; // clove type htobe16buf (payload + 1, cloveSize); // size payload += 3; - *payload = 0; payload++; // flag and delivery instructions - *payload = msg->GetTypeID (); // I2NP msg type - htobe32buf (payload + 1, msg->GetMsgID ()); // msgID - htobe32buf (payload + 5, msg->GetExpiration () / 1000); // expiration in seconds - memcpy (payload + 9, msg->GetPayload (), msg->GetPayloadLength ()); + payload[0] = 0; // flag and delivery instructions + payload[1] = msg->GetTypeID (); // I2NP msg type + htobe32buf (payload + 2, msg->GetMsgID ()); // msgID + htobe32buf (payload + 6, msg->GetExpiration () / 1000); // expiration in seconds + memcpy (payload + 10, msg->GetPayload (), msg->GetPayloadLength ()); + len += cloveSize + 3; + payload += cloveSize; + // padding + int delta = (int)optimalSize - (int)len; + if (delta < 0 || delta > 3) // don't create padding if we are close to optimal size + { + uint8_t paddingSize = rand () & 0x0F; // 0 - 15 + if (delta > 3) + { + delta -= 3; + if (paddingSize > delta) paddingSize %= delta; + } + payload[0] = eECIESx25519BlkPadding; + htobe16buf (payload + 1, paddingSize); + if (paddingSize) memset (payload + 3, 0, paddingSize); + len += paddingSize + 3; + } + return len; + } - if (!i2p::crypto::AEADChaCha20Poly1305 (buf + offset, len, buf, 8, key, nonce, buf + offset, len + 16, true)) // encrypt + std::shared_ptr WrapECIESX25519Message (std::shared_ptr msg, const uint8_t * key, uint64_t tag) + { + auto m = NewI2NPMessage ((msg ? msg->GetPayloadLength () : 0) + 128); + m->Align (12); // in order to get buf aligned to 16 (12 + 4) + uint8_t * buf = m->GetPayload () + 4; // 4 bytes for length + size_t offset = 0; + memcpy (buf + offset, &tag, 8); offset += 8; + auto payload = buf + offset; + size_t len = CreateGarlicPayload (msg, payload, false, 956); // 1003 - 8 tag - 16 Poly1305 hash - 16 I2NP header - 4 garlic length - 3 local tunnel delivery + uint8_t nonce[12]; + memset (nonce, 0, 12); // n = 0 + if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, buf, 8, key, nonce, payload, len + 16, true)) // encrypt { LogPrint (eLogWarning, "Garlic: Payload section AEAD encryption failed"); return nullptr; } offset += len + 16; - htobe32buf (m->GetPayload (), offset); m->len += offset + 4; m->FillI2NPMessageHeader (eI2NPGarlic); return m; } + std::shared_ptr WrapECIESX25519MessageForRouter (std::shared_ptr msg, const uint8_t * routerPublicKey) + { + // Noise_N, we are Alice, routerPublicKey is Bob's + i2p::crypto::NoiseSymmetricState noiseState; + i2p::crypto::InitNoiseNState (noiseState, routerPublicKey); + auto m = NewI2NPMessage ((msg ? msg->GetPayloadLength () : 0) + 128); + m->Align (12); // in order to get buf aligned to 16 (12 + 4) + uint8_t * buf = m->GetPayload () + 4; // 4 bytes for length + size_t offset = 0; + auto ephemeralKeys = i2p::transport::transports.GetNextX25519KeysPair (); + memcpy (buf + offset, ephemeralKeys->GetPublicKey (), 32); + noiseState.MixHash (buf + offset, 32); // h = SHA256(h || aepk) + offset += 32; + uint8_t sharedSecret[32]; + if (!ephemeralKeys->Agree (routerPublicKey, sharedSecret)) // x25519(aesk, bpk) + { + LogPrint (eLogWarning, "Garlic: Incorrect Bob static key"); + return nullptr; + } + noiseState.MixKey (sharedSecret); + auto payload = buf + offset; + size_t len = CreateGarlicPayload (msg, payload, true, 900); // 1003 - 32 eph key - 16 Poly1305 hash - 16 I2NP header - 4 garlic length - 35 router tunnel delivery + uint8_t nonce[12]; + memset (nonce, 0, 12); + // encrypt payload + if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, noiseState.m_H, 32, noiseState.m_CK + 32, nonce, payload, len + 16, true)) // encrypt + { + LogPrint (eLogWarning, "Garlic: Payload for router AEAD encryption failed"); + return nullptr; + } + offset += len + 16; + htobe32buf (m->GetPayload (), offset); + m->len += offset + 4; + m->FillI2NPMessageHeader (eI2NPGarlic); + return m; + } } } diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index af0b5de5..301f597a 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -26,25 +26,25 @@ namespace i2p namespace garlic { const int ECIESX25519_RESTART_TIMEOUT = 120; // number of second since session creation we can restart session after - const int ECIESX25519_EXPIRATION_TIMEOUT = 480; // in seconds const int ECIESX25519_INACTIVITY_TIMEOUT = 90; // number of seconds we receive nothing and should restart if we can - const int ECIESX25519_SEND_INACTIVITY_TIMEOUT = 5000; // number of milliseconds we can send empty(pyaload only) packet after - const int ECIESX25519_INCOMING_TAGS_EXPIRATION_TIMEOUT = 600; // in seconds + const int ECIESX25519_SEND_INACTIVITY_TIMEOUT = 5000; // number of milliseconds we can send empty(pyaload only) packet after + const int ECIESX25519_SEND_EXPIRATION_TIMEOUT = 480; // in seconds + const int ECIESX25519_RECEIVE_EXPIRATION_TIMEOUT = 600; // in seconds const int ECIESX25519_PREVIOUS_TAGSET_EXPIRATION_TIMEOUT = 180; // 180 - const int ECIESX25519_TAGSET_MAX_NUM_TAGS = 4096; // number of tags we request new tagset after + const int ECIESX25519_TAGSET_MAX_NUM_TAGS = 8192; // number of tags we request new tagset after const int ECIESX25519_MIN_NUM_GENERATED_TAGS = 24; - const int ECIESX25519_MAX_NUM_GENERATED_TAGS = 160; + const int ECIESX25519_MAX_NUM_GENERATED_TAGS = 320; const int ECIESX25519_NSR_NUM_GENERATED_TAGS = 12; const size_t ECIESX25519_OPTIMAL_PAYLOAD_SIZE = 1912; // 1912 = 1956 /* to fit 2 tunnel messages */ // - 16 /* I2NP header */ - 16 /* poly hash */ - 8 /* tag */ - 4 /* garlic length */ - class ECIESX25519AEADRatchetSession; - class RatchetTagSet: public std::enable_shared_from_this + class RatchetTagSet { public: - RatchetTagSet (std::shared_ptr session): m_Session (session) {}; + RatchetTagSet () {}; + virtual ~RatchetTagSet () {}; void DHInitialize (const uint8_t * rootKey, const uint8_t * k); void NextSessionTagRatchet (); @@ -54,64 +54,62 @@ namespace garlic void GetSymmKey (int index, uint8_t * key); void DeleteSymmKey (int index); - std::shared_ptr GetSession () { return m_Session.lock (); }; int GetTagSetID () const { return m_TagSetID; }; void SetTagSetID (int tagsetID) { m_TagSetID = tagsetID; }; - void SetTrimBehind (int index) { if (index > m_TrimBehindIndex) m_TrimBehindIndex = index; }; - void Expire (); - bool IsExpired (uint64_t ts) const { return m_ExpirationTimestamp && ts > m_ExpirationTimestamp; }; - virtual bool IsIndexExpired (int index) const { return m_Session.expired () || index < m_TrimBehindIndex; }; - - virtual bool HandleNextMessage (uint8_t * buf, size_t len, int index); - private: - union - { - uint64_t ll[8]; - uint8_t buf[64]; - - const uint8_t * GetSessTagCK () const { return buf; }; // sessTag_chainKey = keydata[0:31] - const uint8_t * GetSessTagConstant () const { return buf + 32; }; // SESSTAG_CONSTANT = keydata[32:63] - uint64_t GetTag () const { return ll[4]; }; // tag = keydata[32:39] - - } m_KeyData; + i2p::data::Tag<64> m_SessionTagKeyData; uint8_t m_SessTagConstant[32], m_SymmKeyCK[32], m_CurrentSymmKeyCK[64], m_NextRootKey[32]; - int m_NextIndex, m_NextSymmKeyIndex, m_TrimBehindIndex = 0; + int m_NextIndex, m_NextSymmKeyIndex; std::unordered_map > m_ItermediateSymmKeys; - std::weak_ptr m_Session; + int m_TagSetID = 0; + }; + + class ECIESX25519AEADRatchetSession; + class ReceiveRatchetTagSet: public RatchetTagSet, + public std::enable_shared_from_this + { + public: + + ReceiveRatchetTagSet (std::shared_ptr session, bool isNS = false): + m_Session (session), m_IsNS (isNS) {}; + + bool IsNS () const { return m_IsNS; }; + std::shared_ptr GetSession () { return m_Session; }; + void SetTrimBehind (int index) { if (index > m_TrimBehindIndex) m_TrimBehindIndex = index; }; + int GetTrimBehind () const { return m_TrimBehindIndex; }; + + void Expire (); + bool IsExpired (uint64_t ts) const; + + virtual bool IsIndexExpired (int index) const; + virtual bool HandleNextMessage (uint8_t * buf, size_t len, int index); + + private: + + int m_TrimBehindIndex = 0; + std::shared_ptr m_Session; + bool m_IsNS; uint64_t m_ExpirationTimestamp = 0; }; - class NSRatchetTagSet: public RatchetTagSet - { - public: - - NSRatchetTagSet (std::shared_ptr session): - RatchetTagSet (session), m_DummySession (session) {}; - - private: - - std::shared_ptr m_DummySession; // we need a strong pointer for NS - }; - - class DatabaseLookupTagSet: public RatchetTagSet + class SymmetricKeyTagSet: public ReceiveRatchetTagSet { public: - DatabaseLookupTagSet (GarlicDestination * destination, const uint8_t * key); + SymmetricKeyTagSet (GarlicDestination * destination, const uint8_t * key); bool IsIndexExpired (int index) const { return false; }; bool HandleNextMessage (uint8_t * buf, size_t len, int index); - + private: GarlicDestination * m_Destination; uint8_t m_Key[32]; - }; - + }; + enum ECIESx25519BlockType { eECIESx25519BlkDateTime = 0, @@ -129,7 +127,9 @@ namespace garlic const uint8_t ECIESX25519_NEXT_KEY_REVERSE_KEY_FLAG = 0x02; const uint8_t ECIESX25519_NEXT_KEY_REQUEST_REVERSE_KEY_FLAG = 0x04; - class ECIESX25519AEADRatchetSession: public GarlicRoutingSession, public std::enable_shared_from_this + class ECIESX25519AEADRatchetSession: public GarlicRoutingSession, + private i2p::crypto::NoiseSymmetricState, + public std::enable_shared_from_this { enum SessionState { @@ -137,7 +137,8 @@ namespace garlic eSessionStateNewSessionReceived, eSessionStateNewSessionSent, eSessionStateNewSessionReplySent, - eSessionStateEstablished + eSessionStateEstablished, + eSessionStateOneTime }; struct DHRatchet @@ -150,15 +151,17 @@ namespace garlic public: - ECIESX25519AEADRatchetSession (GarlicDestination * owner, bool attachLeaseSet); + ECIESX25519AEADRatchetSession (GarlicDestination * owner, bool attachLeaseSetNS); ~ECIESX25519AEADRatchetSession (); - bool HandleNextMessage (uint8_t * buf, size_t len, std::shared_ptr receiveTagset, int index = 0); + bool HandleNextMessage (uint8_t * buf, size_t len, std::shared_ptr receiveTagset, int index = 0); std::shared_ptr WrapSingleMessage (std::shared_ptr msg); + std::shared_ptr WrapOneTimeMessage (std::shared_ptr msg); const uint8_t * GetRemoteStaticKey () const { return m_RemoteStaticKey; } void SetRemoteStaticKey (const uint8_t * key) { memcpy (m_RemoteStaticKey, key, 32); } + void Terminate () { m_IsTerminated = true; } void SetDestination (const i2p::data::IdentHash& dest) // TODO: { if (!m_Destination) m_Destination.reset (new i2p::data::IdentHash (dest)); @@ -167,52 +170,57 @@ namespace garlic bool CheckExpired (uint64_t ts); // true is expired bool CanBeRestarted (uint64_t ts) const { return ts > m_SessionCreatedTimestamp + ECIESX25519_RESTART_TIMEOUT; } bool IsInactive (uint64_t ts) const { return ts > m_LastActivityTimestamp + ECIESX25519_INACTIVITY_TIMEOUT && CanBeRestarted (ts); } - + bool IsRatchets () const { return true; }; + bool IsReadyToSend () const { return m_State != eSessionStateNewSessionSent; }; + bool IsTerminated () const { return m_IsTerminated; } uint64_t GetLastActivityTimestamp () const { return m_LastActivityTimestamp; }; + protected: + + i2p::crypto::NoiseSymmetricState& GetNoiseState () { return *this; }; + void SetNoiseState (const i2p::crypto::NoiseSymmetricState& state) { GetNoiseState () = state; }; + void CreateNonce (uint64_t seqn, uint8_t * nonce); + void HandlePayload (const uint8_t * buf, size_t len, const std::shared_ptr& receiveTagset, int index); + private: - void ResetKeys (); - void MixHash (const uint8_t * buf, size_t len); - void CreateNonce (uint64_t seqn, uint8_t * nonce); bool GenerateEphemeralKeysAndEncode (uint8_t * buf); // buf is 32 bytes - std::shared_ptr CreateNewSessionTagset (); + void InitNewSessionTagset (std::shared_ptr tagsetNsr) const; bool HandleNewIncomingSession (const uint8_t * buf, size_t len); bool HandleNewOutgoingSessionReply (uint8_t * buf, size_t len); - bool HandleExistingSessionMessage (uint8_t * buf, size_t len, std::shared_ptr receiveTagset, int index); - void HandlePayload (const uint8_t * buf, size_t len, const std::shared_ptr& receiveTagset, int index); - void HandleNextKey (const uint8_t * buf, size_t len, const std::shared_ptr& receiveTagset); + bool HandleExistingSessionMessage (uint8_t * buf, size_t len, std::shared_ptr receiveTagset, int index); + void HandleNextKey (const uint8_t * buf, size_t len, const std::shared_ptr& receiveTagset); - bool NewOutgoingSessionMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); + bool NewOutgoingSessionMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen, bool isStatic = true); bool NewSessionReplyMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); bool NextNewSessionReplyMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); bool NewExistingSessionMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); - std::vector CreatePayload (std::shared_ptr msg, bool first); + size_t CreatePayload (std::shared_ptr msg, bool first, uint8_t * payload); size_t CreateGarlicClove (std::shared_ptr msg, uint8_t * buf, size_t len); size_t CreateLeaseSetClove (std::shared_ptr ls, uint64_t ts, uint8_t * buf, size_t len); - void GenerateMoreReceiveTags (std::shared_ptr receiveTagset, int numTags); + void GenerateMoreReceiveTags (std::shared_ptr receiveTagset, int numTags); void NewNextSendRatchet (); private: - uint8_t m_H[32], m_CK[64] /* [chainkey, key] */, m_RemoteStaticKey[32]; + uint8_t m_RemoteStaticKey[32]; uint8_t m_Aepk[32]; // Alice's ephemeral keys, for incoming only uint8_t m_NSREncodedKey[32], m_NSRH[32], m_NSRKey[32]; // new session reply, for incoming only std::shared_ptr m_EphemeralKeys; SessionState m_State = eSessionStateNew; - uint64_t m_SessionCreatedTimestamp = 0, m_LastActivityTimestamp = 0, // incoming + uint64_t m_SessionCreatedTimestamp = 0, m_LastActivityTimestamp = 0, // incoming (in seconds) m_LastSentTimestamp = 0; // in milliseconds std::shared_ptr m_SendTagset, m_NSRSendTagset; std::unique_ptr m_Destination;// TODO: might not need it std::list > m_AckRequests; // (tagsetid, index) - bool m_SendReverseKey = false, m_SendForwardKey = false; + bool m_SendReverseKey = false, m_SendForwardKey = false, m_IsTerminated = false; std::unique_ptr m_NextReceiveRatchet, m_NextSendRatchet; uint8_t m_PaddingSizes[32], m_NextPaddingSize; - + public: // for HTTP only @@ -221,10 +229,26 @@ namespace garlic { return m_Destination ? *m_Destination : i2p::data::IdentHash (); } - }; + }; - std::shared_ptr WrapECIESX25519AEADRatchetMessage (std::shared_ptr msg, const uint8_t * key, uint64_t tag); + // single session for all incoming messages + class RouterIncomingRatchetSession: public ECIESX25519AEADRatchetSession + { + public: + + RouterIncomingRatchetSession (const i2p::crypto::NoiseSymmetricState& initState); + bool HandleNextMessage (const uint8_t * buf, size_t len); + i2p::crypto::NoiseSymmetricState& GetCurrentNoiseState () { return m_CurrentNoiseState; }; + + private: + + i2p::crypto::NoiseSymmetricState m_CurrentNoiseState; + }; + + std::shared_ptr WrapECIESX25519Message (std::shared_ptr msg, const uint8_t * key, uint64_t tag); + std::shared_ptr WrapECIESX25519MessageForRouter (std::shared_ptr msg, const uint8_t * routerPublicKey); } } #endif + diff --git a/libi2pd/Ed25519.cpp b/libi2pd/Ed25519.cpp index 791bd685..3e0795d5 100644 --- a/libi2pd/Ed25519.cpp +++ b/libi2pd/Ed25519.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -33,7 +33,7 @@ namespace crypto BN_add (l, l, tmp); BN_sub_word (two_252_2, 2); // 2^252 - 2 - // -121665*inv(121666) + // -121665*inv(121666) d = BN_new (); BN_set_word (tmp, 121666); BN_mod_inverse (tmp, tmp, q, ctx); @@ -61,7 +61,7 @@ namespace crypto BN_mod (By, By, q, ctx); // % q // precalculate Bi256 table - Bi256Carry = { Bx, By }; // B + Bi256Carry = { Bx, By }; // B for (int i = 0; i < 32; i++) { Bi256[i][0] = Bi256Carry; // first point @@ -215,7 +215,7 @@ namespace crypto if (!t1) { t1 = BN_CTX_get (ctx); BN_mul (t1, p1.x, p1.y, ctx); } if (!t2) { t2 = BN_CTX_get (ctx); BN_mul (t2, p2.x, p2.y, ctx); } BN_mul (t3, t1, t2, ctx); - BN_mul (t3, t3, d, ctx); // C = d*t1*t2 + BN_mul (t3, t3, d, ctx); // C = d*t1*t2 if (p1.z) { @@ -264,9 +264,9 @@ namespace crypto else { BN_mul (t2, p.x, p.y, ctx); // t = x*y - BN_sqr (t2, t2, ctx); // t2 = t^2 + BN_sqr (t2, t2, ctx); // t2 = t^2 } - BN_mul (t2, t2, d, ctx); // t2 = C = d*t^2 + BN_mul (t2, t2, d, ctx); // t2 = C = d*t^2 if (p.z) BN_sqr (z2, p.z, ctx); // z2 = D = z^2 else @@ -349,7 +349,7 @@ namespace crypto BN_mod_inverse (y, p.z, q, ctx); BN_mod_mul (x, p.x, y, q, ctx); // x = x/z BN_mod_mul (y, p.y, y, q, ctx); // y = y/z - return EDDSAPoint{x, y}; + return EDDSAPoint{x, y}; } else return EDDSAPoint{BN_dup (p.x), BN_dup (p.y)}; @@ -413,7 +413,7 @@ namespace crypto BIGNUM * y = BN_new (); BN_bin2bn (buf1, EDDSA25519_PUBLIC_KEY_LENGTH, y); BIGNUM * x = RecoverX (y, ctx); - if (BN_is_bit_set (x, 0) != isHighestBitSet) + if ((bool)BN_is_bit_set (x, 0) != isHighestBitSet) BN_sub (x, q, x); // x = q - x BIGNUM * z = BN_new (), * t = BN_new (); BN_one (z); BN_mod_mul (t, x, y, q, ctx); // pre-calculate t @@ -506,13 +506,13 @@ namespace crypto std::swap (z2, z3); } BN_mod_inverse (z2, z2, q, ctx); - BIGNUM * res = BN_new (); // not from ctx + BIGNUM * res = BN_new (); // not from ctx BN_mod_mul(res, x2, z2, q, ctx); BN_CTX_end (ctx); return res; } - void Ed25519::ScalarMul (const uint8_t * p, const uint8_t * e, uint8_t * buf, BN_CTX * ctx) const + void Ed25519::ScalarMul (const uint8_t * p, const uint8_t * e, uint8_t * buf, BN_CTX * ctx) const { BIGNUM * p1 = DecodeBN<32> (p); uint8_t k[32]; @@ -524,7 +524,7 @@ namespace crypto BN_free (p1); BN_free (n); BN_free (q1); } - void Ed25519::ScalarMulB (const uint8_t * e, uint8_t * buf, BN_CTX * ctx) const + void Ed25519::ScalarMulB (const uint8_t * e, uint8_t * buf, BN_CTX * ctx) const { BIGNUM *p1 = BN_new (); BN_set_word (p1, 9); uint8_t k[32]; diff --git a/libi2pd/Ed25519.h b/libi2pd/Ed25519.h index 28d4e930..470d802f 100644 --- a/libi2pd/Ed25519.h +++ b/libi2pd/Ed25519.h @@ -85,8 +85,8 @@ namespace crypto EDDSAPoint DecodePublicKey (const uint8_t * buf, BN_CTX * ctx) const; void EncodePublicKey (const EDDSAPoint& publicKey, uint8_t * buf, BN_CTX * ctx) const; #if !OPENSSL_X25519 - void ScalarMul (const uint8_t * p, const uint8_t * e, uint8_t * buf, BN_CTX * ctx) const; // p is point, e is number for x25519 - void ScalarMulB (const uint8_t * e, uint8_t * buf, BN_CTX * ctx) const; + void ScalarMul (const uint8_t * p, const uint8_t * e, uint8_t * buf, BN_CTX * ctx) const; // p is point, e is number for x25519 + void ScalarMulB (const uint8_t * e, uint8_t * buf, BN_CTX * ctx) const; #endif void BlindPublicKey (const uint8_t * pub, const uint8_t * seed, uint8_t * blinded); // for encrypted LeaseSet2, pub - 32, seed - 64, blinded - 32 void BlindPrivateKey (const uint8_t * priv, const uint8_t * seed, uint8_t * blindedPriv, uint8_t * blindedPub); // for encrypted LeaseSet2, pub - 32, seed - 64, blinded - 32 diff --git a/libi2pd/Elligator.cpp b/libi2pd/Elligator.cpp index 712e514a..25e09893 100644 --- a/libi2pd/Elligator.cpp +++ b/libi2pd/Elligator.cpp @@ -189,7 +189,7 @@ namespace crypto // assume a < p, so don't check for a % p = 0, but a = 0 only if (BN_is_zero(a)) return 0; BIGNUM * r = BN_CTX_get (ctx); - BN_mod_exp (r, a, p12, p, ctx); // r = a^((p-1)/2) mod p + BN_mod_exp (r, a, p12, p, ctx); // r = a^((p-1)/2) mod p if (BN_is_word(r, 1)) return 1; else if (BN_is_zero(r)) diff --git a/libi2pd/FS.cpp b/libi2pd/FS.cpp index 32fc3ec0..7334550f 100644 --- a/libi2pd/FS.cpp +++ b/libi2pd/FS.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -12,6 +12,7 @@ #ifdef _WIN32 #include #include +#include #endif #include "Base.h" @@ -23,6 +24,7 @@ namespace i2p { namespace fs { std::string appName = "i2pd"; std::string dataDir = ""; + std::string certsDir = ""; #ifdef _WIN32 std::string dirSep = "\\"; #else @@ -41,18 +43,62 @@ namespace fs { return dataDir; } + const std::string & GetCertsDir () { + return certsDir; + } + + const std::string GetUTF8DataDir () { +#ifdef _WIN32 + boost::filesystem::wpath path (dataDir); + auto loc = boost::filesystem::path::imbue(std::locale( std::locale(), new std::codecvt_utf8_utf16() ) ); // convert path to UTF-8 + auto dataDirUTF8 = path.string(); + boost::filesystem::path::imbue(loc); // Return locale settings back + return dataDirUTF8; +#else + return dataDir; // linux, osx, android uses UTF-8 by default +#endif + } + void DetectDataDir(const std::string & cmdline_param, bool isService) { + // with 'datadir' option if (cmdline_param != "") { dataDir = cmdline_param; return; } -#if defined(WIN32) || defined(_WIN32) - char localAppData[MAX_PATH]; + +#if !defined(MAC_OSX) && !defined(ANDROID) + // with 'service' option + if (isService) { +#ifdef _WIN32 + wchar_t commonAppData[MAX_PATH]; + if(SHGetFolderPathW(NULL, CSIDL_COMMON_APPDATA, NULL, 0, commonAppData) != S_OK) + { +#ifdef WIN32_APP + MessageBox(NULL, TEXT("Unable to get common AppData path!"), TEXT("I2Pd: error"), MB_ICONERROR | MB_OK); +#else + fprintf(stderr, "Error: Unable to get common AppData path!"); +#endif + exit(1); + } + else + { + dataDir = boost::filesystem::wpath(commonAppData).string() + "\\" + appName; + } +#else + dataDir = "/var/lib/" + appName; +#endif + return; + } +#endif + + // detect directory as usual +#ifdef _WIN32 + wchar_t localAppData[MAX_PATH]; // check executable directory first - if(!GetModuleFileName(NULL, localAppData, MAX_PATH)) + if(!GetModuleFileNameW(NULL, localAppData, MAX_PATH)) { -#if defined(WIN32_APP) +#ifdef WIN32_APP MessageBox(NULL, TEXT("Unable to get application path!"), TEXT("I2Pd: error"), MB_ICONERROR | MB_OK); #else fprintf(stderr, "Error: Unable to get application path!"); @@ -61,16 +107,17 @@ namespace fs { } else { - auto execPath = boost::filesystem::path(localAppData).parent_path(); + auto execPath = boost::filesystem::wpath(localAppData).parent_path(); // if config file exists in .exe's folder use it if(boost::filesystem::exists(execPath/"i2pd.conf")) // TODO: magic string - dataDir = execPath.string (); - else // otherwise %appdata% { - if(SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, localAppData) != S_OK) + dataDir = execPath.string (); + } else // otherwise %appdata% + { + if(SHGetFolderPathW(NULL, CSIDL_APPDATA, NULL, 0, localAppData) != S_OK) { -#if defined(WIN32_APP) +#ifdef WIN32_APP MessageBox(NULL, TEXT("Unable to get AppData path!"), TEXT("I2Pd: error"), MB_ICONERROR | MB_OK); #else fprintf(stderr, "Error: Unable to get AppData path!"); @@ -78,7 +125,9 @@ namespace fs { exit(1); } else - dataDir = std::string(localAppData) + "\\" + appName; + { + dataDir = boost::filesystem::wpath(localAppData).string() + "\\" + appName; + } } } return; @@ -96,12 +145,10 @@ namespace fs { dataDir = std::string (ext) + "/" + appName; return; } -#endif - // otherwise use /data/files +#endif // ANDROID + // use /home/user/.i2pd or /tmp/i2pd char *home = getenv("HOME"); - if (isService) { - dataDir = "/var/lib/" + appName; - } else if (home != NULL && strlen(home) > 0) { + if (home != NULL && strlen(home) > 0) { dataDir = std::string(home) + "/." + appName; } else { dataDir = "/tmp/" + appName; @@ -110,6 +157,21 @@ namespace fs { #endif } + void SetCertsDir(const std::string & cmdline_certsdir) { + if (cmdline_certsdir != "") + { + if (cmdline_certsdir[cmdline_certsdir.length()-1] == '/') + certsDir = cmdline_certsdir.substr(0, cmdline_certsdir.size()-1); // strip trailing slash + else + certsDir = cmdline_certsdir; + } + else + { + certsDir = i2p::fs::DataDirPath("certificates"); + } + return; + } + bool Init() { if (!boost::filesystem::exists(dataDir)) boost::filesystem::create_directory(dataDir); diff --git a/libi2pd/FS.h b/libi2pd/FS.h index 698e9b6b..7911c6a0 100644 --- a/libi2pd/FS.h +++ b/libi2pd/FS.h @@ -75,10 +75,16 @@ namespace fs { /** @brief Returns datadir path */ const std::string & GetDataDir(); + /** @brief Returns certsdir path */ + const std::string & GetCertsDir(); + + /** @brief Returns datadir path in UTF-8 encoding */ + const std::string GetUTF8DataDir(); + /** * @brief Set datadir either from cmdline option or using autodetection - * @param cmdline_param Value of cmdline parameter --datadir= - * @param isService Value of cmdline parameter --service + * @param cmdline_param Value of cmdline parameter --datadir= + * @param isService Value of cmdline parameter --service * * Examples of autodetected paths: * @@ -89,6 +95,19 @@ namespace fs { */ void DetectDataDir(const std::string & cmdline_datadir, bool isService = false); + /** + * @brief Set certsdir either from cmdline option or using autodetection + * @param cmdline_param Value of cmdline parameter --certsdir= + * + * Examples of autodetected paths: + * + * Windows < Vista: C:\Documents and Settings\Username\Application Data\i2pd\certificates + * Windows >= Vista: C:\Users\Username\AppData\Roaming\i2pd\certificates + * Mac: /Library/Application Support/i2pd/ or ~/Library/Application Support/i2pd/certificates + * Unix: /var/lib/i2pd/certificates (system=1) >> ~/.i2pd/ or /tmp/i2pd/certificates + */ + void SetCertsDir(const std::string & cmdline_certsdir); + /** * @brief Create subdirectories inside datadir */ @@ -96,7 +115,7 @@ namespace fs { /** * @brief Get list of files in directory - * @param path Path to directory + * @param path Path to directory * @param files Vector to store found files * @return true on success and false if directory not exists */ diff --git a/libi2pd/Family.cpp b/libi2pd/Family.cpp index fbb7b9ee..8c6d3ba4 100644 --- a/libi2pd/Family.cpp +++ b/libi2pd/Family.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -13,6 +13,7 @@ #include "FS.h" #include "Log.h" #include "Family.h" +#include "Config.h" namespace i2p { @@ -87,7 +88,7 @@ namespace data } EVP_PKEY_free (pkey); if (verifier && cn) - m_SigningKeys[cn] = verifier; + m_SigningKeys.emplace (cn, std::make_pair(verifier, (int)m_SigningKeys.size () + 1)); } SSL_free (ssl); } @@ -98,7 +99,8 @@ namespace data void Families::LoadCertificates () { - std::string certDir = i2p::fs::DataDirPath("certificates", "family"); + std::string certDir = i2p::fs::GetCertsDir() + i2p::fs::dirSep + "family"; + std::vector files; int numCertificates = 0; @@ -119,7 +121,7 @@ namespace data } bool Families::VerifyFamily (const std::string& family, const IdentHash& ident, - const char * signature, const char * key) + const char * signature, const char * key) const { uint8_t buf[100], signatureBuf[64]; size_t len = family.length (), signatureLen = strlen (signature); @@ -135,11 +137,19 @@ namespace data Base64ToByteStream (signature, signatureLen, signatureBuf, 64); auto it = m_SigningKeys.find (family); if (it != m_SigningKeys.end ()) - return it->second->Verify (buf, len, signatureBuf); + return it->second.first->Verify (buf, len, signatureBuf); // TODO: process key return true; } + FamilyID Families::GetFamilyID (const std::string& family) const + { + auto it = m_SigningKeys.find (family); + if (it != m_SigningKeys.end ()) + return it->second.second; + return 0; + } + std::string CreateFamilySignature (const std::string& family, const IdentHash& ident) { auto filename = i2p::fs::DataDirPath("family", (family + ".key")); diff --git a/libi2pd/Family.h b/libi2pd/Family.h index 2a9149ba..b19ea142 100644 --- a/libi2pd/Family.h +++ b/libi2pd/Family.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -19,6 +19,7 @@ namespace i2p { namespace data { + typedef int FamilyID; class Families { public: @@ -27,7 +28,8 @@ namespace data ~Families (); void LoadCertificates (); bool VerifyFamily (const std::string& family, const IdentHash& ident, - const char * signature, const char * key = nullptr); + const char * signature, const char * key = nullptr) const; + FamilyID GetFamilyID (const std::string& family) const; private: @@ -35,7 +37,7 @@ namespace data private: - std::map > m_SigningKeys; + std::map, FamilyID> > m_SigningKeys; // family -> (verifier, id) }; std::string CreateFamilySignature (const std::string& family, const IdentHash& ident); diff --git a/libi2pd/Garlic.cpp b/libi2pd/Garlic.cpp index 429a2092..3f885186 100644 --- a/libi2pd/Garlic.cpp +++ b/libi2pd/Garlic.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -164,9 +164,7 @@ namespace garlic RAND_bytes (elGamal.preIV, 32); // Pre-IV uint8_t iv[32]; // IV is first 16 bytes SHA256(elGamal.preIV, 32, iv); - BN_CTX * ctx = BN_CTX_new (); - m_Destination->Encrypt ((uint8_t *)&elGamal, buf, ctx); - BN_CTX_free (ctx); + m_Destination->Encrypt ((uint8_t *)&elGamal, buf); m_Encryption.SetIV (iv); buf += 514; len += 514; @@ -273,7 +271,7 @@ namespace garlic (*numCloves)++; } } - if (msg) // clove message ifself if presented + if (msg) // clove message itself if presented { size += CreateGarlicClove (payload + size, msg, m_Destination ? m_Destination->IsDestination () : false); (*numCloves)++; @@ -295,14 +293,14 @@ namespace garlic size_t size = 0; if (isDestination) { - buf[size] = eGarlicDeliveryTypeDestination << 5;// delivery instructions flag destination + buf[size] = eGarlicDeliveryTypeDestination << 5;// delivery instructions flag destination size++; memcpy (buf + size, m_Destination->GetIdentHash (), 32); size += 32; } else { - buf[size] = 0;// delivery instructions flag local + buf[size] = 0;// delivery instructions flag local size++; } @@ -433,21 +431,28 @@ namespace garlic } GarlicDestination::GarlicDestination (): m_NumTags (32), // 32 tags by default - m_NumRatchetInboundTags (0) // 0 means standard + m_PayloadBuffer (nullptr), m_NumRatchetInboundTags (0) // 0 means standard { - m_Ctx = BN_CTX_new (); } GarlicDestination::~GarlicDestination () { - BN_CTX_free (m_Ctx); + if (m_PayloadBuffer) + delete[] m_PayloadBuffer; } void GarlicDestination::CleanUp () { + for (auto it: m_Sessions) + it.second->SetOwner (nullptr); m_Sessions.clear (); m_DeliveryStatusSessions.clear (); m_Tags.clear (); + for (auto it: m_ECIESx25519Sessions) + { + it.second->Terminate (); + it.second->SetOwner (nullptr); + } m_ECIESx25519Sessions.clear (); m_ECIESx25519Tags.clear (); } @@ -464,70 +469,70 @@ namespace garlic { uint64_t t; memcpy (&t, tag, 8); - auto tagset = std::make_shared(this, key); - m_ECIESx25519Tags.emplace (t, ECIESX25519AEADRatchetIndexTagset{0, tagset}); - } - + AddECIESx25519Key (key, t); + } + + void GarlicDestination::AddECIESx25519Key (const uint8_t * key, uint64_t tag) + { + auto tagset = std::make_shared(this, key); + m_ECIESx25519Tags.emplace (tag, ECIESX25519AEADRatchetIndexTagset{0, tagset}); + } + bool GarlicDestination::SubmitSessionKey (const uint8_t * key, const uint8_t * tag) { AddSessionKey (key, tag); return true; } + void GarlicDestination::SubmitECIESx25519Key (const uint8_t * key, uint64_t tag) + { + AddECIESx25519Key (key, tag); + } + void GarlicDestination::HandleGarlicMessage (std::shared_ptr msg) { uint8_t * buf = msg->GetPayload (); uint32_t length = bufbe32toh (buf); if (length > msg->GetLength ()) { - LogPrint (eLogWarning, "Garlic: message length ", length, " exceeds I2NP message length ", msg->GetLength ()); + LogPrint (eLogWarning, "Garlic: Message length ", length, " exceeds I2NP message length ", msg->GetLength ()); return; } auto mod = length & 0x0f; // %16 buf += 4; // length - auto it = !mod ? m_Tags.find (SessionTag(buf)) : m_Tags.end (); // AES block is multiple of 16 - // AES tag might be used even if encryption type is not ElGamal/AES - if (it != m_Tags.end ()) - { - // tag found. Use AES - auto decryption = it->second; - m_Tags.erase (it); // tag might be used only once - if (length >= 32) - { - uint8_t iv[32]; // IV is first 16 bytes - SHA256(buf, 32, iv); - decryption->SetIV (iv); - decryption->Decrypt (buf + 32, length - 32, buf + 32); - HandleAESBlock (buf + 32, length - 32, decryption, msg->from); - } - else - LogPrint (eLogWarning, "Garlic: message length ", length, " is less than 32 bytes"); - } - else - { - bool found = false; - if (SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET)) - { - // try ECIESx25519 tag - uint64_t tag; - memcpy (&tag, buf, 8); - auto it1 = m_ECIESx25519Tags.find (tag); - if (it1 != m_ECIESx25519Tags.end ()) - { - found = true; - if (!it1->second.tagset->HandleNextMessage (buf, length, it1->second.index)) - LogPrint (eLogError, "Garlic: can't handle ECIES-X25519-AEAD-Ratchet message"); - m_ECIESx25519Tags.erase (it1); - } - } + bool found = false; + if (SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) + // try ECIESx25519 tag + found = HandleECIESx25519TagMessage (buf, length); + if (!found) + { + auto it = !mod ? m_Tags.find (SessionTag(buf)) : m_Tags.end (); // AES block is multiple of 16 + // AES tag might be used even if encryption type is not ElGamal/AES + if (it != m_Tags.end ()) // try AES tag + { + // tag found. Use AES + auto decryption = it->second; + m_Tags.erase (it); // tag might be used only once + if (length >= 32) + { + uint8_t iv[32]; // IV is first 16 bytes + SHA256(buf, 32, iv); + decryption->SetIV (iv); + decryption->Decrypt (buf + 32, length - 32, buf + 32); + HandleAESBlock (buf + 32, length - 32, decryption, msg->from); + found = true; + } + else + LogPrint (eLogWarning, "Garlic: Message length ", length, " is less than 32 bytes"); + } if (!found) // assume new session { // AES tag not found. Handle depending on encryption type // try ElGamal/AES first if leading block is 514 ElGamalBlock elGamal; if (mod == 2 && length >= 514 && SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ELGAMAL) && - Decrypt (buf, (uint8_t *)&elGamal, m_Ctx, i2p::data::CRYPTO_KEY_TYPE_ELGAMAL)) + Decrypt (buf, (uint8_t *)&elGamal, i2p::data::CRYPTO_KEY_TYPE_ELGAMAL)) { auto decryption = std::make_shared(elGamal.sessionKey); uint8_t iv[32]; // IV is first 16 bytes @@ -536,12 +541,39 @@ namespace garlic decryption->Decrypt(buf + 514, length - 514, buf + 514); HandleAESBlock (buf + 514, length - 514, decryption, msg->from); } - else if (SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET)) + else if (SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) { // otherwise ECIESx25519 auto session = std::make_shared (this, false); // incoming if (!session->HandleNextMessage (buf, length, nullptr, 0)) - LogPrint (eLogError, "Garlic: can't handle ECIES-X25519-AEAD-Ratchet message"); + { + // try to generate more tags for last tagset + if (m_LastTagset && (m_LastTagset->GetNextIndex () - m_LastTagset->GetTrimBehind () < 3*ECIESX25519_MAX_NUM_GENERATED_TAGS)) + { + uint64_t missingTag; memcpy (&missingTag, buf, 8); + auto maxTags = std::max (m_NumRatchetInboundTags, ECIESX25519_MAX_NUM_GENERATED_TAGS); + LogPrint (eLogWarning, "Garlic: Trying to generate more ECIES-X25519-AEAD-Ratchet tags"); + for (int i = 0; i < maxTags; i++) + { + auto nextTag = AddECIESx25519SessionNextTag (m_LastTagset); + if (!nextTag) + { + LogPrint (eLogError, "Garlic: Can't create new ECIES-X25519-AEAD-Ratchet tag for last tagset"); + break; + } + if (nextTag == missingTag) + { + LogPrint (eLogDebug, "Garlic: Missing ECIES-X25519-AEAD-Ratchet tag was generated"); + if (m_LastTagset->HandleNextMessage (buf, length, m_ECIESx25519Tags[nextTag].index)) + found = true; + break; + } + } + if (!found) m_LastTagset = nullptr; + } + if (!found) + LogPrint (eLogError, "Garlic: Can't handle ECIES-X25519-AEAD-Ratchet message"); + } } else LogPrint (eLogError, "Garlic: Failed to decrypt message"); @@ -549,6 +581,23 @@ namespace garlic } } + bool GarlicDestination::HandleECIESx25519TagMessage (uint8_t * buf, size_t len) + { + uint64_t tag; + memcpy (&tag, buf, 8); + auto it = m_ECIESx25519Tags.find (tag); + if (it != m_ECIESx25519Tags.end ()) + { + if (it->second.tagset && it->second.tagset->HandleNextMessage (buf, len, it->second.index)) + m_LastTagset = it->second.tagset; + else + LogPrint (eLogError, "Garlic: Can't handle ECIES-X25519-AEAD-Ratchet message"); + m_ECIESx25519Tags.erase (it); + return true; + } + return false; + } + void GarlicDestination::HandleAESBlock (uint8_t * buf, size_t len, std::shared_ptr decryption, std::shared_ptr from) { @@ -585,7 +634,7 @@ namespace garlic SHA256 (buf, payloadSize, digest); if (memcmp (payloadHash, digest, 32)) // payload hash doesn't match { - LogPrint (eLogError, "Garlic: wrong payload hash"); + LogPrint (eLogError, "Garlic: Wrong payload hash"); return; } HandleGarlicPayload (buf, payloadSize, from); @@ -595,7 +644,7 @@ namespace garlic { if (len < 1) { - LogPrint (eLogError, "Garlic: payload is too short"); + LogPrint (eLogError, "Garlic: Payload is too short"); return; } int numCloves = buf[0]; @@ -610,7 +659,7 @@ namespace garlic if (flag & 0x80) // encrypted? { // TODO: implement - LogPrint (eLogWarning, "Garlic: clove encrypted"); + LogPrint (eLogWarning, "Garlic: Clove encrypted"); buf += 32; } ptrdiff_t offset = buf - buf1; @@ -618,35 +667,35 @@ namespace garlic switch (deliveryType) { case eGarlicDeliveryTypeLocal: - LogPrint (eLogDebug, "Garlic: type local"); + LogPrint (eLogDebug, "Garlic: Type local"); if (offset > (int)len) { - LogPrint (eLogError, "Garlic: message is too short"); + LogPrint (eLogError, "Garlic: Message is too short"); break; } HandleI2NPMessage (buf, len - offset); break; case eGarlicDeliveryTypeDestination: - LogPrint (eLogDebug, "Garlic: type destination"); + LogPrint (eLogDebug, "Garlic: Type destination"); buf += 32; // destination. check it later or for multiple destinations offset = buf - buf1; if (offset > (int)len) { - LogPrint (eLogError, "Garlic: message is too short"); + LogPrint (eLogError, "Garlic: Message is too short"); break; } HandleI2NPMessage (buf, len - offset); break; case eGarlicDeliveryTypeTunnel: { - LogPrint (eLogDebug, "Garlic: type tunnel"); + LogPrint (eLogDebug, "Garlic: Type tunnel"); // gwHash and gwTunnel sequence is reverted uint8_t * gwHash = buf; buf += 32; offset = buf - buf1; if (offset + 4 > (int)len) { - LogPrint (eLogError, "Garlic: message is too short"); + LogPrint (eLogError, "Garlic: Message is too short"); break; } uint32_t gwTunnel = bufbe32toh (buf); @@ -660,7 +709,7 @@ namespace garlic else LogPrint (eLogError, "Garlic: Tunnel pool is not set for inbound tunnel"); if (tunnel) // we have sent it through an outbound tunnel - tunnel->SendTunnelDataMsg (gwHash, gwTunnel, msg); + tunnel->SendTunnelDataMsgTo (gwHash, gwTunnel, msg); else LogPrint (eLogWarning, "Garlic: No outbound tunnels available for garlic clove"); } @@ -677,70 +726,76 @@ namespace garlic { if (offset > (int)len) { - LogPrint (eLogError, "Garlic: message is too short"); + LogPrint (eLogError, "Garlic: Message is too short"); break; } i2p::transport::transports.SendMessage (ident, CreateI2NPMessage (buf, GetI2NPMessageLength (buf, len - offset))); } else - LogPrint (eLogWarning, "Garlic: type router for inbound tunnels not supported"); + LogPrint (eLogWarning, "Garlic: Type router for inbound tunnels not supported"); break; } default: - LogPrint (eLogWarning, "Garlic: unknown delivery type ", (int)deliveryType); + LogPrint (eLogWarning, "Garlic: Unknown delivery type ", (int)deliveryType); } if (offset > (int)len) { - LogPrint (eLogError, "Garlic: message is too short"); + LogPrint (eLogError, "Garlic: Message is too short"); break; } - buf += GetI2NPMessageLength (buf, len - offset); // I2NP + buf += GetI2NPMessageLength (buf, len - offset); // I2NP buf += 4; // CloveID buf += 8; // Date buf += 3; // Certificate offset = buf - buf1; if (offset > (int)len) { - LogPrint (eLogError, "Garlic: clove is too long"); + LogPrint (eLogError, "Garlic: Clove is too long"); break; } len -= offset; } } - std::shared_ptr GarlicDestination::WrapMessage (std::shared_ptr destination, - std::shared_ptr msg, bool attachLeaseSet) + std::shared_ptr GarlicDestination::WrapMessageForRouter (std::shared_ptr router, + std::shared_ptr msg) { - auto session = GetRoutingSession (destination, attachLeaseSet); - return session->WrapSingleMessage (msg); + if (router->GetEncryptionType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) + return WrapECIESX25519MessageForRouter (msg, router->GetIdentity ()->GetEncryptionPublicKey ()); + else + { + auto session = GetRoutingSession (router, false); + return session->WrapSingleMessage (msg); + } } std::shared_ptr GarlicDestination::GetRoutingSession ( std::shared_ptr destination, bool attachLeaseSet) { - if (destination->GetEncryptionType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET && - SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET)) + if (destination->GetEncryptionType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD && + SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) { ECIESX25519AEADRatchetSessionPtr session; uint8_t staticKey[32]; - destination->Encrypt (nullptr, staticKey, nullptr); // we are supposed to get static key + destination->Encrypt (nullptr, staticKey); // we are supposed to get static key auto it = m_ECIESx25519Sessions.find (staticKey); if (it != m_ECIESx25519Sessions.end ()) - { + { session = it->second; if (session->IsInactive (i2p::util::GetSecondsSinceEpoch ())) { - LogPrint (eLogDebug, "Garlic: session restarted"); + LogPrint (eLogDebug, "Garlic: Session restarted"); session = nullptr; - } - } + } + } if (!session) { session = std::make_shared (this, true); session->SetRemoteStaticKey (staticKey); } - session->SetDestination (destination->GetIdentHash ()); // TODO: remove + if (destination->IsDestination ()) + session->SetDestination (destination->GetIdentHash ()); // TODO: remove return session; } else @@ -761,6 +816,7 @@ namespace garlic } return session; } + return nullptr; } void GarlicDestination::CleanupExpiredTags () @@ -789,7 +845,7 @@ namespace garlic it->second->GetSharedRoutingPath (); // delete shared path if necessary if (!it->second->CleanupExpiredTags ()) { - LogPrint (eLogInfo, "Routing session to ", it->first.ToBase32 (), " deleted"); + LogPrint (eLogInfo, "Garlic: Routing session to ", it->first.ToBase32 (), " deleted"); it->second->SetOwner (nullptr); it = m_Sessions.erase (it); } @@ -813,7 +869,7 @@ namespace garlic { if (it->second->CheckExpired (ts)) { - it->second->SetOwner (nullptr); + it->second->Terminate (); it = m_ECIESx25519Sessions.erase (it); } else @@ -827,12 +883,24 @@ namespace garlic { it->second.tagset->DeleteSymmKey (it->second.index); it = m_ECIESx25519Tags.erase (it); - } + numExpiredTags++; + } else - ++it; + { + auto session = it->second.tagset->GetSession (); + if (!session || session->IsTerminated()) + { + it = m_ECIESx25519Tags.erase (it); + numExpiredTags++; + } + else + ++it; + } } if (numExpiredTags > 0) LogPrint (eLogDebug, "Garlic: ", numExpiredTags, " ECIESx25519 tags expired for ", GetIdentHash().ToBase64 ()); + if (m_LastTagset && m_LastTagset->IsExpired (ts)) + m_LastTagset = nullptr; } void GarlicDestination::RemoveDeliveryStatusSession (uint32_t msgID) @@ -862,7 +930,7 @@ namespace garlic if (session) { session->MessageConfirmed (msgID); - LogPrint (eLogDebug, "Garlic: message ", msgID, " acknowledged"); + LogPrint (eLogDebug, "Garlic: Message ", msgID, " acknowledged"); } } @@ -956,7 +1024,7 @@ namespace garlic uint32_t ts = i2p::util::GetSecondsSinceEpoch (); for (auto it: files) if (ts >= i2p::fs::GetLastUpdateTime (it) + INCOMING_TAGS_EXPIRATION_TIMEOUT) - i2p::fs::Remove (it); + i2p::fs::Remove (it); } void GarlicDestination::HandleECIESx25519GarlicClove (const uint8_t * buf, size_t len) @@ -967,7 +1035,7 @@ namespace garlic switch (deliveryType) { case eGarlicDeliveryTypeDestination: - LogPrint (eLogDebug, "Garlic: type destination"); + LogPrint (eLogDebug, "Garlic: Type destination"); buf += 32; // TODO: check destination #if (__cplusplus >= 201703L) // C++ 17 or higher [[fallthrough]]; @@ -975,37 +1043,39 @@ namespace garlic // no break here case eGarlicDeliveryTypeLocal: { - LogPrint (eLogDebug, "Garlic: type local"); + LogPrint (eLogDebug, "Garlic: Type local"); I2NPMessageType typeID = (I2NPMessageType)(buf[0]); buf++; // typeid - buf += (4 + 4); // msgID + expiration + int32_t msgID = bufbe32toh (buf); buf += 4; // msgID + buf += 4; // expiration ptrdiff_t offset = buf - buf1; if (offset <= (int)len) - HandleCloveI2NPMessage (typeID, buf, len - offset); + HandleCloveI2NPMessage (typeID, buf, len - offset, msgID); else - LogPrint (eLogError, "Garlic: clove is too long"); + LogPrint (eLogError, "Garlic: Clove is too long"); break; } case eGarlicDeliveryTypeTunnel: { - LogPrint (eLogDebug, "Garlic: type tunnel"); + LogPrint (eLogDebug, "Garlic: Type tunnel"); // gwHash and gwTunnel sequence is reverted const uint8_t * gwHash = buf; buf += 32; ptrdiff_t offset = buf - buf1; if (offset + 13 > (int)len) { - LogPrint (eLogError, "Garlic: message is too short"); + LogPrint (eLogError, "Garlic: Message is too short"); break; } uint32_t gwTunnel = bufbe32toh (buf); buf += 4; I2NPMessageType typeID = (I2NPMessageType)(buf[0]); buf++; // typeid - buf += (4 + 4); // msgID + expiration + uint32_t msgID = bufbe32toh (buf); buf += 4; // msgID + buf += 4; // expiration offset += 13; if (GetTunnelPool ()) { auto tunnel = GetTunnelPool ()->GetNextOutboundTunnel (); if (tunnel) - tunnel->SendTunnelDataMsg (gwHash, gwTunnel, CreateI2NPMessage (typeID, buf, len - offset)); + tunnel->SendTunnelDataMsgTo (gwHash, gwTunnel, CreateI2NPMessage (typeID, buf, len - offset, msgID)); else LogPrint (eLogWarning, "Garlic: No outbound tunnels available for garlic clove"); } @@ -1014,15 +1084,17 @@ namespace garlic break; } default: - LogPrint (eLogWarning, "Garlic: unexpected delivery type ", (int)deliveryType); + LogPrint (eLogWarning, "Garlic: Unexpected delivery type ", (int)deliveryType); } } - void GarlicDestination::AddECIESx25519SessionNextTag (RatchetTagSetPtr tagset) + uint64_t GarlicDestination::AddECIESx25519SessionNextTag (ReceiveRatchetTagSetPtr tagset) { auto index = tagset->GetNextIndex (); uint64_t tag = tagset->GetNextSessionTag (); - m_ECIESx25519Tags.emplace (tag, ECIESX25519AEADRatchetIndexTagset{index, tagset}); + if (tag) + m_ECIESx25519Tags.emplace (tag, ECIESX25519AEADRatchetIndexTagset{index, tagset}); + return tag; } void GarlicDestination::AddECIESx25519Session (const uint8_t * staticKey, ECIESX25519AEADRatchetSessionPtr session) @@ -1032,13 +1104,13 @@ namespace garlic if (it != m_ECIESx25519Sessions.end ()) { if (it->second->CanBeRestarted (i2p::util::GetSecondsSinceEpoch ())) - { - it->second->SetOwner (nullptr); // detach + { + it->second->Terminate (); // detach m_ECIESx25519Sessions.erase (it); - } + } else { - LogPrint (eLogInfo, "Garlic: ECIESx25519 session with static key ", staticKeyTag.ToBase64 (), " already exists"); + LogPrint (eLogInfo, "Garlic: ECIESx25519 session with static key ", staticKeyTag.ToBase64 (), " already exists"); return; } } @@ -1050,9 +1122,16 @@ namespace garlic auto it = m_ECIESx25519Sessions.find (staticKey); if (it != m_ECIESx25519Sessions.end ()) { - it->second->SetOwner (nullptr); + it->second->Terminate (); m_ECIESx25519Sessions.erase (it); } } + + uint8_t * GarlicDestination::GetPayloadBuffer () + { + if (!m_PayloadBuffer) + m_PayloadBuffer = new uint8_t[I2NP_MAX_MESSAGE_SIZE]; + return m_PayloadBuffer; + } } } diff --git a/libi2pd/Garlic.h b/libi2pd/Garlic.h index f1e363df..b926abda 100644 --- a/libi2pd/Garlic.h +++ b/libi2pd/Garlic.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -114,6 +114,8 @@ namespace garlic virtual bool CleanupUnconfirmedTags () { return false; }; // for I2CP, override in ElGamalAESSession virtual bool MessageConfirmed (uint32_t msgID); virtual bool IsRatchets () const { return false; }; + virtual bool IsReadyToSend () const { return true; }; + virtual bool IsTerminated () const { return !GetOwner (); }; virtual uint64_t GetLastActivityTimestamp () const { return 0; }; // non-zero for rathets only void SetLeaseSetUpdated () @@ -214,12 +216,12 @@ namespace garlic class ECIESX25519AEADRatchetSession; typedef std::shared_ptr ECIESX25519AEADRatchetSessionPtr; - class RatchetTagSet; - typedef std::shared_ptr RatchetTagSetPtr; + class ReceiveRatchetTagSet; + typedef std::shared_ptr ReceiveRatchetTagSetPtr; struct ECIESX25519AEADRatchetIndexTagset { int index; - RatchetTagSetPtr tagset; + ReceiveRatchetTagSetPtr tagset; }; class GarlicDestination: public i2p::data::LocalDestination @@ -237,17 +239,19 @@ namespace garlic std::shared_ptr GetRoutingSession (std::shared_ptr destination, bool attachLeaseSet); void CleanupExpiredTags (); void RemoveDeliveryStatusSession (uint32_t msgID); - std::shared_ptr WrapMessage (std::shared_ptr destination, - std::shared_ptr msg, bool attachLeaseSet = false); + std::shared_ptr WrapMessageForRouter (std::shared_ptr router, + std::shared_ptr msg); void AddSessionKey (const uint8_t * key, const uint8_t * tag); // one tag - void AddECIESx25519Key (const uint8_t * key, const uint8_t * tag); // one tag + void AddECIESx25519Key (const uint8_t * key, uint64_t tag); // one tag virtual bool SubmitSessionKey (const uint8_t * key, const uint8_t * tag); // from different thread + virtual void SubmitECIESx25519Key (const uint8_t * key, uint64_t tag); // from different thread void DeliveryStatusSent (GarlicRoutingSessionPtr session, uint32_t msgID); - void AddECIESx25519SessionNextTag (RatchetTagSetPtr tagset); + uint64_t AddECIESx25519SessionNextTag (ReceiveRatchetTagSetPtr tagset); void AddECIESx25519Session (const uint8_t * staticKey, ECIESX25519AEADRatchetSessionPtr session); void RemoveECIESx25519Session (const uint8_t * staticKey); void HandleECIESx25519GarlicClove (const uint8_t * buf, size_t len); + uint8_t * GetPayloadBuffer (); virtual void ProcessGarlicMessage (std::shared_ptr msg); virtual void ProcessDeliveryStatusMessage (std::shared_ptr msg); @@ -258,8 +262,10 @@ namespace garlic protected: + void AddECIESx25519Key (const uint8_t * key, const uint8_t * tag); // one tag + bool HandleECIESx25519TagMessage (uint8_t * buf, size_t len); // return true if found virtual void HandleI2NPMessage (const uint8_t * buf, size_t len) = 0; // called from clove only - virtual bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len) = 0; + virtual bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID) = 0; void HandleGarlicMessage (std::shared_ptr msg); void HandleDeliveryStatusMessage (uint32_t msgID); @@ -274,16 +280,17 @@ namespace garlic private: - BN_CTX * m_Ctx; // incoming // outgoing sessions int m_NumTags; std::mutex m_SessionsMutex; std::unordered_map m_Sessions; std::unordered_map, ECIESX25519AEADRatchetSessionPtr> m_ECIESx25519Sessions; // static key -> session + uint8_t * m_PayloadBuffer; // for ECIESX25519AEADRatchet // incoming int m_NumRatchetInboundTags; std::unordered_map, std::hash > > m_Tags; std::unordered_map m_ECIESx25519Tags; // session tag -> session + ReceiveRatchetTagSetPtr m_LastTagset; // tagset last message came for // DeliveryStatus std::mutex m_DeliveryStatusSessionsMutex; std::unordered_map m_DeliveryStatusSessions; // msgID -> session diff --git a/libi2pd/Gost.cpp b/libi2pd/Gost.cpp index 5e84a95d..2dafc9ae 100644 --- a/libi2pd/Gost.cpp +++ b/libi2pd/Gost.cpp @@ -96,7 +96,7 @@ namespace crypto EC_POINT * C = EC_POINT_new (m_Group); EC_POINT_mul (m_Group, C, z1, pub, z2, ctx); // z1*P + z2*pub BIGNUM * x = BN_CTX_get (ctx); - GetXY (C, x, nullptr); // Cx + GetXY (C, x, nullptr); // Cx BN_mod (x, x, q, ctx); // Cx % q bool ret = !BN_cmp (x, r); // Cx = r ? EC_POINT_free (C); @@ -111,8 +111,8 @@ namespace crypto BN_CTX * ctx = BN_CTX_new (); BN_CTX_start (ctx); EC_POINT * C = EC_POINT_new (m_Group); // C = k*P = (rx, ry) - EC_POINT * Q = nullptr; - if (EC_POINT_set_compressed_coordinates_GFp (m_Group, C, r, isNegativeY ? 1 : 0, ctx)) + EC_POINT * Q = nullptr; + if (EC_POINT_set_compressed_coordinates_GFp (m_Group, C, r, isNegativeY ? 1 : 0, ctx)) { EC_POINT * S = EC_POINT_new (m_Group); // S = s*P EC_POINT_mul (m_Group, S, s, nullptr, nullptr, ctx); diff --git a/libi2pd/Gzip.cpp b/libi2pd/Gzip.cpp index 07c6a96e..4be8684c 100644 --- a/libi2pd/Gzip.cpp +++ b/libi2pd/Gzip.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -57,7 +57,8 @@ namespace data if ((err = inflate (&m_Inflator, Z_NO_FLUSH)) == Z_STREAM_END) return outLen - m_Inflator.avail_out; // else - LogPrint (eLogError, "Gzip: Inflate error ", err); + if (err) + LogPrint (eLogError, "Gzip: Inflate error ", err); return 0; } } @@ -128,7 +129,8 @@ namespace data return outLen - m_Deflator.avail_out; } // else - LogPrint (eLogError, "Gzip: Deflate error ", err); + if (err) + LogPrint (eLogError, "Gzip: Deflate error ", err); return 0; } @@ -137,7 +139,7 @@ namespace data if (m_IsDirty) deflateReset (&m_Deflator); m_IsDirty = true; size_t offset = 0; - int err; + int err = 0; for (const auto& it: bufs) { m_Deflator.next_in = const_cast(it.first); @@ -158,7 +160,8 @@ namespace data offset = outLen - m_Deflator.avail_out; } // else - LogPrint (eLogError, "Gzip: Deflate error ", err); + if (err) + LogPrint (eLogError, "Gzip: Deflate error ", err); return 0; } diff --git a/libi2pd/HTTP.cpp b/libi2pd/HTTP.cpp index 4f7b03a1..f4c3dcb9 100644 --- a/libi2pd/HTTP.cpp +++ b/libi2pd/HTTP.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -9,15 +9,18 @@ #include #include #include -#include "util.h" -#include "HTTP.h" #include +#include "util.h" +#include "Base.h" +#include "HTTP.h" -namespace i2p { -namespace http { +namespace i2p +{ +namespace http +{ const std::vector HTTP_METHODS = { - "GET", "HEAD", "POST", "PUT", "PATCH", - "DELETE", "OPTIONS", "CONNECT", "PROPFIND" + "GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "CONNECT", // HTTP basic methods + "COPY", "LOCK", "MKCOL", "MOVE", "PROPFIND", "PROPPATCH", "UNLOCK", "SEARCH" // WebDAV methods, for SEARCH see rfc5323 }; const std::vector HTTP_VERSIONS = { "HTTP/1.0", "HTTP/1.1" @@ -90,15 +93,18 @@ namespace http { std::size_t pos_c = 0; /* < work position */ if(url.at(0) != '/' || pos_p > 0) { std::size_t pos_s = 0; + /* schema */ pos_c = url.find("://"); if (pos_c != std::string::npos) { schema = url.substr(0, pos_c); pos_p = pos_c + 3; } + /* user[:pass] */ pos_s = url.find('/', pos_p); /* find first slash */ pos_c = url.find('@', pos_p); /* find end of 'user' or 'user:pass' part */ + if (pos_c != std::string::npos && (pos_s == std::string::npos || pos_s > pos_c)) { std::size_t delim = url.find(':', pos_p); if (delim && delim != std::string::npos && delim < pos_c) { @@ -110,14 +116,28 @@ namespace http { } pos_p = pos_c + 1; } + /* hostname[:port][/path] */ - pos_c = url.find_first_of(":/", pos_p); + if (url.at(pos_p) == '[') // ipv6 + { + auto pos_b = url.find(']', pos_p); + if (pos_b == std::string::npos) return false; + ipv6 = true; + pos_c = url.find_first_of(":/", pos_b); + } + else + pos_c = url.find_first_of(":/", pos_p); + if (pos_c == std::string::npos) { /* only hostname, without post and path */ - host = url.substr(pos_p, std::string::npos); + host = ipv6 ? + url.substr(pos_p + 1, url.length() - 1) : + url.substr(pos_p, std::string::npos); return true; } else if (url.at(pos_c) == ':') { - host = url.substr(pos_p, pos_c - pos_p); + host = ipv6 ? + url.substr(pos_p + 1, pos_c - pos_p - 2) : + url.substr(pos_p, pos_c - pos_p); /* port[/path] */ pos_p = pos_c + 1; pos_c = url.find('/', pos_p); @@ -125,6 +145,7 @@ namespace http { ? url.substr(pos_p, std::string::npos) : url.substr(pos_p, pos_c - pos_p); /* stoi throws exception on failure, we don't need it */ + port = 0; for (char c : port_str) { if (c < '0' || c > '9') return false; @@ -136,7 +157,9 @@ namespace http { pos_p = pos_c; } else { /* start of path part found */ - host = url.substr(pos_p, pos_c - pos_p); + host = ipv6 ? + url.substr(pos_p + 1, pos_c - pos_p - 2) : + url.substr(pos_p, pos_c - pos_p); pos_p = pos_c; } } @@ -149,6 +172,7 @@ namespace http { return true; } else if (url.at(pos_c) == '?') { /* found query part */ + hasquery = true; path = url.substr(pos_p, pos_c - pos_p); pos_p = pos_c + 1; pos_c = url.find('#', pos_p); @@ -177,6 +201,8 @@ namespace http { params.clear(); for (const auto& it : tokens) { + if (!it.length()) // empty + continue; std::size_t eq = it.find ('='); if (eq != std::string::npos) { auto e = std::pair(it.substr(0, eq), it.substr(eq + 1)); @@ -198,15 +224,25 @@ namespace http { } else if (user != "") { out += user + "@"; } - if (port) { - out += host + ":" + std::to_string(port); + if (ipv6) { + if (port) { + out += "[" + host + "]:" + std::to_string(port); + } else { + out += "[" + host + "]"; + } } else { - out += host; + if (port) { + out += host + ":" + std::to_string(port); + } else { + out += host; + } } } out += path; + if (hasquery) // add query even if it was empty + out += "?"; if (query != "") - out += "?" + query; + out += query; if (frag != "") out += "#" + frag; return out; @@ -267,7 +303,7 @@ namespace http { method = tokens[0]; uri = tokens[1]; version = tokens[2]; - expect = HEADER_LINE; + expect = HEADER_LINE; } else { @@ -334,12 +370,20 @@ namespace http { return ""; } + size_t HTTPReq::GetNumHeaders (const std::string& name) const + { + size_t num = 0; + for (auto& it : headers) + if (it.first == name) num++; + return num; + } + bool HTTPRes::is_chunked() const { auto it = headers.find("Transfer-Encoding"); if (it == headers.end()) return false; - if (it->second.find("chunked") == std::string::npos) + if (it->second.find("chunked") != std::string::npos) return true; return false; } @@ -351,7 +395,7 @@ namespace http { return false; /* no header */ if (it->second.find("gzip") != std::string::npos) return true; /* gotcha! */ - if (includingI2PGzip && it->second.find("x-i2p-gzip") != std::string::npos) + if (includingI2PGzip && it->second.find("x-i2p-gzip") != std::string::npos) return true; return false; } @@ -397,7 +441,7 @@ namespace http { /* all ok */ version = tokens[0]; status = tokens[2]; - expect = HEADER_LINE; + expect = HEADER_LINE; } else { std::string line = str.substr(pos, eol - pos); auto p = parse_header_line(line); @@ -448,7 +492,7 @@ namespace http { case 304: ptr = "Not Modified"; break; case 307: ptr = "Temporary Redirect"; break; /* client error */ - case 400: ptr = "Bad Request"; break; + case 400: ptr = "Bad Request"; break; case 401: ptr = "Unauthorized"; break; case 403: ptr = "Forbidden"; break; case 404: ptr = "Not Found"; break; @@ -459,17 +503,20 @@ namespace http { case 502: ptr = "Bad Gateway"; break; case 503: ptr = "Not Implemented"; break; case 504: ptr = "Gateway Timeout"; break; - default: ptr = "Unknown Status"; break; + default: ptr = "Unknown Status"; break; } return ptr; } - std::string UrlDecode(const std::string& data, bool allow_null) { + std::string UrlDecode(const std::string& data, bool allow_null) + { std::string decoded(data); size_t pos = 0; - while ((pos = decoded.find('%', pos)) != std::string::npos) { + while ((pos = decoded.find('%', pos)) != std::string::npos) + { char c = strtol(decoded.substr(pos + 1, 2).c_str(), NULL, 16); - if (c == '\0' && !allow_null) { + if (c == '\0' && !allow_null) + { pos += 3; continue; } @@ -479,9 +526,11 @@ namespace http { return decoded; } - bool MergeChunkedResponse (std::istream& in, std::ostream& out) { + bool MergeChunkedResponse (std::istream& in, std::ostream& out) + { std::string hexLen; - while (!in.eof ()) { + while (!in.eof ()) + { std::getline (in, hexLen); errno = 0; long int len = strtoul(hexLen.c_str(), (char **) NULL, 16); @@ -499,5 +548,12 @@ namespace http { } return true; } + + std::string CreateBasicAuthorizationString (const std::string& user, const std::string& pass) + { + if (user.empty () && pass.empty ()) return ""; + return "Basic " + i2p::data::ToBase64Standard (user + ":" + pass); + } + } // http } // i2p diff --git a/libi2pd/HTTP.h b/libi2pd/HTTP.h index c0cf1285..41f0560a 100644 --- a/libi2pd/HTTP.h +++ b/libi2pd/HTTP.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -33,10 +33,12 @@ namespace http std::string host; unsigned short int port; std::string path; + bool hasquery; std::string query; std::string frag; + bool ipv6; - URL(): schema(""), user(""), pass(""), host(""), port(0), path(""), query(""), frag("") {}; + URL(): schema(""), user(""), pass(""), host(""), port(0), path(""), hasquery(false), query(""), frag(""), ipv6(false) {}; /** * @brief Tries to parse url from string @@ -101,6 +103,8 @@ namespace http void RemoveHeader (const std::string& name, const std::string& exempt); // remove all headers starting with name, but exempt void RemoveHeader (const std::string& name) { RemoveHeader (name, ""); }; std::string GetHeader (const std::string& name) const; + size_t GetNumHeaders (const std::string& name) const; + size_t GetNumHeaders () const { return headers.size (); }; }; struct HTTPRes : HTTPMsg { @@ -161,11 +165,14 @@ namespace http /** * @brief Merge HTTP response content with Transfer-Encoding: chunked - * @param in Input stream + * @param in Input stream * @param out Output stream * @return true on success, false otherwise */ bool MergeChunkedResponse (std::istream& in, std::ostream& out); + + std::string CreateBasicAuthorizationString (const std::string& user, const std::string& pass); + } // http } // i2p diff --git a/libi2pd/I2NPProtocol.cpp b/libi2pd/I2NPProtocol.cpp index 36e7a763..b8147555 100644 --- a/libi2pd/I2NPProtocol.cpp +++ b/libi2pd/I2NPProtocol.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -18,6 +18,7 @@ #include "Tunnel.h" #include "Transports.h" #include "Garlic.h" +#include "ECIESX25519AEADRatchetSession.h" #include "I2NPProtocol.h" #include "version.h" @@ -35,16 +36,22 @@ namespace i2p return std::make_shared >(); } - std::shared_ptr NewI2NPTunnelMessage () + std::shared_ptr NewI2NPMediumMessage () { - auto msg = new I2NPMessageBuffer(); // reserved for alignment and NTCP 16 + 6 + 12 - msg->Align (12); - return std::shared_ptr(msg); + return std::make_shared >(); + } + + std::shared_ptr NewI2NPTunnelMessage (bool endpoint) + { + return i2p::tunnel::tunnels.NewI2NPTunnelMessage (endpoint); } std::shared_ptr NewI2NPMessage (size_t len) { - return (len < I2NP_MAX_SHORT_MESSAGE_SIZE - I2NP_HEADER_SIZE - 2) ? NewI2NPShortMessage () : NewI2NPMessage (); + len += I2NP_HEADER_SIZE + 2; + if (len <= I2NP_MAX_SHORT_MESSAGE_SIZE) return NewI2NPShortMessage (); + if (len <= I2NP_MAX_MEDIUM_MESSAGE_SIZE) return NewI2NPMediumMessage (); + return NewI2NPMessage (); } void I2NPMessage::FillI2NPMessageHeader (I2NPMessageType msgType, uint32_t replyMsgID, bool checksum) @@ -76,7 +83,7 @@ namespace i2p { auto msg = NewI2NPMessage (len); if (msg->Concat (buf, len) < len) - LogPrint (eLogError, "I2NP: message length ", len, " exceeds max length ", msg->maxLen); + LogPrint (eLogError, "I2NP: Message length ", len, " exceeds max length ", msg->maxLen); msg->FillI2NPMessageHeader (msgType, replyMsgID); return msg; } @@ -91,7 +98,7 @@ namespace i2p msg->from = from; } else - LogPrint (eLogError, "I2NP: message length ", len, " exceeds max length"); + LogPrint (eLogError, "I2NP: Message length ", len, " exceeds max length"); return msg; } @@ -127,7 +134,8 @@ namespace i2p std::shared_ptr CreateRouterInfoDatabaseLookupMsg (const uint8_t * key, const uint8_t * from, uint32_t replyTunnelID, bool exploratory, std::set * excludedPeers) { - auto m = excludedPeers ? NewI2NPMessage () : NewI2NPShortMessage (); + int cnt = excludedPeers ? excludedPeers->size () : 0; + auto m = cnt > 7 ? NewI2NPMessage () : NewI2NPShortMessage (); uint8_t * buf = m->GetPayload (); memcpy (buf, key, 32); // key buf += 32; @@ -148,7 +156,6 @@ namespace i2p if (excludedPeers) { - int cnt = excludedPeers->size (); htobe16buf (buf, cnt); buf += 2; for (auto& it: *excludedPeers) @@ -171,8 +178,8 @@ namespace i2p std::shared_ptr CreateLeaseSetDatabaseLookupMsg (const i2p::data::IdentHash& dest, const std::set& excludedFloodfills, - std::shared_ptr replyTunnel, const uint8_t * replyKey, - const uint8_t * replyTag, bool replyECIES) + std::shared_ptr replyTunnel, const uint8_t * replyKey, + const uint8_t * replyTag, bool replyECIES) { int cnt = excludedFloodfills.size (); auto m = cnt > 7 ? NewI2NPMessage () : NewI2NPShortMessage (); @@ -210,12 +217,12 @@ namespace i2p { memcpy (buf + 33, replyTag, 8); // 8 bytes tag buf += 41; - } - else - { + } + else + { memcpy (buf + 33, replyTag, 32); // 32 bytes tag buf += 65; - } + } m->len += (buf - m->GetPayload ()); m->FillI2NPMessageHeader (eI2NPDatabaseLookup); @@ -244,11 +251,18 @@ namespace i2p return m; } - std::shared_ptr CreateDatabaseStoreMsg (std::shared_ptr router, uint32_t replyToken) + std::shared_ptr CreateDatabaseStoreMsg (std::shared_ptr router, + uint32_t replyToken, std::shared_ptr replyTunnel) { if (!router) // we send own RouterInfo router = context.GetSharedRouterInfo (); + if (!router->GetBuffer ()) + { + LogPrint (eLogError, "I2NP: Invalid RouterInfo buffer for DatabaseStore"); + return nullptr; + } + auto m = NewI2NPShortMessage (); uint8_t * payload = m->GetPayload (); @@ -258,10 +272,20 @@ namespace i2p uint8_t * buf = payload + DATABASE_STORE_HEADER_SIZE; if (replyToken) { - memset (buf, 0, 4); // zero tunnelID means direct reply - buf += 4; - memcpy (buf, router->GetIdentHash (), 32); - buf += 32; + if (replyTunnel) + { + htobe32buf (buf, replyTunnel->GetNextTunnelID ()); + buf += 4; // reply tunnelID + memcpy (buf, replyTunnel->GetNextIdentHash (), 32); + buf += 32; // reply tunnel gateway + } + else + { + memset (buf, 0, 4); // zero tunnelID means direct reply + buf += 4; + memcpy (buf, context.GetIdentHash (), 32); + buf += 32; + } } uint8_t * sizePtr = buf; @@ -274,7 +298,7 @@ namespace i2p { i2p::data::GzipDeflator deflator; size = deflator.Deflate (router->GetBuffer (), router->GetBufferLen (), buf, m->maxLen -m->len); - } + } if (size) { htobe16buf (sizePtr, size); // size @@ -337,22 +361,7 @@ namespace i2p return !msg->GetPayload ()[DATABASE_STORE_TYPE_OFFSET]; // 0- RouterInfo } - static uint16_t g_MaxNumTransitTunnels = DEFAULT_MAX_NUM_TRANSIT_TUNNELS; // TODO: - void SetMaxNumTransitTunnels (uint16_t maxNumTransitTunnels) - { - if (maxNumTransitTunnels > 0 && g_MaxNumTransitTunnels != maxNumTransitTunnels) - { - LogPrint (eLogDebug, "I2NP: Max number of transit tunnels set to ", maxNumTransitTunnels); - g_MaxNumTransitTunnels = maxNumTransitTunnels; - } - } - - uint16_t GetMaxNumTransitTunnels () - { - return g_MaxNumTransitTunnels; - } - - bool HandleBuildRequestRecords (int num, uint8_t * records, uint8_t * clearText) + static bool HandleBuildRequestRecords (int num, uint8_t * records, uint8_t * clearText) { for (int i = 0; i < num; i++) { @@ -360,40 +369,50 @@ namespace i2p if (!memcmp (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)i2p::context.GetRouterInfo ().GetIdentHash (), 16)) { LogPrint (eLogDebug, "I2NP: Build request record ", i, " is ours"); - BN_CTX * ctx = BN_CTX_new (); - i2p::context.DecryptTunnelBuildRecord (record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, clearText, ctx); - BN_CTX_free (ctx); + if (!i2p::context.DecryptTunnelBuildRecord (record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, clearText)) return false; + uint8_t retCode = 0; // replace record to reply - if (i2p::context.AcceptsTunnels () && - i2p::tunnel::tunnels.GetTransitTunnels ().size () <= g_MaxNumTransitTunnels && - !i2p::transport::transports.IsBandwidthExceeded () && - !i2p::transport::transports.IsTransitBandwidthExceeded ()) + if (i2p::context.AcceptsTunnels () && !i2p::context.IsHighCongestion ()) { auto transitTunnel = i2p::tunnel::CreateTransitTunnel ( - bufbe32toh (clearText + BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET), - clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, - bufbe32toh (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), - clearText + BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET, - clearText + BUILD_REQUEST_RECORD_IV_KEY_OFFSET, - clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] & 0x80, - clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET ] & 0x40); - i2p::tunnel::tunnels.AddTransitTunnel (transitTunnel); - record[BUILD_RESPONSE_RECORD_RET_OFFSET] = 0; + bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET), + clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, + bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), + clearText + ECIES_BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET, + clearText + ECIES_BUILD_REQUEST_RECORD_IV_KEY_OFFSET, + clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_GATEWAY_FLAG, + clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG); + if (!i2p::tunnel::tunnels.AddTransitTunnel (transitTunnel)) + retCode = 30; } else - record[BUILD_RESPONSE_RECORD_RET_OFFSET] = 30; // always reject with bandwidth reason (30) + retCode = 30; // always reject with bandwidth reason (30) - //TODO: fill filler - SHA256 (record + BUILD_RESPONSE_RECORD_PADDING_OFFSET, BUILD_RESPONSE_RECORD_PADDING_SIZE + 1, // + 1 byte of ret - record + BUILD_RESPONSE_RECORD_HASH_OFFSET); + 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++) { - encryption.SetKey (clearText + BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET); - encryption.SetIV (clearText + BUILD_REQUEST_RECORD_REPLY_IV_OFFSET); uint8_t * reply = records + j*TUNNEL_BUILD_RECORD_SIZE; - encryption.Encrypt(reply, TUNNEL_BUILD_RECORD_SIZE, reply); + if (j == i) + { + uint8_t nonce[12]; + memset (nonce, 0, 12); + auto& noiseState = i2p::context.GetCurrentNoiseState (); + if (!i2p::crypto::AEADChaCha20Poly1305 (reply, TUNNEL_BUILD_RECORD_SIZE - 16, + noiseState.m_H, 32, noiseState.m_CK, nonce, reply, TUNNEL_BUILD_RECORD_SIZE, true)) // encrypt + { + LogPrint (eLogWarning, "I2NP: Reply AEAD encryption failed"); + return false; + } + } + else + { + encryption.SetKey (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET); + encryption.SetIV (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_IV_OFFSET); + encryption.Encrypt(reply, TUNNEL_BUILD_RECORD_SIZE, reply); + } } return true; } @@ -401,13 +420,18 @@ namespace i2p return false; } - void HandleVariableTunnelBuildMsg (uint32_t replyMsgID, uint8_t * buf, size_t len) + static void HandleVariableTunnelBuildMsg (uint32_t replyMsgID, uint8_t * buf, size_t len) { int num = buf[0]; LogPrint (eLogDebug, "I2NP: VariableTunnelBuild ", num, " records"); - if (len < num*BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE + 1) + if (num > i2p::tunnel::MAX_NUM_RECORDS) { - LogPrint (eLogError, "VaribleTunnelBuild message of ", num, " records is too short ", len); + LogPrint (eLogError, "I2NP: Too many records in VaribleTunnelBuild message ", num); + return; + } + if (len < num*TUNNEL_BUILD_RECORD_SIZE + 1) + { + LogPrint (eLogError, "I2NP: VaribleTunnelBuild message of ", num, " records is too short ", len); return; } @@ -430,57 +454,43 @@ namespace i2p } else { - uint8_t clearText[BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; + uint8_t clearText[ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; if (HandleBuildRequestRecords (num, buf + 1, clearText)) { - if (clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] & 0x40) // we are endpoint of outboud tunnel + if (clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG) // we are endpoint of outboud tunnel { // so we send it to reply tunnel - transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, - CreateTunnelGatewayMsg (bufbe32toh (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), + 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 + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); + bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); } else - transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, + transports.SendMessage (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, CreateI2NPMessage (eI2NPVariableTunnelBuild, buf, len, - bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); + bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); } } } - void HandleTunnelBuildMsg (uint8_t * buf, size_t len) + static void HandleTunnelBuildMsg (uint8_t * buf, size_t len) { - if (len < NUM_TUNNEL_BUILD_RECORDS*BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE) - { - LogPrint (eLogError, "TunnelBuild message is too short ", len); - return; - } - uint8_t clearText[BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; - if (HandleBuildRequestRecords (NUM_TUNNEL_BUILD_RECORDS, buf, clearText)) - { - if (clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] & 0x40) // we are endpoint of outbound tunnel - { - // so we send it to reply tunnel - transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, - CreateTunnelGatewayMsg (bufbe32toh (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), - eI2NPTunnelBuildReply, buf, len, - bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); - } - else - transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, - CreateI2NPMessage (eI2NPTunnelBuild, buf, len, - bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); - } + LogPrint (eLogWarning, "I2NP: TunnelBuild is too old for ECIES router"); } - void HandleVariableTunnelBuildReplyMsg (uint32_t replyMsgID, uint8_t * buf, size_t len) + static void HandleTunnelBuildReplyMsg (uint32_t replyMsgID, uint8_t * buf, size_t len, bool isShort) { int num = buf[0]; - LogPrint (eLogDebug, "I2NP: VariableTunnelBuildReplyMsg of ", num, " records replyMsgID=", replyMsgID); - if (len < num*BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE + 1) + LogPrint (eLogDebug, "I2NP: TunnelBuildReplyMsg of ", num, " records replyMsgID=", replyMsgID); + if (num > i2p::tunnel::MAX_NUM_RECORDS) { - LogPrint (eLogError, "VaribleTunnelBuildReply message of ", num, " records is too short ", len); + LogPrint (eLogError, "I2NP: Too many records in TunnelBuildReply message ", num); + return; + } + size_t recordSize = isShort ? SHORT_TUNNEL_BUILD_RECORD_SIZE : TUNNEL_BUILD_RECORD_SIZE; + if (len < num*recordSize + 1) + { + LogPrint (eLogError, "I2NP: TunnelBuildReply message of ", num, " records is too short ", len); return; } @@ -504,10 +514,152 @@ namespace i2p LogPrint (eLogWarning, "I2NP: Pending tunnel for message ", replyMsgID, " not found"); } + static void HandleShortTunnelBuildMsg (uint32_t replyMsgID, uint8_t * buf, size_t len) + { + int num = buf[0]; + LogPrint (eLogDebug, "I2NP: ShortTunnelBuild ", num, " records"); + if (num > i2p::tunnel::MAX_NUM_RECORDS) + { + LogPrint (eLogError, "I2NP: Too many records in ShortTunnelBuild message ", num); + return; + } + if (len < num*SHORT_TUNNEL_BUILD_RECORD_SIZE + 1) + { + LogPrint (eLogError, "I2NP: ShortTunnelBuild message of ", num, " records is too short ", len); + return; + } + auto tunnel = i2p::tunnel::tunnels.GetPendingInboundTunnel (replyMsgID); + if (tunnel) + { + // endpoint of inbound tunnel + LogPrint (eLogDebug, "I2NP: ShortTunnelBuild reply for tunnel ", tunnel->GetTunnelID ()); + if (tunnel->HandleTunnelBuildResponse (buf, len)) + { + LogPrint (eLogInfo, "I2NP: Inbound tunnel ", tunnel->GetTunnelID (), " has been created"); + tunnel->SetState (i2p::tunnel::eTunnelStateEstablished); + i2p::tunnel::tunnels.AddInboundTunnel (tunnel); + } + else + { + LogPrint (eLogInfo, "I2NP: Inbound tunnel ", tunnel->GetTunnelID (), " has been declined"); + tunnel->SetState (i2p::tunnel::eTunnelStateBuildFailed); + } + return; + } + const uint8_t * record = buf + 1; + for (int i = 0; i < num; i++) + { + if (!memcmp (record, (const uint8_t *)i2p::context.GetRouterInfo ().GetIdentHash (), 16)) + { + LogPrint (eLogDebug, "I2NP: Short request record ", i, " is ours"); + uint8_t clearText[SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE]; + if (!i2p::context.DecryptTunnelShortRequestRecord (record + SHORT_REQUEST_RECORD_ENCRYPTED_OFFSET, clearText)) + { + LogPrint (eLogWarning, "I2NP: Can't decrypt short request record ", i); + return; + } + if (clearText[SHORT_REQUEST_RECORD_LAYER_ENCRYPTION_TYPE]) // not AES + { + LogPrint (eLogWarning, "I2NP: Unknown layer encryption type ", clearText[SHORT_REQUEST_RECORD_LAYER_ENCRYPTION_TYPE], " in short request record"); + return; + } + auto& noiseState = i2p::context.GetCurrentNoiseState (); + uint8_t replyKey[32]; // AEAD/Chacha20/Poly1305 + i2p::crypto::AESKey layerKey, ivKey; // AES + i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "SMTunnelReplyKey", noiseState.m_CK); + memcpy (replyKey, noiseState.m_CK + 32, 32); + i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "SMTunnelLayerKey", noiseState.m_CK); + memcpy (layerKey, noiseState.m_CK + 32, 32); + bool isEndpoint = clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG; + if (isEndpoint) + { + i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "TunnelLayerIVKey", noiseState.m_CK); + memcpy (ivKey, noiseState.m_CK + 32, 32); + } + else + memcpy (ivKey, noiseState.m_CK , 32); + + // check if we accept this tunnel + uint8_t retCode = 0; + if (!i2p::context.AcceptsTunnels () || i2p::context.IsHighCongestion ()) + retCode = 30; + if (!retCode) + { + // create new transit tunnel + auto transitTunnel = i2p::tunnel::CreateTransitTunnel ( + bufbe32toh (clearText + SHORT_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET), + clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, + bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), + layerKey, ivKey, + clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_GATEWAY_FLAG, + clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG); + if (!i2p::tunnel::tunnels.AddTransitTunnel (transitTunnel)) + retCode = 30; + } + + // encrypt reply + uint8_t nonce[12]; + memset (nonce, 0, 12); + uint8_t * reply = buf + 1; + for (int j = 0; j < num; j++) + { + nonce[4] = j; // nonce is record # + if (j == i) + { + memset (reply + SHORT_RESPONSE_RECORD_OPTIONS_OFFSET, 0, 2); // no options + reply[SHORT_RESPONSE_RECORD_RET_OFFSET] = retCode; + if (!i2p::crypto::AEADChaCha20Poly1305 (reply, SHORT_TUNNEL_BUILD_RECORD_SIZE - 16, + noiseState.m_H, 32, replyKey, nonce, reply, SHORT_TUNNEL_BUILD_RECORD_SIZE, true)) // encrypt + { + LogPrint (eLogWarning, "I2NP: Short reply AEAD encryption failed"); + return; + } + } + else + i2p::crypto::ChaCha20 (reply, SHORT_TUNNEL_BUILD_RECORD_SIZE, replyKey, nonce, reply); + reply += SHORT_TUNNEL_BUILD_RECORD_SIZE; + } + // send reply + if (isEndpoint) + { + auto replyMsg = NewI2NPShortMessage (); + replyMsg->Concat (buf, len); + replyMsg->FillI2NPMessageHeader (eI2NPShortTunnelBuildReply, bufbe32toh (clearText + SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET)); + if (memcmp ((const uint8_t *)i2p::context.GetIdentHash (), + clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, 32)) // reply IBGW is not local? + { + i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "RGarlicKeyAndTag", noiseState.m_CK); + uint64_t tag; + memcpy (&tag, noiseState.m_CK, 8); + // we send it to reply tunnel + transports.SendMessage (clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, + CreateTunnelGatewayMsg (bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), + i2p::garlic::WrapECIESX25519Message (replyMsg, noiseState.m_CK + 32, tag))); + } + else + { + // IBGW is local + uint32_t tunnelID = bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET); + auto tunnel = i2p::tunnel::tunnels.GetTunnel (tunnelID); + if (tunnel) + tunnel->SendTunnelDataMsg (replyMsg); + else + LogPrint (eLogWarning, "I2NP: Tunnel ", tunnelID, " not found for short tunnel build reply"); + } + } + else + transports.SendMessage (clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, + CreateI2NPMessage (eI2NPShortTunnelBuild, buf, len, + bufbe32toh (clearText + SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); + return; + } + record += SHORT_TUNNEL_BUILD_RECORD_SIZE; + } + } std::shared_ptr CreateTunnelDataMsg (const uint8_t * buf) { - auto msg = NewI2NPTunnelMessage (); + auto msg = NewI2NPTunnelMessage (false); msg->Concat (buf, i2p::tunnel::TUNNEL_DATA_MSG_SIZE); msg->FillI2NPMessageHeader (eI2NPTunnelData); return msg; @@ -515,7 +667,7 @@ namespace i2p std::shared_ptr CreateTunnelDataMsg (uint32_t tunnelID, const uint8_t * payload) { - auto msg = NewI2NPTunnelMessage (); + auto msg = NewI2NPTunnelMessage (false); htobe32buf (msg->GetPayload (), tunnelID); msg->len += 4; // tunnelID msg->Concat (payload, i2p::tunnel::TUNNEL_DATA_MSG_SIZE - 4); @@ -523,9 +675,9 @@ namespace i2p return msg; } - std::shared_ptr CreateEmptyTunnelDataMsg () + std::shared_ptr CreateEmptyTunnelDataMsg (bool endpoint) { - auto msg = NewI2NPTunnelMessage (); + auto msg = NewI2NPTunnelMessage (endpoint); msg->len += i2p::tunnel::TUNNEL_DATA_MSG_SIZE; return msg; } @@ -538,7 +690,7 @@ namespace i2p htobe16buf (payload + TUNNEL_GATEWAY_HEADER_LENGTH_OFFSET, len); msg->len += TUNNEL_GATEWAY_HEADER_SIZE; if (msg->Concat (buf, len) < len) - LogPrint (eLogError, "I2NP: tunnel gateway buffer overflow ", msg->maxLen); + LogPrint (eLogError, "I2NP: Tunnel gateway buffer overflow ", msg->maxLen); msg->FillI2NPMessageHeader (eI2NPTunnelGateway); return msg; } @@ -569,7 +721,7 @@ namespace i2p msg->offset += gatewayMsgOffset; msg->len += gatewayMsgOffset; if (msg->Concat (buf, len) < len) - LogPrint (eLogError, "I2NP: tunnel gateway buffer overflow ", msg->maxLen); + LogPrint (eLogError, "I2NP: Tunnel gateway buffer overflow ", msg->maxLen); msg->FillI2NPMessageHeader (msgType, replyMsgID); // create content message len = msg->GetLength (); msg->offset -= gatewayMsgOffset; @@ -584,52 +736,50 @@ namespace i2p { if (len < I2NP_HEADER_SIZE_OFFSET + 2) { - LogPrint (eLogError, "I2NP: message length ", len, " is smaller than header"); + LogPrint (eLogError, "I2NP: Message length ", len, " is smaller than header"); return len; } auto l = bufbe16toh (msg + I2NP_HEADER_SIZE_OFFSET) + I2NP_HEADER_SIZE; if (l > len) { - LogPrint (eLogError, "I2NP: message length ", l, " exceeds buffer length ", len); + LogPrint (eLogError, "I2NP: Message length ", l, " exceeds buffer length ", len); l = len; } return l; } - void HandleI2NPMessage (uint8_t * msg, size_t len) + void HandleTunnelBuildI2NPMessage (std::shared_ptr msg) { - if (len < I2NP_HEADER_SIZE) + if (msg) { - LogPrint (eLogError, "I2NP: message length ", len, " is smaller than header"); - return; - } - uint8_t typeID = msg[I2NP_HEADER_TYPEID_OFFSET]; - uint32_t msgID = bufbe32toh (msg + I2NP_HEADER_MSGID_OFFSET); - LogPrint (eLogDebug, "I2NP: msg received len=", len,", type=", (int)typeID, ", msgID=", (unsigned int)msgID); - uint8_t * buf = msg + I2NP_HEADER_SIZE; - auto size = bufbe16toh (msg + I2NP_HEADER_SIZE_OFFSET); - len -= I2NP_HEADER_SIZE; - if (size > len) - { - LogPrint (eLogError, "I2NP: payload size ", size, " exceeds buffer length ", len); - size = len; - } - switch (typeID) - { - case eI2NPVariableTunnelBuild: - HandleVariableTunnelBuildMsg (msgID, buf, size); - break; - case eI2NPVariableTunnelBuildReply: - HandleVariableTunnelBuildReplyMsg (msgID, buf, size); - break; - case eI2NPTunnelBuild: - HandleTunnelBuildMsg (buf, size); - break; - case eI2NPTunnelBuildReply: - // TODO: - break; - default: - LogPrint (eLogWarning, "I2NP: Unexpected message ", (int)typeID); + uint8_t typeID = msg->GetTypeID(); + uint32_t msgID = msg->GetMsgID(); + LogPrint (eLogDebug, "I2NP: Handling tunnel build message with len=", msg->GetLength(),", type=", (int)typeID, ", msgID=", (unsigned int)msgID); + uint8_t * payload = msg->GetPayload(); + auto size = msg->GetPayloadLength(); + switch (typeID) + { + case eI2NPVariableTunnelBuild: + HandleVariableTunnelBuildMsg (msgID, payload, size); + break; + case eI2NPShortTunnelBuild: + HandleShortTunnelBuildMsg (msgID, payload, size); + break; + case eI2NPVariableTunnelBuildReply: + HandleTunnelBuildReplyMsg (msgID, payload, size, false); + break; + case eI2NPShortTunnelBuildReply: + HandleTunnelBuildReplyMsg (msgID, payload, size, true); + break; + case eI2NPTunnelBuild: + HandleTunnelBuildMsg (payload, size); + break; + case eI2NPTunnelBuildReply: + // TODO: + break; + default: + LogPrint (eLogError, "I2NP: Unexpected message with type", (int)typeID, " during handling TBM; skipping"); + } } } @@ -642,29 +792,32 @@ namespace i2p switch (typeID) { case eI2NPTunnelData: - i2p::tunnel::tunnels.PostTunnelData (msg); + if (!msg->from) + i2p::tunnel::tunnels.PostTunnelData (msg); break; case eI2NPTunnelGateway: - i2p::tunnel::tunnels.PostTunnelData (msg); + if (!msg->from) + i2p::tunnel::tunnels.PostTunnelData (msg); break; case eI2NPGarlic: { - if (msg->from) - { - if (msg->from->GetTunnelPool ()) - msg->from->GetTunnelPool ()->ProcessGarlicMessage (msg); - else - LogPrint (eLogInfo, "I2NP: Local destination for garlic doesn't exist anymore"); - } + if (msg->from && msg->from->GetTunnelPool ()) + msg->from->GetTunnelPool ()->ProcessGarlicMessage (msg); else i2p::context.ProcessGarlicMessage (msg); break; } case eI2NPDatabaseStore: - case eI2NPDatabaseSearchReply: + case eI2NPDatabaseSearchReply: + // forward to netDb if came directly or through exploratory tunnel as response to our request + if (!msg->from || !msg->from->GetTunnelPool () || msg->from->GetTunnelPool ()->IsExploratory ()) + i2p::data::netdb.PostI2NPMsg (msg); + break; + case eI2NPDatabaseLookup: - // forward to netDb - i2p::data::netdb.PostI2NPMsg (msg); + // forward to netDb if floodfill and came directly + if (!msg->from && i2p::context.IsFloodfill ()) + i2p::data::netdb.PostI2NPMsg (msg); break; case eI2NPDeliveryStatus: { @@ -675,14 +828,20 @@ namespace i2p break; } case eI2NPVariableTunnelBuild: - case eI2NPVariableTunnelBuildReply: case eI2NPTunnelBuild: + case eI2NPShortTunnelBuild: + // forward to tunnel thread + if (!msg->from) + i2p::tunnel::tunnels.PostTunnelData (msg); + break; + case eI2NPVariableTunnelBuildReply: case eI2NPTunnelBuildReply: + case eI2NPShortTunnelBuildReply: // forward to tunnel thread i2p::tunnel::tunnels.PostTunnelData (msg); break; default: - HandleI2NPMessage (msg->GetBuffer (), msg->GetLength ()); + LogPrint(eLogError, "I2NP: Unexpected I2NP message with type ", int(typeID), " during handling; skipping"); } } } @@ -692,7 +851,7 @@ namespace i2p Flush (); } - void I2NPMessagesHandler::PutNextMessage (std::shared_ptr msg) + void I2NPMessagesHandler::PutNextMessage (std::shared_ptr&& msg) { if (msg) { diff --git a/libi2pd/I2NPProtocol.h b/libi2pd/I2NPProtocol.h index fe5ca968..6c64f2ab 100644 --- a/libi2pd/I2NPProtocol.h +++ b/libi2pd/I2NPProtocol.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -55,31 +55,49 @@ namespace i2p // TunnelBuild const size_t TUNNEL_BUILD_RECORD_SIZE = 528; - - //BuildRequestRecordClearText - const size_t BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET = 0; - const size_t BUILD_REQUEST_RECORD_OUR_IDENT_OFFSET = BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET + 4; - const size_t BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET = BUILD_REQUEST_RECORD_OUR_IDENT_OFFSET + 32; - const size_t BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET = BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET + 4; - const size_t BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET = BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET + 32; - const size_t BUILD_REQUEST_RECORD_IV_KEY_OFFSET = BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET + 32; - const size_t BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET = BUILD_REQUEST_RECORD_IV_KEY_OFFSET + 32; - const size_t BUILD_REQUEST_RECORD_REPLY_IV_OFFSET = BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET + 32; - const size_t BUILD_REQUEST_RECORD_FLAG_OFFSET = BUILD_REQUEST_RECORD_REPLY_IV_OFFSET + 16; - const size_t BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET = BUILD_REQUEST_RECORD_FLAG_OFFSET + 1; - const size_t BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET = BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET + 4; - const size_t BUILD_REQUEST_RECORD_PADDING_OFFSET = BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET + 4; - const size_t BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE = 222; + const size_t SHORT_TUNNEL_BUILD_RECORD_SIZE = 218; // BuildRequestRecordEncrypted const size_t BUILD_REQUEST_RECORD_TO_PEER_OFFSET = 0; const size_t BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET = BUILD_REQUEST_RECORD_TO_PEER_OFFSET + 16; - // BuildResponseRecord - const size_t BUILD_RESPONSE_RECORD_HASH_OFFSET = 0; - const size_t BUILD_RESPONSE_RECORD_PADDING_OFFSET = 32; - const size_t BUILD_RESPONSE_RECORD_PADDING_SIZE = 495; - const size_t BUILD_RESPONSE_RECORD_RET_OFFSET = BUILD_RESPONSE_RECORD_PADDING_OFFSET + BUILD_RESPONSE_RECORD_PADDING_SIZE; + // ECIES BuildRequestRecordClearText + const size_t ECIES_BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET = 0; + const size_t ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET = ECIES_BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET + 4; + const size_t ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET = ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET + 4; + const size_t ECIES_BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET = ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET + 32; + const size_t ECIES_BUILD_REQUEST_RECORD_IV_KEY_OFFSET = ECIES_BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET + 32; + const size_t ECIES_BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET = ECIES_BUILD_REQUEST_RECORD_IV_KEY_OFFSET + 32; + const size_t ECIES_BUILD_REQUEST_RECORD_REPLY_IV_OFFSET = ECIES_BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET + 32; + const size_t ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET = ECIES_BUILD_REQUEST_RECORD_REPLY_IV_OFFSET + 16; + const size_t ECIES_BUILD_REQUEST_RECORD_MORE_FLAGS_OFFSET = ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET + 1; + const size_t ECIES_BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET = ECIES_BUILD_REQUEST_RECORD_MORE_FLAGS_OFFSET + 3; + const size_t ECIES_BUILD_REQUEST_RECORD_REQUEST_EXPIRATION_OFFSET = ECIES_BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET + 4; + const size_t ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET = ECIES_BUILD_REQUEST_RECORD_REQUEST_EXPIRATION_OFFSET + 4; + const size_t ECIES_BUILD_REQUEST_RECORD_PADDING_OFFSET = ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET + 4; + const size_t ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE = 464; + + // ECIES BuildResponseRecord + const size_t ECIES_BUILD_RESPONSE_RECORD_OPTIONS_OFFSET = 0; + const size_t ECIES_BUILD_RESPONSE_RECORD_RET_OFFSET = 511; + + // ShortRequestRecordClearText + const size_t SHORT_REQUEST_RECORD_ENCRYPTED_OFFSET = 16; + const size_t SHORT_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET = 0; + const size_t SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET = SHORT_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET + 4; + const size_t SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET = SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET + 4; + const size_t SHORT_REQUEST_RECORD_FLAG_OFFSET = SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET + 32; + const size_t SHORT_REQUEST_RECORD_MORE_FLAGS_OFFSET = SHORT_REQUEST_RECORD_FLAG_OFFSET + 1; + const size_t SHORT_REQUEST_RECORD_LAYER_ENCRYPTION_TYPE = SHORT_REQUEST_RECORD_MORE_FLAGS_OFFSET + 2; + const size_t SHORT_REQUEST_RECORD_REQUEST_TIME_OFFSET = SHORT_REQUEST_RECORD_LAYER_ENCRYPTION_TYPE + 1; + const size_t SHORT_REQUEST_RECORD_REQUEST_EXPIRATION_OFFSET = SHORT_REQUEST_RECORD_REQUEST_TIME_OFFSET + 4; + const size_t SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET = SHORT_REQUEST_RECORD_REQUEST_EXPIRATION_OFFSET + 4; + const size_t SHORT_REQUEST_RECORD_PADDING_OFFSET = SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET + 4; + const size_t SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE = 154; + + // ShortResponseRecord + const size_t SHORT_RESPONSE_RECORD_OPTIONS_OFFSET = 0; + const size_t SHORT_RESPONSE_RECORD_RET_OFFSET = 201; enum I2NPMessageType { @@ -95,9 +113,13 @@ namespace i2p eI2NPTunnelBuild = 21, eI2NPTunnelBuildReply = 22, eI2NPVariableTunnelBuild = 23, - eI2NPVariableTunnelBuildReply = 24 + eI2NPVariableTunnelBuildReply = 24, + eI2NPShortTunnelBuild = 25, + eI2NPShortTunnelBuildReply = 26 }; + const uint8_t TUNNEL_BUILD_RECORD_GATEWAY_FLAG = 0x80; + const uint8_t TUNNEL_BUILD_RECORD_ENDPOINT_FLAG = 0x40; const int NUM_TUNNEL_BUILD_RECORDS = 8; // DatabaseLookup flags @@ -118,6 +140,7 @@ namespace tunnel const size_t I2NP_MAX_MESSAGE_SIZE = 62708; const size_t I2NP_MAX_SHORT_MESSAGE_SIZE = 4096; + const size_t I2NP_MAX_MEDIUM_MESSAGE_SIZE = 16384; const unsigned int I2NP_MESSAGE_EXPIRATION_TIMEOUT = 8000; // in milliseconds (as initial RTT) const unsigned int I2NP_MESSAGE_CLOCK_SKEW = 60*1000; // 1 minute in milliseconds @@ -128,7 +151,7 @@ namespace tunnel std::shared_ptr from; I2NPMessage (): buf (nullptr),len (I2NP_HEADER_SIZE + 2), - offset(2), maxLen (0), from (nullptr) {}; // reserve 2 bytes for NTCP header + offset(2), maxLen (0), from (nullptr) {}; // reserve 2 bytes for NTCP header // header accessors uint8_t * GetHeader () { return GetBuffer (); }; @@ -240,7 +263,8 @@ namespace tunnel std::shared_ptr NewI2NPMessage (); std::shared_ptr NewI2NPShortMessage (); - std::shared_ptr NewI2NPTunnelMessage (); + std::shared_ptr NewI2NPMediumMessage (); + std::shared_ptr NewI2NPTunnelMessage (bool endpoint); std::shared_ptr NewI2NPMessage (size_t len); std::shared_ptr CreateI2NPMessage (I2NPMessageType msgType, const uint8_t * buf, size_t len, uint32_t replyMsgID = 0); @@ -251,24 +275,19 @@ namespace tunnel std::shared_ptr CreateRouterInfoDatabaseLookupMsg (const uint8_t * key, const uint8_t * from, uint32_t replyTunnelID, bool exploratory = false, std::set * excludedPeers = nullptr); std::shared_ptr CreateLeaseSetDatabaseLookupMsg (const i2p::data::IdentHash& dest, - const std::set& excludedFloodfills, - std::shared_ptr replyTunnel, - const uint8_t * replyKey, const uint8_t * replyTag, bool replyECIES = false); + const std::set& excludedFloodfills, + std::shared_ptr replyTunnel, + const uint8_t * replyKey, const uint8_t * replyTag, bool replyECIES = false); std::shared_ptr CreateDatabaseSearchReply (const i2p::data::IdentHash& ident, std::vector routers); - std::shared_ptr CreateDatabaseStoreMsg (std::shared_ptr router = nullptr, uint32_t replyToken = 0); + std::shared_ptr CreateDatabaseStoreMsg (std::shared_ptr router = nullptr, uint32_t replyToken = 0, std::shared_ptr replyTunnel = nullptr); std::shared_ptr CreateDatabaseStoreMsg (const i2p::data::IdentHash& storeHash, std::shared_ptr leaseSet); // for floodfill only std::shared_ptr CreateDatabaseStoreMsg (std::shared_ptr leaseSet, uint32_t replyToken = 0, std::shared_ptr replyTunnel = nullptr); bool IsRouterInfoMsg (std::shared_ptr msg); - bool HandleBuildRequestRecords (int num, uint8_t * records, uint8_t * clearText); - void HandleVariableTunnelBuildMsg (uint32_t replyMsgID, uint8_t * buf, size_t len); - void HandleVariableTunnelBuildReplyMsg (uint32_t replyMsgID, uint8_t * buf, size_t len); - void HandleTunnelBuildMsg (uint8_t * buf, size_t len); - std::shared_ptr CreateTunnelDataMsg (const uint8_t * buf); std::shared_ptr CreateTunnelDataMsg (uint32_t tunnelID, const uint8_t * payload); - std::shared_ptr CreateEmptyTunnelDataMsg (); + std::shared_ptr CreateEmptyTunnelDataMsg (bool endpoint); std::shared_ptr CreateTunnelGatewayMsg (uint32_t tunnelID, const uint8_t * buf, size_t len); std::shared_ptr CreateTunnelGatewayMsg (uint32_t tunnelID, I2NPMessageType msgType, @@ -276,7 +295,7 @@ namespace tunnel std::shared_ptr CreateTunnelGatewayMsg (uint32_t tunnelID, std::shared_ptr msg); size_t GetI2NPMessageLength (const uint8_t * msg, size_t len); - void HandleI2NPMessage (uint8_t * msg, size_t len); + void HandleTunnelBuildI2NPMessage (std::shared_ptr msg); void HandleI2NPMessage (std::shared_ptr msg); class I2NPMessagesHandler @@ -284,17 +303,13 @@ namespace tunnel public: ~I2NPMessagesHandler (); - void PutNextMessage (std::shared_ptr msg); + void PutNextMessage (std::shared_ptr&& msg); void Flush (); private: std::vector > m_TunnelMsgs, m_TunnelGatewayMsgs; }; - - const uint16_t DEFAULT_MAX_NUM_TRANSIT_TUNNELS = 2500; - void SetMaxNumTransitTunnels (uint16_t maxNumTransitTunnels); - uint16_t GetMaxNumTransitTunnels (); } #endif diff --git a/libi2pd/I2PEndian.cpp b/libi2pd/I2PEndian.cpp index 32ca4e26..5496f144 100644 --- a/libi2pd/I2PEndian.cpp +++ b/libi2pd/I2PEndian.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -50,42 +50,3 @@ uint64_t be64toh(uint64_t big64) return u64.raw_value; } #endif - -/* it can be used in Windows 8 -#include - -uint16_t htobe16(uint16_t int16) -{ - return htons(int16); -} - -uint32_t htobe32(uint32_t int32) -{ - return htonl(int32); -} - -uint64_t htobe64(uint64_t int64) -{ - // http://msdn.microsoft.com/en-us/library/windows/desktop/jj710199%28v=vs.85%29.aspx - //return htonll(int64); - return 0; -} - - -uint16_t be16toh(uint16_t big16) -{ - return ntohs(big16); -} - -uint32_t be32toh(uint32_t big32) -{ - return ntohl(big32); -} - -uint64_t be64toh(uint64_t big64) -{ - // http://msdn.microsoft.com/en-us/library/windows/desktop/jj710199%28v=vs.85%29.aspx - //return ntohll(big64); - return 0; -} -*/ \ No newline at end of file diff --git a/libi2pd/I2PEndian.h b/libi2pd/I2PEndian.h index 9ffc28d0..06abf29a 100644 --- a/libi2pd/I2PEndian.h +++ b/libi2pd/I2PEndian.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -13,10 +13,11 @@ #if defined(__FreeBSD__) || defined(__NetBSD__) #include + #elif defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__OpenBSD__) || defined(__GLIBC__) #include -#elif defined(__APPLE__) && defined(__MACH__) +#elif defined(__APPLE__) && defined(__MACH__) #include #define htobe16(x) OSSwapHostToBigInt16(x) @@ -34,6 +35,40 @@ #define be64toh(x) OSSwapBigToHostInt64(x) #define le64toh(x) OSSwapLittleToHostInt64(x) +#elif defined(_WIN32) +#if defined(_MSC_VER) +#include +#define htobe16(x) _byteswap_ushort(x) +#define htole16(x) (x) +#define be16toh(x) _byteswap_ushort(x) +#define le16toh(x) (x) + +#define htobe32(x) _byteswap_ulong(x) +#define htole32(x) (x) +#define be32toh(x) _byteswap_ulong(x) +#define le32toh(x) (x) + +#define htobe64(x) _byteswap_uint64(x) +#define htole64(x) (x) +#define be64toh(x) _byteswap_uint64(x) +#define le64toh(x) (x) +#else +#define htobe16(x) __builtin_bswap16(x) +#define htole16(x) (x) +#define be16toh(x) __builtin_bswap16(x) +#define le16toh(x) (x) + +#define htobe32(x) __builtin_bswap32(x) +#define htole32(x) (x) +#define be32toh(x) __builtin_bswap32(x) +#define le32toh(x) (x) + +#define htobe64(x) __builtin_bswap64(x) +#define htole64(x) (x) +#define be64toh(x) __builtin_bswap64(x) +#define le64toh(x) (x) +#endif + #else #define NEEDS_LOCAL_ENDIAN #include diff --git a/libi2pd/Identity.cpp b/libi2pd/Identity.cpp index b2b5f2b4..3a659c11 100644 --- a/libi2pd/Identity.cpp +++ b/libi2pd/Identity.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -19,7 +19,8 @@ namespace data Identity& Identity::operator=(const Keys& keys) { // copy public and signing keys together - memcpy (publicKey, keys.publicKey, sizeof (publicKey) + sizeof (signingKey)); + memcpy (publicKey, keys.publicKey, sizeof (publicKey)); + memcpy (signingKey, keys.signingKey, sizeof (signingKey)); memset (certificate, 0, sizeof (certificate)); return *this; } @@ -42,13 +43,28 @@ namespace data } IdentityEx::IdentityEx (): - m_ExtendedLen (0), m_ExtendedBuffer (nullptr) + m_ExtendedLen (0) { } IdentityEx::IdentityEx(const uint8_t * publicKey, const uint8_t * signingKey, SigningKeyType type, CryptoKeyType cryptoType) { - memcpy (m_StandardIdentity.publicKey, publicKey, 256); // publicKey in awlays assumed 256 regardless actual size, padding must be taken care of + uint8_t randomPaddingBlock[32]; + RAND_bytes (randomPaddingBlock, 32); + if (cryptoType == CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) + { + memcpy (m_StandardIdentity.publicKey, publicKey ? publicKey : randomPaddingBlock, 32); + for (int i = 0; i < 7; i++) // 224 bytes + memcpy (m_StandardIdentity.publicKey + 32*(i + 1), randomPaddingBlock, 32); + } + else + { + if (publicKey) + memcpy (m_StandardIdentity.publicKey, publicKey, 256); + else + for (int i = 0; i < 8; i++) // 256 bytes + memcpy (m_StandardIdentity.publicKey + 32*i, randomPaddingBlock, 32); + } if (type != SIGNING_KEY_TYPE_DSA_SHA1) { size_t excessLen = 0; @@ -57,7 +73,7 @@ namespace data { case SIGNING_KEY_TYPE_ECDSA_SHA256_P256: { - size_t padding = 128 - i2p::crypto::ECDSAP256_KEY_LENGTH; // 64 = 128 - 64 + size_t padding = 128 - i2p::crypto::ECDSAP256_KEY_LENGTH; // 64 = 128 - 64 RAND_bytes (m_StandardIdentity.signingKey, padding); memcpy (m_StandardIdentity.signingKey + padding, signingKey, i2p::crypto::ECDSAP256_KEY_LENGTH); break; @@ -86,7 +102,8 @@ namespace data case SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519: { size_t padding = 128 - i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH; // 96 = 128 - 32 - RAND_bytes (m_StandardIdentity.signingKey, padding); + for (int i = 0; i < 3; i++) // 96 bytes + memcpy (m_StandardIdentity.signingKey + 32*i, randomPaddingBlock, 32); memcpy (m_StandardIdentity.signingKey + padding, signingKey, i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH); break; } @@ -113,11 +130,15 @@ namespace data m_StandardIdentity.certificate[0] = CERTIFICATE_TYPE_KEY; htobe16buf (m_StandardIdentity.certificate + 1, m_ExtendedLen); // fill extended buffer - m_ExtendedBuffer = new uint8_t[m_ExtendedLen]; htobe16buf (m_ExtendedBuffer, type); htobe16buf (m_ExtendedBuffer + 2, cryptoType); if (excessLen && excessBuf) { + if (excessLen > MAX_EXTENDED_BUFFER_SIZE - 4) + { + LogPrint (eLogError, "Identity: Unexpected excessive signing key len ", excessLen); + excessLen = MAX_EXTENDED_BUFFER_SIZE - 4; + } memcpy (m_ExtendedBuffer + 4, excessBuf, excessLen); delete[] excessBuf; } @@ -130,7 +151,6 @@ namespace data memset (m_StandardIdentity.certificate, 0, sizeof (m_StandardIdentity.certificate)); m_IdentHash = m_StandardIdentity.Hash (); m_ExtendedLen = 0; - m_ExtendedBuffer = nullptr; } CreateVerifier (); } @@ -148,27 +168,25 @@ namespace data } IdentityEx::IdentityEx (const uint8_t * buf, size_t len): - m_ExtendedLen (0), m_ExtendedBuffer (nullptr) + m_ExtendedLen (0) { FromBuffer (buf, len); } IdentityEx::IdentityEx (const IdentityEx& other): - m_ExtendedLen (0), m_ExtendedBuffer (nullptr) + m_ExtendedLen (0) { *this = other; } IdentityEx::IdentityEx (const Identity& standard): - m_ExtendedLen (0), m_ExtendedBuffer (nullptr) + m_ExtendedLen (0) { *this = standard; } IdentityEx::~IdentityEx () { - delete[] m_ExtendedBuffer; - delete m_Verifier; } IdentityEx& IdentityEx::operator=(const IdentityEx& other) @@ -176,18 +194,14 @@ namespace data memcpy (&m_StandardIdentity, &other.m_StandardIdentity, DEFAULT_IDENTITY_SIZE); m_IdentHash = other.m_IdentHash; - delete[] m_ExtendedBuffer; m_ExtendedLen = other.m_ExtendedLen; if (m_ExtendedLen > 0) { - m_ExtendedBuffer = new uint8_t[m_ExtendedLen]; + if (m_ExtendedLen > MAX_EXTENDED_BUFFER_SIZE) m_ExtendedLen = MAX_EXTENDED_BUFFER_SIZE; memcpy (m_ExtendedBuffer, other.m_ExtendedBuffer, m_ExtendedLen); } - else - m_ExtendedBuffer = nullptr; - - delete m_Verifier; m_Verifier = nullptr; + CreateVerifier (); return *this; } @@ -196,13 +210,10 @@ namespace data { m_StandardIdentity = standard; m_IdentHash = m_StandardIdentity.Hash (); - - delete[] m_ExtendedBuffer; - m_ExtendedBuffer = nullptr; m_ExtendedLen = 0; - delete m_Verifier; m_Verifier = nullptr; + CreateVerifier (); return *this; } @@ -211,20 +222,17 @@ namespace data { if (len < DEFAULT_IDENTITY_SIZE) { - LogPrint (eLogError, "Identity: buffer length ", len, " is too small"); + LogPrint (eLogError, "Identity: Buffer length ", len, " is too small"); return 0; } memcpy (&m_StandardIdentity, buf, DEFAULT_IDENTITY_SIZE); - if(m_ExtendedBuffer) delete[] m_ExtendedBuffer; - m_ExtendedBuffer = nullptr; - m_ExtendedLen = bufbe16toh (m_StandardIdentity.certificate + 1); if (m_ExtendedLen) { if (m_ExtendedLen + DEFAULT_IDENTITY_SIZE <= len) { - m_ExtendedBuffer = new uint8_t[m_ExtendedLen]; + if (m_ExtendedLen > MAX_EXTENDED_BUFFER_SIZE) m_ExtendedLen = MAX_EXTENDED_BUFFER_SIZE; memcpy (m_ExtendedBuffer, buf + DEFAULT_IDENTITY_SIZE, m_ExtendedLen); } else @@ -235,14 +243,11 @@ namespace data } } else - { m_ExtendedLen = 0; - m_ExtendedBuffer = nullptr; - } SHA256(buf, GetFullLen (), m_IdentHash); - delete m_Verifier; m_Verifier = nullptr; + CreateVerifier (); return GetFullLen (); } @@ -252,7 +257,7 @@ namespace data const size_t fullLen = GetFullLen(); if (fullLen > len) return 0; // buffer is too small and may overflow somewhere else memcpy (buf, &m_StandardIdentity, DEFAULT_IDENTITY_SIZE); - if (m_ExtendedLen > 0 && m_ExtendedBuffer) + if (m_ExtendedLen > 0) memcpy (buf + DEFAULT_IDENTITY_SIZE, m_ExtendedBuffer, m_ExtendedLen); return fullLen; } @@ -278,7 +283,6 @@ namespace data size_t IdentityEx::GetSigningPublicKeyLen () const { - if (!m_Verifier) CreateVerifier (); if (m_Verifier) return m_Verifier->GetPublicKeyLen (); return 128; @@ -293,7 +297,6 @@ namespace data size_t IdentityEx::GetSigningPrivateKeyLen () const { - if (!m_Verifier) CreateVerifier (); if (m_Verifier) return m_Verifier->GetPrivateKeyLen (); return GetSignatureLen ()/2; @@ -301,14 +304,12 @@ namespace data size_t IdentityEx::GetSignatureLen () const { - if (!m_Verifier) CreateVerifier (); if (m_Verifier) return m_Verifier->GetSignatureLen (); return i2p::crypto::DSA_SIGNATURE_LENGTH; } bool IdentityEx::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const { - if (!m_Verifier) CreateVerifier (); if (m_Verifier) return m_Verifier->Verify (buf, len, signature); return false; @@ -365,52 +366,29 @@ namespace data return nullptr; } - void IdentityEx::CreateVerifier () const + void IdentityEx::CreateVerifier () { - if (m_Verifier) return; // don't create again - auto verifier = CreateVerifier (GetSigningKeyType ()); - if (verifier) + if (!m_Verifier) { - auto keyLen = verifier->GetPublicKeyLen (); - if (keyLen <= 128) - verifier->SetPublicKey (m_StandardIdentity.signingKey + 128 - keyLen); - else + auto verifier = CreateVerifier (GetSigningKeyType ()); + if (verifier) { - // for P521 - uint8_t * signingKey = new uint8_t[keyLen]; - memcpy (signingKey, m_StandardIdentity.signingKey, 128); - size_t excessLen = keyLen - 128; - memcpy (signingKey + 128, m_ExtendedBuffer + 4, excessLen); // right after signing and crypto key types - verifier->SetPublicKey (signingKey); - delete[] signingKey; + auto keyLen = verifier->GetPublicKeyLen (); + if (keyLen <= 128) + verifier->SetPublicKey (m_StandardIdentity.signingKey + 128 - keyLen); + else + { + // for P521 + uint8_t * signingKey = new uint8_t[keyLen]; + memcpy (signingKey, m_StandardIdentity.signingKey, 128); + size_t excessLen = keyLen - 128; + memcpy (signingKey + 128, m_ExtendedBuffer + 4, excessLen); // right after signing and crypto key types + verifier->SetPublicKey (signingKey); + delete[] signingKey; + } } + m_Verifier.reset (verifier); } - UpdateVerifier (verifier); - } - - void IdentityEx::UpdateVerifier (i2p::crypto::Verifier * verifier) const - { - bool del = false; - { - std::lock_guard l(m_VerifierMutex); - if (!m_Verifier) - m_Verifier = verifier; - else - del = true; - } - if (del) - delete verifier; - } - - void IdentityEx::DropVerifier () const - { - i2p::crypto::Verifier * verifier; - { - std::lock_guard l(m_VerifierMutex); - verifier = m_Verifier; - m_Verifier = nullptr; - } - delete verifier; } std::shared_ptr IdentityEx::CreateEncryptor (CryptoKeyType keyType, const uint8_t * key) @@ -420,7 +398,7 @@ namespace data case CRYPTO_KEY_TYPE_ELGAMAL: return std::make_shared(key); break; - case CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET: + case CRYPTO_KEY_TYPE_ECIES_X25519_AEAD: return std::make_shared(key); break; case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC: @@ -470,7 +448,7 @@ namespace data size_t PrivateKeys::GetFullLen () const { - size_t ret = m_Public->GetFullLen () + 256 + m_Public->GetSigningPrivateKeyLen (); + size_t ret = m_Public->GetFullLen () + GetPrivateKeyLen () + m_Public->GetSigningPrivateKeyLen (); if (IsOfflineSignature ()) ret += m_OfflineSignature.size () + m_TransientSigningPrivateKeyLen; return ret; @@ -480,9 +458,10 @@ namespace data { m_Public = std::make_shared(); size_t ret = m_Public->FromBuffer (buf, len); - if (!ret || ret + 256 > len) return 0; // overflow - memcpy (m_PrivateKey, buf + ret, 256); // private key always 256 - ret += 256; + auto cryptoKeyLen = GetPrivateKeyLen (); + if (!ret || ret + cryptoKeyLen > len) return 0; // overflow + memcpy (m_PrivateKey, buf + ret, cryptoKeyLen); + ret += cryptoKeyLen; size_t signingPrivateKeySize = m_Public->GetSigningPrivateKeyLen (); if(signingPrivateKeySize + ret > len || signingPrivateKeySize > 128) return 0; // overflow memcpy (m_SigningPrivateKey, buf + ret, signingPrivateKeySize); @@ -510,7 +489,7 @@ namespace data if (m_Public->GetSignatureLen () + ret > len) return 0; if (!m_Public->Verify (offlineInfo, keyLen + 6, buf + ret)) { - LogPrint (eLogError, "Identity: offline signature verification failed"); + LogPrint (eLogError, "Identity: Offline signature verification failed"); return 0; } ret += m_Public->GetSignatureLen (); @@ -534,8 +513,9 @@ namespace data size_t PrivateKeys::ToBuffer (uint8_t * buf, size_t len) const { size_t ret = m_Public->ToBuffer (buf, len); - memcpy (buf + ret, m_PrivateKey, 256); // private key always 256 - ret += 256; + auto cryptoKeyLen = GetPrivateKeyLen (); + memcpy (buf + ret, m_PrivateKey, cryptoKeyLen); + ret += cryptoKeyLen; size_t signingPrivateKeySize = m_Public->GetSigningPrivateKeyLen (); if(ret + signingPrivateKeySize > len) return 0; // overflow if (IsOfflineSignature ()) @@ -601,7 +581,7 @@ namespace data if (keyType == SIGNING_KEY_TYPE_DSA_SHA1) m_Signer.reset (new i2p::crypto::DSASigner (m_SigningPrivateKey, m_Public->GetStandardIdentity ().signingKey)); else if (keyType == SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519 && !IsOfflineSignature ()) - m_Signer.reset (new i2p::crypto::EDDSA25519Signer (m_SigningPrivateKey, m_Public->GetStandardIdentity ().certificate - i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH)); // TODO: remove public key check + m_Signer.reset (new i2p::crypto::EDDSA25519Signer (m_SigningPrivateKey, m_Public->GetStandardIdentity ().signingKey + (sizeof(Identity::signingKey) - i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH))); // TODO: remove public key check else { // public key is not required @@ -651,6 +631,12 @@ namespace data return IsOfflineSignature () ? m_TransientSignatureLen : m_Public->GetSignatureLen (); } + size_t PrivateKeys::GetPrivateKeyLen () const + { + // private key length always 256, but type 4 + return (m_Public->GetCryptoKeyType () == CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) ? 32 : 256; + } + uint8_t * PrivateKeys::GetPadding() { if(m_Public->GetSigningKeyType () == SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519) @@ -673,6 +659,9 @@ namespace data case CRYPTO_KEY_TYPE_ELGAMAL: return std::make_shared(key); break; + case CRYPTO_KEY_TYPE_ECIES_X25519_AEAD: + return std::make_shared(key); + break; case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC: case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC_TEST: return std::make_shared(key); @@ -680,16 +669,13 @@ namespace data case CRYPTO_KEY_TYPE_ECIES_GOSTR3410_CRYPTO_PRO_A_SHA256_AES256CBC: return std::make_shared(key); break; - case CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET: - return std::make_shared(key); - break; default: LogPrint (eLogError, "Identity: Unknown crypto key type ", (int)cryptoType); }; return nullptr; } - PrivateKeys PrivateKeys::CreateRandomKeys (SigningKeyType type, CryptoKeyType cryptoType) + PrivateKeys PrivateKeys::CreateRandomKeys (SigningKeyType type, CryptoKeyType cryptoType, bool isDestination) { if (type != SIGNING_KEY_TYPE_DSA_SHA1) { @@ -699,9 +685,12 @@ namespace data GenerateSigningKeyPair (type, keys.m_SigningPrivateKey, signingPublicKey); // encryption uint8_t publicKey[256]; - GenerateCryptoKeyPair (cryptoType, keys.m_PrivateKey, publicKey); + if (isDestination) + RAND_bytes (keys.m_PrivateKey, 256); + else + GenerateCryptoKeyPair (cryptoType, keys.m_PrivateKey, publicKey); // identity - keys.m_Public = std::make_shared (publicKey, signingPublicKey, type, cryptoType); + keys.m_Public = std::make_shared (isDestination ? nullptr : publicKey, signingPublicKey, type, cryptoType); keys.CreateSigner (); return keys; @@ -762,7 +751,7 @@ namespace data case CRYPTO_KEY_TYPE_ECIES_GOSTR3410_CRYPTO_PRO_A_SHA256_AES256CBC: i2p::crypto::CreateECIESGOSTR3410RandomKeys (priv, pub); break; - case CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET: + case CRYPTO_KEY_TYPE_ECIES_X25519_AEAD: i2p::crypto::CreateECIESX25519AEADRatchetRandomKeys (priv, pub); break; default: @@ -782,7 +771,7 @@ namespace data keys.m_OfflineSignature.resize (pubKeyLen + m_Public->GetSignatureLen () + 6); htobe32buf (keys.m_OfflineSignature.data (), expires); // expires htobe16buf (keys.m_OfflineSignature.data () + 4, type); // type - GenerateSigningKeyPair (type, keys.m_SigningPrivateKey, keys.m_OfflineSignature.data () + 6); // public key + GenerateSigningKeyPair (type, keys.m_SigningPrivateKey, keys.m_OfflineSignature.data () + 6); // public key Sign (keys.m_OfflineSignature.data (), pubKeyLen + 6, keys.m_OfflineSignature.data () + 6 + pubKeyLen); // signature // recreate signer keys.m_Signer = nullptr; @@ -814,29 +803,12 @@ namespace data XORMetric operator^(const IdentHash& key1, const IdentHash& key2) { XORMetric m; -#ifdef __AVX__ - if(i2p::cpu::avx) - { - __asm__ - ( - "vmovups %1, %%ymm0 \n" - "vmovups %2, %%ymm1 \n" - "vxorps %%ymm0, %%ymm1, %%ymm1 \n" - "vmovups %%ymm1, %0 \n" - : "=m"(*m.metric) - : "m"(*key1), "m"(*key2) - : "memory", "%xmm0", "%xmm1" // should be replaced by %ymm0/1 once supported by compiler - ); - } - else -#endif - { - const uint64_t * hash1 = key1.GetLL (), * hash2 = key2.GetLL (); - m.metric_ll[0] = hash1[0] ^ hash2[0]; - m.metric_ll[1] = hash1[1] ^ hash2[1]; - m.metric_ll[2] = hash1[2] ^ hash2[2]; - m.metric_ll[3] = hash1[3] ^ hash2[3]; - } + + const uint64_t * hash1 = key1.GetLL (), * hash2 = key2.GetLL (); + m.metric_ll[0] = hash1[0] ^ hash2[0]; + m.metric_ll[1] = hash1[1] ^ hash2[1]; + m.metric_ll[2] = hash1[2] ^ hash2[2]; + m.metric_ll[3] = hash1[3] ^ hash2[3]; return m; } diff --git a/libi2pd/Identity.h b/libi2pd/Identity.h index 534b8f4c..97d596d8 100644 --- a/libi2pd/Identity.h +++ b/libi2pd/Identity.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -13,9 +13,7 @@ #include #include #include -#include #include -#include #include "Base.h" #include "Signature.h" #include "CryptoKey.h" @@ -64,7 +62,7 @@ namespace data const uint16_t CRYPTO_KEY_TYPE_ELGAMAL = 0; const uint16_t CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC = 1; - const uint16_t CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET = 4; + const uint16_t CRYPTO_KEY_TYPE_ECIES_X25519_AEAD = 4; const uint16_t CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC_TEST = 65280; // TODO: remove later const uint16_t CRYPTO_KEY_TYPE_ECIES_GOSTR3410_CRYPTO_PRO_A_SHA256_AES256CBC = 65281; // TODO: use GOST R 34.11 instead SHA256 and GOST 28147-89 instead AES @@ -84,6 +82,7 @@ namespace data typedef uint16_t SigningKeyType; typedef uint16_t CryptoKeyType; + const size_t MAX_EXTENDED_BUFFER_SIZE = 8; // cryptoKeyType + signingKeyType + 4 extra bytes of P521 class IdentityEx { public: @@ -117,9 +116,8 @@ namespace data SigningKeyType GetSigningKeyType () const; bool IsRSA () const; // signing key type CryptoKeyType GetCryptoKeyType () const; - void DropVerifier () const; // to save memory - bool operator == (const IdentityEx & other) const { return GetIdentHash() == other.GetIdentHash(); } + bool operator == (const IdentityEx & other) const { return GetIdentHash() == other.GetIdentHash(); } void RecalculateIdentHash(uint8_t * buff=nullptr); static i2p::crypto::Verifier * CreateVerifier (SigningKeyType keyType); @@ -127,17 +125,15 @@ namespace data private: - void CreateVerifier () const; - void UpdateVerifier (i2p::crypto::Verifier * verifier) const; - + void CreateVerifier (); + private: Identity m_StandardIdentity; IdentHash m_IdentHash; - mutable i2p::crypto::Verifier * m_Verifier = nullptr; - mutable std::mutex m_VerifierMutex; + std::unique_ptr m_Verifier; size_t m_ExtendedLen; - uint8_t * m_ExtendedBuffer; + uint8_t m_ExtendedBuffer[MAX_EXTENDED_BUFFER_SIZE]; }; class PrivateKeys // for eepsites @@ -170,7 +166,7 @@ namespace data std::shared_ptr CreateDecryptor (const uint8_t * key) const; static std::shared_ptr CreateDecryptor (CryptoKeyType cryptoType, const uint8_t * key); - static PrivateKeys CreateRandomKeys (SigningKeyType type = SIGNING_KEY_TYPE_DSA_SHA1, CryptoKeyType cryptoType = CRYPTO_KEY_TYPE_ELGAMAL); + static PrivateKeys CreateRandomKeys (SigningKeyType type = SIGNING_KEY_TYPE_DSA_SHA1, CryptoKeyType cryptoType = CRYPTO_KEY_TYPE_ELGAMAL, bool isDestination = false); static void GenerateSigningKeyPair (SigningKeyType type, uint8_t * priv, uint8_t * pub); static void GenerateCryptoKeyPair (CryptoKeyType type, uint8_t * priv, uint8_t * pub); // priv and pub are 256 bytes long static i2p::crypto::Signer * CreateSigner (SigningKeyType keyType, const uint8_t * priv); @@ -183,6 +179,7 @@ namespace data void CreateSigner () const; void CreateSigner (SigningKeyType keyType) const; + size_t GetPrivateKeyLen () const; private: @@ -220,8 +217,8 @@ namespace data RoutingDestination () {}; virtual ~RoutingDestination () {}; - virtual std::shared_ptr GetIdentity () const = 0; - virtual void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx) const = 0; // encrypt data for + virtual std::shared_ptr GetIdentity () const = 0; + virtual void Encrypt (const uint8_t * data, uint8_t * encrypted) const = 0; // encrypt data for virtual bool IsDestination () const = 0; // for garlic const IdentHash& GetIdentHash () const { return GetIdentity ()->GetIdentHash (); }; @@ -233,7 +230,7 @@ namespace data public: virtual ~LocalDestination() {}; - virtual bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, CryptoKeyType preferredCrypto = CRYPTO_KEY_TYPE_ELGAMAL) const = 0; + virtual bool Decrypt (const uint8_t * encrypted, uint8_t * data, CryptoKeyType preferredCrypto = CRYPTO_KEY_TYPE_ELGAMAL) const = 0; virtual std::shared_ptr GetIdentity () const = 0; const IdentHash& GetIdentHash () const { return GetIdentity ()->GetIdentHash (); }; diff --git a/libi2pd/KadDHT.cpp b/libi2pd/KadDHT.cpp new file mode 100644 index 00000000..0f9df8e4 --- /dev/null +++ b/libi2pd/KadDHT.cpp @@ -0,0 +1,372 @@ +/* +* Copyright (c) 2023, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +* +*/ + +#include "KadDHT.h" + +namespace i2p +{ +namespace data +{ + DHTNode::DHTNode (): + zero (nullptr), one (nullptr) + { + } + + DHTNode::~DHTNode () + { + if (zero) delete zero; + if (one) delete one; + } + + void DHTNode::MoveRouterUp (bool fromOne) + { + DHTNode *& side = fromOne ? one : zero; + if (side) + { + if (router) router = nullptr; // shouldn't happen + router = side->router; + side->router = nullptr; + delete side; + side = nullptr; + } + } + + DHTTable::DHTTable (): + m_Size (0) + { + m_Root = new DHTNode; + } + + DHTTable::~DHTTable () + { + delete m_Root; + } + + void DHTTable::Clear () + { + m_Size = 0; + delete m_Root; + m_Root = new DHTNode; + } + + void DHTTable::Insert (const std::shared_ptr& r) + { + if (!r) return; + return Insert (r, m_Root, 0); + } + + void DHTTable::Insert (const std::shared_ptr& r, DHTNode * root, int level) + { + if (root->router) + { + if (root->router->GetIdentHash () == r->GetIdentHash ()) + { + root->router = r; // replace + return; + } + auto r2 = root->router; + root->router = nullptr; m_Size--; + int bit1, bit2; + do + { + bit1 = r->GetIdentHash ().GetBit (level); + bit2 = r2->GetIdentHash ().GetBit (level); + if (bit1 == bit2) + { + if (bit1) + { + if (root->one) return; // something wrong + root->one = new DHTNode; + root = root->one; + } + else + { + if (root->zero) return; // something wrong + root->zero = new DHTNode; + root = root->zero; + } + level++; + } + } + while (bit1 == bit2); + + if (!root->zero) + root->zero = new DHTNode; + if (!root->one) + root->one = new DHTNode; + if (bit1) + { + Insert (r2, root->zero, level + 1); + Insert (r, root->one, level + 1); + } + else + { + Insert (r2, root->one, level + 1); + Insert (r, root->zero, level + 1); + } + } + else + { + if (!root->zero && !root->one) + { + root->router = r; m_Size++; + return; + } + int bit = r->GetIdentHash ().GetBit (level); + if (bit) + { + if (!root->one) + root->one = new DHTNode; + Insert (r, root->one, level + 1); + } + else + { + if (!root->zero) + root->zero = new DHTNode; + Insert (r, root->zero, level + 1); + } + } + } + + bool DHTTable::Remove (const IdentHash& h) + { + return Remove (h, m_Root, 0); + } + + bool DHTTable::Remove (const IdentHash& h, DHTNode * root, int level) + { + if (root) + { + if (root->router && root->router->GetIdentHash () == h) + { + root->router = nullptr; + m_Size--; + return true; + } + int bit = h.GetBit (level); + if (bit) + { + if (root->one && Remove (h, root->one, level + 1)) + { + if (root->one->IsEmpty ()) + { + delete root->one; + root->one = nullptr; + if (root->zero && root->zero->router) + root->MoveRouterUp (false); + } + else if (root->one->router && !root->zero) + root->MoveRouterUp (true); + return true; + } + } + else + { + if (root->zero && Remove (h, root->zero, level + 1)) + { + if (root->zero->IsEmpty ()) + { + delete root->zero; + root->zero = nullptr; + if (root->one && root->one->router) + root->MoveRouterUp (true); + } + else if (root->zero->router && !root->one) + root->MoveRouterUp (false); + return true; + } + } + } + return false; + } + + std::shared_ptr DHTTable::FindClosest (const IdentHash& h, const Filter& filter) const + { + if (filter) m_Filter = filter; + auto r = FindClosest (h, m_Root, 0); + m_Filter = nullptr; + return r; + } + + std::shared_ptr DHTTable::FindClosest (const IdentHash& h, DHTNode * root, int level) const + { + bool split = false; + do + { + if (root->router) + return (!m_Filter || m_Filter (root->router)) ? root->router : nullptr; + split = root->zero && root->one; + if (!split) + { + if (root->zero) root = root->zero; + else if (root->one) root = root->one; + else return nullptr; + level++; + } + } + while (!split); + int bit = h.GetBit (level); + if (bit) + { + if (root->one) + { + auto r = FindClosest (h, root->one, level + 1); + if (r) return r; + } + if (root->zero) + { + auto r = FindClosest (h, root->zero, level + 1); + if (r) return r; + } + } + else + { + if (root->zero) + { + auto r = FindClosest (h, root->zero, level + 1); + if (r) return r; + } + if (root->one) + { + auto r = FindClosest (h, root->one, level + 1); + if (r) return r; + } + } + return nullptr; + } + + std::vector > DHTTable::FindClosest (const IdentHash& h, size_t num, const Filter& filter) const + { + std::vector > vec; + if (num > 0) + { + if (filter) m_Filter = filter; + FindClosest (h, num, m_Root, 0, vec); + m_Filter = nullptr; + } + return vec; + } + + void DHTTable::FindClosest (const IdentHash& h, size_t num, DHTNode * root, int level, std::vector >& hashes) const + { + if (hashes.size () >= num) return; + bool split = false; + do + { + if (root->router) + { + if (!m_Filter || m_Filter (root->router)) + hashes.push_back (root->router); + return; + } + split = root->zero && root->one; + if (!split) + { + if (root->zero) root = root->zero; + else if (root->one) root = root->one; + else return; + level++; + } + } + while (!split); + int bit = h.GetBit (level); + if (bit) + { + if (root->one) + FindClosest (h, num, root->one, level + 1, hashes); + if (hashes.size () < num && root->zero) + FindClosest (h, num, root->zero, level + 1, hashes); + } + else + { + if (root->zero) + FindClosest (h, num, root->zero, level + 1, hashes); + if (hashes.size () < num && root->one) + FindClosest (h, num, root->one, level + 1, hashes); + } + } + + void DHTTable::Cleanup (const Filter& filter) + { + if (filter) + { + m_Filter = filter; + Cleanup (m_Root); + m_Filter = nullptr; + } + else + Clear (); + } + + void DHTTable::Cleanup (DHTNode * root) + { + if (!root) return; + if (root->router) + { + if (!m_Filter || !m_Filter (root->router)) + { + m_Size--; + root->router = nullptr; + } + return; + } + if (root->zero) + { + Cleanup (root->zero); + if (root->zero->IsEmpty ()) + { + delete root->zero; + root->zero = nullptr; + } + } + if (root->one) + { + Cleanup (root->one); + if (root->one->IsEmpty ()) + { + delete root->one; + root->one = nullptr; + if (root->zero && root->zero->router) + root->MoveRouterUp (false); + } + else if (root->one->router && !root->zero) + root->MoveRouterUp (true); + } + } + + void DHTTable::Print (std::stringstream& s) + { + Print (s, m_Root, 0); + } + + void DHTTable::Print (std::stringstream& s, DHTNode * root, int level) + { + if (!root) return; + s << std::string (level, '-'); + if (root->router) + { + if (!root->zero && !root->one) + s << '>' << GetIdentHashAbbreviation (root->router->GetIdentHash ()); + else + s << "error"; + } + s << std::endl; + if (root->zero) + { + s << std::string (level, '-') << "0" << std::endl; + Print (s, root->zero, level + 1); + } + if (root->one) + { + s << std::string (level, '-') << "1" << std::endl; + Print (s, root->one, level + 1); + } + } +} +} diff --git a/libi2pd/KadDHT.h b/libi2pd/KadDHT.h new file mode 100644 index 00000000..3bc31780 --- /dev/null +++ b/libi2pd/KadDHT.h @@ -0,0 +1,74 @@ +/* +* Copyright (c) 2023, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +* +*/ + +#ifndef KADDHT_H__ +#define KADDHT_H__ + +#include +#include +#include +#include +#include "RouterInfo.h" + +// Kademlia DHT (XOR distance) + +namespace i2p +{ +namespace data +{ + struct DHTNode + { + DHTNode * zero, * one; + std::shared_ptr router; + + DHTNode (); + ~DHTNode (); + + bool IsEmpty () const { return !zero && !one && !router; }; + void MoveRouterUp (bool fromOne); + }; + + class DHTTable + { + typedef std::function&)> Filter; + public: + + DHTTable (); + ~DHTTable (); + + void Insert (const std::shared_ptr& r); + bool Remove (const IdentHash& h); + std::shared_ptr FindClosest (const IdentHash& h, const Filter& filter = nullptr) const; + std::vector > FindClosest (const IdentHash& h, size_t num, const Filter& filter = nullptr) const; + + void Print (std::stringstream& s); + size_t GetSize () const { return m_Size; }; + void Clear (); + void Cleanup (const Filter& filter); + + private: + + void Insert (const std::shared_ptr& r, DHTNode * root, int level); // recursive + bool Remove (const IdentHash& h, DHTNode * root, int level); + std::shared_ptr FindClosest (const IdentHash& h, DHTNode * root, int level) const; + void FindClosest (const IdentHash& h, size_t num, DHTNode * root, int level, std::vector >& hashes) const; + void Cleanup (DHTNode * root); + void Print (std::stringstream& s, DHTNode * root, int level); + + private: + + DHTNode * m_Root; + size_t m_Size; + // transient + mutable Filter m_Filter; + }; +} +} + +#endif diff --git a/libi2pd/LeaseSet.cpp b/libi2pd/LeaseSet.cpp index b90d6d85..675f6503 100644 --- a/libi2pd/LeaseSet.cpp +++ b/libi2pd/LeaseSet.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -37,14 +37,7 @@ namespace data void LeaseSet::Update (const uint8_t * buf, size_t len, bool verifySignature) { - if (len > m_BufferLen) - { - auto oldBuffer = m_Buffer; - m_Buffer = new uint8_t[len]; - delete[] oldBuffer; - } - memcpy (m_Buffer, buf, len); - m_BufferLen = len; + SetBuffer (buf, len); ReadFromBuffer (false, verifySignature); } @@ -57,11 +50,11 @@ namespace data void LeaseSet::ReadFromBuffer (bool readIdentity, bool verifySignature) { if (readIdentity || !m_Identity) - m_Identity = std::make_shared(m_Buffer, m_BufferLen); + m_Identity = netdb.NewIdentity (m_Buffer, m_BufferLen); size_t size = m_Identity->GetFullLen (); - if (size > m_BufferLen) + if (size + 256 > m_BufferLen) { - LogPrint (eLogError, "LeaseSet: identity length ", size, " exceeds buffer size ", m_BufferLen); + LogPrint (eLogError, "LeaseSet: Identity length ", int(size), " exceeds buffer size ", int(m_BufferLen)); m_IsValid = false; return; } @@ -72,18 +65,29 @@ namespace data } size += 256; // encryption key size += m_Identity->GetSigningPublicKeyLen (); // unused signing key + if (size + 1 > m_BufferLen) + { + LogPrint (eLogError, "LeaseSet: ", int(size), " exceeds buffer size ", int(m_BufferLen)); + m_IsValid = false; + return; + } uint8_t num = m_Buffer[size]; size++; // num - LogPrint (eLogDebug, "LeaseSet: read num=", (int)num); + LogPrint (eLogDebug, "LeaseSet: Read num=", (int)num); if (!num || num > MAX_NUM_LEASES) { - LogPrint (eLogError, "LeaseSet: incorrect number of leases", (int)num); + LogPrint (eLogError, "LeaseSet: Incorrect number of leases", (int)num); + m_IsValid = false; + return; + } + if (size + num*LEASE_SIZE > m_BufferLen) + { + LogPrint (eLogError, "LeaseSet: ", int(size), " exceeds buffer size ", int(m_BufferLen)); m_IsValid = false; return; } UpdateLeasesBegin (); - // process leases m_ExpirationTime = 0; auto ts = i2p::util::GetMillisecondsSinceEpoch (); @@ -101,19 +105,27 @@ namespace data } if (!m_ExpirationTime) { - LogPrint (eLogWarning, "LeaseSet: all leases are expired. Dropped"); + LogPrint (eLogWarning, "LeaseSet: All leases are expired. Dropped"); m_IsValid = false; return; } m_ExpirationTime += LEASE_ENDDATE_THRESHOLD; - UpdateLeasesEnd (); // verify - if (verifySignature && !m_Identity->Verify (m_Buffer, leases - m_Buffer, leases)) + if (verifySignature) { - LogPrint (eLogWarning, "LeaseSet: verification failed"); - m_IsValid = false; + auto signedSize = leases - m_Buffer; + if (signedSize + m_Identity->GetSignatureLen () > m_BufferLen) + { + LogPrint (eLogError, "LeaseSet: Signature exceeds buffer size ", int(m_BufferLen)); + m_IsValid = false; + } + else if (!m_Identity->Verify (m_Buffer, signedSize, leases)) + { + LogPrint (eLogWarning, "LeaseSet: Verification failed"); + m_IsValid = false; + } } } @@ -153,7 +165,7 @@ namespace data m_ExpirationTime = lease.endDate; if (m_StoreLeases) { - auto ret = m_Leases.insert (std::make_shared(lease)); + auto ret = m_Leases.insert (i2p::data::netdb.NewLease (lease)); if (!ret.second) (*ret.first)->endDate = lease.endDate; // update existing (*ret.first)->isUpdated = true; } @@ -235,18 +247,28 @@ namespace data return ts > m_ExpirationTime; } - void LeaseSet::Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx) const + void LeaseSet::Encrypt (const uint8_t * data, uint8_t * encrypted) const { if (!m_EncryptionKey) return; auto encryptor = m_Identity->CreateEncryptor (m_EncryptionKey); if (encryptor) - encryptor->Encrypt (data, encrypted, ctx, true); + encryptor->Encrypt (data, encrypted); } void LeaseSet::SetBuffer (const uint8_t * buf, size_t len) { - if (m_Buffer) delete[] m_Buffer; - m_Buffer = new uint8_t[len]; + if (len > MAX_LS_BUFFER_SIZE) + { + LogPrint (eLogError, "LeaseSet: Buffer is too long ", len); + len = MAX_LS_BUFFER_SIZE; + } + if (m_Buffer && len > m_BufferLen) + { + delete[] m_Buffer; + m_Buffer = nullptr; + } + if (!m_Buffer) + m_Buffer = new uint8_t[len]; m_BufferLen = len; memcpy (m_Buffer, buf, len); } @@ -255,7 +277,7 @@ namespace data { if (len <= m_BufferLen) m_BufferLen = len; else - LogPrint (eLogError, "LeaseSet2: actual buffer size ", len , " exceeds full buffer size ", m_BufferLen); + LogPrint (eLogError, "LeaseSet2: Actual buffer size ", int(len) , " exceeds full buffer size ", int(m_BufferLen)); } LeaseSet2::LeaseSet2 (uint8_t storeType, const uint8_t * buf, size_t len, bool storeLeases, CryptoKeyType preferredCrypto): @@ -293,15 +315,15 @@ namespace data { // standard LS2 header std::shared_ptr identity; - if (readIdentity) + if (readIdentity || !GetIdentity ()) { - identity = std::make_shared(buf, len); + identity = netdb.NewIdentity (buf, len); SetIdentity (identity); } else identity = GetIdentity (); size_t offset = identity->GetFullLen (); - if (offset + 8 >= len) return; + if (offset + 8 > len) return; m_PublishedTimestamp = bufbe32toh (buf + offset); offset += 4; // published timestamp (seconds) uint16_t expires = bufbe16toh (buf + offset); offset += 2; // expires (seconds) SetExpirationTime ((m_PublishedTimestamp + expires)*1000LL); // in milliseconds @@ -312,7 +334,7 @@ namespace data m_TransientVerifier = ProcessOfflineSignature (identity, buf, len, offset); if (!m_TransientVerifier) { - LogPrint (eLogError, "LeaseSet2: offline signature failed"); + LogPrint (eLogError, "LeaseSet2: Offline signature failed"); return; } } @@ -344,7 +366,13 @@ namespace data VerifySignature (identity, buf, len, offset); SetIsValid (verified); } + else + SetIsValid (true); offset += m_TransientVerifier ? m_TransientVerifier->GetSignatureLen () : identity->GetSignatureLen (); + if (offset > len) { + LogPrint (eLogWarning, "LeaseSet2: short buffer: wanted ", int(offset), "bytes, have ", int(len)); + return; + } SetBufferLen (offset); } @@ -359,7 +387,7 @@ namespace data bool verified = verifier->Verify (buf - 1, signatureOffset + 1, buf + signatureOffset); const_cast(buf)[-1] = c; if (!verified) - LogPrint (eLogWarning, "LeaseSet2: verification failed"); + LogPrint (eLogWarning, "LeaseSet2: Verification failed"); return verified; } @@ -369,17 +397,17 @@ namespace data // properties uint16_t propertiesLen = bufbe16toh (buf + offset); offset += 2; offset += propertiesLen; // skip for now. TODO: implement properties - if (offset + 1 >= len) return 0; // key sections CryptoKeyType preferredKeyType = m_EncryptionType; bool preferredKeyFound = false; + if (offset + 1 > len) return 0; int numKeySections = buf[offset]; offset++; for (int i = 0; i < numKeySections; i++) { + if (offset + 4 > len) return 0; uint16_t keyType = bufbe16toh (buf + offset); offset += 2; // encryption key type - if (offset + 2 >= len) return 0; uint16_t encryptionKeyLen = bufbe16toh (buf + offset); offset += 2; - if (offset + encryptionKeyLen >= len) return 0; + if (offset + encryptionKeyLen > len) return 0; if (IsStoreLeases () && !preferredKeyFound) // create encryptor with leases only { // we pick first valid key if preferred not found @@ -394,7 +422,7 @@ namespace data offset += encryptionKeyLen; } // leases - if (offset + 1 >= len) return 0; + if (offset + 1 > len) return 0; int numLeases = buf[offset]; offset++; auto ts = i2p::util::GetMillisecondsSinceEpoch (); if (IsStoreLeases ()) @@ -413,7 +441,8 @@ namespace data } else offset += numLeases*LEASE2_SIZE; // 40 bytes per lease - return offset; + + return (offset > len ? 0 : offset); } size_t LeaseSet2::ReadMetaLS2TypeSpecificPart (const uint8_t * buf, size_t len) @@ -423,18 +452,18 @@ namespace data uint16_t propertiesLen = bufbe16toh (buf + offset); offset += 2; offset += propertiesLen; // skip for now. TODO: implement properties // entries - if (offset + 1 >= len) return 0; + if (offset + 1 > len) return 0; int numEntries = buf[offset]; offset++; for (int i = 0; i < numEntries; i++) { - if (offset + 40 >= len) return 0; + if (offset + LEASE2_SIZE > len) return 0; offset += 32; // hash offset += 3; // flags offset += 1; // cost offset += 4; // expires } // revocations - if (offset + 1 >= len) return 0; + if (offset + 1 > len) return 0; int numRevocations = buf[offset]; offset++; for (int i = 0; i < numRevocations; i++) { @@ -470,7 +499,7 @@ namespace data m_TransientVerifier = ProcessOfflineSignature (blindedVerifier, buf, len, offset); if (!m_TransientVerifier) { - LogPrint (eLogError, "LeaseSet2: offline signature failed"); + LogPrint (eLogError, "LeaseSet2: Offline signature failed"); return; } } @@ -496,7 +525,7 @@ namespace data key->GetBlindedKey (date, blinded.data ()); if (memcmp (blindedPublicKey, blinded.data (), blindedKeyLen)) { - LogPrint (eLogError, "LeaseSet2: blinded public key doesn't match"); + LogPrint (eLogError, "LeaseSet2: Blinded public key doesn't match"); return; } } @@ -550,7 +579,7 @@ namespace data ReadFromBuffer (innerPlainText.data () + 1, lenInnerPlaintext - 1); } else - LogPrint (eLogError, "LeaseSet2: unexpected LeaseSet type ", (int)innerPlainText[0], " inside encrypted LeaseSet"); + LogPrint (eLogError, "LeaseSet2: Unexpected LeaseSet type ", (int)innerPlainText[0], " inside encrypted LeaseSet"); } else { @@ -563,7 +592,7 @@ namespace data // helper for ExtractClientAuthData static inline bool GetAuthCookie (const uint8_t * authClients, int numClients, const uint8_t * okm, uint8_t * authCookie) { - // try to find clientCookie_i for clientID_i = okm[44:51] + // try to find clientCookie_i for clientID_i = okm[44:51] for (int i = 0; i < numClients; i++) { if (!memcmp (okm + 44, authClients + i*40, 8)) // clientID_i @@ -587,7 +616,7 @@ namespace data { const uint8_t * ephemeralPublicKey = buf + offset; offset += 32; // ephemeralPublicKey uint16_t numClients = bufbe16toh (buf + offset); offset += 2; // clients - const uint8_t * authClients = buf + offset; offset += numClients*40; // authClients + const uint8_t * authClients = buf + offset; offset += numClients*40; // authClients if (offset > len) { LogPrint (eLogError, "LeaseSet2: Too many clients ", numClients, " in DH auth data"); @@ -613,7 +642,7 @@ namespace data { const uint8_t * authSalt = buf + offset; offset += 32; // authSalt uint16_t numClients = bufbe16toh (buf + offset); offset += 2; // clients - const uint8_t * authClients = buf + offset; offset += numClients*40; // authClients + const uint8_t * authClients = buf + offset; offset += numClients*40; // authClients if (offset > len) { LogPrint (eLogError, "LeaseSet2: Too many clients ", numClients, " in PSK auth data"); @@ -634,16 +663,16 @@ namespace data LogPrint (eLogError, "LeaseSet2: Can't calculate authCookie: psk_i is not provided"); } else - LogPrint (eLogError, "LeaseSet2: unknown client auth type ", (int)flag); + LogPrint (eLogError, "LeaseSet2: Unknown client auth type ", (int)flag); } return offset - 1; } - void LeaseSet2::Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx) const + void LeaseSet2::Encrypt (const uint8_t * data, uint8_t * encrypted) const { auto encryptor = m_Encryptor; // TODO: atomic if (encryptor) - encryptor->Encrypt (data, encrypted, ctx, true); + encryptor->Encrypt (data, encrypted); } uint64_t LeaseSet2::ExtractExpirationTimestamp (const uint8_t * buf, size_t len) const @@ -718,7 +747,7 @@ namespace data htobe64buf (m_Buffer + offset, ts); offset += 8; // end date } - // we don't sign it yet. must be signed later on + // we don't sign it yet. must be signed later on } LocalLeaseSet::LocalLeaseSet (std::shared_ptr identity, const uint8_t * buf, size_t len): @@ -749,7 +778,7 @@ namespace data size_t size = ident.GetFullLen (); if (size > sz) { - LogPrint (eLogError, "LeaseSet: identity length ", size, " exceeds buffer size ", sz); + LogPrint (eLogError, "LeaseSet: Identity length ", size, " exceeds buffer size ", sz); return false; } // encryption key @@ -760,7 +789,7 @@ namespace data ++size; if (!numLeases || numLeases > MAX_NUM_LEASES) { - LogPrint (eLogError, "LeaseSet: incorrect number of leases", (int)numLeases); + LogPrint (eLogError, "LeaseSet: Incorrect number of leases", (int)numLeases); return false; } const uint8_t * leases = ptr + size; @@ -778,7 +807,7 @@ namespace data } LocalLeaseSet2::LocalLeaseSet2 (uint8_t storeType, const i2p::data::PrivateKeys& keys, - const KeySections& encryptionKeys, std::vector > tunnels, + const KeySections& encryptionKeys, const std::vector >& tunnels, bool isPublic, bool isPublishedEncrypted): LocalLeaseSet (keys.GetPublic (), nullptr, 0) { @@ -843,9 +872,18 @@ namespace data offset += 4; // end date } // update expiration - SetExpirationTime (expirationTime*1000LL); - auto expires = expirationTime - timestamp; - htobe16buf (expiresBuf, expires > 0 ? expires : 0); + if (expirationTime) + { + SetExpirationTime (expirationTime*1000LL); + auto expires = (int)expirationTime - timestamp; + htobe16buf (expiresBuf, expires > 0 ? expires : 0); + } + else + { + // no tunnels or withdraw + SetExpirationTime (timestamp*1000LL); + memset (expiresBuf, 0, 2); // expires immeditely + } // sign keys.Sign (m_Buffer, offset, m_Buffer + offset); // LS + leading store type } @@ -884,6 +922,11 @@ namespace data uint8_t blindedPriv[64], blindedPub[128]; // 64 and 128 max size_t publicKeyLen = blindedKey.BlindPrivateKey (keys.GetSigningPrivateKey (), date, blindedPriv, blindedPub); std::unique_ptr blindedSigner (i2p::data::PrivateKeys::CreateSigner (blindedKey.GetBlindedSigType (), blindedPriv)); + if (!blindedSigner) + { + LogPrint (eLogError, "LeaseSet2: Can't create blinded signer for signature type ", blindedKey.GetSigType ()); + return; + } auto offset = 1; htobe16buf (m_Buffer + offset, blindedKey.GetBlindedSigType ()); offset += 2; // Blinded Public Key Sig Type memcpy (m_Buffer + offset, blindedPub, publicKeyLen); offset += publicKeyLen; // Blinded Public Key @@ -951,7 +994,7 @@ namespace data m_StoreHash = blindedKey->GetStoreHash (); } else - LogPrint (eLogError, "LeaseSet2: couldn't extract inner layer"); + LogPrint (eLogError, "LeaseSet2: Couldn't extract inner layer"); } void LocalEncryptedLeaseSet2::CreateClientAuthData (const uint8_t * subcredential, int authType, std::shared_ptr > authKeys, const uint8_t * authCookie, uint8_t * authData) const @@ -962,7 +1005,7 @@ namespace data ek.GenerateKeys (); // esk and epk memcpy (authData, ek.GetPublicKey (), 32); authData += 32; // epk htobe16buf (authData, authKeys->size ()); authData += 2; // num clients - uint8_t authInput[100]; // sharedSecret || cpk_i || subcredential || publishedTimestamp + uint8_t authInput[100]; // sharedSecret || cpk_i || subcredential || publishedTimestamp memcpy (authInput + 64, subcredential, 36); for (auto& it: *authKeys) { diff --git a/libi2pd/LeaseSet.h b/libi2pd/LeaseSet.h index cd31bf30..4b1311a5 100644 --- a/libi2pd/LeaseSet.h +++ b/libi2pd/LeaseSet.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -93,9 +93,12 @@ namespace data // implements RoutingDestination std::shared_ptr GetIdentity () const { return m_Identity; }; - void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx) const; + void Encrypt (const uint8_t * data, uint8_t * encrypted) const; bool IsDestination () const { return true; }; + // used in webconsole + void ExpireLease () { m_ExpirationTime = i2p::util::GetSecondsSinceEpoch (); }; + protected: void UpdateLeasesBegin (); @@ -128,8 +131,8 @@ namespace data }; /** - validate lease set buffer signature and extract expiration timestamp - @returns true if the leaseset is well formed and signature is valid + * validate lease set buffer signature and extract expiration timestamp + * @returns true if the leaseset is well formed and signature is valid */ bool LeaseSetBufferValidate(const uint8_t * ptr, size_t sz, uint64_t & expires); @@ -145,6 +148,7 @@ namespace data { public: + LeaseSet2 (uint8_t storeType): LeaseSet (true), m_StoreType (storeType) {}; // for update LeaseSet2 (uint8_t storeType, const uint8_t * buf, size_t len, bool storeLeases = true, CryptoKeyType preferredCrypto = CRYPTO_KEY_TYPE_ELGAMAL); LeaseSet2 (const uint8_t * buf, size_t len, std::shared_ptr key, const uint8_t * secret = nullptr, CryptoKeyType preferredCrypto = CRYPTO_KEY_TYPE_ELGAMAL); // store type 5, called from local netdb only uint8_t GetStoreType () const { return m_StoreType; }; @@ -156,7 +160,7 @@ namespace data bool IsNewer (const uint8_t * buf, size_t len) const; // implements RoutingDestination - void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx) const; + void Encrypt (const uint8_t * data, uint8_t * encrypted) const; CryptoKeyType GetEncryptionType () const { return m_EncryptionType; }; private: @@ -251,7 +255,7 @@ namespace data LocalLeaseSet2 (uint8_t storeType, const i2p::data::PrivateKeys& keys, const KeySections& encryptionKeys, - std::vector > tunnels, + const std::vector >& tunnels, bool isPublic, bool isPublishedEncrypted = false); LocalLeaseSet2 (uint8_t storeType, std::shared_ptr identity, const uint8_t * buf, size_t len); // from I2CP diff --git a/libi2pd/Log.cpp b/libi2pd/Log.cpp index a0014841..76e85d4a 100644 --- a/libi2pd/Log.cpp +++ b/libi2pd/Log.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -7,6 +7,7 @@ */ #include "Log.h" +#include "util.h" //for std::transform #include @@ -17,13 +18,14 @@ namespace log { /** * @brief Maps our loglevel to their symbolic name */ - static const char * g_LogLevelStr[eNumLogLevels] = + static const char *g_LogLevelStr[eNumLogLevels] = { - "none", // eLogNone - "error", // eLogError - "warn", // eLogWarn - "info", // eLogInfo - "debug" // eLogDebug + "none", // eLogNone + "critical", // eLogCritical + "error", // eLogError + "warn", // eLogWarning + "info", // eLogInfo + "debug" // eLogDebug }; /** @@ -31,27 +33,29 @@ namespace log { * @note Using ISO 6429 (ANSI) color sequences */ #ifdef _WIN32 - static const char *LogMsgColors[] = { "", "", "", "", "", "" }; + static const char *LogMsgColors[] = { "", "", "", "", "", "", "" }; #else /* UNIX */ static const char *LogMsgColors[] = { - [eLogNone] = "\033[0m", /* reset */ - [eLogError] = "\033[1;31m", /* red */ - [eLogWarning] = "\033[1;33m", /* yellow */ - [eLogInfo] = "\033[1;36m", /* cyan */ - [eLogDebug] = "\033[1;34m", /* blue */ - [eNumLogLevels] = "\033[0m", /* reset */ + "\033[1;32m", /* none: green */ + "\033[1;41m", /* critical: red background */ + "\033[1;31m", /* error: red */ + "\033[1;33m", /* warning: yellow */ + "\033[1;36m", /* info: cyan */ + "\033[1;34m", /* debug: blue */ + "\033[0m" /* reset */ }; #endif #ifndef _WIN32 /** - * @brief Maps our log levels to syslog one + * @brief Maps our log levels to syslog one * @return syslog priority LOG_*, as defined in syslog.h */ static inline int GetSyslogPrio (enum LogLevel l) { int priority = LOG_DEBUG; switch (l) { case eLogNone : priority = LOG_CRIT; break; + case eLogCritical: priority = LOG_CRIT; break; case eLogError : priority = LOG_ERR; break; case eLogWarning : priority = LOG_WARNING; break; case eLogInfo : priority = LOG_INFO; break; @@ -112,26 +116,27 @@ namespace log { std::string str_tolower(std::string s) { std::transform(s.begin(), s.end(), s.begin(), - // static_cast(std::tolower) // wrong - // [](int c){ return std::tolower(c); } // wrong - // [](char c){ return std::tolower(c); } // wrong - [](unsigned char c){ return std::tolower(c); } // correct - ); + // static_cast(std::tolower) // wrong + // [](int c){ return std::tolower(c); } // wrong + // [](char c){ return std::tolower(c); } // wrong + [](unsigned char c){ return std::tolower(c); } // correct + ); return s; } void Log::SetLogLevel (const std::string& level_) { std::string level=str_tolower(level_); - if (level == "none") { m_MinLevel = eLogNone; } - else if (level == "error") { m_MinLevel = eLogError; } - else if (level == "warn") { m_MinLevel = eLogWarning; } - else if (level == "info") { m_MinLevel = eLogInfo; } - else if (level == "debug") { m_MinLevel = eLogDebug; } + if (level == "none") { m_MinLevel = eLogNone; } + else if (level == "critical") { m_MinLevel = eLogCritical; } + else if (level == "error") { m_MinLevel = eLogError; } + else if (level == "warn") { m_MinLevel = eLogWarning; } + else if (level == "info") { m_MinLevel = eLogInfo; } + else if (level == "debug") { m_MinLevel = eLogDebug; } else { - LogPrint(eLogError, "Log: unknown loglevel: ", level); + LogPrint(eLogCritical, "Log: Unknown loglevel: ", level); return; } - LogPrint(eLogInfo, "Log: min messages level set to ", level); + LogPrint(eLogInfo, "Log: Logging level set to ", level); } const char * Log::TimeAsString(std::time_t t) { @@ -169,7 +174,7 @@ namespace log { break; case eLogStdout: default: - std::cout << TimeAsString(msg->timestamp) + std::cout << TimeAsString(msg->timestamp) << "@" << short_tid << "/" << LogMsgColors[msg->level] << g_LogLevelStr[msg->level] << LogMsgColors[eNumLogLevels] << " - " << msg->text << std::endl; @@ -179,6 +184,8 @@ namespace log { void Log::Run () { + i2p::util::SetThreadName("Logging"); + Reopen (); while (m_IsRunning) { @@ -209,7 +216,7 @@ namespace log { m_LogStream = os; return; } - LogPrint(eLogError, "Log: can't open file ", path); + LogPrint(eLogCritical, "Log: Can't open file ", path); } void Log::SendTo (std::shared_ptr os) { diff --git a/libi2pd/Log.h b/libi2pd/Log.h index 972a00e1..1ec0c5fe 100644 --- a/libi2pd/Log.h +++ b/libi2pd/Log.h @@ -27,6 +27,7 @@ enum LogLevel { eLogNone = 0, + eLogCritical, eLogError, eLogWarning, eLogInfo, @@ -52,7 +53,7 @@ namespace log { { private: - enum LogType m_Destination; + enum LogType m_Destination; enum LogLevel m_MinLevel; std::shared_ptr m_LogStream; std::string m_Logfile; @@ -75,7 +76,7 @@ namespace log { /** * @brief Makes formatted string from unix timestamp - * @param ts Second since epoch + * @param ts Second since epoch * * This function internally caches the result for last provided value */ @@ -86,52 +87,52 @@ namespace log { Log (); ~Log (); - LogType GetLogType () { return m_Destination; }; + LogType GetLogType () { return m_Destination; }; LogLevel GetLogLevel () { return m_MinLevel; }; void Start (); void Stop (); /** - * @brief Sets minimal allowed level for log messages - * @param level String with wanted minimal msg level + * @brief Sets minimal allowed level for log messages + * @param level String with wanted minimal msg level */ - void SetLogLevel (const std::string& level); + void SetLogLevel (const std::string& level); /** * @brief Sets log destination to logfile - * @param path Path to logfile + * @param path Path to logfile */ void SendTo (const std::string &path); /** * @brief Sets log destination to given output stream - * @param os Output stream + * @param os Output stream */ void SendTo (std::shared_ptr os); /** - * @brief Sets format for timestamps in log - * @param format String with timestamp format + * @brief Sets format for timestamps in log + * @param format String with timestamp format */ void SetTimeFormat (std::string format) { m_TimeFormat = format; }; #ifndef _WIN32 /** * @brief Sets log destination to syslog - * @param name Wanted program name + * @param name Wanted program name * @param facility Wanted log category */ void SendTo (const char *name, int facility); #endif /** - * @brief Format log message and write to output stream/syslog - * @param msg Pointer to processed message + * @brief Format log message and write to output stream/syslog + * @param msg Pointer to processed message */ void Append(std::shared_ptr &); - /** @brief Reopen log file */ + /** @brief Reopen log file */ void Reopen(); }; @@ -144,16 +145,16 @@ namespace log { */ struct LogMsg { std::time_t timestamp; - std::string text; /**< message text as single string */ - LogLevel level; /**< message level */ + std::string text; /**< message text as single string */ + LogLevel level; /**< message level */ std::thread::id tid; /**< id of thread that generated message */ - LogMsg (LogLevel lvl, std::time_t ts, const std::string & txt): timestamp(ts), text(txt), level(lvl) {}; + LogMsg (LogLevel lvl, std::time_t ts, std::string&& txt): timestamp(ts), text(std::move(txt)), level(lvl) {} }; Log & Logger(); - typedef std::function ThrowFunction; + typedef std::function ThrowFunction; ThrowFunction GetThrowFunction (); void SetThrowFunction (ThrowFunction f); } // log @@ -189,7 +190,7 @@ void LogPrint (LogLevel level, TArgs&&... args) noexcept return; // fold message to single string - std::stringstream ss(""); + std::stringstream ss; #if (__cplusplus >= 201703L) // C++ 17 or higher (LogPrint (ss, std::forward(args)), ...); @@ -197,7 +198,7 @@ void LogPrint (LogLevel level, TArgs&&... args) noexcept LogPrint (ss, std::forward(args)...); #endif - auto msg = std::make_shared(level, std::time(nullptr), ss.str()); + auto msg = std::make_shared(level, std::time(nullptr), std::move(ss).str()); msg->tid = std::this_thread::get_id(); log.Append(msg); } diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp index cdf660b7..bbd93435 100644 --- a/libi2pd/NTCP2.cpp +++ b/libi2pd/NTCP2.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -23,56 +23,27 @@ #include "HTTP.h" #include "util.h" +#if defined(__linux__) && !defined(_NETINET_IN_H) + #include +#endif + namespace i2p { namespace transport { NTCP2Establisher::NTCP2Establisher (): - m_SessionRequestBuffer (nullptr), m_SessionCreatedBuffer (nullptr), m_SessionConfirmedBuffer (nullptr) + m_SessionConfirmedBuffer (nullptr) { } NTCP2Establisher::~NTCP2Establisher () { - delete[] m_SessionRequestBuffer; - delete[] m_SessionCreatedBuffer; delete[] m_SessionConfirmedBuffer; } - void NTCP2Establisher::MixKey (const uint8_t * inputKeyMaterial) - { - i2p::crypto::HKDF (m_CK, inputKeyMaterial, 32, "", m_CK); - // ck is m_CK[0:31], k is m_CK[32:63] - } - - void NTCP2Establisher::MixHash (const uint8_t * buf, size_t len) - { - SHA256_CTX ctx; - SHA256_Init (&ctx); - SHA256_Update (&ctx, m_H, 32); - SHA256_Update (&ctx, buf, len); - SHA256_Final (m_H, &ctx); - } - void NTCP2Establisher::KeyDerivationFunction1 (const uint8_t * pub, i2p::crypto::X25519Keys& priv, const uint8_t * rs, const uint8_t * epub) { - static const uint8_t protocolNameHash[] = - { - 0x72, 0xe8, 0x42, 0xc5, 0x45, 0xe1, 0x80, 0x80, 0xd3, 0x9c, 0x44, 0x93, 0xbb, 0x91, 0xd7, 0xed, - 0xf2, 0x28, 0x98, 0x17, 0x71, 0x21, 0x8c, 0x1f, 0x62, 0x4e, 0x20, 0x6f, 0x28, 0xd3, 0x2f, 0x71 - }; // SHA256 ("Noise_XKaesobfse+hs2+hs3_25519_ChaChaPoly_SHA256") - static const uint8_t hh[32] = - { - 0x49, 0xff, 0x48, 0x3f, 0xc4, 0x04, 0xb9, 0xb2, 0x6b, 0x11, 0x94, 0x36, 0x72, 0xff, 0x05, 0xb5, - 0x61, 0x27, 0x03, 0x31, 0xba, 0x89, 0xb8, 0xfc, 0x33, 0x15, 0x93, 0x87, 0x57, 0xdd, 0x3d, 0x1e - }; // SHA256 (protocolNameHash) - memcpy (m_CK, protocolNameHash, 32); - // h = SHA256(hh || rs) - SHA256_CTX ctx; - SHA256_Init (&ctx); - SHA256_Update (&ctx, hh, 32); - SHA256_Update (&ctx, rs, 32); - SHA256_Final (m_H, &ctx); + i2p::crypto::InitNoiseXKState (*this, rs); // h = SHA256(h || epub) MixHash (epub, 32); // x25519 between pub and priv @@ -88,14 +59,14 @@ namespace transport void NTCP2Establisher::KDF1Bob () { - KeyDerivationFunction1 (GetRemotePub (), i2p::context.GetStaticKeys (), i2p::context.GetNTCP2StaticPublicKey (), GetRemotePub ()); + KeyDerivationFunction1 (GetRemotePub (), i2p::context.GetNTCP2StaticKeys (), i2p::context.GetNTCP2StaticPublicKey (), GetRemotePub ()); } void NTCP2Establisher::KeyDerivationFunction2 (const uint8_t * sessionRequest, size_t sessionRequestLen, const uint8_t * epub) { MixHash (sessionRequest + 32, 32); // encrypted payload - int paddingLength = sessionRequestLen - 64; + int paddingLength = sessionRequestLen - 64; if (paddingLength > 0) MixHash (sessionRequest + 64, paddingLength); MixHash (epub, 32); @@ -120,7 +91,7 @@ namespace transport void NTCP2Establisher::KDF3Alice () { uint8_t inputKeyMaterial[32]; - i2p::context.GetStaticKeys ().Agree (GetRemotePub (), inputKeyMaterial); + i2p::context.GetNTCP2StaticKeys ().Agree (GetRemotePub (), inputKeyMaterial); MixKey (inputKeyMaterial); } @@ -139,9 +110,8 @@ namespace transport void NTCP2Establisher::CreateSessionRequestMessage () { // create buffer and fill padding - auto paddingLength = rand () % (287 - 64); // message length doesn't exceed 287 bytes + auto paddingLength = rand () % (NTCP2_SESSION_REQUEST_MAX_SIZE - 64); // message length doesn't exceed 287 bytes m_SessionRequestBufferLen = paddingLength + 64; - m_SessionRequestBuffer = new uint8_t[m_SessionRequestBufferLen]; RAND_bytes (m_SessionRequestBuffer + 64, paddingLength); // encrypt X i2p::crypto::CBCEncryption encryption; @@ -160,7 +130,7 @@ namespace transport // m3p2Len auto bufLen = i2p::context.GetRouterInfo ().GetBufferLen (); m3p2Len = bufLen + 4 + 16; // (RI header + RI + MAC for now) TODO: implement options - htobe16buf (options + 4, m3p2Len); + htobe16buf (options + 4, m3p2Len); // fill m3p2 payload (RouterInfo block) m_SessionConfirmedBuffer = new uint8_t[m3p2Len + 48]; // m3p1 is 48 bytes uint8_t * m3p2 = m_SessionConfirmedBuffer + 48; @@ -169,7 +139,7 @@ namespace transport m3p2[3] = 0; // flag memcpy (m3p2 + 4, i2p::context.GetRouterInfo ().GetBuffer (), bufLen); // TODO: own RI should be protected by mutex // 2 bytes reserved - htobe32buf (options + 8, i2p::util::GetSecondsSinceEpoch ()); // tsA + htobe32buf (options + 8, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000); // tsA, rounded to seconds // 4 bytes reserved // sign and encrypt options, use m_H as AD uint8_t nonce[12]; @@ -179,9 +149,8 @@ namespace transport void NTCP2Establisher::CreateSessionCreatedMessage () { - auto paddingLen = rand () % (287 - 64); + auto paddingLen = rand () % (NTCP2_SESSION_CREATED_MAX_SIZE - 64); m_SessionCreatedBufferLen = paddingLen + 64; - m_SessionCreatedBuffer = new uint8_t[m_SessionCreatedBufferLen]; RAND_bytes (m_SessionCreatedBuffer + 64, paddingLen); // encrypt Y i2p::crypto::CBCEncryption encryption; @@ -193,7 +162,7 @@ namespace transport uint8_t options[16]; memset (options, 0, 16); htobe16buf (options + 2, paddingLen); // padLen - htobe32buf (options + 8, i2p::util::GetSecondsSinceEpoch ()); // tsB + htobe32buf (options + 8, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000); // tsB, rounded to seconds // sign and encrypt options, use m_H as AD uint8_t nonce[12]; memset (nonce, 0, 12); // set nonce to zero @@ -226,8 +195,9 @@ namespace transport MixHash (m3p2, m3p2Len); //h = SHA256(h || ciphertext) } - bool NTCP2Establisher::ProcessSessionRequestMessage (uint16_t& paddingLen) + bool NTCP2Establisher::ProcessSessionRequestMessage (uint16_t& paddingLen, bool& clockSkew) { + clockSkew = false; // decrypt X i2p::crypto::CBCDecryption decryption; decryption.SetKey (i2p::context.GetIdentHash ()); @@ -263,7 +233,8 @@ namespace transport if (tsA < ts - NTCP2_CLOCK_SKEW || tsA > ts + NTCP2_CLOCK_SKEW) { LogPrint (eLogWarning, "NTCP2: SessionRequest time difference ", (int)(ts - tsA), " exceeds clock skew"); - return false; + clockSkew = true; + // we send SessionCreate to let Alice know our time and then close session } } else @@ -338,7 +309,7 @@ namespace transport KDF3Bob (); if (i2p::crypto::AEADChaCha20Poly1305 (m_SessionConfirmedBuffer + 48, m3p2Len - 16, GetH (), 32, GetK (), nonce, m3p2Buf, m3p2Len - 16, false)) // decrypt - // caclulate new h again for KDF data + // calculate new h again for KDF data MixHash (m_SessionConfirmedBuffer + 48, m3p2Len); // h = SHA256(h || ciphertext) else { @@ -348,30 +319,35 @@ namespace transport return true; } - NTCP2Session::NTCP2Session (NTCP2Server& server, std::shared_ptr in_RemoteRouter): + NTCP2Session::NTCP2Session (NTCP2Server& server, std::shared_ptr in_RemoteRouter, + std::shared_ptr addr): TransportSession (in_RemoteRouter, NTCP2_ESTABLISH_TIMEOUT), m_Server (server), m_Socket (m_Server.GetService ()), m_IsEstablished (false), m_IsTerminated (false), m_Establisher (new NTCP2Establisher), - m_SendSipKey (nullptr), m_ReceiveSipKey (nullptr), #if OPENSSL_SIPHASH m_SendMDCtx(nullptr), m_ReceiveMDCtx (nullptr), +#else + m_SendSipKey (nullptr), m_ReceiveSipKey (nullptr), #endif m_NextReceivedLen (0), m_NextReceivedBuffer (nullptr), m_NextSendBuffer (nullptr), - m_ReceiveSequenceNumber (0), m_SendSequenceNumber (0), m_IsSending (false) + m_NextReceivedBufferSize (0), m_ReceiveSequenceNumber (0), m_SendSequenceNumber (0), + m_IsSending (false), m_IsReceiving (false), m_NextPaddingSize (16) { if (in_RemoteRouter) // Alice { m_Establisher->m_RemoteIdentHash = GetRemoteIdentity ()->GetIdentHash (); - auto addr = in_RemoteRouter->GetNTCP2Address (true); // we need a published address if (addr) { - memcpy (m_Establisher->m_RemoteStaticKey, addr->ntcp2->staticKey, 32); - memcpy (m_Establisher->m_IV, addr->ntcp2->iv, 16); + memcpy (m_Establisher->m_RemoteStaticKey, addr->s, 32); + memcpy (m_Establisher->m_IV, addr->i, 16); + m_RemoteEndpoint = boost::asio::ip::tcp::endpoint (addr->host, addr->port); } else - LogPrint (eLogWarning, "NTCP2: Missing NTCP2 parameters"); + LogPrint (eLogWarning, "NTCP2: Missing NTCP2 address"); } + m_NextRouterInfoResendTime = i2p::util::GetSecondsSinceEpoch () + NTCP2_ROUTERINFO_RESEND_INTERVAL + + rand ()%NTCP2_ROUTERINFO_RESEND_INTERVAL_THRESHOLD; } NTCP2Session::~NTCP2Session () @@ -379,8 +355,6 @@ namespace transport delete[] m_NextReceivedBuffer; delete[] m_NextSendBuffer; #if OPENSSL_SIPHASH - if (m_SendSipKey) EVP_PKEY_free (m_SendSipKey); - if (m_ReceiveSipKey) EVP_PKEY_free (m_ReceiveSipKey); if (m_SendMDCtx) EVP_MD_CTX_destroy (m_SendMDCtx); if (m_ReceiveMDCtx) EVP_MD_CTX_destroy (m_ReceiveMDCtx); #endif @@ -400,10 +374,25 @@ namespace transport transports.PeerDisconnected (shared_from_this ()); m_Server.RemoveNTCP2Session (shared_from_this ()); m_SendQueue.clear (); - LogPrint (eLogDebug, "NTCP2: session terminated"); + m_SendQueueSize = 0; + auto remoteIdentity = GetRemoteIdentity (); + if (remoteIdentity) + { + LogPrint (eLogDebug, "NTCP2: Session with ", GetRemoteEndpoint (), + " (", i2p::data::GetIdentHashAbbreviation (GetRemoteIdentity ()->GetIdentHash ()), ") terminated"); + } + else + { + LogPrint (eLogDebug, "NTCP2: Session with ", GetRemoteEndpoint (), " terminated"); + } } } + void NTCP2Session::Close () + { + m_Socket.close (); + } + void NTCP2Session::TerminateByTimeout () { SendTerminationAndTerminate (eNTCP2IdleTimeout); @@ -428,6 +417,29 @@ namespace transport htole64buf (nonce + 4, seqn); } + void NTCP2Session::CreateNextReceivedBuffer (size_t size) + { + if (m_NextReceivedBuffer) + { + if (size <= m_NextReceivedBufferSize) + return; // buffer is good, do nothing + else + delete[] m_NextReceivedBuffer; + } + m_NextReceivedBuffer = new uint8_t[size]; + m_NextReceivedBufferSize = size; + } + + void NTCP2Session::DeleteNextReceiveBuffer (uint64_t ts) + { + if (m_NextReceivedBuffer && !m_IsReceiving && + ts > m_LastActivityTimestamp + NTCP2_RECEIVE_BUFFER_DELETION_TIMEOUT) + { + delete[] m_NextReceivedBuffer; + m_NextReceivedBuffer = nullptr; + m_NextReceivedBufferSize = 0; + } + } void NTCP2Session::KeyDerivationFunctionDataPhase () { @@ -449,6 +461,7 @@ namespace transport { m_Establisher->CreateSessionRequestMessage (); // send message + m_HandshakeInterval = i2p::util::GetMillisecondsSinceEpoch (); boost::asio::async_write (m_Socket, boost::asio::buffer (m_Establisher->m_SessionRequestBuffer, m_Establisher->m_SessionRequestBufferLen), boost::asio::transfer_all (), std::bind(&NTCP2Session::HandleSessionRequestSent, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } @@ -458,12 +471,11 @@ namespace transport (void) bytes_transferred; if (ecode) { - LogPrint (eLogWarning, "NTCP2: couldn't send SessionRequest message: ", ecode.message ()); + LogPrint (eLogWarning, "NTCP2: Couldn't send SessionRequest message: ", ecode.message ()); Terminate (); } else { - m_Establisher->m_SessionCreatedBuffer = new uint8_t[287]; // TODO: determine actual max size // we receive first 64 bytes (32 Y, and 32 ChaCha/Poly frame) first boost::asio::async_read (m_Socket, boost::asio::buffer(m_Establisher->m_SessionCreatedBuffer, 64), boost::asio::transfer_all (), std::bind(&NTCP2Session::HandleSessionCreatedReceived, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); @@ -482,18 +494,25 @@ namespace transport { LogPrint (eLogDebug, "NTCP2: SessionRequest received ", bytes_transferred); uint16_t paddingLen = 0; - if (m_Establisher->ProcessSessionRequestMessage (paddingLen)) + bool clockSkew = false; + if (m_Establisher->ProcessSessionRequestMessage (paddingLen, clockSkew)) { - if (paddingLen > 0) + if (clockSkew) { - if (paddingLen <= 287 - 64) // session request is 287 bytes max + // we don't care about padding, send SessionCreated and close session + SendSessionCreated (); + m_Server.GetService ().post (std::bind (&NTCP2Session::Terminate, shared_from_this ())); + } + else if (paddingLen > 0) + { + if (paddingLen <= NTCP2_SESSION_REQUEST_MAX_SIZE - 64) // session request is 287 bytes max { boost::asio::async_read (m_Socket, boost::asio::buffer(m_Establisher->m_SessionRequestBuffer + 64, paddingLen), boost::asio::transfer_all (), std::bind(&NTCP2Session::HandleSessionRequestPaddingReceived, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } else { - LogPrint (eLogWarning, "NTCP2: SessionRequest padding length ", (int)paddingLen, " is too long"); + LogPrint (eLogWarning, "NTCP2: SessionRequest padding length ", (int)paddingLen, " is too long"); Terminate (); } } @@ -520,6 +539,7 @@ namespace transport { m_Establisher->CreateSessionCreatedMessage (); // send message + m_HandshakeInterval = i2p::util::GetMillisecondsSinceEpoch (); boost::asio::async_write (m_Socket, boost::asio::buffer (m_Establisher->m_SessionCreatedBuffer, m_Establisher->m_SessionCreatedBufferLen), boost::asio::transfer_all (), std::bind(&NTCP2Session::HandleSessionCreatedSent, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } @@ -533,20 +553,21 @@ namespace transport } else { + m_HandshakeInterval = i2p::util::GetMillisecondsSinceEpoch () - m_HandshakeInterval; LogPrint (eLogDebug, "NTCP2: SessionCreated received ", bytes_transferred); uint16_t paddingLen = 0; if (m_Establisher->ProcessSessionCreatedMessage (paddingLen)) { if (paddingLen > 0) { - if (paddingLen <= 287 - 64) // session created is 287 bytes max + if (paddingLen <= NTCP2_SESSION_CREATED_MAX_SIZE - 64) // session created is 287 bytes max { boost::asio::async_read (m_Socket, boost::asio::buffer(m_Establisher->m_SessionCreatedBuffer + 64, paddingLen), boost::asio::transfer_all (), std::bind(&NTCP2Session::HandleSessionCreatedPaddingReceived, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } else { - LogPrint (eLogWarning, "NTCP2: SessionCreated padding length ", (int)paddingLen, " is too long"); + LogPrint (eLogWarning, "NTCP2: SessionCreated padding length ", (int)paddingLen, " is too long"); Terminate (); } } @@ -554,7 +575,11 @@ namespace transport SendSessionConfirmed (); } else + { + if (GetRemoteIdentity ()) + i2p::data::netdb.SetUnreachable (GetRemoteIdentity ()->GetIdentHash (), true); // assume wrong s key Terminate (); + } } } @@ -589,7 +614,7 @@ namespace transport (void) bytes_transferred; if (ecode) { - LogPrint (eLogWarning, "NTCP2: couldn't send SessionConfirmed message: ", ecode.message ()); + LogPrint (eLogWarning, "NTCP2: Couldn't send SessionConfirmed message: ", ecode.message ()); Terminate (); } else @@ -616,7 +641,7 @@ namespace transport (void) bytes_transferred; if (ecode) { - LogPrint (eLogWarning, "NTCP2: couldn't send SessionCreated message: ", ecode.message ()); + LogPrint (eLogWarning, "NTCP2: Couldn't send SessionCreated message: ", ecode.message ()); Terminate (); } else @@ -637,6 +662,7 @@ namespace transport } else { + m_HandshakeInterval = i2p::util::GetMillisecondsSinceEpoch () - m_HandshakeInterval; LogPrint (eLogDebug, "NTCP2: SessionConfirmed received"); // part 1 uint8_t nonce[12]; @@ -659,7 +685,7 @@ namespace transport // process RI if (buf[0] != eNTCP2BlkRouterInfo) { - LogPrint (eLogWarning, "NTCP2: unexpected block ", (int)buf[0], " in SessionConfirmed"); + LogPrint (eLogWarning, "NTCP2: Unexpected block ", (int)buf[0], " in SessionConfirmed"); Terminate (); return; } @@ -674,27 +700,40 @@ namespace transport i2p::data::RouterInfo ri (buf.data () + 4, size - 1); // 1 byte block type + 2 bytes size + 1 byte flag if (ri.IsUnreachable ()) { - LogPrint (eLogError, "NTCP2: Signature verification failed in SessionConfirmed"); + LogPrint (eLogError, "NTCP2: RouterInfo verification failed in SessionConfirmed from ", GetRemoteEndpoint ()); SendTerminationAndTerminate (eNTCP2RouterInfoSignatureVerificationFail); return; } - if (i2p::util::GetMillisecondsSinceEpoch () > ri.GetTimestamp () + i2p::data::NETDB_MIN_EXPIRATION_TIMEOUT*1000LL) // 90 minutes + LogPrint(eLogDebug, "NTCP2: SessionConfirmed from ", GetRemoteEndpoint (), + " (", i2p::data::GetIdentHashAbbreviation (ri.GetIdentHash ()), ")"); + auto ts = i2p::util::GetMillisecondsSinceEpoch (); + if (ts > ri.GetTimestamp () + i2p::data::NETDB_MIN_EXPIRATION_TIMEOUT*1000LL) // 90 minutes { - LogPrint (eLogError, "NTCP2: RouterInfo is too old in SessionConfirmed"); + LogPrint (eLogError, "NTCP2: RouterInfo is too old in SessionConfirmed for ", (ts - ri.GetTimestamp ())/1000LL, " seconds"); SendTerminationAndTerminate (eNTCP2Message3Error); return; } - auto addr = ri.GetNTCP2Address (false); // any NTCP2 address - if (!addr) + if (ts + i2p::data::NETDB_EXPIRATION_TIMEOUT_THRESHOLD*1000LL < ri.GetTimestamp ()) // 2 minutes { - LogPrint (eLogError, "NTCP2: No NTCP2 address found in SessionConfirmed"); + LogPrint (eLogError, "NTCP2: RouterInfo is from future for ", (ri.GetTimestamp () - ts)/1000LL, " seconds"); + SendTerminationAndTerminate (eNTCP2Message3Error); + return; + } + auto addr = m_RemoteEndpoint.address ().is_v4 () ? ri.GetNTCP2V4Address () : + (i2p::util::net::IsYggdrasilAddress (m_RemoteEndpoint.address ()) ? ri.GetYggdrasilAddress () : ri.GetNTCP2V6Address ()); + if (!addr || memcmp (m_Establisher->m_RemoteStaticKey, addr->s, 32)) + { + LogPrint (eLogError, "NTCP2: Wrong static key in SessionConfirmed"); Terminate (); return; } - if (memcmp (addr->ntcp2->staticKey, m_Establisher->m_RemoteStaticKey, 32)) + if (addr->IsPublishedNTCP2 () && m_RemoteEndpoint.address () != addr->host && + (!m_RemoteEndpoint.address ().is_v6 () || (i2p::util::net::IsYggdrasilAddress (m_RemoteEndpoint.address ()) ? + memcmp (m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data () + 1, addr->host.to_v6 ().to_bytes ().data () + 1, 7) : // from the same yggdrasil subnet + memcmp (m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data (), addr->host.to_v6 ().to_bytes ().data (), 8)))) // temporary address { - LogPrint (eLogError, "NTCP2: Static key mismatch in SessionConfirmed"); - SendTerminationAndTerminate (eNTCP2IncorrectSParameter); + LogPrint (eLogError, "NTCP2: Host mismatch between published address ", addr->host, " and actual endpoint ", m_RemoteEndpoint.address ()); + Terminate (); return; } i2p::data::netdb.PostI2NPMsg (CreateI2NPMessage (eI2NPDummyMsg, buf.data () + 3, size)); // TODO: should insert ri and not parse it twice @@ -722,17 +761,19 @@ namespace transport void NTCP2Session::SetSipKeys (const uint8_t * sendSipKey, const uint8_t * receiveSipKey) { #if OPENSSL_SIPHASH - m_SendSipKey = EVP_PKEY_new_raw_private_key (EVP_PKEY_SIPHASH, nullptr, sendSipKey, 16); + EVP_PKEY * sipKey = EVP_PKEY_new_raw_private_key (EVP_PKEY_SIPHASH, nullptr, sendSipKey, 16); m_SendMDCtx = EVP_MD_CTX_create (); EVP_PKEY_CTX *ctx = nullptr; - EVP_DigestSignInit (m_SendMDCtx, &ctx, nullptr, nullptr, m_SendSipKey); + EVP_DigestSignInit (m_SendMDCtx, &ctx, nullptr, nullptr, sipKey); EVP_PKEY_CTX_ctrl (ctx, -1, EVP_PKEY_OP_SIGNCTX, EVP_PKEY_CTRL_SET_DIGEST_SIZE, 8, nullptr); + EVP_PKEY_free (sipKey); - m_ReceiveSipKey = EVP_PKEY_new_raw_private_key (EVP_PKEY_SIPHASH, nullptr, receiveSipKey, 16); + sipKey = EVP_PKEY_new_raw_private_key (EVP_PKEY_SIPHASH, nullptr, receiveSipKey, 16); m_ReceiveMDCtx = EVP_MD_CTX_create (); ctx = nullptr; - EVP_DigestSignInit (m_ReceiveMDCtx, &ctx, NULL, NULL, m_ReceiveSipKey); + EVP_DigestSignInit (m_ReceiveMDCtx, &ctx, NULL, NULL, sipKey); EVP_PKEY_CTX_ctrl (ctx, -1, EVP_PKEY_OP_SIGNCTX, EVP_PKEY_CTRL_SET_DIGEST_SIZE, 8, nullptr); + EVP_PKEY_free (sipKey); #else m_SendSipKey = sendSipKey; m_ReceiveSipKey = receiveSipKey; @@ -747,16 +788,21 @@ namespace transport void NTCP2Session::ServerLogin () { + SetTerminationTimeout (NTCP2_ESTABLISH_TIMEOUT); + m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch (); m_Establisher->CreateEphemeralKey (); - m_Establisher->m_SessionRequestBuffer = new uint8_t[287]; // 287 bytes max for now boost::asio::async_read (m_Socket, boost::asio::buffer(m_Establisher->m_SessionRequestBuffer, 64), boost::asio::transfer_all (), std::bind(&NTCP2Session::HandleSessionRequestReceived, shared_from_this (), - std::placeholders::_1, std::placeholders::_2)); + std::placeholders::_1, std::placeholders::_2)); } void NTCP2Session::ReceiveLength () { if (IsTerminated ()) return; +#ifdef __linux__ + const int one = 1; + setsockopt(m_Socket.native_handle(), IPPROTO_TCP, TCP_QUICKACK, &one, sizeof(one)); +#endif boost::asio::async_read (m_Socket, boost::asio::buffer(&m_NextReceivedLen, 2), boost::asio::transfer_all (), std::bind(&NTCP2Session::HandleReceivedLength, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } @@ -766,7 +812,7 @@ namespace transport if (ecode) { if (ecode != boost::asio::error::operation_aborted) - LogPrint (eLogWarning, "NTCP2: receive length read error: ", ecode.message ()); + LogPrint (eLogWarning, "NTCP2: Receive length read error: ", ecode.message ()); Terminate (); } else @@ -781,11 +827,10 @@ namespace transport #endif // m_NextReceivedLen comes from the network in BigEndian m_NextReceivedLen = be16toh (m_NextReceivedLen) ^ le16toh (m_ReceiveIV.key); - LogPrint (eLogDebug, "NTCP2: received length ", m_NextReceivedLen); + LogPrint (eLogDebug, "NTCP2: Received length ", m_NextReceivedLen); if (m_NextReceivedLen >= 16) { - if (m_NextReceivedBuffer) delete[] m_NextReceivedBuffer; - m_NextReceivedBuffer = new uint8_t[m_NextReceivedLen]; + CreateNextReceivedBuffer (m_NextReceivedLen); boost::system::error_code ec; size_t moreBytes = m_Socket.available(ec); if (!ec && moreBytes >= m_NextReceivedLen) @@ -799,7 +844,7 @@ namespace transport } else { - LogPrint (eLogError, "NTCP2: received length ", m_NextReceivedLen, " is too short"); + LogPrint (eLogError, "NTCP2: Received length ", m_NextReceivedLen, " is too short"); Terminate (); } } @@ -808,6 +853,11 @@ namespace transport void NTCP2Session::Receive () { if (IsTerminated ()) return; +#ifdef __linux__ + const int one = 1; + setsockopt(m_Socket.native_handle(), IPPROTO_TCP, TCP_QUICKACK, &one, sizeof(one)); +#endif + m_IsReceiving = true; boost::asio::async_read (m_Socket, boost::asio::buffer(m_NextReceivedBuffer, m_NextReceivedLen), boost::asio::transfer_all (), std::bind(&NTCP2Session::HandleReceived, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } @@ -817,7 +867,7 @@ namespace transport if (ecode) { if (ecode != boost::asio::error::operation_aborted) - LogPrint (eLogWarning, "NTCP2: receive read error: ", ecode.message ()); + LogPrint (eLogWarning, "NTCP2: Receive read error: ", ecode.message ()); Terminate (); } else @@ -829,9 +879,9 @@ namespace transport CreateNonce (m_ReceiveSequenceNumber, nonce); m_ReceiveSequenceNumber++; if (i2p::crypto::AEADChaCha20Poly1305 (m_NextReceivedBuffer, m_NextReceivedLen-16, nullptr, 0, m_ReceiveKey, nonce, m_NextReceivedBuffer, m_NextReceivedLen, false)) { - LogPrint (eLogDebug, "NTCP2: received message decrypted"); + LogPrint (eLogDebug, "NTCP2: Received message decrypted"); ProcessNextFrame (m_NextReceivedBuffer, m_NextReceivedLen-16); - delete[] m_NextReceivedBuffer; m_NextReceivedBuffer = nullptr; // we don't need received buffer anymore + m_IsReceiving = false; ReceiveLength (); } else @@ -852,7 +902,7 @@ namespace transport auto size = bufbe16toh (frame + offset); offset += 2; LogPrint (eLogDebug, "NTCP2: Block type ", (int)blk, " of size ", size); - if (size > len) + if (offset + size > len) { LogPrint (eLogError, "NTCP2: Unexpected block length ", size); break; @@ -860,10 +910,22 @@ namespace transport switch (blk) { case eNTCP2BlkDateTime: - LogPrint (eLogDebug, "NTCP2: datetime"); - break; + { + LogPrint (eLogDebug, "NTCP2: Datetime"); + if (m_IsEstablished) + { + uint64_t ts = i2p::util::GetSecondsSinceEpoch (); + uint64_t tsA = bufbe32toh (frame + offset); + if (tsA < ts - NTCP2_CLOCK_SKEW || tsA > ts + NTCP2_CLOCK_SKEW) + { + LogPrint (eLogWarning, "NTCP2: Established session time difference ", (int)(ts - tsA), " exceeds clock skew"); + SendTerminationAndTerminate (eNTCP2ClockSkew); + } + } + break; + } case eNTCP2BlkOptions: - LogPrint (eLogDebug, "NTCP2: options"); + LogPrint (eLogDebug, "NTCP2: Options"); break; case eNTCP2BlkRouterInfo: { @@ -879,25 +941,29 @@ namespace transport LogPrint (eLogError, "NTCP2: I2NP block is too long ", size); break; } - auto nextMsg = NewI2NPMessage (size); - nextMsg->Align (12); // for possible tunnel msg + auto nextMsg = (frame[offset] == eI2NPTunnelData) ? NewI2NPTunnelMessage (true) : NewI2NPMessage (size); nextMsg->len = nextMsg->offset + size + 7; // 7 more bytes for full I2NP header - memcpy (nextMsg->GetNTCP2Header (), frame + offset, size); - nextMsg->FromNTCP2 (); - m_Handler.PutNextMessage (nextMsg); + if (nextMsg->len <= nextMsg->maxLen) + { + memcpy (nextMsg->GetNTCP2Header (), frame + offset, size); + nextMsg->FromNTCP2 (); + m_Handler.PutNextMessage (std::move (nextMsg)); + } + else + LogPrint (eLogError, "NTCP2: I2NP block is too long for I2NP message"); break; } case eNTCP2BlkTermination: if (size >= 9) { - LogPrint (eLogDebug, "NTCP2: termination. reason=", (int)(frame[offset + 8])); + LogPrint (eLogDebug, "NTCP2: Termination. reason=", (int)(frame[offset + 8])); Terminate (); } else LogPrint (eLogWarning, "NTCP2: Unexpected termination block size ", size); break; case eNTCP2BlkPadding: - LogPrint (eLogDebug, "NTCP2: padding"); + LogPrint (eLogDebug, "NTCP2: Padding"); break; default: LogPrint (eLogWarning, "NTCP2: Unknown block type ", (int)blk); @@ -909,7 +975,7 @@ namespace transport void NTCP2Session::SetNextSentFrameLength (size_t frameLen, uint8_t * lengthBuf) { - #if OPENSSL_SIPHASH +#if OPENSSL_SIPHASH EVP_DigestSignInit (m_SendMDCtx, nullptr, nullptr, nullptr, nullptr); EVP_DigestSignUpdate (m_SendMDCtx, m_SendIV.buf, 8); size_t l = 8; @@ -919,7 +985,7 @@ namespace transport #endif // length must be in BigEndian htobe16buf (lengthBuf, frameLen ^ le16toh (m_SendIV.key)); - LogPrint (eLogDebug, "NTCP2: sent length ", frameLen); + LogPrint (eLogDebug, "NTCP2: Sent length ", frameLen); } void NTCP2Session::SendI2NPMsgs (std::vector >& msgs) @@ -972,7 +1038,7 @@ namespace transport { // allocate send buffer m_NextSendBuffer = new uint8_t[287]; // can be any size > 16, we just allocate 287 frequently - // crate padding block + // create padding block auto paddingLen = CreatePaddingBlock (totalLen, m_NextSendBuffer, 287 - 16); // and padding block to encrypt and send if (paddingLen) @@ -1033,7 +1099,17 @@ namespace transport m_NumSentBytes += bytes_transferred; i2p::transport::transports.UpdateSentBytes (bytes_transferred); LogPrint (eLogDebug, "NTCP2: Next frame sent ", bytes_transferred); - SendQueue (); + if (m_LastActivityTimestamp > m_NextRouterInfoResendTime) + { + m_NextRouterInfoResendTime += NTCP2_ROUTERINFO_RESEND_INTERVAL + + rand ()%NTCP2_ROUTERINFO_RESEND_INTERVAL_THRESHOLD; + SendRouterInfo (); + } + else + { + SendQueue (); + m_SendQueueSize = m_SendQueue.size (); + } } } @@ -1073,7 +1149,15 @@ namespace transport size_t paddingSize = (msgLen*NTCP2_MAX_PADDING_RATIO)/100; if (msgLen + paddingSize + 3 > NTCP2_UNENCRYPTED_FRAME_MAX_SIZE) paddingSize = NTCP2_UNENCRYPTED_FRAME_MAX_SIZE - msgLen -3; if (paddingSize > len) paddingSize = len; - if (paddingSize) paddingSize = rand () % paddingSize; + if (paddingSize) + { + if (m_NextPaddingSize >= 16) + { + RAND_bytes ((uint8_t *)m_PaddingSizes, sizeof (m_PaddingSizes)); + m_NextPaddingSize = 0; + } + paddingSize = m_PaddingSizes[m_NextPaddingSize++] % paddingSize; + } buf[0] = eNTCP2BlkPadding; // blk htobe16buf (buf + 1, paddingSize); // size memset (buf + 3, 0, paddingSize); @@ -1084,12 +1168,17 @@ namespace transport { if (!IsEstablished ()) return; auto riLen = i2p::context.GetRouterInfo ().GetBufferLen (); - size_t payloadLen = riLen + 4; // 3 bytes block header + 1 byte RI flag + size_t payloadLen = riLen + 3 + 1 + 7; // 3 bytes block header + 1 byte RI flag + 7 bytes DateTime m_NextSendBuffer = new uint8_t[payloadLen + 16 + 2 + 64]; // up to 64 bytes padding - m_NextSendBuffer[2] = eNTCP2BlkRouterInfo; - htobe16buf (m_NextSendBuffer + 3, riLen + 1); // size - m_NextSendBuffer[5] = 0; // flag - memcpy (m_NextSendBuffer + 6, i2p::context.GetRouterInfo ().GetBuffer (), riLen); + // DateTime block + m_NextSendBuffer[2] = eNTCP2BlkDateTime; + htobe16buf (m_NextSendBuffer + 3, 4); + htobe32buf (m_NextSendBuffer + 5, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000); + // RouterInfo block + m_NextSendBuffer[9] = eNTCP2BlkRouterInfo; + htobe16buf (m_NextSendBuffer + 10, riLen + 1); // size + m_NextSendBuffer[12] = 0; // flag + memcpy (m_NextSendBuffer + 13, i2p::context.GetRouterInfo ().GetBuffer (), riLen); // padding block auto paddingSize = CreatePaddingBlock (payloadLen, m_NextSendBuffer + 2 + payloadLen, 64); payloadLen += paddingSize; @@ -1099,7 +1188,13 @@ namespace transport void NTCP2Session::SendTermination (NTCP2TerminationReason reason) { - if (!m_SendKey || !m_SendSipKey) return; + if (!m_SendKey || +#if OPENSSL_SIPHASH + !m_SendMDCtx +#else + !m_SendSipKey +#endif + ) return; m_NextSendBuffer = new uint8_t[49]; // 49 = 12 bytes message + 16 bytes MAC + 2 bytes size + up to 19 padding block // termination block m_NextSendBuffer[2] = eNTCP2BlkTermination; @@ -1127,26 +1222,27 @@ namespace transport { if (m_IsTerminated) return; for (auto it: msgs) - m_SendQueue.push_back (it); + m_SendQueue.push_back (std::move (it)); if (!m_IsSending) SendQueue (); else if (m_SendQueue.size () > NTCP2_MAX_OUTGOING_QUEUE_SIZE) { - LogPrint (eLogWarning, "NTCP2: outgoing messages queue size to ", - GetIdentHashBase64(), " exceeds ", NTCP2_MAX_OUTGOING_QUEUE_SIZE); + LogPrint (eLogWarning, "NTCP2: Outgoing messages queue size to ", + GetIdentHashBase64(), " exceeds ", NTCP2_MAX_OUTGOING_QUEUE_SIZE); Terminate (); } + m_SendQueueSize = m_SendQueue.size (); } - void NTCP2Session::SendLocalRouterInfo () + void NTCP2Session::SendLocalRouterInfo (bool update) { - if (!IsOutgoing ()) // we send it in SessionConfirmed + if (update || !IsOutgoing ()) // we send it in SessionConfirmed for ougoing session m_Server.GetService ().post (std::bind (&NTCP2Session::SendRouterInfo, shared_from_this ())); } NTCP2Server::NTCP2Server (): RunnableServiceWithWork ("NTCP2"), m_TerminationTimer (GetService ()), - m_ProxyType(eNoProxy), m_Resolver(GetService ()) + m_ProxyType(eNoProxy), m_Resolver(GetService ()) { } @@ -1168,7 +1264,7 @@ namespace transport boost::system::error_code e; auto itr = m_Resolver.resolve(q, e); if(e) - LogPrint(eLogError, "NTCP2: Failed to resolve proxy ", e.message()); + LogPrint(eLogCritical, "NTCP2: Failed to resolve proxy ", e.message()); else { m_ProxyEndpoint.reset (new boost::asio::ip::tcp::endpoint(*itr)); @@ -1177,52 +1273,71 @@ namespace transport } } else - { LogPrint(eLogInfo, "NTCP2: Proxy is not used"); - auto& addresses = context.GetRouterInfo ().GetAddresses (); - for (const auto& address: addresses) + // start acceptors + auto addresses = context.GetRouterInfo ().GetAddresses (); + if (!addresses) return; + for (const auto& address: *addresses) + { + if (!address) continue; + if (address->IsPublishedNTCP2 () && address->port) { - if (!address) continue; - if (address->IsPublishedNTCP2 ()) + if (address->IsV4()) { - if (address->host.is_v4()) + try { - try - { - m_NTCP2Acceptor.reset (new boost::asio::ip::tcp::acceptor (GetService (), boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), address->port))); - } - catch ( std::exception & ex ) - { - LogPrint(eLogError, "NTCP2: Failed to bind to v4 port ", address->port, ex.what()); - ThrowFatal ("Unable to start IPv4 NTCP2 transport at port ", address->port, ": ", ex.what ()); - continue; - } - - LogPrint (eLogInfo, "NTCP2: Start listening v4 TCP port ", address->port); - auto conn = std::make_shared(*this); - m_NTCP2Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCP2Server::HandleAccept, this, conn, std::placeholders::_1)); + auto ep = m_Address4 ? boost::asio::ip::tcp::endpoint (m_Address4->address(), address->port): + boost::asio::ip::tcp::endpoint (boost::asio::ip::tcp::v4(), address->port); + m_NTCP2Acceptor.reset (new boost::asio::ip::tcp::acceptor (GetService (), ep)); } - else if (address->host.is_v6() && context.SupportsV6 ()) + catch ( std::exception & ex ) { - m_NTCP2V6Acceptor.reset (new boost::asio::ip::tcp::acceptor (GetService ())); - try - { - m_NTCP2V6Acceptor->open (boost::asio::ip::tcp::v6()); - m_NTCP2V6Acceptor->set_option (boost::asio::ip::v6_only (true)); - m_NTCP2V6Acceptor->set_option (boost::asio::socket_base::reuse_address (true)); - m_NTCP2V6Acceptor->bind (boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v6(), address->port)); - m_NTCP2V6Acceptor->listen (); + LogPrint(eLogCritical, "NTCP2: Failed to bind to v4 port ", address->port, ex.what()); + ThrowFatal ("Unable to start IPv4 NTCP2 transport at port ", address->port, ": ", ex.what ()); + continue; + } - LogPrint (eLogInfo, "NTCP2: Start listening v6 TCP port ", address->port); - auto conn = std::make_shared (*this); - m_NTCP2V6Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCP2Server::HandleAcceptV6, this, conn, std::placeholders::_1)); - } - catch ( std::exception & ex ) + LogPrint (eLogInfo, "NTCP2: Start listening v4 TCP port ", address->port); + auto conn = std::make_shared(*this); + m_NTCP2Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCP2Server::HandleAccept, this, conn, std::placeholders::_1)); + } + else if (address->IsV6() && (context.SupportsV6 () || context.SupportsMesh ())) + { + m_NTCP2V6Acceptor.reset (new boost::asio::ip::tcp::acceptor (GetService ())); + try + { + m_NTCP2V6Acceptor->open (boost::asio::ip::tcp::v6()); + m_NTCP2V6Acceptor->set_option (boost::asio::ip::v6_only (true)); + m_NTCP2V6Acceptor->set_option (boost::asio::socket_base::reuse_address (true)); +#if defined(__linux__) && !defined(_NETINET_IN_H) + if (!m_Address6 && !m_YggdrasilAddress) // only if not binded to address { - LogPrint(eLogError, "NTCP2: failed to bind to v6 port ", address->port, ": ", ex.what()); - ThrowFatal ("Unable to start IPv6 NTCP2 transport at port ", address->port, ": ", ex.what ()); - continue; + // Set preference to use public IPv6 address -- tested on linux, not works on windows, and not tested on others +#if (BOOST_VERSION >= 105500) + typedef boost::asio::detail::socket_option::integer ipv6PreferAddr; +#else + typedef boost::asio::detail::socket_option::integer ipv6PreferAddr; +#endif + m_NTCP2V6Acceptor->set_option (ipv6PreferAddr(IPV6_PREFER_SRC_PUBLIC | IPV6_PREFER_SRC_HOME | IPV6_PREFER_SRC_NONCGA)); } +#endif + auto ep = boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v6(), address->port); + if (m_Address6 && !context.SupportsMesh ()) + ep = boost::asio::ip::tcp::endpoint (m_Address6->address(), address->port); + else if (m_YggdrasilAddress && !context.SupportsV6 ()) + ep = boost::asio::ip::tcp::endpoint (m_YggdrasilAddress->address(), address->port); + m_NTCP2V6Acceptor->bind (ep); + m_NTCP2V6Acceptor->listen (); + + LogPrint (eLogInfo, "NTCP2: Start listening v6 TCP port ", address->port); + auto conn = std::make_shared (*this); + m_NTCP2V6Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCP2Server::HandleAcceptV6, this, conn, std::placeholders::_1)); + } + catch ( std::exception & ex ) + { + LogPrint(eLogCritical, "NTCP2: Failed to bind to v6 port ", address->port, ": ", ex.what()); + ThrowFatal ("Unable to start IPv6 NTCP2 transport at port ", address->port, ": ", ex.what ()); + continue; } } } @@ -1239,7 +1354,7 @@ namespace transport for (auto& it: ntcpSessions) it.second->Terminate (); for (auto& it: m_PendingIncomingSessions) - it->Terminate (); + it.second->Terminate (); } m_NTCP2Sessions.clear (); @@ -1255,20 +1370,32 @@ namespace transport { if (!session) return false; if (incoming) - m_PendingIncomingSessions.remove (session); - if (!session->GetRemoteIdentity ()) return false; + m_PendingIncomingSessions.erase (session->GetRemoteEndpoint ().address ()); + if (!session->GetRemoteIdentity ()) + { + LogPrint (eLogWarning, "NTCP2: Unknown identity for ", session->GetRemoteEndpoint ()); + session->Terminate (); + return false; + } auto& ident = session->GetRemoteIdentity ()->GetIdentHash (); auto it = m_NTCP2Sessions.find (ident); if (it != m_NTCP2Sessions.end ()) { - LogPrint (eLogWarning, "NTCP2: session to ", ident.ToBase64 (), " already exists"); + LogPrint (eLogWarning, "NTCP2: Session with ", ident.ToBase64 (), " already exists. ", incoming ? "Replaced" : "Dropped"); if (incoming) + { // replace by new session - it->second->Terminate (); + auto s = it->second; + m_NTCP2Sessions.erase (it); + s->Terminate (); + } else + { + session->Terminate (); return false; + } } - m_NTCP2Sessions.insert (std::make_pair (ident, session)); + m_NTCP2Sessions.emplace (ident, session); return true; } @@ -1286,10 +1413,16 @@ namespace transport return nullptr; } - void NTCP2Server::Connect(const boost::asio::ip::address & address, uint16_t port, std::shared_ptr conn) + void NTCP2Server::Connect(std::shared_ptr conn) { - LogPrint (eLogDebug, "NTCP2: Connecting to ", address ,":", port); - GetService ().post([this, address, port, conn]() + if (!conn || conn->GetRemoteEndpoint ().address ().is_unspecified ()) + { + LogPrint (eLogError, "NTCP2: Can't connect to unspecified address"); + return; + } + LogPrint (eLogDebug, "NTCP2: Connecting to ", conn->GetRemoteEndpoint (), + " (", i2p::data::GetIdentHashAbbreviation (conn->GetRemoteIdentity ()->GetIdentHash ()), ")"); + GetService ().post([this, conn]() { if (this->AddNTCP2Session (conn)) { @@ -1302,12 +1435,32 @@ namespace transport if (ecode != boost::asio::error::operation_aborted) { LogPrint (eLogInfo, "NTCP2: Not connected in ", timeout, " seconds"); - if (conn->GetRemoteIdentity ()) - i2p::data::netdb.SetUnreachable (conn->GetRemoteIdentity ()->GetIdentHash (), true); conn->Terminate (); } }); - conn->GetSocket ().async_connect (boost::asio::ip::tcp::endpoint (address, port), std::bind (&NTCP2Server::HandleConnect, this, std::placeholders::_1, conn, timer)); + // bind to local address + std::shared_ptr localAddress; + if (conn->GetRemoteEndpoint ().address ().is_v6 ()) + { + if (i2p::util::net::IsYggdrasilAddress (conn->GetRemoteEndpoint ().address ())) + localAddress = m_YggdrasilAddress; + else + localAddress = m_Address6; + conn->GetSocket ().open (boost::asio::ip::tcp::v6 ()); + } + else + { + localAddress = m_Address4; + conn->GetSocket ().open (boost::asio::ip::tcp::v4 ()); + } + if (localAddress) + { + boost::system::error_code ec; + conn->GetSocket ().bind (*localAddress, ec); + if (ec) + LogPrint (eLogError, "NTCP2: Can't bind to ", localAddress->address ().to_string (), ": ", ec.message ()); + } + conn->GetSocket ().async_connect (conn->GetRemoteEndpoint (), std::bind (&NTCP2Server::HandleConnect, this, std::placeholders::_1, conn, timer)); } else conn->Terminate (); @@ -1324,32 +1477,47 @@ namespace transport } else { - LogPrint (eLogDebug, "NTCP2: Connected to ", conn->GetSocket ().remote_endpoint ()); + LogPrint (eLogDebug, "NTCP2: Connected to ", conn->GetRemoteEndpoint (), + " (", i2p::data::GetIdentHashAbbreviation (conn->GetRemoteIdentity ()->GetIdentHash ()), ")"); conn->ClientLogin (); } } void NTCP2Server::HandleAccept (std::shared_ptr conn, const boost::system::error_code& error) { - if (!error) + if (!error && conn) { boost::system::error_code ec; auto ep = conn->GetSocket ().remote_endpoint(ec); if (!ec) { LogPrint (eLogDebug, "NTCP2: Connected from ", ep); - if (conn) + if (!i2p::util::net::IsInReservedRange(ep.address ())) { - conn->ServerLogin (); - m_PendingIncomingSessions.push_back (conn); - conn = nullptr; + if (m_PendingIncomingSessions.emplace (ep.address (), conn).second) + { + conn->SetRemoteEndpoint (ep); + conn->ServerLogin (); + conn = nullptr; + } + else + LogPrint (eLogInfo, "NTCP2: Incoming session from ", ep.address (), " is already pending"); } + else + LogPrint (eLogError, "NTCP2: Incoming connection from invalid IP ", ep.address ()); } else LogPrint (eLogError, "NTCP2: Connected from error ", ec.message ()); } else + { LogPrint (eLogError, "NTCP2: Accept error ", error.message ()); + if (error == boost::asio::error::no_descriptors) + { + i2p::context.SetError (eRouterErrorNoDescriptors); + return; + } + } if (error != boost::asio::error::operation_aborted) { @@ -1364,26 +1532,47 @@ namespace transport void NTCP2Server::HandleAcceptV6 (std::shared_ptr conn, const boost::system::error_code& error) { - if (!error) + if (!error && conn) { boost::system::error_code ec; auto ep = conn->GetSocket ().remote_endpoint(ec); if (!ec) { LogPrint (eLogDebug, "NTCP2: Connected from ", ep); - if (conn) + if (!i2p::util::net::IsInReservedRange(ep.address ()) || + i2p::util::net::IsYggdrasilAddress (ep.address ())) { - conn->ServerLogin (); - m_PendingIncomingSessions.push_back (conn); + if (m_PendingIncomingSessions.emplace (ep.address (), conn).second) + { + conn->SetRemoteEndpoint (ep); + conn->ServerLogin (); + conn = nullptr; + } + else + LogPrint (eLogInfo, "NTCP2: Incoming session from ", ep.address (), " is already pending"); } + else + LogPrint (eLogError, "NTCP2: Incoming connection from invalid IP ", ep.address ()); } else LogPrint (eLogError, "NTCP2: Connected from error ", ec.message ()); } + else + { + LogPrint (eLogError, "NTCP2: Accept ipv6 error ", error.message ()); + if (error == boost::asio::error::no_descriptors) + { + i2p::context.SetErrorV6 (eRouterErrorNoDescriptors); + return; + } + } if (error != boost::asio::error::operation_aborted) { - conn = std::make_shared (*this); + if (!conn) // connection is used, create new one + conn = std::make_shared (*this); + else // reuse failed + conn->Close (); m_NTCP2V6Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCP2Server::HandleAcceptV6, this, conn, std::placeholders::_1)); } @@ -1409,31 +1598,54 @@ namespace transport LogPrint (eLogDebug, "NTCP2: No activity for ", session->GetTerminationTimeout (), " seconds"); session->TerminateByTimeout (); // it doesn't change m_NTCP2Session right a way } + else + it.second->DeleteNextReceiveBuffer (ts); // pending for (auto it = m_PendingIncomingSessions.begin (); it != m_PendingIncomingSessions.end ();) { - if ((*it)->IsEstablished () || (*it)->IsTerminationTimeoutExpired (ts)) + if (it->second->IsEstablished () || it->second->IsTerminationTimeoutExpired (ts)) { - (*it)->Terminate (); + it->second->Terminate (); it = m_PendingIncomingSessions.erase (it); // established of expired } - else if ((*it)->IsTerminated ()) + else if (it->second->IsTerminated ()) it = m_PendingIncomingSessions.erase (it); // already terminated else it++; } - ScheduleTermination (); + + // try to restart acceptors if no description + // we do it after timer to let timer take descriptor first + if (i2p::context.GetError () == eRouterErrorNoDescriptors) + { + i2p::context.SetError (eRouterErrorNone); + auto conn = std::make_shared (*this); + m_NTCP2Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCP2Server::HandleAccept, this, + conn, std::placeholders::_1)); + } + if (i2p::context.GetErrorV6 () == eRouterErrorNoDescriptors) + { + i2p::context.SetErrorV6 (eRouterErrorNone); + auto conn = std::make_shared (*this); + m_NTCP2V6Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCP2Server::HandleAcceptV6, this, + conn, std::placeholders::_1)); + } } } - void NTCP2Server::ConnectWithProxy (const std::string& host, uint16_t port, RemoteAddressType addrtype, std::shared_ptr conn) + void NTCP2Server::ConnectWithProxy (std::shared_ptr conn) { if(!m_ProxyEndpoint) return; - GetService().post([this, host, port, addrtype, conn]() { + if (!conn || conn->GetRemoteEndpoint ().address ().is_unspecified ()) + { + LogPrint (eLogError, "NTCP2: Can't connect to unspecified address"); + return; + } + GetService().post([this, conn]() + { if (this->AddNTCP2Session (conn)) { - auto timer = std::make_shared(GetService()); auto timeout = NTCP2_CONNECT_TIMEOUT * 5; conn->SetTerminationTimeout(timeout * 2); @@ -1443,27 +1655,29 @@ namespace transport if (ecode != boost::asio::error::operation_aborted) { LogPrint (eLogInfo, "NTCP2: Not connected in ", timeout, " seconds"); - i2p::data::netdb.SetUnreachable (conn->GetRemoteIdentity ()->GetIdentHash (), true); conn->Terminate (); } }); - conn->GetSocket ().async_connect (*m_ProxyEndpoint, std::bind (&NTCP2Server::HandleProxyConnect, this, std::placeholders::_1, conn, timer, host, port, addrtype)); + conn->GetSocket ().async_connect (*m_ProxyEndpoint, std::bind (&NTCP2Server::HandleProxyConnect, this, std::placeholders::_1, conn, timer)); } }); } - void NTCP2Server::UseProxy(ProxyType proxytype, const std::string & addr, uint16_t port) + void NTCP2Server::UseProxy(ProxyType proxytype, const std::string& addr, uint16_t port, + const std::string& user, const std::string& pass) { m_ProxyType = proxytype; m_ProxyAddress = addr; m_ProxyPort = port; + if (m_ProxyType == eHTTPProxy ) + m_ProxyAuthorization = i2p::http::CreateBasicAuthorizationString (user, pass); } - void NTCP2Server::HandleProxyConnect(const boost::system::error_code& ecode, std::shared_ptr conn, std::shared_ptr timer, const std::string & host, uint16_t port, RemoteAddressType addrtype) + void NTCP2Server::HandleProxyConnect(const boost::system::error_code& ecode, std::shared_ptr conn, std::shared_ptr timer) { if (ecode) { - LogPrint(eLogWarning, "NTCP2: failed to connect to proxy ", ecode.message()); + LogPrint(eLogWarning, "NTCP2: Failed to connect to proxy ", ecode.message()); timer->cancel(); conn->Terminate(); return; @@ -1473,23 +1687,23 @@ namespace transport case eSocksProxy: { // TODO: support username/password auth etc - static const uint8_t buff[3] = {0x05, 0x01, 0x00}; + static const uint8_t buff[3] = {SOCKS5_VER, 0x01, 0x00}; boost::asio::async_write(conn->GetSocket(), boost::asio::buffer(buff, 3), boost::asio::transfer_all(), [] (const boost::system::error_code & ec, std::size_t transferred) { (void) transferred; if(ec) { - LogPrint(eLogWarning, "NTCP2: socks5 write error ", ec.message()); + LogPrint(eLogWarning, "NTCP2: SOCKS5 write error ", ec.message()); } }); auto readbuff = std::make_shared >(2); boost::asio::async_read(conn->GetSocket(), boost::asio::buffer(readbuff->data (), 2), - [this, readbuff, timer, conn, host, port, addrtype](const boost::system::error_code & ec, std::size_t transferred) + [this, readbuff, timer, conn](const boost::system::error_code & ec, std::size_t transferred) { if(ec) { - LogPrint(eLogError, "NTCP2: socks5 read error ", ec.message()); + LogPrint(eLogError, "NTCP2: SOCKS5 read error ", ec.message()); timer->cancel(); conn->Terminate(); return; @@ -1498,19 +1712,19 @@ namespace transport { if((*readbuff)[1] == 0x00) { - AfterSocksHandshake(conn, timer, host, port, addrtype); + AfterSocksHandshake(conn, timer); return; } else if ((*readbuff)[1] == 0xff) { - LogPrint(eLogError, "NTCP2: socks5 proxy rejected authentication"); + LogPrint(eLogError, "NTCP2: SOCKS5 proxy rejected authentication"); timer->cancel(); conn->Terminate(); return; } LogPrint(eLogError, "NTCP2:", (int)(*readbuff)[1]); } - LogPrint(eLogError, "NTCP2: socks5 server gave invalid response"); + LogPrint(eLogError, "NTCP2: SOCKS5 server gave invalid response"); timer->cancel(); conn->Terminate(); }); @@ -1518,13 +1732,16 @@ namespace transport } case eHTTPProxy: { + auto& ep = conn->GetRemoteEndpoint (); i2p::http::HTTPReq req; req.method = "CONNECT"; req.version ="HTTP/1.1"; - if(addrtype == eIP6Address) - req.uri = "[" + host + "]:" + std::to_string(port); + if(ep.address ().is_v6 ()) + req.uri = "[" + ep.address ().to_string() + "]:" + std::to_string(ep.port ()); else - req.uri = host + ":" + std::to_string(port); + req.uri = ep.address ().to_string() + ":" + std::to_string(ep.port ()); + if (!m_ProxyAuthorization.empty ()) + req.AddHeader("Proxy-Authorization", m_ProxyAuthorization); boost::asio::streambuf writebuff; std::ostream out(&writebuff); @@ -1535,16 +1752,16 @@ namespace transport { (void) transferred; if(ec) - LogPrint(eLogError, "NTCP2: http proxy write error ", ec.message()); + LogPrint(eLogError, "NTCP2: HTTP proxy write error ", ec.message()); }); boost::asio::streambuf * readbuff = new boost::asio::streambuf; boost::asio::async_read_until(conn->GetSocket(), *readbuff, "\r\n\r\n", - [this, readbuff, timer, conn] (const boost::system::error_code & ec, std::size_t transferred) + [readbuff, timer, conn] (const boost::system::error_code & ec, std::size_t transferred) { if(ec) { - LogPrint(eLogError, "NTCP2: http proxy read error ", ec.message()); + LogPrint(eLogError, "NTCP2: HTTP proxy read error ", ec.message()); timer->cancel(); conn->Terminate(); } @@ -1562,10 +1779,10 @@ namespace transport return; } else - LogPrint(eLogError, "NTCP2: http proxy rejected request ", res.code); + LogPrint(eLogError, "NTCP2: HTTP proxy rejected request ", res.code); } else - LogPrint(eLogError, "NTCP2: http proxy gave malformed response"); + LogPrint(eLogError, "NTCP2: HTTP proxy gave malformed response"); timer->cancel(); conn->Terminate(); delete readbuff; @@ -1574,72 +1791,87 @@ namespace transport break; } default: - LogPrint(eLogError, "NTCP2: unknown proxy type, invalid state"); + LogPrint(eLogError, "NTCP2: Unknown proxy type, invalid state"); } } - void NTCP2Server::AfterSocksHandshake(std::shared_ptr conn, std::shared_ptr timer, const std::string & host, uint16_t port, RemoteAddressType addrtype) + void NTCP2Server::AfterSocksHandshake(std::shared_ptr conn, std::shared_ptr timer) { // build request size_t sz = 6; // header + port auto buff = std::make_shared >(256); auto readbuff = std::make_shared >(256); - (*buff)[0] = 0x05; - (*buff)[1] = 0x01; + (*buff)[0] = SOCKS5_VER; + (*buff)[1] = SOCKS5_CMD_CONNECT; (*buff)[2] = 0x00; - if(addrtype == eIP4Address) + auto& ep = conn->GetRemoteEndpoint (); + if(ep.address ().is_v4 ()) { - (*buff)[3] = 0x01; - auto addrbytes = boost::asio::ip::address::from_string(host).to_v4().to_bytes(); + (*buff)[3] = SOCKS5_ATYP_IPV4; + auto addrbytes = ep.address ().to_v4().to_bytes(); sz += 4; memcpy(buff->data () + 4, addrbytes.data(), 4); } - else if (addrtype == eIP6Address) + else if (ep.address ().is_v6 ()) { - (*buff)[3] = 0x04; - auto addrbytes = boost::asio::ip::address::from_string(host).to_v6().to_bytes(); + (*buff)[3] = SOCKS5_ATYP_IPV6; + auto addrbytes = ep.address ().to_v6().to_bytes(); sz += 16; memcpy(buff->data () + 4, addrbytes.data(), 16); } - else if (addrtype == eHostname) + else { // We mustn't really fall here because all connections are made to IP addresses - LogPrint(eLogError, "NTCP2: Tried to connect to domain name via socks proxy"); + LogPrint(eLogError, "NTCP2: Tried to connect to unexpected address via proxy"); return; } - htobe16buf(buff->data () + sz - 2, port); + htobe16buf(buff->data () + sz - 2, ep.port ()); boost::asio::async_write(conn->GetSocket(), boost::asio::buffer(buff->data (), sz), boost::asio::transfer_all(), [buff](const boost::system::error_code & ec, std::size_t written) { if(ec) { - LogPrint(eLogError, "NTCP2: failed to write handshake to socks proxy ", ec.message()); + LogPrint(eLogError, "NTCP2: Failed to write handshake to socks proxy ", ec.message()); return; } }); - boost::asio::async_read(conn->GetSocket(), boost::asio::buffer(readbuff->data (), 10), - [timer, conn, sz, readbuff](const boost::system::error_code & e, std::size_t transferred) + boost::asio::async_read(conn->GetSocket(), boost::asio::buffer(readbuff->data (), SOCKS5_UDP_IPV4_REQUEST_HEADER_SIZE), // read min reply size + boost::asio::transfer_all(), + [timer, conn, readbuff](const boost::system::error_code & e, std::size_t transferred) { - if(e) + if (e) + LogPrint(eLogError, "NTCP2: SOCKS proxy read error ", e.message()); + else if (!(*readbuff)[1]) // succeeded { - LogPrint(eLogError, "NTCP2: socks proxy read error ", e.message()); + boost::system::error_code ec; + size_t moreBytes = conn->GetSocket ().available(ec); + if (moreBytes) // read remaining portion of reply if ipv6 received + boost::asio::read (conn->GetSocket (), boost::asio::buffer(readbuff->data (), moreBytes), boost::asio::transfer_all (), ec); + timer->cancel(); + conn->ClientLogin(); + return; } - else if(transferred == sz) - { - if((*readbuff)[1] == 0x00) - { - timer->cancel(); - conn->ClientLogin(); - return; - } - } - if(!e) - i2p::data::netdb.SetUnreachable (conn->GetRemoteIdentity ()->GetIdentHash (), true); + else + LogPrint(eLogError, "NTCP2: Proxy reply error ", (int)(*readbuff)[1]); timer->cancel(); conn->Terminate(); }); } + + void NTCP2Server::SetLocalAddress (const boost::asio::ip::address& localAddress) + { + auto addr = std::make_shared(boost::asio::ip::tcp::endpoint(localAddress, 0)); + if (localAddress.is_v6 ()) + { + if (i2p::util::net::IsYggdrasilAddress (localAddress)) + m_YggdrasilAddress = addr; + else + m_Address6 = addr; + } + else + m_Address4 = addr; + } } } diff --git a/libi2pd/NTCP2.h b/libi2pd/NTCP2.h index df72fed0..ba1380c3 100644 --- a/libi2pd/NTCP2.h +++ b/libi2pd/NTCP2.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,7 +11,6 @@ #include #include -#include #include #include #include @@ -29,12 +28,17 @@ namespace transport { const size_t NTCP2_UNENCRYPTED_FRAME_MAX_SIZE = 65519; + const size_t NTCP2_SESSION_REQUEST_MAX_SIZE = 287; + const size_t NTCP2_SESSION_CREATED_MAX_SIZE = 287; const int NTCP2_MAX_PADDING_RATIO = 6; // in % const int NTCP2_CONNECT_TIMEOUT = 5; // 5 seconds const int NTCP2_ESTABLISH_TIMEOUT = 10; // 10 seconds const int NTCP2_TERMINATION_TIMEOUT = 120; // 2 minutes const int NTCP2_TERMINATION_CHECK_TIMEOUT = 30; // 30 seconds + const int NTCP2_RECEIVE_BUFFER_DELETION_TIMEOUT = 3; // 3 seconds + const int NTCP2_ROUTERINFO_RESEND_INTERVAL = 25*60; // 25 minuntes in seconds + const int NTCP2_ROUTERINFO_RESEND_INTERVAL_THRESHOLD = 25*60; // 25 minuntes const int NTCP2_CLOCK_SKEW = 60; // in seconds const int NTCP2_MAX_OUTGOING_QUEUE_SIZE = 500; // how many messages we can queue up @@ -74,7 +78,7 @@ namespace transport // RouterInfo flags const uint8_t NTCP2_ROUTER_INFO_FLAG_REQUEST_FLOOD = 0x01; - struct NTCP2Establisher + struct NTCP2Establisher: private i2p::crypto::NoiseSymmetricState { NTCP2Establisher (); ~NTCP2Establisher (); @@ -94,8 +98,6 @@ namespace transport void KDF3Alice (); // for SessionConfirmed part 2 void KDF3Bob (); - void MixKey (const uint8_t * inputKeyMaterial); - void MixHash (const uint8_t * buf, size_t len); void KeyDerivationFunction1 (const uint8_t * pub, i2p::crypto::X25519Keys& priv, const uint8_t * rs, const uint8_t * epub); // for SessionRequest, (pub, priv) for DH void KeyDerivationFunction2 (const uint8_t * sessionRequest, size_t sessionRequestLen, const uint8_t * epub); // for SessionCreate void CreateEphemeralKey (); @@ -105,18 +107,19 @@ namespace transport void CreateSessionConfirmedMessagePart1 (const uint8_t * nonce); void CreateSessionConfirmedMessagePart2 (const uint8_t * nonce); - bool ProcessSessionRequestMessage (uint16_t& paddingLen); + bool ProcessSessionRequestMessage (uint16_t& paddingLen, bool& clockSkew); bool ProcessSessionCreatedMessage (uint16_t& paddingLen); bool ProcessSessionConfirmedMessagePart1 (const uint8_t * nonce); bool ProcessSessionConfirmedMessagePart2 (const uint8_t * nonce, uint8_t * m3p2Buf); std::shared_ptr m_EphemeralKeys; uint8_t m_RemoteEphemeralPublicKey[32]; // x25519 - uint8_t m_RemoteStaticKey[32], m_IV[16], m_H[32] /*h*/, m_CK[64] /* [ck, k]*/; + uint8_t m_RemoteStaticKey[32], m_IV[16]; i2p::data::IdentHash m_RemoteIdentHash; uint16_t m3p2Len; - uint8_t * m_SessionRequestBuffer, * m_SessionCreatedBuffer, * m_SessionConfirmedBuffer; + uint8_t m_SessionRequestBuffer[NTCP2_SESSION_REQUEST_MAX_SIZE], + m_SessionCreatedBuffer[NTCP2_SESSION_CREATED_MAX_SIZE], * m_SessionConfirmedBuffer; size_t m_SessionRequestBufferLen, m_SessionCreatedBufferLen; }; @@ -126,29 +129,34 @@ namespace transport { public: - NTCP2Session (NTCP2Server& server, std::shared_ptr in_RemoteRouter = nullptr); + NTCP2Session (NTCP2Server& server, std::shared_ptr in_RemoteRouter = nullptr, + std::shared_ptr addr = nullptr); ~NTCP2Session (); void Terminate (); void TerminateByTimeout (); - void Done (); - void Close () { m_Socket.close (); }; // for accept + void Done () override; + void Close (); // for accept + void DeleteNextReceiveBuffer (uint64_t ts); boost::asio::ip::tcp::socket& GetSocket () { return m_Socket; }; + const boost::asio::ip::tcp::endpoint& GetRemoteEndpoint () { return m_RemoteEndpoint; }; + void SetRemoteEndpoint (const boost::asio::ip::tcp::endpoint& ep) { m_RemoteEndpoint = ep; }; - bool IsEstablished () const { return m_IsEstablished; }; + bool IsEstablished () const override { return m_IsEstablished; }; bool IsTerminated () const { return m_IsTerminated; }; void ClientLogin (); // Alice void ServerLogin (); // Bob - void SendLocalRouterInfo (); // after handshake - void SendI2NPMessages (const std::vector >& msgs); + void SendLocalRouterInfo (bool update) override; // after handshake or by update + void SendI2NPMessages (const std::vector >& msgs) override; private: void Established (); void CreateNonce (uint64_t seqn, uint8_t * nonce); + void CreateNextReceivedBuffer (size_t size); void KeyDerivationFunctionDataPhase (); void SetSipKeys (const uint8_t * sendSipKey, const uint8_t * receiveSipKey); @@ -189,6 +197,7 @@ namespace transport NTCP2Server& m_Server; boost::asio::ip::tcp::socket m_Socket; + boost::asio::ip::tcp::endpoint m_RemoteEndpoint; bool m_IsEstablished, m_IsTerminated; std::unique_ptr m_Establisher; @@ -196,13 +205,13 @@ namespace transport uint8_t m_Kab[32], m_Kba[32], m_Sipkeysab[32], m_Sipkeysba[32]; const uint8_t * m_SendKey, * m_ReceiveKey; #if OPENSSL_SIPHASH - EVP_PKEY * m_SendSipKey, * m_ReceiveSipKey; EVP_MD_CTX * m_SendMDCtx, * m_ReceiveMDCtx; #else const uint8_t * m_SendSipKey, * m_ReceiveSipKey; #endif uint16_t m_NextReceivedLen; uint8_t * m_NextReceivedBuffer, * m_NextSendBuffer; + size_t m_NextReceivedBufferSize; union { uint8_t buf[8]; @@ -212,21 +221,18 @@ namespace transport i2p::I2NPMessagesHandler m_Handler; - bool m_IsSending; + bool m_IsSending, m_IsReceiving; std::list > m_SendQueue; + uint64_t m_NextRouterInfoResendTime; // seconds since epoch + + uint16_t m_PaddingSizes[16]; + int m_NextPaddingSize; }; class NTCP2Server: private i2p::util::RunnableServiceWithWork { public: - enum RemoteAddressType - { - eIP4Address, - eIP6Address, - eHostname - }; - enum ProxyType { eNoProxy, @@ -245,14 +251,13 @@ namespace transport void RemoveNTCP2Session (std::shared_ptr session); std::shared_ptr FindNTCP2Session (const i2p::data::IdentHash& ident); - void ConnectWithProxy (const std::string& addr, uint16_t port, RemoteAddressType addrtype, std::shared_ptr conn); - void Connect(const boost::asio::ip::address & address, uint16_t port, std::shared_ptr conn); - - void AfterSocksHandshake(std::shared_ptr conn, std::shared_ptr timer, const std::string & host, uint16_t port, RemoteAddressType addrtype); - + void ConnectWithProxy (std::shared_ptr conn); + void Connect(std::shared_ptr conn); bool UsingProxy() const { return m_ProxyType != eNoProxy; }; - void UseProxy(ProxyType proxy, const std::string & address, uint16_t port); + void UseProxy(ProxyType proxy, const std::string& address, uint16_t port, const std::string& user, const std::string& pass); + + void SetLocalAddress (const boost::asio::ip::address& localAddress); private: @@ -260,7 +265,8 @@ namespace transport void HandleAcceptV6 (std::shared_ptr conn, const boost::system::error_code& error); void HandleConnect (const boost::system::error_code& ecode, std::shared_ptr conn, std::shared_ptr timer); - void HandleProxyConnect(const boost::system::error_code& ecode, std::shared_ptr conn, std::shared_ptr timer, const std::string & host, uint16_t port, RemoteAddressType adddrtype); + void HandleProxyConnect(const boost::system::error_code& ecode, std::shared_ptr conn, std::shared_ptr timer); + void AfterSocksHandshake(std::shared_ptr conn, std::shared_ptr timer); // timer void ScheduleTermination (); @@ -271,13 +277,14 @@ namespace transport boost::asio::deadline_timer m_TerminationTimer; std::unique_ptr m_NTCP2Acceptor, m_NTCP2V6Acceptor; std::map > m_NTCP2Sessions; - std::list > m_PendingIncomingSessions; + std::map > m_PendingIncomingSessions; ProxyType m_ProxyType; - std::string m_ProxyAddress; + std::string m_ProxyAddress, m_ProxyAuthorization; uint16_t m_ProxyPort; boost::asio::ip::tcp::resolver m_Resolver; std::unique_ptr m_ProxyEndpoint; + std::shared_ptr m_Address4, m_Address6, m_YggdrasilAddress; public: diff --git a/libi2pd/NetDb.cpp b/libi2pd/NetDb.cpp index 4ce839ee..02d4dfd8 100644 --- a/libi2pd/NetDb.cpp +++ b/libi2pd/NetDb.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -26,6 +26,7 @@ #include "ECIESX25519AEADRatchetSession.h" #include "Config.h" #include "NetDb.hpp" +#include "util.h" using namespace i2p::transport; @@ -35,7 +36,7 @@ namespace data { NetDb netdb; - NetDb::NetDb (): m_IsRunning (false), m_Thread (nullptr), m_Reseeder (nullptr), m_Storage("netDb", "r", "routerInfo-", "dat"), m_PersistProfiles (true), m_HiddenMode(false) + NetDb::NetDb (): m_IsRunning (false), m_Thread (nullptr), m_Reseeder (nullptr), m_Storage("netDb", "r", "routerInfo-", "dat"), m_PersistProfiles (true) { } @@ -54,8 +55,24 @@ namespace data Load (); uint16_t threshold; i2p::config::GetOption("reseed.threshold", threshold); - if (m_RouterInfos.size () < threshold) // reseed if # of router less than threshold + if (m_RouterInfos.size () < threshold || m_Floodfills.GetSize () < NETDB_MIN_FLOODFILLS) // reseed if # of router less than threshold or too few floodfiils + { Reseed (); + } + else if (!GetRandomRouter (i2p::context.GetSharedRouterInfo (), false, false)) + Reseed (); // we don't have a router we can connect to. Trying to reseed + + auto it = m_RouterInfos.find (i2p::context.GetIdentHash ()); + if (it != m_RouterInfos.end ()) + { + // remove own router + m_Floodfills.Remove (it->second->GetIdentHash ()); + m_RouterInfos.erase (it); + } + // insert own router + m_RouterInfos.emplace (i2p::context.GetIdentHash (), i2p::context.GetSharedRouterInfo ()); + if (i2p::context.IsFloodfill ()) + m_Floodfills.Insert (i2p::context.GetSharedRouterInfo ()); i2p::config::GetOption("persist.profiles", m_PersistProfiles); @@ -68,11 +85,10 @@ namespace data if (m_IsRunning) { if (m_PersistProfiles) - for (auto& it: m_RouterInfos) - it.second->SaveProfile (); + SaveProfiles (); DeleteObsoleteProfiles (); m_RouterInfos.clear (); - m_Floodfills.clear (); + m_Floodfills.Clear (); if (m_Thread) { m_IsRunning = false; @@ -88,7 +104,12 @@ namespace data void NetDb::Run () { - uint32_t lastSave = 0, lastPublish = 0, lastExploratory = 0, lastManageRequest = 0, lastDestinationCleanup = 0; + i2p::util::SetThreadName("NetDB"); + + uint64_t lastManage = 0, lastExploratory = 0, lastManageRequest = 0, lastDestinationCleanup = 0; + uint64_t lastProfilesCleanup = i2p::util::GetSecondsSinceEpoch (); + int16_t profilesCleanupVariance = 0; + while (m_IsRunning) { try @@ -99,7 +120,7 @@ namespace data int numMsgs = 0; while (msg) { - LogPrint(eLogDebug, "NetDb: got request with type ", (int) msg->GetTypeID ()); + LogPrint(eLogDebug, "NetDb: Got request with type ", (int) msg->GetTypeID ()); switch (msg->GetTypeID ()) { case eI2NPDatabaseStore: @@ -116,7 +137,7 @@ namespace data HandleNTCP2RouterInfoMsg (msg); break; default: // WTF? - LogPrint (eLogError, "NetDb: unexpected message type ", (int) msg->GetTypeID ()); + LogPrint (eLogError, "NetDb: Unexpected message type ", (int) msg->GetTypeID ()); //i2p::HandleI2NPMessage (msg); } if (numMsgs > 100) break; @@ -125,35 +146,43 @@ namespace data } } if (!m_IsRunning) break; + if (!i2p::transport::transports.IsOnline ()) continue; // don't manage netdb when offline uint64_t ts = i2p::util::GetSecondsSinceEpoch (); - if (ts - lastManageRequest >= 15) // manage requests every 15 seconds + if (ts - lastManageRequest >= 15 || ts + 15 < lastManageRequest) // manage requests every 15 seconds { m_Requests.ManageRequests (); lastManageRequest = ts; } - if (ts - lastSave >= 60) // save routers, manage leasesets and validate subscriptions every minute + + if (ts - lastManage >= 60 || ts + 60 < lastManage) // manage routers and leasesets every minute { - if (lastSave) + if (lastManage) { - SaveUpdated (); + ManageRouterInfos (); ManageLeaseSets (); } - lastSave = ts; + lastManage = ts; } - if (ts - lastDestinationCleanup >= i2p::garlic::INCOMING_TAGS_EXPIRATION_TIMEOUT) + + if (ts - lastDestinationCleanup >= i2p::garlic::INCOMING_TAGS_EXPIRATION_TIMEOUT || + ts + i2p::garlic::INCOMING_TAGS_EXPIRATION_TIMEOUT < lastDestinationCleanup) { i2p::context.CleanupDestination (); lastDestinationCleanup = ts; } - if (ts - lastPublish >= NETDB_PUBLISH_INTERVAL) // update timestamp and publish + if (ts - lastProfilesCleanup >= (uint64_t)(i2p::data::PEER_PROFILE_AUTOCLEAN_TIMEOUT + profilesCleanupVariance) || + ts + i2p::data::PEER_PROFILE_AUTOCLEAN_TIMEOUT < lastProfilesCleanup) { - i2p::context.UpdateTimestamp (ts); - if (!m_HiddenMode) Publish (); - lastPublish = ts; + m_RouterProfilesPool.CleanUpMt (); + if (m_PersistProfiles) PersistProfiles (); + DeleteObsoleteProfiles (); + lastProfilesCleanup = ts; + profilesCleanupVariance = (rand () % (2 * i2p::data::PEER_PROFILE_AUTOCLEAN_VARIANCE) - i2p::data::PEER_PROFILE_AUTOCLEAN_VARIANCE); } - if (ts - lastExploratory >= 30) // exploratory every 30 seconds + + if (ts - lastExploratory >= 30 || ts + 30 < lastExploratory) // exploratory every 30 seconds { auto numRouters = m_RouterInfos.size (); if (!numRouters) @@ -166,7 +195,7 @@ namespace data if (numRouters < 1) numRouters = 1; if (numRouters > 9) numRouters = 9; m_Requests.ManageRequests (); - if(!m_HiddenMode) + if(!i2p::context.IsHidden ()) Explore (numRouters); lastExploratory = ts; } @@ -174,22 +203,15 @@ namespace data } catch (std::exception& ex) { - LogPrint (eLogError, "NetDb: runtime exception: ", ex.what ()); + LogPrint (eLogError, "NetDb: Runtime exception: ", ex.what ()); } } } - void NetDb::SetHidden(bool hide) - { - // TODO: remove reachable addresses from router info - m_HiddenMode = hide; - } - - bool NetDb::AddRouterInfo (const uint8_t * buf, int len) + std::shared_ptr NetDb::AddRouterInfo (const uint8_t * buf, int len) { bool updated; - AddRouterInfo (buf, len, updated); - return updated; + return AddRouterInfo (buf, len, updated); } std::shared_ptr NetDb::AddRouterInfo (const uint8_t * buf, int len, bool& updated) @@ -217,16 +239,41 @@ namespace data if (r->IsNewer (buf, len)) { bool wasFloodfill = r->IsFloodfill (); - r->Update (buf, len); + { + std::unique_lock l(m_RouterInfosMutex); + if (!r->Update (buf, len)) + { + updated = false; + m_Requests.RequestComplete (ident, r); + return r; + } + if (r->IsUnreachable ()) + { + // delete router as invalid after update + m_RouterInfos.erase (ident); + if (wasFloodfill) + { + std::unique_lock l(m_FloodfillsMutex); + m_Floodfills.Remove (r->GetIdentHash ()); + } + m_Requests.RequestComplete (ident, nullptr); + return nullptr; + } + } LogPrint (eLogInfo, "NetDb: RouterInfo updated: ", ident.ToBase64()); if (wasFloodfill != r->IsFloodfill ()) // if floodfill status updated { LogPrint (eLogDebug, "NetDb: RouterInfo floodfill status updated: ", ident.ToBase64()); std::unique_lock l(m_FloodfillsMutex); if (wasFloodfill) - m_Floodfills.remove (r); - else - m_Floodfills.push_back (r); + m_Floodfills.Remove (r->GetIdentHash ()); + else if (r->IsEligibleFloodfill ()) + { + if (m_Floodfills.GetSize () < NETDB_NUM_FLOODFILLS_THRESHOLD || r->GetProfile ()->IsReal ()) + m_Floodfills.Insert (r); + else + r->ResetFlooldFill (); + } } } else @@ -238,7 +285,8 @@ namespace data else { r = std::make_shared (buf, len); - if (!r->IsUnreachable () && r->HasValidAddresses ()) + if (!r->IsUnreachable () && r->HasValidAddresses () && (!r->IsFloodfill () || !r->GetProfile ()->IsUnreachable ()) && + i2p::util::GetMillisecondsSinceEpoch () + NETDB_EXPIRATION_TIMEOUT_THRESHOLD*1000LL > r->GetTimestamp ()) { bool inserted = false; { @@ -248,10 +296,16 @@ namespace data if (inserted) { LogPrint (eLogInfo, "NetDb: RouterInfo added: ", ident.ToBase64()); - if (r->IsFloodfill () && r->IsReachable ()) // floodfill must be reachable + if (r->IsFloodfill () && r->IsEligibleFloodfill ()) { - std::unique_lock l(m_FloodfillsMutex); - m_Floodfills.push_back (r); + if (m_Floodfills.GetSize () < NETDB_NUM_FLOODFILLS_THRESHOLD || + r->GetProfile ()->IsReal ()) // don't insert floodfill until it's known real if we have enough + { + std::unique_lock l(m_FloodfillsMutex); + m_Floodfills.Insert (r); + } + else + r->ResetFlooldFill (); } } else @@ -301,22 +355,23 @@ namespace data updated = true; } else - LogPrint (eLogError, "NetDb: new LeaseSet validation failed: ", ident.ToBase32()); + LogPrint (eLogError, "NetDb: New LeaseSet validation failed: ", ident.ToBase32()); } return updated; } bool NetDb::AddLeaseSet2 (const IdentHash& ident, const uint8_t * buf, int len, uint8_t storeType) { - std::unique_lock lock(m_LeaseSetsMutex); auto leaseSet = std::make_shared (storeType, buf, len, false); // we don't need leases in netdb if (leaseSet->IsValid ()) { + std::unique_lock lock(m_LeaseSetsMutex); auto it = m_LeaseSets.find(ident); if (it == m_LeaseSets.end () || it->second->GetStoreType () != storeType || leaseSet->GetPublishedTimestamp () > it->second->GetPublishedTimestamp ()) { - if (leaseSet->IsPublic ()) + if (leaseSet->IsPublic () && !leaseSet->IsExpired () && + i2p::util::GetSecondsSinceEpoch () + NETDB_EXPIRATION_TIMEOUT_THRESHOLD > leaseSet->GetPublishedTimestamp ()) { // TODO: implement actual update LogPrint (eLogInfo, "NetDb: LeaseSet2 updated: ", ident.ToBase32()); @@ -325,13 +380,13 @@ namespace data } else { - LogPrint (eLogWarning, "NetDb: Unpublished LeaseSet2 received: ", ident.ToBase32()); + LogPrint (eLogWarning, "NetDb: Unpublished or expired or future LeaseSet2 received: ", ident.ToBase32()); m_LeaseSets.erase (ident); } } } else - LogPrint (eLogError, "NetDb: new LeaseSet2 validation failed: ", ident.ToBase32()); + LogPrint (eLogError, "NetDb: New LeaseSet2 validation failed: ", ident.ToBase32()); return false; } @@ -366,9 +421,24 @@ namespace data void NetDb::SetUnreachable (const IdentHash& ident, bool unreachable) { - auto it = m_RouterInfos.find (ident); - if (it != m_RouterInfos.end ()) - return it->second->SetUnreachable (unreachable); + auto r = FindRouter (ident); + if (r) + { + r->SetUnreachable (unreachable); + auto profile = r->GetProfile (); + if (profile) + profile->Unreachable (unreachable); + } + } + + void NetDb::ExcludeReachableTransports (const IdentHash& ident, RouterInfo::CompatibleTransports transports) + { + auto r = FindRouter (ident); + if (r) + { + std::unique_lock l(m_RouterInfosMutex); + r->ExcludeReachableTransports (transports); + } } void NetDb::Reseed () @@ -380,15 +450,18 @@ namespace data } // try reseeding from floodfill first if specified - std::string riPath; - if(i2p::config::GetOption("reseed.floodfill", riPath)) { + std::string riPath; i2p::config::GetOption("reseed.floodfill", riPath); + if (!riPath.empty()) + { auto ri = std::make_shared(riPath); - if (ri->IsFloodfill()) { + if (ri->IsFloodfill()) + { const uint8_t * riData = ri->GetBuffer(); int riLen = ri->GetBufferLen(); - if(!i2p::data::netdb.AddRouterInfo(riData, riLen)) { + if (!i2p::data::netdb.AddRouterInfo(riData, riLen)) + { // bad router info - LogPrint(eLogError, "NetDb: bad router info"); + LogPrint(eLogError, "NetDb: Bad router info"); return; } m_FloodfillBootstrap = ri; @@ -403,7 +476,7 @@ namespace data void NetDb::ReseedFromFloodfill(const RouterInfo & ri, int numRouters, int numFloodfills) { - LogPrint(eLogInfo, "NetDB: reseeding from floodfill ", ri.GetIdentHashBase64()); + LogPrint(eLogInfo, "NetDB: Reseeding from floodfill ", ri.GetIdentHashBase64()); std::vector > requests; i2p::data::IdentHash ourIdent = i2p::context.GetIdentHash(); @@ -430,21 +503,22 @@ namespace data i2p::transport::transports.SendMessages(ih, requests); } - bool NetDb::LoadRouterInfo (const std::string & path) + bool NetDb::LoadRouterInfo (const std::string& path, uint64_t ts) { auto r = std::make_shared(path); - if (r->GetRouterIdentity () && !r->IsUnreachable () && - (!r->UsesIntroducer () || m_LastLoad < r->GetTimestamp () + NETDB_INTRODUCEE_EXPIRATION_TIMEOUT*1000LL)) // 1 hour + if (r->GetRouterIdentity () && !r->IsUnreachable () && r->HasValidAddresses () && + ts < r->GetTimestamp () + 24*60*60*NETDB_MAX_OFFLINE_EXPIRATION_TIMEOUT*1000LL) // too old { r->DeleteBuffer (); - r->ClearProperties (); // properties are not used for regular routers - m_RouterInfos[r->GetIdentHash ()] = r; - if (r->IsFloodfill () && r->IsReachable ()) // floodfill must be reachable - m_Floodfills.push_back (r); + if (m_RouterInfos.emplace (r->GetIdentHash (), r).second) + { + if (r->IsFloodfill () && r->IsEligibleFloodfill ()) + m_Floodfills.Insert (r); + } } else { - LogPrint(eLogWarning, "NetDb: RI from ", path, " is invalid. Delete"); + LogPrint(eLogWarning, "NetDb: RI from ", path, " is invalid or too old. Delete"); i2p::fs::Remove(path); } return true; @@ -523,92 +597,115 @@ namespace data { // make sure we cleanup netDb from previous attempts m_RouterInfos.clear (); - m_Floodfills.clear (); + m_Floodfills.Clear (); - m_LastLoad = i2p::util::GetSecondsSinceEpoch(); + uint64_t ts = i2p::util::GetMillisecondsSinceEpoch(); std::vector files; m_Storage.Traverse(files); for (const auto& path : files) - LoadRouterInfo(path); + LoadRouterInfo (path, ts); - LogPrint (eLogInfo, "NetDb: ", m_RouterInfos.size(), " routers loaded (", m_Floodfills.size (), " floodfils)"); + LogPrint (eLogInfo, "NetDb: ", m_RouterInfos.size(), " routers loaded (", m_Floodfills.GetSize (), " floodfils)"); } void NetDb::SaveUpdated () { - int updatedCount = 0, deletedCount = 0; + int updatedCount = 0, deletedCount = 0, deletedFloodfillsCount = 0; auto total = m_RouterInfos.size (); + auto totalFloodfills = m_Floodfills.GetSize (); uint64_t expirationTimeout = NETDB_MAX_EXPIRATION_TIMEOUT*1000LL; uint64_t ts = i2p::util::GetMillisecondsSinceEpoch(); auto uptime = i2p::context.GetUptime (); + double minTunnelCreationSuccessRate; + i2p::config::GetOption("limits.zombies", minTunnelCreationSuccessRate); + bool isLowRate = i2p::tunnel::tunnels.GetPreciseTunnelCreationSuccessRate () < minTunnelCreationSuccessRate; // routers don't expire if less than 90 or uptime is less than 1 hour bool checkForExpiration = total > NETDB_MIN_ROUTERS && uptime > 600; // 10 minutes if (checkForExpiration && uptime > 3600) // 1 hour expirationTimeout = i2p::context.IsFloodfill () ? NETDB_FLOODFILL_EXPIRATION_TIMEOUT*1000LL : NETDB_MIN_EXPIRATION_TIMEOUT*1000LL + (NETDB_MAX_EXPIRATION_TIMEOUT - NETDB_MIN_EXPIRATION_TIMEOUT)*1000LL*NETDB_MIN_ROUTERS/total; + auto own = i2p::context.GetSharedRouterInfo (); for (auto& it: m_RouterInfos) { + if (it.second == own) continue; // skip own std::string ident = it.second->GetIdentHashBase64(); - std::string path = m_Storage.Path(ident); if (it.second->IsUpdated ()) { - it.second->SaveToFile (path); + if (it.second->GetBuffer ()) + { + // we have something to save + it.second->SaveToFile (m_Storage.Path(ident)); + it.second->SetUnreachable (false); + it.second->DeleteBuffer (); + } it.second->SetUpdated (false); - it.second->SetUnreachable (false); - it.second->DeleteBuffer (); updatedCount++; continue; } - // make router reachable back if too few routers - if (it.second->IsUnreachable () && total - deletedCount < NETDB_MIN_ROUTERS) - it.second->SetUnreachable (false); - // find & mark expired routers - if (it.second->UsesIntroducer ()) + if (it.second->GetProfile ()->IsUnreachable ()) + it.second->SetUnreachable (true); + // make router reachable back if too few routers or floodfills + if (it.second->IsUnreachable () && (total - deletedCount < NETDB_MIN_ROUTERS || isLowRate || + (it.second->IsFloodfill () && totalFloodfills - deletedFloodfillsCount < NETDB_MIN_FLOODFILLS))) + it.second->SetUnreachable (false); + if (!it.second->IsUnreachable ()) { - if (ts > it.second->GetTimestamp () + NETDB_INTRODUCEE_EXPIRATION_TIMEOUT*1000LL) - // RouterInfo expires after 1 hour if uses introducer + // find & mark expired routers + if (!it.second->GetCompatibleTransports (true)) // non reachable by any transport it.second->SetUnreachable (true); + else if (checkForExpiration && ts > it.second->GetTimestamp () + expirationTimeout) + it.second->SetUnreachable (true); + else if (ts + NETDB_EXPIRATION_TIMEOUT_THRESHOLD*1000LL < it.second->GetTimestamp ()) + { + LogPrint (eLogWarning, "NetDb: RouterInfo is from future for ", (it.second->GetTimestamp () - ts)/1000LL, " seconds"); + it.second->SetUnreachable (true); + } + if (it.second->IsUnreachable () && i2p::transport::transports.IsConnected (it.second->GetIdentHash ())) + it.second->SetUnreachable (false); // don't expire connected router } - else if (checkForExpiration && ts > it.second->GetTimestamp () + expirationTimeout) - it.second->SetUnreachable (true); if (it.second->IsUnreachable ()) { + if (it.second->IsFloodfill ()) deletedFloodfillsCount++; // delete RI file m_Storage.Remove(ident); deletedCount++; - if (total - deletedCount < NETDB_MIN_ROUTERS) checkForExpiration = false; + if (total - deletedCount < NETDB_MIN_ROUTERS) checkForExpiration = false; } } // m_RouterInfos iteration + m_RouterInfoBuffersPool.CleanUpMt (); + m_RouterInfoAddressesPool.CleanUpMt (); + m_RouterInfoAddressVectorsPool.CleanUpMt (); + m_IdentitiesPool.CleanUpMt (); + if (updatedCount > 0) - LogPrint (eLogInfo, "NetDb: saved ", updatedCount, " new/updated routers"); + LogPrint (eLogInfo, "NetDb: Saved ", updatedCount, " new/updated routers"); if (deletedCount > 0) { - LogPrint (eLogInfo, "NetDb: deleting ", deletedCount, " unreachable routers"); + LogPrint (eLogInfo, "NetDb: Deleting ", deletedCount, " unreachable routers"); // clean up RouterInfos table { std::unique_lock l(m_RouterInfosMutex); for (auto it = m_RouterInfos.begin (); it != m_RouterInfos.end ();) { if (it->second->IsUnreachable ()) - { - if (m_PersistProfiles) it->second->SaveProfile (); it = m_RouterInfos.erase (it); - continue; + else + { + it->second->DropProfile (); + it++; } - ++it; } } // clean up expired floodfills or not floodfills anymore { std::unique_lock l(m_FloodfillsMutex); - for (auto it = m_Floodfills.begin (); it != m_Floodfills.end ();) - if ((*it)->IsUnreachable () || !(*it)->IsFloodfill ()) - it = m_Floodfills.erase (it); - else - ++it; + m_Floodfills.Cleanup ([](const std::shared_ptr& r)->bool + { + return r && r->IsFloodfill () && !r->IsUnreachable (); + }); } } } @@ -618,29 +715,36 @@ namespace data auto dest = m_Requests.CreateRequest (destination, false, requestComplete); // non-exploratory if (!dest) { - LogPrint (eLogWarning, "NetDb: destination ", destination.ToBase64(), " is requested already"); + LogPrint (eLogWarning, "NetDb: Destination ", destination.ToBase64(), " is requested already"); return; } auto floodfill = GetClosestFloodfill (destination, dest->GetExcludedPeers ()); if (floodfill) { + if (direct && !floodfill->IsReachableFrom (i2p::context.GetRouterInfo ()) && + !i2p::transport::transports.IsConnected (floodfill->GetIdentHash ())) + direct = false; // floodfill can't be reached directly if (direct) transports.SendMessage (floodfill->GetIdentHash (), dest->CreateRequestMessage (floodfill->GetIdentHash ())); else { auto pool = i2p::tunnel::tunnels.GetExploratoryPool (); - auto outbound = pool ? pool->GetNextOutboundTunnel () : nullptr; - auto inbound = pool ? pool->GetNextInboundTunnel () : nullptr; + auto outbound = pool ? pool->GetNextOutboundTunnel (nullptr, floodfill->GetCompatibleTransports (false)) : nullptr; + auto inbound = pool ? pool->GetNextInboundTunnel (nullptr, floodfill->GetCompatibleTransports (true)) : nullptr; if (outbound && inbound) - outbound->SendTunnelDataMsg (floodfill->GetIdentHash (), 0, dest->CreateRequestMessage (floodfill, inbound)); + { + auto msg = dest->CreateRequestMessage (floodfill, inbound); + outbound->SendTunnelDataMsgTo (floodfill->GetIdentHash (), 0, + i2p::garlic::WrapECIESX25519MessageForRouter (msg, floodfill->GetIdentity ()->GetEncryptionPublicKey ())); + } else { LogPrint (eLogError, "NetDb: ", destination.ToBase64(), " destination requested, but no tunnels found"); - m_Requests.RequestComplete (destination, nullptr); - } - } - } + m_Requests.RequestComplete (destination, nullptr); + } + } + } else { LogPrint (eLogError, "NetDb: ", destination.ToBase64(), " destination requested, but no floodfills found"); @@ -654,10 +758,10 @@ namespace data auto dest = m_Requests.CreateRequest (destination, exploritory, requestComplete); // non-exploratory if (!dest) { - LogPrint (eLogWarning, "NetDb: destination ", destination.ToBase64(), " is requested already"); + LogPrint (eLogWarning, "NetDb: Destination ", destination.ToBase64(), " is requested already"); return; } - LogPrint(eLogInfo, "NetDb: destination ", destination.ToBase64(), " being requested directly from ", from.ToBase64()); + LogPrint(eLogInfo, "NetDb: Destination ", destination.ToBase64(), " being requested directly from ", from.ToBase64()); // direct transports.SendMessage (from, dest->CreateRequestMessage (nullptr, nullptr)); } @@ -678,36 +782,49 @@ namespace data { const uint8_t * buf = m->GetPayload (); size_t len = m->GetSize (); + if (len < DATABASE_STORE_HEADER_SIZE) + { + LogPrint (eLogError, "NetDb: Database store msg is too short ", len, ". Dropped"); + return; + } IdentHash ident (buf + DATABASE_STORE_KEY_OFFSET); if (ident.IsZero ()) { - LogPrint (eLogDebug, "NetDb: database store with zero ident, dropped"); + LogPrint (eLogDebug, "NetDb: Database store with zero ident, dropped"); return; } uint32_t replyToken = bufbe32toh (buf + DATABASE_STORE_REPLY_TOKEN_OFFSET); size_t offset = DATABASE_STORE_HEADER_SIZE; if (replyToken) { - auto deliveryStatus = CreateDeliveryStatusMsg (replyToken); + if (len < offset + 36) // 32 + 4 + { + LogPrint (eLogError, "NetDb: Database store msg with reply token is too short ", len, ". Dropped"); + return; + } uint32_t tunnelID = bufbe32toh (buf + offset); offset += 4; - if (!tunnelID) // send response directly - transports.SendMessage (buf + offset, deliveryStatus); - else + if (replyToken != 0xFFFFFFFFU) // if not caught on OBEP or IBGW { - auto pool = i2p::tunnel::tunnels.GetExploratoryPool (); - auto outbound = pool ? pool->GetNextOutboundTunnel () : nullptr; - if (outbound) - outbound->SendTunnelDataMsg (buf + offset, tunnelID, deliveryStatus); + auto deliveryStatus = CreateDeliveryStatusMsg (replyToken); + if (!tunnelID) // send response directly + transports.SendMessage (buf + offset, deliveryStatus); else - LogPrint (eLogWarning, "NetDb: no outbound tunnels for DatabaseStore reply found"); + { + auto pool = i2p::tunnel::tunnels.GetExploratoryPool (); + auto outbound = pool ? pool->GetNextOutboundTunnel () : nullptr; + if (outbound) + outbound->SendTunnelDataMsgTo (buf + offset, tunnelID, deliveryStatus); + else + LogPrint (eLogWarning, "NetDb: No outbound tunnels for DatabaseStore reply found"); + } } offset += 32; } // we must send reply back before this check if (ident == i2p::context.GetIdentHash ()) { - LogPrint (eLogDebug, "NetDb: database store with own RouterInfo received, dropped"); + LogPrint (eLogDebug, "NetDb: Database store with own RouterInfo received, dropped"); return; } size_t payloadOffset = offset; @@ -716,28 +833,38 @@ namespace data uint8_t storeType = buf[DATABASE_STORE_TYPE_OFFSET]; if (storeType) // LeaseSet or LeaseSet2 { - if (!m->from) // unsolicited LS must be received directly + if (len > MAX_LS_BUFFER_SIZE + offset) + { + LogPrint (eLogError, "NetDb: Database store message is too long ", len); + return; + } + if (!context.IsFloodfill ()) + { + LogPrint (eLogInfo, "NetDb: Not Floodfill, LeaseSet store request ignored for ", ident.ToBase32()); + return; + } + else if (!m->from) // unsolicited LS must be received directly { if (storeType == NETDB_STORE_TYPE_LEASESET) // 1 { - LogPrint (eLogDebug, "NetDb: store request: LeaseSet for ", ident.ToBase32()); + LogPrint (eLogDebug, "NetDb: Store request: LeaseSet for ", ident.ToBase32()); updated = AddLeaseSet (ident, buf + offset, len - offset); } else // all others are considered as LeaseSet2 { - LogPrint (eLogDebug, "NetDb: store request: LeaseSet2 of type ", storeType, " for ", ident.ToBase32()); + LogPrint (eLogDebug, "NetDb: Store request: LeaseSet2 of type ", int(storeType), " for ", ident.ToBase32()); updated = AddLeaseSet2 (ident, buf + offset, len - offset, storeType); } } } else // RouterInfo { - LogPrint (eLogDebug, "NetDb: store request: RouterInfo"); + LogPrint (eLogDebug, "NetDb: Store request: RouterInfo"); size_t size = bufbe16toh (buf + offset); offset += 2; if (size > MAX_RI_BUFFER_SIZE || size > len - offset) { - LogPrint (eLogError, "NetDb: invalid RouterInfo length ", (int)size); + LogPrint (eLogError, "NetDb: Invalid RouterInfo length ", (int)size); return; } uint8_t uncompressed[MAX_RI_BUFFER_SIZE]; @@ -746,7 +873,7 @@ namespace data updated = AddRouterInfo (ident, uncompressed, uncompressedSize); else { - LogPrint (eLogInfo, "NetDb: decompression failed ", uncompressedSize); + LogPrint (eLogInfo, "NetDb: Decompression failed ", uncompressedSize); return; } } @@ -802,7 +929,7 @@ namespace data { // request destination LogPrint (eLogDebug, "NetDb: Try ", key, " at ", count, " floodfill ", nextFloodfill->GetIdentHash ().ToBase64 ()); - outbound->SendTunnelDataMsg (nextFloodfill->GetIdentHash (), 0, + outbound->SendTunnelDataMsgTo (nextFloodfill->GetIdentHash (), 0, dest->CreateRequestMessage (nextFloodfill, inbound)); deleteDest = false; } @@ -821,7 +948,7 @@ namespace data m_Requests.RequestComplete (ident, nullptr); } else if(!m_FloodfillBootstrap) - LogPrint (eLogWarning, "NetDb: requested destination for ", key, " not found"); + LogPrint (eLogWarning, "NetDb: Requested destination for ", key, " not found"); // try responses for (int i = 0; i < num; i++) @@ -836,7 +963,7 @@ namespace data if (!r || i2p::util::GetMillisecondsSinceEpoch () > r->GetTimestamp () + 3600*1000LL) { // router with ident not found or too old (1 hour) - LogPrint (eLogDebug, "NetDb: found new/outdated router. Requesting RouterInfo ..."); + LogPrint (eLogDebug, "NetDb: Found new/outdated router. Requesting RouterInfo..."); if(m_FloodfillBootstrap) RequestDestinationFrom(router, m_FloodfillBootstrap->GetIdentHash(), true); else @@ -875,21 +1002,22 @@ namespace data } uint16_t numExcluded = bufbe16toh (excluded); excluded += 2; - if (numExcluded > 512) + if (numExcluded > 512 || (excluded - buf) + numExcluded*32 > (int)msg->GetPayloadLength ()) { - LogPrint (eLogWarning, "NetDb: number of excluded peers", numExcluded, " exceeds 512"); + LogPrint (eLogWarning, "NetDb: Number of excluded peers", numExcluded, " is too much"); return; } std::shared_ptr replyMsg; if (lookupType == DATABASE_LOOKUP_TYPE_EXPLORATORY_LOOKUP) { - LogPrint (eLogInfo, "NetDb: exploratory close to ", key, " ", numExcluded, " excluded"); + LogPrint (eLogInfo, "NetDb: Exploratory close to ", key, " ", numExcluded, " excluded"); std::set excludedRouters; + const uint8_t * excluded_ident = excluded; for (int i = 0; i < numExcluded; i++) { - excludedRouters.insert (excluded); - excluded += 32; + excludedRouters.insert (excluded_ident); + excluded_ident += 32; } std::vector routers; for (int i = 0; i < 3; i++) @@ -906,14 +1034,13 @@ namespace data else { if (lookupType == DATABASE_LOOKUP_TYPE_ROUTERINFO_LOOKUP || - lookupType == DATABASE_LOOKUP_TYPE_NORMAL_LOOKUP) + lookupType == DATABASE_LOOKUP_TYPE_NORMAL_LOOKUP) { auto router = FindRouter (ident); - if (router) + if (router && !router->IsUnreachable ()) { - LogPrint (eLogDebug, "NetDb: requested RouterInfo ", key, " found"); - router->LoadBuffer (); - if (router->GetBuffer ()) + LogPrint (eLogDebug, "NetDb: Requested RouterInfo ", key, " found"); + if (PopulateRouterInfoBuffer (router)) replyMsg = CreateDatabaseStoreMsg (router); } } @@ -925,11 +1052,11 @@ namespace data if (!leaseSet) { // no lease set found - LogPrint(eLogDebug, "NetDb: requested LeaseSet not found for ", ident.ToBase32()); + LogPrint(eLogDebug, "NetDb: Requested LeaseSet not found for ", ident.ToBase32()); } else if (!leaseSet->IsExpired ()) // we don't send back our LeaseSets { - LogPrint (eLogDebug, "NetDb: requested LeaseSet ", key, " found"); + LogPrint (eLogDebug, "NetDb: Requested LeaseSet ", key, " found"); replyMsg = CreateDatabaseStoreMsg (ident, leaseSet); } } @@ -947,7 +1074,7 @@ namespace data if (closestFloodfills.empty ()) LogPrint (eLogWarning, "NetDb: Requested ", key, " not found, ", numExcluded, " peers excluded"); replyMsg = CreateDatabaseSearchReply (ident, closestFloodfills); - } + } } excluded += numExcluded * 32; if (replyMsg) @@ -965,7 +1092,7 @@ namespace data { uint64_t tag; memcpy (&tag, excluded + 33, 8); - replyMsg = i2p::garlic::WrapECIESX25519AEADRatchetMessage (replyMsg, sessionKey, tag); + replyMsg = i2p::garlic::WrapECIESX25519Message (replyMsg, sessionKey, tag); } else { @@ -974,15 +1101,15 @@ namespace data replyMsg = garlic.WrapSingleMessage (replyMsg); } if (!replyMsg) - LogPrint (eLogError, "NetDb: failed to wrap message"); + LogPrint (eLogError, "NetDb: Failed to wrap message"); } else - LogPrint(eLogWarning, "NetDb: encrypted reply requested but no tags provided"); + LogPrint(eLogWarning, "NetDb: Encrypted reply requested but no tags provided"); } auto exploratoryPool = i2p::tunnel::tunnels.GetExploratoryPool (); auto outbound = exploratoryPool ? exploratoryPool->GetNextOutboundTunnel () : nullptr; if (outbound) - outbound->SendTunnelDataMsg (replyIdent, replyTunnelID, replyMsg); + outbound->SendTunnelDataMsgTo (replyIdent, replyTunnelID, replyMsg); else transports.SendMessage (replyIdent, i2p::CreateTunnelGatewayMsg (replyTunnelID, replyMsg)); } @@ -1001,14 +1128,14 @@ namespace data uint8_t randomHash[32]; std::vector msgs; - LogPrint (eLogInfo, "NetDb: exploring new ", numDestinations, " routers ..."); + LogPrint (eLogInfo, "NetDb: Exploring new ", numDestinations, " routers ..."); for (int i = 0; i < numDestinations; i++) { RAND_bytes (randomHash, 32); auto dest = m_Requests.CreateRequest (randomHash, true); // exploratory if (!dest) { - LogPrint (eLogWarning, "NetDb: exploratory destination is requested already"); + LogPrint (eLogWarning, "NetDb: Exploratory destination is requested already"); return; } auto floodfill = GetClosestFloodfill (randomHash, dest->GetExcludedPeers ()); @@ -1038,25 +1165,7 @@ namespace data m_Requests.RequestComplete (randomHash, nullptr); } if (throughTunnels && msgs.size () > 0) - outbound->SendTunnelDataMsg (msgs); - } - - void NetDb::Publish () - { - i2p::context.UpdateStats (); // for floodfill - std::set excluded; // TODO: fill up later - for (int i = 0; i < 2; i++) - { - auto floodfill = GetClosestFloodfill (i2p::context.GetRouterInfo ().GetIdentHash (), excluded); - if (floodfill) - { - uint32_t replyToken; - RAND_bytes ((uint8_t *)&replyToken, 4); - LogPrint (eLogInfo, "NetDb: Publishing our RouterInfo to ", i2p::data::GetIdentHashAbbreviation(floodfill->GetIdentHash ()), ". reply token=", replyToken); - transports.SendMessage (floodfill->GetIdentHash (), CreateDatabaseStoreMsg (i2p::context.GetSharedRouterInfo (), replyToken)); - excluded.insert (floodfill->GetIdentHash ()); - } - } + outbound->SendTunnelDataMsgs (msgs); } void NetDb::Flood (const IdentHash& ident, std::shared_ptr floodMsg) @@ -1088,52 +1197,54 @@ namespace data }); } - std::shared_ptr NetDb::GetRandomRouter (std::shared_ptr compatibleWith) const + std::shared_ptr NetDb::GetRandomRouter (std::shared_ptr compatibleWith, + bool reverse, bool endpoint) const { return GetRandomRouter ( - [compatibleWith](std::shared_ptr router)->bool + [compatibleWith, reverse, endpoint](std::shared_ptr router)->bool { return !router->IsHidden () && router != compatibleWith && - router->IsCompatible (*compatibleWith); + (reverse ? (compatibleWith->IsReachableFrom (*router) && router->GetCompatibleTransports (true)): + router->IsReachableFrom (*compatibleWith)) && + router->IsECIES () && !router->IsHighCongestion (false) && + (!endpoint || (router->IsV4 () && (!reverse || router->IsPublished (true)))); // endpoint must be ipv4 and published if inbound(reverse) }); } - std::shared_ptr NetDb::GetRandomPeerTestRouter (bool v4only) const + std::shared_ptr NetDb::GetRandomSSU2PeerTestRouter (bool v4, const std::set& excluded) const { return GetRandomRouter ( - [v4only](std::shared_ptr router)->bool + [v4, &excluded](std::shared_ptr router)->bool { - return !router->IsHidden () && router->IsPeerTesting () && router->IsSSU (v4only); + return !router->IsHidden () && router->IsECIES () && + router->IsSSU2PeerTesting (v4) && !excluded.count (router->GetIdentHash ()); }); } - std::shared_ptr NetDb::GetRandomSSUV6Router () const + std::shared_ptr NetDb::GetRandomSSU2Introducer (bool v4, const std::set& excluded) const { return GetRandomRouter ( - [](std::shared_ptr router)->bool + [v4, &excluded](std::shared_ptr router)->bool { - return !router->IsHidden () && router->IsSSUV6 (); + return !router->IsHidden () && router->IsSSU2Introducer (v4) && + !excluded.count (router->GetIdentHash ()); }); } - std::shared_ptr NetDb::GetRandomIntroducer () const + std::shared_ptr NetDb::GetHighBandwidthRandomRouter (std::shared_ptr compatibleWith, + bool reverse, bool endpoint) const { return GetRandomRouter ( - [](std::shared_ptr router)->bool - { - return !router->IsHidden () && router->IsIntroducer (); - }); - } - - std::shared_ptr NetDb::GetHighBandwidthRandomRouter (std::shared_ptr compatibleWith) const - { - return GetRandomRouter ( - [compatibleWith](std::shared_ptr router)->bool + [compatibleWith, reverse, endpoint](std::shared_ptr router)->bool { return !router->IsHidden () && router != compatibleWith && - router->IsCompatible (*compatibleWith) && + (reverse ? (compatibleWith->IsReachableFrom (*router) && router->GetCompatibleTransports (true)) : + router->IsReachableFrom (*compatibleWith)) && (router->GetCaps () & RouterInfo::eHighBandwidth) && - router->GetVersion () >= NETDB_MIN_HIGHBANDWIDTH_VERSION; + router->GetVersion () >= NETDB_MIN_HIGHBANDWIDTH_VERSION && + router->IsECIES () && !router->IsHighCongestion (true) && + (!endpoint || (router->IsV4 () && (!reverse || router->IsPublished (true)))); // endpoint must be ipv4 and published if inbound(reverse) + }); } @@ -1142,23 +1253,57 @@ namespace data { if (m_RouterInfos.empty()) return 0; - uint32_t ind = rand () % m_RouterInfos.size (); - for (int j = 0; j < 2; j++) + uint16_t inds[3]; + RAND_bytes ((uint8_t *)inds, sizeof (inds)); + std::unique_lock l(m_RouterInfosMutex); + auto count = m_RouterInfos.size (); + if(count == 0) return nullptr; + inds[0] %= count; + auto it = m_RouterInfos.begin (); + std::advance (it, inds[0]); + // try random router + if (it != m_RouterInfos.end () && !it->second->IsUnreachable () && filter (it->second)) + return it->second; + // try some routers around + auto it1 = m_RouterInfos.begin (); + if (inds[0]) { - uint32_t i = 0; - std::unique_lock l(m_RouterInfosMutex); - for (const auto& it: m_RouterInfos) - { - if (i >= ind) - { - if (!it.second->IsUnreachable () && filter (it.second)) - return it.second; - } - else - i++; - } - // we couldn't find anything, try second pass - ind = 0; + // before + inds[1] %= inds[0]; + std::advance (it1, (inds[1] + inds[0])/2); + } + else + it1 = it; + auto it2 = it; + if (inds[0] < m_RouterInfos.size () - 1) + { + // after + inds[2] %= (m_RouterInfos.size () - 1 - inds[0]); inds[2] /= 2; + std::advance (it2, inds[2]); + } + // it1 - from, it2 - to + it = it1; + while (it != it2 && it != m_RouterInfos.end ()) + { + if (!it->second->IsUnreachable () && filter (it->second)) + return it->second; + it++; + } + // still not found, try from the beginning + it = m_RouterInfos.begin (); + while (it != it1 && it != m_RouterInfos.end ()) + { + if (!it->second->IsUnreachable () && filter (it->second)) + return it->second; + it++; + } + // still not found, try to the beginning + it = it2; + while (it != m_RouterInfos.end ()) + { + if (!it->second->IsUnreachable () && filter (it->second)) + return it->second; + it++; } return nullptr; // seems we have too few routers } @@ -1169,84 +1314,45 @@ namespace data } std::shared_ptr NetDb::GetClosestFloodfill (const IdentHash& destination, - const std::set& excluded, bool closeThanUsOnly) const + const std::set& excluded) const { - std::shared_ptr r; - XORMetric minMetric; IdentHash destKey = CreateRoutingKey (destination); - if (closeThanUsOnly) - minMetric = destKey ^ i2p::context.GetIdentHash (); - else - minMetric.SetMax (); std::unique_lock l(m_FloodfillsMutex); - for (const auto& it: m_Floodfills) - { - if (!it->IsUnreachable ()) + return m_Floodfills.FindClosest (destKey, [&excluded](const std::shared_ptr& r)->bool { - XORMetric m = destKey ^ it->GetIdentHash (); - if (m < minMetric && !excluded.count (it->GetIdentHash ())) - { - minMetric = m; - r = it; - } - } - } - return r; + return r && !r->IsUnreachable () && !r->GetProfile ()->IsUnreachable () && + !excluded.count (r->GetIdentHash ()); + }); } std::vector NetDb::GetClosestFloodfills (const IdentHash& destination, size_t num, std::set& excluded, bool closeThanUsOnly) const { - struct Sorted - { - std::shared_ptr r; - XORMetric metric; - bool operator< (const Sorted& other) const { return metric < other.metric; }; - }; - - std::set sorted; + std::vector res; IdentHash destKey = CreateRoutingKey (destination); - XORMetric ourMetric; - if (closeThanUsOnly) ourMetric = destKey ^ i2p::context.GetIdentHash (); + std::vector > v; { std::unique_lock l(m_FloodfillsMutex); - for (const auto& it: m_Floodfills) - { - if (!it->IsUnreachable ()) + v = m_Floodfills.FindClosest (destKey, num, [&excluded](const std::shared_ptr& r)->bool { - XORMetric m = destKey ^ it->GetIdentHash (); - if (closeThanUsOnly && ourMetric < m) continue; - if (sorted.size () < num) - sorted.insert ({it, m}); - else if (m < sorted.rbegin ()->metric) - { - sorted.insert ({it, m}); - sorted.erase (std::prev (sorted.end ())); - } - } - } + return r && !r->IsUnreachable () && !r->GetProfile ()->IsUnreachable () && + !excluded.count (r->GetIdentHash ()); + }); } + if (v.empty ()) return res; - std::vector res; - size_t i = 0; - for (const auto& it: sorted) + XORMetric ourMetric; + if (closeThanUsOnly) ourMetric = destKey ^ i2p::context.GetIdentHash (); + for (auto& it: v) { - if (i < num) - { - const auto& ident = it.r->GetIdentHash (); - if (!excluded.count (ident)) - { - res.push_back (ident); - i++; - } - } - else - break; + if (closeThanUsOnly && ourMetric < (destKey ^ it->GetIdentHash ())) break; + res.push_back (it->GetIdentHash ()); } return res; } - std::shared_ptr NetDb::GetRandomRouterInFamily(const std::string & fam) const { + std::shared_ptr NetDb::GetRandomRouterInFamily (FamilyID fam) const + { return GetRandomRouter( [fam](std::shared_ptr router)->bool { @@ -1277,6 +1383,17 @@ namespace data return r; } + void NetDb::ManageRouterInfos () + { + auto ts = i2p::util::GetSecondsSinceEpoch (); + { + std::unique_lock l(m_RouterInfosMutex); + for (auto& it: m_RouterInfos) + it.second->UpdateIntroducers (ts); + } + SaveUpdated (); + } + void NetDb::ManageLeaseSets () { auto ts = i2p::util::GetMillisecondsSinceEpoch (); @@ -1290,6 +1407,14 @@ namespace data else ++it; } + m_LeasesPool.CleanUpMt (); + } + + bool NetDb::PopulateRouterInfoBuffer (std::shared_ptr r) + { + if (!r) return false; + if (r->GetBuffer ()) return true; + return r->LoadBuffer (m_Storage.Path (r->GetIdentHashBase64 ())); } } } diff --git a/libi2pd/NetDb.hpp b/libi2pd/NetDb.hpp index f9a57ccd..44dff3b8 100644 --- a/libi2pd/NetDb.hpp +++ b/libi2pd/NetDb.hpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -12,7 +12,6 @@ #include #include #include -#include #include #include #include @@ -30,18 +29,24 @@ #include "NetDbRequests.h" #include "Family.h" #include "version.h" +#include "util.h" +#include "KadDHT.h" namespace i2p { namespace data { const int NETDB_MIN_ROUTERS = 90; + const int NETDB_MIN_FLOODFILLS = 5; + const int NETDB_NUM_FLOODFILLS_THRESHOLD = 1500; const int NETDB_FLOODFILL_EXPIRATION_TIMEOUT = 60 * 60; // 1 hour, in seconds - const int NETDB_INTRODUCEE_EXPIRATION_TIMEOUT = 65 * 60; const int NETDB_MIN_EXPIRATION_TIMEOUT = 90 * 60; // 1.5 hours const int NETDB_MAX_EXPIRATION_TIMEOUT = 27 * 60 * 60; // 27 hours - const int NETDB_PUBLISH_INTERVAL = 60 * 40; - const int NETDB_MIN_HIGHBANDWIDTH_VERSION = MAKE_VERSION_NUMBER(0, 9, 36); // 0.9.36 + const int NETDB_MAX_OFFLINE_EXPIRATION_TIMEOUT = 180; // in days + const int NETDB_EXPIRATION_TIMEOUT_THRESHOLD = 2*60; // 2 minutes + const int NETDB_MIN_HIGHBANDWIDTH_VERSION = MAKE_VERSION_NUMBER(0, 9, 51); // 0.9.51 + const int NETDB_MIN_FLOODFILL_VERSION = MAKE_VERSION_NUMBER(0, 9, 51); // 0.9.51 + const int NETDB_MIN_SHORT_TUNNEL_BUILD_VERSION = MAKE_VERSION_NUMBER(0, 9, 51); // 0.9.51 /** function for visiting a leaseset stored in a floodfill */ typedef std::function)> LeaseSetVisitor; @@ -62,7 +67,7 @@ namespace data void Start (); void Stop (); - bool AddRouterInfo (const uint8_t * buf, int len); + std::shared_ptr AddRouterInfo (const uint8_t * buf, int len); bool AddRouterInfo (const IdentHash& ident, const uint8_t * buf, int len); bool AddLeaseSet (const IdentHash& ident, const uint8_t * buf, int len); bool AddLeaseSet2 (const IdentHash& ident, const uint8_t * buf, int len, uint8_t storeType); @@ -79,29 +84,26 @@ namespace data void HandleNTCP2RouterInfoMsg (std::shared_ptr m); std::shared_ptr GetRandomRouter () const; - std::shared_ptr GetRandomRouter (std::shared_ptr compatibleWith) const; - std::shared_ptr GetHighBandwidthRandomRouter (std::shared_ptr compatibleWith) const; - std::shared_ptr GetRandomPeerTestRouter (bool v4only = true) const; - std::shared_ptr GetRandomSSUV6Router () const; // TODO: change to v6 peer test later - std::shared_ptr GetRandomIntroducer () const; - std::shared_ptr GetClosestFloodfill (const IdentHash& destination, const std::set& excluded, bool closeThanUsOnly = false) const; + std::shared_ptr GetRandomRouter (std::shared_ptr compatibleWith, bool reverse, bool endpoint) const; + std::shared_ptr GetHighBandwidthRandomRouter (std::shared_ptr compatibleWith, bool reverse, bool endpoint) const; + std::shared_ptr GetRandomSSU2PeerTestRouter (bool v4, const std::set& excluded) const; + std::shared_ptr GetRandomSSU2Introducer (bool v4, const std::set& excluded) const; + std::shared_ptr GetClosestFloodfill (const IdentHash& destination, const std::set& excluded) const; std::vector GetClosestFloodfills (const IdentHash& destination, size_t num, std::set& excluded, bool closeThanUsOnly = false) const; std::shared_ptr GetClosestNonFloodfill (const IdentHash& destination, const std::set& excluded) const; - std::shared_ptr GetRandomRouterInFamily(const std::string & fam) const; + std::shared_ptr GetRandomRouterInFamily (FamilyID fam) const; void SetUnreachable (const IdentHash& ident, bool unreachable); + void ExcludeReachableTransports (const IdentHash& ident, RouterInfo::CompatibleTransports transports); void PostI2NPMsg (std::shared_ptr msg); - /** set hidden mode, aka don't publish our RI to netdb and don't explore */ - void SetHidden(bool hide); - void Reseed (); Families& GetFamilies () { return m_Families; }; // for web interface int GetNumRouters () const { return m_RouterInfos.size (); }; - int GetNumFloodfills () const { return m_Floodfills.size (); }; + int GetNumFloodfills () const { return m_Floodfills.GetSize (); }; int GetNumLeaseSets () const { return m_LeaseSets.size (); }; /** visit all lease sets we currently store */ @@ -114,16 +116,31 @@ namespace data size_t VisitRandomRouterInfos(RouterInfoFilter f, RouterInfoVisitor v, size_t n); void ClearRouterInfos () { m_RouterInfos.clear (); }; + std::shared_ptr NewRouterInfoBuffer () { return m_RouterInfoBuffersPool.AcquireSharedMt (); }; + bool PopulateRouterInfoBuffer (std::shared_ptr r); + std::shared_ptr NewRouterInfoAddress () { return m_RouterInfoAddressesPool.AcquireSharedMt (); }; + boost::shared_ptr NewRouterInfoAddresses () + { + return boost::shared_ptr(m_RouterInfoAddressVectorsPool.AcquireMt (), + std::bind ::*)(RouterInfo::Addresses *)> + (&i2p::util::MemoryPoolMt::ReleaseMt, + &m_RouterInfoAddressVectorsPool, std::placeholders::_1)); + }; + std::shared_ptr NewLease (const Lease& lease) { return m_LeasesPool.AcquireSharedMt (lease); }; + std::shared_ptr NewIdentity (const uint8_t * buf, size_t len) { return m_IdentitiesPool.AcquireSharedMt (buf, len); }; + std::shared_ptr NewRouterProfile () { return m_RouterProfilesPool.AcquireSharedMt (); }; + + uint32_t GetPublishReplyToken () const { return m_PublishReplyToken; }; private: void Load (); - bool LoadRouterInfo (const std::string & path); + bool LoadRouterInfo (const std::string& path, uint64_t ts); void SaveUpdated (); void Run (); // exploratory thread void Explore (int numDestinations); - void Publish (); void Flood (const IdentHash& ident, std::shared_ptr floodMsg); + void ManageRouterInfos (); void ManageLeaseSets (); void ManageRequests (); @@ -142,10 +159,9 @@ namespace data mutable std::mutex m_RouterInfosMutex; std::unordered_map > m_RouterInfos; mutable std::mutex m_FloodfillsMutex; - std::list > m_Floodfills; + DHTTable m_Floodfills; bool m_IsRunning; - uint64_t m_LastLoad; std::thread * m_Thread; i2p::util::Queue > m_Queue; // of I2NPDatabaseStoreMsg @@ -162,9 +178,15 @@ namespace data /** router info we are bootstrapping from or nullptr if we are not currently doing that*/ std::shared_ptr m_FloodfillBootstrap; + std::set m_PublishExcluded; + uint32_t m_PublishReplyToken = 0; - /** true if in hidden mode */ - bool m_HiddenMode; + i2p::util::MemoryPoolMt m_RouterInfoBuffersPool; + i2p::util::MemoryPoolMt m_RouterInfoAddressesPool; + i2p::util::MemoryPoolMt m_RouterInfoAddressVectorsPool; + i2p::util::MemoryPoolMt m_LeasesPool; + i2p::util::MemoryPoolMt m_IdentitiesPool; + i2p::util::MemoryPoolMt m_RouterProfilesPool; }; extern NetDb netdb; diff --git a/libi2pd/NetDbRequests.cpp b/libi2pd/NetDbRequests.cpp index e7aab34c..4011b8aa 100644 --- a/libi2pd/NetDbRequests.cpp +++ b/libi2pd/NetDbRequests.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -137,7 +137,7 @@ namespace data auto inbound = pool->GetNextInboundTunnel (); auto nextFloodfill = netdb.GetClosestFloodfill (dest->GetDestination (), dest->GetExcludedPeers ()); if (nextFloodfill && outbound && inbound) - outbound->SendTunnelDataMsg (nextFloodfill->GetIdentHash (), 0, + outbound->SendTunnelDataMsgTo (nextFloodfill->GetIdentHash (), 0, dest->CreateRequestMessage (nextFloodfill, inbound)); else { diff --git a/libi2pd/NetDbRequests.h b/libi2pd/NetDbRequests.h index 16ea430d..cf2f0915 100644 --- a/libi2pd/NetDbRequests.h +++ b/libi2pd/NetDbRequests.h @@ -60,7 +60,7 @@ namespace data void Start (); void Stop (); - std::shared_ptr CreateRequest (const IdentHash& destination, bool isExploratory, RequestedDestination::RequestComplete requestComplete = nullptr); + std::shared_ptr CreateRequest (const IdentHash& destination, bool isExploratory, RequestedDestination::RequestComplete requestComplete = nullptr); void RequestComplete (const IdentHash& ident, std::shared_ptr r); std::shared_ptr FindRequest (const IdentHash& ident) const; void ManageRequests (); diff --git a/libi2pd/Poly1305.cpp b/libi2pd/Poly1305.cpp index 23098d74..20b3ab2a 100644 --- a/libi2pd/Poly1305.cpp +++ b/libi2pd/Poly1305.cpp @@ -1,12 +1,13 @@ -#include "Poly1305.h" /** - This code is licensed under the MCGSI Public License - Copyright 2018 Jeff Becker - - Kovri go write your own code - + * This code is licensed under the MCGSI Public License + * Copyright 2018 Jeff Becker + * + *Kovri go write your own code + * */ +#include "Poly1305.h" + #if !OPENSSL_AEAD_CHACHA20_POLY1305 namespace i2p { diff --git a/libi2pd/Poly1305.h b/libi2pd/Poly1305.h index f91a037e..db659b84 100644 --- a/libi2pd/Poly1305.h +++ b/libi2pd/Poly1305.h @@ -5,6 +5,7 @@ * Kovri go write your own code * */ + #ifndef LIBI2PD_POLY1305_H #define LIBI2PD_POLY1305_H #include diff --git a/libi2pd/Profiling.cpp b/libi2pd/Profiling.cpp index 850774d9..2031fa39 100644 --- a/libi2pd/Profiling.cpp +++ b/libi2pd/Profiling.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -7,34 +7,43 @@ */ #include +#include +#include +#include #include #include #include "Base.h" #include "FS.h" #include "Log.h" +#include "Timestamp.h" +#include "NetDb.hpp" #include "Profiling.h" namespace i2p { namespace data { - i2p::fs::HashedStorage m_ProfilesStorage("peerProfiles", "p", "profile-", "txt"); + static i2p::fs::HashedStorage g_ProfilesStorage("peerProfiles", "p", "profile-", "txt"); + static std::unordered_map > g_Profiles; + static std::mutex g_ProfilesMutex; - RouterProfile::RouterProfile (): - m_LastUpdateTime (boost::posix_time::second_clock::local_time()), - m_NumTunnelsAgreed (0), m_NumTunnelsDeclined (0), m_NumTunnelsNonReplied (0), - m_NumTimesTaken (0), m_NumTimesRejected (0) - { - } - - boost::posix_time::ptime RouterProfile::GetTime () const + static boost::posix_time::ptime GetTime () { return boost::posix_time::second_clock::local_time(); } + RouterProfile::RouterProfile (): + m_LastUpdateTime (GetTime ()), m_IsUpdated (false), + m_LastDeclineTime (0), m_LastUnreachableTime (0), + m_NumTunnelsAgreed (0), m_NumTunnelsDeclined (0), m_NumTunnelsNonReplied (0), + m_NumTimesTaken (0), m_NumTimesRejected (0), m_HasConnected (false) + { + } + void RouterProfile::UpdateTime () { m_LastUpdateTime = GetTime (); + m_IsUpdated = true; } void RouterProfile::Save (const IdentHash& identHash) @@ -47,15 +56,18 @@ namespace data boost::property_tree::ptree usage; usage.put (PEER_PROFILE_USAGE_TAKEN, m_NumTimesTaken); usage.put (PEER_PROFILE_USAGE_REJECTED, m_NumTimesRejected); + usage.put (PEER_PROFILE_USAGE_CONNECTED, m_HasConnected); // fill property tree boost::property_tree::ptree pt; pt.put (PEER_PROFILE_LAST_UPDATE_TIME, boost::posix_time::to_simple_string (m_LastUpdateTime)); + if (m_LastUnreachableTime) + pt.put (PEER_PROFILE_LAST_UNREACHABLE_TIME, m_LastUnreachableTime); pt.put_child (PEER_PROFILE_SECTION_PARTICIPATION, participation); pt.put_child (PEER_PROFILE_SECTION_USAGE, usage); // save to file std::string ident = identHash.ToBase64 (); - std::string path = m_ProfilesStorage.Path(ident); + std::string path = g_ProfilesStorage.Path(ident); try { boost::property_tree::write_ini (path, pt); @@ -68,12 +80,12 @@ namespace data void RouterProfile::Load (const IdentHash& identHash) { std::string ident = identHash.ToBase64 (); - std::string path = m_ProfilesStorage.Path(ident); + std::string path = g_ProfilesStorage.Path(ident); boost::property_tree::ptree pt; if (!i2p::fs::Exists(path)) { - LogPrint(eLogWarning, "Profiling: no profile yet for ", ident); + LogPrint(eLogWarning, "Profiling: No profile yet for ", ident); return; } @@ -94,6 +106,7 @@ namespace data m_LastUpdateTime = boost::posix_time::time_from_string (t); if ((GetTime () - m_LastUpdateTime).hours () < PEER_PROFILE_EXPIRATION_TIMEOUT) { + m_LastUnreachableTime = pt.get (PEER_PROFILE_LAST_UNREACHABLE_TIME, 0); try { // read participations @@ -112,10 +125,11 @@ namespace data auto usage = pt.get_child (PEER_PROFILE_SECTION_USAGE); m_NumTimesTaken = usage.get (PEER_PROFILE_USAGE_TAKEN, 0); m_NumTimesRejected = usage.get (PEER_PROFILE_USAGE_REJECTED, 0); + m_HasConnected = usage.get (PEER_PROFILE_USAGE_CONNECTED, false); } catch (boost::property_tree::ptree_bad_path& ex) { - LogPrint (eLogWarning, "Missing section ", PEER_PROFILE_SECTION_USAGE, " in profile for ", ident); + LogPrint (eLogWarning, "Profiling: Missing section ", PEER_PROFILE_SECTION_USAGE, " in profile for ", ident); } } else @@ -131,14 +145,36 @@ namespace data { UpdateTime (); if (ret > 0) + { m_NumTunnelsDeclined++; + m_LastDeclineTime = i2p::util::GetSecondsSinceEpoch (); + } else - m_NumTunnelsAgreed++; + { + m_NumTunnelsAgreed++; + m_LastDeclineTime = 0; + } } void RouterProfile::TunnelNonReplied () { - m_NumTunnelsNonReplied++; + m_NumTunnelsNonReplied++; + UpdateTime (); + if (m_NumTunnelsNonReplied > 2*m_NumTunnelsAgreed && m_NumTunnelsNonReplied > 3) + { + m_LastDeclineTime = i2p::util::GetSecondsSinceEpoch (); + } + } + + void RouterProfile::Unreachable (bool unreachable) + { + m_LastUnreachableTime = unreachable ? i2p::util::GetSecondsSinceEpoch () : 0; + UpdateTime (); + } + + void RouterProfile::Connected () + { + m_HasConnected = true; UpdateTime (); } @@ -153,8 +189,19 @@ namespace data return m_NumTunnelsNonReplied > 10*(total + 1); } + bool RouterProfile::IsDeclinedRecently () + { + if (!m_LastDeclineTime) return false; + auto ts = i2p::util::GetSecondsSinceEpoch (); + if (ts > m_LastDeclineTime + PEER_PROFILE_DECLINED_RECENTLY_INTERVAL || + ts + PEER_PROFILE_DECLINED_RECENTLY_INTERVAL < m_LastDeclineTime) + m_LastDeclineTime = 0; + return (bool)m_LastDeclineTime; + } + bool RouterProfile::IsBad () { + if (IsDeclinedRecently () || IsUnreachable ()) return true; auto isBad = IsAlwaysDeclining () || IsLowPartcipationRate () /*|| IsLowReplyRate ()*/; if (isBad && m_NumTimesRejected > 10*(m_NumTimesTaken + 1)) { @@ -168,33 +215,104 @@ namespace data return isBad; } + bool RouterProfile::IsUnreachable () + { + if (!m_LastUnreachableTime) return false; + auto ts = i2p::util::GetSecondsSinceEpoch (); + if (ts > m_LastUnreachableTime + PEER_PROFILE_UNREACHABLE_INTERVAL || + ts + PEER_PROFILE_UNREACHABLE_INTERVAL < m_LastUnreachableTime) + m_LastUnreachableTime = 0; + return (bool)m_LastUnreachableTime; + } + + bool RouterProfile::IsUseful() const + { + return IsReal () || m_NumTunnelsNonReplied >= PEER_PROFILE_USEFUL_THRESHOLD; + } + std::shared_ptr GetRouterProfile (const IdentHash& identHash) { - auto profile = std::make_shared (); + { + std::unique_lock l(g_ProfilesMutex); + auto it = g_Profiles.find (identHash); + if (it != g_Profiles.end ()) + return it->second; + } + auto profile = netdb.NewRouterProfile (); profile->Load (identHash); // if possible + std::unique_lock l(g_ProfilesMutex); + g_Profiles.emplace (identHash, profile); return profile; } void InitProfilesStorage () { - m_ProfilesStorage.SetPlace(i2p::fs::GetDataDir()); - m_ProfilesStorage.Init(i2p::data::GetBase64SubstitutionTable(), 64); + g_ProfilesStorage.SetPlace(i2p::fs::GetDataDir()); + g_ProfilesStorage.Init(i2p::data::GetBase64SubstitutionTable(), 64); + } + + void PersistProfiles () + { + auto ts = GetTime (); + std::list > > tmp; + { + std::unique_lock l(g_ProfilesMutex); + for (auto it = g_Profiles.begin (); it != g_Profiles.end ();) + { + if ((ts - it->second->GetLastUpdateTime ()).total_seconds () > PEER_PROFILE_PERSIST_INTERVAL) + { + if (it->second->IsUpdated ()) + tmp.push_back (std::make_pair (it->first, it->second)); + it = g_Profiles.erase (it); + } + else + it++; + } + } + for (auto& it: tmp) + if (it.second) it.second->Save (it.first); + } + + void SaveProfiles () + { + std::unordered_map > tmp; + { + std::unique_lock l(g_ProfilesMutex); + tmp = g_Profiles; + g_Profiles.clear (); + } + auto ts = GetTime (); + for (auto& it: tmp) + if (it.second->IsUseful() && (it.second->IsUpdated () || (ts - it.second->GetLastUpdateTime ()).total_seconds () < PEER_PROFILE_EXPIRATION_TIMEOUT*3600)) + it.second->Save (it.first); } void DeleteObsoleteProfiles () { + { + auto ts = GetTime (); + std::unique_lock l(g_ProfilesMutex); + for (auto it = g_Profiles.begin (); it != g_Profiles.end ();) + { + if ((ts - it->second->GetLastUpdateTime ()).total_seconds () >= PEER_PROFILE_EXPIRATION_TIMEOUT*3600) + it = g_Profiles.erase (it); + else + it++; + } + } + struct stat st; std::time_t now = std::time(nullptr); std::vector files; - m_ProfilesStorage.Traverse(files); + g_ProfilesStorage.Traverse(files); for (const auto& path: files) { if (stat(path.c_str(), &st) != 0) { LogPrint(eLogWarning, "Profiling: Can't stat(): ", path); continue; } - if (((now - st.st_mtime) / 3600) >= PEER_PROFILE_EXPIRATION_TIMEOUT) { - LogPrint(eLogDebug, "Profiling: removing expired peer profile: ", path); + if (now - st.st_mtime >= PEER_PROFILE_EXPIRATION_TIMEOUT*3600) { + LogPrint(eLogDebug, "Profiling: Removing expired peer profile: ", path); i2p::fs::Remove(path); } } diff --git a/libi2pd/Profiling.h b/libi2pd/Profiling.h index dab50e6b..6531e060 100644 --- a/libi2pd/Profiling.h +++ b/libi2pd/Profiling.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -22,41 +22,60 @@ namespace data const char PEER_PROFILE_SECTION_USAGE[] = "usage"; // params const char PEER_PROFILE_LAST_UPDATE_TIME[] = "lastupdatetime"; + const char PEER_PROFILE_LAST_UNREACHABLE_TIME[] = "lastunreachabletime"; const char PEER_PROFILE_PARTICIPATION_AGREED[] = "agreed"; const char PEER_PROFILE_PARTICIPATION_DECLINED[] = "declined"; const char PEER_PROFILE_PARTICIPATION_NON_REPLIED[] = "nonreplied"; const char PEER_PROFILE_USAGE_TAKEN[] = "taken"; const char PEER_PROFILE_USAGE_REJECTED[] = "rejected"; - - const int PEER_PROFILE_EXPIRATION_TIMEOUT = 72; // in hours (3 days) + const char PEER_PROFILE_USAGE_CONNECTED[] = "connected"; + + const int PEER_PROFILE_EXPIRATION_TIMEOUT = 36; // in hours (1.5 days) + const int PEER_PROFILE_AUTOCLEAN_TIMEOUT = 3 * 3600; // in seconds (3 hours) + const int PEER_PROFILE_AUTOCLEAN_VARIANCE = 3600; // in seconds (1 hour) + const int PEER_PROFILE_DECLINED_RECENTLY_INTERVAL = 150; // in seconds (2.5 minutes) + const int PEER_PROFILE_PERSIST_INTERVAL = 3300; // in seconds (55 minutes) + const int PEER_PROFILE_UNREACHABLE_INTERVAL = 2*3600; // on seconds (2 hours) + const int PEER_PROFILE_USEFUL_THRESHOLD = 3; class RouterProfile { public: RouterProfile (); - RouterProfile& operator= (const RouterProfile& ) = default; void Save (const IdentHash& identHash); void Load (const IdentHash& identHash); bool IsBad (); + bool IsUnreachable (); + bool IsReal () const { return m_HasConnected || m_NumTunnelsAgreed > 0 || m_NumTunnelsDeclined > 0; } void TunnelBuildResponse (uint8_t ret); void TunnelNonReplied (); + void Unreachable (bool unreachable); + void Connected (); + + boost::posix_time::ptime GetLastUpdateTime () const { return m_LastUpdateTime; }; + bool IsUpdated () const { return m_IsUpdated; }; + + bool IsUseful() const; + private: - boost::posix_time::ptime GetTime () const; void UpdateTime (); bool IsAlwaysDeclining () const { return !m_NumTunnelsAgreed && m_NumTunnelsDeclined >= 5; }; bool IsLowPartcipationRate () const; bool IsLowReplyRate () const; + bool IsDeclinedRecently (); private: - boost::posix_time::ptime m_LastUpdateTime; + boost::posix_time::ptime m_LastUpdateTime; // TODO: use std::chrono + bool m_IsUpdated; + uint64_t m_LastDeclineTime, m_LastUnreachableTime; // in seconds // participation uint32_t m_NumTunnelsAgreed; uint32_t m_NumTunnelsDeclined; @@ -64,11 +83,14 @@ namespace data // usage uint32_t m_NumTimesTaken; uint32_t m_NumTimesRejected; + bool m_HasConnected; // successful trusted(incoming or NTCP2) connection }; std::shared_ptr GetRouterProfile (const IdentHash& identHash); void InitProfilesStorage (); void DeleteObsoleteProfiles (); + void SaveProfiles (); + void PersistProfiles (); } } diff --git a/libi2pd/Queue.h b/libi2pd/Queue.h index d43567a5..441f8c3a 100644 --- a/libi2pd/Queue.h +++ b/libi2pd/Queue.h @@ -28,7 +28,7 @@ namespace util void Put (Element e) { - std::unique_lock l(m_QueueMutex); + std::unique_lock l(m_QueueMutex); m_Queue.push (std::move(e)); m_NonEmpty.notify_one (); } @@ -38,7 +38,7 @@ namespace util { if (!vec.empty ()) { - std::unique_lock l(m_QueueMutex); + std::unique_lock l(m_QueueMutex); for (const auto& it: vec) m_Queue.push (std::move(it)); m_NonEmpty.notify_one (); diff --git a/libi2pd/Reseed.cpp b/libi2pd/Reseed.cpp index 2812f413..28e4db24 100644 --- a/libi2pd/Reseed.cpp +++ b/libi2pd/Reseed.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -60,19 +60,19 @@ namespace data num = ProcessSU3File (su3FileName.c_str ()); } if (num == 0) - LogPrint (eLogWarning, "Reseed: failed to reseed from ", su3FileName); + LogPrint (eLogWarning, "Reseed: Failed to reseed from ", su3FileName); } else if (zipFileName.length() > 0) // bootstrap from ZIP file { int num = ProcessZIPFile (zipFileName.c_str ()); if (num == 0) - LogPrint (eLogWarning, "Reseed: failed to reseed from ", zipFileName); + LogPrint (eLogWarning, "Reseed: Failed to reseed from ", zipFileName); } else // bootstrap from reseed servers { int num = ReseedFromServers (); if (num == 0) - LogPrint (eLogWarning, "Reseed: failed to reseed from servers"); + LogPrint (eLogWarning, "Reseed: Failed to reseed from servers"); } } @@ -82,11 +82,28 @@ namespace data */ int Reseeder::ReseedFromServers () { - std::string reseedURLs; i2p::config::GetOption("reseed.urls", reseedURLs); - std::vector httpsReseedHostList; - boost::split(httpsReseedHostList, reseedURLs, boost::is_any_of(","), boost::token_compress_on); + bool ipv6; i2p::config::GetOption("ipv6", ipv6); + bool ipv4; i2p::config::GetOption("ipv4", ipv4); + bool yggdrasil; i2p::config::GetOption("meshnets.yggdrasil", yggdrasil); - if (reseedURLs.length () == 0) + std::vector httpsReseedHostList; + if (ipv4 || ipv6) + { + std::string reseedURLs; i2p::config::GetOption("reseed.urls", reseedURLs); + if (!reseedURLs.empty ()) + boost::split(httpsReseedHostList, reseedURLs, boost::is_any_of(","), boost::token_compress_on); + } + + std::vector yggReseedHostList; + if (yggdrasil && !i2p::util::net::GetYggdrasilAddress ().is_unspecified ()) + { + LogPrint (eLogInfo, "Reseed: Yggdrasil is supported"); + std::string yggReseedURLs; i2p::config::GetOption("reseed.yggurls", yggReseedURLs); + if (!yggReseedURLs.empty ()) + boost::split(yggReseedHostList, yggReseedURLs, boost::is_any_of(","), boost::token_compress_on); + } + + if (httpsReseedHostList.empty () && yggReseedHostList.empty()) { LogPrint (eLogWarning, "Reseed: No reseed servers specified"); return 0; @@ -95,13 +112,16 @@ namespace data int reseedRetries = 0; while (reseedRetries < 10) { - auto ind = rand () % httpsReseedHostList.size (); - std::string reseedUrl = httpsReseedHostList[ind] + "i2pseeds.su3"; - auto num = ReseedFromSU3Url (reseedUrl); + auto ind = rand () % (httpsReseedHostList.size () + yggReseedHostList.size ()); + bool isHttps = ind < httpsReseedHostList.size (); + std::string reseedUrl = isHttps ? httpsReseedHostList[ind] : + yggReseedHostList[ind - httpsReseedHostList.size ()]; + reseedUrl += "i2pseeds.su3"; + auto num = ReseedFromSU3Url (reseedUrl, isHttps); if (num > 0) return num; // success reseedRetries++; } - LogPrint (eLogWarning, "Reseed: failed to reseed from servers after 10 attempts"); + LogPrint (eLogWarning, "Reseed: Failed to reseed from servers after 10 attempts"); return 0; } @@ -110,10 +130,10 @@ namespace data * @param url * @return number of entries added to netDb */ - int Reseeder::ReseedFromSU3Url (const std::string& url) + int Reseeder::ReseedFromSU3Url (const std::string& url, bool isHttps) { LogPrint (eLogInfo, "Reseed: Downloading SU3 from ", url); - std::string su3 = HttpsRequest (url); + std::string su3 = isHttps ? HttpsRequest (url) : YggdrasilRequest (url); if (su3.length () > 0) { std::stringstream s(su3); @@ -133,7 +153,7 @@ namespace data return ProcessSU3Stream (s); else { - LogPrint (eLogError, "Reseed: Can't open file ", filename); + LogPrint (eLogCritical, "Reseed: Can't open file ", filename); return 0; } } @@ -150,7 +170,7 @@ namespace data } else { - LogPrint (eLogError, "Reseed: Can't open file ", filename); + LogPrint (eLogCritical, "Reseed: Can't open file ", filename); return 0; } } @@ -167,31 +187,31 @@ namespace data } s.seekg (1, std::ios::cur); // su3 file format version SigningKeyType signatureType; - s.read ((char *)&signatureType, 2); // signature type + s.read ((char *)&signatureType, 2); // signature type signatureType = be16toh (signatureType); uint16_t signatureLength; - s.read ((char *)&signatureLength, 2); // signature length + s.read ((char *)&signatureLength, 2); // signature length signatureLength = be16toh (signatureLength); s.seekg (1, std::ios::cur); // unused uint8_t versionLength; - s.read ((char *)&versionLength, 1); // version length + s.read ((char *)&versionLength, 1); // version length s.seekg (1, std::ios::cur); // unused uint8_t signerIDLength; - s.read ((char *)&signerIDLength, 1); // signer ID length + s.read ((char *)&signerIDLength, 1); // signer ID length uint64_t contentLength; - s.read ((char *)&contentLength, 8); // content length + s.read ((char *)&contentLength, 8); // content length contentLength = be64toh (contentLength); s.seekg (1, std::ios::cur); // unused uint8_t fileType; - s.read ((char *)&fileType, 1); // file type - if (fileType != 0x00) // zip file + s.read ((char *)&fileType, 1); // file type + if (fileType != 0x00) // zip file { LogPrint (eLogError, "Reseed: Can't handle file type ", (int)fileType); return 0; } s.seekg (1, std::ios::cur); // unused uint8_t contentType; - s.read ((char *)&contentType, 1); // content type + s.read ((char *)&contentType, 1); // content type if (contentType != 0x03) // reseed data { LogPrint (eLogError, "Reseed: Unexpected content type ", (int)contentType); @@ -258,7 +278,7 @@ namespace data if (verify) // not verified { - LogPrint (eLogError, "Reseed: SU3 verification failed"); + LogPrint (eLogCritical, "Reseed: SU3 verification failed"); return 0; } @@ -300,7 +320,7 @@ namespace data uint16_t fileNameLength, extraFieldLength; s.read ((char *)&fileNameLength, 2); fileNameLength = le16toh (fileNameLength); - if ( fileNameLength > 255 ) { + if ( fileNameLength >= 255 ) { // too big LogPrint(eLogError, "Reseed: SU3 fileNameLength too large: ", fileNameLength); return numFiles; @@ -395,13 +415,13 @@ namespace data { if (r && ts > r->GetTimestamp () + 10*i2p::data::NETDB_MAX_EXPIRATION_TIMEOUT*1000LL) // 270 hours { - LogPrint (eLogError, "Reseed: router ", r->GetIdentHash().ToBase64 (), " is outdated by ", (ts - r->GetTimestamp ())/1000LL/3600LL, " hours"); + LogPrint (eLogError, "Reseed: Router ", r->GetIdentHash().ToBase64 (), " is outdated by ", (ts - r->GetTimestamp ())/1000LL/3600LL, " hours"); numOutdated++; } }); if (numOutdated > numFiles/2) // more than half { - LogPrint (eLogError, "Reseed: mammoth's shit\n" + LogPrint (eLogError, "Reseed: Mammoth's shit\n" " *_____*\n" " *_*****_*\n" " *_(O)_(O)_*\n" @@ -459,7 +479,7 @@ namespace data if (terminator) terminator[0] = 0; } // extract RSA key (we need n only, e = 65537) - RSA * key = EVP_PKEY_get0_RSA (X509_get_pubkey (cert)); + const RSA * key = EVP_PKEY_get0_RSA (X509_get_pubkey (cert)); const BIGNUM * n, * e, * d; RSA_get0_key(key, &n, &e, &d); PublicKey value; @@ -472,13 +492,14 @@ namespace data SSL_free (ssl); } else - LogPrint (eLogError, "Reseed: Can't open certificate file ", filename); + LogPrint (eLogCritical, "Reseed: Can't open certificate file ", filename); SSL_CTX_free (ctx); } void Reseeder::LoadCertificates () { - std::string certDir = i2p::fs::DataDirPath("certificates", "reseed"); + std::string certDir = i2p::fs::GetCertsDir() + i2p::fs::dirSep + "reseed"; + std::vector files; int numCertificates = 0; @@ -489,7 +510,7 @@ namespace data for (const std::string & file : files) { if (file.compare(file.size() - 4, 4, ".crt") != 0) { - LogPrint(eLogWarning, "Reseed: ignoring file ", file); + LogPrint(eLogWarning, "Reseed: Ignoring file ", file); continue; } LoadCertificate (file); @@ -513,17 +534,17 @@ namespace data } // check for valid proxy url schema if (proxyUrl.schema != "http" && proxyUrl.schema != "socks") { - LogPrint(eLogError, "Reseed: bad proxy url: ", proxy); + LogPrint(eLogCritical, "Reseed: Bad proxy url: ", proxy); return ""; } } else { - LogPrint(eLogError, "Reseed: bad proxy url: ", proxy); + LogPrint(eLogCritical, "Reseed: Bad proxy url: ", proxy); return ""; } } i2p::http::URL url; if (!url.parse(address)) { - LogPrint(eLogError, "Reseed: failed to parse url: ", address); + LogPrint(eLogCritical, "Reseed: Failed to parse url: ", address); return ""; } url.schema = "https"; @@ -555,9 +576,11 @@ namespace data proxyReq.method = "CONNECT"; proxyReq.version = "HTTP/1.1"; proxyReq.uri = url.host + ":" + std::to_string(url.port); + auto auth = i2p::http::CreateBasicAuthorizationString (proxyUrl.user, proxyUrl.pass); + if (!auth.empty ()) + proxyReq.AddHeader("Proxy-Authorization", auth); boost::asio::streambuf writebuf, readbuf; - std::ostream out(&writebuf); out << proxyReq.to_string(); @@ -657,8 +680,42 @@ namespace data // direct connection auto it = boost::asio::ip::tcp::resolver(service).resolve ( boost::asio::ip::tcp::resolver::query (url.host, std::to_string(url.port)), ecode); - if(!ecode) - s.lowest_layer().connect (*it, ecode); + if (!ecode) + { + bool connected = false; + boost::asio::ip::tcp::resolver::iterator end; + while (it != end) + { + boost::asio::ip::tcp::endpoint ep = *it; + if ( + ( + !i2p::util::net::IsInReservedRange(ep.address ()) && ( + (ep.address ().is_v4 () && i2p::context.SupportsV4 ()) || + (ep.address ().is_v6 () && i2p::context.SupportsV6 ()) + ) + ) || + ( + i2p::util::net::IsYggdrasilAddress (ep.address ()) && + i2p::context.SupportsMesh () + ) + ) + { + s.lowest_layer().connect (ep, ecode); + if (!ecode) + { + LogPrint (eLogDebug, "Reseed: Resolved to ", ep.address ()); + connected = true; + break; + } + } + it++; + } + if (!connected) + { + LogPrint(eLogError, "Reseed: Failed to connect to ", url.host); + return ""; + } + } } if (!ecode) { @@ -667,42 +724,7 @@ namespace data if (!ecode) { LogPrint (eLogDebug, "Reseed: Connected to ", url.host, ":", url.port); - i2p::http::HTTPReq req; - req.uri = url.to_string(); - req.AddHeader("User-Agent", "Wget/1.11.4"); - req.AddHeader("Connection", "close"); - s.write_some (boost::asio::buffer (req.to_string())); - // read response - std::stringstream rs; - char recv_buf[1024]; size_t l = 0; - do { - l = s.read_some (boost::asio::buffer (recv_buf, sizeof(recv_buf)), ecode); - if (l) rs.write (recv_buf, l); - } while (!ecode && l); - // process response - std::string data = rs.str(); - i2p::http::HTTPRes res; - int len = res.parse(data); - if (len <= 0) { - LogPrint(eLogWarning, "Reseed: incomplete/broken response from ", url.host); - return ""; - } - if (res.code != 200) { - LogPrint(eLogError, "Reseed: failed to reseed from ", url.host, ", http code ", res.code); - return ""; - } - data.erase(0, len); /* drop http headers from response */ - LogPrint(eLogDebug, "Reseed: got ", data.length(), " bytes of data from ", url.host); - if (res.is_chunked()) { - std::stringstream in(data), out; - if (!i2p::http::MergeChunkedResponse(in, out)) { - LogPrint(eLogWarning, "Reseed: failed to merge chunked response from ", url.host); - return ""; - } - LogPrint(eLogDebug, "Reseed: got ", data.length(), "(", out.tellg(), ") bytes of data from ", url.host); - data = out.str(); - } - return data; + return ReseedRequest (s, url.to_string()); } else LogPrint (eLogError, "Reseed: SSL handshake failed: ", ecode.message ()); @@ -711,5 +733,105 @@ namespace data LogPrint (eLogError, "Reseed: Couldn't connect to ", url.host, ": ", ecode.message ()); return ""; } + + template + std::string Reseeder::ReseedRequest (Stream& s, const std::string& uri) + { + boost::system::error_code ecode; + i2p::http::HTTPReq req; + req.uri = uri; + req.AddHeader("User-Agent", "Wget/1.11.4"); + req.AddHeader("Connection", "close"); + s.write_some (boost::asio::buffer (req.to_string())); + // read response + std::stringstream rs; + char recv_buf[1024]; size_t l = 0; + do { + l = s.read_some (boost::asio::buffer (recv_buf, sizeof(recv_buf)), ecode); + if (l) rs.write (recv_buf, l); + } while (!ecode && l); + // process response + std::string data = rs.str(); + i2p::http::HTTPRes res; + int len = res.parse(data); + if (len <= 0) { + LogPrint(eLogWarning, "Reseed: Incomplete/broken response from ", uri); + return ""; + } + if (res.code != 200) { + LogPrint(eLogError, "Reseed: Failed to reseed from ", uri, ", http code ", res.code); + return ""; + } + data.erase(0, len); /* drop http headers from response */ + LogPrint(eLogDebug, "Reseed: Got ", data.length(), " bytes of data from ", uri); + if (res.is_chunked()) { + std::stringstream in(data), out; + if (!i2p::http::MergeChunkedResponse(in, out)) { + LogPrint(eLogWarning, "Reseed: Failed to merge chunked response from ", uri); + return ""; + } + LogPrint(eLogDebug, "Reseed: Got ", data.length(), "(", out.tellg(), ") bytes of data from ", uri); + data = out.str(); + } + return data; + } + + std::string Reseeder::YggdrasilRequest (const std::string& address) + { + i2p::http::URL url; + if (!url.parse(address)) + { + LogPrint(eLogError, "Reseed: Failed to parse url: ", address); + return ""; + } + url.schema = "http"; + if (!url.port) url.port = 80; + + boost::system::error_code ecode; + boost::asio::io_service service; + boost::asio::ip::tcp::socket s(service, boost::asio::ip::tcp::v6()); + + auto it = boost::asio::ip::tcp::resolver(service).resolve ( + boost::asio::ip::tcp::resolver::query (url.host, std::to_string(url.port)), ecode); + + if (!ecode) + { + bool connected = false; + boost::asio::ip::tcp::resolver::iterator end; + while (it != end) + { + boost::asio::ip::tcp::endpoint ep = *it; + if ( + i2p::util::net::IsYggdrasilAddress (ep.address ()) && + i2p::context.SupportsMesh () + ) + { + LogPrint (eLogDebug, "Reseed: Yggdrasil: Resolved to ", ep.address ()); + s.connect (ep, ecode); + if (!ecode) + { + connected = true; + break; + } + } + it++; + } + if (!connected) + { + LogPrint(eLogError, "Reseed: Yggdrasil: Failed to connect to ", url.host); + return ""; + } + } + + if (!ecode) + { + LogPrint (eLogDebug, "Reseed: Yggdrasil: Connected to ", url.host, ":", url.port); + return ReseedRequest (s, url.to_string()); + } + else + LogPrint (eLogError, "Reseed: Yggdrasil: Couldn't connect to ", url.host, ": ", ecode.message ()); + + return ""; + } } } diff --git a/libi2pd/Reseed.h b/libi2pd/Reseed.h index 345b45bf..a6de6fa4 100644 --- a/libi2pd/Reseed.h +++ b/libi2pd/Reseed.h @@ -31,7 +31,6 @@ namespace data ~Reseeder(); void Bootstrap (); int ReseedFromServers (); - int ReseedFromSU3Url (const std::string& url); int ProcessSU3File (const char * filename); int ProcessZIPFile (const char * filename); @@ -39,6 +38,7 @@ namespace data private: + int ReseedFromSU3Url (const std::string& url, bool isHttps = true); void LoadCertificate (const std::string& filename); int ProcessSU3Stream (std::istream& s); @@ -47,6 +47,9 @@ namespace data bool FindZipDataDescriptor (std::istream& s); std::string HttpsRequest (const std::string& address); + std::string YggdrasilRequest (const std::string& address); + template + std::string ReseedRequest (Stream& s, const std::string& uri); private: diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index 69e69d7c..fa3ba7bd 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -19,6 +19,9 @@ #include "version.h" #include "Log.h" #include "Family.h" +#include "ECIESX25519AEADRatchetSession.h" +#include "Transports.h" +#include "Tunnel.h" #include "RouterContext.h" namespace i2p @@ -27,8 +30,10 @@ namespace i2p RouterContext::RouterContext (): m_LastUpdateTime (0), m_AcceptsTunnels (true), m_IsFloodfill (false), - m_ShareRatio (100), m_Status (eRouterStatusOK), - m_Error (eRouterErrorNone), m_NetID (I2PD_NET_ID) + m_ShareRatio (100), m_Status (eRouterStatusUnknown), m_StatusV6 (eRouterStatusUnknown), + m_Error (eRouterErrorNone), m_ErrorV6 (eRouterErrorNone), + m_Testing (false), m_TestingV6 (false), m_NetID (I2PD_NET_ID), + m_PublishReplyToken (0), m_IsHiddenMode (false) { } @@ -40,90 +45,204 @@ namespace i2p if (!Load ()) CreateNewRouter (); m_Decryptor = m_Keys.CreateDecryptor (nullptr); + m_TunnelDecryptor = m_Keys.CreateDecryptor (nullptr); UpdateRouterInfo (); + i2p::crypto::InitNoiseNState (m_InitialNoiseState, GetIdentity ()->GetEncryptionPublicKey ()); + m_ECIESSession = std::make_shared(m_InitialNoiseState); } + void RouterContext::Start () + { + if (!m_Service) + { + m_Service.reset (new RouterService); + m_Service->Start (); + if (!m_IsHiddenMode) + { + m_PublishTimer.reset (new boost::asio::deadline_timer (m_Service->GetService ())); + ScheduleInitialPublish (); + m_CongestionUpdateTimer.reset (new boost::asio::deadline_timer (m_Service->GetService ())); + ScheduleCongestionUpdate (); + } + } + } + + void RouterContext::Stop () + { + if (m_Service) + { + if (m_PublishTimer) + m_PublishTimer->cancel (); + if (m_CongestionUpdateTimer) + m_CongestionUpdateTimer->cancel (); + m_Service->Stop (); + } + } + void RouterContext::CreateNewRouter () { - m_Keys = i2p::data::PrivateKeys::CreateRandomKeys (i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519); + m_Keys = i2p::data::PrivateKeys::CreateRandomKeys (i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519, + i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); SaveKeys (); NewRouterInfo (); } void RouterContext::NewRouterInfo () { - i2p::data::RouterInfo routerInfo; + i2p::data::LocalRouterInfo routerInfo; routerInfo.SetRouterIdentity (GetIdentity ()); uint16_t port; i2p::config::GetOption("port", port); - if (!port) + if (!port) port = SelectRandomPort (); + bool ipv4; i2p::config::GetOption("ipv4", ipv4); + bool ipv6; i2p::config::GetOption("ipv6", ipv6); + bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); + bool ssu2; i2p::config::GetOption("ssu2.enabled", ssu2); + bool ygg; i2p::config::GetOption("meshnets.yggdrasil", ygg); + bool nat; i2p::config::GetOption("nat", nat); + + if ((ntcp2 || ygg) && !m_NTCP2Keys) + NewNTCP2Keys (); + if (ssu2 && !m_SSU2Keys) + NewSSU2Keys (); + bool ntcp2Published = false; + if (ntcp2) { - port = rand () % (30777 - 9111) + 9111; // I2P network ports range - if (port == 9150) port = 9151; // Tor browser + i2p::config::GetOption("ntcp2.published", ntcp2Published); + if (ntcp2Published) + { + std::string ntcp2proxy; i2p::config::GetOption("ntcp2.proxy", ntcp2proxy); + if (!ntcp2proxy.empty ()) ntcp2Published = false; + } } - bool ipv4; i2p::config::GetOption("ipv4", ipv4); - bool ipv6; i2p::config::GetOption("ipv6", ipv6); - bool ssu; i2p::config::GetOption("ssu", ssu); - bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); - bool nat; i2p::config::GetOption("nat", nat); - std::string ifname; i2p::config::GetOption("ifname", ifname); - std::string ifname4; i2p::config::GetOption("ifname4", ifname4); - std::string ifname6; i2p::config::GetOption("ifname6", ifname6); + bool ssu2Published = false; + if (ssu2) + i2p::config::GetOption("ssu2.published", ssu2Published); + uint8_t caps = 0; if (ipv4) { - std::string host = "127.0.0.1"; - if (!i2p::config::IsDefault("host")) - i2p::config::GetOption("host", host); - else if (!nat && !ifname.empty()) - /* bind to interface, we have no NAT so set external address too */ - host = i2p::util::net::GetInterfaceAddress(ifname, false).to_string(); // v4 + std::string host; + if (!nat) + // we have no NAT so set external address from local address + i2p::config::GetOption("address4", host); + if (host.empty ()) i2p::config::GetOption("host", host); - if(ifname4.size()) - host = i2p::util::net::GetInterfaceAddress(ifname4, false).to_string(); - - if (ssu) - routerInfo.AddSSUAddress (host.c_str(), port, routerInfo.GetIdentHash ()); + if (ntcp2) + { + uint16_t ntcp2Port; i2p::config::GetOption ("ntcp2.port", ntcp2Port); + if (!ntcp2Port) ntcp2Port = port; + if (ntcp2Published && ntcp2Port) + { + boost::asio::ip::address addr; + if (!host.empty ()) + addr = boost::asio::ip::address::from_string (host); + if (!addr.is_v4()) + addr = boost::asio::ip::address_v4 (); + routerInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, addr, ntcp2Port); + } + else + { + // add non-published NTCP2 address + uint8_t addressCaps = i2p::data::RouterInfo::AddressCaps::eV4; + if (ipv6) addressCaps |= i2p::data::RouterInfo::AddressCaps::eV6; + routerInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, ntcp2Port, addressCaps); + } + } + if (ssu2) + { + uint16_t ssu2Port; i2p::config::GetOption ("ssu2.port", ssu2Port); + if (!ssu2Port) ssu2Port = port; + if (ssu2Published && ssu2Port) + { + boost::asio::ip::address addr; + if (!host.empty ()) + addr = boost::asio::ip::address::from_string (host); + if (!addr.is_v4()) + addr = boost::asio::ip::address_v4 (); + routerInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, addr, ssu2Port); + } + else + { + uint8_t addressCaps = i2p::data::RouterInfo::AddressCaps::eV4; + if (ipv6) addressCaps |= i2p::data::RouterInfo::AddressCaps::eV6; + routerInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, ssu2Port, addressCaps); + } + } } if (ipv6) { - std::string host = "::1"; - if (!i2p::config::IsDefault("host") && !ipv4) // override if v6 only - i2p::config::GetOption("host", host); - else if (!ifname.empty()) - host = i2p::util::net::GetInterfaceAddress(ifname, true).to_string(); // v6 + std::string host; i2p::config::GetOption("address6", host); + if (host.empty () && !ipv4) i2p::config::GetOption("host", host); // use host for ipv6 only if ipv4 is not presented - if(ifname6.size()) - host = i2p::util::net::GetInterfaceAddress(ifname6, true).to_string(); - - if (ssu) - routerInfo.AddSSUAddress (host.c_str(), port, routerInfo.GetIdentHash ()); + if (ntcp2) + { + uint16_t ntcp2Port; i2p::config::GetOption ("ntcp2.port", ntcp2Port); + if (!ntcp2Port) ntcp2Port = port; + if (ntcp2Published && ntcp2Port) + { + std::string ntcp2Host; + if (!i2p::config::IsDefault ("ntcp2.addressv6")) + i2p::config::GetOption ("ntcp2.addressv6", ntcp2Host); + else + ntcp2Host = host; + boost::asio::ip::address addr; + if (!ntcp2Host.empty ()) + addr = boost::asio::ip::address::from_string (ntcp2Host); + if (!addr.is_v6()) + addr = boost::asio::ip::address_v6 (); + routerInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, addr, ntcp2Port); + } + else + { + if (!ipv4) // no other ntcp2 addresses yet + routerInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, ntcp2Port, i2p::data::RouterInfo::AddressCaps::eV6); + } + } + if (ssu2) + { + uint16_t ssu2Port; i2p::config::GetOption ("ssu2.port", ssu2Port); + if (!ssu2Port) ssu2Port = port; + if (ssu2Published && ssu2Port) + { + boost::asio::ip::address addr; + if (!host.empty ()) + addr = boost::asio::ip::address::from_string (host); + if (!addr.is_v6()) + addr = boost::asio::ip::address_v6 (); + routerInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, addr, ssu2Port); + } + else + { + if (!ipv4) // no other ssu2 addresses yet + routerInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, ssu2Port, i2p::data::RouterInfo::AddressCaps::eV6); + } + } + } + if (ygg) + { + auto yggaddr = i2p::util::net::GetYggdrasilAddress (); + if (!yggaddr.is_unspecified ()) + routerInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, yggaddr, port); } - routerInfo.SetCaps (i2p::data::RouterInfo::eReachable | - i2p::data::RouterInfo::eSSUTesting | i2p::data::RouterInfo::eSSUIntroducer); // LR, BC + routerInfo.UpdateCaps (caps); // caps + L routerInfo.SetProperty ("netId", std::to_string (m_NetID)); routerInfo.SetProperty ("router.version", I2P_VERSION); routerInfo.CreateBuffer (m_Keys); m_RouterInfo.SetRouterIdentity (GetIdentity ()); m_RouterInfo.Update (routerInfo.GetBuffer (), routerInfo.GetBufferLen ()); + m_RouterInfo.SetUnreachable (false); + } - if (ntcp2) // we don't store iv in the address if non published so we must update it from keys + uint16_t RouterContext::SelectRandomPort () const + { + uint16_t port; + do { - if (!m_NTCP2Keys) NewNTCP2Keys (); - UpdateNTCP2Address (true); - bool published; i2p::config::GetOption("ntcp2.published", published); - if (published) - { - PublishNTCP2Address (port, true); - if (ipv6) - { - // add NTCP2 ipv6 address - std::string host = "::1"; - if (!i2p::config::IsDefault ("ntcp2.addressv6")) - i2p::config::GetOption ("ntcp2.addressv6", host); - m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, boost::asio::ip::address_v6::from_string (host), port); - } - } + port = rand () % (30777 - 9111) + 9111; // I2P network ports range } + while(i2p::util::net::IsPortInReservedRange(port)); + + return port; } void RouterContext::UpdateRouterInfo () @@ -135,30 +254,83 @@ namespace i2p void RouterContext::NewNTCP2Keys () { - m_StaticKeys.reset (new i2p::crypto::X25519Keys ()); - m_StaticKeys->GenerateKeys (); + m_NTCP2StaticKeys.reset (new i2p::crypto::X25519Keys ()); + m_NTCP2StaticKeys->GenerateKeys (); m_NTCP2Keys.reset (new NTCP2PrivateKeys ()); - m_StaticKeys->GetPrivateKey (m_NTCP2Keys->staticPrivateKey); - memcpy (m_NTCP2Keys->staticPublicKey, m_StaticKeys->GetPublicKey (), 32); + m_NTCP2StaticKeys->GetPrivateKey (m_NTCP2Keys->staticPrivateKey); + memcpy (m_NTCP2Keys->staticPublicKey, m_NTCP2StaticKeys->GetPublicKey (), 32); RAND_bytes (m_NTCP2Keys->iv, 16); // save std::ofstream fk (i2p::fs::DataDirPath (NTCP2_KEYS), std::ofstream::binary | std::ofstream::out); fk.write ((char *)m_NTCP2Keys.get (), sizeof (NTCP2PrivateKeys)); } + void RouterContext::NewSSU2Keys () + { + m_SSU2StaticKeys.reset (new i2p::crypto::X25519Keys ()); + m_SSU2StaticKeys->GenerateKeys (); + m_SSU2Keys.reset (new SSU2PrivateKeys ()); + m_SSU2StaticKeys->GetPrivateKey (m_SSU2Keys->staticPrivateKey); + memcpy (m_SSU2Keys->staticPublicKey, m_SSU2StaticKeys->GetPublicKey (), 32); + RAND_bytes (m_SSU2Keys->intro, 32); + // save + std::ofstream fk (i2p::fs::DataDirPath (SSU2_KEYS), std::ofstream::binary | std::ofstream::out); + fk.write ((char *)m_SSU2Keys.get (), sizeof (SSU2PrivateKeys)); + } + + void RouterContext::SetTesting (bool testing) + { + if (testing != m_Testing) + { + m_Testing = testing; + if (m_Testing) + m_Error = eRouterErrorNone; + } + } + + void RouterContext::SetTestingV6 (bool testing) + { + if (testing != m_TestingV6) + { + m_TestingV6 = testing; + if (m_TestingV6) + m_ErrorV6 = eRouterErrorNone; + } + } + void RouterContext::SetStatus (RouterStatus status) { + SetTesting (false); if (status != m_Status) { m_Status = status; - m_Error = eRouterErrorNone; switch (m_Status) { case eRouterStatusOK: - SetReachable (); + SetReachable (true, false); // ipv4 break; case eRouterStatusFirewalled: - SetUnreachable (); + SetUnreachable (true, false); // ipv4 + break; + default: + ; + } + } + } + + void RouterContext::SetStatusV6 (RouterStatus status) + { + SetTestingV6 (false); + if (status != m_StatusV6) + { + m_StatusV6 = status; + switch (m_StatusV6) + { + case eRouterStatusOK: + SetReachable (false, true); // ipv6 + break; + case eRouterStatusFirewalled: + SetUnreachable (false, true); // ipv6 break; default: ; @@ -168,10 +340,12 @@ namespace i2p void RouterContext::UpdatePort (int port) { + auto addresses = m_RouterInfo.GetAddresses (); + if (!addresses) return; bool updated = false; - for (auto& address : m_RouterInfo.GetAddresses ()) + for (auto& address : *addresses) { - if (!address->IsNTCP2 () && address->port != port) + if (address && address->port != port) { address->port = port; updated = true; @@ -181,24 +355,98 @@ namespace i2p UpdateRouterInfo (); } - void RouterContext::PublishNTCP2Address (int port, bool publish, bool v4only) + void RouterContext::PublishNTCP2Address (std::shared_ptr address, + int port, bool publish) const + { + if (!address) return; + if (!port && !address->port) port = SelectRandomPort (); + if (port) address->port = port; + address->published = publish; + memcpy (address->i, m_NTCP2Keys->iv, 16); + } + + void RouterContext::PublishNTCP2Address (int port, bool publish, bool v4, bool v6, bool ygg) { if (!m_NTCP2Keys) return; + auto addresses = m_RouterInfo.GetAddresses (); + if (!addresses) return; bool updated = false; - for (auto& address : m_RouterInfo.GetAddresses ()) + if (v4) { - if (address->IsNTCP2 () && (address->port != port || address->ntcp2->isPublished != publish) && (!v4only || address->host.is_v4 ())) + auto addr = (*addresses)[i2p::data::RouterInfo::eNTCP2V4Idx]; + if (addr && (addr->port != port || addr->published != publish)) { - if (!port && !address->port) + PublishNTCP2Address (addr, port, publish); + updated = true; + } + } + if (v6) + { + auto addr = (*addresses)[i2p::data::RouterInfo::eNTCP2V6Idx]; + if (addr && (addr->port != port || addr->published != publish)) + { + PublishNTCP2Address (addr, port, publish); + updated = true; + } + } + if (ygg) + { + auto addr = (*addresses)[i2p::data::RouterInfo::eNTCP2V6MeshIdx]; + if (addr && (addr->port != port || addr->published != publish)) + { + PublishNTCP2Address (addr, port, publish); + updated = true; + } + } + + if (updated) + UpdateRouterInfo (); + } + + void RouterContext::UpdateNTCP2Keys () + { + if (!m_NTCP2Keys) return; + auto addresses = m_RouterInfo.GetAddresses (); + if (!addresses) return; + for (auto& it: *addresses) + { + if (it && it->IsNTCP2 ()) + { + it->s = m_NTCP2Keys->staticPublicKey; + memcpy (it->i, m_NTCP2Keys->iv, 16); + } + } + } + + void RouterContext::PublishSSU2Address (int port, bool publish, bool v4, bool v6) + { + if (!m_SSU2Keys) return; + auto addresses = m_RouterInfo.GetAddresses (); + if (!addresses) return; + int newPort = 0; + if (!port) + { + for (const auto& address : *addresses) + if (address && address->port) { - // select random port only if address's port is not set - port = rand () % (30777 - 9111) + 9111; // I2P network ports range - if (port == 9150) port = 9151; // Tor browser + newPort = address->port; + break; } + if (!newPort) newPort = SelectRandomPort (); + } + bool updated = false; + for (auto& address : *addresses) + { + if (address && address->IsSSU2 () && (!address->port || address->port != port || address->published != publish) && + ((v4 && address->IsV4 ()) || (v6 && address->IsV6 ()))) + { if (port) address->port = port; - address->cost = publish ? 3 : 14; - address->ntcp2->isPublished = publish; - address->ntcp2->iv = m_NTCP2Keys->iv; + else if (!address->port) address->port = newPort; + address->published = publish; + if (publish) + address->caps |= (i2p::data::RouterInfo::eSSUIntroducer | i2p::data::RouterInfo::eSSUTesting); + else + address->caps &= ~(i2p::data::RouterInfo::eSSUIntroducer | i2p::data::RouterInfo::eSSUTesting); updated = true; } } @@ -206,84 +454,110 @@ namespace i2p UpdateRouterInfo (); } - void RouterContext::UpdateNTCP2Address (bool enable) + void RouterContext::UpdateSSU2Keys () { - auto& addresses = m_RouterInfo.GetAddresses (); - bool found = false, updated = false; - for (auto it = addresses.begin (); it != addresses.end (); ++it) + if (!m_SSU2Keys) return; + auto addresses = m_RouterInfo.GetAddresses (); + if (!addresses) return; + for (auto& it: *addresses) { - if ((*it)->IsNTCP2 ()) + if (it && it->IsSSU2 ()) { - found = true; - if (!enable) - { - addresses.erase (it); - updated= true; - } - break; + it->s = m_SSU2Keys->staticPublicKey; + it->i = m_SSU2Keys->intro; } } - if (enable && !found) - { - m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv); - updated = true; - } - if (updated) - UpdateRouterInfo (); } void RouterContext::UpdateAddress (const boost::asio::ip::address& host) { + auto addresses = m_RouterInfo.GetAddresses (); + if (!addresses) return; bool updated = false; - for (auto& address : m_RouterInfo.GetAddresses ()) + if (host.is_v4 ()) { - if (address->host != host && address->IsCompatible (host)) + auto addr = (*addresses)[i2p::data::RouterInfo::eNTCP2V4Idx]; + if (addr && addr->host != host) { - address->host = host; - if (host.is_v6 () && address->transportStyle == i2p::data::RouterInfo::eTransportSSU) + addr->host = host; + updated = true; + } + addr = (*addresses)[i2p::data::RouterInfo::eSSU2V4Idx]; + if (addr && addr->host != host) + { + addr->host = host; + updated = true; + } + } + else if (host.is_v6 ()) + { + auto addr = (*addresses)[i2p::data::RouterInfo::eNTCP2V6Idx]; + if (addr && addr->host != host) + { + addr->host = host; + updated = true; + } + addr = (*addresses)[i2p::data::RouterInfo::eSSU2V6Idx]; + if (addr && (addr->host != host || !addr->ssu->mtu)) + { + addr->host = host; + if (m_StatusV6 != eRouterStatusProxy) { // update MTU auto mtu = i2p::util::net::GetMTU (host); if (mtu) { LogPrint (eLogDebug, "Router: Our v6 MTU=", mtu); - if (mtu > 1472) { // TODO: magic constant - mtu = 1472; - LogPrint(eLogWarning, "Router: MTU dropped to upper limit of 1472 bytes"); + int maxMTU = i2p::util::net::GetMaxMTU (host.to_v6 ()); + if (mtu > maxMTU) + { + mtu = maxMTU; + LogPrint(eLogWarning, "Router: MTU dropped to upper limit of ", maxMTU, " bytes"); } - if (address->ssu) address->ssu->mtu = mtu; + addr->ssu->mtu = mtu; } } updated = true; } } + auto ts = i2p::util::GetSecondsSinceEpoch (); if (updated || ts > m_LastUpdateTime + ROUTER_INFO_UPDATE_INTERVAL) UpdateRouterInfo (); } - bool RouterContext::AddIntroducer (const i2p::data::RouterInfo::Introducer& introducer) + bool RouterContext::AddSSU2Introducer (const i2p::data::RouterInfo::Introducer& introducer, bool v4) { - bool ret = m_RouterInfo.AddIntroducer (introducer); + bool ret = m_RouterInfo.AddSSU2Introducer (introducer, v4); if (ret) UpdateRouterInfo (); return ret; } - void RouterContext::RemoveIntroducer (const boost::asio::ip::udp::endpoint& e) + void RouterContext::RemoveSSU2Introducer (const i2p::data::IdentHash& h, bool v4) { - if (m_RouterInfo.RemoveIntroducer (e)) + if (m_RouterInfo.RemoveSSU2Introducer (h, v4)) UpdateRouterInfo (); } + void RouterContext::ClearSSU2Introducers (bool v4) + { + auto addr = m_RouterInfo.GetSSU2Address (v4); + if (addr && !addr->ssu->introducers.empty ()) + { + addr->ssu->introducers.clear (); + UpdateRouterInfo (); + } + } + void RouterContext::SetFloodfill (bool floodfill) { m_IsFloodfill = floodfill; if (floodfill) - m_RouterInfo.SetCaps (m_RouterInfo.GetCaps () | i2p::data::RouterInfo::eFloodfill); + m_RouterInfo.UpdateCaps (m_RouterInfo.GetCaps () | i2p::data::RouterInfo::eFloodfill); else { - m_RouterInfo.SetCaps (m_RouterInfo.GetCaps () & ~i2p::data::RouterInfo::eFloodfill); + m_RouterInfo.UpdateCaps (m_RouterInfo.GetCaps () & ~i2p::data::RouterInfo::eFloodfill); // we don't publish number of routers and leaseset for non-floodfill m_RouterInfo.DeleteProperty (i2p::data::ROUTER_INFO_PROPERTY_LEASESETS); m_RouterInfo.DeleteProperty (i2p::data::ROUTER_INFO_PROPERTY_ROUTERS); @@ -328,7 +602,7 @@ namespace i2p case i2p::data::CAPS_FLAG_EXTRA_BANDWIDTH1 : limit = 2048; type = extra; break; case i2p::data::CAPS_FLAG_EXTRA_BANDWIDTH2 : limit = 1000000; type = unlim; break; // 1Gbyte/s default: - limit = 48; type = low; + limit = 48; type = low; } /* update caps & flags in RI */ auto caps = m_RouterInfo.GetCaps (); @@ -342,10 +616,10 @@ namespace i2p #if (__cplusplus >= 201703L) // C++ 17 or higher [[fallthrough]]; #endif - // no break here, extra + high means 'X' - case high : caps |= i2p::data::RouterInfo::eHighBandwidth; break; + // no break here, extra + high means 'X' + case high : caps |= i2p::data::RouterInfo::eHighBandwidth; break; } - m_RouterInfo.SetCaps (caps); + m_RouterInfo.UpdateCaps (caps); UpdateRouterInfo (); m_BandwidthLimit = limit; } @@ -359,6 +633,7 @@ namespace i2p else if (limit > 48) { SetBandwidth('M'); } else if (limit > 12) { SetBandwidth('L'); } else { SetBandwidth('K'); } + m_BandwidthLimit = limit; // set precise limit } void RouterContext::SetShareRatio (int percents) @@ -373,68 +648,69 @@ namespace i2p return m_RouterInfo.GetCaps () & i2p::data::RouterInfo::eUnreachable; } - void RouterContext::RemoveNTCPAddress (bool v4only) + void RouterContext::SetUnreachable (bool v4, bool v6) { - auto& addresses = m_RouterInfo.GetAddresses (); - for (auto it = addresses.begin (); it != addresses.end ();) + if (v4 || (v6 && !SupportsV4 ())) { - if ((*it)->transportStyle == i2p::data::RouterInfo::eTransportNTCP && !(*it)->IsNTCP2 () && - (!v4only || (*it)->host.is_v4 ())) - { - it = addresses.erase (it); - if (v4only) break; // otherwise might be more than one address - } - else - ++it; + // set caps + uint8_t caps = m_RouterInfo.GetCaps (); + caps &= ~i2p::data::RouterInfo::eReachable; + caps |= i2p::data::RouterInfo::eUnreachable; + if (v6 || !SupportsV6 ()) + caps &= ~i2p::data::RouterInfo::eFloodfill; // can't be floodfill + m_RouterInfo.UpdateCaps (caps); } - } - - void RouterContext::SetUnreachable () - { - // set caps - uint8_t caps = m_RouterInfo.GetCaps (); - caps &= ~i2p::data::RouterInfo::eReachable; - caps |= i2p::data::RouterInfo::eUnreachable; - caps &= ~i2p::data::RouterInfo::eFloodfill; // can't be floodfill - caps &= ~i2p::data::RouterInfo::eSSUIntroducer; // can't be introducer - m_RouterInfo.SetCaps (caps); uint16_t port = 0; // delete previous introducers - auto& addresses = m_RouterInfo.GetAddresses (); - for (auto& addr : addresses) - if (addr->ssu) - { - addr->ssu->introducers.clear (); - port = addr->port; - } - // remove NTCP2 v4 address + auto addresses = m_RouterInfo.GetAddresses (); + if (addresses) + { + for (auto& addr : *addresses) + if (addr && addr->ssu && ((v4 && addr->IsV4 ()) || (v6 && addr->IsV6 ()))) + { + addr->published = false; + addr->caps &= ~i2p::data::RouterInfo::eSSUIntroducer; // can't be introducer + addr->ssu->introducers.clear (); + port = addr->port; + } + } + // unpublish NTCP2 addreeses bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); if (ntcp2) - PublishNTCP2Address (port, false, true); + PublishNTCP2Address (port, false, v4, v6, false); // update + m_RouterInfo.UpdateSupportedTransports (); UpdateRouterInfo (); } - void RouterContext::SetReachable () + void RouterContext::SetReachable (bool v4, bool v6) { - // update caps - uint8_t caps = m_RouterInfo.GetCaps (); - caps &= ~i2p::data::RouterInfo::eUnreachable; - caps |= i2p::data::RouterInfo::eReachable; - caps |= i2p::data::RouterInfo::eSSUIntroducer; - if (m_IsFloodfill) - caps |= i2p::data::RouterInfo::eFloodfill; - m_RouterInfo.SetCaps (caps); + if (v4 || (v6 && !SupportsV4 ())) + { + // update caps + uint8_t caps = m_RouterInfo.GetCaps (); + caps &= ~i2p::data::RouterInfo::eUnreachable; + caps |= i2p::data::RouterInfo::eReachable; + if (m_IsFloodfill) + caps |= i2p::data::RouterInfo::eFloodfill; + m_RouterInfo.UpdateCaps (caps); + } uint16_t port = 0; // delete previous introducers - auto& addresses = m_RouterInfo.GetAddresses (); - for (auto& addr : addresses) - if (addr->ssu) - { - addr->ssu->introducers.clear (); - port = addr->port; - } - // insert NTCP2 back + bool isSSU2Published; i2p::config::GetOption ("ssu2.published", isSSU2Published); + auto addresses = m_RouterInfo.GetAddresses (); + if (addresses) + { + for (auto& addr : *addresses) + if (addr && addr->ssu && isSSU2Published && ((v4 && addr->IsV4 ()) || (v6 && addr->IsV6 ()))) + { + addr->published = true; + addr->caps |= i2p::data::RouterInfo::eSSUIntroducer; + addr->ssu->introducers.clear (); + if (addr->port) port = addr->port; + } + } + // publish NTCP2 bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); if (ntcp2) { @@ -443,10 +719,11 @@ namespace i2p { uint16_t ntcp2Port; i2p::config::GetOption ("ntcp2.port", ntcp2Port); if (!ntcp2Port) ntcp2Port = port; - PublishNTCP2Address (ntcp2Port, true, true); + PublishNTCP2Address (ntcp2Port, true, v4, v6, false); } } // update + m_RouterInfo.UpdateSupportedTransports (); UpdateRouterInfo (); } @@ -454,50 +731,99 @@ namespace i2p { if (supportsV6) { - m_RouterInfo.EnableV6 (); // insert v6 addresses if necessary - bool foundSSU = false, foundNTCP2 = false; + bool foundNTCP2 = false, foundSSU2 = false; uint16_t port = 0; - auto& addresses = m_RouterInfo.GetAddresses (); - for (auto& addr: addresses) + auto addresses = m_RouterInfo.GetAddresses (); + if (addresses) { - if (addr->host.is_v6 ()) + for (auto& addr: *addresses) { - if (addr->transportStyle == i2p::data::RouterInfo::eTransportSSU) - foundSSU = true; - else if (addr->IsPublishedNTCP2 ()) - foundNTCP2 = true; + if (addr && addr->IsV6 () && !i2p::util::net::IsYggdrasilAddress (addr->host)) + { + switch (addr->transportStyle) + { + case i2p::data::RouterInfo::eTransportNTCP2: + foundNTCP2 = true; + break; + case i2p::data::RouterInfo::eTransportSSU2: + foundSSU2 = true; + break; + default: ; + } + } + if (addr) port = addr->port; } - port = addr->port; } - if (!port) i2p::config::GetOption("port", port); - // SSU - if (!foundSSU) + if (!port) { - bool ssu; i2p::config::GetOption("ssu", ssu); - if (ssu) - { - std::string host = "::1"; // TODO: read host - m_RouterInfo.AddSSUAddress (host.c_str (), port, GetIdentHash ()); - } + i2p::config::GetOption("port", port); + if (!port) port = SelectRandomPort (); } // NTCP2 - if (!foundNTCP2) + bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); + if (ntcp2) { - bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); - bool ntcp2Published; i2p::config::GetOption("ntcp2.published", ntcp2Published); - if (ntcp2 && ntcp2Published) + if (!foundNTCP2) { - std::string ntcp2Host; - if (!i2p::config::IsDefault ("ntcp2.addressv6")) - i2p::config::GetOption ("ntcp2.addressv6", ntcp2Host); - else - ntcp2Host = "::1"; uint16_t ntcp2Port; i2p::config::GetOption ("ntcp2.port", ntcp2Port); if (!ntcp2Port) ntcp2Port = port; - m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, boost::asio::ip::address::from_string (ntcp2Host), ntcp2Port); + bool added = false; + bool ntcp2Published; i2p::config::GetOption("ntcp2.published", ntcp2Published); + if (ntcp2Published) + { + std::string ntcp2Host; + if (!i2p::config::IsDefault ("ntcp2.addressv6")) + i2p::config::GetOption ("ntcp2.addressv6", ntcp2Host); + else + i2p::config::GetOption("host", ntcp2Host); + if (!ntcp2Host.empty () && ntcp2Port) + { + auto addr = boost::asio::ip::address::from_string (ntcp2Host); + if (addr.is_v6 ()) + { + m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, addr, ntcp2Port); + added = true; + } + } + } + if (!added) + m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, ntcp2Port, i2p::data::RouterInfo::eV6); } } + else + m_RouterInfo.RemoveNTCP2Address (false); + // SSU2 + bool ssu2; i2p::config::GetOption("ssu2.enabled", ssu2); + if (ssu2) + { + if (!foundSSU2) + { + uint16_t ssu2Port; i2p::config::GetOption ("ssu2.port", ssu2Port); + if (!ssu2Port) ssu2Port = port; + bool added = false; + bool ssu2Published; i2p::config::GetOption("ssu2.published", ssu2Published); + if (ssu2Published && ssu2Port) + { + std::string host; i2p::config::GetOption("host", host); + if (!host.empty ()) + { + auto addr = boost::asio::ip::address::from_string (host); + if (addr.is_v6 ()) + { + m_RouterInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, addr, ssu2Port); + added = true; + } + } + } + if (!added) + m_RouterInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, ssu2Port, i2p::data::RouterInfo::eV6); + } + } + else + m_RouterInfo.RemoveSSU2Address (false); + if (ntcp2 || ssu2) + m_RouterInfo.EnableV6 (); } else m_RouterInfo.DisableV6 (); @@ -507,34 +833,161 @@ namespace i2p void RouterContext::SetSupportsV4 (bool supportsV4) { if (supportsV4) - m_RouterInfo.EnableV4 (); + { + bool foundNTCP2 = false, foundSSU2 = false; + uint16_t port = 0; + auto addresses = m_RouterInfo.GetAddresses (); + if (addresses) + { + for (auto& addr: *addresses) + { + if (addr && addr->IsV4 ()) + { + switch (addr->transportStyle) + { + case i2p::data::RouterInfo::eTransportNTCP2: + foundNTCP2 = true; + break; + case i2p::data::RouterInfo::eTransportSSU2: + foundSSU2 = true; + break; + default: ; + } + } + if (addr && addr->port) port = addr->port; + } + } + if (!port) + { + i2p::config::GetOption("port", port); + if (!port) port = SelectRandomPort (); + } + // NTCP2 + bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); + if (ntcp2) + { + if (!foundNTCP2) + { + uint16_t ntcp2Port; i2p::config::GetOption ("ntcp2.port", ntcp2Port); + if (!ntcp2Port) ntcp2Port = port; + bool added = false; + bool ntcp2Published; i2p::config::GetOption("ntcp2.published", ntcp2Published); + if (ntcp2Published && ntcp2Port) + { + std::string host; i2p::config::GetOption("host", host); + if (!host.empty ()) + { + auto addr = boost::asio::ip::address::from_string (host); + if (addr.is_v4 ()) + { + m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, addr, ntcp2Port); + added = true; + } + } + } + if (!added) + m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, ntcp2Port, i2p::data::RouterInfo::eV4); + } + } + else + m_RouterInfo.RemoveNTCP2Address (true); + // SSU2 + bool ssu2; i2p::config::GetOption("ssu2.enabled", ssu2); + if (ssu2) + { + if (!foundSSU2) + { + uint16_t ssu2Port; i2p::config::GetOption ("ssu2.port", ssu2Port); + if (!ssu2Port) ssu2Port = port; + bool added = false; + bool ssu2Published; i2p::config::GetOption("ssu2.published", ssu2Published); + std::string host; i2p::config::GetOption("host", host); + if (ssu2Published && ssu2Port) + { + std::string host; i2p::config::GetOption("host", host); + if (!host.empty ()) + { + auto addr = boost::asio::ip::address::from_string (host); + if (addr.is_v4 ()) + { + m_RouterInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, addr, ssu2Port); + added = true; + } + } + } + if (!added) + m_RouterInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, ssu2Port, i2p::data::RouterInfo::eV4); + } + } + else + m_RouterInfo.RemoveSSU2Address (true); + if (ntcp2 || ssu2) + m_RouterInfo.EnableV4 (); + } else m_RouterInfo.DisableV4 (); UpdateRouterInfo (); } - void RouterContext::UpdateNTCP2V6Address (const boost::asio::ip::address& host) + void RouterContext::SetSupportsMesh (bool supportsmesh, const boost::asio::ip::address_v6& host) { - bool updated = false; - auto& addresses = m_RouterInfo.GetAddresses (); - for (auto& addr: addresses) + if (supportsmesh) { - if (addr->IsPublishedNTCP2 ()) + auto addresses = m_RouterInfo.GetAddresses (); + if (!addresses) return; + m_RouterInfo.EnableMesh (); + if ((*addresses)[i2p::data::RouterInfo::eNTCP2V6MeshIdx]) return; // we have mesh address already + uint16_t port = 0; + i2p::config::GetOption ("ntcp2.port", port); + if (!port) i2p::config::GetOption("port", port); + if (!port) { - if (addr->host.is_v6 ()) + for (auto& addr: *addresses) { - if (addr->host != host) + if (addr && addr->port) { - addr->host = host; - updated = true; + port = addr->port; + break; } - break; } } + if (!port) port = SelectRandomPort (); + m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, host, port); } + else + m_RouterInfo.DisableMesh (); + UpdateRouterInfo (); + } - if (updated) + void RouterContext::SetMTU (int mtu, bool v4) + { + if (mtu < 1280 || mtu > 1500) return; + auto addresses = m_RouterInfo.GetAddresses (); + if (!addresses) return; + for (auto& addr: *addresses) + { + if (addr && addr->ssu && ((v4 && addr->IsV4 ()) || (!v4 && addr->IsV6 ()))) + { + addr->ssu->mtu = mtu; + LogPrint (eLogDebug, "Router: MTU for ", v4 ? "ipv4" : "ipv6", " address ", addr->host.to_string(), " is set to ", mtu); + } + } + } + + void RouterContext::UpdateNTCP2V6Address (const boost::asio::ip::address& host) + { + auto addresses = m_RouterInfo.GetAddresses (); + if (!addresses) return; + std::shared_ptr addr; + if (i2p::util::net::IsYggdrasilAddress (host)) // yggdrasil + addr = (*addresses)[i2p::data::RouterInfo::eNTCP2V6MeshIdx]; + else if (host.is_v6 ()) + addr = (*addresses)[i2p::data::RouterInfo::eNTCP2V6Idx]; + if (addr && addr->IsPublishedNTCP2 () && addr->host != host) + { + addr->host = host; UpdateRouterInfo (); + } } void RouterContext::UpdateStats () @@ -556,31 +1009,44 @@ namespace i2p bool RouterContext::Load () { - std::ifstream fk (i2p::fs::DataDirPath (ROUTER_KEYS), std::ifstream::in | std::ifstream::binary); - if (!fk.is_open ()) return false; - fk.seekg (0, std::ios::end); - size_t len = fk.tellg(); - fk.seekg (0, std::ios::beg); + { + std::ifstream fk (i2p::fs::DataDirPath (ROUTER_KEYS), std::ifstream::in | std::ifstream::binary); + if (!fk.is_open ()) return false; + fk.seekg (0, std::ios::end); + size_t len = fk.tellg(); + fk.seekg (0, std::ios::beg); - if (len == sizeof (i2p::data::Keys)) // old keys file format - { - i2p::data::Keys keys; - fk.read ((char *)&keys, sizeof (keys)); - m_Keys = keys; + if (len == sizeof (i2p::data::Keys)) // old keys file format + { + i2p::data::Keys keys; + fk.read ((char *)&keys, sizeof (keys)); + m_Keys = keys; + } + else // new keys file format + { + uint8_t * buf = new uint8_t[len]; + fk.read ((char *)buf, len); + m_Keys.FromBuffer (buf, len); + delete[] buf; + } } - else // new keys file format + std::shared_ptr oldIdentity; + if (m_Keys.GetPublic ()->GetSigningKeyType () == i2p::data::SIGNING_KEY_TYPE_DSA_SHA1 || + m_Keys.GetPublic ()->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ELGAMAL) { - uint8_t * buf = new uint8_t[len]; - fk.read ((char *)buf, len); - m_Keys.FromBuffer (buf, len); - delete[] buf; + // update keys + LogPrint (eLogInfo, "Router: router keys are obsolete. Creating new"); + oldIdentity = m_Keys.GetPublic (); + m_Keys = i2p::data::PrivateKeys::CreateRandomKeys (i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519, + i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); + SaveKeys (); } // read NTCP2 keys if available std::ifstream n2k (i2p::fs::DataDirPath (NTCP2_KEYS), std::ifstream::in | std::ifstream::binary); if (n2k) { n2k.seekg (0, std::ios::end); - len = n2k.tellg(); + size_t len = n2k.tellg(); n2k.seekg (0, std::ios::beg); if (len == sizeof (NTCP2PrivateKeys)) { @@ -589,18 +1055,30 @@ namespace i2p } n2k.close (); } + // read SSU2 keys if available + std::ifstream s2k (i2p::fs::DataDirPath (SSU2_KEYS), std::ifstream::in | std::ifstream::binary); + if (s2k) + { + s2k.seekg (0, std::ios::end); + size_t len = s2k.tellg(); + s2k.seekg (0, std::ios::beg); + if (len == sizeof (SSU2PrivateKeys)) + { + m_SSU2Keys.reset (new SSU2PrivateKeys ()); + s2k.read ((char *)m_SSU2Keys.get (), sizeof (SSU2PrivateKeys)); + } + s2k.close (); + } // read RouterInfo - m_RouterInfo.SetRouterIdentity (GetIdentity ()); + m_RouterInfo.SetRouterIdentity (oldIdentity ? oldIdentity : GetIdentity ()); i2p::data::RouterInfo routerInfo(i2p::fs::DataDirPath (ROUTER_INFO)); if (!routerInfo.IsUnreachable ()) // router.info looks good { m_RouterInfo.Update (routerInfo.GetBuffer (), routerInfo.GetBufferLen ()); - m_RouterInfo.SetProperty ("coreVersion", I2P_VERSION); + if (oldIdentity) + m_RouterInfo.SetRouterIdentity (GetIdentity ()); // from new keys m_RouterInfo.SetProperty ("router.version", I2P_VERSION); - - // Migration to 0.9.24. TODO: remove later - m_RouterInfo.DeleteProperty ("coreVersion"); - m_RouterInfo.DeleteProperty ("stat_uptime"); + m_RouterInfo.DeleteProperty ("coreVersion"); // TODO: remove later } else { @@ -609,17 +1087,30 @@ namespace i2p } if (IsUnreachable ()) - SetReachable (); // we assume reachable until we discover firewall through peer tests + SetReachable (true, true); // we assume reachable until we discover firewall through peer tests - // read NTCP2 - bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); - if (ntcp2) + bool updated = false; + // create new NTCP2 keys if required + bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); + bool ygg; i2p::config::GetOption("meshnets.yggdrasil", ygg); + if ((ntcp2 || ygg) && !m_NTCP2Keys) { - if (!m_NTCP2Keys) NewNTCP2Keys (); - UpdateNTCP2Address (true); // enable NTCP2 + NewNTCP2Keys (); + UpdateNTCP2Keys (); + updated = true; } - else - UpdateNTCP2Address (false); // disable NTCP2 + // create new SSU2 keys if required + bool ssu2; i2p::config::GetOption("ssu2.enabled", ssu2); + if (ssu2 && !m_SSU2Keys) + { + NewSSU2Keys (); + UpdateSSU2Keys (); + updated = true; + } + if (m_RouterInfo.UpdateCongestion (i2p::data::RouterInfo::eLowCongestion)) + updated = true; + if (updated) + UpdateRouterInfo (); return true; } @@ -640,27 +1131,84 @@ namespace i2p return i2p::tunnel::tunnels.GetExploratoryPool (); } + bool RouterContext::IsHighCongestion () const + { + return i2p::tunnel::tunnels.IsTooManyTransitTunnels () || + i2p::transport::transports.IsBandwidthExceeded () || + i2p::transport::transports.IsTransitBandwidthExceeded (); + } + void RouterContext::HandleI2NPMessage (const uint8_t * buf, size_t len) { i2p::HandleI2NPMessage (CreateI2NPMessage (buf, GetI2NPMessageLength (buf, len))); } + bool RouterContext::HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID) + { + auto msg = CreateI2NPMessage (typeID, payload, len, msgID); + if (!msg) return false; + i2p::HandleI2NPMessage (msg); + return true; + } + void RouterContext::ProcessGarlicMessage (std::shared_ptr msg) { - std::unique_lock l(m_GarlicMutex); - i2p::garlic::GarlicDestination::ProcessGarlicMessage (msg); + if (m_Service) + m_Service->GetService ().post (std::bind (&RouterContext::PostGarlicMessage, this, msg)); + else + LogPrint (eLogError, "Router: service is NULL"); } + void RouterContext::PostGarlicMessage (std::shared_ptr msg) + { + uint8_t * buf = msg->GetPayload (); + uint32_t len = bufbe32toh (buf); + if (len > msg->GetLength ()) + { + LogPrint (eLogWarning, "Router: garlic message length ", len, " exceeds I2NP message length ", msg->GetLength ()); + return; + } + buf += 4; + if (!HandleECIESx25519TagMessage (buf, len)) // try tag first + { + // then Noise_N one-time decryption + if (m_ECIESSession) + m_ECIESSession->HandleNextMessage (buf, len); + else + LogPrint (eLogError, "Router: Session is not set for ECIES router"); + } + } + void RouterContext::ProcessDeliveryStatusMessage (std::shared_ptr msg) { - std::unique_lock l(m_GarlicMutex); - i2p::garlic::GarlicDestination::ProcessDeliveryStatusMessage (msg); + if (m_Service) + m_Service->GetService ().post (std::bind (&RouterContext::PostDeliveryStatusMessage, this, msg)); + else + LogPrint (eLogError, "Router: service is NULL"); } + void RouterContext::PostDeliveryStatusMessage (std::shared_ptr msg) + { + if (m_PublishReplyToken == bufbe32toh (msg->GetPayload () + DELIVERY_STATUS_MSGID_OFFSET)) + { + LogPrint (eLogInfo, "Router: Publishing confirmed. reply token=", m_PublishReplyToken); + m_PublishExcluded.clear (); + m_PublishReplyToken = 0; + SchedulePublish (); + } + else + i2p::garlic::GarlicDestination::ProcessDeliveryStatusMessage (msg); + } + void RouterContext::CleanupDestination () { - std::unique_lock l(m_GarlicMutex); - i2p::garlic::GarlicDestination::CleanupExpiredTags (); + if (m_Service) + m_Service->GetService ().post ([this]() + { + this->i2p::garlic::GarlicDestination::CleanupExpiredTags (); + }); + else + LogPrint (eLogError, "Router: service is NULL"); } uint32_t RouterContext::GetUptime () const @@ -668,27 +1216,219 @@ namespace i2p return std::chrono::duration_cast (std::chrono::steady_clock::now() - m_StartupTime).count (); } - bool RouterContext::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, i2p::data::CryptoKeyType preferredCrypto) const + bool RouterContext::Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const { - return m_Decryptor ? m_Decryptor->Decrypt (encrypted, data, ctx, true) : false; + return m_Decryptor ? m_Decryptor->Decrypt (encrypted, data) : false; } - bool RouterContext::DecryptTunnelBuildRecord (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) const + bool RouterContext::DecryptTunnelBuildRecord (const uint8_t * encrypted, uint8_t * data) { - return m_Decryptor ? m_Decryptor->Decrypt (encrypted, data, ctx, false) : false; + return DecryptECIESTunnelBuildRecord (encrypted, data, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE); } - i2p::crypto::X25519Keys& RouterContext::GetStaticKeys () + bool RouterContext::DecryptECIESTunnelBuildRecord (const uint8_t * encrypted, uint8_t * data, size_t clearTextSize) { - if (!m_StaticKeys) + // m_InitialNoiseState is h = SHA256(h || hepk) + m_CurrentNoiseState = m_InitialNoiseState; + m_CurrentNoiseState.MixHash (encrypted, 32); // h = SHA256(h || sepk) + uint8_t sharedSecret[32]; + if (!m_TunnelDecryptor->Decrypt (encrypted, sharedSecret)) + { + LogPrint (eLogWarning, "Router: Incorrect ephemeral public key"); + return false; + } + m_CurrentNoiseState.MixKey (sharedSecret); + encrypted += 32; + uint8_t nonce[12]; + memset (nonce, 0, 12); + if (!i2p::crypto::AEADChaCha20Poly1305 (encrypted, clearTextSize, m_CurrentNoiseState.m_H, 32, + m_CurrentNoiseState.m_CK + 32, nonce, data, clearTextSize, false)) // decrypt + { + LogPrint (eLogWarning, "Router: Tunnel record AEAD decryption failed"); + return false; + } + m_CurrentNoiseState.MixHash (encrypted, clearTextSize + 16); // h = SHA256(h || ciphertext) + return true; + } + + bool RouterContext::DecryptTunnelShortRequestRecord (const uint8_t * encrypted, uint8_t * data) + { + return DecryptECIESTunnelBuildRecord (encrypted, data, SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE); + } + + i2p::crypto::X25519Keys& RouterContext::GetNTCP2StaticKeys () + { + if (!m_NTCP2StaticKeys) { if (!m_NTCP2Keys) NewNTCP2Keys (); auto x = new i2p::crypto::X25519Keys (m_NTCP2Keys->staticPrivateKey, m_NTCP2Keys->staticPublicKey); - if (!m_StaticKeys) - m_StaticKeys.reset (x); + if (!m_NTCP2StaticKeys) + m_NTCP2StaticKeys.reset (x); else delete x; } - return *m_StaticKeys; + return *m_NTCP2StaticKeys; } + + i2p::crypto::X25519Keys& RouterContext::GetSSU2StaticKeys () + { + if (!m_SSU2StaticKeys) + { + if (!m_SSU2Keys) NewSSU2Keys (); + auto x = new i2p::crypto::X25519Keys (m_SSU2Keys->staticPrivateKey, m_SSU2Keys->staticPublicKey); + if (!m_SSU2StaticKeys) + m_SSU2StaticKeys.reset (x); + else + delete x; + } + return *m_SSU2StaticKeys; + } + + void RouterContext::ScheduleInitialPublish () + { + if (m_PublishTimer) + { + m_PublishTimer->expires_from_now (boost::posix_time::seconds(ROUTER_INFO_INITIAL_PUBLISH_INTERVAL)); + m_PublishTimer->async_wait (std::bind (&RouterContext::HandleInitialPublishTimer, + this, std::placeholders::_1)); + } + else + LogPrint (eLogError, "Router: Publish timer is NULL"); + } + + void RouterContext::HandleInitialPublishTimer (const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + { + if (m_RouterInfo.IsReachableBy (i2p::data::RouterInfo::eAllTransports)) + HandlePublishTimer (ecode); + else + ScheduleInitialPublish (); + } + } + + void RouterContext::SchedulePublish () + { + if (m_PublishTimer) + { + m_PublishTimer->cancel (); + m_PublishTimer->expires_from_now (boost::posix_time::seconds(ROUTER_INFO_PUBLISH_INTERVAL + + rand () % ROUTER_INFO_PUBLISH_INTERVAL_VARIANCE)); + m_PublishTimer->async_wait (std::bind (&RouterContext::HandlePublishTimer, + this, std::placeholders::_1)); + } + else + LogPrint (eLogError, "Router: Publish timer is NULL"); + } + + void RouterContext::HandlePublishTimer (const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + { + m_PublishExcluded.clear (); + m_PublishReplyToken = 0; + if (IsFloodfill ()) + { + UpdateStats (); // for floodfill + m_PublishExcluded.insert (i2p::context.GetIdentHash ()); // don't publish to ourselves + } + UpdateTimestamp (i2p::util::GetSecondsSinceEpoch ()); + Publish (); + SchedulePublishResend (); + } + } + + void RouterContext::Publish () + { + if (!i2p::transport::transports.IsOnline ()) return; + if (m_PublishExcluded.size () > ROUTER_INFO_MAX_PUBLISH_EXCLUDED_FLOODFILLS) + { + LogPrint (eLogError, "Router: Couldn't publish our RouterInfo to ", ROUTER_INFO_MAX_PUBLISH_EXCLUDED_FLOODFILLS, " closest routers. Try again"); + m_PublishExcluded.clear (); + UpdateTimestamp (i2p::util::GetSecondsSinceEpoch ()); + } + + auto floodfill = i2p::data::netdb.GetClosestFloodfill (i2p::context.GetIdentHash (), m_PublishExcluded); + if (floodfill) + { + uint32_t replyToken; + RAND_bytes ((uint8_t *)&replyToken, 4); + LogPrint (eLogInfo, "Router: Publishing our RouterInfo to ", i2p::data::GetIdentHashAbbreviation(floodfill->GetIdentHash ()), ". reply token=", replyToken); + if (floodfill->IsReachableFrom (i2p::context.GetRouterInfo ()) || // are we able to connect? + i2p::transport::transports.IsConnected (floodfill->GetIdentHash ())) // already connected ? + // send directly + i2p::transport::transports.SendMessage (floodfill->GetIdentHash (), CreateDatabaseStoreMsg (i2p::context.GetSharedRouterInfo (), replyToken)); + else + { + // otherwise through exploratory + auto exploratoryPool = i2p::tunnel::tunnels.GetExploratoryPool (); + auto outbound = exploratoryPool ? exploratoryPool->GetNextOutboundTunnel (nullptr, floodfill->GetCompatibleTransports (false)) : nullptr; + auto inbound = exploratoryPool ? exploratoryPool->GetNextInboundTunnel (nullptr, floodfill->GetCompatibleTransports (true)) : nullptr; + if (inbound && outbound) + { + // encrypt for floodfill + auto msg = CreateDatabaseStoreMsg (i2p::context.GetSharedRouterInfo (), replyToken, inbound); + outbound->SendTunnelDataMsgTo (floodfill->GetIdentHash (), 0, + i2p::garlic::WrapECIESX25519MessageForRouter (msg, floodfill->GetIdentity ()->GetEncryptionPublicKey ())); + } + else + LogPrint (eLogInfo, "Router: Can't publish our RouterInfo. No tunnles. Try again in ", ROUTER_INFO_CONFIRMATION_TIMEOUT, " seconds"); + } + m_PublishExcluded.insert (floodfill->GetIdentHash ()); + m_PublishReplyToken = replyToken; + } + else + LogPrint (eLogInfo, "Router: Can't find floodfill to publish our RouterInfo"); + } + + void RouterContext::SchedulePublishResend () + { + if (m_PublishTimer) + { + m_PublishTimer->cancel (); + m_PublishTimer->expires_from_now (boost::posix_time::seconds(ROUTER_INFO_CONFIRMATION_TIMEOUT)); + m_PublishTimer->async_wait (std::bind (&RouterContext::HandlePublishResendTimer, + this, std::placeholders::_1)); + } + else + LogPrint (eLogError, "Router: Publish timer is NULL"); + } + + void RouterContext::HandlePublishResendTimer (const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + { + i2p::context.UpdateTimestamp (i2p::util::GetSecondsSinceEpoch ()); + Publish (); + SchedulePublishResend (); + } + } + + void RouterContext::ScheduleCongestionUpdate () + { + if (m_CongestionUpdateTimer) + { + m_CongestionUpdateTimer->cancel (); + m_CongestionUpdateTimer->expires_from_now (boost::posix_time::seconds(ROUTER_INFO_CONGESTION_UPDATE_INTERVAL)); + m_CongestionUpdateTimer->async_wait (std::bind (&RouterContext::HandleCongestionUpdateTimer, + this, std::placeholders::_1)); + } + else + LogPrint (eLogError, "Router: Congestion update timer is NULL"); + } + + void RouterContext::HandleCongestionUpdateTimer (const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + { + auto c = i2p::data::RouterInfo::eLowCongestion; + if (!AcceptsTunnels ()) + c = i2p::data::RouterInfo::eRejectAll; + else if (IsHighCongestion ()) + c = i2p::data::RouterInfo::eHighCongestion; + if (m_RouterInfo.UpdateCongestion (c)) + UpdateRouterInfo (); + ScheduleCongestionUpdate (); + } + } } diff --git a/libi2pd/RouterContext.h b/libi2pd/RouterContext.h index a576d6b6..d49b5523 100644 --- a/libi2pd/RouterContext.h +++ b/libi2pd/RouterContext.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -12,32 +12,50 @@ #include #include #include -#include #include +#include #include #include "Identity.h" #include "RouterInfo.h" #include "Garlic.h" +#include "util.h" namespace i2p { +namespace garlic +{ + class RouterIncomingRatchetSession; +} + const char ROUTER_INFO[] = "router.info"; const char ROUTER_KEYS[] = "router.keys"; const char NTCP2_KEYS[] = "ntcp2.keys"; - const int ROUTER_INFO_UPDATE_INTERVAL = 1800; // 30 minutes + const char SSU2_KEYS[] = "ssu2.keys"; + const int ROUTER_INFO_UPDATE_INTERVAL = 30*60; // 30 minutes + const int ROUTER_INFO_PUBLISH_INTERVAL = 39*60; // in seconds + const int ROUTER_INFO_INITIAL_PUBLISH_INTERVAL = 10; // in seconds + const int ROUTER_INFO_PUBLISH_INTERVAL_VARIANCE = 105;// in seconds + const int ROUTER_INFO_CONFIRMATION_TIMEOUT = 5; // in seconds + const int ROUTER_INFO_MAX_PUBLISH_EXCLUDED_FLOODFILLS = 15; + const int ROUTER_INFO_CONGESTION_UPDATE_INTERVAL = 12*60; // in seconds enum RouterStatus { eRouterStatusOK = 0, - eRouterStatusTesting = 1, - eRouterStatusFirewalled = 2, - eRouterStatusError = 3 + eRouterStatusFirewalled = 1, + eRouterStatusUnknown = 2, + eRouterStatusProxy = 3, + eRouterStatusMesh = 4 }; enum RouterError { eRouterErrorNone = 0, - eRouterErrorClockSkew = 1 + eRouterErrorClockSkew = 1, + eRouterErrorOffline = 2, + eRouterErrorSymmetricNAT = 3, + eRouterErrorFullConeNAT = 4, + eRouterErrorNoDescriptors = 5 }; class RouterContext: public i2p::garlic::GarlicDestination @@ -51,50 +69,84 @@ namespace i2p uint8_t iv[16]; }; + struct SSU2PrivateKeys + { + uint8_t staticPublicKey[32]; + uint8_t staticPrivateKey[32]; + uint8_t intro[32]; + }; + + class RouterService: public i2p::util::RunnableServiceWithWork + { + public: + + RouterService (): RunnableServiceWithWork ("Router") {}; + boost::asio::io_service& GetService () { return GetIOService (); }; + void Start () { StartIOService (); }; + void Stop () { StopIOService (); }; + }; + public: RouterContext (); void Init (); - + void Start (); + void Stop (); + const i2p::data::PrivateKeys& GetPrivateKeys () const { return m_Keys; }; - i2p::data::RouterInfo& GetRouterInfo () { return m_RouterInfo; }; - std::shared_ptr GetSharedRouterInfo () const + i2p::data::LocalRouterInfo& GetRouterInfo () { return m_RouterInfo; }; + std::shared_ptr GetSharedRouterInfo () { - return std::shared_ptr (&m_RouterInfo, - [](const i2p::data::RouterInfo *) {}); + return std::shared_ptr (&m_RouterInfo, + [](i2p::data::RouterInfo *) {}); } std::shared_ptr GetSharedDestination () { return std::shared_ptr (this, [](i2p::garlic::GarlicDestination *) {}); } + const uint8_t * GetNTCP2StaticPublicKey () const { return m_NTCP2Keys ? m_NTCP2Keys->staticPublicKey : nullptr; }; const uint8_t * GetNTCP2StaticPrivateKey () const { return m_NTCP2Keys ? m_NTCP2Keys->staticPrivateKey : nullptr; }; const uint8_t * GetNTCP2IV () const { return m_NTCP2Keys ? m_NTCP2Keys->iv : nullptr; }; - i2p::crypto::X25519Keys& GetStaticKeys (); + i2p::crypto::X25519Keys& GetNTCP2StaticKeys (); + + const uint8_t * GetSSU2StaticPublicKey () const { return m_SSU2Keys ? m_SSU2Keys->staticPublicKey : nullptr; }; + const uint8_t * GetSSU2StaticPrivateKey () const { return m_SSU2Keys ? m_SSU2Keys->staticPrivateKey : nullptr; }; + const uint8_t * GetSSU2IntroKey () const { return m_SSU2Keys ? m_SSU2Keys->intro : nullptr; }; + i2p::crypto::X25519Keys& GetSSU2StaticKeys (); uint32_t GetUptime () const; // in seconds uint64_t GetLastUpdateTime () const { return m_LastUpdateTime; }; uint64_t GetBandwidthLimit () const { return m_BandwidthLimit; }; uint64_t GetTransitBandwidthLimit () const { return (m_BandwidthLimit*m_ShareRatio)/100LL; }; + bool GetTesting () const { return m_Testing; }; + void SetTesting (bool testing); RouterStatus GetStatus () const { return m_Status; }; void SetStatus (RouterStatus status); RouterError GetError () const { return m_Error; }; - void SetError (RouterError error) { m_Status = eRouterStatusError; m_Error = error; }; + void SetError (RouterError error) { m_Error = error; }; + bool GetTestingV6 () const { return m_TestingV6; }; + void SetTestingV6 (bool testing); + RouterStatus GetStatusV6 () const { return m_StatusV6; }; + void SetStatusV6 (RouterStatus status); + RouterError GetErrorV6 () const { return m_ErrorV6; }; + void SetErrorV6 (RouterError error) { m_ErrorV6 = error; }; int GetNetID () const { return m_NetID; }; void SetNetID (int netID) { m_NetID = netID; }; - bool DecryptTunnelBuildRecord (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) const; + bool DecryptTunnelBuildRecord (const uint8_t * encrypted, uint8_t * data); + bool DecryptTunnelShortRequestRecord (const uint8_t * encrypted, uint8_t * data); void UpdatePort (int port); // called from Daemon - void UpdateAddress (const boost::asio::ip::address& host); // called from SSU or Daemon - void PublishNTCP2Address (int port, bool publish = true, bool v4only = false); - void UpdateNTCP2Address (bool enable); - void RemoveNTCPAddress (bool v4only = true); // delete NTCP address for older routers. TODO: remove later - bool AddIntroducer (const i2p::data::RouterInfo::Introducer& introducer); - void RemoveIntroducer (const boost::asio::ip::udp::endpoint& e); + void UpdateAddress (const boost::asio::ip::address& host); // called from SSU2 or Daemon + void PublishNTCP2Address (int port, bool publish, bool v4, bool v6, bool ygg); + void PublishSSU2Address (int port, bool publish, bool v4, bool v6); + bool AddSSU2Introducer (const i2p::data::RouterInfo::Introducer& introducer, bool v4); + void RemoveSSU2Introducer (const i2p::data::IdentHash& h, bool v4); + void ClearSSU2Introducers (bool v4); bool IsUnreachable () const; - void SetUnreachable (); - void SetReachable (); + void SetUnreachable (bool v4, bool v6); + void SetReachable (bool v4, bool v6); bool IsFloodfill () const { return m_IsFloodfill; }; void SetFloodfill (bool floodfill); void SetFamily (const std::string& family); @@ -104,19 +156,26 @@ namespace i2p void SetShareRatio (int percents); // 0 - 100 bool AcceptsTunnels () const { return m_AcceptsTunnels; }; void SetAcceptsTunnels (bool acceptsTunnels) { m_AcceptsTunnels = acceptsTunnels; }; + bool IsHighCongestion () const; bool SupportsV6 () const { return m_RouterInfo.IsV6 (); }; bool SupportsV4 () const { return m_RouterInfo.IsV4 (); }; + bool SupportsMesh () const { return m_RouterInfo.IsMesh (); }; void SetSupportsV6 (bool supportsV6); void SetSupportsV4 (bool supportsV4); + void SetSupportsMesh (bool supportsmesh, const boost::asio::ip::address_v6& host); + void SetMTU (int mtu, bool v4); + void SetHidden(bool hide) { m_IsHiddenMode = hide; }; + bool IsHidden() const { return m_IsHiddenMode; }; + i2p::crypto::NoiseSymmetricState& GetCurrentNoiseState () { return m_CurrentNoiseState; }; void UpdateNTCP2V6Address (const boost::asio::ip::address& host); // called from Daemon. TODO: remove void UpdateStats (); void UpdateTimestamp (uint64_t ts); // in seconds, called from NetDb before publishing - void CleanupDestination (); // garlic destination + void CleanupDestination (); // garlic destination // implements LocalDestination std::shared_ptr GetIdentity () const { return m_Keys.GetPublic (); }; - bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, i2p::data::CryptoKeyType preferredCrypto) const; + bool Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const; void Sign (const uint8_t * buf, int len, uint8_t * signature) const { m_Keys.Sign (buf, len, signature); }; void SetLeaseSetUpdated () {}; @@ -132,7 +191,7 @@ namespace i2p // implements GarlicDestination void HandleI2NPMessage (const uint8_t * buf, size_t len); - bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len) { return false; }; // not implemented + bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID); private: @@ -140,25 +199,54 @@ namespace i2p void NewRouterInfo (); void UpdateRouterInfo (); void NewNTCP2Keys (); + void NewSSU2Keys (); + void UpdateNTCP2Keys (); + void UpdateSSU2Keys (); bool Load (); void SaveKeys (); + uint16_t SelectRandomPort () const; + void PublishNTCP2Address (std::shared_ptr address, int port, bool publish) const; + bool DecryptECIESTunnelBuildRecord (const uint8_t * encrypted, uint8_t * data, size_t clearTextSize); + void PostGarlicMessage (std::shared_ptr msg); + void PostDeliveryStatusMessage (std::shared_ptr msg); + + void ScheduleInitialPublish (); + void HandleInitialPublishTimer (const boost::system::error_code& ecode); + void SchedulePublish (); + void HandlePublishTimer (const boost::system::error_code& ecode); + void Publish (); + void SchedulePublishResend (); + void HandlePublishResendTimer (const boost::system::error_code& ecode); + void ScheduleCongestionUpdate (); + void HandleCongestionUpdateTimer (const boost::system::error_code& ecode); + private: - i2p::data::RouterInfo m_RouterInfo; + i2p::data::LocalRouterInfo m_RouterInfo; i2p::data::PrivateKeys m_Keys; - std::shared_ptr m_Decryptor; + std::shared_ptr m_Decryptor, m_TunnelDecryptor; + std::shared_ptr m_ECIESSession; uint64_t m_LastUpdateTime; // in seconds bool m_AcceptsTunnels, m_IsFloodfill; std::chrono::time_point m_StartupTime; uint64_t m_BandwidthLimit; // allowed bandwidth int m_ShareRatio; - RouterStatus m_Status; - RouterError m_Error; + RouterStatus m_Status, m_StatusV6; + RouterError m_Error, m_ErrorV6; + bool m_Testing, m_TestingV6; int m_NetID; - std::mutex m_GarlicMutex; std::unique_ptr m_NTCP2Keys; - std::unique_ptr m_StaticKeys; + std::unique_ptr m_SSU2Keys; + std::unique_ptr m_NTCP2StaticKeys, m_SSU2StaticKeys; + // for ECIESx25519 + i2p::crypto::NoiseSymmetricState m_InitialNoiseState, m_CurrentNoiseState; + // publish + std::unique_ptr m_Service; + std::unique_ptr m_PublishTimer, m_CongestionUpdateTimer; + std::set m_PublishExcluded; + uint32_t m_PublishReplyToken; + bool m_IsHiddenMode; // not publish }; extern RouterContext context; diff --git a/libi2pd/RouterInfo.cpp b/libi2pd/RouterInfo.cpp index 12f81a42..63cb79ef 100644 --- a/libi2pd/RouterInfo.cpp +++ b/libi2pd/RouterInfo.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -16,6 +16,7 @@ #include #endif #include "version.h" +#include "util.h" #include "Crypto.h" #include "Base.h" #include "Timestamp.h" @@ -28,29 +29,36 @@ namespace i2p { namespace data { + RouterInfo::Buffer::Buffer (const uint8_t * buf, size_t len) + { + if (len > size ()) len = size (); + memcpy (data (), buf, len); + } + RouterInfo::RouterInfo (): m_Buffer (nullptr) { m_Addresses = boost::make_shared(); // create empty list } RouterInfo::RouterInfo (const std::string& fullPath): - m_FullPath (fullPath), m_IsUpdated (false), m_IsUnreachable (false), - m_SupportedTransports (0), m_Caps (0), m_Version (0) + m_FamilyID (0), m_IsUpdated (false), m_IsUnreachable (false), + m_SupportedTransports (0),m_ReachableTransports (0), + m_Caps (0), m_Version (0), m_Congestion (eLowCongestion) { m_Addresses = boost::make_shared(); // create empty list - m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE]; - ReadFromFile (); + m_Buffer = NewBuffer (); // always RouterInfo's + ReadFromFile (fullPath); } - RouterInfo::RouterInfo (const uint8_t * buf, int len): - m_IsUpdated (true), m_IsUnreachable (false), m_SupportedTransports (0), - m_Caps (0), m_Version (0) + RouterInfo::RouterInfo (std::shared_ptr&& buf, size_t len): + m_FamilyID (0), m_IsUpdated (true), m_IsUnreachable (false), + m_SupportedTransports (0), m_ReachableTransports (0), + m_Caps (0), m_Version (0), m_Congestion (eLowCongestion) { - m_Addresses = boost::make_shared(); // create empty list if (len <= MAX_RI_BUFFER_SIZE) { - m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE]; - memcpy (m_Buffer, buf, len); + m_Addresses = boost::make_shared(); // create empty list + m_Buffer = buf; m_BufferLen = len; ReadFromBuffer (true); } @@ -62,18 +70,21 @@ namespace data } } - RouterInfo::~RouterInfo () + RouterInfo::RouterInfo (const uint8_t * buf, size_t len): + RouterInfo (std::make_shared (buf, len), len) { - delete[] m_Buffer; } - void RouterInfo::Update (const uint8_t * buf, size_t len) + RouterInfo::~RouterInfo () + { + } + + bool RouterInfo::Update (const uint8_t * buf, size_t len) { if (len > MAX_RI_BUFFER_SIZE) { - LogPrint (eLogError, "RouterInfo: Buffer is too long ", len); - m_IsUnreachable = true; - return; + LogPrint (eLogWarning, "RouterInfo: Updated buffer is too long ", len, ". Not changed"); + return false; } // verify signature since we have identity already int l = len - m_RouterIdentity->GetSignatureLen (); @@ -83,26 +94,25 @@ namespace data m_IsUpdated = true; m_IsUnreachable = false; m_SupportedTransports = 0; + m_ReachableTransports = 0; m_Caps = 0; // don't clean up m_Addresses, it will be replaced in ReadFromStream - m_Properties.clear (); - // copy buffer - if (!m_Buffer) - m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE]; - memcpy (m_Buffer, buf, len); - m_BufferLen = len; + ClearProperties (); // skip identity size_t identityLen = m_RouterIdentity->GetFullLen (); // read new RI - std::stringstream str (std::string ((char *)m_Buffer + identityLen, m_BufferLen - identityLen)); + std::stringstream str (std::string ((char *)buf + identityLen, len - identityLen)); ReadFromStream (str); + if (!m_IsUnreachable) + UpdateBuffer (buf, len); // save buffer // don't delete buffer until saved to the file } else - { - LogPrint (eLogError, "RouterInfo: signature verification failed"); - m_IsUnreachable = true; - } + { + LogPrint (eLogWarning, "RouterInfo: Updated signature verification failed. Not changed"); + return false; + } + return true; } void RouterInfo::SetRouterIdentity (std::shared_ptr identity) @@ -111,33 +121,34 @@ namespace data m_Timestamp = i2p::util::GetMillisecondsSinceEpoch (); } - bool RouterInfo::LoadFile () + bool RouterInfo::LoadFile (const std::string& fullPath) { - std::ifstream s(m_FullPath, std::ifstream::binary); + std::ifstream s(fullPath, std::ifstream::binary); if (s.is_open ()) { s.seekg (0,std::ios::end); m_BufferLen = s.tellg (); if (m_BufferLen < 40 || m_BufferLen > MAX_RI_BUFFER_SIZE) { - LogPrint(eLogError, "RouterInfo: File", m_FullPath, " is malformed"); + LogPrint(eLogError, "RouterInfo: File", fullPath, " is malformed"); return false; } s.seekg(0, std::ios::beg); - if (!m_Buffer) m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE]; - s.read((char *)m_Buffer, m_BufferLen); + if (!m_Buffer) + m_Buffer = NewBuffer (); + s.read((char *)m_Buffer->data (), m_BufferLen); } else { - LogPrint (eLogError, "RouterInfo: Can't open file ", m_FullPath); + LogPrint (eLogError, "RouterInfo: Can't open file ", fullPath); return false; } return true; } - void RouterInfo::ReadFromFile () + void RouterInfo::ReadFromFile (const std::string& fullPath) { - if (LoadFile ()) + if (LoadFile (fullPath)) ReadFromBuffer (false); else m_IsUnreachable = true; @@ -145,11 +156,16 @@ namespace data void RouterInfo::ReadFromBuffer (bool verifySignature) { - m_RouterIdentity = std::make_shared(m_Buffer, m_BufferLen); + if (!m_Buffer) + { + m_IsUnreachable = true; + return; + } + m_RouterIdentity = NewIdentity (m_Buffer->data (), m_BufferLen); size_t identityLen = m_RouterIdentity->GetFullLen (); if (identityLen >= m_BufferLen) { - LogPrint (eLogError, "RouterInfo: identity length ", identityLen, " exceeds buffer size ", m_BufferLen); + LogPrint (eLogError, "RouterInfo: Identity length ", identityLen, " exceeds buffer size ", m_BufferLen); m_IsUnreachable = true; return; } @@ -164,60 +180,65 @@ namespace data } // verify signature int l = m_BufferLen - m_RouterIdentity->GetSignatureLen (); - if (l < 0 || !m_RouterIdentity->Verify ((uint8_t *)m_Buffer, l, (uint8_t *)m_Buffer + l)) + if (l < 0 || !m_RouterIdentity->Verify ((uint8_t *)m_Buffer->data (), l, (uint8_t *)m_Buffer->data () + l)) { - LogPrint (eLogError, "RouterInfo: signature verification failed"); + LogPrint (eLogError, "RouterInfo: Signature verification failed"); m_IsUnreachable = true; return; } - m_RouterIdentity->DropVerifier (); } // parse RI std::stringstream str; - str.write ((const char *)m_Buffer + identityLen, m_BufferLen - identityLen); + str.write ((const char *)m_Buffer->data () + identityLen, m_BufferLen - identityLen); ReadFromStream (str); if (!str) { - LogPrint (eLogError, "RouterInfo: malformed message"); + LogPrint (eLogError, "RouterInfo: Malformed message"); m_IsUnreachable = true; } } void RouterInfo::ReadFromStream (std::istream& s) { + if (!s) return; + m_Caps = 0; m_Congestion = eLowCongestion; s.read ((char *)&m_Timestamp, sizeof (m_Timestamp)); m_Timestamp = be64toh (m_Timestamp); // read addresses - auto addresses = boost::make_shared(); + auto addresses = NewAddresses (); uint8_t numAddresses; - s.read ((char *)&numAddresses, sizeof (numAddresses)); if (!s) return; - bool introducers = false; + s.read ((char *)&numAddresses, sizeof (numAddresses)); for (int i = 0; i < numAddresses; i++) { uint8_t supportedTransports = 0; - auto address = std::make_shared

(); - s.read ((char *)&address->cost, sizeof (address->cost)); + auto address = NewAddress (); + uint8_t cost; // ignore + s.read ((char *)&cost, sizeof (cost)); s.read ((char *)&address->date, sizeof (address->date)); - bool isNTCP2Only = false; + bool isHost = false, isStaticKey = false, isV2 = false; char transportStyle[6]; - auto transportStyleLen = ReadString (transportStyle, 6, s) - 1; + ReadString (transportStyle, 6, s); if (!strncmp (transportStyle, "NTCP", 4)) // NTCP or NTCP2 + address->transportStyle = eTransportNTCP2; + else if (!strncmp (transportStyle, "SSU", 3)) // SSU or SSU2 { - address->transportStyle = eTransportNTCP; - if (transportStyleLen > 4 && transportStyle[4] == '2') isNTCP2Only= true; - } - else if (!strcmp (transportStyle, "SSU")) - { - address->transportStyle = eTransportSSU; + address->transportStyle = eTransportSSU2; address->ssu.reset (new SSUExt ()); address->ssu->mtu = 0; } else address->transportStyle = eTransportUnknown; + address->caps = 0; address->port = 0; uint16_t size, r = 0; s.read ((char *)&size, sizeof (size)); if (!s) return; size = be16toh (size); + if (address->transportStyle == eTransportUnknown) + { + // skip unknown address + s.seekg (size, std::ios_base::cur); + if (s) continue; else return; + } while (r < size) { char key[255], value[255]; @@ -230,53 +251,67 @@ namespace data { boost::system::error_code ecode; address->host = boost::asio::ip::address::from_string (value, ecode); - if (!ecode) + if (!ecode && !address->host.is_unspecified ()) { -#if BOOST_VERSION >= 104900 - if (!address->host.is_unspecified ()) // check if address is valid -#else - address->host.to_string (ecode); - if (!ecode) -#endif - { - // add supported protocol - if (address->host.is_v4 ()) - supportedTransports |= (address->transportStyle == eTransportNTCP) ? eNTCPV4 : eSSUV4; - else - supportedTransports |= (address->transportStyle == eTransportNTCP) ? eNTCPV6 : eSSUV6; - } + if (!i2p::util::net::IsInReservedRange (address->host) || + i2p::util::net::IsYggdrasilAddress (address->host)) + isHost = true; + else + // we consider such address as invalid + address->transportStyle = eTransportUnknown; } } else if (!strcmp (key, "port")) - address->port = boost::lexical_cast(value); + { + try + { + address->port = boost::lexical_cast(value); + } + catch (std::exception& ex) + { + LogPrint (eLogWarning, "RouterInfo: 'port' exception ", ex.what ()); + } + } else if (!strcmp (key, "mtu")) { if (address->ssu) - address->ssu->mtu = boost::lexical_cast(value); + { + try + { + address->ssu->mtu = boost::lexical_cast(value); + } + catch (std::exception& ex) + { + LogPrint (eLogWarning, "RouterInfo: 'mtu' exception ", ex.what ()); + } + } else - LogPrint (eLogWarning, "RouterInfo: Unexpected field 'mtu' for NTCP"); - } - else if (!strcmp (key, "key")) - { - if (address->ssu) - Base64ToByteStream (value, strlen (value), address->ssu->key, 32); - else - LogPrint (eLogWarning, "RouterInfo: Unexpected field 'key' for NTCP"); + LogPrint (eLogWarning, "RouterInfo: Unexpected field 'mtu' for NTCP2"); } else if (!strcmp (key, "caps")) - ExtractCaps (value); - else if (!strcmp (key, "s")) // ntcp2 static key + address->caps = ExtractAddressCaps (value); + else if (!strcmp (key, "s")) // ntcp2 or ssu2 static key { - if (!address->ntcp2) address->ntcp2.reset (new NTCP2Ext ()); - supportedTransports |= (address->host.is_v4 ()) ? eNTCP2V4 : eNTCP2V6; - Base64ToByteStream (value, strlen (value), address->ntcp2->staticKey, 32); + Base64ToByteStream (value, strlen (value), address->s, 32); + if (!(address->s[31] & 0x80)) // check if x25519 public key + isStaticKey = true; } - else if (!strcmp (key, "i")) // ntcp2 iv + else if (!strcmp (key, "i")) // ntcp2 iv or ssu2 intro { - if (!address->ntcp2) address->ntcp2.reset (new NTCP2Ext ()); - supportedTransports |= (address->host.is_v4 ()) ? eNTCP2V4 : eNTCP2V6; - Base64ToByteStream (value, strlen (value), address->ntcp2->iv, 16); - address->ntcp2->isPublished = true; // presence if "i" means "published" + if (address->IsNTCP2 ()) + { + Base64ToByteStream (value, strlen (value), address->i, 16); + address->published = true; // presence of "i" means "published" NTCP2 + } + else if (address->IsSSU2 ()) + Base64ToByteStream (value, strlen (value), address->i, 32); + } + else if (!strcmp (key, "v")) + { + if (!strcmp (value, "2")) + isV2 = true; + else + LogPrint (eLogWarning, "RouterInfo: Unexpected value ", value, " for v"); } else if (key[0] == 'i') { @@ -286,7 +321,6 @@ namespace data LogPrint (eLogError, "RouterInfo: Introducer is presented for non-SSU address. Skipped"); continue; } - introducers = true; size_t l = strlen(key); unsigned char index = key[l-1] - '0'; // TODO: key[l-1] = 0; @@ -296,32 +330,96 @@ namespace data if (s) continue; else return; } if (index >= address->ssu->introducers.size ()) - address->ssu->introducers.resize (index + 1); - Introducer& introducer = address->ssu->introducers.at (index); - if (!strcmp (key, "ihost")) { - boost::system::error_code ecode; - introducer.iHost = boost::asio::ip::address::from_string (value, ecode); + if (address->ssu->introducers.empty ()) // first time + address->ssu->introducers.reserve (3); + address->ssu->introducers.resize (index + 1); } - else if (!strcmp (key, "iport")) - introducer.iPort = boost::lexical_cast(value); - else if (!strcmp (key, "itag")) - introducer.iTag = boost::lexical_cast(value); - else if (!strcmp (key, "ikey")) - Base64ToByteStream (value, strlen (value), introducer.iKey, 32); + Introducer& introducer = address->ssu->introducers.at (index); + if (!strcmp (key, "itag")) + { + try + { + introducer.iTag = boost::lexical_cast(value); + } + catch (std::exception& ex) + { + LogPrint (eLogWarning, "RouterInfo: 'itag' exception ", ex.what ()); + } + } + else if (!strcmp (key, "ih")) + Base64ToByteStream (value, strlen (value), introducer.iH, 32); else if (!strcmp (key, "iexp")) - introducer.iExp = boost::lexical_cast(value); + { + try + { + introducer.iExp = boost::lexical_cast(value); + } + catch (std::exception& ex) + { + LogPrint (eLogWarning, "RouterInfo: 'iexp' exception ", ex.what ()); + } + } } if (!s) return; } - if (introducers) supportedTransports |= eSSUV4; // in case if host is not presented - if (isNTCP2Only && address->ntcp2) address->ntcp2->isNTCP2Only = true; - if (supportedTransports & ~(eNTCPV4 | eNTCPV6)) // exclude NTCP only + + if (address->transportStyle == eTransportNTCP2) { - addresses->push_back(address); + if (isStaticKey) + { + if (isHost && address->port) + { + if (address->host.is_v6 ()) + supportedTransports |= (i2p::util::net::IsYggdrasilAddress (address->host) ? eNTCP2V6Mesh : eNTCP2V6); + else + supportedTransports |= eNTCP2V4; + m_ReachableTransports |= supportedTransports; + } + else + { + address->published = false; + if (address->caps) + { + if (address->caps & AddressCaps::eV4) supportedTransports |= eNTCP2V4; + if (address->caps & AddressCaps::eV6) supportedTransports |= eNTCP2V6; + } + else + supportedTransports |= eNTCP2V4; // most likely, since we don't have host + } + } + } + else if (address->transportStyle == eTransportSSU2 && isV2 && isStaticKey) + { + if (address->IsV4 ()) supportedTransports |= eSSU2V4; + if (address->IsV6 ()) supportedTransports |= eSSU2V6; + if (isHost && address->port) + { + if (address->host.is_v4 ()) m_ReachableTransports |= eSSU2V4; + if (address->host.is_v6 ()) m_ReachableTransports |= eSSU2V6; + address->published = true; + } + if (address->ssu && !address->ssu->introducers.empty ()) + { + // exclude invalid introducers + uint32_t ts = i2p::util::GetSecondsSinceEpoch (); + UpdateIntroducers (address, ts); + if (!address->ssu->introducers.empty ()) // still has something + m_ReachableTransports |= supportedTransports; + } + } + if (supportedTransports) + { + if (!(m_SupportedTransports & supportedTransports)) // avoid duplicates + { + for (uint8_t i = 0; i < eNumTransports; i++) + if ((1 << i) & supportedTransports) + (*addresses)[i] = address; + } m_SupportedTransports |= supportedTransports; } } + // update addresses #if (BOOST_VERSION >= 105300) boost::atomic_store (&m_Addresses, addresses); #else @@ -332,6 +430,9 @@ namespace data s.read ((char *)&numPeers, sizeof (numPeers)); if (!s) return; s.seekg (numPeers*32, std::ios_base::cur); // TODO: read peers // read properties + m_Version = 0; + bool isNetId = false; + std::string family; uint16_t size, r = 0; s.read ((char *)&size, sizeof (size)); if (!s) return; size = be16toh (size); @@ -343,7 +444,7 @@ namespace data r += ReadString (value, 255, s); s.seekg (1, std::ios_base::cur); r++; // ; if (!s) return; - m_Properties[key] = value; + SetProperty (key, value); // extract caps if (!strcmp (key, "caps")) @@ -364,36 +465,42 @@ namespace data } } // check netId - else if (!strcmp (key, ROUTER_INFO_PROPERTY_NETID) && atoi (value) != i2p::context.GetNetID ()) + else if (!strcmp (key, ROUTER_INFO_PROPERTY_NETID)) { - LogPrint (eLogError, "RouterInfo: Unexpected ", ROUTER_INFO_PROPERTY_NETID, "=", value); - m_IsUnreachable = true; + isNetId = true; + if (atoi (value) != i2p::context.GetNetID ()) + { + LogPrint (eLogError, "RouterInfo: Unexpected ", ROUTER_INFO_PROPERTY_NETID, "=", value); + m_IsUnreachable = true; + } } // family else if (!strcmp (key, ROUTER_INFO_PROPERTY_FAMILY)) { - m_Family = value; - boost::to_lower (m_Family); + family = value; + boost::to_lower (family); } else if (!strcmp (key, ROUTER_INFO_PROPERTY_FAMILY_SIG)) { - if (!netdb.GetFamilies ().VerifyFamily (m_Family, GetIdentHash (), value)) - { - LogPrint (eLogWarning, "RouterInfo: family signature verification failed"); - m_Family.clear (); - } + if (netdb.GetFamilies ().VerifyFamily (family, GetIdentHash (), value)) + m_FamilyID = netdb.GetFamilies ().GetFamilyID (family); + else + { + LogPrint (eLogWarning, "RouterInfo: Family ", family, " signature verification failed"); + SetUnreachable (true); + } } if (!s) return; } - if (!m_SupportedTransports || !m_Addresses->size() || (UsesIntroducer () && !introducers)) + if (!m_SupportedTransports || !isNetId || !m_Version) SetUnreachable (true); } - bool RouterInfo::IsFamily(const std::string & fam) const + bool RouterInfo::IsFamily (FamilyID famid) const { - return m_Family == fam; + return m_FamilyID == famid; } void RouterInfo::ExtractCaps (const char * value) @@ -424,24 +531,649 @@ namespace data case CAPS_FLAG_UNREACHABLE: m_Caps |= Caps::eUnreachable; break; - case CAPS_FLAG_SSU_TESTING: - m_Caps |= Caps::eSSUTesting; + case CAPS_FLAG_MEDIUM_CONGESTION: + m_Congestion = eMediumCongestion; break; - case CAPS_FLAG_SSU_INTRODUCER: - m_Caps |= Caps::eSSUIntroducer; + case CAPS_FLAG_HIGH_CONGESTION: + m_Congestion = eHighCongestion; break; + case CAPS_FLAG_REJECT_ALL_CONGESTION: + m_Congestion = eRejectAll; + break; default: ; } cap++; } } - void RouterInfo::UpdateCapsProperty () + uint8_t RouterInfo::ExtractAddressCaps (const char * value) const + { + uint8_t caps = 0; + const char * cap = value; + while (*cap) + { + switch (*cap) + { + case CAPS_FLAG_V4: + caps |= AddressCaps::eV4; + break; + case CAPS_FLAG_V6: + caps |= AddressCaps::eV6; + break; + case CAPS_FLAG_SSU2_TESTING: + caps |= AddressCaps::eSSUTesting; + break; + case CAPS_FLAG_SSU2_INTRODUCER: + caps |= AddressCaps::eSSUIntroducer; + break; + default: ; + } + cap++; + } + return caps; + } + + void RouterInfo::UpdateIntroducers (std::shared_ptr
address, uint64_t ts) + { + if (!address || !address->ssu) return; + int numValid = 0; + for (auto& it: address->ssu->introducers) + { + if (it.iTag && ts < it.iExp && !it.iH.IsZero ()) + numValid++; + else + it.iTag = 0; + } + if (!numValid) + address->ssu->introducers.resize (0); + } + + bool RouterInfo::IsNewer (const uint8_t * buf, size_t len) const + { + if (!m_RouterIdentity) return false; + 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) + { + if (m_IsUnreachable) return false; // don't save bad router + if (!m_Buffer) + { + LogPrint (eLogWarning, "RouterInfo: Can't save, m_Buffer == NULL"); + return false; + } + std::ofstream f (fullPath, std::ofstream::binary | std::ofstream::out); + if (!f.is_open ()) + { + LogPrint (eLogError, "RouterInfo: Can't save to ", fullPath); + return false; + } + f.write ((char *)m_Buffer->data (), m_BufferLen); + return true; + } + + 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; + (*m_Addresses)[eNTCP2V4Idx] = addr; + } + if (addr->IsV6 ()) + { + m_SupportedTransports |= eNTCP2V6; + (*m_Addresses)[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; + } + if (addr->IsV4 ()) + { + m_SupportedTransports |= eNTCP2V4; + m_ReachableTransports |= eNTCP2V4; + (*m_Addresses)[eNTCP2V4Idx] = addr; + } + if (addr->IsV6 ()) + { + if (i2p::util::net::IsYggdrasilAddress (addr->host)) + { + m_SupportedTransports |= eNTCP2V6Mesh; + m_ReachableTransports |= eNTCP2V6Mesh; + (*m_Addresses)[eNTCP2V6MeshIdx] = addr; + } + else + { + m_SupportedTransports |= eNTCP2V6; + m_ReachableTransports |= eNTCP2V6; + (*m_Addresses)[eNTCP2V6Idx] = addr; + } + } + } + + void RouterInfo::RemoveNTCP2Address (bool v4) + { + if (v4) + { + if ((*m_Addresses)[eNTCP2V6Idx]) + (*m_Addresses)[eNTCP2V6Idx]->caps &= ~AddressCaps::eV4; + (*m_Addresses)[eNTCP2V4Idx].reset (); + } + else + { + if ((*m_Addresses)[eNTCP2V4Idx]) + (*m_Addresses)[eNTCP2V4Idx]->caps &= ~AddressCaps::eV6; + (*m_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); + if (addr->IsV4 ()) + { + m_SupportedTransports |= eSSU2V4; + (*m_Addresses)[eSSU2V4Idx] = addr; + } + if (addr->IsV6 ()) + { + m_SupportedTransports |= eSSU2V6; + (*m_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; + } + if (addr->IsV4 ()) + { + m_SupportedTransports |= eSSU2V4; + m_ReachableTransports |= eSSU2V4; + (*m_Addresses)[eSSU2V4Idx] = addr; + } + if (addr->IsV6 ()) + { + m_SupportedTransports |= eSSU2V6; + m_ReachableTransports |= eSSU2V6; + (*m_Addresses)[eSSU2V6Idx] = addr; + } + } + + void RouterInfo::RemoveSSU2Address (bool v4) + { + if (v4) + { + if ((*m_Addresses)[eSSU2V6Idx]) + (*m_Addresses)[eSSU2V6Idx]->caps &= ~AddressCaps::eV4; + (*m_Addresses)[eSSU2V4Idx].reset (); + } + else + { + if ((*m_Addresses)[eSSU2V4Idx]) + (*m_Addresses)[eSSU2V4Idx]->caps &= ~AddressCaps::eV6; + (*m_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 ()) + { + if ((*m_Addresses)[eNTCP2V6Idx]) + { + if ((*m_Addresses)[eNTCP2V6Idx]->IsV4 () && (*m_Addresses)[eNTCP2V4Idx]) + (*m_Addresses)[eNTCP2V4Idx]->caps &= ~AddressCaps::eV6; + (*m_Addresses)[eNTCP2V6Idx].reset (); + } + if ((*m_Addresses)[eSSU2V6Idx]) + { + if ((*m_Addresses)[eSSU2V6Idx]->IsV4 () && (*m_Addresses)[eSSU2V4Idx]) + (*m_Addresses)[eSSU2V4Idx]->caps &= ~AddressCaps::eV6; + (*m_Addresses)[eSSU2V6Idx].reset (); + } + UpdateSupportedTransports (); + } + } + + void RouterInfo::DisableV4 () + { + if (IsV4 ()) + { + if ((*m_Addresses)[eNTCP2V4Idx]) + { + if ((*m_Addresses)[eNTCP2V4Idx]->IsV6 () && (*m_Addresses)[eNTCP2V6Idx]) + (*m_Addresses)[eNTCP2V6Idx]->caps &= ~AddressCaps::eV4; + (*m_Addresses)[eNTCP2V4Idx].reset (); + } + if ((*m_Addresses)[eSSU2V4Idx]) + { + if ((*m_Addresses)[eSSU2V4Idx]->IsV6 () && (*m_Addresses)[eSSU2V6Idx]) + (*m_Addresses)[eSSU2V6Idx]->caps &= ~AddressCaps::eV4; + (*m_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; + (*m_Addresses)[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; + } + + boost::shared_ptr RouterInfo::GetAddresses () const + { +#if (BOOST_VERSION >= 105300) + return boost::atomic_load (&m_Addresses); +#else + return m_Addresses; +#endif + } + + template + std::shared_ptr RouterInfo::GetAddress (Filter filter) const + { + // TODO: make it more generic using comparator +#if (BOOST_VERSION >= 105300) + auto addresses = boost::atomic_load (&m_Addresses); +#else + auto addresses = m_Addresses; +#endif + for (const auto& address : *addresses) + if (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, >= 0.9.38 and not DSA + return m_Version >= NETDB_MIN_FLOODFILL_VERSION && IsPublished (true) && + 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 addreses are not published + auto addr = GetAddresses (); + if (v4) + return ((*addr)[eNTCP2V4Idx] && ((*addr)[eNTCP2V4Idx])->published) || + ((*addr)[eSSU2V4Idx] && ((*addr)[eSSU2V4Idx])->published); + else + return ((*addr)[eNTCP2V6Idx] && ((*addr)[eNTCP2V6Idx])->published) || + ((*addr)[eSSU2V6Idx] && ((*addr)[eSSU2V6Idx])->published); + } + + 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: *m_Addresses) + { + if (addr && !addr->published) + { + addr->caps &= ~(eV4 | eV6); + addr->caps |= transports; + } + } + } + + void RouterInfo::UpdateSupportedTransports () + { + m_SupportedTransports = 0; + m_ReachableTransports = 0; + for (const auto& addr: *m_Addresses) + { + 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) + { + if (!m_Buffer) + m_Buffer = NewBuffer (); + if (len > m_Buffer->size ()) len = m_Buffer->size (); + memcpy (m_Buffer->data (), buf, len); + m_BufferLen = len; + } + + std::shared_ptr RouterInfo::NewBuffer () const + { + return netdb.NewRouterInfoBuffer (); + } + + std::shared_ptr RouterInfo::NewAddress () const + { + return netdb.NewRouterInfoAddress (); + } + + boost::shared_ptr 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; + } + } + + 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; - if (m_Caps & eFloodfill) + uint8_t c = GetCaps (); + if (c & eFloodfill) { - if (m_Caps & eExtraBandwidth) caps += (m_Caps & eHighBandwidth) ? + if (c & eExtraBandwidth) caps += (c & eHighBandwidth) ? CAPS_FLAG_EXTRA_BANDWIDTH2 : // 'X' CAPS_FLAG_EXTRA_BANDWIDTH1; // 'P' else @@ -450,108 +1182,146 @@ namespace data } else { - if (m_Caps & eExtraBandwidth) - caps += (m_Caps & eHighBandwidth) ? CAPS_FLAG_EXTRA_BANDWIDTH2 /* 'X' */ : CAPS_FLAG_EXTRA_BANDWIDTH1; /*'P' */ + if (c & eExtraBandwidth) + caps += (c & eHighBandwidth) ? CAPS_FLAG_EXTRA_BANDWIDTH2 /* 'X' */ : CAPS_FLAG_EXTRA_BANDWIDTH1; /*'P' */ else - caps += (m_Caps & eHighBandwidth) ? CAPS_FLAG_HIGH_BANDWIDTH3 /* 'O' */: CAPS_FLAG_LOW_BANDWIDTH2 /* 'L' */; // bandwidth + caps += (c & eHighBandwidth) ? CAPS_FLAG_HIGH_BANDWIDTH3 /* 'O' */: CAPS_FLAG_LOW_BANDWIDTH2 /* 'L' */; // bandwidth } - if (m_Caps & eHidden) caps += CAPS_FLAG_HIDDEN; // hidden - if (m_Caps & eReachable) caps += CAPS_FLAG_REACHABLE; // reachable - if (m_Caps & eUnreachable) caps += CAPS_FLAG_UNREACHABLE; // unreachable + 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); } - void RouterInfo::WriteToStream (std::ostream& s) const + bool LocalRouterInfo::UpdateCongestion (Congestion c) { - uint64_t ts = htobe64 (m_Timestamp); - s.write ((const char *)&ts, sizeof (ts)); - - // addresses - uint8_t numAddresses = m_Addresses->size (); - s.write ((char *)&numAddresses, sizeof (numAddresses)); - for (const auto& addr_ptr : *m_Addresses) + 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; - s.write ((const char *)&address.cost, sizeof (address.cost)); + // 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; - if (address.transportStyle == eTransportNTCP) + bool isPublished = address.published && !address.host.is_unspecified () && address.port; + if (address.transportStyle == eTransportNTCP2) { - if (address.IsNTCP2 ()) - WriteString ("NTCP2", s); - else - continue; // don't write NTCP address - } - else if (address.transportStyle == eTransportSSU) - { - WriteString ("SSU", s); + 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 - WriteString ("caps", properties); - properties << '='; std::string caps; - if (IsPeerTesting ()) caps += CAPS_FLAG_SSU_TESTING; - if (IsIntroducer ()) caps += CAPS_FLAG_SSU_INTRODUCER; - WriteString (caps, properties); - properties << ';'; + 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 (!address.IsNTCP2 () || address.IsPublishedNTCP2 ()) + if (isPublished && !address.host.is_unspecified ()) { WriteString ("host", properties); properties << '='; WriteString (address.host.to_string (), properties); properties << ';'; } - if (address.transportStyle == eTransportSSU) + 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->introducers.size () > 0) + if (address.ssu && !address.ssu->introducers.empty()) { int i = 0; for (const auto& introducer: address.ssu->introducers) { - WriteString ("ihost" + boost::lexical_cast(i), properties); - properties << '='; - WriteString (introducer.iHost.to_string (), properties); - properties << ';'; - i++; - } - i = 0; - for (const auto& introducer: address.ssu->introducers) - { - WriteString ("ikey" + boost::lexical_cast(i), properties); - properties << '='; - char value[64]; - size_t l = ByteStreamToBase64 (introducer.iKey, 32, value, 64); - value[l] = 0; - WriteString (value, properties); - properties << ';'; - i++; - } - i = 0; - for (const auto& introducer: address.ssu->introducers) - { - WriteString ("iport" + boost::lexical_cast(i), properties); - properties << '='; - WriteString (boost::lexical_cast(introducer.iPort), properties); - properties << ';'; - i++; - } - i = 0; - for (const auto& introducer: address.ssu->introducers) - { - WriteString ("itag" + boost::lexical_cast(i), properties); - properties << '='; - WriteString (boost::lexical_cast(introducer.iTag), properties); - properties << ';'; - i++; - } - 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); @@ -561,17 +1331,36 @@ namespace data } 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++; + } } - // write intro key - WriteString ("key", properties); - properties << '='; - char value[64]; - size_t l = ByteStreamToBase64 (address.ssu->key, 32, value, 64); - value[l] = 0; - WriteString (value, properties); - properties << ';'; + } + + if (address.transportStyle == eTransportSSU2) + { // write mtu - if (address.ssu->mtu) + if (address.ssu && address.ssu->mtu) { WriteString ("mtu", properties); properties << '='; @@ -579,26 +1368,18 @@ namespace data properties << ';'; } } - - if (address.IsPublishedNTCP2 ()) - { - // publish i for NTCP2 - WriteString ("i", properties); properties << '='; - WriteString (address.ntcp2->iv.ToBase64 (), properties); properties << ';'; - } - - if (!address.IsNTCP2 () || address.IsPublishedNTCP2 ()) + if (isPublished && address.port) { WriteString ("port", properties); properties << '='; WriteString (boost::lexical_cast(address.port), properties); properties << ';'; } - if (address.IsNTCP2 ()) + if (address.IsNTCP2 () || address.IsSSU2 ()) { - // publish s and v for NTCP2 + // publish s and v for NTCP2 or SSU2 WriteString ("s", properties); properties << '='; - WriteString (address.ntcp2->staticKey.ToBase64 (), properties); properties << ';'; + WriteString (address.s.ToBase64 (), properties); properties << ';'; WriteString ("v", properties); properties << '='; WriteString ("2", properties); properties << ';'; } @@ -626,181 +1407,17 @@ namespace data s.write (properties.str ().c_str (), properties.str ().size ()); } - 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 () - { - if (!m_Buffer) - { - if (LoadFile ()) - LogPrint (eLogDebug, "RouterInfo: Buffer for ", GetIdentHashAbbreviation (GetIdentHash ()), " loaded from file"); - } - return m_Buffer; - } - - void RouterInfo::CreateBuffer (const PrivateKeys& privateKeys) - { - m_Timestamp = i2p::util::GetMillisecondsSinceEpoch (); // refresh timstamp - std::stringstream s; - uint8_t ident[1024]; - auto identLen = privateKeys.GetPublic ()->ToBuffer (ident, 1024); - auto signatureLen = privateKeys.GetPublic ()->GetSignatureLen (); - s.write ((char *)ident, identLen); - WriteToStream (s); - m_BufferLen = s.str ().size (); - if (!m_Buffer) - m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE]; - if (m_BufferLen + signatureLen < MAX_RI_BUFFER_SIZE) - { - memcpy (m_Buffer, s.str ().c_str (), m_BufferLen); - // signature - privateKeys.Sign ((uint8_t *)m_Buffer, m_BufferLen, (uint8_t *)m_Buffer + m_BufferLen); - m_BufferLen += signatureLen; - } - else - LogPrint (eLogError, "RouterInfo: Our RouterInfo is too long ", m_BufferLen + signatureLen); - } - - bool RouterInfo::SaveToFile (const std::string& fullPath) - { - m_FullPath = fullPath; - if (!m_Buffer) { - LogPrint (eLogError, "RouterInfo: Can't save, m_Buffer == NULL"); - return false; - } - std::ofstream f (fullPath, std::ofstream::binary | std::ofstream::out); - if (!f.is_open ()) { - LogPrint(eLogError, "RouterInfo: Can't save to ", fullPath); - return false; - } - f.write ((char *)m_Buffer, m_BufferLen); - return true; - } - - size_t RouterInfo::ReadString (char * str, size_t len, std::istream& s) const - { - uint8_t l; - s.read ((char *)&l, 1); - if (l < len) - { - s.read (str, l); - if (!s) l = 0; // failed, return empty string - str[l] = 0; - } - else - { - LogPrint (eLogWarning, "RouterInfo: string length ", (int)l, " exceeds buffer size ", len); - s.seekg (l, std::ios::cur); // skip - str[0] = 0; - } - return l+1; - } - - void RouterInfo::WriteString (const std::string& str, std::ostream& s) const - { - uint8_t len = str.size (); - s.write ((char *)&len, 1); - s.write (str.c_str (), len); - } - - void RouterInfo::AddSSUAddress (const char * host, int port, const uint8_t * key, int mtu) - { - auto addr = std::make_shared
(); - addr->host = boost::asio::ip::address::from_string (host); - addr->port = port; - addr->transportStyle = eTransportSSU; - addr->cost = 10; // NTCP should have priority over SSU - addr->date = 0; - addr->ssu.reset (new SSUExt ()); - addr->ssu->mtu = mtu; - memcpy (addr->ssu->key, key, 32); - for (const auto& it: *m_Addresses) // don't insert same address twice - if (*it == *addr) return; - m_SupportedTransports |= addr->host.is_v6 () ? eSSUV6 : eSSUV4; - m_Addresses->push_back(std::move(addr)); - - m_Caps |= eSSUTesting; - m_Caps |= eSSUIntroducer; - } - - void RouterInfo::AddNTCP2Address (const uint8_t * staticKey, const uint8_t * iv, const boost::asio::ip::address& host, int port) - { - auto addr = std::make_shared
(); - addr->host = host; - addr->port = port; - addr->transportStyle = eTransportNTCP; - addr->cost = port ? 3 : 14; // override from RouterContext::PublishNTCP2Address - addr->date = 0; - addr->ntcp2.reset (new NTCP2Ext ()); - addr->ntcp2->isNTCP2Only = true; // NTCP2 only address - if (port) addr->ntcp2->isPublished = true; - memcpy (addr->ntcp2->staticKey, staticKey, 32); - memcpy (addr->ntcp2->iv, iv, 16); - m_Addresses->push_back(std::move(addr)); - } - - bool RouterInfo::AddIntroducer (const Introducer& introducer) - { - for (auto& addr : *m_Addresses) - { - if (addr->transportStyle == eTransportSSU && addr->host.is_v4 ()) - { - for (auto& intro: addr->ssu->introducers) - if (intro.iTag == introducer.iTag) return false; // already presented - addr->ssu->introducers.push_back (introducer); - return true; - } - } - return false; - } - - bool RouterInfo::RemoveIntroducer (const boost::asio::ip::udp::endpoint& e) - { - for (auto& addr: *m_Addresses) - { - if (addr->transportStyle == eTransportSSU && addr->host.is_v4 ()) - { - for (auto it = addr->ssu->introducers.begin (); it != addr->ssu->introducers.end (); ++it) - if ( boost::asio::ip::udp::endpoint (it->iHost, it->iPort) == e) - { - addr->ssu->introducers.erase (it); - return true; - } - } - } - return false; - } - - void RouterInfo::SetCaps (uint8_t caps) - { - m_Caps = caps; - UpdateCapsProperty (); - } - - void RouterInfo::SetCaps (const char * caps) - { - SetProperty ("caps", caps); - m_Caps = 0; - ExtractCaps (caps); - } - - void RouterInfo::SetProperty (const std::string& key, const std::string& value) + void LocalRouterInfo::SetProperty (const std::string& key, const std::string& value) { m_Properties[key] = value; } - void RouterInfo::DeleteProperty (const std::string& key) + void LocalRouterInfo::DeleteProperty (const std::string& key) { m_Properties.erase (key); } - std::string RouterInfo::GetProperty (const std::string& key) const + std::string LocalRouterInfo::GetProperty (const std::string& key) const { auto it = m_Properties.find (key); if (it != m_Properties.end ()) @@ -808,142 +1425,66 @@ namespace data return ""; } - bool RouterInfo::IsSSU (bool v4only) const + void LocalRouterInfo::WriteString (const std::string& str, std::ostream& s) const { - if (v4only) - return m_SupportedTransports & eSSUV4; - else - return m_SupportedTransports & (eSSUV4 | eSSUV6); + uint8_t len = str.size (); + s.write ((char *)&len, 1); + s.write (str.c_str (), len); } - bool RouterInfo::IsSSUV6 () const + std::shared_ptr LocalRouterInfo::NewBuffer () const { - return m_SupportedTransports & eSSUV6; + return std::make_shared (); } - bool RouterInfo::IsNTCP2 (bool v4only) const + std::shared_ptr LocalRouterInfo::NewAddress () const { - if (v4only) - return m_SupportedTransports & eNTCP2V4; - else - return m_SupportedTransports & (eNTCP2V4 | eNTCP2V6); + return std::make_shared
(); } - bool RouterInfo::IsV6 () const + boost::shared_ptr LocalRouterInfo::NewAddresses () const { - return m_SupportedTransports & (eNTCPV6 | eSSUV6 | eNTCP2V6); + return boost::make_shared (); } - bool RouterInfo::IsV4 () const + std::shared_ptr LocalRouterInfo::NewIdentity (const uint8_t * buf, size_t len) const { - return m_SupportedTransports & (eNTCPV4 | eSSUV4 | eNTCP2V4); - } - - void RouterInfo::EnableV6 () + return std::make_shared (buf, len); + } + + bool LocalRouterInfo::AddSSU2Introducer (const Introducer& introducer, bool v4) { - if (!IsV6 ()) - m_SupportedTransports |= eNTCPV6 | eSSUV6 | eNTCP2V6; - } - - void RouterInfo::EnableV4 () - { - if (!IsV4 ()) - m_SupportedTransports |= eNTCPV4 | eSSUV4 | eNTCP2V4; - } - - - void RouterInfo::DisableV6 () - { - if (IsV6 ()) + auto addresses = GetAddresses (); + if (!addresses) return false; + auto addr = (*addresses)[v4 ? eSSU2V4Idx : eSSU2V6Idx]; + if (addr) { - m_SupportedTransports &= ~(eNTCPV6 | eSSUV6 | eNTCP2V6); - for (auto it = m_Addresses->begin (); it != m_Addresses->end ();) - { - auto addr = *it; - if (addr->host.is_v6 ()) - it = m_Addresses->erase (it); - else - ++it; - } + 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; } - void RouterInfo::DisableV4 () + bool LocalRouterInfo::RemoveSSU2Introducer (const IdentHash& h, bool v4) { - if (IsV4 ()) + auto addresses = GetAddresses (); + if (!addresses) return false; + auto addr = (*addresses)[v4 ? eSSU2V4Idx : eSSU2V6Idx]; + if (addr) { - m_SupportedTransports &= ~(eNTCPV4 | eSSUV4 | eNTCP2V4); - for (auto it = m_Addresses->begin (); it != m_Addresses->end ();) - { - auto addr = *it; - if (addr->host.is_v4 ()) - it = m_Addresses->erase (it); - else - ++it; - } + 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; + } } - } - - - bool RouterInfo::UsesIntroducer () const - { - return m_Caps & Caps::eUnreachable; // non-reachable - } - - std::shared_ptr RouterInfo::GetSSUAddress (bool v4only) const - { - return GetAddress ( - [v4only](std::shared_ptr address)->bool - { - return (address->transportStyle == eTransportSSU) && (!v4only || address->host.is_v4 ()); - }); - } - - std::shared_ptr RouterInfo::GetSSUV6Address () const - { - return GetAddress ( - [](std::shared_ptr address)->bool - { - return (address->transportStyle == eTransportSSU) && address->host.is_v6 (); - }); - } - - template - std::shared_ptr RouterInfo::GetAddress (Filter filter) const - { - // TODO: make it more generic using comparator -#if (BOOST_VERSION >= 105300) - auto addresses = boost::atomic_load (&m_Addresses); -#else - auto addresses = m_Addresses; -#endif - for (const auto& address : *addresses) - if (filter (address)) return address; - - return nullptr; - } - - std::shared_ptr RouterInfo::GetNTCP2Address (bool publishedOnly, bool v4only) const - { - return GetAddress ( - [publishedOnly, v4only](std::shared_ptr address)->bool - { - return address->IsNTCP2 () && (!publishedOnly || address->IsPublishedNTCP2 ()) && (!v4only || address->host.is_v4 ()); - }); - } - - std::shared_ptr RouterInfo::GetProfile () const - { - if (!m_Profile) - m_Profile = GetRouterProfile (GetIdentHash ()); - return m_Profile; - } - - void RouterInfo::Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx) const - { - auto encryptor = m_RouterIdentity->CreateEncryptor (nullptr); - if (encryptor) - encryptor->Encrypt (data, encrypted, ctx, true); + return false; } } } diff --git a/libi2pd/RouterInfo.h b/libi2pd/RouterInfo.h index 282c34f9..9aff2240 100644 --- a/libi2pd/RouterInfo.h +++ b/libi2pd/RouterInfo.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -13,12 +13,13 @@ #include #include #include -#include +#include #include #include #include #include "Identity.h" #include "Profiling.h" +#include "Family.h" namespace i2p { @@ -43,51 +44,89 @@ namespace data const char CAPS_FLAG_HIGH_BANDWIDTH3 = 'O'; /* 128-256 KBps */ const char CAPS_FLAG_EXTRA_BANDWIDTH1 = 'P'; /* 256-2000 KBps */ const char CAPS_FLAG_EXTRA_BANDWIDTH2 = 'X'; /* > 2000 KBps */ + // congesion flags + const char CAPS_FLAG_MEDIUM_CONGESTION = 'D'; + const char CAPS_FLAG_HIGH_CONGESTION = 'E'; + const char CAPS_FLAG_REJECT_ALL_CONGESTION = 'G'; + + const char CAPS_FLAG_V4 = '4'; + const char CAPS_FLAG_V6 = '6'; + const char CAPS_FLAG_SSU2_TESTING = 'B'; + const char CAPS_FLAG_SSU2_INTRODUCER = 'C'; - const char CAPS_FLAG_SSU_TESTING = 'B'; - const char CAPS_FLAG_SSU_INTRODUCER = 'C'; + const uint8_t COST_NTCP2_PUBLISHED = 3; + const uint8_t COST_NTCP2_NON_PUBLISHED = 14; + const uint8_t COST_SSU2_DIRECT = 8; + const uint8_t COST_SSU2_NON_PUBLISHED = 15; - const int MAX_RI_BUFFER_SIZE = 2048; // if RouterInfo exceeds 2048 we consider it as malformed, might be changed later + 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 SupportedTranports + enum SupportedTransportsIdx { - eNTCPV4 = 0x01, - eNTCPV6 = 0x02, - eSSUV4 = 0x04, - eSSUV6 = 0x08, - eNTCP2V4 = 0x10, - eNTCP2V6 = 0x20 + 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, - eSSUTesting = 0x10, - eSSUIntroducer = 0x20, - eHidden = 0x40, - eUnreachable = 0x80 + 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, - eTransportNTCP, - eTransportSSU + eTransportNTCP2, + eTransportSSU2 }; - typedef Tag<32> IntroKey; // should be castable to MacKey and AESKey struct Introducer { - Introducer (): iExp (0) {}; - boost::asio::ip::address iHost; - int iPort; - IntroKey iKey; + Introducer (): iTag (0), iExp (0) { iH.Fill(0); }; + IdentHash iH; uint32_t iTag; uint32_t iExp; }; @@ -95,37 +134,29 @@ namespace data struct SSUExt { int mtu; - IntroKey key; // intro key for SSU std::vector introducers; }; - struct NTCP2Ext - { - Tag<32> staticKey; - Tag<16> iv; - bool isPublished = false; - bool isNTCP2Only = false; - }; - struct Address { TransportStyle transportStyle; boost::asio::ip::address host; + Tag<32> s, i; // keys, i is first 16 bytes for NTCP2 and 32 bytes intro key for SSU int port; uint64_t date; - uint8_t cost; + uint8_t caps; + bool published = false; std::unique_ptr ssu; // not null for SSU - std::unique_ptr ntcp2; // not null for NTCP2 bool IsCompatible (const boost::asio::ip::address& other) const { - return (host.is_v4 () && other.is_v4 ()) || - (host.is_v6 () && other.is_v6 ()); + return (IsV4 () && other.is_v4 ()) || + (IsV6 () && other.is_v6 ()); } bool operator==(const Address& other) const { - return transportStyle == other.transportStyle && IsNTCP2 () == other.IsNTCP2 () && + return transportStyle == other.transportStyle && host == other.host && port == other.port; } @@ -134,117 +165,202 @@ namespace data return !(*this == other); } - bool IsNTCP2 () const { return (bool)ntcp2; }; - bool IsPublishedNTCP2 () const { return IsNTCP2 () && ntcp2->isPublished; }; - bool IsNTCP2Only () const { return ntcp2 && ntcp2->isNTCP2Only; }; - }; - typedef std::list > Addresses; + 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); + }; + + typedef std::array, eNumTransports> Addresses; - RouterInfo (); RouterInfo (const std::string& fullPath); RouterInfo (const RouterInfo& ) = default; RouterInfo& operator=(const RouterInfo& ) = default; - RouterInfo (const uint8_t * buf, int len); - ~RouterInfo (); + 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; }; - Addresses& GetAddresses () { return *m_Addresses; }; // should be called for local RI only, otherwise must return shared_ptr - std::shared_ptr GetNTCP2Address (bool publishedOnly, bool v4only = true) const; - std::shared_ptr GetSSUAddress (bool v4only = true) const; - std::shared_ptr GetSSUV6Address () const; + virtual void SetProperty (const std::string& key, const std::string& value) {}; + virtual void ClearProperties () {}; + boost::shared_ptr 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 AddSSUAddress (const char * host, int port, const uint8_t * key, int mtu = 0); - void AddNTCP2Address (const uint8_t * staticKey, const uint8_t * iv, const boost::asio::ip::address& host = boost::asio::ip::address(), int port = 0); - bool AddIntroducer (const Introducer& introducer); - bool RemoveIntroducer (const boost::asio::ip::udp::endpoint& e); - void SetProperty (const std::string& key, const std::string& value); // called from RouterContext only - void DeleteProperty (const std::string& key); // called from RouterContext only - std::string GetProperty (const std::string& key) const; // called from RouterContext only - void ClearProperties () { m_Properties.clear (); }; + 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_Caps & Caps::eFloodfill; }; - bool IsReachable () const { return m_Caps & Caps::eReachable; }; - bool IsSSU (bool v4only = true) const; - bool IsSSUV6 () const; + void ResetFlooldFill () { m_Caps &= ~Caps::eFloodfill; }; + bool IsECIES () const { return m_RouterIdentity->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD; }; bool IsNTCP2 (bool v4only = true) const; - bool IsV6 () const; - bool IsV4 () 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; }; bool HasValidAddresses () const { return m_SupportedTransports; }; - bool UsesIntroducer () const; - bool IsIntroducer () const { return m_Caps & eSSUIntroducer; }; - bool IsPeerTesting () const { return m_Caps & eSSUTesting; }; bool IsHidden () const { return m_Caps & eHidden; }; bool IsHighBandwidth () const { return m_Caps & RouterInfo::eHighBandwidth; }; bool IsExtraBandwidth () const { return m_Caps & RouterInfo::eExtraBandwidth; }; + bool IsEligibleFloodfill () const; + bool IsPublished (bool v4) const; + bool IsSSU2PeerTesting (bool v4) const; + bool IsSSU2Introducer (bool v4) const; + bool IsHighCongestion (bool highBandwidth) const; uint8_t GetCaps () const { return m_Caps; }; - void SetCaps (uint8_t caps); - void SetCaps (const char * caps); + 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; }; - const uint8_t * LoadBuffer (); // load if necessary - int GetBufferLen () const { return m_BufferLen; }; - void CreateBuffer (const PrivateKeys& privateKeys); + const uint8_t * GetBuffer () const { return m_Buffer ? m_Buffer->data () : nullptr; }; + const uint8_t * LoadBuffer (const std::string& fullPath); // load if necessary + size_t GetBufferLen () const { return m_BufferLen; }; bool IsUpdated () const { return m_IsUpdated; }; void SetUpdated (bool updated) { m_IsUpdated = updated; }; bool SaveToFile (const std::string& fullPath); std::shared_ptr GetProfile () const; - void SaveProfile () { if (m_Profile) m_Profile->Save (GetIdentHash ()); }; + void DropProfile () { m_Profile = nullptr; }; - void Update (const uint8_t * buf, size_t len); - void DeleteBuffer () { delete[] m_Buffer; m_Buffer = nullptr; }; + bool Update (const uint8_t * buf, size_t len); + void DeleteBuffer () { m_Buffer = nullptr; }; bool IsNewer (const uint8_t * buf, size_t len) const; /** return true if we are in a router family and the signature is valid */ - bool IsFamily(const std::string & fam) const; + bool IsFamily (FamilyID famid) const; // implements RoutingDestination std::shared_ptr GetIdentity () const { return m_RouterIdentity; }; - void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx) const; + 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) { m_BufferLen = len; }; + void RefreshTimestamp (); + CompatibleTransports GetReachableTransports () const { return m_ReachableTransports; }; + void SetReachableTransports (CompatibleTransports transports) { m_ReachableTransports = transports; }; + void SetCongestion (Congestion c) { m_Congestion = c; }; + private: - bool LoadFile (); - void ReadFromFile (); + bool LoadFile (const std::string& fullPath); + void ReadFromFile (const std::string& fullPath); void ReadFromStream (std::istream& s); void ReadFromBuffer (bool verifySignature); - void WriteToStream (std::ostream& s) const; size_t ReadString (char* str, size_t len, std::istream& s) const; - void WriteString (const std::string& str, std::ostream& s) const; void ExtractCaps (const char * value); + uint8_t ExtractAddressCaps (const char * value) const; + void UpdateIntroducers (std::shared_ptr
address, uint64_t ts); template std::shared_ptr GetAddress (Filter filter) const; - void UpdateCapsProperty (); + virtual std::shared_ptr NewBuffer () const; + virtual std::shared_ptr
NewAddress () const; + virtual boost::shared_ptr NewAddresses () const; + virtual std::shared_ptr NewIdentity (const uint8_t * buf, size_t len) const; private: - std::string m_FullPath, m_Family; + FamilyID m_FamilyID; std::shared_ptr m_RouterIdentity; - uint8_t * m_Buffer; + std::shared_ptr m_Buffer; size_t m_BufferLen; - uint64_t m_Timestamp; + uint64_t m_Timestamp; // in milliseconds boost::shared_ptr m_Addresses; // TODO: use std::shared_ptr and std::atomic_store for gcc >= 4.9 - std::map m_Properties; bool m_IsUpdated, m_IsUnreachable; - uint8_t m_SupportedTransports, m_Caps; + CompatibleTransports m_SupportedTransports, m_ReachableTransports; + uint8_t m_Caps; int m_Version; + Congestion m_Congestion; mutable std::shared_ptr m_Profile; }; + + 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 (); }; + + bool AddSSU2Introducer (const Introducer& introducer, bool v4); + bool RemoveSSU2Introducer (const IdentHash& h, bool v4); + + 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; + boost::shared_ptr NewAddresses () const override; + std::shared_ptr NewIdentity (const uint8_t * buf, size_t len) const override; + + private: + + std::map m_Properties; + }; } } diff --git a/libi2pd/SSU.cpp b/libi2pd/SSU.cpp deleted file mode 100644 index 0f4526bd..00000000 --- a/libi2pd/SSU.cpp +++ /dev/null @@ -1,835 +0,0 @@ -/* -* Copyright (c) 2013-2020, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - -#include -#include "Log.h" -#include "Timestamp.h" -#include "RouterContext.h" -#include "NetDb.hpp" -#include "SSU.h" - -namespace i2p -{ -namespace transport -{ - - SSUServer::SSUServer (const boost::asio::ip::address & addr, int port): - m_OnlyV6(true), m_IsRunning(false), - m_Thread (nullptr), m_ThreadV6 (nullptr), m_ReceiversThread (nullptr), - m_ReceiversThreadV6 (nullptr), m_Work (m_Service), m_WorkV6 (m_ServiceV6), - m_ReceiversWork (m_ReceiversService), m_ReceiversWorkV6 (m_ReceiversServiceV6), - m_EndpointV6 (addr, port), m_Socket (m_ReceiversService, m_Endpoint), - m_SocketV6 (m_ReceiversServiceV6), m_IntroducersUpdateTimer (m_Service), - m_PeerTestsCleanupTimer (m_Service), m_TerminationTimer (m_Service), - m_TerminationTimerV6 (m_ServiceV6) - { - OpenSocketV6 (); - } - - SSUServer::SSUServer (int port): - m_OnlyV6(false), m_IsRunning(false), - m_Thread (nullptr), m_ThreadV6 (nullptr), m_ReceiversThread (nullptr), - m_ReceiversThreadV6 (nullptr), m_Work (m_Service), m_WorkV6 (m_ServiceV6), - m_ReceiversWork (m_ReceiversService), m_ReceiversWorkV6 (m_ReceiversServiceV6), - m_Endpoint (boost::asio::ip::udp::v4 (), port), m_EndpointV6 (boost::asio::ip::udp::v6 (), port), - m_Socket (m_ReceiversService), m_SocketV6 (m_ReceiversServiceV6), - m_IntroducersUpdateTimer (m_Service), m_PeerTestsCleanupTimer (m_Service), - m_TerminationTimer (m_Service), m_TerminationTimerV6 (m_ServiceV6) - { - OpenSocket (); - if (context.SupportsV6 ()) - OpenSocketV6 (); - } - - SSUServer::~SSUServer () - { - } - - void SSUServer::OpenSocket () - { - try - { - m_Socket.open (boost::asio::ip::udp::v4()); - m_Socket.set_option (boost::asio::socket_base::receive_buffer_size (SSU_SOCKET_RECEIVE_BUFFER_SIZE)); - m_Socket.set_option (boost::asio::socket_base::send_buffer_size (SSU_SOCKET_SEND_BUFFER_SIZE)); - m_Socket.bind (m_Endpoint); - LogPrint (eLogInfo, "SSU: Start listening v4 port ", m_Endpoint.port()); - } - catch ( std::exception & ex ) - { - LogPrint (eLogError, "SSU: failed to bind to v4 port ", m_Endpoint.port(), ": ", ex.what()); - ThrowFatal ("Unable to start IPv4 SSU transport at port ", m_Endpoint.port(), ": ", ex.what ()); - } - } - - void SSUServer::OpenSocketV6 () - { - try - { - m_SocketV6.open (boost::asio::ip::udp::v6()); - m_SocketV6.set_option (boost::asio::ip::v6_only (true)); - m_SocketV6.set_option (boost::asio::socket_base::receive_buffer_size (SSU_SOCKET_RECEIVE_BUFFER_SIZE)); - m_SocketV6.set_option (boost::asio::socket_base::send_buffer_size (SSU_SOCKET_SEND_BUFFER_SIZE)); - m_SocketV6.bind (m_EndpointV6); - LogPrint (eLogInfo, "SSU: Start listening v6 port ", m_EndpointV6.port()); - } - catch ( std::exception & ex ) - { - LogPrint (eLogError, "SSU: failed to bind to v6 port ", m_EndpointV6.port(), ": ", ex.what()); - ThrowFatal ("Unable to start IPv6 SSU transport at port ", m_Endpoint.port(), ": ", ex.what ()); - } - } - - void SSUServer::Start () - { - m_IsRunning = true; - if (!m_OnlyV6) - { - m_ReceiversThread = new std::thread (std::bind (&SSUServer::RunReceivers, this)); - m_Thread = new std::thread (std::bind (&SSUServer::Run, this)); - m_ReceiversService.post (std::bind (&SSUServer::Receive, this)); - ScheduleTermination (); - } - if (context.SupportsV6 ()) - { - m_ReceiversThreadV6 = new std::thread (std::bind (&SSUServer::RunReceiversV6, this)); - m_ThreadV6 = new std::thread (std::bind (&SSUServer::RunV6, this)); - m_ReceiversServiceV6.post (std::bind (&SSUServer::ReceiveV6, this)); - ScheduleTerminationV6 (); - } - SchedulePeerTestsCleanupTimer (); - ScheduleIntroducersUpdateTimer (); // wait for 30 seconds and decide if we need introducers - } - - void SSUServer::Stop () - { - DeleteAllSessions (); - m_IsRunning = false; - m_TerminationTimer.cancel (); - m_TerminationTimerV6.cancel (); - m_Service.stop (); - m_Socket.close (); - m_ServiceV6.stop (); - m_SocketV6.close (); - m_ReceiversService.stop (); - m_ReceiversServiceV6.stop (); - if (m_ReceiversThread) - { - m_ReceiversThread->join (); - delete m_ReceiversThread; - m_ReceiversThread = nullptr; - } - if (m_Thread) - { - m_Thread->join (); - delete m_Thread; - m_Thread = nullptr; - } - if (m_ReceiversThreadV6) - { - m_ReceiversThreadV6->join (); - delete m_ReceiversThreadV6; - m_ReceiversThreadV6 = nullptr; - } - if (m_ThreadV6) - { - m_ThreadV6->join (); - delete m_ThreadV6; - m_ThreadV6 = nullptr; - } - } - - void SSUServer::Run () - { - while (m_IsRunning) - { - try - { - m_Service.run (); - } - catch (std::exception& ex) - { - LogPrint (eLogError, "SSU: server runtime exception: ", ex.what ()); - } - } - } - - void SSUServer::RunV6 () - { - while (m_IsRunning) - { - try - { - m_ServiceV6.run (); - } - catch (std::exception& ex) - { - LogPrint (eLogError, "SSU: v6 server runtime exception: ", ex.what ()); - } - } - } - - void SSUServer::RunReceivers () - { - while (m_IsRunning) - { - try - { - m_ReceiversService.run (); - } - catch (std::exception& ex) - { - LogPrint (eLogError, "SSU: receivers runtime exception: ", ex.what ()); - if (m_IsRunning) - { - // restart socket - m_Socket.close (); - OpenSocket (); - Receive (); - } - } - } - } - - void SSUServer::RunReceiversV6 () - { - while (m_IsRunning) - { - try - { - m_ReceiversServiceV6.run (); - } - catch (std::exception& ex) - { - LogPrint (eLogError, "SSU: v6 receivers runtime exception: ", ex.what ()); - if (m_IsRunning) - { - m_SocketV6.close (); - OpenSocketV6 (); - ReceiveV6 (); - } - } - } - } - - void SSUServer::AddRelay (uint32_t tag, std::shared_ptr relay) - { - m_Relays[tag] = relay; - } - - void SSUServer::RemoveRelay (uint32_t tag) - { - m_Relays.erase (tag); - } - - std::shared_ptr SSUServer::FindRelaySession (uint32_t tag) - { - auto it = m_Relays.find (tag); - if (it != m_Relays.end ()) - { - if (it->second->GetState () == eSessionStateEstablished) - return it->second; - else - m_Relays.erase (it); - } - return nullptr; - } - - void SSUServer::Send (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& to) - { - if (to.protocol () == boost::asio::ip::udp::v4()) - m_Socket.send_to (boost::asio::buffer (buf, len), to); - else - m_SocketV6.send_to (boost::asio::buffer (buf, len), to); - } - - void SSUServer::Receive () - { - SSUPacket * packet = new SSUPacket (); - m_Socket.async_receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V4), packet->from, - std::bind (&SSUServer::HandleReceivedFrom, this, std::placeholders::_1, std::placeholders::_2, packet)); - } - - void SSUServer::ReceiveV6 () - { - SSUPacket * packet = new SSUPacket (); - m_SocketV6.async_receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V6), packet->from, - std::bind (&SSUServer::HandleReceivedFromV6, this, std::placeholders::_1, std::placeholders::_2, packet)); - } - - void SSUServer::HandleReceivedFrom (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet) - { - if (!ecode) - { - packet->len = bytes_transferred; - std::vector packets; - packets.push_back (packet); - - boost::system::error_code ec; - size_t moreBytes = m_Socket.available(ec); - if (!ec) - { - while (moreBytes && packets.size () < 25) - { - packet = new SSUPacket (); - packet->len = m_Socket.receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V4), packet->from, 0, ec); - if (!ec) - { - packets.push_back (packet); - moreBytes = m_Socket.available(ec); - if (ec) break; - } - else - { - LogPrint (eLogError, "SSU: receive_from error: ", ec.message ()); - delete packet; - break; - } - } - } - - m_Service.post (std::bind (&SSUServer::HandleReceivedPackets, this, packets, &m_Sessions)); - Receive (); - } - else - { - delete packet; - if (ecode != boost::asio::error::operation_aborted) - { - LogPrint (eLogError, "SSU: receive error: ", ecode.message ()); - m_Socket.close (); - OpenSocket (); - Receive (); - } - } - } - - void SSUServer::HandleReceivedFromV6 (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet) - { - if (!ecode) - { - packet->len = bytes_transferred; - std::vector packets; - packets.push_back (packet); - - boost::system::error_code ec; - size_t moreBytes = m_SocketV6.available (ec); - if (!ec) - { - while (moreBytes && packets.size () < 25) - { - packet = new SSUPacket (); - packet->len = m_SocketV6.receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V6), packet->from, 0, ec); - if (!ec) - { - packets.push_back (packet); - moreBytes = m_SocketV6.available(ec); - if (ec) break; - } - else - { - LogPrint (eLogError, "SSU: v6 receive_from error: ", ec.message ()); - delete packet; - break; - } - } - } - - m_ServiceV6.post (std::bind (&SSUServer::HandleReceivedPackets, this, packets, &m_SessionsV6)); - ReceiveV6 (); - } - else - { - delete packet; - if (ecode != boost::asio::error::operation_aborted) - { - LogPrint (eLogError, "SSU: v6 receive error: ", ecode.message ()); - m_SocketV6.close (); - OpenSocketV6 (); - ReceiveV6 (); - } - } - } - - void SSUServer::HandleReceivedPackets (std::vector packets, - std::map > * sessions) - { - std::shared_ptr session; - for (auto& packet: packets) - { - try - { - if (!session || session->GetRemoteEndpoint () != packet->from) // we received packet for other session than previous - { - if (session) - { - session->FlushData (); - session = nullptr; - } - auto it = sessions->find (packet->from); - if (it != sessions->end ()) - session = it->second; - if (!session) - { - session = std::make_shared (*this, packet->from); - session->WaitForConnect (); - (*sessions)[packet->from] = session; - LogPrint (eLogDebug, "SSU: new session from ", packet->from.address ().to_string (), ":", packet->from.port (), " created"); - } - } - session->ProcessNextMessage (packet->buf, packet->len, packet->from); - } - catch (std::exception& ex) - { - LogPrint (eLogError, "SSU: HandleReceivedPackets ", ex.what ()); - if (session) session->FlushData (); - session = nullptr; - } - delete packet; - } - if (session) session->FlushData (); - } - - std::shared_ptr SSUServer::FindSession (std::shared_ptr router) const - { - if (!router) return nullptr; - auto address = router->GetSSUAddress (true); // v4 only - if (!address) return nullptr; - auto session = FindSession (boost::asio::ip::udp::endpoint (address->host, address->port)); - if (session || !context.SupportsV6 ()) - return session; - // try v6 - address = router->GetSSUV6Address (); - if (!address) return nullptr; - return FindSession (boost::asio::ip::udp::endpoint (address->host, address->port)); - } - - std::shared_ptr SSUServer::FindSession (const boost::asio::ip::udp::endpoint& e) const - { - auto& sessions = e.address ().is_v6 () ? m_SessionsV6 : m_Sessions; - auto it = sessions.find (e); - if (it != sessions.end ()) - return it->second; - else - return nullptr; - } - - void SSUServer::CreateSession (std::shared_ptr router, bool peerTest, bool v4only) - { - auto address = router->GetSSUAddress (v4only || !context.SupportsV6 ()); - if (address) - CreateSession (router, address->host, address->port, peerTest); - else - LogPrint (eLogWarning, "SSU: Router ", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), " doesn't have SSU address"); - } - - void SSUServer::CreateSession (std::shared_ptr router, - const boost::asio::ip::address& addr, int port, bool peerTest) - { - if (router) - { - if (router->UsesIntroducer ()) - m_Service.post (std::bind (&SSUServer::CreateSessionThroughIntroducer, this, router, peerTest)); // always V4 thread - else - { - boost::asio::ip::udp::endpoint remoteEndpoint (addr, port); - auto& s = addr.is_v6 () ? m_ServiceV6 : m_Service; - s.post (std::bind (&SSUServer::CreateDirectSession, this, router, remoteEndpoint, peerTest)); - } - } - } - - void SSUServer::CreateDirectSession (std::shared_ptr router, boost::asio::ip::udp::endpoint remoteEndpoint, bool peerTest) - { - auto& sessions = remoteEndpoint.address ().is_v6 () ? m_SessionsV6 : m_Sessions; - auto it = sessions.find (remoteEndpoint); - if (it != sessions.end ()) - { - auto session = it->second; - if (peerTest && session->GetState () == eSessionStateEstablished) - session->SendPeerTest (); - } - else - { - // otherwise create new session - auto session = std::make_shared (*this, remoteEndpoint, router, peerTest); - sessions[remoteEndpoint] = session; - // connect - LogPrint (eLogDebug, "SSU: Creating new session to [", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), "] ", - remoteEndpoint.address ().to_string (), ":", remoteEndpoint.port ()); - session->Connect (); - } - } - - void SSUServer::CreateSessionThroughIntroducer (std::shared_ptr router, bool peerTest) - { - if (router && router->UsesIntroducer ()) - { - auto address = router->GetSSUAddress (true); // v4 only for now - if (address) - { - boost::asio::ip::udp::endpoint remoteEndpoint (address->host, address->port); - auto it = m_Sessions.find (remoteEndpoint); - // check if session is presented already - if (it != m_Sessions.end ()) - { - auto session = it->second; - if (peerTest && session->GetState () == eSessionStateEstablished) - session->SendPeerTest (); - return; - } - // create new session - int numIntroducers = address->ssu->introducers.size (); - if (numIntroducers > 0) - { - uint32_t ts = i2p::util::GetSecondsSinceEpoch (); - std::shared_ptr introducerSession; - const i2p::data::RouterInfo::Introducer * introducer = nullptr; - // we might have a session to introducer already - for (int i = 0; i < numIntroducers; i++) - { - auto intr = &(address->ssu->introducers[i]); - if (intr->iExp > 0 && ts > intr->iExp) continue; // skip expired introducer - boost::asio::ip::udp::endpoint ep (intr->iHost, intr->iPort); - if (ep.address ().is_v4 ()) // ipv4 only - { - if (!introducer) introducer = intr; // we pick first one for now - it = m_Sessions.find (ep); - if (it != m_Sessions.end ()) - { - introducerSession = it->second; - break; - } - } - } - if (!introducer) - { - LogPrint (eLogWarning, "SSU: Can't connect to unreachable router and no ipv4 non-expired introducers presented"); - return; - } - - if (introducerSession) // session found - LogPrint (eLogWarning, "SSU: Session to introducer already exists"); - else // create new - { - LogPrint (eLogDebug, "SSU: Creating new session to introducer ", introducer->iHost); - boost::asio::ip::udp::endpoint introducerEndpoint (introducer->iHost, introducer->iPort); - introducerSession = std::make_shared (*this, introducerEndpoint, router); - m_Sessions[introducerEndpoint] = introducerSession; - } -#if BOOST_VERSION >= 104900 - if (!address->host.is_unspecified () && address->port) -#endif - { - // create session - auto session = std::make_shared (*this, remoteEndpoint, router, peerTest); - m_Sessions[remoteEndpoint] = session; - - // introduce - LogPrint (eLogInfo, "SSU: Introduce new session to [", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), - "] through introducer ", introducer->iHost, ":", introducer->iPort); - session->WaitForIntroduction (); - if (i2p::context.GetRouterInfo ().UsesIntroducer ()) // if we are unreachable - { - uint8_t buf[1]; - Send (buf, 0, remoteEndpoint); // send HolePunch - } - } - introducerSession->Introduce (*introducer, router); - } - else - LogPrint (eLogWarning, "SSU: Can't connect to unreachable router and no introducers present"); - } - else - LogPrint (eLogWarning, "SSU: Router ", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), " doesn't have SSU address"); - } - } - - void SSUServer::DeleteSession (std::shared_ptr session) - { - if (session) - { - session->Close (); - auto& ep = session->GetRemoteEndpoint (); - if (ep.address ().is_v6 ()) - m_SessionsV6.erase (ep); - else - m_Sessions.erase (ep); - } - } - - void SSUServer::DeleteAllSessions () - { - for (auto& it: m_Sessions) - it.second->Close (); - m_Sessions.clear (); - - for (auto& it: m_SessionsV6) - it.second->Close (); - m_SessionsV6.clear (); - } - - template - std::shared_ptr SSUServer::GetRandomV4Session (Filter filter) // v4 only - { - std::vector > filteredSessions; - for (const auto& s :m_Sessions) - if (filter (s.second)) filteredSessions.push_back (s.second); - if (filteredSessions.size () > 0) - { - auto ind = rand () % filteredSessions.size (); - return filteredSessions[ind]; - } - return nullptr; - } - - std::shared_ptr SSUServer::GetRandomEstablishedV4Session (std::shared_ptr excluded) // v4 only - { - return GetRandomV4Session ( - [excluded](std::shared_ptr session)->bool - { - return session->GetState () == eSessionStateEstablished && session != excluded; - } - ); - } - - template - std::shared_ptr SSUServer::GetRandomV6Session (Filter filter) // v6 only - { - std::vector > filteredSessions; - for (const auto& s :m_SessionsV6) - if (filter (s.second)) filteredSessions.push_back (s.second); - if (filteredSessions.size () > 0) - { - auto ind = rand () % filteredSessions.size (); - return filteredSessions[ind]; - } - return nullptr; - } - - std::shared_ptr SSUServer::GetRandomEstablishedV6Session (std::shared_ptr excluded) // v6 only - { - return GetRandomV6Session ( - [excluded](std::shared_ptr session)->bool - { - return session->GetState () == eSessionStateEstablished && session != excluded; - } - ); - } - - std::set SSUServer::FindIntroducers (int maxNumIntroducers) - { - uint32_t ts = i2p::util::GetSecondsSinceEpoch (); - std::set ret; - for (int i = 0; i < maxNumIntroducers; i++) - { - auto session = GetRandomV4Session ( - [&ret, ts](std::shared_ptr session)->bool - { - return session->GetRelayTag () && !ret.count (session.get ()) && - session->GetState () == eSessionStateEstablished && - ts < session->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_DURATION; - } - ); - if (session) - { - ret.insert (session.get ()); - break; - } - } - return ret; - } - - void SSUServer::ScheduleIntroducersUpdateTimer () - { - m_IntroducersUpdateTimer.expires_from_now (boost::posix_time::seconds(SSU_KEEP_ALIVE_INTERVAL)); - m_IntroducersUpdateTimer.async_wait (std::bind (&SSUServer::HandleIntroducersUpdateTimer, - this, std::placeholders::_1)); - } - - void SSUServer::HandleIntroducersUpdateTimer (const boost::system::error_code& ecode) - { - if (ecode != boost::asio::error::operation_aborted) - { - // timeout expired - if (i2p::context.GetStatus () == eRouterStatusTesting) - { - // we still don't know if we need introducers - ScheduleIntroducersUpdateTimer (); - return; - } - if (i2p::context.GetStatus () == eRouterStatusOK) return; // we don't need introducers anymore - // we are firewalled - if (!i2p::context.IsUnreachable ()) i2p::context.SetUnreachable (); - std::list newList; - size_t numIntroducers = 0; - uint32_t ts = i2p::util::GetSecondsSinceEpoch (); - for (const auto& it : m_Introducers) - { - auto session = FindSession (it); - if (session && ts < session->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_DURATION) - { - session->SendKeepAlive (); - newList.push_back (it); - numIntroducers++; - } - else - i2p::context.RemoveIntroducer (it); - } - - if (numIntroducers < SSU_MAX_NUM_INTRODUCERS) - { - // create new - auto introducers = FindIntroducers (SSU_MAX_NUM_INTRODUCERS); - for (const auto& it1: introducers) - { - const auto& ep = it1->GetRemoteEndpoint (); - i2p::data::RouterInfo::Introducer introducer; - introducer.iHost = ep.address (); - introducer.iPort = ep.port (); - introducer.iTag = it1->GetRelayTag (); - introducer.iKey = it1->GetIntroKey (); - if (i2p::context.AddIntroducer (introducer)) - { - newList.push_back (ep); - if (newList.size () >= SSU_MAX_NUM_INTRODUCERS) break; - } - } - } - m_Introducers = newList; - if (m_Introducers.size () < SSU_MAX_NUM_INTRODUCERS) - { - auto introducer = i2p::data::netdb.GetRandomIntroducer (); - if (introducer) - CreateSession (introducer); - } - ScheduleIntroducersUpdateTimer (); - } - } - - void SSUServer::NewPeerTest (uint32_t nonce, PeerTestParticipant role, std::shared_ptr session) - { - m_PeerTests[nonce] = { i2p::util::GetMillisecondsSinceEpoch (), role, session }; - } - - PeerTestParticipant SSUServer::GetPeerTestParticipant (uint32_t nonce) - { - auto it = m_PeerTests.find (nonce); - if (it != m_PeerTests.end ()) - return it->second.role; - else - return ePeerTestParticipantUnknown; - } - - std::shared_ptr SSUServer::GetPeerTestSession (uint32_t nonce) - { - auto it = m_PeerTests.find (nonce); - if (it != m_PeerTests.end ()) - return it->second.session; - else - return nullptr; - } - - void SSUServer::UpdatePeerTest (uint32_t nonce, PeerTestParticipant role) - { - auto it = m_PeerTests.find (nonce); - if (it != m_PeerTests.end ()) - it->second.role = role; - } - - void SSUServer::RemovePeerTest (uint32_t nonce) - { - m_PeerTests.erase (nonce); - } - - void SSUServer::SchedulePeerTestsCleanupTimer () - { - m_PeerTestsCleanupTimer.expires_from_now (boost::posix_time::seconds(SSU_PEER_TEST_TIMEOUT)); - m_PeerTestsCleanupTimer.async_wait (std::bind (&SSUServer::HandlePeerTestsCleanupTimer, - this, std::placeholders::_1)); - } - - void SSUServer::HandlePeerTestsCleanupTimer (const boost::system::error_code& ecode) - { - if (ecode != boost::asio::error::operation_aborted) - { - int numDeleted = 0; - uint64_t ts = i2p::util::GetMillisecondsSinceEpoch (); - for (auto it = m_PeerTests.begin (); it != m_PeerTests.end ();) - { - if (ts > it->second.creationTime + SSU_PEER_TEST_TIMEOUT*1000LL) - { - numDeleted++; - it = m_PeerTests.erase (it); - } - else - ++it; - } - if (numDeleted > 0) - LogPrint (eLogDebug, "SSU: ", numDeleted, " peer tests have been expired"); - SchedulePeerTestsCleanupTimer (); - } - } - - void SSUServer::ScheduleTermination () - { - m_TerminationTimer.expires_from_now (boost::posix_time::seconds(SSU_TERMINATION_CHECK_TIMEOUT)); - m_TerminationTimer.async_wait (std::bind (&SSUServer::HandleTerminationTimer, - this, std::placeholders::_1)); - } - - void SSUServer::HandleTerminationTimer (const boost::system::error_code& ecode) - { - if (ecode != boost::asio::error::operation_aborted) - { - auto ts = i2p::util::GetSecondsSinceEpoch (); - for (auto& it: m_Sessions) - if (it.second->IsTerminationTimeoutExpired (ts)) - { - auto session = it.second; - if (it.first != session->GetRemoteEndpoint ()) - LogPrint (eLogWarning, "SSU: remote endpoint ", session->GetRemoteEndpoint (), " doesn't match key ", it.first, " adjusted"); - m_Service.post ([session] - { - LogPrint (eLogWarning, "SSU: no activity with ", session->GetRemoteEndpoint (), " for ", session->GetTerminationTimeout (), " seconds"); - session->Failed (); - }); - } - ScheduleTermination (); - } - } - - void SSUServer::ScheduleTerminationV6 () - { - m_TerminationTimerV6.expires_from_now (boost::posix_time::seconds(SSU_TERMINATION_CHECK_TIMEOUT)); - m_TerminationTimerV6.async_wait (std::bind (&SSUServer::HandleTerminationTimerV6, - this, std::placeholders::_1)); - } - - void SSUServer::HandleTerminationTimerV6 (const boost::system::error_code& ecode) - { - if (ecode != boost::asio::error::operation_aborted) - { - auto ts = i2p::util::GetSecondsSinceEpoch (); - for (auto& it: m_SessionsV6) - if (it.second->IsTerminationTimeoutExpired (ts)) - { - auto session = it.second; - if (it.first != session->GetRemoteEndpoint ()) - LogPrint (eLogWarning, "SSU: remote endpoint ", session->GetRemoteEndpoint (), " doesn't match key ", it.first); - m_ServiceV6.post ([session] - { - LogPrint (eLogWarning, "SSU: no activity with ", session->GetRemoteEndpoint (), " for ", session->GetTerminationTimeout (), " seconds"); - session->Failed (); - }); - } - ScheduleTerminationV6 (); - } - } -} -} diff --git a/libi2pd/SSU.h b/libi2pd/SSU.h deleted file mode 100644 index 6a79f754..00000000 --- a/libi2pd/SSU.h +++ /dev/null @@ -1,145 +0,0 @@ -/* -* Copyright (c) 2013-2020, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - -#ifndef SSU_H__ -#define SSU_H__ - -#include -#include -#include -#include -#include -#include -#include -#include -#include "Crypto.h" -#include "I2PEndian.h" -#include "Identity.h" -#include "RouterInfo.h" -#include "I2NPProtocol.h" -#include "SSUSession.h" - -namespace i2p -{ -namespace transport -{ - const int SSU_KEEP_ALIVE_INTERVAL = 30; // 30 seconds - const int SSU_PEER_TEST_TIMEOUT = 60; // 60 seconds - const int SSU_TO_INTRODUCER_SESSION_DURATION = 3600; // 1 hour - const int SSU_TERMINATION_CHECK_TIMEOUT = 30; // 30 seconds - const size_t SSU_MAX_NUM_INTRODUCERS = 3; - const size_t SSU_SOCKET_RECEIVE_BUFFER_SIZE = 0x1FFFF; // 128K - const size_t SSU_SOCKET_SEND_BUFFER_SIZE = 0x1FFFF; // 128K - - struct SSUPacket - { - i2p::crypto::AESAlignedBuffer buf; // max MTU + iv + size - boost::asio::ip::udp::endpoint from; - size_t len; - }; - - class SSUServer - { - public: - - SSUServer (int port); - SSUServer (const boost::asio::ip::address & addr, int port); // ipv6 only constructor - ~SSUServer (); - void Start (); - void Stop (); - void CreateSession (std::shared_ptr router, bool peerTest = false, bool v4only = false); - void CreateSession (std::shared_ptr router, - const boost::asio::ip::address& addr, int port, bool peerTest = false); - void CreateDirectSession (std::shared_ptr router, boost::asio::ip::udp::endpoint remoteEndpoint, bool peerTest); - std::shared_ptr FindSession (std::shared_ptr router) const; - std::shared_ptr FindSession (const boost::asio::ip::udp::endpoint& e) const; - std::shared_ptr GetRandomEstablishedV4Session (std::shared_ptr excluded); - std::shared_ptr GetRandomEstablishedV6Session (std::shared_ptr excluded); - void DeleteSession (std::shared_ptr session); - void DeleteAllSessions (); - - boost::asio::io_service& GetService () { return m_Service; }; - boost::asio::io_service& GetServiceV6 () { return m_ServiceV6; }; - const boost::asio::ip::udp::endpoint& GetEndpoint () const { return m_Endpoint; }; - void Send (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& to); - void AddRelay (uint32_t tag, std::shared_ptr relay); - void RemoveRelay (uint32_t tag); - std::shared_ptr FindRelaySession (uint32_t tag); - - void NewPeerTest (uint32_t nonce, PeerTestParticipant role, std::shared_ptr session = nullptr); - PeerTestParticipant GetPeerTestParticipant (uint32_t nonce); - std::shared_ptr GetPeerTestSession (uint32_t nonce); - void UpdatePeerTest (uint32_t nonce, PeerTestParticipant role); - void RemovePeerTest (uint32_t nonce); - - private: - - void OpenSocket (); - void OpenSocketV6 (); - void Run (); - void RunV6 (); - void RunReceivers (); - void RunReceiversV6 (); - void Receive (); - void ReceiveV6 (); - void HandleReceivedFrom (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet); - void HandleReceivedFromV6 (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet); - void HandleReceivedPackets (std::vector packets, - std::map >* sessions); - - void CreateSessionThroughIntroducer (std::shared_ptr router, bool peerTest = false); - template - std::shared_ptr GetRandomV4Session (Filter filter); - template - std::shared_ptr GetRandomV6Session (Filter filter); - - std::set FindIntroducers (int maxNumIntroducers); - void ScheduleIntroducersUpdateTimer (); - void HandleIntroducersUpdateTimer (const boost::system::error_code& ecode); - - void SchedulePeerTestsCleanupTimer (); - void HandlePeerTestsCleanupTimer (const boost::system::error_code& ecode); - - // timer - void ScheduleTermination (); - void HandleTerminationTimer (const boost::system::error_code& ecode); - void ScheduleTerminationV6 (); - void HandleTerminationTimerV6 (const boost::system::error_code& ecode); - - private: - - struct PeerTest - { - uint64_t creationTime; - PeerTestParticipant role; - std::shared_ptr session; // for Bob to Alice - }; - - bool m_OnlyV6; - bool m_IsRunning; - std::thread * m_Thread, * m_ThreadV6, * m_ReceiversThread, * m_ReceiversThreadV6; - boost::asio::io_service m_Service, m_ServiceV6, m_ReceiversService, m_ReceiversServiceV6; - boost::asio::io_service::work m_Work, m_WorkV6, m_ReceiversWork, m_ReceiversWorkV6; - boost::asio::ip::udp::endpoint m_Endpoint, m_EndpointV6; - boost::asio::ip::udp::socket m_Socket, m_SocketV6; - boost::asio::deadline_timer m_IntroducersUpdateTimer, m_PeerTestsCleanupTimer, - m_TerminationTimer, m_TerminationTimerV6; - std::list m_Introducers; // introducers we are connected to - std::map > m_Sessions, m_SessionsV6; - std::map > m_Relays; // we are introducer - std::map m_PeerTests; // nonce -> creation time in milliseconds - - public: - // for HTTP only - const decltype(m_Sessions)& GetSessions () const { return m_Sessions; }; - const decltype(m_SessionsV6)& GetSessionsV6 () const { return m_SessionsV6; }; - }; -} -} - -#endif diff --git a/libi2pd/SSU2.cpp b/libi2pd/SSU2.cpp new file mode 100644 index 00000000..7099ba0c --- /dev/null +++ b/libi2pd/SSU2.cpp @@ -0,0 +1,1454 @@ +/* +* Copyright (c) 2022-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 "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_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)); + m_ReceiveService.GetService ().post( + [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)); + m_ReceiveService.GetService ().post( + [this]() + { + Receive (m_SocketV6); + }); + ScheduleIntroducersUpdateTimerV6 (); // wait for 30 seconds and decide if we need introducers + } + } + else + LogPrint (eLogCritical, "SSU2: Can't start server because port not specified"); + } + } + 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_Introducers.clear (); + m_IntroducersV6.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 (); + } + + 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)); + socket.set_option (boost::asio::socket_base::receive_buffer_size (SSU2_SOCKET_RECEIVE_BUFFER_SIZE)); + socket.set_option (boost::asio::socket_base::send_buffer_size (SSU2_SOCKET_SEND_BUFFER_SIZE)); + socket.bind (localEndpoint); + LogPrint (eLogInfo, "SSU2: Start listening on ", localEndpoint); + } + catch (std::exception& ex ) + { + LogPrint (eLogCritical, "SSU2: Failed to bind to ", localEndpoint, ": ", ex.what()); + ThrowFatal ("Unable to start SSU2 transport on ", localEndpoint, ": ", ex.what ()); + } + 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); + packet->len = bytes_transferred; + + boost::system::error_code ec; + size_t moreBytes = socket.available (ec); + if (!ec && moreBytes) + { + std::vector packets; + packets.push_back (packet); + while (moreBytes && packets.size () < 32) + { + packet = m_PacketsPool.AcquireMt (); + packet->len = socket.receive_from (boost::asio::buffer (packet->buf, SSU2_MAX_PACKET_SIZE), packet->from, 0, ec); + if (!ec) + { + i2p::transport::transports.UpdateReceivedBytes (packet->len); + packets.push_back (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; + } + } + GetService ().post (std::bind (&SSU2Server::HandleReceivedPackets, this, packets)); + } + else + GetService ().post (std::bind (&SSU2Server::HandleReceivedPacket, this, 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::HandleReceivedPacket (Packet * packet) + { + if (packet) + { + if (m_IsThroughProxy) + ProcessNextPacketFromProxy (packet->buf, packet->len); + else + ProcessNextPacket (packet->buf, packet->len, packet->from); + m_PacketsPool.ReleaseMt (packet); + if (m_LastSession && m_LastSession->GetState () != eSSU2SessionStateTerminated) + m_LastSession->FlushData (); + } + } + + void SSU2Server::HandleReceivedPackets (std::vector packets) + { + if (m_IsThroughProxy) + for (auto& packet: packets) + ProcessNextPacketFromProxy (packet->buf, packet->len); + else + for (auto& packet: packets) + ProcessNextPacket (packet->buf, packet->len, packet->from); + m_PacketsPool.ReleaseMt (packets); + if (m_LastSession && m_LastSession->GetState () != eSSU2SessionStateTerminated) + m_LastSession->FlushData (); + } + + void SSU2Server::AddSession (std::shared_ptr session) + { + if (session) + { + m_Sessions.emplace (session->GetConnID (), session); + AddSessionByRouterHash (session); + } + } + + void SSU2Server::RemoveSession (uint64_t connID) + { + auto it = m_Sessions.find (connID); + if (it != m_Sessions.end ()) + { + auto ident = it->second->GetRemoteIdentity (); + if (ident) + m_SessionsByRouterHash.erase (ident->GetIdentHash ()); + if (m_LastSession == it->second) + m_LastSession = nullptr; + m_Sessions.erase (it); + } + } + + void SSU2Server::AddSessionByRouterHash (std::shared_ptr session) + { + if (session) + { + auto ident = session->GetRemoteIdentity (); + if (ident) + { + auto ret = m_SessionsByRouterHash.emplace (ident->GetIdentHash (), session); + if (!ret.second) + { + // session already exists + LogPrint (eLogWarning, "SSU2: Session to ", ident->GetIdentHash ().ToBase64 (), " already exists"); + // terminate existing + GetService ().post (std::bind (&SSU2Session::RequestTermination, ret.first->second, eSSU2TerminationReasonReplacedByNewSession)); + // update session + ret.first->second = session; + } + } + } + } + + bool SSU2Server::AddPendingOutgoingSession (std::shared_ptr session) + { + if (!session) return false; + std::unique_lock l(m_PendingOutgoingSessionsMutex); + return m_PendingOutgoingSessions.emplace (session->GetRemoteEndpoint (), session).second; + } + + std::shared_ptr SSU2Server::FindSession (const i2p::data::IdentHash& ident) const + { + auto it = m_SessionsByRouterHash.find (ident); + if (it != m_SessionsByRouterHash.end ()) + return it->second; + return nullptr; + } + + std::shared_ptr SSU2Server::FindPendingOutgoingSession (const boost::asio::ip::udp::endpoint& ep) const + { + std::unique_lock 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::unique_lock l(m_PendingOutgoingSessionsMutex); + m_PendingOutgoingSessions.erase (ep); + } + + std::shared_ptr SSU2Server::GetRandomSession ( + i2p::data::RouterInfo::CompatibleTransports remoteTransports, const i2p::data::IdentHash& excluded) const + { + if (m_Sessions.empty ()) return nullptr; + uint16_t ind; + RAND_bytes ((uint8_t *)&ind, sizeof (ind)); + ind %= m_Sessions.size (); + auto it = m_Sessions.begin (); + std::advance (it, ind); + while (it != m_Sessions.end ()) + { + if ((it->second->GetRemoteTransports () & 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->GetRemoteTransports () & 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->IsEstablished ()) + return it->second; + else + m_Relays.erase (it); + } + 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 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::unique_lock l(m_PendingOutgoingSessionsMutex); + m_PendingOutgoingSessions.erase (it1); // we are done with that endpoint + } + else + it1->second->ProcessRetry (buf, len); + } + else if (!i2p::util::net::IsInReservedRange(senderEndpoint.address ()) && senderEndpoint.port ()) + { + // 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 (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 (eLogError, "SSU2: Send exception: ", ec.message (), " to ", to); + } + + bool SSU2Server::CreateSession (std::shared_ptr router, + std::shared_ptr address, bool peerTest) + { + if (router && address) + { + // check if no session + auto it = m_SessionsByRouterHash.find (router->GetIdentHash ()); + if (it != m_SessionsByRouterHash.end ()) + { + // session with router found, trying to send peer test if requested + if (peerTest && it->second->IsEstablished ()) + { + auto session = it->second; + GetService ().post ([session]() { session->SendPeerTest (); }); + } + return false; + } + // check is no pending session + bool isValidEndpoint = !address->host.is_unspecified () && address->port; + if (isValidEndpoint) + { + if (i2p::util::net::IsInReservedRange(address->host)) return false; + auto s = FindPendingOutgoingSession (boost::asio::ip::udp::endpoint (address->host, address->port)); + if (s) + { + if (peerTest) + { + // if peer test requested add it to the list for pending session + auto onEstablished = s->GetOnEstablished (); + if (onEstablished) + s->SetOnEstablished ([s, onEstablished]() + { + onEstablished (); + s->SendPeerTest (); + }); + else + s->SetOnEstablished ([s]() { s->SendPeerTest (); }); + } + return false; + } + } + + auto session = std::make_shared (*this, router, address); + if (peerTest) + session->SetOnEstablished ([session]() {session->SendPeerTest (); }); + + if (address->UsesIntroducer ()) + GetService ().post (std::bind (&SSU2Server::ConnectThroughIntroducer, this, session)); + else if (isValidEndpoint) // we can't connect without endpoint + GetService ().post ([session]() { session->Connect (); }); + 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 (); + // try to find existing session first + for (auto& it: address->ssu->introducers) + { + auto it1 = m_SessionsByRouterHash.find (it.iH); + if (it1 != m_SessionsByRouterHash.end ()) + { + it1->second->Introduce (session, it.iTag); + return; + } + } + // we have to start a new session to an introducer + auto ts = i2p::util::GetSecondsSinceEpoch (); + std::shared_ptr r; + uint32_t relayTag = 0; + if (!address->ssu->introducers.empty ()) + { + std::vector indices; + for (int i = 0; i < (int)address->ssu->introducers.size (); i++) indices.push_back(i); + if (indices.size () > 1) + std::shuffle (indices.begin(), indices.end(), std::mt19937(std::random_device()())); + + for (auto i: indices) + { + const auto& introducer = address->ssu->introducers[indices[i]]; + if (introducer.iTag && ts < introducer.iExp) + { + r = i2p::data::netdb.FindRouter (introducer.iH); + if (r && r->IsReachableFrom (i2p::context.GetRouterInfo ())) + { + relayTag = introducer.iTag; + if (relayTag) break; + } + } + } + } + if (r) + { + if (relayTag) + { + // introducer and tag found connect to it through SSU2 + auto addr = address->IsV6 () ? r->GetSSU2V6Address () : r->GetSSU2V4Address (); + if (addr) + { + bool isValidEndpoint = !addr->host.is_unspecified () && addr->port && + !i2p::util::net::IsInReservedRange(addr->host); + if (isValidEndpoint) + { + auto s = FindPendingOutgoingSession (boost::asio::ip::udp::endpoint (addr->host, addr->port)); + if (!s) + { + s = std::make_shared (*this, r, addr); + s->SetOnEstablished ([session, s, relayTag]() { s->Introduce (session, relayTag); }); + s->Connect (); + } + else + { + auto onEstablished = s->GetOnEstablished (); + if (onEstablished) + s->SetOnEstablished ([session, s, relayTag, onEstablished]() + { + onEstablished (); + s->Introduce (session, relayTag); + }); + else + s->SetOnEstablished ([session, s, relayTag]() {s->Introduce (session, relayTag); }); + } + } + } + } + } + else + { + // introducers not found, try to request them + for (auto& it: address->ssu->introducers) + if (it.iTag && ts < it.iExp) + i2p::data::netdb.RequestDestination (it.iH); + } + } + + 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 it = m_SessionsByRouterHash.find (router->GetIdentHash ()); + if (it != m_SessionsByRouterHash.end ()) + { + auto s = it->second; + if (it->second->IsEstablished ()) + GetService ().post ([s]() { s->SendPeerTest (); }); + else + s->SetOnEstablished ([s]() { s->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_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 (); + for (auto it = m_PendingOutgoingSessions.begin (); it != m_PendingOutgoingSessions.end ();) + { + if (it->second->IsTerminationTimeoutExpired (ts)) + { + //it->second->Terminate (); + std::unique_lock l(m_PendingOutgoingSessionsMutex); + 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); + } + + for (auto it = m_SessionsByRouterHash.begin (); it != m_SessionsByRouterHash.begin ();) + { + if (it->second && it->second->GetState () == eSSU2SessionStateTerminated) + it = m_SessionsByRouterHash.erase (it); + else + it++; + } + + ScheduleTermination (); + } + } + + 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 && it->second->GetState () == eSSU2SessionStateTerminated) + it = m_Relays.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++; + } + + 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 : + (SSU2_RESEND_CHECK_TIMEOUT + rand () % 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) + { + 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::list > SSU2Server::FindIntroducers (int maxNumIntroducers, + bool v4, const std::set& excluded) const + { + std::list > ret; + for (const auto& s : m_Sessions) + { + if (s.second->IsEstablished () && (s.second->GetRelayTag () && s.second->IsOutgoing ()) && + !excluded.count (s.second->GetRemoteIdentity ()->GetIdentHash ()) && + ((v4 && (s.second->GetRemoteTransports () & i2p::data::RouterInfo::eSSU2V4)) || + (!v4 && (s.second->GetRemoteTransports () & i2p::data::RouterInfo::eSSU2V6)))) + ret.push_back (s.second); + } + if ((int)ret.size () > maxNumIntroducers) + { + // shink ret randomly + int sz = ret.size () - maxNumIntroducers; + for (int i = 0; i < sz; i++) + { + auto ind = rand () % ret.size (); + auto it = ret.begin (); + std::advance (it, ind); + ret.erase (it); + } + } + 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::set excluded; + for (const auto& it : introducers) + { + std::shared_ptr session; + auto it1 = m_SessionsByRouterHash.find (it); + if (it1 != m_SessionsByRouterHash.end ()) + { + session = it1->second; + excluded.insert (it); + } + if (session && session->IsEstablished () && session->GetRelayTag () && session->IsOutgoing ()) // still session with introducer? + { + if (ts < session->GetCreationTime () + SSU2_TO_INTRODUCER_SESSION_EXPIRATION) + { + session->SendKeepAlive (); + if (ts < session->GetCreationTime () + SSU2_TO_INTRODUCER_SESSION_DURATION) + newList.push_back (it); + else + { + impliedList.push_back (it); // keep in introducers list, but not publish + session = nullptr; + } + } + else + session = nullptr; + } + if (!session) + i2p::context.RemoveSSU2Introducer (it, v4); + } + if (newList.size () < SSU2_MAX_NUM_INTRODUCERS) + { + auto sessions = FindIntroducers (SSU2_MAX_NUM_INTRODUCERS - newList.size (), v4, excluded); + if (sessions.empty () && !introducers.empty ()) + { + // bump creation time for previous introducers if no new sessions found + LogPrint (eLogDebug, "SSU2: No new introducers found. Trying to reuse existing"); + impliedList.clear (); + for (auto& it : introducers) + { + auto it1 = m_SessionsByRouterHash.find (it); + if (it1 != m_SessionsByRouterHash.end ()) + { + auto session = it1->second; + if (session->IsEstablished () && session->GetRelayTag () && session->IsOutgoing ()) + { + session->SetCreationTime (session->GetCreationTime () + SSU2_TO_INTRODUCER_SESSION_DURATION); + if (std::find (newList.begin (), newList.end (), it) == newList.end ()) + sessions.push_back (session); + } + } + } + } + + for (const auto& it : sessions) + { + uint32_t tag = it->GetRelayTag (); + uint32_t exp = it->GetCreationTime () + SSU2_TO_INTRODUCER_SESSION_EXPIRATION; + if (!tag || ts + SSU2_TO_INTRODUCER_SESSION_DURATION/2 > exp) + continue; // don't pick too old session for 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 ()); + if (newList.size () >= SSU2_MAX_NUM_INTRODUCERS) break; + } + } + } + introducers = newList; + + if (introducers.size () < SSU2_MAX_NUM_INTRODUCERS) + { + for (auto i = introducers.size (); i < SSU2_MAX_NUM_INTRODUCERS; 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 + rand () % 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 + rand () % 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 + rand () % 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 + rand () % 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 (); + } + } + } + + 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::address::from_string (address, ecode); + if (!ecode && !addr.is_unspecified () && port) + { + m_IsThroughProxy = true; + m_ProxyEndpoint.reset (new boost::asio::ip::tcp::endpoint (addr, port)); + } + else + { + if (ecode) + LogPrint (eLogError, "SSU2: Invalid proxy address ", address, " ", ecode.message()); + return false; + } + return true; + } +} +} diff --git a/libi2pd/SSU2.h b/libi2pd/SSU2.h new file mode 100644 index 00000000..a1fafc63 --- /dev/null +++ b/libi2pd/SSU2.h @@ -0,0 +1,181 @@ +/* +* Copyright (c) 2022-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 SSU2_H__ +#define SSU2_H__ + +#include +#include +#include "util.h" +#include "SSU2Session.h" + +namespace i2p +{ +namespace transport +{ + const int SSU2_TERMINATION_CHECK_TIMEOUT = 25; // in seconds + const int SSU2_CLEANUP_INTERVAL = 72; // in seconds + const int SSU2_RESEND_CHECK_TIMEOUT = 400; // in milliseconds + const int SSU2_RESEND_CHECK_TIMEOUT_VARIANCE = 100; // in milliseconds + const int SSU2_RESEND_CHECK_MORE_TIMEOUT = 10; // in milliseconds + const size_t SSU2_MAX_RESEND_PACKETS = 128; // packets to resend at the time + const size_t SSU2_SOCKET_RECEIVE_BUFFER_SIZE = 0x1FFFF; // 128K + const size_t SSU2_SOCKET_SEND_BUFFER_SIZE = 0x1FFFF; // 128K + const size_t SSU2_MAX_NUM_INTRODUCERS = 3; + 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 + + 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) {}; + boost::asio::io_service& GetService () { return GetIOService (); }; + void Start () { StartIOService (); }; + void Stop () { StopIOService (); }; + }; + + public: + + SSU2Server (); + ~SSU2Server () {}; + + void Start (); + void Stop (); + boost::asio::io_service& 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 IsSyncClockFromPeers () const { return m_IsSyncClockFromPeers; }; + + void AddSession (std::shared_ptr session); + void RemoveSession (uint64_t connID); + void AddSessionByRouterHash (std::shared_ptr session); + bool AddPendingOutgoingSession (std::shared_ptr session); + void RemovePendingOutgoingSession (const boost::asio::ip::udp::endpoint& ep); + std::shared_ptr FindSession (const i2p::data::IdentHash& ident) const; + std::shared_ptr FindPendingOutgoingSession (const boost::asio::ip::udp::endpoint& ep) const; + std::shared_ptr GetRandomSession (i2p::data::RouterInfo::CompatibleTransports remoteTransports, + const i2p::data::IdentHash& excluded) const; + + void AddRelay (uint32_t tag, std::shared_ptr relay); + void RemoveRelay (uint32_t tag); + std::shared_ptr FindRelaySession (uint32_t tag); + + 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 HandleReceivedPacket (Packet * packet); + void HandleReceivedPackets (std::vector packets); + void ProcessNextPacket (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); + + 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); + + void ConnectThroughIntroducer (std::shared_ptr session); + std::list > FindIntroducers (int maxNumIntroducers, + bool v4, const std::set& excluded) const; + 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; + std::map > m_PendingOutgoingSessions; + mutable std::mutex m_PendingOutgoingSessionsMutex; + std::map > m_IncomingTokens, m_OutgoingTokens; // remote endpoint -> (token, expires in seconds) + std::map > m_Relays; // we are introducer, relay tag -> session + std::list m_Introducers, m_IntroducersV6; // introducers we are connected to + 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; + + // proxy + bool m_IsThroughProxy; + uint8_t m_UDPRequestHeader[SOCKS5_UDP_IPV6_REQUEST_HEADER_SIZE]; + std::unique_ptr m_ProxyEndpoint; + std::unique_ptr m_UDPAssociateSocket; + std::unique_ptr m_ProxyRelayEndpoint; + std::unique_ptr m_ProxyConnectRetryTimer; + + public: + + // for HTTP/I2PControl + const decltype(m_Sessions)& GetSSU2Sessions () const { return m_Sessions; }; + }; +} +} + +#endif diff --git a/libi2pd/SSU2Session.cpp b/libi2pd/SSU2Session.cpp new file mode 100644 index 00000000..f0e539c7 --- /dev/null +++ b/libi2pd/SSU2Session.cpp @@ -0,0 +1,3028 @@ +/* +* Copyright (c) 2022-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 "Log.h" +#include "Transports.h" +#include "Gzip.h" +#include "NetDb.hpp" +#include "SSU2.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): + TransportSession (in_RemoteRouter, SSU2_CONNECT_TIMEOUT), + m_Server (server), m_Address (addr), m_RemoteTransports (0), + m_DestConnID (0), m_SourceConnID (0), m_State (eSSU2SessionStateUnknown), + m_SendPacketNum (0), m_ReceivePacketNum (0), m_LastDatetimeSentPacketNum (0), + m_IsDataReceived (false), m_WindowSize (SSU2_MIN_WINDOW_SIZE), + m_RTT (SSU2_RESEND_INTERVAL), m_RTO (SSU2_RESEND_INTERVAL*SSU2_kAPPA), m_RelayTag (0), + m_ConnectTimer (server.GetService ()), m_TerminationReason (eSSU2TerminationReasonNormalClose), + m_MaxPayloadSize (SSU2_MIN_PACKET_SIZE - IPV6_HEADER_SIZE - UDP_HEADER_SIZE - 32) // min size + { + m_NoiseState.reset (new i2p::crypto::NoiseSymmetricState); + if (in_RemoteRouter && m_Address) + { + // outgoing + InitNoiseXKState1 (*m_NoiseState, m_Address->s); + m_RemoteEndpoint = boost::asio::ip::udp::endpoint (m_Address->host, m_Address->port); + m_RemoteTransports = in_RemoteRouter->GetCompatibleTransports (false); + RAND_bytes ((uint8_t *)&m_DestConnID, 8); + RAND_bytes ((uint8_t *)&m_SourceConnID, 8); + } + else + { + // incoming + InitNoiseXKState1 (*m_NoiseState, i2p::context.GetSSU2StaticPublicKey ()); + } + } + + SSU2Session::~SSU2Session () + { + } + + void SSU2Session::Connect () + { + 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) + { + // 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) return false; + // create nonce + uint32_t nonce; + RAND_bytes ((uint8_t *)&nonce, 4); + auto ts = i2p::util::GetSecondsSinceEpoch (); + // payload + uint8_t payload[SSU2_MAX_PACKET_SIZE]; + size_t payloadSize = 0; + payload[0] = eSSU2BlkRelayRequest; + payload[3] = 0; // flag + htobe32buf (payload + 4, nonce); + htobe32buf (payload + 8, relayTag); + htobe32buf (payload + 12, ts); + payload[16] = 2; // ver + size_t asz = CreateEndpoint (payload + 18, m_MaxPayloadSize - 18, boost::asio::ip::udp::endpoint (localAddress->host, localAddress->port)); + if (!asz) return false; + payload[17] = asz; + 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 + payloadSize); + payloadSize += i2p::context.GetIdentity ()->GetSignatureLen (); + htobe16buf (payload + 1, payloadSize - 3); // size + payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize); + // send + m_RelaySessions.emplace (nonce, std::make_pair (session, ts)); + session->m_SourceConnID = htobe64 (((uint64_t)nonce << 32) | nonce); + session->m_DestConnID = ~session->m_SourceConnID; + m_Server.AddSession (session); + SendData (payload, payloadSize); + + return true; + } + + void SSU2Session::WaitForIntroduction () + { + m_State = eSSU2SessionStateIntroduced; + ScheduleConnectTimer (); + } + + void SSU2Session::ConnectAfterIntroduction () + { + if (m_State == eSSU2SessionStateIntroduced) + { + // create new connID + uint64_t oldConnID = GetConnID (); + RAND_bytes ((uint8_t *)&m_DestConnID, 8); + RAND_bytes ((uint8_t *)&m_SourceConnID, 8); + // connect + m_State = eSSU2SessionStateTokenReceived; + m_Server.AddPendingOutgoingSession (shared_from_this ()); + m_Server.RemoveSession (oldConnID); + Connect (); + } + } + + 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); + session->SetState (eSSU2SessionStatePeerTest); + m_PeerTests.emplace (nonce, std::make_pair (session, ts/1000)); + session->m_SourceConnID = htobe64 (((uint64_t)nonce << 32) | nonce); + session->m_DestConnID = ~session->m_SourceConnID; + 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); + } + } + + 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_SentHandshakePacket.reset (nullptr); + m_SessionConfirmedFragment.reset (nullptr); + m_PathChallenge.reset (nullptr); + m_SendQueue.clear (); + m_SendQueueSize = 0; + m_SentPackets.clear (); + m_IncompleteMessages.clear (); + m_RelaySessions.clear (); + m_PeerTests.clear (); + m_ReceivedI2NPMsgIDs.clear (); + m_Server.RemoveSession (m_SourceConnID); + transports.PeerDisconnected (shared_from_this ()); + auto remoteIdentity = GetRemoteIdentity (); + if (remoteIdentity) + { + LogPrint (eLogDebug, "SSU2: Session with ", GetRemoteEndpoint (), + " (", i2p::data::GetIdentHashAbbreviation (GetRemoteIdentity ()->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; + } + + 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); + transports.PeerConnected (shared_from_this ()); + if (m_OnEstablished) + { + m_OnEstablished (); + m_OnEstablished = nullptr; + } + LogPrint(eLogDebug, "SSU2: Session with ", GetRemoteEndpoint (), + " (", i2p::data::GetIdentHashAbbreviation (GetRemoteIdentity ()->GetIdentHash ()), ") established"); + } + + void SSU2Session::Done () + { + m_Server.GetService ().post (std::bind (&SSU2Session::Terminate, shared_from_this ())); + } + + void SSU2Session::SendLocalRouterInfo (bool update) + { + if (update || !IsOutgoing ()) + { + auto s = shared_from_this (); + m_Server.GetService ().post ([s]() + { + if (!s->IsEstablished ()) return; + uint8_t payload[SSU2_MAX_PACKET_SIZE]; + size_t payloadSize = s->CreateRouterInfoBlock (payload, s->m_MaxPayloadSize - 32, i2p::context.GetSharedRouterInfo ()); + 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 (const std::vector >& msgs) + { + m_Server.GetService ().post (std::bind (&SSU2Session::PostI2NPMessages, shared_from_this (), msgs)); + } + + void SSU2Session::PostI2NPMessages (std::vector > msgs) + { + if (m_State == eSSU2SessionStateTerminated) return; + for (auto it: msgs) + m_SendQueue.push_back (std::move (it)); + SendQueue (); + + if (m_SendQueue.size () > 0) // windows is full + { + if (m_SendQueue.size () <= SSU2_MAX_OUTGOING_QUEUE_SIZE) + Resend (i2p::util::GetMillisecondsSinceEpoch ()); + else + { + LogPrint (eLogWarning, "SSU2: Outgoing messages queue size to ", + GetIdentHashBase64(), " exceeds ", SSU2_MAX_OUTGOING_QUEUE_SIZE); + RequestTermination (eSSU2TerminationReasonTimeout); + } + } + m_SendQueueSize = m_SendQueue.size (); + } + + bool SSU2Session::SendQueue () + { + if (!m_SendQueue.empty () && m_SentPackets.size () <= m_WindowSize) + { + auto ts = i2p::util::GetMillisecondsSinceEpoch (); + 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) + { + 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 ? (rand () % 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 ? (rand () % 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) + { + // 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*m_RTO) + { + if (it->second->numResends > SSU2_MAX_NUM_RESENDS) + { + LogPrint (eLogInfo, "SSU2: Packet was not Acked after ", it->second->numResends, " attempts. Terminate session"); + m_SentPackets.clear (); + m_SendQueue.clear (); + m_SendQueueSize = 0; + RequestTermination (eSSU2TerminationReasonTimeout); + return resentPackets.size (); + } + else + { + uint32_t packetNum = SendData (it->second->payload, it->second->payloadSize); + it->second->numResends++; + it->second->sendTime = ts; + resentPackets.emplace (packetNum, it->second); + it = m_SentPackets.erase (it); + } + } + else + it++; + if (!resentPackets.empty ()) + { +#if (__cplusplus >= 201703L) // C++ 17 or higher + m_SentPackets.merge (resentPackets); +#else + m_SentPackets.insert (resentPackets.begin (), resentPackets.end ()); +#endif + 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]; + i2p::crypto::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 ()) + { + // 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)); + i2p::crypto::ChaCha20 (headerX, 48, m_Address->i, nonce, headerX); + m_NoiseState->MixHash (payload, payloadSize); // h = SHA256(h || encrypted payload from Session Request) for SessionCreated + 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]; + i2p::crypto::ChaCha20 (buf + 16, 48, i2p::context.GetSSU2IntroKey (), nonce, headerX); + memcpy (&m_DestConnID, headerX, 8); + uint64_t token; + memcpy (&token, headerX + 8, 8); + if (!token || token != m_Server.GetIncomingToken (m_RemoteEndpoint)) + { + LogPrint (eLogDebug, "SSU2: SessionRequest token mismatch. Retry"); + SendRetry (); + return; + } + // KDF for session request + m_NoiseState->MixHash ( { {header.buf, 16}, {headerX, 16} } ); // h = SHA256(h || header) + m_NoiseState->MixHash (headerX + 16, 32); // h = SHA256(h || aepk); + uint8_t sharedSecret[32]; + i2p::context.GetSSU2StaticKeys ().Agree (headerX + 16, sharedSecret); + m_NoiseState->MixKey (sharedSecret); + // decrypt + uint8_t * payload = buf + 64; + std::vector decryptedPayload(len - 80); + if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len - 80, m_NoiseState->m_H, 32, + m_NoiseState->m_CK + 32, nonce, decryptedPayload.data (), decryptedPayload.size (), false)) + { + LogPrint (eLogWarning, "SSU2: SessionRequest AEAD verification failed "); + return; + } + m_NoiseState->MixHash (payload, len - 64); // h = SHA256(h || encrypted payload from Session Request) for SessionCreated + // payload + 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)); + i2p::crypto::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]; + i2p::crypto::ChaCha20 (buf + 16, 48, kh2, nonce, headerX); + // KDF for SessionCreated + m_NoiseState->MixHash ( { {header.buf, 16}, {headerX, 16} } ); // h = SHA256(h || header) + m_NoiseState->MixHash (headerX + 16, 32); // h = SHA256(h || bepk); + uint8_t sharedSecret[32]; + m_EphemeralKeys->Agree (headerX + 16, sharedSecret); + m_NoiseState->MixKey (sharedSecret); + // decrypt + uint8_t * payload = buf + 64; + std::vector decryptedPayload(len - 80); + if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len - 80, m_NoiseState->m_H, 32, + m_NoiseState->m_CK + 32, nonce, decryptedPayload.data (), decryptedPayload.size (), false)) + { + LogPrint (eLogWarning, "SSU2: SessionCreated AEAD verification failed "); + 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.GetSharedRouterInfo ()); + if (!payloadSize) + { + // split by two fragments + maxPayloadSize += m_MaxPayloadSize; + payloadSize = CreateRouterInfoBlock (payload, maxPayloadSize, i2p::context.GetSharedRouterInfo ()); + 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 - (rand () % 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; + } + 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 + { + LogPrint (eLogError, "SSU2: Host mismatch between published address ", m_Address->host, + " and actual endpoint ", m_RemoteEndpoint.address (), " from ", i2p::data::GetIdentHashAbbreviation (ri->GetIdentHash ())); + return false; + } + // update RouterInfo in netdb + ri = i2p::data::netdb.AddRouterInfo (ri->GetBuffer (), ri->GetBufferLen ()); // ri points to one from netdb now + if (!ri) + { + LogPrint (eLogError, "SSU2: Couldn't update RouterInfo from SessionConfirmed in netdb"); + return false; + } + SetRemoteIdentity (ri->GetRouterIdentity ()); + AdjustMaxPayloadSize (); + m_Server.AddSessionByRouterHash (shared_from_this ()); // we know remote router now + m_RemoteTransports = ri->GetCompatibleTransports (false); + // 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); + i2p::crypto::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); + i2p::crypto::ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, h + 16); + memcpy (&m_DestConnID, h + 16, 8); + // decrypt + CreateNonce (be32toh (header.h.packetNum), nonce); + uint8_t * payload = buf + 32; + if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len - 48, h, 32, + i2p::context.GetSSU2IntroKey (), nonce, payload, len - 48, false)) + { + LogPrint (eLogWarning, "SSU2: TokenRequest AEAD verification failed "); + return; + } + // payload + 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); + i2p::crypto::ChaCha20 (h + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, h + 16); + // send + m_Server.Send (header.buf, 16, h + 16, 16, payload, payloadSize, m_RemoteEndpoint); + } + + bool SSU2Session::ProcessRetry (uint8_t * buf, size_t len) + { + // we are Alice + Header header; + memcpy (header.buf, buf, 16); + header.ll[0] ^= CreateHeaderMask (m_Address->i, buf + (len - 24)); + header.ll[1] ^= CreateHeaderMask (m_Address->i, buf + (len - 12)); + if (header.h.type != eSSU2Retry) + { + LogPrint (eLogWarning, "SSU2: Unexpected message type ", (int)header.h.type, " 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 + i2p::crypto::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; + } + + void SSU2Session::SendHolePunch (uint32_t nonce, const boost::asio::ip::udp::endpoint& ep, + const uint8_t * introKey, uint64_t token) + { + // we are Charlie + LogPrint (eLogDebug, "SSU2: Sending HolePunch to ", ep); + Header header; + uint8_t h[32], payload[SSU2_MAX_PACKET_SIZE]; + // fill packet + header.h.connID = htobe64 (((uint64_t)nonce << 32) | nonce); // dest id + RAND_bytes (header.buf + 8, 4); // random packet num + header.h.type = eSSU2HolePunch; + header.h.flags[0] = 2; // ver + header.h.flags[1] = (uint8_t)i2p::context.GetNetID (); // netID + header.h.flags[2] = 0; // flag + memcpy (h, header.buf, 16); + uint64_t c = ~header.h.connID; + memcpy (h + 16, &c, 8); // source id + RAND_bytes (h + 24, 8); // token + // payload + payload[0] = eSSU2BlkDateTime; + htobe16buf (payload + 1, 4); + htobe32buf (payload + 3, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000); + size_t payloadSize = 7; + payloadSize += CreateAddressBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize, ep); + payloadSize += CreateRelayResponseBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize, + eSSU2RelayResponseCodeAccept, nonce, token, ep.address ().is_v4 ()); + payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize); + // encrypt + uint8_t n[12]; + CreateNonce (be32toh (header.h.packetNum), n); + i2p::crypto::AEADChaCha20Poly1305 (payload, payloadSize, h, 32, introKey, n, payload, payloadSize + 16, true); + payloadSize += 16; + header.ll[0] ^= CreateHeaderMask (introKey, payload + (payloadSize - 24)); + header.ll[1] ^= CreateHeaderMask (introKey, payload + (payloadSize - 12)); + memset (n, 0, 12); + i2p::crypto::ChaCha20 (h + 16, 16, introKey, n, h + 16); + // send + m_Server.Send (header.buf, 16, h + 16, 16, payload, payloadSize, ep); + } + + bool SSU2Session::ProcessHolePunch (uint8_t * buf, size_t len) + { + // we are Alice + 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 + i2p::crypto::ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, (uint8_t *)headerX); + m_DestConnID = headerX[0]; + // decrypt and handle payload + uint8_t * payload = buf + 32; + CreateNonce (be32toh (header.h.packetNum), nonce); + uint8_t h[32]; + memcpy (h, header.buf, 16); + memcpy (h + 16, &headerX, 16); + if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len - 48, h, 32, + i2p::context.GetSSU2IntroKey (), nonce, payload, len - 48, false)) + { + LogPrint (eLogWarning, "SSU2: HolePunch AEAD verification failed "); + return false; + } + HandlePayload (payload, len - 48); + // connect to Charlie + ConnectAfterIntroduction (); + + return true; + } + + void SSU2Session::SendPeerTest (uint8_t msg, const uint8_t * signedData, size_t signedDataLen, const uint8_t * introKey) + { + Header header; + uint8_t h[32], payload[SSU2_MAX_PACKET_SIZE]; + // fill packet + header.h.connID = m_DestConnID; // dest id + RAND_bytes (header.buf + 8, 4); // random packet num + header.h.type = eSSU2PeerTest; + header.h.flags[0] = 2; // ver + header.h.flags[1] = (uint8_t)i2p::context.GetNetID (); // netID + header.h.flags[2] = 0; // flag + memcpy (h, header.buf, 16); + memcpy (h + 16, &m_SourceConnID, 8); // source id + // payload + payload[0] = eSSU2BlkDateTime; + htobe16buf (payload + 1, 4); + htobe32buf (payload + 3, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000); + size_t payloadSize = 7; + if (msg == 6 || msg == 7) + payloadSize += CreateAddressBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize, m_RemoteEndpoint); + payloadSize += CreatePeerTestBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize, + msg, eSSU2PeerTestCodeAccept, nullptr, signedData, signedDataLen); + payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize); + // encrypt + uint8_t n[12]; + CreateNonce (be32toh (header.h.packetNum), n); + i2p::crypto::AEADChaCha20Poly1305 (payload, payloadSize, h, 32, introKey, n, payload, payloadSize + 16, true); + payloadSize += 16; + header.ll[0] ^= CreateHeaderMask (introKey, payload + (payloadSize - 24)); + header.ll[1] ^= CreateHeaderMask (introKey, payload + (payloadSize - 12)); + memset (n, 0, 12); + i2p::crypto::ChaCha20 (h + 16, 16, introKey, n, h + 16); + // send + m_Server.Send (header.buf, 16, h + 16, 16, payload, payloadSize, m_RemoteEndpoint); + } + + bool SSU2Session::ProcessPeerTest (uint8_t * buf, size_t len) + { + // we are Alice or Charlie + Header header; + memcpy (header.buf, buf, 16); + header.ll[0] ^= CreateHeaderMask (i2p::context.GetSSU2IntroKey (), buf + (len - 24)); + header.ll[1] ^= CreateHeaderMask (i2p::context.GetSSU2IntroKey (), buf + (len - 12)); + if (header.h.type != eSSU2PeerTest) + { + LogPrint (eLogWarning, "SSU2: Unexpected message type ", (int)header.h.type, " instead ", (int)eSSU2PeerTest); + return false; + } + if (len < 48) + { + LogPrint (eLogWarning, "SSU2: PeerTest message too short ", len); + return false; + } + uint8_t nonce[12] = {0}; + uint64_t headerX[2]; // sourceConnID, token + i2p::crypto::ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, (uint8_t *)headerX); + m_DestConnID = headerX[0]; + // decrypt and handle payload + uint8_t * payload = buf + 32; + CreateNonce (be32toh (header.h.packetNum), nonce); + uint8_t h[32]; + memcpy (h, header.buf, 16); + memcpy (h + 16, &headerX, 16); + if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len - 48, h, 32, + i2p::context.GetSSU2IntroKey (), nonce, payload, len - 48, false)) + { + LogPrint (eLogWarning, "SSU2: PeerTest AEAD verification failed "); + return false; + } + HandlePayload (payload, len - 48); + return true; + } + + 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]; + i2p::crypto::AEADChaCha20Poly1305 (buf, len, header.buf, 16, m_KeyDataSend, nonce, payload, SSU2_MAX_PACKET_SIZE, true); + header.ll[0] ^= CreateHeaderMask (m_Address->i, payload + (len - 8)); + header.ll[1] ^= CreateHeaderMask (m_KeyDataSend + 32, payload + (len + 4)); + m_Server.Send (header.buf, 16, payload, len + 16, m_RemoteEndpoint); + m_SendPacketNum++; + m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch (); + m_NumSentBytes += len + 32; + return m_SendPacketNum - 1; + } + + void SSU2Session::ProcessData (uint8_t * buf, size_t len, 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::util::net::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 (!i2p::crypto::AEADChaCha20Poly1305 (buf + 16, payloadSize, header.buf, 16, + m_KeyDataReceive, nonce, payload, payloadSize, false)) + { + LogPrint (eLogWarning, "SSU2: Data AEAD verification failed "); + return; + } + m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch (); + m_NumReceivedBytes += len; + if (!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: + { + // not from SessionConfirmed, we must add it instantly to use in next block + LogPrint (eLogDebug, "SSU2: RouterInfo"); + auto ri = ExtractRouterInfo (buf + offset, size); + if (ri) + i2p::data::netdb.AddRouterInfo (ri->GetBuffer (), ri->GetBufferLen ()); // TODO: add ri + break; + } + 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); + break; + case eSSU2BlkRelayResponse: + LogPrint (eLogDebug, "SSU2: RelayResponse"); + HandleRelayResponse (buf + offset, size); + break; + case eSSU2BlkRelayIntro: + LogPrint (eLogDebug, "SSU2: RelayIntro"); + HandleRelayIntro (buf + offset, size); + break; + case eSSU2BlkPeerTest: + LogPrint (eLogDebug, "SSU2: PeerTest 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) + { + 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: Clock adjusted by ", -offset, " seconds"); + i2p::util::AdjustTimeOffset (-offset); + } + } + 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::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; + 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; + m_RTT = std::round ((m_RTT*m_SendPacketNum + rtt)/(m_SendPacketNum + 1.0)); + m_RTO = m_RTT*SSU2_kAPPA; + 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::util::net::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 + uint32_t relayTag = bufbe32toh (buf + 5); // relay tag + auto session = m_Server.FindRelaySession (relayTag); + if (!session) + { + LogPrint (eLogWarning, "SSU2: RelayRequest session with relay tag ", relayTag, " not found"); + // send relay response back to Alice + uint8_t payload[SSU2_MAX_PACKET_SIZE]; + size_t payloadSize = CreateRelayResponseBlock (payload, m_MaxPayloadSize, + eSSU2RelayResponseCodeBobRelayTagNotFound, bufbe32toh (buf + 1), 0, false); + payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize); + SendData (payload, payloadSize); + return; + } + session->m_RelaySessions.emplace (bufbe32toh (buf + 1), // nonce + std::make_pair (shared_from_this (), i2p::util::GetSecondsSinceEpoch ()) ); + + // send relay intro to Charlie + auto r = i2p::data::netdb.FindRouter (GetRemoteIdentity ()->GetIdentHash ()); // Alice's RI + if (r && (r->IsUnreachable () || !i2p::data::netdb.PopulateRouterInfoBuffer (r))) r = nullptr; + if (!r) LogPrint (eLogWarning, "SSU2: RelayRequest Alice's router info not found"); + + uint8_t payload[SSU2_MAX_PACKET_SIZE]; + size_t payloadSize = r ? CreateRouterInfoBlock (payload, m_MaxPayloadSize - len - 32, r) : 0; + if (!payloadSize && r) + session->SendFragmentedMessage (CreateDatabaseStoreMsg (r)); + payloadSize += CreateRelayIntroBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize, buf + 1, len -1); + if (payloadSize < m_MaxPayloadSize) + payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize); + session->SendData (payload, payloadSize); + } + + void SSU2Session::HandleRelayIntro (const uint8_t * buf, size_t len, int attempts) + { + // we are Charlie + SSU2RelayResponseCode code = eSSU2RelayResponseCodeAccept; + uint64_t token = 0; + bool isV4 = false; + 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)) + { + // send HolePunch + boost::asio::ip::udp::endpoint ep; + if (ExtractEndpoint (buf + 47, asz, ep)) + { + auto addr = ep.address ().is_v6 () ? r->GetSSU2V6Address () : r->GetSSU2V4Address (); + if (addr) + { + if (m_Server.IsSupported (ep.address ())) + { + token = m_Server.GetIncomingToken (ep); + isV4 = ep.address ().is_v4 (); + SendHolePunch (bufbe32toh (buf + 33), ep, addr->i, token); + } + else + { + LogPrint (eLogWarning, "SSU2: RelayIntro unsupported address"); + code = eSSU2RelayResponseCodeCharlieUnsupportedAddress; + } + } + else + { + LogPrint (eLogWarning, "SSU2: RelayIntro unknown address"); + 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 (); + m_Server.GetService ().post ([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 + uint8_t payload[SSU2_MAX_PACKET_SIZE]; + size_t payloadSize = CreateRelayResponseBlock (payload, m_MaxPayloadSize, + code, bufbe32toh (buf + 33), token, isV4); + payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize); + SendData (payload, payloadSize); + } + + 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 + uint8_t payload[SSU2_MAX_PACKET_SIZE]; + payload[0] = eSSU2BlkRelayResponse; + htobe16buf (payload + 1, len); + memcpy (payload + 3, buf, len); // forward to Alice as is + size_t payloadSize = len + 3; + payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize); + it->second.first->SendData (payload, payloadSize); + } + 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]); + it->second.first->Done (); + } + } + m_RelaySessions.erase (it); + } + else + LogPrint (eLogWarning, "SSU2: RelayResponse unknown nonce ", bufbe32toh (buf + 2)); + } + + void SSU2Session::HandlePeerTest (const uint8_t * buf, size_t len) + { + 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.GetRandomSession ((buf[12] == 6) ? i2p::data::RouterInfo::eSSU2V4 : i2p::data::RouterInfo::eSSU2V6, + GetRemoteIdentity ()->GetIdentHash ()); + if (session) // session with Charlie + { + session->m_PeerTests.emplace (nonce, std::make_pair (shared_from_this (), i2p::util::GetSecondsSinceEpoch ())); + auto packet = m_Server.GetSentPacketsPool ().AcquireShared (); + // Alice's RouterInfo + auto r = i2p::data::netdb.FindRouter (GetRemoteIdentity ()->GetIdentHash ()); + if (r && (r->IsUnreachable () || !i2p::data::netdb.PopulateRouterInfoBuffer (r))) r = nullptr; + packet->payloadSize = r ? CreateRouterInfoBlock (packet->payload, m_MaxPayloadSize - len - 32, r) : 0; + if (!packet->payloadSize && r) + session->SendFragmentedMessage (CreateDatabaseStoreMsg (r)); + if (packet->payloadSize + len + 48 > m_MaxPayloadSize) + { + // doesn't fit one message, send RouterInfo in separate message + 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 + { + // Charlie not found, send error back to Alice + uint8_t payload[SSU2_MAX_PACKET_SIZE], zeroHash[32] = {0}; + size_t payloadSize = CreatePeerTestBlock (payload, m_MaxPayloadSize, 4, + eSSU2PeerTestCodeBobNoCharlieAvailable, zeroHash, buf + offset, len - offset); + payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize); + SendData (payload, payloadSize); + } + 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)) + addr = r->GetSSU2Address (ep.address ().is_v4 ()); + if (addr && m_Server.IsSupported (ep.address ())) + { + // send msg 5 to Alice + auto session = std::make_shared (m_Server, r, addr); + session->SetState (eSSU2SessionStatePeerTest); + session->m_RemoteEndpoint = ep; // might be different + session->m_DestConnID = htobe64 (((uint64_t)nonce << 32) | nonce); + session->m_SourceConnID = ~session->m_DestConnID; + m_Server.AddSession (session); + session->SendPeerTest (5, newSignedData.data (), newSignedData.size (), addr->i); + } + else + code = eSSU2PeerTestCodeCharlieUnsupportedAddress; + } + else + code = eSSU2PeerTestCodeCharlieAliceIsAlreadyConnected; + } + else + code = eSSU2PeerTestCodeCharlieSignatureFailure; + } + else // maformed message + code = eSSU2PeerTestCodeCharlieReasonUnspecified; + } + else + code = eSSU2PeerTestCodeCharlieAliceIsUnknown; + // send msg 3 back to Bob + uint8_t payload[SSU2_MAX_PACKET_SIZE]; + size_t payloadSize = CreatePeerTestBlock (payload, m_MaxPayloadSize, 3, + code, nullptr, newSignedData.data (), newSignedData.size ()); + payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize); + SendData (payload, payloadSize); + break; + } + case 3: // Bob from Charlie + { + auto it = m_PeerTests.find (nonce); + if (it != m_PeerTests.end () && it->second.first) + { + uint8_t payload[SSU2_MAX_PACKET_SIZE]; + // Charlie's RouterInfo + auto r = i2p::data::netdb.FindRouter (GetRemoteIdentity ()->GetIdentHash ()); + if (r && (r->IsUnreachable () || !i2p::data::netdb.PopulateRouterInfoBuffer (r))) r = nullptr; + size_t payloadSize = r ? CreateRouterInfoBlock (payload, m_MaxPayloadSize - len - 32, r) : 0; + if (!payloadSize && r) + it->second.first->SendFragmentedMessage (CreateDatabaseStoreMsg (r)); + if (payloadSize + len + 16 > m_MaxPayloadSize) + { + // doesn't fit one message, send RouterInfo in separate message + it->second.first->SendData (payload, payloadSize); + payloadSize = 0; + } + // PeerTest to Alice + payloadSize += CreatePeerTestBlock (payload + payloadSize, m_MaxPayloadSize, 4, + (SSU2PeerTestCode)buf[1], GetRemoteIdentity ()->GetIdentHash (), buf + offset, len - offset); + if (payloadSize < m_MaxPayloadSize) + payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize); + it->second.first->SendData (payload, payloadSize); + m_PeerTests.erase (it); + } + else + LogPrint (eLogWarning, "SSU2: Unknown peer test 3 nonce ", nonce); + break; + } + case 4: // Alice from Bob + { + auto it = m_PeerTests.find (nonce); + if (it != m_PeerTests.end ()) + { + if (buf[1] == eSSU2PeerTestCodeAccept) + { + if (GetRouterStatus () == eRouterStatusUnknown) + SetTestingState (true); + auto r = i2p::data::netdb.FindRouter (buf + 3); // find Charlie + if (r && it->second.first) + { + 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)) + { + it->second.first->SetRemoteIdentity (r->GetIdentity ()); + auto addr = r->GetSSU2Address (m_Address->IsV4 ()); + if (addr) + { + it->second.first->m_Address = addr; + if (it->second.first->m_State == eSSU2SessionStatePeerTestReceived) + { + // msg 5 already received. send msg 6 + SetRouterStatus (eRouterStatusOK); + it->second.first->m_State = eSSU2SessionStatePeerTest; + it->second.first->SendPeerTest (6, buf + offset, len - offset, addr->i); + } + else + { + if (GetTestingState ()) + { + SetTestingState (false); + if (GetRouterStatus () != eRouterStatusFirewalled) + { + SetRouterStatus (eRouterStatusFirewalled); + 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"); + it->second.first->Done (); + } + } + else + { + LogPrint (eLogWarning, "SSU2: Peer test 4 signature verification failed"); + it->second.first->Done (); + } + } + else + { + LogPrint (eLogWarning, "SSU2: Peer test 4 router not found"); + if (it->second.first) + it->second.first->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 ()) + SetRouterStatus (eRouterStatusUnknown); + it->second.first->Done (); + } + m_PeerTests.erase (it); + } + else + LogPrint (eLogWarning, "SSU2: Unknown peer test 4 nonce ", nonce); + break; + } + case 5: // Alice from Charlie 1 + if (htobe64 (((uint64_t)nonce << 32) | nonce) == m_SourceConnID) + { + if (m_Address) + { + SetRouterStatus (eRouterStatusOK); + SendPeerTest (6, buf + offset, len - offset, m_Address->i); + } + else + // we received msg 5 before msg 4 + m_State = eSSU2SessionStatePeerTestReceived; + } + else + LogPrint (eLogWarning, "SSU2: Peer test 5 nonce mismatch ", nonce, " connID=", m_SourceConnID); + break; + case 6: // Charlie from Alice + if (m_Address) + SendPeerTest (7, buf + offset, len - offset, m_Address->i); + else + LogPrint (eLogWarning, "SSU2: Unknown address for peer test 6"); + m_Server.RemoveSession (~htobe64 (((uint64_t)nonce << 32) | nonce)); + break; + case 7: // Alice from Charlie 2 + if (m_Address->IsV6 ()) + i2p::context.SetStatusV6 (eRouterStatusOK); // set status OK for ipv6 even if from SSU2 + m_Server.RemoveSession (htobe64 (((uint64_t)nonce << 32) | nonce)); + break; + default: + LogPrint (eLogWarning, "SSU2: PeerTest unexpected msg num ", buf[0]); + } + } + + 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)m_LastActivityTimestamp).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 ()); + 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); + } + } + + 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 || !r->GetBuffer () || len < 5) return 0; + buf[0] = eSSU2BlkRouterInfo; + size_t size = r->GetBufferLen (); + if (size + 5 < len) + { + memcpy (buf + 5, r->GetBuffer (), size); + buf[3] = 0; // flag + } + else + { + i2p::data::GzipDeflator deflator; + deflator.SetCompressionLevel (9); + size = deflator.Deflate (r->GetBuffer (), r->GetBufferLen (), buf + 5, len - 5); + if (!size) return 0; // doesn't fit + buf[3] = SSU2_ROUTER_INFO_FLAG_GZIP; // flag + } + htobe16buf (buf + 1, size + 2); // size + buf[4] = 1; // frag + return size + 5; + } + + size_t SSU2Session::CreateAckBlock (uint8_t * buf, size_t len) + { + if (len < 8) return 0; + int maxNumRanges = (len - 8) >> 1; + if (maxNumRanges > SSU2_MAX_NUM_ACK_RANGES) maxNumRanges = SSU2_MAX_NUM_ACK_RANGES; + buf[0] = eSSU2BlkAck; + uint32_t ackThrough = m_OutOfSequencePackets.empty () ? m_ReceivePacketNum : *m_OutOfSequencePackets.rbegin (); + htobe32buf (buf + 3, ackThrough); // Ack Through + uint16_t acnt = 0; + int numRanges = 0; + if (ackThrough) + { + if (m_OutOfSequencePackets.empty ()) + acnt = std::min ((int)ackThrough, SSU2_MAX_NUM_ACNT); // no gaps + 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 + 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++) + { + buf[8 + numRanges*2] = 0; buf[8 + numRanges*2 + 1] = SSU2_MAX_NUM_ACNT; // NACKs 0, Acks 255 + numRanges++; + } + if (d.rem > 0) + { + buf[8 + numRanges*2] = 0; buf[8 + numRanges*2 + 1] = d.rem; + numRanges++; + } + } + int numPackets = acnt + numRanges*SSU2_MAX_NUM_ACNT; + while (it != m_OutOfSequencePackets.rend () && + numRanges < maxNumRanges && numPackets < SSU2_MAX_NUM_ACK_PACKETS) + { + if (lastNum - (*it) > SSU2_MAX_NUM_ACNT) + { + // NACKs only ranges + if (lastNum > (*it) + SSU2_MAX_NUM_ACNT*(maxNumRanges - numRanges)) break; // too many NACKs + while (lastNum - (*it) > SSU2_MAX_NUM_ACNT) + { + buf[8 + numRanges*2] = SSU2_MAX_NUM_ACNT; buf[8 + numRanges*2 + 1] = 0; // NACKs 255, Acks 0 + lastNum -= SSU2_MAX_NUM_ACNT; + numRanges++; + numPackets += SSU2_MAX_NUM_ACNT; + } + } + // NACKs and Acks ranges + buf[8 + numRanges*2] = lastNum - (*it) - 1; // NACKs + numPackets += buf[8 + numRanges*2]; + lastNum = *it; it++; + int numAcks = 1; + while (it != m_OutOfSequencePackets.rend () && lastNum > 0 && *it == lastNum - 1) + { + numAcks++; lastNum--; + it++; + } + while (numAcks > SSU2_MAX_NUM_ACNT) + { + // Acks only ranges + buf[8 + numRanges*2 + 1] = SSU2_MAX_NUM_ACNT; // Acks 255 + numAcks -= SSU2_MAX_NUM_ACNT; + numRanges++; + numPackets += SSU2_MAX_NUM_ACNT; + buf[8 + numRanges*2] = 0; // NACKs 0 + if (numRanges >= maxNumRanges || numPackets >= SSU2_MAX_NUM_ACK_PACKETS) break; + } + if (numAcks > SSU2_MAX_NUM_ACNT) numAcks = SSU2_MAX_NUM_ACNT; + buf[8 + numRanges*2 + 1] = (uint8_t)numAcks; // Acks + numPackets += numAcks; + numRanges++; + } + if (it == m_OutOfSequencePackets.rend () && + numRanges < maxNumRanges && numPackets < SSU2_MAX_NUM_ACK_PACKETS) + { + // add range between out-of-sequence and received + int nacks = *m_OutOfSequencePackets.begin () - m_ReceivePacketNum - 1; + if (nacks > 0) + { + if (nacks > SSU2_MAX_NUM_ACNT) nacks = SSU2_MAX_NUM_ACNT; + buf[8 + numRanges*2] = nacks; + buf[8 + numRanges*2 + 1] = std::min ((int)m_ReceivePacketNum + 1, SSU2_MAX_NUM_ACNT); + numRanges++; + } + } + } + } + buf[7] = (uint8_t)acnt; // acnt + htobe16buf (buf + 1, 5 + numRanges*2); + return 8 + numRanges*2; + } + + size_t SSU2Session::CreatePaddingBlock (uint8_t * buf, size_t len, size_t minSize) + { + if (len < 3 || len < minSize) return 0; + size_t paddingSize = rand () & 0x0F; // 0 - 15 + 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 + ri = std::make_shared(buf + 2, size - 2); + return ri; + } + + void SSU2Session::CreateNonce (uint64_t seqn, uint8_t * nonce) + { + memset (nonce, 0, 4); + htole64buf (nonce + 4, seqn); + } + + bool SSU2Session::UpdateReceivePacketNum (uint32_t packetNum) + { + if (packetNum <= m_ReceivePacketNum) return false; // duplicate + if (packetNum == m_ReceivePacketNum + 1) + { + 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_ReceivePacketNum = packetNum; + } + else + 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 = rand () % (m_MaxPayloadSize - 3); + htobe16buf (payload + 1, len); + if (len > 0) + { + RAND_bytes (payload + 3, len); + i2p::data::IdentHash * hash = new i2p::data::IdentHash (); + SHA256 (payload + 3, len, *hash); + m_PathChallenge.reset (hash); + } + len += 3; + if (len < m_MaxPayloadSize) + len += CreatePaddingBlock (payload + len, m_MaxPayloadSize - len, 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 > m_LastActivityTimestamp + 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 (eLogWarning, "SSU2: Relay nonce ", it->first, " was not responded in ", SSU2_RELAY_NONCE_EXPIRATION_TIMEOUT, " seconds, deleted"); + it = m_RelaySessions.erase (it); + } + else + ++it; + } + for (auto it = m_PeerTests.begin (); it != m_PeerTests.end ();) + { + if (ts > it->second.second + SSU2_PEER_TEST_EXPIRATION_TIMEOUT) + { + LogPrint (eLogWarning, "SSU2: Peer test nonce ", it->first, " was not responded in ", SSU2_PEER_TEST_EXPIRATION_TIMEOUT, " seconds, deleted"); + it = m_PeerTests.erase (it); + } + else + ++it; + } + if (m_PathChallenge) + RequestTermination (eSSU2TerminationReasonNormalClose); + } + + void SSU2Session::FlushData () + { + bool sent = SendQueue (); // if we have something to send + if (sent) + m_SendQueueSize = m_SendQueue.size (); + if (m_IsDataReceived) + { + if (!sent) SendQuickAck (); + m_Handler.Flush (); + m_IsDataReceived = false; + } + } + +} +} diff --git a/libi2pd/SSU2Session.h b/libi2pd/SSU2Session.h new file mode 100644 index 00000000..14d76971 --- /dev/null +++ b/libi2pd/SSU2Session.h @@ -0,0 +1,378 @@ +/* +* Copyright (c) 2022-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 SSU2_SESSION_H__ +#define SSU2_SESSION_H__ + +#include +#include +#include +#include +#include +#include +#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 = 330; // 5.5 minutes + 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_RESEND_INTERVAL = 300; // in milliseconds + const int SSU2_MAX_NUM_RESENDS = 5; + 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_MAX_RTO = 2500; // in milliseconds + const float SSU2_kAPPA = 1.8; + const size_t SSU2_MAX_OUTGOING_QUEUE_SIZE = 500; // in messages + const int SSU2_MAX_NUM_ACNT = 255; // acnt, acks or nacks + const int SSU2_MAX_NUM_ACK_PACKETS = 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; + + // 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, + eSSU2SessionStatePeerTest, + eSSU2SessionStatePeerTestReceived, // 5 before 4 + 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 + { + union Header + { + uint64_t ll[2]; + uint8_t buf[16]; + struct + { + uint64_t connID; + uint32_t packetNum; + uint8_t type; + uint8_t flags[3]; + } h; + }; + + struct 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); + ~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; }; + std::shared_ptr GetAddress () const { return m_Address; }; + void SetOnEstablished (OnEstablished e) { m_OnEstablished = e; }; + OnEstablished GetOnEstablished () const { return m_OnEstablished; }; + + 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 (const std::vector >& msgs) override; + uint32_t GetRelayTag () const override { return m_RelayTag; }; + size_t Resend (uint64_t ts); // return number or resent packets + bool IsEstablished () const override { return m_State == eSSU2SessionStateEstablished; }; + uint64_t GetConnID () const { return m_SourceConnID; }; + SSU2SessionState GetState () const { return m_State; }; + void SetState (SSU2SessionState state) { m_State = state; }; + + bool ProcessFirstIncomingMessage (uint64_t connID, uint8_t * buf, size_t len); + bool ProcessSessionCreated (uint8_t * buf, size_t len); + bool ProcessSessionConfirmed (uint8_t * buf, size_t len); + bool ProcessRetry (uint8_t * buf, size_t len); + bool ProcessHolePunch (uint8_t * buf, size_t len); + bool ProcessPeerTest (uint8_t * buf, size_t len); + void ProcessData (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& from); + + private: + + void Terminate (); + void Established (); + void ScheduleConnectTimer (); + void HandleConnectTimer (const boost::system::error_code& ecode); + void PostI2NPMessages (std::vector > msgs); + 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 SendHolePunch (uint32_t nonce, const boost::asio::ip::udp::endpoint& ep, const uint8_t * introKey, uint64_t token); + void SendPeerTest (uint8_t msg, const uint8_t * signedData, size_t signedDataLen, const uint8_t * introKey); // PeerTest message + void SendPathResponse (const uint8_t * data, size_t len); + void SendPathChallenge (); + + void HandlePayload (const uint8_t * buf, size_t len); + void HandleDateTime (const uint8_t * buf, size_t len); + void HandleAck (const uint8_t * buf, size_t len); + void HandleAckRange (uint32_t firstPacketNum, uint32_t lastPacketNum, uint64_t ts); + void HandleAddress (const uint8_t * buf, size_t len); + bool ExtractEndpoint (const uint8_t * buf, size_t size, boost::asio::ip::udp::endpoint& ep); + size_t CreateEndpoint (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& ep); + std::shared_ptr FindLocalAddress () const; + void AdjustMaxPayloadSize (); + RouterStatus GetRouterStatus () const; + void SetRouterStatus (RouterStatus status) const; + bool GetTestingState () const; + void SetTestingState(bool testing) const; + std::shared_ptr ExtractRouterInfo (const uint8_t * buf, size_t size); + void CreateNonce (uint64_t seqn, uint8_t * nonce); + bool UpdateReceivePacketNum (uint32_t packetNum); // for Ack, returns false if duplicate + void HandleFirstFragment (const uint8_t * buf, size_t len); + void HandleFollowOnFragment (const uint8_t * buf, size_t len); + void HandleRelayRequest (const uint8_t * buf, size_t len); + void HandleRelayIntro (const uint8_t * buf, size_t len, int attempts = 0); + void HandleRelayResponse (const uint8_t * buf, size_t len); + void HandlePeerTest (const uint8_t * buf, size_t len); + void HandleI2NPMsg (std::shared_ptr&& msg); + + size_t CreateAddressBlock (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& ep); + size_t CreateRouterInfoBlock (uint8_t * buf, size_t len, std::shared_ptr r); + size_t CreateAckBlock (uint8_t * buf, size_t len); + size_t CreatePaddingBlock (uint8_t * buf, size_t len, size_t minSize = 0); + size_t CreateI2NPBlock (uint8_t * buf, size_t len, std::shared_ptr&& msg); + size_t CreateFirstFragmentBlock (uint8_t * buf, size_t len, std::shared_ptr msg); + size_t CreateFollowOnFragmentBlock (uint8_t * buf, size_t len, std::shared_ptr msg, uint8_t& fragmentNum, uint32_t msgID); + size_t CreateRelayIntroBlock (uint8_t * buf, size_t len, const uint8_t * introData, size_t introDataLen); + size_t CreateRelayResponseBlock (uint8_t * buf, size_t len, SSU2RelayResponseCode code, uint32_t nonce, uint64_t token, bool v4); + size_t CreatePeerTestBlock (uint8_t * buf, size_t len, uint8_t msg, SSU2PeerTestCode code, const uint8_t * routerHash, const uint8_t * signedData, size_t signedDataLen); + size_t CreatePeerTestBlock (uint8_t * buf, size_t len, uint32_t nonce); // Alice + size_t CreateTerminationBlock (uint8_t * buf, size_t len); + + private: + + SSU2Server& m_Server; + 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; // for peer tests + 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::map, uint64_t > > m_RelaySessions; // nonce->(Alice, timestamp) for Bob or nonce->(Charlie, timestamp) for Alice + std::map, uint64_t > > m_PeerTests; // same as for relay sessions + std::list > m_SendQueue; + i2p::I2NPMessagesHandler m_Handler; + bool m_IsDataReceived; + size_t m_WindowSize, m_RTT, 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 + }; + + 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; + } +} +} + +#endif diff --git a/libi2pd/SSUData.cpp b/libi2pd/SSUData.cpp deleted file mode 100644 index 4e0e712f..00000000 --- a/libi2pd/SSUData.cpp +++ /dev/null @@ -1,520 +0,0 @@ -/* -* Copyright (c) 2013-2020, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - -#include -#include "Log.h" -#include "Timestamp.h" -#include "NetDb.hpp" -#include "SSU.h" -#include "SSUData.h" - -namespace i2p -{ -namespace transport -{ - void IncompleteMessage::AttachNextFragment (const uint8_t * fragment, size_t fragmentSize) - { - if (msg->len + fragmentSize > msg->maxLen) - { - LogPrint (eLogWarning, "SSU: I2NP message size ", msg->maxLen, " is not enough"); - auto newMsg = NewI2NPMessage (); - *newMsg = *msg; - msg = newMsg; - } - if (msg->Concat (fragment, fragmentSize) < fragmentSize) - LogPrint (eLogError, "SSU: I2NP buffer overflow ", msg->maxLen); - nextFragmentNum++; - } - - SSUData::SSUData (SSUSession& session): - m_Session (session), m_ResendTimer (session.GetService ()), - m_IncompleteMessagesCleanupTimer (session.GetService ()), - m_MaxPacketSize (session.IsV6 () ? SSU_V6_MAX_PACKET_SIZE : SSU_V4_MAX_PACKET_SIZE), - m_PacketSize (m_MaxPacketSize), m_LastMessageReceivedTime (0) - { - } - - SSUData::~SSUData () - { - } - - void SSUData::Start () - { - ScheduleIncompleteMessagesCleanup (); - } - - void SSUData::Stop () - { - m_ResendTimer.cancel (); - m_IncompleteMessagesCleanupTimer.cancel (); - m_IncompleteMessages.clear (); - m_SentMessages.clear (); - m_ReceivedMessages.clear (); - } - - void SSUData::AdjustPacketSize (std::shared_ptr remoteRouter) - { - if (!remoteRouter) return; - auto ssuAddress = remoteRouter->GetSSUAddress (); - if (ssuAddress && ssuAddress->ssu->mtu) - { - if (m_Session.IsV6 ()) - m_PacketSize = ssuAddress->ssu->mtu - IPV6_HEADER_SIZE - UDP_HEADER_SIZE; - else - m_PacketSize = ssuAddress->ssu->mtu - IPV4_HEADER_SIZE - UDP_HEADER_SIZE; - if (m_PacketSize > 0) - { - // make sure packet size multiple of 16 - m_PacketSize >>= 4; - m_PacketSize <<= 4; - if (m_PacketSize > m_MaxPacketSize) m_PacketSize = m_MaxPacketSize; - LogPrint (eLogDebug, "SSU: MTU=", ssuAddress->ssu->mtu, " packet size=", m_PacketSize); - } - else - { - LogPrint (eLogWarning, "SSU: Unexpected MTU ", ssuAddress->ssu->mtu); - m_PacketSize = m_MaxPacketSize; - } - } - } - - void SSUData::UpdatePacketSize (const i2p::data::IdentHash& remoteIdent) - { - auto routerInfo = i2p::data::netdb.FindRouter (remoteIdent); - if (routerInfo) - AdjustPacketSize (routerInfo); - } - - void SSUData::ProcessSentMessageAck (uint32_t msgID) - { - auto it = m_SentMessages.find (msgID); - if (it != m_SentMessages.end ()) - { - m_SentMessages.erase (it); - if (m_SentMessages.empty ()) - m_ResendTimer.cancel (); - } - } - - void SSUData::ProcessAcks (uint8_t *& buf, uint8_t flag) - { - if (flag & DATA_FLAG_EXPLICIT_ACKS_INCLUDED) - { - // explicit ACKs - uint8_t numAcks =*buf; - buf++; - for (int i = 0; i < numAcks; i++) - ProcessSentMessageAck (bufbe32toh (buf+i*4)); - buf += numAcks*4; - } - if (flag & DATA_FLAG_ACK_BITFIELDS_INCLUDED) - { - // explicit ACK bitfields - uint8_t numBitfields =*buf; - buf++; - for (int i = 0; i < numBitfields; i++) - { - uint32_t msgID = bufbe32toh (buf); - buf += 4; // msgID - auto it = m_SentMessages.find (msgID); - // process individual Ack bitfields - bool isNonLast = false; - int fragment = 0; - do - { - uint8_t bitfield = *buf; - isNonLast = bitfield & 0x80; - bitfield &= 0x7F; // clear MSB - if (bitfield && it != m_SentMessages.end ()) - { - int numSentFragments = it->second->fragments.size (); - // process bits - uint8_t mask = 0x01; - for (int j = 0; j < 7; j++) - { - if (bitfield & mask) - { - if (fragment < numSentFragments) - it->second->fragments[fragment].reset (nullptr); - } - fragment++; - mask <<= 1; - } - } - buf++; - } - while (isNonLast); - } - } - } - - void SSUData::ProcessFragments (uint8_t * buf) - { - uint8_t numFragments = *buf; // number of fragments - buf++; - for (int i = 0; i < numFragments; i++) - { - uint32_t msgID = bufbe32toh (buf); // message ID - buf += 4; - uint8_t frag[4] = {0}; - memcpy (frag + 1, buf, 3); - buf += 3; - uint32_t fragmentInfo = bufbe32toh (frag); // fragment info - uint16_t fragmentSize = fragmentInfo & 0x3FFF; // bits 0 - 13 - bool isLast = fragmentInfo & 0x010000; // bit 16 - uint8_t fragmentNum = fragmentInfo >> 17; // bits 23 - 17 - if (fragmentSize >= SSU_V4_MAX_PACKET_SIZE) - { - LogPrint (eLogError, "SSU: Fragment size ", fragmentSize, " exceeds max SSU packet size"); - return; - } - - // find message with msgID - auto it = m_IncompleteMessages.find (msgID); - if (it == m_IncompleteMessages.end ()) - { - // create new message - auto msg = NewI2NPShortMessage (); - msg->len -= I2NP_SHORT_HEADER_SIZE; - it = m_IncompleteMessages.insert (std::make_pair (msgID, - std::unique_ptr(new IncompleteMessage (msg)))).first; - } - std::unique_ptr& incompleteMessage = it->second; - - // handle current fragment - if (fragmentNum == incompleteMessage->nextFragmentNum) - { - // expected fragment - incompleteMessage->AttachNextFragment (buf, fragmentSize); - if (!isLast && !incompleteMessage->savedFragments.empty ()) - { - // try saved fragments - for (auto it1 = incompleteMessage->savedFragments.begin (); it1 != incompleteMessage->savedFragments.end ();) - { - auto& savedFragment = *it1; - if (savedFragment->fragmentNum == incompleteMessage->nextFragmentNum) - { - incompleteMessage->AttachNextFragment (savedFragment->buf, savedFragment->len); - isLast = savedFragment->isLast; - incompleteMessage->savedFragments.erase (it1++); - } - else - break; - } - if (isLast) - LogPrint (eLogDebug, "SSU: Message ", msgID, " complete"); - } - } - else - { - if (fragmentNum < incompleteMessage->nextFragmentNum) - // duplicate fragment - LogPrint (eLogWarning, "SSU: Duplicate fragment ", (int)fragmentNum, " of message ", msgID, ", ignored"); - else - { - // missing fragment - LogPrint (eLogWarning, "SSU: Missing fragments from ", (int)incompleteMessage->nextFragmentNum, " to ", fragmentNum - 1, " of message ", msgID); - auto savedFragment = new Fragment (fragmentNum, buf, fragmentSize, isLast); - if (incompleteMessage->savedFragments.insert (std::unique_ptr(savedFragment)).second) - incompleteMessage->lastFragmentInsertTime = i2p::util::GetSecondsSinceEpoch (); - else - LogPrint (eLogWarning, "SSU: Fragment ", (int)fragmentNum, " of message ", msgID, " already saved"); - } - isLast = false; - } - - if (isLast) - { - // delete incomplete message - auto msg = incompleteMessage->msg; - incompleteMessage->msg = nullptr; - m_IncompleteMessages.erase (msgID); - // process message - SendMsgAck (msgID); - msg->FromSSU (msgID); - if (m_Session.GetState () == eSessionStateEstablished) - { - if (!m_ReceivedMessages.count (msgID)) - { - m_ReceivedMessages.insert (msgID); - m_LastMessageReceivedTime = i2p::util::GetSecondsSinceEpoch (); - if (!msg->IsExpired ()) - { - m_Handler.PutNextMessage (msg); - } - else - LogPrint (eLogDebug, "SSU: message expired"); - } - else - LogPrint (eLogWarning, "SSU: Message ", msgID, " already received"); - } - else - { - // we expect DeliveryStatus - if (msg->GetTypeID () == eI2NPDeliveryStatus) - { - LogPrint (eLogDebug, "SSU: session established"); - m_Session.Established (); - } - else - LogPrint (eLogError, "SSU: unexpected message ", (int)msg->GetTypeID ()); - } - } - else - SendFragmentAck (msgID, fragmentNum); - buf += fragmentSize; - } - } - - void SSUData::FlushReceivedMessage () - { - m_Handler.Flush (); - } - - void SSUData::ProcessMessage (uint8_t * buf, size_t len) - { - //uint8_t * start = buf; - uint8_t flag = *buf; - buf++; - LogPrint (eLogDebug, "SSU: Process data, flags=", (int)flag, ", len=", len); - // process acks if presented - if (flag & (DATA_FLAG_ACK_BITFIELDS_INCLUDED | DATA_FLAG_EXPLICIT_ACKS_INCLUDED)) - ProcessAcks (buf, flag); - // extended data if presented - if (flag & DATA_FLAG_EXTENDED_DATA_INCLUDED) - { - uint8_t extendedDataSize = *buf; - buf++; // size - LogPrint (eLogDebug, "SSU: extended data of ", extendedDataSize, " bytes present"); - buf += extendedDataSize; - } - // process data - ProcessFragments (buf); - } - - void SSUData::Send (std::shared_ptr msg) - { - uint32_t msgID = msg->ToSSU (); - if (m_SentMessages.find (msgID) != m_SentMessages.end()) - { - LogPrint (eLogWarning, "SSU: message ", msgID, " already sent"); - return; - } - if (m_SentMessages.empty ()) // schedule resend at first message only - ScheduleResend (); - - auto ret = m_SentMessages.insert (std::make_pair (msgID, std::unique_ptr(new SentMessage))); - std::unique_ptr& sentMessage = ret.first->second; - if (ret.second) - { - sentMessage->nextResendTime = i2p::util::GetSecondsSinceEpoch () + RESEND_INTERVAL; - sentMessage->numResends = 0; - } - auto& fragments = sentMessage->fragments; - size_t payloadSize = m_PacketSize - sizeof (SSUHeader) - 9; // 9 = flag + #frg(1) + messageID(4) + frag info (3) - size_t len = msg->GetLength (); - uint8_t * msgBuf = msg->GetSSUHeader (); - - uint32_t fragmentNum = 0; - while (len > 0 && fragmentNum <= 127) - { - Fragment * fragment = new Fragment; - fragment->fragmentNum = fragmentNum; - uint8_t * payload = fragment->buf + sizeof (SSUHeader); - *payload = DATA_FLAG_WANT_REPLY; // for compatibility - payload++; - *payload = 1; // always 1 message fragment per message - payload++; - htobe32buf (payload, msgID); - payload += 4; - bool isLast = (len <= payloadSize) || fragmentNum == 127; // 127 fragments max - size_t size = isLast ? len : payloadSize; - uint32_t fragmentInfo = (fragmentNum << 17); - if (isLast) - fragmentInfo |= 0x010000; - - fragmentInfo |= size; - fragmentInfo = htobe32 (fragmentInfo); - memcpy (payload, (uint8_t *)(&fragmentInfo) + 1, 3); - payload += 3; - memcpy (payload, msgBuf, size); - - size += payload - fragment->buf; - uint8_t rem = size & 0x0F; - if (rem) // make sure 16 bytes boundary - { - auto padding = 16 - rem; - memset (fragment->buf + size, 0, padding); - size += padding; - } - fragment->len = size; - fragments.push_back (std::unique_ptr (fragment)); - - // encrypt message with session key - uint8_t buf[SSU_V4_MAX_PACKET_SIZE + 18]; - m_Session.FillHeaderAndEncrypt (PAYLOAD_TYPE_DATA, fragment->buf, size, buf); - try - { - m_Session.Send (buf, size); - } - catch (boost::system::system_error& ec) - { - LogPrint (eLogWarning, "SSU: Can't send data fragment ", ec.what ()); - } - if (!isLast) - { - len -= payloadSize; - msgBuf += payloadSize; - } - else - len = 0; - fragmentNum++; - } - } - - void SSUData::SendMsgAck (uint32_t msgID) - { - uint8_t buf[48 + 18] = {0}; // actual length is 44 = 37 + 7 but pad it to multiple of 16 - uint8_t * payload = buf + sizeof (SSUHeader); - *payload = DATA_FLAG_EXPLICIT_ACKS_INCLUDED; // flag - payload++; - *payload = 1; // number of ACKs - payload++; - htobe32buf (payload, msgID); // msgID - payload += 4; - *payload = 0; // number of fragments - - // encrypt message with session key - m_Session.FillHeaderAndEncrypt (PAYLOAD_TYPE_DATA, buf, 48); - m_Session.Send (buf, 48); - } - - void SSUData::SendFragmentAck (uint32_t msgID, int fragmentNum) - { - if (fragmentNum > 64) - { - LogPrint (eLogWarning, "SSU: Fragment number ", fragmentNum, " exceeds 64"); - return; - } - uint8_t buf[64 + 18] = {0}; - uint8_t * payload = buf + sizeof (SSUHeader); - *payload = DATA_FLAG_ACK_BITFIELDS_INCLUDED; // flag - payload++; - *payload = 1; // number of ACK bitfields - payload++; - // one ack - *(uint32_t *)(payload) = htobe32 (msgID); // msgID - payload += 4; - div_t d = div (fragmentNum, 7); - memset (payload, 0x80, d.quot); // 0x80 means non-last - payload += d.quot; - *payload = 0x01 << d.rem; // set corresponding bit - payload++; - *payload = 0; // number of fragments - - size_t len = d.quot < 4 ? 48 : 64; // 48 = 37 + 7 + 4 (3+1) - // encrypt message with session key - m_Session.FillHeaderAndEncrypt (PAYLOAD_TYPE_DATA, buf, len); - m_Session.Send (buf, len); - } - - void SSUData::ScheduleResend() - { - m_ResendTimer.cancel (); - m_ResendTimer.expires_from_now (boost::posix_time::seconds(RESEND_INTERVAL)); - auto s = m_Session.shared_from_this(); - m_ResendTimer.async_wait ([s](const boost::system::error_code& ecode) - { s->m_Data.HandleResendTimer (ecode); }); - } - - void SSUData::HandleResendTimer (const boost::system::error_code& ecode) - { - if (ecode != boost::asio::error::operation_aborted) - { - uint8_t buf[SSU_V4_MAX_PACKET_SIZE + 18]; - uint32_t ts = i2p::util::GetSecondsSinceEpoch (); - int numResent = 0; - for (auto it = m_SentMessages.begin (); it != m_SentMessages.end ();) - { - if (ts >= it->second->nextResendTime) - { - if (it->second->numResends < MAX_NUM_RESENDS) - { - for (auto& f: it->second->fragments) - if (f) - { - try - { - m_Session.FillHeaderAndEncrypt (PAYLOAD_TYPE_DATA, f->buf, f->len, buf); - m_Session.Send (buf, f->len); // resend - numResent++; - } - catch (boost::system::system_error& ec) - { - LogPrint (eLogWarning, "SSU: Can't resend message ", it->first, " data fragment: ", ec.what ()); - } - } - - it->second->numResends++; - it->second->nextResendTime += it->second->numResends*RESEND_INTERVAL; - ++it; - } - else - { - LogPrint (eLogInfo, "SSU: message ", it->first, " has not been ACKed after ", MAX_NUM_RESENDS, " attempts, deleted"); - it = m_SentMessages.erase (it); - } - } - else - ++it; - } - if (m_SentMessages.empty ()) return; // nothing to resend - if (numResent < MAX_OUTGOING_WINDOW_SIZE) - ScheduleResend (); - else - { - LogPrint (eLogError, "SSU: resend window exceeds max size. Session terminated"); - m_Session.Close (); - } - } - } - - void SSUData::ScheduleIncompleteMessagesCleanup () - { - m_IncompleteMessagesCleanupTimer.cancel (); - m_IncompleteMessagesCleanupTimer.expires_from_now (boost::posix_time::seconds(INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT)); - auto s = m_Session.shared_from_this(); - m_IncompleteMessagesCleanupTimer.async_wait ([s](const boost::system::error_code& ecode) - { s->m_Data.HandleIncompleteMessagesCleanupTimer (ecode); }); - } - - void SSUData::HandleIncompleteMessagesCleanupTimer (const boost::system::error_code& ecode) - { - if (ecode != boost::asio::error::operation_aborted) - { - uint32_t ts = i2p::util::GetSecondsSinceEpoch (); - for (auto it = m_IncompleteMessages.begin (); it != m_IncompleteMessages.end ();) - { - if (ts > it->second->lastFragmentInsertTime + INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT) - { - LogPrint (eLogWarning, "SSU: message ", it->first, " was not completed in ", INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT, " seconds, deleted"); - it = m_IncompleteMessages.erase (it); - } - else - ++it; - } - // decay - if (m_ReceivedMessages.size () > MAX_NUM_RECEIVED_MESSAGES || - i2p::util::GetSecondsSinceEpoch () > m_LastMessageReceivedTime + DECAY_INTERVAL) - m_ReceivedMessages.clear (); - - ScheduleIncompleteMessagesCleanup (); - } - } -} -} diff --git a/libi2pd/SSUData.h b/libi2pd/SSUData.h deleted file mode 100644 index 2e606053..00000000 --- a/libi2pd/SSUData.h +++ /dev/null @@ -1,138 +0,0 @@ -/* -* Copyright (c) 2013-2020, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - -#ifndef SSU_DATA_H__ -#define SSU_DATA_H__ - -#include -#include -#include -#include -#include -#include -#include -#include "I2NPProtocol.h" -#include "Identity.h" -#include "RouterInfo.h" - -namespace i2p -{ -namespace transport -{ - - const size_t SSU_MTU_V4 = 1484; - #ifdef MESHNET - const size_t SSU_MTU_V6 = 1286; - #else - const size_t SSU_MTU_V6 = 1488; - #endif - const size_t IPV4_HEADER_SIZE = 20; - const size_t IPV6_HEADER_SIZE = 40; - const size_t UDP_HEADER_SIZE = 8; - const size_t SSU_V4_MAX_PACKET_SIZE = SSU_MTU_V4 - IPV4_HEADER_SIZE - UDP_HEADER_SIZE; // 1456 - const size_t SSU_V6_MAX_PACKET_SIZE = SSU_MTU_V6 - IPV6_HEADER_SIZE - UDP_HEADER_SIZE; // 1440 - const int RESEND_INTERVAL = 3; // in seconds - const int MAX_NUM_RESENDS = 5; - const int DECAY_INTERVAL = 20; // in seconds - const int INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT = 30; // in seconds - const unsigned int MAX_NUM_RECEIVED_MESSAGES = 1000; // how many msgID we store for duplicates check - const int MAX_OUTGOING_WINDOW_SIZE = 200; // how many unacked message we can store - // data flags - const uint8_t DATA_FLAG_EXTENDED_DATA_INCLUDED = 0x02; - const uint8_t DATA_FLAG_WANT_REPLY = 0x04; - const uint8_t DATA_FLAG_REQUEST_PREVIOUS_ACKS = 0x08; - const uint8_t DATA_FLAG_EXPLICIT_CONGESTION_NOTIFICATION = 0x10; - const uint8_t DATA_FLAG_ACK_BITFIELDS_INCLUDED = 0x40; - const uint8_t DATA_FLAG_EXPLICIT_ACKS_INCLUDED = 0x80; - - struct Fragment - { - int fragmentNum; - size_t len; - bool isLast; - uint8_t buf[SSU_V4_MAX_PACKET_SIZE + 18]; // use biggest - - Fragment () = default; - Fragment (int n, const uint8_t * b, int l, bool last): - fragmentNum (n), len (l), isLast (last) { memcpy (buf, b, len); }; - }; - - struct FragmentCmp - { - bool operator() (const std::unique_ptr& f1, const std::unique_ptr& f2) const - { - return f1->fragmentNum < f2->fragmentNum; - }; - }; - - struct IncompleteMessage - { - std::shared_ptr msg; - int nextFragmentNum; - uint32_t lastFragmentInsertTime; // in seconds - std::set, FragmentCmp> savedFragments; - - IncompleteMessage (std::shared_ptr m): msg (m), nextFragmentNum (0), lastFragmentInsertTime (0) {}; - void AttachNextFragment (const uint8_t * fragment, size_t fragmentSize); - }; - - struct SentMessage - { - std::vector > fragments; - uint32_t nextResendTime; // in seconds - int numResends; - }; - - class SSUSession; - class SSUData - { - public: - - SSUData (SSUSession& session); - ~SSUData (); - - void Start (); - void Stop (); - - void ProcessMessage (uint8_t * buf, size_t len); - void FlushReceivedMessage (); - void Send (std::shared_ptr msg); - - void AdjustPacketSize (std::shared_ptr remoteRouter); - void UpdatePacketSize (const i2p::data::IdentHash& remoteIdent); - - private: - - void SendMsgAck (uint32_t msgID); - void SendFragmentAck (uint32_t msgID, int fragmentNum); - void ProcessAcks (uint8_t *& buf, uint8_t flag); - void ProcessFragments (uint8_t * buf); - void ProcessSentMessageAck (uint32_t msgID); - - void ScheduleResend (); - void HandleResendTimer (const boost::system::error_code& ecode); - - void ScheduleIncompleteMessagesCleanup (); - void HandleIncompleteMessagesCleanupTimer (const boost::system::error_code& ecode); - - - private: - - SSUSession& m_Session; - std::unordered_map > m_IncompleteMessages; - std::unordered_map > m_SentMessages; - std::unordered_set m_ReceivedMessages; - boost::asio::deadline_timer m_ResendTimer, m_IncompleteMessagesCleanupTimer; - int m_MaxPacketSize, m_PacketSize; - i2p::I2NPMessagesHandler m_Handler; - uint32_t m_LastMessageReceivedTime; // in second - }; -} -} - -#endif diff --git a/libi2pd/SSUSession.cpp b/libi2pd/SSUSession.cpp deleted file mode 100644 index 399b2fc7..00000000 --- a/libi2pd/SSUSession.cpp +++ /dev/null @@ -1,1221 +0,0 @@ -/* -* Copyright (c) 2013-2020, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - -#include "version.h" -#include "Crypto.h" -#include "Log.h" -#include "Timestamp.h" -#include "RouterContext.h" -#include "Transports.h" -#include "NetDb.hpp" -#include "SSU.h" -#include "SSUSession.h" - -namespace i2p -{ -namespace transport -{ - SSUSession::SSUSession (SSUServer& server, boost::asio::ip::udp::endpoint& remoteEndpoint, - std::shared_ptr router, bool peerTest ): - TransportSession (router, SSU_TERMINATION_TIMEOUT), - m_Server (server), m_RemoteEndpoint (remoteEndpoint), m_ConnectTimer (GetService ()), - m_IsPeerTest (peerTest),m_State (eSessionStateUnknown), m_IsSessionKey (false), - m_RelayTag (0), m_SentRelayTag (0), m_Data (*this), m_IsDataReceived (false) - { - if (router) - { - // we are client - auto address = router->GetSSUAddress (false); - if (address) m_IntroKey = address->ssu->key; - m_Data.AdjustPacketSize (router); // mtu - } - else - { - // we are server - auto address = i2p::context.GetRouterInfo ().GetSSUAddress (false); - if (address) m_IntroKey = address->ssu->key; - } - m_CreationTime = i2p::util::GetSecondsSinceEpoch (); - } - - SSUSession::~SSUSession () - { - } - - boost::asio::io_service& SSUSession::GetService () - { - return IsV6 () ? m_Server.GetServiceV6 () : m_Server.GetService (); - } - - void SSUSession::CreateAESandMacKey (const uint8_t * pubKey) - { - uint8_t sharedKey[256]; - m_DHKeysPair->Agree (pubKey, sharedKey); - - uint8_t * sessionKey = m_SessionKey, * macKey = m_MacKey; - if (sharedKey[0] & 0x80) - { - sessionKey[0] = 0; - memcpy (sessionKey + 1, sharedKey, 31); - memcpy (macKey, sharedKey + 31, 32); - } - else if (sharedKey[0]) - { - memcpy (sessionKey, sharedKey, 32); - memcpy (macKey, sharedKey + 32, 32); - } - else - { - // find first non-zero byte - uint8_t * nonZero = sharedKey + 1; - while (!*nonZero) - { - nonZero++; - if (nonZero - sharedKey > 32) - { - LogPrint (eLogWarning, "SSU: first 32 bytes of shared key is all zeros. Ignored"); - return; - } - } - - memcpy (sessionKey, nonZero, 32); - SHA256(nonZero, 64 - (nonZero - sharedKey), macKey); - } - m_IsSessionKey = true; - m_SessionKeyEncryption.SetKey (m_SessionKey); - m_SessionKeyDecryption.SetKey (m_SessionKey); - } - - void SSUSession::ProcessNextMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint) - { - m_NumReceivedBytes += len; - i2p::transport::transports.UpdateReceivedBytes (len); - if (m_State == eSessionStateIntroduced) - { - // HolePunch received - LogPrint (eLogDebug, "SSU: HolePunch of ", len, " bytes received"); - m_State = eSessionStateUnknown; - Connect (); - } - else - { - if (!len) return; // ignore zero-length packets - if (m_State == eSessionStateEstablished) - m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch (); - - if (m_IsSessionKey && Validate (buf, len, m_MacKey)) // try session key first - DecryptSessionKey (buf, len); - else - { - if (m_State == eSessionStateEstablished) Reset (); // new session key required - // try intro key depending on side - if (Validate (buf, len, m_IntroKey)) - Decrypt (buf, len, m_IntroKey); - else - { - // try own intro key - auto address = i2p::context.GetRouterInfo ().GetSSUAddress (false); - if (!address) - { - LogPrint (eLogInfo, "SSU is not supported"); - return; - } - if (Validate (buf, len, address->ssu->key)) - Decrypt (buf, len, address->ssu->key); - else - { - LogPrint (eLogWarning, "SSU: MAC verification failed ", len, " bytes from ", senderEndpoint); - m_Server.DeleteSession (shared_from_this ()); - return; - } - } - } - // successfully decrypted - ProcessMessage (buf, len, senderEndpoint); - } - } - - size_t SSUSession::GetSSUHeaderSize (const uint8_t * buf) const - { - size_t s = sizeof (SSUHeader); - if (((const SSUHeader *)buf)->IsExtendedOptions ()) - s += buf[s] + 1; // byte right after header is extended options length - return s; - } - - void SSUSession::ProcessMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint) - { - len -= (len & 0x0F); // %16, delete extra padding - if (len <= sizeof (SSUHeader)) return; // drop empty message - //TODO: since we are accessing a uint8_t this is unlikely to crash due to alignment but should be improved - auto headerSize = GetSSUHeaderSize (buf); - if (headerSize >= len) - { - LogPrint (eLogError, "SSU header size ", headerSize, " exceeds packet length ", len); - return; - } - SSUHeader * header = (SSUHeader *)buf; - switch (header->GetPayloadType ()) - { - case PAYLOAD_TYPE_DATA: - ProcessData (buf + headerSize, len - headerSize); - break; - case PAYLOAD_TYPE_SESSION_REQUEST: - ProcessSessionRequest (buf, len); // buf with header - break; - case PAYLOAD_TYPE_SESSION_CREATED: - ProcessSessionCreated (buf, len); // buf with header - break; - case PAYLOAD_TYPE_SESSION_CONFIRMED: - ProcessSessionConfirmed (buf, len); // buf with header - break; - case PAYLOAD_TYPE_PEER_TEST: - LogPrint (eLogDebug, "SSU: peer test received"); - ProcessPeerTest (buf + headerSize, len - headerSize, senderEndpoint); - break; - case PAYLOAD_TYPE_SESSION_DESTROYED: - { - LogPrint (eLogDebug, "SSU: session destroy received"); - m_Server.DeleteSession (shared_from_this ()); - break; - } - case PAYLOAD_TYPE_RELAY_RESPONSE: - ProcessRelayResponse (buf + headerSize, len - headerSize); - if (m_State != eSessionStateEstablished) - m_Server.DeleteSession (shared_from_this ()); - break; - case PAYLOAD_TYPE_RELAY_REQUEST: - LogPrint (eLogDebug, "SSU: relay request received"); - ProcessRelayRequest (buf + headerSize, len - headerSize, senderEndpoint); - break; - case PAYLOAD_TYPE_RELAY_INTRO: - LogPrint (eLogDebug, "SSU: relay intro received"); - ProcessRelayIntro (buf + headerSize, len - headerSize); - break; - default: - LogPrint (eLogWarning, "SSU: Unexpected payload type ", (int)header->GetPayloadType ()); - } - } - - void SSUSession::ProcessSessionRequest (const uint8_t * buf, size_t len) - { - LogPrint (eLogDebug, "SSU message: session request"); - bool sendRelayTag = true; - auto headerSize = sizeof (SSUHeader); - if (((SSUHeader *)buf)->IsExtendedOptions ()) - { - uint8_t extendedOptionsLen = buf[headerSize]; - headerSize++; - if (extendedOptionsLen >= 3) // options are presented - { - uint16_t flags = bufbe16toh (buf + headerSize); - sendRelayTag = flags & EXTENDED_OPTIONS_FLAG_REQUEST_RELAY_TAG; - } - headerSize += extendedOptionsLen; - } - if (headerSize >= len) - { - LogPrint (eLogError, "Session request header size ", headerSize, " exceeds packet length ", len); - return; - } - if (!m_DHKeysPair) - m_DHKeysPair = transports.GetNextDHKeysPair (); - CreateAESandMacKey (buf + headerSize); - SendSessionCreated (buf + headerSize, sendRelayTag); - } - - void SSUSession::ProcessSessionCreated (uint8_t * buf, size_t len) - { - if (!IsOutgoing () || !m_DHKeysPair) - { - LogPrint (eLogWarning, "SSU: Unsolicited session created message"); - return; - } - - LogPrint (eLogDebug, "SSU message: session created"); - m_ConnectTimer.cancel (); // connect timer - SignedData s; // x,y, our IP, our port, remote IP, remote port, relayTag, signed on time - auto headerSize = GetSSUHeaderSize (buf); - if (headerSize >= len) - { - LogPrint (eLogError, "Session created header size ", headerSize, " exceeds packet length ", len); - return; - } - uint8_t * payload = buf + headerSize; - uint8_t * y = payload; - CreateAESandMacKey (y); - s.Insert (m_DHKeysPair->GetPublicKey (), 256); // x - s.Insert (y, 256); // y - payload += 256; - uint8_t addressSize = *payload; - payload += 1; // size - uint8_t * ourAddress = payload; - boost::asio::ip::address ourIP; - if (addressSize == 4) // v4 - { - boost::asio::ip::address_v4::bytes_type bytes; - memcpy (bytes.data (), ourAddress, 4); - ourIP = boost::asio::ip::address_v4 (bytes); - } - else // v6 - { - boost::asio::ip::address_v6::bytes_type bytes; - memcpy (bytes.data (), ourAddress, 16); - ourIP = boost::asio::ip::address_v6 (bytes); - } - s.Insert (ourAddress, addressSize); // our IP - payload += addressSize; // address - uint16_t ourPort = bufbe16toh (payload); - s.Insert (payload, 2); // our port - payload += 2; // port - if (m_RemoteEndpoint.address ().is_v4 ()) - s.Insert (m_RemoteEndpoint.address ().to_v4 ().to_bytes ().data (), 4); // remote IP v4 - else - s.Insert (m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data (), 16); // remote IP v6 - s.Insert (htobe16 (m_RemoteEndpoint.port ())); // remote port - s.Insert (payload, 8); // relayTag and signed on time - m_RelayTag = bufbe32toh (payload); - payload += 4; // relayTag - if (i2p::context.GetStatus () == eRouterStatusTesting) - { - auto ts = i2p::util::GetSecondsSinceEpoch (); - uint32_t signedOnTime = bufbe32toh(payload); - if (signedOnTime < ts - SSU_CLOCK_SKEW || signedOnTime > ts + SSU_CLOCK_SKEW) - { - LogPrint (eLogError, "SSU: clock skew detected ", (int)ts - signedOnTime, ". Check your clock"); - i2p::context.SetError (eRouterErrorClockSkew); - } - } - payload += 4; // signed on time - // decrypt signature - size_t signatureLen = m_RemoteIdentity->GetSignatureLen (); - size_t paddingSize = signatureLen & 0x0F; // %16 - if (paddingSize > 0) signatureLen += (16 - paddingSize); - //TODO: since we are accessing a uint8_t this is unlikely to crash due to alignment but should be improved - m_SessionKeyDecryption.SetIV (((SSUHeader *)buf)->iv); - m_SessionKeyDecryption.Decrypt (payload, signatureLen, payload); // TODO: non-const payload - // verify signature - if (s.Verify (m_RemoteIdentity, payload)) - { - LogPrint (eLogInfo, "SSU: Our external address is ", ourIP.to_string (), ":", ourPort); - i2p::context.UpdateAddress (ourIP); - SendSessionConfirmed (y, ourAddress, addressSize + 2); - } - else - { - LogPrint (eLogError, "SSU: message 'created' signature verification failed"); - Failed (); - } - } - - void SSUSession::ProcessSessionConfirmed (const uint8_t * buf, size_t len) - { - LogPrint (eLogDebug, "SSU: Session confirmed received"); - auto headerSize = GetSSUHeaderSize (buf); - if (headerSize >= len) - { - LogPrint (eLogError, "SSU: Session confirmed header size ", len, " exceeds packet length ", len); - return; - } - const uint8_t * payload = buf + headerSize; - payload++; // identity fragment info - uint16_t identitySize = bufbe16toh (payload); - payload += 2; // size of identity fragment - auto identity = std::make_shared (payload, identitySize); - auto existing = i2p::data::netdb.FindRouter (identity->GetIdentHash ()); // check if exists already - SetRemoteIdentity (existing ? existing->GetRouterIdentity () : identity); - m_Data.UpdatePacketSize (m_RemoteIdentity->GetIdentHash ()); - payload += identitySize; // identity - auto ts = i2p::util::GetSecondsSinceEpoch (); - uint32_t signedOnTime = bufbe32toh(payload); - if (signedOnTime < ts - SSU_CLOCK_SKEW || signedOnTime > ts + SSU_CLOCK_SKEW) - { - LogPrint (eLogError, "SSU message 'confirmed' time difference ", (int)ts - signedOnTime, " exceeds clock skew"); - Failed (); - return; - } - if (m_SignedData) - m_SignedData->Insert (payload, 4); // insert Alice's signed on time - payload += 4; // signed-on time - size_t paddingSize = (payload - buf) + m_RemoteIdentity->GetSignatureLen (); - paddingSize &= 0x0F; // %16 - if (paddingSize > 0) paddingSize = 16 - paddingSize; - payload += paddingSize; - // verify signature - if (m_SignedData && m_SignedData->Verify (m_RemoteIdentity, payload)) - { - m_Data.Send (CreateDeliveryStatusMsg (0)); - Established (); - } - else - { - LogPrint (eLogError, "SSU message 'confirmed' signature verification failed"); - Failed (); - } - } - - void SSUSession::SendSessionRequest () - { - uint8_t buf[320 + 18] = {0}; // 304 bytes for ipv4, 320 for ipv6 - uint8_t * payload = buf + sizeof (SSUHeader); - uint8_t flag = 0; - // fill extended options, 3 bytes extended options don't change message size - if (i2p::context.GetStatus () == eRouterStatusOK) // we don't need relays - { - // tell out peer to now assign relay tag - flag = SSU_HEADER_EXTENDED_OPTIONS_INCLUDED; - *payload = 2; payload++; // 1 byte length - uint16_t flags = 0; // clear EXTENDED_OPTIONS_FLAG_REQUEST_RELAY_TAG - htobe16buf (payload, flags); - payload += 2; - } - // fill payload - memcpy (payload, m_DHKeysPair->GetPublicKey (), 256); // x - bool isV4 = m_RemoteEndpoint.address ().is_v4 (); - if (isV4) - { - payload[256] = 4; - memcpy (payload + 257, m_RemoteEndpoint.address ().to_v4 ().to_bytes ().data(), 4); - } - else - { - payload[256] = 16; - memcpy (payload + 257, m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data(), 16); - } - // encrypt and send - uint8_t iv[16]; - RAND_bytes (iv, 16); // random iv - FillHeaderAndEncrypt (PAYLOAD_TYPE_SESSION_REQUEST, buf, isV4 ? 304 : 320, m_IntroKey, iv, m_IntroKey, flag); - m_Server.Send (buf, isV4 ? 304 : 320, m_RemoteEndpoint); - } - - void SSUSession::SendRelayRequest (const i2p::data::RouterInfo::Introducer& introducer, uint32_t nonce) - { - auto address = i2p::context.GetRouterInfo ().GetSSUAddress (false); - if (!address) - { - LogPrint (eLogInfo, "SSU is not supported"); - return; - } - - uint8_t buf[96 + 18] = {0}; - uint8_t * payload = buf + sizeof (SSUHeader); - htobe32buf (payload, introducer.iTag); - payload += 4; - *payload = 0; // no address - payload++; - htobuf16(payload, 0); // port = 0 - payload += 2; - *payload = 0; // challenge - payload++; - memcpy (payload, (const uint8_t *)address->ssu->key, 32); - payload += 32; - htobe32buf (payload, nonce); // nonce - - uint8_t iv[16]; - RAND_bytes (iv, 16); // random iv - if (m_State == eSessionStateEstablished) - FillHeaderAndEncrypt (PAYLOAD_TYPE_RELAY_REQUEST, buf, 96, m_SessionKey, iv, m_MacKey); - else - FillHeaderAndEncrypt (PAYLOAD_TYPE_RELAY_REQUEST, buf, 96, introducer.iKey, iv, introducer.iKey); - m_Server.Send (buf, 96, m_RemoteEndpoint); - } - - void SSUSession::SendSessionCreated (const uint8_t * x, bool sendRelayTag) - { - auto address = IsV6 () ? i2p::context.GetRouterInfo ().GetSSUV6Address () : - i2p::context.GetRouterInfo ().GetSSUAddress (true); //v4 only - if (!address) - { - LogPrint (eLogInfo, "SSU is not supported"); - return; - } - SignedData s; // x,y, remote IP, remote port, our IP, our port, relayTag, signed on time - s.Insert (x, 256); // x - - uint8_t buf[384 + 18] = {0}; - uint8_t * payload = buf + sizeof (SSUHeader); - memcpy (payload, m_DHKeysPair->GetPublicKey (), 256); - s.Insert (payload, 256); // y - payload += 256; - if (m_RemoteEndpoint.address ().is_v4 ()) - { - // ipv4 - *payload = 4; - payload++; - memcpy (payload, m_RemoteEndpoint.address ().to_v4 ().to_bytes ().data(), 4); - s.Insert (payload, 4); // remote endpoint IP V4 - payload += 4; - } - else - { - // ipv6 - *payload = 16; - payload++; - memcpy (payload, m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data(), 16); - s.Insert (payload, 16); // remote endpoint IP V6 - payload += 16; - } - htobe16buf (payload, m_RemoteEndpoint.port ()); - s.Insert (payload, 2); // remote port - payload += 2; - if (address->host.is_v4 ()) - s.Insert (address->host.to_v4 ().to_bytes ().data (), 4); // our IP V4 - else - s.Insert (address->host.to_v6 ().to_bytes ().data (), 16); // our IP V6 - s.Insert (htobe16 (address->port)); // our port - if (sendRelayTag && i2p::context.GetRouterInfo ().IsIntroducer () && !IsV6 ()) - { - RAND_bytes((uint8_t *)&m_SentRelayTag, 4); - if (!m_SentRelayTag) m_SentRelayTag = 1; - } - htobe32buf (payload, m_SentRelayTag); - payload += 4; // relay tag - htobe32buf (payload, i2p::util::GetSecondsSinceEpoch ()); // signed on time - payload += 4; - s.Insert (payload - 8, 4); // relayTag - // we have to store this signed data for session confirmed - // same data but signed on time, it will Alice's there - m_SignedData = std::unique_ptr(new SignedData (s)); - s.Insert (payload - 4, 4); // BOB's signed on time - s.Sign (i2p::context.GetPrivateKeys (), payload); // DSA signature - - uint8_t iv[16]; - RAND_bytes (iv, 16); // random iv - // encrypt signature and padding with newly created session key - size_t signatureLen = i2p::context.GetIdentity ()->GetSignatureLen (); - size_t paddingSize = signatureLen & 0x0F; // %16 - if (paddingSize > 0) - { - // fill random padding - RAND_bytes(payload + signatureLen, (16 - paddingSize)); - signatureLen += (16 - paddingSize); - } - m_SessionKeyEncryption.SetIV (iv); - m_SessionKeyEncryption.Encrypt (payload, signatureLen, payload); - payload += signatureLen; - size_t msgLen = payload - buf; - - // encrypt message with intro key - FillHeaderAndEncrypt (PAYLOAD_TYPE_SESSION_CREATED, buf, msgLen, m_IntroKey, iv, m_IntroKey); - Send (buf, msgLen); - } - - void SSUSession::SendSessionConfirmed (const uint8_t * y, const uint8_t * ourAddress, size_t ourAddressLen) - { - uint8_t buf[512 + 18] = {0}; - uint8_t * payload = buf + sizeof (SSUHeader); - *payload = 1; // 1 fragment - payload++; // info - size_t identLen = i2p::context.GetIdentity ()->GetFullLen (); // 387+ bytes - htobe16buf (payload, identLen); - payload += 2; // cursize - i2p::context.GetIdentity ()->ToBuffer (payload, identLen); - payload += identLen; - uint32_t signedOnTime = i2p::util::GetSecondsSinceEpoch (); - htobe32buf (payload, signedOnTime); // signed on time - payload += 4; - auto signatureLen = i2p::context.GetIdentity ()->GetSignatureLen (); - size_t paddingSize = ((payload - buf) + signatureLen)%16; - if (paddingSize > 0) paddingSize = 16 - paddingSize; - RAND_bytes(payload, paddingSize); // fill padding with random - payload += paddingSize; // padding size - // signature - SignedData s; // x,y, our IP, our port, remote IP, remote port, relayTag, our signed on time - s.Insert (m_DHKeysPair->GetPublicKey (), 256); // x - s.Insert (y, 256); // y - s.Insert (ourAddress, ourAddressLen); // our address/port as seem by party - if (m_RemoteEndpoint.address ().is_v4 ()) - s.Insert (m_RemoteEndpoint.address ().to_v4 ().to_bytes ().data (), 4); // remote IP V4 - else - s.Insert (m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data (), 16); // remote IP V6 - s.Insert (htobe16 (m_RemoteEndpoint.port ())); // remote port - s.Insert (htobe32 (m_RelayTag)); // relay tag - s.Insert (htobe32 (signedOnTime)); // signed on time - s.Sign (i2p::context.GetPrivateKeys (), payload); // DSA signature - payload += signatureLen; - - size_t msgLen = payload - buf; - uint8_t iv[16]; - RAND_bytes (iv, 16); // random iv - // encrypt message with session key - FillHeaderAndEncrypt (PAYLOAD_TYPE_SESSION_CONFIRMED, buf, msgLen, m_SessionKey, iv, m_MacKey); - Send (buf, msgLen); - } - - void SSUSession::ProcessRelayRequest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& from) - { - uint32_t relayTag = bufbe32toh (buf); - auto session = m_Server.FindRelaySession (relayTag); - if (session) - { - buf += 4; // relay tag - uint8_t size = *buf; - buf++; // size - buf += size; // address - buf += 2; // port - uint8_t challengeSize = *buf; - buf++; // challenge size - buf += challengeSize; - const uint8_t * introKey = buf; - buf += 32; // introkey - uint32_t nonce = bufbe32toh (buf); - SendRelayResponse (nonce, from, introKey, session->m_RemoteEndpoint); - SendRelayIntro (session, from); - } - } - - void SSUSession::SendRelayResponse (uint32_t nonce, const boost::asio::ip::udp::endpoint& from, - const uint8_t * introKey, const boost::asio::ip::udp::endpoint& to) - { - // Charlie's address always v4 - if (!to.address ().is_v4 ()) - { - LogPrint (eLogWarning, "SSU: Charlie's IP must be v4"); - return; - } - uint8_t buf[80 + 18] = {0}; // 64 Alice's ipv4 and 80 Alice's ipv6 - uint8_t * payload = buf + sizeof (SSUHeader); - *payload = 4; - payload++; // size - htobe32buf (payload, to.address ().to_v4 ().to_ulong ()); // Charlie's IP - payload += 4; // address - htobe16buf (payload, to.port ()); // Charlie's port - payload += 2; // port - // Alice - bool isV4 = from.address ().is_v4 (); // Alice's - if (isV4) - { - *payload = 4; - payload++; // size - memcpy (payload, from.address ().to_v4 ().to_bytes ().data (), 4); // Alice's IP V4 - payload += 4; // address - } - else - { - *payload = 16; - payload++; // size - memcpy (payload, from.address ().to_v6 ().to_bytes ().data (), 16); // Alice's IP V6 - payload += 16; // address - } - htobe16buf (payload, from.port ()); // Alice's port - payload += 2; // port - htobe32buf (payload, nonce); - - if (m_State == eSessionStateEstablished) - { - // encrypt with session key - FillHeaderAndEncrypt (PAYLOAD_TYPE_RELAY_RESPONSE, buf, isV4 ? 64 : 80); - Send (buf, isV4 ? 64 : 80); - } - else - { - // ecrypt with Alice's intro key - uint8_t iv[16]; - RAND_bytes (iv, 16); // random iv - FillHeaderAndEncrypt (PAYLOAD_TYPE_RELAY_RESPONSE, buf, isV4 ? 64 : 80, introKey, iv, introKey); - m_Server.Send (buf, isV4 ? 64 : 80, from); - } - LogPrint (eLogDebug, "SSU: relay response sent"); - } - - void SSUSession::SendRelayIntro (std::shared_ptr session, const boost::asio::ip::udp::endpoint& from) - { - if (!session) return; - // Alice's address always v4 - if (!from.address ().is_v4 ()) - { - LogPrint (eLogWarning, "SSU: Alice's IP must be v4"); - return; - } - uint8_t buf[48 + 18] = {0}; - uint8_t * payload = buf + sizeof (SSUHeader); - *payload = 4; - payload++; // size - htobe32buf (payload, from.address ().to_v4 ().to_ulong ()); // Alice's IP - payload += 4; // address - htobe16buf (payload, from.port ()); // Alice's port - payload += 2; // port - *payload = 0; // challenge size - uint8_t iv[16]; - RAND_bytes (iv, 16); // random iv - FillHeaderAndEncrypt (PAYLOAD_TYPE_RELAY_INTRO, buf, 48, session->m_SessionKey, iv, session->m_MacKey); - m_Server.Send (buf, 48, session->m_RemoteEndpoint); - LogPrint (eLogDebug, "SSU: relay intro sent"); - } - - void SSUSession::ProcessRelayResponse (const uint8_t * buf, size_t len) - { - LogPrint (eLogDebug, "SSU message: Relay response received"); - uint8_t remoteSize = *buf; - buf++; // remote size - boost::asio::ip::address_v4 remoteIP (bufbe32toh (buf)); - buf += remoteSize; // remote address - uint16_t remotePort = bufbe16toh (buf); - buf += 2; // remote port - uint8_t ourSize = *buf; - buf++; // our size - boost::asio::ip::address ourIP; - if (ourSize == 4) - { - boost::asio::ip::address_v4::bytes_type bytes; - memcpy (bytes.data (), buf, 4); - ourIP = boost::asio::ip::address_v4 (bytes); - } - else - { - boost::asio::ip::address_v6::bytes_type bytes; - memcpy (bytes.data (), buf, 16); - ourIP = boost::asio::ip::address_v6 (bytes); - } - buf += ourSize; // our address - uint16_t ourPort = bufbe16toh (buf); - buf += 2; // our port - LogPrint (eLogInfo, "SSU: Our external address is ", ourIP.to_string (), ":", ourPort); - i2p::context.UpdateAddress (ourIP); - uint32_t nonce = bufbe32toh (buf); - buf += 4; // nonce - auto it = m_RelayRequests.find (nonce); - if (it != m_RelayRequests.end ()) - { - // check if we are waiting for introduction - boost::asio::ip::udp::endpoint remoteEndpoint (remoteIP, remotePort); - if (!m_Server.FindSession (remoteEndpoint)) - { - // we didn't have correct endpoint when sent relay request - // now we do - LogPrint (eLogInfo, "SSU: RelayReponse connecting to endpoint ", remoteEndpoint); - if (i2p::context.GetRouterInfo ().UsesIntroducer ()) // if we are unreachable - m_Server.Send (buf, 0, remoteEndpoint); // send HolePunch - m_Server.CreateDirectSession (it->second, remoteEndpoint, false); - } - // delete request - m_RelayRequests.erase (it); - } - else - LogPrint (eLogError, "SSU: Unsolicited RelayResponse, nonce=", nonce); - } - - void SSUSession::ProcessRelayIntro (const uint8_t * buf, size_t len) - { - uint8_t size = *buf; - if (size == 4) - { - buf++; // size - boost::asio::ip::address_v4 address (bufbe32toh (buf)); - buf += 4; // address - uint16_t port = bufbe16toh (buf); - // send hole punch of 0 bytes - m_Server.Send (buf, 0, boost::asio::ip::udp::endpoint (address, port)); - } - else - LogPrint (eLogWarning, "SSU: Address size ", size, " is not supported"); - } - - void SSUSession::FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len, - const i2p::crypto::AESKey& aesKey, const uint8_t * iv, const i2p::crypto::MACKey& macKey, uint8_t flag) - { - if (len < sizeof (SSUHeader)) - { - LogPrint (eLogError, "SSU: Unexpected packet length ", len); - return; - } - SSUHeader * header = (SSUHeader *)buf; - memcpy (header->iv, iv, 16); - header->flag = flag | (payloadType << 4); // MSB is 0 - htobe32buf (header->time, i2p::util::GetSecondsSinceEpoch ()); - uint8_t * encrypted = &header->flag; - uint16_t encryptedLen = len - (encrypted - buf); - i2p::crypto::CBCEncryption encryption; - encryption.SetKey (aesKey); - encryption.SetIV (iv); - encryption.Encrypt (encrypted, encryptedLen, encrypted); - // assume actual buffer size is 18 (16 + 2) bytes more - memcpy (buf + len, iv, 16); - uint16_t netid = i2p::context.GetNetID (); - htobe16buf (buf + len + 16, (netid == I2PD_NET_ID) ? encryptedLen : encryptedLen ^ ((netid - 2) << 8)); - i2p::crypto::HMACMD5Digest (encrypted, encryptedLen + 18, macKey, header->mac); - } - - void SSUSession::FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len) - { - FillHeaderAndEncrypt (payloadType, buf, len, buf); - } - - void SSUSession::FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * in, size_t len, uint8_t * out) - { - if (len < sizeof (SSUHeader)) - { - LogPrint (eLogError, "SSU: Unexpected packet length ", len); - return; - } - SSUHeader * header = (SSUHeader *)out; - RAND_bytes (header->iv, 16); // random iv - m_SessionKeyEncryption.SetIV (header->iv); - SSUHeader * inHeader = (SSUHeader *)in; - inHeader->flag = payloadType << 4; // MSB is 0 - htobe32buf (inHeader->time, i2p::util::GetSecondsSinceEpoch ()); - uint8_t * encrypted = &header->flag, * clear = &inHeader->flag; - uint16_t encryptedLen = len - (encrypted - out); - m_SessionKeyEncryption.Encrypt (clear, encryptedLen, encrypted); - // assume actual out buffer size is 18 (16 + 2) bytes more - memcpy (out + len, header->iv, 16); - uint16_t netid = i2p::context.GetNetID (); - htobe16buf (out + len + 16, (netid == I2PD_NET_ID) ? encryptedLen : encryptedLen ^ ((netid - 2) << 8)); - i2p::crypto::HMACMD5Digest (encrypted, encryptedLen + 18, m_MacKey, header->mac); - } - - void SSUSession::Decrypt (uint8_t * buf, size_t len, const i2p::crypto::AESKey& aesKey) - { - if (len < sizeof (SSUHeader)) - { - LogPrint (eLogError, "SSU: Unexpected packet length ", len); - return; - } - SSUHeader * header = (SSUHeader *)buf; - uint8_t * encrypted = &header->flag; - uint16_t encryptedLen = len - (encrypted - buf); - i2p::crypto::CBCDecryption decryption; - decryption.SetKey (aesKey); - decryption.SetIV (header->iv); - decryption.Decrypt (encrypted, encryptedLen, encrypted); - } - - void SSUSession::DecryptSessionKey (uint8_t * buf, size_t len) - { - if (len < sizeof (SSUHeader)) - { - LogPrint (eLogError, "SSU: Unexpected packet length ", len); - return; - } - SSUHeader * header = (SSUHeader *)buf; - uint8_t * encrypted = &header->flag; - uint16_t encryptedLen = len - (encrypted - buf); - if (encryptedLen > 0) - { - m_SessionKeyDecryption.SetIV (header->iv); - m_SessionKeyDecryption.Decrypt (encrypted, encryptedLen, encrypted); - } - } - - bool SSUSession::Validate (uint8_t * buf, size_t len, const i2p::crypto::MACKey& macKey) - { - if (len < sizeof (SSUHeader)) - { - LogPrint (eLogError, "SSU: Unexpected packet length ", len); - return false; - } - SSUHeader * header = (SSUHeader *)buf; - uint8_t * encrypted = &header->flag; - uint16_t encryptedLen = len - (encrypted - buf); - // assume actual buffer size is 18 (16 + 2) bytes more - memcpy (buf + len, header->iv, 16); - uint16_t netid = i2p::context.GetNetID (); - htobe16buf (buf + len + 16, (netid == I2PD_NET_ID) ? encryptedLen : encryptedLen ^ ((netid - 2) << 8)); - uint8_t digest[16]; - i2p::crypto::HMACMD5Digest (encrypted, encryptedLen + 18, macKey, digest); - return !memcmp (header->mac, digest, 16); - } - - void SSUSession::Connect () - { - if (m_State == eSessionStateUnknown) - { - // set connect timer - ScheduleConnectTimer (); - m_DHKeysPair = transports.GetNextDHKeysPair (); - SendSessionRequest (); - } - } - - void SSUSession::WaitForConnect () - { - if (!IsOutgoing ()) // incoming session - ScheduleConnectTimer (); - else - LogPrint (eLogError, "SSU: wait for connect for outgoing session"); - } - - void SSUSession::ScheduleConnectTimer () - { - m_ConnectTimer.cancel (); - m_ConnectTimer.expires_from_now (boost::posix_time::seconds(SSU_CONNECT_TIMEOUT)); - m_ConnectTimer.async_wait (std::bind (&SSUSession::HandleConnectTimer, - shared_from_this (), std::placeholders::_1)); -} - - void SSUSession::HandleConnectTimer (const boost::system::error_code& ecode) - { - if (!ecode) - { - // timeout expired - LogPrint (eLogWarning, "SSU: session with ", m_RemoteEndpoint, " was not established after ", SSU_CONNECT_TIMEOUT, " seconds"); - Failed (); - } - } - - void SSUSession::Introduce (const i2p::data::RouterInfo::Introducer& introducer, - std::shared_ptr to) - { - if (m_State == eSessionStateUnknown) - { - // set connect timer - m_ConnectTimer.expires_from_now (boost::posix_time::seconds(SSU_CONNECT_TIMEOUT)); - m_ConnectTimer.async_wait (std::bind (&SSUSession::HandleConnectTimer, - shared_from_this (), std::placeholders::_1)); - } - uint32_t nonce; - RAND_bytes ((uint8_t *)&nonce, 4); - m_RelayRequests[nonce] = to; - SendRelayRequest (introducer, nonce); - } - - void SSUSession::WaitForIntroduction () - { - m_State = eSessionStateIntroduced; - // set connect timer - m_ConnectTimer.expires_from_now (boost::posix_time::seconds(SSU_CONNECT_TIMEOUT)); - m_ConnectTimer.async_wait (std::bind (&SSUSession::HandleConnectTimer, - shared_from_this (), std::placeholders::_1)); - } - - void SSUSession::Close () - { - SendSessionDestroyed (); - Reset (); - m_State = eSessionStateClosed; - } - - void SSUSession::Reset () - { - m_State = eSessionStateUnknown; - transports.PeerDisconnected (shared_from_this ()); - m_Data.Stop (); - m_ConnectTimer.cancel (); - if (m_SentRelayTag) - { - m_Server.RemoveRelay (m_SentRelayTag); // relay tag is not valid anymore - m_SentRelayTag = 0; - } - m_DHKeysPair = nullptr; - m_SignedData = nullptr; - m_IsSessionKey = false; - } - - void SSUSession::Done () - { - GetService ().post (std::bind (&SSUSession::Failed, shared_from_this ())); - } - - void SSUSession::Established () - { - m_State = eSessionStateEstablished; - m_DHKeysPair = nullptr; - m_SignedData = nullptr; - m_Data.Start (); - transports.PeerConnected (shared_from_this ()); - if (m_IsPeerTest) - SendPeerTest (); - if (m_SentRelayTag) - m_Server.AddRelay (m_SentRelayTag, shared_from_this ()); - m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch (); - } - - void SSUSession::Failed () - { - if (m_State != eSessionStateFailed) - { - m_State = eSessionStateFailed; - m_Server.DeleteSession (shared_from_this ()); - } - } - - void SSUSession::SendI2NPMessages (const std::vector >& msgs) - { - GetService ().post (std::bind (&SSUSession::PostI2NPMessages, shared_from_this (), msgs)); - } - - void SSUSession::PostI2NPMessages (std::vector > msgs) - { - if (m_State == eSessionStateEstablished) - { - for (const auto& it: msgs) - if (it) - { - if (it->GetLength () <= SSU_MAX_I2NP_MESSAGE_SIZE) - m_Data.Send (it); - else - LogPrint (eLogError, "SSU: I2NP message of size ", it->GetLength (), " can't be sent. Dropped"); - } - } - } - - void SSUSession::ProcessData (uint8_t * buf, size_t len) - { - m_Data.ProcessMessage (buf, len); - m_IsDataReceived = true; - } - - void SSUSession::FlushData () - { - if (m_IsDataReceived) - { - m_Data.FlushReceivedMessage (); - m_IsDataReceived = false; - } - } - - void SSUSession::ProcessPeerTest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint) - { - uint32_t nonce = bufbe32toh (buf); // 4 bytes - uint8_t size = buf[4]; // 1 byte - const uint8_t * address = buf + 5; // big endian, size bytes - uint16_t port = buf16toh(buf + size + 5); // big endian, 2 bytes - const uint8_t * introKey = buf + size + 7; - if (port && (size != 4) && (size != 16)) - { - LogPrint (eLogWarning, "SSU: Address of ", size, " bytes not supported"); - return; - } - switch (m_Server.GetPeerTestParticipant (nonce)) - { - // existing test - case ePeerTestParticipantAlice1: - { - if (m_Server.GetPeerTestSession (nonce) == shared_from_this ()) // Alice-Bob - { - LogPrint (eLogDebug, "SSU: peer test from Bob. We are Alice"); - if (i2p::context.GetStatus () == eRouterStatusTesting) // still not OK - i2p::context.SetStatus (eRouterStatusFirewalled); - } - else - { - LogPrint (eLogDebug, "SSU: first peer test from Charlie. We are Alice"); - if (m_State == eSessionStateEstablished) - LogPrint (eLogWarning, "SSU: first peer test from Charlie through established session. We are Alice"); - i2p::context.SetStatus (eRouterStatusOK); - m_Server.UpdatePeerTest (nonce, ePeerTestParticipantAlice2); - SendPeerTest (nonce, senderEndpoint.address (), senderEndpoint.port (), introKey, true, false); // to Charlie - } - break; - } - case ePeerTestParticipantAlice2: - { - if (m_Server.GetPeerTestSession (nonce) == shared_from_this ()) // Alice-Bob - LogPrint (eLogDebug, "SSU: peer test from Bob. We are Alice"); - else - { - // peer test successive - LogPrint (eLogDebug, "SSU: second peer test from Charlie. We are Alice"); - i2p::context.SetStatus (eRouterStatusOK); - m_Server.RemovePeerTest (nonce); - } - break; - } - case ePeerTestParticipantBob: - { - LogPrint (eLogDebug, "SSU: peer test from Charlie. We are Bob"); - auto session = m_Server.GetPeerTestSession (nonce); // session with Alice from PeerTest - if (session && session->m_State == eSessionStateEstablished) - session->Send (PAYLOAD_TYPE_PEER_TEST, buf, len); // back to Alice - m_Server.RemovePeerTest (nonce); // nonce has been used - break; - } - case ePeerTestParticipantCharlie: - { - LogPrint (eLogDebug, "SSU: peer test from Alice. We are Charlie"); - SendPeerTest (nonce, senderEndpoint.address (), senderEndpoint.port (), introKey); // to Alice with her actual address - m_Server.RemovePeerTest (nonce); // nonce has been used - break; - } - // test not found - case ePeerTestParticipantUnknown: - { - if (m_State == eSessionStateEstablished) - { - // new test - if (port) - { - LogPrint (eLogDebug, "SSU: peer test from Bob. We are Charlie"); - m_Server.NewPeerTest (nonce, ePeerTestParticipantCharlie); - Send (PAYLOAD_TYPE_PEER_TEST, buf, len); // back to Bob - boost::asio::ip::address addr; // Alice's address - if (size == 4) // v4 - { - boost::asio::ip::address_v4::bytes_type bytes; - memcpy (bytes.data (), address, 4); - addr = boost::asio::ip::address_v4 (bytes); - } - else // v6 - { - boost::asio::ip::address_v6::bytes_type bytes; - memcpy (bytes.data (), address, 16); - addr = boost::asio::ip::address_v6 (bytes); - } - SendPeerTest (nonce, addr, be16toh (port), introKey); // to Alice with her address received from Bob - } - else - { - LogPrint (eLogDebug, "SSU: peer test from Alice. We are Bob"); - auto session = senderEndpoint.address ().is_v4 () ? m_Server.GetRandomEstablishedV4Session (shared_from_this ()) : m_Server.GetRandomEstablishedV6Session (shared_from_this ()); // Charlie - if (session) - { - m_Server.NewPeerTest (nonce, ePeerTestParticipantBob, shared_from_this ()); - session->SendPeerTest (nonce, senderEndpoint.address (), senderEndpoint.port (), introKey, false); // to Charlie with Alice's actual address - } - } - } - else - LogPrint (eLogError, "SSU: unexpected peer test"); - } - } - } - - void SSUSession::SendPeerTest (uint32_t nonce, const boost::asio::ip::address& address, uint16_t port, - const uint8_t * introKey, bool toAddress, bool sendAddress) - // toAddress is true for Alice<->Chalie communications only - // sendAddress is false if message comes from Alice - { - uint8_t buf[80 + 18] = {0}; - uint8_t iv[16]; - uint8_t * payload = buf + sizeof (SSUHeader); - htobe32buf (payload, nonce); - payload += 4; // nonce - // address and port - if (sendAddress) - { - if (address.is_v4 ()) - { - *payload = 4; - memcpy (payload + 1, address.to_v4 ().to_bytes ().data (), 4); // our IP V4 - } - else if (address.is_v6 ()) - { - *payload = 16; - memcpy (payload + 1, address.to_v6 ().to_bytes ().data (), 16); // our IP V6 - } - else - *payload = 0; - payload += (payload[0] + 1); - } - else - { - *payload = 0; - payload++; //size - } - htobe16buf (payload, port); - payload += 2; // port - // intro key - if (toAddress) - { - // send our intro key to address instead of its own - auto addr = i2p::context.GetRouterInfo ().GetSSUAddress (); - if (addr) - memcpy (payload, addr->ssu->key, 32); // intro key - else - LogPrint (eLogInfo, "SSU is not supported. Can't send peer test"); - } - else - memcpy (payload, introKey, 32); // intro key - - // send - RAND_bytes (iv, 16); // random iv - if (toAddress) - { - // encrypt message with specified intro key - FillHeaderAndEncrypt (PAYLOAD_TYPE_PEER_TEST, buf, 80, introKey, iv, introKey); - boost::asio::ip::udp::endpoint e (address, port); - m_Server.Send (buf, 80, e); - } - else - { - // encrypt message with session key - FillHeaderAndEncrypt (PAYLOAD_TYPE_PEER_TEST, buf, 80); - Send (buf, 80); - } - } - - void SSUSession::SendPeerTest () - { - // we are Alice - LogPrint (eLogDebug, "SSU: sending peer test"); - auto address = i2p::context.GetRouterInfo ().GetSSUAddress (i2p::context.SupportsV4 ()); - if (!address) - { - LogPrint (eLogInfo, "SSU is not supported. Can't send peer test"); - return; - } - uint32_t nonce; - RAND_bytes ((uint8_t *)&nonce, 4); - if (!nonce) nonce = 1; - m_IsPeerTest = false; - m_Server.NewPeerTest (nonce, ePeerTestParticipantAlice1, shared_from_this ()); - SendPeerTest (nonce, boost::asio::ip::address(), 0, address->ssu->key, false, false); // address and port always zero for Alice - } - - void SSUSession::SendKeepAlive () - { - if (m_State == eSessionStateEstablished) - { - uint8_t buf[48 + 18] = {0}; - uint8_t * payload = buf + sizeof (SSUHeader); - *payload = 0; // flags - payload++; - *payload = 0; // num fragments - // encrypt message with session key - FillHeaderAndEncrypt (PAYLOAD_TYPE_DATA, buf, 48); - Send (buf, 48); - LogPrint (eLogDebug, "SSU: keep-alive sent"); - m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch (); - } - } - - void SSUSession::SendSessionDestroyed () - { - if (m_IsSessionKey) - { - uint8_t buf[48 + 18] = {0}; - // encrypt message with session key - FillHeaderAndEncrypt (PAYLOAD_TYPE_SESSION_DESTROYED, buf, 48); - try - { - Send (buf, 48); - } - catch (std::exception& ex) - { - LogPrint (eLogWarning, "SSU: exception while sending session destoroyed: ", ex.what ()); - } - LogPrint (eLogDebug, "SSU: session destroyed sent"); - } - } - - void SSUSession::Send (uint8_t type, const uint8_t * payload, size_t len) - { - uint8_t buf[SSU_MTU_V4 + 18] = {0}; - size_t msgSize = len + sizeof (SSUHeader); - size_t paddingSize = msgSize & 0x0F; // %16 - if (paddingSize > 0) msgSize += (16 - paddingSize); - if (msgSize > SSU_MTU_V4) - { - LogPrint (eLogWarning, "SSU: payload size ", msgSize, " exceeds MTU"); - return; - } - memcpy (buf + sizeof (SSUHeader), payload, len); - // encrypt message with session key - FillHeaderAndEncrypt (type, buf, msgSize); - Send (buf, msgSize); - } - - void SSUSession::Send (const uint8_t * buf, size_t size) - { - m_NumSentBytes += size; - i2p::transport::transports.UpdateSentBytes (size); - m_Server.Send (buf, size, m_RemoteEndpoint); - } -} -} diff --git a/libi2pd/SSUSession.h b/libi2pd/SSUSession.h deleted file mode 100644 index ea820517..00000000 --- a/libi2pd/SSUSession.h +++ /dev/null @@ -1,173 +0,0 @@ -/* -* Copyright (c) 2013-2020, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - -#ifndef SSU_SESSION_H__ -#define SSU_SESSION_H__ - -#include -#include -#include -#include "Crypto.h" -#include "I2NPProtocol.h" -#include "TransportSession.h" -#include "SSUData.h" - -namespace i2p -{ -namespace transport -{ - const uint8_t SSU_HEADER_EXTENDED_OPTIONS_INCLUDED = 0x04; - struct SSUHeader - { - uint8_t mac[16]; - uint8_t iv[16]; - uint8_t flag; - uint8_t time[4]; - - uint8_t GetPayloadType () const { return flag >> 4; }; - bool IsExtendedOptions () const { return flag & SSU_HEADER_EXTENDED_OPTIONS_INCLUDED; }; - }; - - const int SSU_CONNECT_TIMEOUT = 5; // 5 seconds - const int SSU_TERMINATION_TIMEOUT = 330; // 5.5 minutes - const int SSU_CLOCK_SKEW = 60; // in seconds - const size_t SSU_MAX_I2NP_MESSAGE_SIZE = 32768; - - // payload types (4 bits) - const uint8_t PAYLOAD_TYPE_SESSION_REQUEST = 0; - const uint8_t PAYLOAD_TYPE_SESSION_CREATED = 1; - const uint8_t PAYLOAD_TYPE_SESSION_CONFIRMED = 2; - const uint8_t PAYLOAD_TYPE_RELAY_REQUEST = 3; - const uint8_t PAYLOAD_TYPE_RELAY_RESPONSE = 4; - const uint8_t PAYLOAD_TYPE_RELAY_INTRO = 5; - const uint8_t PAYLOAD_TYPE_DATA = 6; - const uint8_t PAYLOAD_TYPE_PEER_TEST = 7; - const uint8_t PAYLOAD_TYPE_SESSION_DESTROYED = 8; - - // extended options - const uint16_t EXTENDED_OPTIONS_FLAG_REQUEST_RELAY_TAG = 0x0001; - - enum SessionState - { - eSessionStateUnknown, - eSessionStateIntroduced, - eSessionStateEstablished, - eSessionStateClosed, - eSessionStateFailed - }; - - enum PeerTestParticipant - { - ePeerTestParticipantUnknown = 0, - ePeerTestParticipantAlice1, - ePeerTestParticipantAlice2, - ePeerTestParticipantBob, - ePeerTestParticipantCharlie - }; - - class SSUServer; - class SSUSession: public TransportSession, public std::enable_shared_from_this - { - public: - - SSUSession (SSUServer& server, boost::asio::ip::udp::endpoint& remoteEndpoint, - std::shared_ptr router = nullptr, bool peerTest = false); - void ProcessNextMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); - ~SSUSession (); - - void Connect (); - void WaitForConnect (); - void Introduce (const i2p::data::RouterInfo::Introducer& introducer, - std::shared_ptr to); // Alice to Charlie - void WaitForIntroduction (); - void Close (); - void Done (); - void Failed (); - const boost::asio::ip::udp::endpoint& GetRemoteEndpoint () { return m_RemoteEndpoint; }; - - bool IsV6 () const { return m_RemoteEndpoint.address ().is_v6 (); }; - void SendI2NPMessages (const std::vector >& msgs); - void SendPeerTest (); // Alice - - SessionState GetState () const { return m_State; }; - size_t GetNumSentBytes () const { return m_NumSentBytes; }; - size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; }; - - void SendKeepAlive (); - uint32_t GetRelayTag () const { return m_RelayTag; }; - const i2p::data::RouterInfo::IntroKey& GetIntroKey () const { return m_IntroKey; }; - uint32_t GetCreationTime () const { return m_CreationTime; }; - - void FlushData (); - - private: - - boost::asio::io_service& GetService (); - void CreateAESandMacKey (const uint8_t * pubKey); - size_t GetSSUHeaderSize (const uint8_t * buf) const; - void PostI2NPMessages (std::vector > msgs); - void ProcessMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); // call for established session - void ProcessSessionRequest (const uint8_t * buf, size_t len); - void SendSessionRequest (); - void SendRelayRequest (const i2p::data::RouterInfo::Introducer& introducer, uint32_t nonce); - void ProcessSessionCreated (uint8_t * buf, size_t len); - void SendSessionCreated (const uint8_t * x, bool sendRelayTag = true); - void ProcessSessionConfirmed (const uint8_t * buf, size_t len); - void SendSessionConfirmed (const uint8_t * y, const uint8_t * ourAddress, size_t ourAddressLen); - void ProcessRelayRequest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& from); - void SendRelayResponse (uint32_t nonce, const boost::asio::ip::udp::endpoint& from, - const uint8_t * introKey, const boost::asio::ip::udp::endpoint& to); - void SendRelayIntro (std::shared_ptr session, const boost::asio::ip::udp::endpoint& from); - void ProcessRelayResponse (const uint8_t * buf, size_t len); - void ProcessRelayIntro (const uint8_t * buf, size_t len); - void Established (); - void ScheduleConnectTimer (); - void HandleConnectTimer (const boost::system::error_code& ecode); - void ProcessPeerTest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); - void SendPeerTest (uint32_t nonce, const boost::asio::ip::address& address, uint16_t port, const uint8_t * introKey, bool toAddress = true, bool sendAddress = true); - void ProcessData (uint8_t * buf, size_t len); - void SendSessionDestroyed (); - void Send (uint8_t type, const uint8_t * payload, size_t len); // with session key - void Send (const uint8_t * buf, size_t size); - - void FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len, const i2p::crypto::AESKey& aesKey, - const uint8_t * iv, const i2p::crypto::MACKey& macKey, uint8_t flag = 0); - void FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len); // with session key - void FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * in, size_t len, uint8_t * out); // with session key - void Decrypt (uint8_t * buf, size_t len, const i2p::crypto::AESKey& aesKey); - void DecryptSessionKey (uint8_t * buf, size_t len); - bool Validate (uint8_t * buf, size_t len, const i2p::crypto::MACKey& macKey); - - void Reset (); - - private: - - friend class SSUData; // TODO: change in later - SSUServer& m_Server; - const boost::asio::ip::udp::endpoint m_RemoteEndpoint; - boost::asio::deadline_timer m_ConnectTimer; - bool m_IsPeerTest; - SessionState m_State; - bool m_IsSessionKey; - uint32_t m_RelayTag; // received from peer - uint32_t m_SentRelayTag; // sent by us - i2p::crypto::CBCEncryption m_SessionKeyEncryption; - i2p::crypto::CBCDecryption m_SessionKeyDecryption; - i2p::crypto::AESKey m_SessionKey; - i2p::crypto::MACKey m_MacKey; - i2p::data::RouterInfo::IntroKey m_IntroKey; - uint32_t m_CreationTime; // seconds since epoch - SSUData m_Data; - bool m_IsDataReceived; - std::unique_ptr m_SignedData; // we need it for SessionConfirmed only - std::map > m_RelayRequests; // nonce->Charlie - }; -} -} - -#endif diff --git a/libi2pd/Signature.cpp b/libi2pd/Signature.cpp index 88ee4060..ebc188a9 100644 --- a/libi2pd/Signature.cpp +++ b/libi2pd/Signature.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -15,8 +15,7 @@ namespace i2p namespace crypto { #if OPENSSL_EDDSA - EDDSA25519Verifier::EDDSA25519Verifier (): - m_Pkey (nullptr) + EDDSA25519Verifier::EDDSA25519Verifier () { m_MDCtx = EVP_MD_CTX_create (); } @@ -24,13 +23,13 @@ namespace crypto EDDSA25519Verifier::~EDDSA25519Verifier () { EVP_MD_CTX_destroy (m_MDCtx); - if (m_Pkey) EVP_PKEY_free (m_Pkey); } void EDDSA25519Verifier::SetPublicKey (const uint8_t * signingKey) { - m_Pkey = EVP_PKEY_new_raw_public_key (EVP_PKEY_ED25519, NULL, signingKey, 32); - EVP_DigestVerifyInit (m_MDCtx, NULL, NULL, NULL, m_Pkey); + EVP_PKEY * pkey = EVP_PKEY_new_raw_public_key (EVP_PKEY_ED25519, NULL, signingKey, 32); + EVP_DigestVerifyInit (m_MDCtx, NULL, NULL, NULL, pkey); + EVP_PKEY_free (pkey); } bool EDDSA25519Verifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const @@ -100,33 +99,29 @@ namespace crypto #if OPENSSL_EDDSA EDDSA25519Signer::EDDSA25519Signer (const uint8_t * signingPrivateKey, const uint8_t * signingPublicKey): - m_Fallback (nullptr) + m_MDCtx (nullptr), m_Fallback (nullptr) { - m_Pkey = EVP_PKEY_new_raw_private_key (EVP_PKEY_ED25519, NULL, signingPrivateKey, 32); + EVP_PKEY * pkey = EVP_PKEY_new_raw_private_key (EVP_PKEY_ED25519, NULL, signingPrivateKey, 32); uint8_t publicKey[EDDSA25519_PUBLIC_KEY_LENGTH]; size_t len = EDDSA25519_PUBLIC_KEY_LENGTH; - EVP_PKEY_get_raw_public_key (m_Pkey, publicKey, &len); + EVP_PKEY_get_raw_public_key (pkey, publicKey, &len); if (signingPublicKey && memcmp (publicKey, signingPublicKey, EDDSA25519_PUBLIC_KEY_LENGTH)) { LogPrint (eLogWarning, "EdDSA public key mismatch. Fallback"); - EVP_PKEY_free (m_Pkey); m_Fallback = new EDDSA25519SignerCompat (signingPrivateKey, signingPublicKey); } else { m_MDCtx = EVP_MD_CTX_create (); - EVP_DigestSignInit (m_MDCtx, NULL, NULL, NULL, m_Pkey); + EVP_DigestSignInit (m_MDCtx, NULL, NULL, NULL, pkey); } + EVP_PKEY_free (pkey); } EDDSA25519Signer::~EDDSA25519Signer () { if (m_Fallback) delete m_Fallback; - else - { - EVP_MD_CTX_destroy (m_MDCtx); - EVP_PKEY_free (m_Pkey); - } + EVP_MD_CTX_destroy (m_MDCtx); } void EDDSA25519Signer::Sign (const uint8_t * buf, int len, uint8_t * signature) const @@ -135,7 +130,7 @@ namespace crypto else { size_t l = 64; - uint8_t sig[64]; // temporary buffer for signature. openssl issue #7232 + uint8_t sig[64]; // temporary buffer for signature. openssl issue #7232 EVP_DigestSign (m_MDCtx, sig, &l, buf, len); memcpy (signature, sig, 64); } diff --git a/libi2pd/Signature.h b/libi2pd/Signature.h index 18084603..e153e66d 100644 --- a/libi2pd/Signature.h +++ b/libi2pd/Signature.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -304,7 +304,6 @@ namespace crypto private: #if OPENSSL_EDDSA - EVP_PKEY * m_Pkey; EVP_MD_CTX * m_MDCtx; #else EDDSAPoint m_PublicKey; @@ -341,7 +340,7 @@ namespace crypto void Sign (const uint8_t * buf, int len, uint8_t * signature) const; private: - EVP_PKEY * m_Pkey; + EVP_MD_CTX * m_MDCtx; EDDSA25519SignerCompat * m_Fallback; }; diff --git a/libi2pd/Streaming.cpp b/libi2pd/Streaming.cpp index ab08f41f..c2cf3a0a 100644 --- a/libi2pd/Streaming.cpp +++ b/libi2pd/Streaming.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -21,8 +21,16 @@ namespace stream { void SendBufferQueue::Add (const uint8_t * buf, size_t len, SendHandler handler) { - m_Buffers.push_back (std::make_shared(buf, len, handler)); - m_Size += len; + Add (std::make_shared(buf, len, handler)); + } + + void SendBufferQueue::Add (std::shared_ptr buf) + { + if (buf) + { + m_Buffers.push_back (buf); + m_Size += buf->len; + } } size_t SendBufferQueue::Get (uint8_t * buf, size_t len) @@ -43,8 +51,8 @@ namespace stream { // partially rem = len - offset; - memcpy (buf + offset, nextBuffer->GetRemaningBuffer (), len - offset); - nextBuffer->offset += (len - offset); + memcpy (buf + offset, nextBuffer->GetRemaningBuffer (), rem); + nextBuffer->offset += rem; offset = len; // break } } @@ -94,8 +102,9 @@ namespace stream LogPrint (eLogDebug, "Streaming: Stream deleted"); } - void Stream::Terminate (bool deleteFromDestination) // shoudl be called from StreamingDestination::Stop only + void Stream::Terminate (bool deleteFromDestination) // should be called from StreamingDestination::Stop only { + m_Status = eStreamStatusTerminated; m_AckSendTimer.cancel (); m_ReceiveTimer.cancel (); m_ResendTimer.cancel (); @@ -130,14 +139,22 @@ namespace stream { 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 (); - bool isSyn = packet->IsSYN (); - if (!receivedSeqn && !isSyn) + if (!receivedSeqn && !packet->GetFlags ()) { // plain ack LogPrint (eLogDebug, "Streaming: Plain ACK received"); @@ -173,12 +190,13 @@ namespace stream m_IsAckSendScheduled = true; auto ackTimeout = m_RTT/10; if (ackTimeout > m_AckDelay) ackTimeout = m_AckDelay; + else if (ackTimeout < MIN_SEND_ACK_TIMEOUT) ackTimeout = MIN_SEND_ACK_TIMEOUT; m_AckSendTimer.expires_from_now (boost::posix_time::milliseconds(ackTimeout)); m_AckSendTimer.async_wait (std::bind (&Stream::HandleAckSendTimer, shared_from_this (), std::placeholders::_1)); } } - else if (isSyn) + else if (packet->IsSYN ()) // we have to send SYN back to incoming connection SendBuffer (); // also sets m_IsOpen } @@ -268,7 +286,20 @@ namespace stream const uint8_t * optionData = packet->GetOptionData (); size_t optionSize = packet->GetOptionSize (); if (flags & PACKET_FLAG_DELAY_REQUESTED) + { + if (!m_IsAckSendScheduled) + { + uint16_t delayRequested = bufbe16toh (optionData); + if (delayRequested > 0 && delayRequested < m_RTT) + { + m_IsAckSendScheduled = true; + m_AckSendTimer.expires_from_now (boost::posix_time::milliseconds(delayRequested)); + m_AckSendTimer.async_wait (std::bind (&Stream::HandleAckSendTimer, + shared_from_this (), std::placeholders::_1)); + } + } optionData += 2; + } if (flags & PACKET_FLAG_FROM_INCLUDED) { @@ -325,7 +356,7 @@ namespace stream if (flags & PACKET_FLAG_SIGNATURE_INCLUDED) { uint8_t signature[256]; - auto signatureLen = m_RemoteIdentity->GetSignatureLen (); + auto signatureLen = m_TransientVerifier ? m_TransientVerifier->GetSignatureLen () : m_RemoteIdentity->GetSignatureLen (); if(signatureLen <= sizeof(signature)) { memcpy (signature, optionData, signatureLen); @@ -361,7 +392,7 @@ namespace stream memset (p.buf, 0, 22); // minimal header all zeroes memcpy (p.buf + 4, packet->buf, 4); // but receiveStreamID is the sendStreamID from the ping htobe16buf (p.buf + 18, PACKET_FLAG_ECHO); // and echo flag - ssize_t payloadLen = packet->len - (packet->GetPayload () - packet->buf); + auto payloadLen = int(packet->len) - (packet->GetPayload () - packet->buf); if (payloadLen > 0) memcpy (p.buf + 22, packet->GetPayload (), payloadLen); else @@ -369,10 +400,10 @@ namespace stream 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; @@ -412,7 +443,7 @@ namespace stream LogPrint(eLogError, "Streaming: Packet ", seqn, "sent from the future, sendTime=", sentPacket->sendTime); rtt = 1; } - m_RTT = (m_RTT*seqn + rtt)/(seqn + 1); + m_RTT = std::round ((m_RTT*seqn + rtt)/(seqn + 1.0)); m_RTO = m_RTT*1.5; // TODO: implement it better LogPrint (eLogDebug, "Streaming: Packet ", seqn, " acknowledged rtt=", rtt, " sentTime=", sentPacket->sendTime); m_SentPackets.erase (it++); @@ -451,6 +482,48 @@ namespace stream Close (); // check is all outgoing messages have been sent and we can send close } + size_t Stream::Receive (uint8_t * buf, size_t len, int timeout) + { + if (!len) return 0; + size_t ret = 0; + volatile bool done = false; + std::condition_variable newDataReceived; + std::mutex newDataReceivedMutex; + AsyncReceive (boost::asio::buffer (buf, len), + [&ret, &done, &newDataReceived, &newDataReceivedMutex](const boost::system::error_code& ecode, std::size_t bytes_transferred) + { + if (ecode == boost::asio::error::timed_out) + ret = 0; + else + ret = bytes_transferred; + std::unique_lock l(newDataReceivedMutex); + newDataReceived.notify_all (); + done = true; + }, + timeout); + if (!done) + { std::unique_lock l(newDataReceivedMutex); + if (!done && newDataReceived.wait_for (l, std::chrono::seconds (timeout)) == std::cv_status::timeout) + ret = 0; + } + if (!done) + { + // make sure that AsycReceive complete + auto s = shared_from_this(); + m_Service.post ([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); @@ -495,8 +568,19 @@ namespace stream else htobe32buf (packet + size, m_LastReceivedSequenceNumber); size += 4; // ack Through - packet[size] = 0; - size++; // NACK count + 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) @@ -643,6 +727,42 @@ namespace stream 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); @@ -670,7 +790,7 @@ namespace stream Terminate (); break; default: - LogPrint (eLogWarning, "Streaming: Unexpected stream status ", (int)m_Status, "sSID=", m_SendStreamID); + LogPrint (eLogWarning, "Streaming: Unexpected stream status=", (int)m_Status, " for sSID=", m_SendStreamID); }; } @@ -693,7 +813,7 @@ namespace stream size++; // resend delay htobe16buf (packet + size, PACKET_FLAG_CLOSE | PACKET_FLAG_SIGNATURE_INCLUDED); size += 2; // flags - size_t signatureLen = m_LocalDestination.GetOwner ()->GetIdentity ()->GetSignatureLen (); + size_t signatureLen = m_LocalDestination.GetOwner ()->GetPrivateKeys ().GetSignatureLen (); htobe16buf (packet + size, signatureLen); // signature only size += 2; // options size uint8_t * signature = packet + size; @@ -756,7 +876,7 @@ namespace stream return; } } - if (!m_RoutingSession || !m_RoutingSession->GetOwner ()) // expired and detached + 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); if (!m_CurrentOutboundTunnel && m_RoutingSession) // first message to send { @@ -770,13 +890,6 @@ namespace stream m_RTO = m_RTT*1.5; // TODO: implement it better } } - if (!m_CurrentOutboundTunnel || !m_CurrentOutboundTunnel->IsEstablished ()) - m_CurrentOutboundTunnel = m_LocalDestination.GetOwner ()->GetTunnelPool ()->GetNewOutboundTunnel (m_CurrentOutboundTunnel); - if (!m_CurrentOutboundTunnel) - { - LogPrint (eLogError, "Streaming: No outbound tunnels in the pool, sSID=", m_SendStreamID); - return; - } auto ts = i2p::util::GetMillisecondsSinceEpoch (); if (!m_CurrentRemoteLease || !m_CurrentRemoteLease->endDate || // excluded from LeaseSet @@ -784,11 +897,26 @@ namespace stream UpdateCurrentRemoteLease (true); if (m_CurrentRemoteLease && ts < m_CurrentRemoteLease->endDate + i2p::data::LEASE_ENDDATE_THRESHOLD) { + 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); + } + else if (!m_CurrentOutboundTunnel->IsEstablished ()) + m_CurrentOutboundTunnel = m_LocalDestination.GetOwner ()->GetTunnelPool ()->GetNewOutboundTunnel (m_CurrentOutboundTunnel); + if (!m_CurrentOutboundTunnel) + { + LogPrint (eLogError, "Streaming: No outbound tunnels in the pool, sSID=", m_SendStreamID); + m_CurrentRemoteLease = nullptr; + return; + } + std::vector msgs; - for (auto it: packets) + for (const auto& it: packets) { auto msg = m_RoutingSession->WrapSingleMessage (m_LocalDestination.CreateDataMessage ( - it->GetBuffer (), it->GetLength (), m_Port, !m_RoutingSession->IsRatchets ())); + it->GetBuffer (), it->GetLength (), m_Port, !m_RoutingSession->IsRatchets (), it->IsSYN ())); msgs.push_back (i2p::tunnel::TunnelMessageBlock { i2p::tunnel::eDeliveryTypeTunnel, @@ -797,7 +925,7 @@ namespace stream }); m_NumSentBytes += it->GetLength (); } - m_CurrentOutboundTunnel->SendTunnelDataMsg (msgs); + m_CurrentOutboundTunnel->SendTunnelDataMsgs (msgs); } else { @@ -809,7 +937,7 @@ namespace stream void Stream::SendUpdatedLeaseSet () { - if (m_RoutingSession) + if (m_RoutingSession && !m_RoutingSession->IsTerminated ()) { if (m_RoutingSession->IsLeaseSetNonConfirmed ()) { @@ -830,16 +958,21 @@ namespace stream SendQuickAck (); } } + else + SendQuickAck (); } void Stream::ScheduleResend () { - 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)); + 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) @@ -932,15 +1065,26 @@ namespace stream { if (!m_RemoteLeaseSet || m_RemoteLeaseSet->IsExpired ()) { - m_RemoteLeaseSet = m_LocalDestination.GetOwner ()->FindLeaseSet (m_RemoteIdentity->GetIdentHash ()); - if (!m_RemoteLeaseSet) + auto remoteLeaseSet = m_LocalDestination.GetOwner ()->FindLeaseSet (m_RemoteIdentity->GetIdentHash ()); + if (!remoteLeaseSet) { - LogPrint (eLogWarning, "Streaming: LeaseSet ", m_RemoteIdentity->GetIdentHash ().ToBase64 (), " not found"); - m_LocalDestination.GetOwner ()->RequestDestination (m_RemoteIdentity->GetIdentHash ()); // try to request for a next attempt + LogPrint (eLogWarning, "Streaming: LeaseSet ", m_RemoteIdentity->GetIdentHash ().ToBase64 (), m_RemoteLeaseSet ? " expired" : " not found"); + if (m_RemoteLeaseSet && m_RemoteLeaseSet->IsPublishedEncrypted ()) + { + m_LocalDestination.GetOwner ()->RequestDestinationWithEncryptedLeaseSet ( + std::make_shared(m_RemoteIdentity)); + return; // we keep m_RemoteLeaseSet for possible next request + } + else + { + m_RemoteLeaseSet = nullptr; + m_LocalDestination.GetOwner ()->RequestDestination (m_RemoteIdentity->GetIdentHash ()); // try to request for a next attempt + } } else { // LeaseSet updated + m_RemoteLeaseSet = remoteLeaseSet; m_RemoteIdentity = m_RemoteLeaseSet->GetIdentity (); m_TransientVerifier = m_RemoteLeaseSet->GetTransientVerifier (); } @@ -1029,6 +1173,7 @@ namespace stream it.second->Terminate (false); // we delete here m_Streams.clear (); m_IncomingStreams.clear (); + m_LastStream = nullptr; } } @@ -1037,16 +1182,23 @@ namespace stream uint32_t sendStreamID = packet->GetSendStreamID (); if (sendStreamID) { - auto it = m_Streams.find (sendStreamID); - if (it != m_Streams.end ()) - it->second->HandleNextPacket (packet); + 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); @@ -1055,6 +1207,13 @@ namespace stream } else { + if (packet->IsEcho ()) + { + // pong + LogPrint (eLogInfo, "Streaming: Pong received rSID=", packet->GetReceiveStreamID ()); + DeletePacket (packet); + return; + } if (packet->IsSYN () && !packet->GetSeqn ()) // new incoming stream { uint32_t receiveStreamID = packet->GetReceiveStreamID (); @@ -1145,16 +1304,22 @@ namespace stream { auto s = std::make_shared (m_Owner->GetService (), *this, remote, port); std::unique_lock l(m_StreamsMutex); - m_Streams[s->GetRecvStreamID ()] = s; + 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[s->GetRecvStreamID ()] = s; - m_IncomingStreams[receiveStreamID] = s; + m_Streams.emplace (s->GetRecvStreamID (), s); + m_IncomingStreams.emplace (receiveStreamID, s); return s; } @@ -1165,6 +1330,12 @@ namespace stream std::unique_lock l(m_StreamsMutex); m_Streams.erase (stream->GetRecvStreamID ()); m_IncomingStreams.erase (stream->GetSendStreamID ()); + if (m_LastStream == stream) m_LastStream = nullptr; + } + if (m_Streams.empty ()) + { + m_PacketsPool.CleanUp (); + m_I2NPMsgsPool.CleanUp (); } } @@ -1173,7 +1344,13 @@ namespace stream auto it = m_Streams.find (recvStreamID); if (it == m_Streams.end ()) return false; - DeleteStream (it->second); + auto s = it->second; + m_Owner->GetService ().post ([this, s] () + { + s->Close (); // try to send FIN + s->Terminate (false); + DeleteStream (s); + }); return true; } @@ -1223,6 +1400,26 @@ namespace stream acceptor (stream); } + std::shared_ptr StreamingDestination::AcceptStream (int timeout) + { + std::shared_ptr stream; + std::condition_variable streamAccept; + std::mutex streamAcceptMutex; + std::unique_lock l(streamAcceptMutex); + AcceptOnce ( + [&streamAccept, &streamAcceptMutex, &stream](std::shared_ptr s) + { + stream = s; + std::unique_lock l(streamAcceptMutex); + streamAccept.notify_all (); + }); + if (timeout) + streamAccept.wait_for (l, std::chrono::seconds (timeout)); + else + streamAccept.wait (l); + return stream; + } + void StreamingDestination::HandlePendingIncomingTimer (const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) @@ -1247,15 +1444,19 @@ namespace stream } std::shared_ptr StreamingDestination::CreateDataMessage ( - const uint8_t * payload, size_t len, uint16_t toPort, bool checksum) + const uint8_t * payload, size_t len, uint16_t toPort, bool checksum, bool gzip) { - auto msg = m_I2NPMsgsPool.AcquireShared (); + 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; - size_t size = (!m_Gzip || len <= i2p::stream::COMPRESSION_THRESHOLD_SIZE)? - i2p::data::GzipNoCompression (payload, len, buf, msg->maxLen - msg->len): - m_Deflator.Deflate (payload, len, buf, msg->maxLen - msg->len); + + 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 diff --git a/libi2pd/Streaming.h b/libi2pd/Streaming.h index e8b8db91..3609df92 100644 --- a/libi2pd/Streaming.h +++ b/libi2pd/Streaming.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,7 +11,7 @@ #include #include -#include +#include #include #include #include @@ -58,10 +58,11 @@ namespace stream const int MAX_WINDOW_SIZE = 128; const int INITIAL_RTT = 8000; // in milliseconds const int INITIAL_RTO = 9000; // in milliseconds + const int MIN_SEND_ACK_TIMEOUT = 2; // in milliseconds const int SYN_TIMEOUT = 200; // how long we wait for SYN after follow-on, in milliseconds const size_t MAX_PENDING_INCOMING_BACKLOG = 128; const int PENDING_INCOMING_TIMEOUT = 10; // in seconds - const int MAX_RECEIVE_TIMEOUT = 30; // in seconds + const int MAX_RECEIVE_TIMEOUT = 20; // in seconds struct Packet { @@ -79,6 +80,7 @@ namespace stream uint32_t GetAckThrough () const { return bufbe32toh (buf + 12); }; uint8_t GetNACKCount () const { return buf[16]; }; uint32_t GetNACK (int i) const { return bufbe32toh (buf + 17 + 4 * i); }; + const uint8_t * GetNACKs () const { return buf + 17; }; const uint8_t * GetOption () const { return buf + 17 + GetNACKCount ()*4 + 3; }; // 3 = resendDelay + flags uint16_t GetFlags () const { return bufbe16toh (GetOption () - 2); }; uint16_t GetOptionSize () const { return bufbe16toh (GetOption ()); }; @@ -111,6 +113,11 @@ namespace stream buf = new uint8_t[len]; memcpy (buf, b, len); } + SendBuffer (size_t l): // create empty buffer + len(l), offset (0) + { + buf = new uint8_t[len]; + } ~SendBuffer () { delete[] buf; @@ -129,6 +136,7 @@ namespace stream ~SendBufferQueue () { CleanUp (); }; void Add (const uint8_t * buf, size_t len, SendHandler handler); + void Add (std::shared_ptr buf); size_t Get (uint8_t * buf, size_t len); size_t GetSize () const { return m_Size; }; bool IsEmpty () const { return m_Buffers.empty (); }; @@ -146,7 +154,8 @@ namespace stream eStreamStatusOpen, eStreamStatusReset, eStreamStatusClosing, - eStreamStatusClosed + eStreamStatusClosed, + eStreamStatusTerminated }; class StreamingDestination; @@ -172,10 +181,12 @@ namespace stream 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() { m_Service.post(std::bind(&Stream::Close, shared_from_this())); }; @@ -254,13 +265,14 @@ namespace stream typedef std::function)> Acceptor; - StreamingDestination (std::shared_ptr owner, uint16_t localPort = 0, bool gzip = true); + 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); @@ -268,13 +280,14 @@ namespace stream 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); + 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); } @@ -291,15 +304,16 @@ namespace stream uint16_t m_LocalPort; bool m_Gzip; // gzip compression of data messages std::mutex m_StreamsMutex; - std::map > m_Streams; // sendStreamID->stream - std::map > m_IncomingStreams; // receiveStreamID->stream + 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::map > m_SavedPackets; // receiveStreamID->packets, arrived before SYN + std::unordered_map > m_SavedPackets; // receiveStreamID->packets, arrived before SYN i2p::util::MemoryPool m_PacketsPool; - i2p::util::MemoryPool > m_I2NPMsgsPool; + i2p::util::MemoryPool > m_I2NPMsgsPool; public: @@ -325,11 +339,10 @@ namespace stream int t = (timeout > MAX_RECEIVE_TIMEOUT) ? MAX_RECEIVE_TIMEOUT : timeout; s->m_ReceiveTimer.expires_from_now (boost::posix_time::seconds(t)); int left = timeout - t; - auto self = s->shared_from_this(); - self->m_ReceiveTimer.async_wait ( - [self, buffer, handler, left](const boost::system::error_code & ec) + s->m_ReceiveTimer.async_wait ( + [s, buffer, handler, left](const boost::system::error_code & ec) { - self->HandleReceiveTimer(ec, buffer, handler, left); + s->HandleReceiveTimer(ec, buffer, handler, left); }); } }); @@ -356,7 +369,7 @@ namespace stream handler (boost::asio::error::make_error_code (boost::asio::error::timed_out), received); else { - // itermediate iterrupt + // itermediate interrupt SendUpdatedLeaseSet (); // send our leaseset if applicable AsyncReceive (buffer, handler, remainingTimeout); } diff --git a/libi2pd/Tag.h b/libi2pd/Tag.h index 3856abd9..72f181a2 100644 --- a/libi2pd/Tag.h +++ b/libi2pd/Tag.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -9,14 +9,6 @@ #ifndef TAG_H__ #define TAG_H__ -/* -* Copyright (c) 2013-2017, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - #include #include #include @@ -64,17 +56,17 @@ namespace data { RAND_bytes(m_Buf, sz); } - std::string ToBase64 () const + std::string ToBase64 (size_t len = sz) const { char str[sz*2]; - size_t l = i2p::data::ByteStreamToBase64 (m_Buf, sz, str, sz*2); + size_t l = i2p::data::ByteStreamToBase64 (m_Buf, len, str, sz*2); return std::string (str, str + l); } - std::string ToBase32 () const + std::string ToBase32 (size_t len = sz) const { char str[sz*2]; - size_t l = i2p::data::ByteStreamToBase32 (m_Buf, sz, str, sz*2); + size_t l = i2p::data::ByteStreamToBase32 (m_Buf, len, str, sz*2); return std::string (str, str + l); } @@ -88,6 +80,13 @@ namespace data { return i2p::data::Base64ToByteStream (s.c_str (), s.length (), m_Buf, sz); } + uint8_t GetBit (int i) const + { + int pos = i >> 3; // /8 + if (pos >= (int)sz) return 0; + return m_Buf[pos] & (0x80 >> (i & 0x07)); + } + private: union // 8 bytes aligned diff --git a/libi2pd/Timestamp.cpp b/libi2pd/Timestamp.cpp index 4362a878..99507398 100644 --- a/libi2pd/Timestamp.cpp +++ b/libi2pd/Timestamp.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -16,10 +16,12 @@ #include #include "Config.h" #include "Log.h" +#include "RouterContext.h" #include "I2PEndian.h" #include "Timestamp.h" +#include "util.h" -#ifdef WIN32 +#ifdef _WIN32 #ifndef _WIN64 #define _USE_32BIT_TIME_T #endif @@ -35,32 +37,67 @@ namespace util 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 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_service service; - boost::asio::ip::udp::resolver::query query (boost::asio::ip::udp::v4 (), address, "ntp"); boost::system::error_code ec; - auto it = boost::asio::ip::udp::resolver (service).resolve (query, ec); - if (!ec && it != boost::asio::ip::udp::resolver::iterator()) + auto it = boost::asio::ip::udp::resolver (service).resolve ( + boost::asio::ip::udp::resolver::query (address, "ntp"), ec); + if (!ec) { - auto ep = (*it).endpoint (); // take first one + bool found = false; + boost::asio::ip::udp::resolver::iterator end; + boost::asio::ip::udp::endpoint ep; + while (it != end) + { + 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; + it++; + } + if (!found) + { + LogPrint (eLogError, "Timestamp: can't find compatible address for ", address); + return; + } + boost::asio::ip::udp::socket socket (service); - socket.open (boost::asio::ip::udp::v4 (), ec); + socket.open (ep.protocol (), ec); if (!ec) { uint8_t buf[48];// 48 bytes NTP request/response @@ -96,7 +133,7 @@ namespace util LogPrint (eLogError, "Timestamp: Couldn't open UDP socket"); } else - LogPrint (eLogError, "Timestamp: Couldn't resove address ", address); + LogPrint (eLogError, "Timestamp: Couldn't resolve address ", address); } NTPTimeSync::NTPTimeSync (): m_IsRunning (false), m_Timer (m_Service) @@ -142,6 +179,8 @@ namespace util void NTPTimeSync::Run () { + i2p::util::SetThreadName("Timesync"); + while (m_IsRunning) { try @@ -178,16 +217,21 @@ namespace util return GetLocalMillisecondsSinceEpoch () + g_TimeOffset*1000; } - uint32_t GetHoursSinceEpoch () - { - return GetLocalHoursSinceEpoch () + g_TimeOffset/3600; - } - uint64_t GetSecondsSinceEpoch () { return GetLocalSecondsSinceEpoch () + g_TimeOffset; } + uint32_t GetMinutesSinceEpoch () + { + return GetLocalMinutesSinceEpoch () + g_TimeOffset/60; + } + + uint32_t GetHoursSinceEpoch () + { + return GetLocalHoursSinceEpoch () + g_TimeOffset/3600; + } + void GetCurrentDate (char * date) { GetDateString (GetSecondsSinceEpoch (), date); @@ -206,5 +250,10 @@ namespace util sprintf(date, "%04i%02i%02i", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday); #endif } + + void AdjustTimeOffset (int64_t offset) + { + g_TimeOffset += offset; + } } } diff --git a/libi2pd/Timestamp.h b/libi2pd/Timestamp.h index 91175a49..ff777257 100644 --- a/libi2pd/Timestamp.h +++ b/libi2pd/Timestamp.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -20,11 +20,13 @@ namespace i2p namespace util { uint64_t GetMillisecondsSinceEpoch (); - uint32_t GetHoursSinceEpoch (); uint64_t GetSecondsSinceEpoch (); + uint32_t GetMinutesSinceEpoch (); + uint32_t GetHoursSinceEpoch (); void GetCurrentDate (char * date); // returns date as YYYYMMDD string, 9 bytes - void GetDateString (uint64_t timestamp, char * date); // timestap is seconds since epoch, returns date as YYYYMMDD string, 9 bytes + void 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 { diff --git a/libi2pd/TransitTunnel.cpp b/libi2pd/TransitTunnel.cpp index 73ca977c..6c2c52a7 100644 --- a/libi2pd/TransitTunnel.cpp +++ b/libi2pd/TransitTunnel.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -20,16 +20,21 @@ namespace i2p namespace tunnel { TransitTunnel::TransitTunnel (uint32_t receiveTunnelID, - const uint8_t * nextIdent, uint32_t nextTunnelID, - const uint8_t * layerKey,const uint8_t * ivKey): - TunnelBase (receiveTunnelID, nextTunnelID, nextIdent) + 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) { - m_Encryption.SetKeys (layerKey, ivKey); } void TransitTunnel::EncryptTunnelMsg (std::shared_ptr in, std::shared_ptr out) { - m_Encryption.Encrypt (in->GetPayload () + 4, out->GetPayload () + 4); + 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); } @@ -37,15 +42,14 @@ namespace tunnel { } - void TransitTunnelParticipant::HandleTunnelDataMsg (std::shared_ptr tunnelMsg) + void TransitTunnelParticipant::HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg) { - auto newMsg = CreateEmptyTunnelDataMsg (); - EncryptTunnelMsg (tunnelMsg, newMsg); + EncryptTunnelMsg (tunnelMsg, tunnelMsg); m_NumTransmittedBytes += tunnelMsg->GetLength (); - htobe32buf (newMsg->GetPayload (), GetNextTunnelID ()); - newMsg->FillI2NPMessageHeader (eI2NPTunnelData); - m_TunnelDataMsgs.push_back (newMsg); + htobe32buf (tunnelMsg->GetPayload (), GetNextTunnelID ()); + tunnelMsg->FillI2NPMessageHeader (eI2NPTunnelData); + m_TunnelDataMsgs.push_back (tunnelMsg); } void TransitTunnelParticipant::FlushTunnelDataMsgs () @@ -65,7 +69,7 @@ namespace tunnel LogPrint (eLogError, "TransitTunnel: We are not a gateway for ", GetTunnelID ()); } - void TransitTunnel::HandleTunnelDataMsg (std::shared_ptr tunnelMsg) + void TransitTunnel::HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg) { LogPrint (eLogError, "TransitTunnel: Incoming tunnel message is not supported ", GetTunnelID ()); } @@ -85,9 +89,9 @@ namespace tunnel m_Gateway.SendBuffer (); } - void TransitTunnelEndpoint::HandleTunnelDataMsg (std::shared_ptr tunnelMsg) + void TransitTunnelEndpoint::HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg) { - auto newMsg = CreateEmptyTunnelDataMsg (); + auto newMsg = CreateEmptyTunnelDataMsg (true); EncryptTunnelMsg (tunnelMsg, newMsg); LogPrint (eLogDebug, "TransitTunnel: handle msg for endpoint ", GetTunnelID ()); @@ -95,8 +99,8 @@ namespace tunnel } std::shared_ptr CreateTransitTunnel (uint32_t receiveTunnelID, - const uint8_t * nextIdent, uint32_t nextTunnelID, - const uint8_t * layerKey,const uint8_t * ivKey, + const i2p::data::IdentHash& nextIdent, uint32_t nextTunnelID, + const i2p::crypto::AESKey& layerKey, const i2p::crypto::AESKey& ivKey, bool isGateway, bool isEndpoint) { if (isEndpoint) diff --git a/libi2pd/TransitTunnel.h b/libi2pd/TransitTunnel.h index e71ec750..f83007a9 100644 --- a/libi2pd/TransitTunnel.h +++ b/libi2pd/TransitTunnel.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -28,18 +28,19 @@ namespace tunnel public: TransitTunnel (uint32_t receiveTunnelID, - const uint8_t * nextIdent, uint32_t nextTunnelID, - const uint8_t * layerKey,const uint8_t * ivKey); + 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; }; // implements TunnelBase - void SendTunnelDataMsg (std::shared_ptr msg); - void HandleTunnelDataMsg (std::shared_ptr tunnelMsg); - void EncryptTunnelMsg (std::shared_ptr in, std::shared_ptr out); + void SendTunnelDataMsg (std::shared_ptr msg) override; + void HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg) override; + void EncryptTunnelMsg (std::shared_ptr in, std::shared_ptr out) override; private: - i2p::crypto::TunnelEncryption m_Encryption; + i2p::crypto::AESKey m_LayerKey, m_IVKey; + std::unique_ptr m_Encryption; }; class TransitTunnelParticipant: public TransitTunnel @@ -47,15 +48,15 @@ namespace tunnel public: TransitTunnelParticipant (uint32_t receiveTunnelID, - const uint8_t * nextIdent, uint32_t nextTunnelID, - const uint8_t * layerKey,const uint8_t * ivKey): + 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 { return m_NumTransmittedBytes; }; - void HandleTunnelDataMsg (std::shared_ptr tunnelMsg); - void FlushTunnelDataMsgs (); + size_t GetNumTransmittedBytes () const override { return m_NumTransmittedBytes; }; + void HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg) override; + void FlushTunnelDataMsgs () override; private: @@ -68,14 +69,14 @@ namespace tunnel public: TransitTunnelGateway (uint32_t receiveTunnelID, - const uint8_t * nextIdent, uint32_t nextTunnelID, - const uint8_t * layerKey,const uint8_t * ivKey): + const i2p::data::IdentHash& nextIdent, uint32_t nextTunnelID, + const i2p::crypto::AESKey& layerKey, const i2p::crypto::AESKey& ivKey): TransitTunnel (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey), m_Gateway(this) {}; - void SendTunnelDataMsg (std::shared_ptr msg); - void FlushTunnelDataMsgs (); - size_t GetNumTransmittedBytes () const { return m_Gateway.GetNumSentBytes (); }; + void SendTunnelDataMsg (std::shared_ptr msg) override; + void FlushTunnelDataMsgs () override; + size_t GetNumTransmittedBytes () const override { return m_Gateway.GetNumSentBytes (); }; private: @@ -88,15 +89,15 @@ namespace tunnel public: TransitTunnelEndpoint (uint32_t receiveTunnelID, - const uint8_t * nextIdent, uint32_t nextTunnelID, - const uint8_t * layerKey,const uint8_t * ivKey): + const i2p::data::IdentHash& nextIdent, uint32_t nextTunnelID, + const i2p::crypto::AESKey& layerKey, const i2p::crypto::AESKey& ivKey): TransitTunnel (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey), m_Endpoint (false) {}; // transit endpoint is always outbound - void Cleanup () { m_Endpoint.Cleanup (); } + void Cleanup () override { m_Endpoint.Cleanup (); } - void HandleTunnelDataMsg (std::shared_ptr tunnelMsg); - size_t GetNumTransmittedBytes () const { return m_Endpoint.GetNumReceivedBytes (); } + void HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg) override; + size_t GetNumTransmittedBytes () const override { return m_Endpoint.GetNumReceivedBytes (); } private: @@ -104,8 +105,8 @@ namespace tunnel }; std::shared_ptr CreateTransitTunnel (uint32_t receiveTunnelID, - const uint8_t * nextIdent, uint32_t nextTunnelID, - const uint8_t * layerKey,const uint8_t * ivKey, + const i2p::data::IdentHash& nextIdent, uint32_t nextTunnelID, + const i2p::crypto::AESKey& layerKey, const i2p::crypto::AESKey& ivKey, bool isGateway, bool isEndpoint); } } diff --git a/libi2pd/TransportSession.h b/libi2pd/TransportSession.h index a97f246f..7d2f2653 100644 --- a/libi2pd/TransportSession.h +++ b/libi2pd/TransportSession.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -24,6 +24,10 @@ 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: @@ -33,6 +37,12 @@ namespace transport { 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); @@ -59,16 +69,21 @@ namespace transport 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 class TransportSession { public: TransportSession (std::shared_ptr router, int terminationTimeout): - m_DHKeysPair (nullptr), m_NumSentBytes (0), m_NumReceivedBytes (0), m_IsOutgoing (router), m_TerminationTimeout (terminationTimeout), - m_LastActivityTimestamp (i2p::util::GetSecondsSinceEpoch ()) + m_NumSentBytes (0), m_NumReceivedBytes (0), m_SendQueueSize (0), + m_IsOutgoing (router), m_TerminationTimeout (terminationTimeout), + m_LastActivityTimestamp (i2p::util::GetSecondsSinceEpoch ()), + m_HandshakeInterval (0) { if (router) m_RemoteIdentity = router->GetRouterIdentity (); + m_CreationTime = m_LastActivityTimestamp; } virtual ~TransportSession () {}; @@ -89,26 +104,47 @@ namespace transport size_t GetNumSentBytes () const { return m_NumSentBytes; }; size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; }; + size_t GetSendQueueSize () const { return m_SendQueueSize; }; bool IsOutgoing () const { return m_IsOutgoing; }; - + bool IsSlow () const { return m_HandshakeInterval > TRANSPORT_SESSION_SLOWNESS_THRESHOLD && + m_HandshakeInterval < TRANSPORT_SESSION_MAX_HANDSHAKE_INTERVAL; }; + int GetTerminationTimeout () const { return m_TerminationTimeout; }; void SetTerminationTimeout (int terminationTimeout) { m_TerminationTimeout = terminationTimeout; }; bool IsTerminationTimeoutExpired (uint64_t ts) const - { return ts >= m_LastActivityTimestamp + GetTerminationTimeout (); }; + { + return ts >= m_LastActivityTimestamp + GetTerminationTimeout () || + ts + GetTerminationTimeout () < m_LastActivityTimestamp; + }; - virtual void SendLocalRouterInfo () { SendI2NPMessages ({ CreateDatabaseStoreMsg () }); }; + uint32_t GetCreationTime () const { return m_CreationTime; }; + void SetCreationTime (uint32_t ts) { m_CreationTime = ts; }; // for introducers + + virtual uint32_t GetRelayTag () const { return 0; }; + virtual void SendLocalRouterInfo (bool update = false) { SendI2NPMessages ({ CreateDatabaseStoreMsg () }); }; virtual void SendI2NPMessages (const std::vector >& msgs) = 0; + virtual bool IsEstablished () const = 0; protected: std::shared_ptr m_RemoteIdentity; mutable std::mutex m_RemoteIdentityMutex; - std::shared_ptr m_DHKeysPair; // X - for client and Y - for server - size_t m_NumSentBytes, m_NumReceivedBytes; + size_t m_NumSentBytes, m_NumReceivedBytes, m_SendQueueSize; bool m_IsOutgoing; int m_TerminationTimeout; uint64_t m_LastActivityTimestamp; + uint32_t m_CreationTime; // seconds since epoch + int64_t m_HandshakeInterval; // in milliseconds between SessionRequest->SessionCreated or SessionCreated->SessionConfirmed }; + + // SOCKS5 proxy + const uint8_t SOCKS5_VER = 0x05; + const uint8_t SOCKS5_CMD_CONNECT = 0x01; + const uint8_t SOCKS5_CMD_UDP_ASSOCIATE = 0x03; + const uint8_t SOCKS5_ATYP_IPV4 = 0x01; + const uint8_t SOCKS5_ATYP_IPV6 = 0x04; + const size_t SOCKS5_UDP_IPV4_REQUEST_HEADER_SIZE = 10; + const size_t SOCKS5_UDP_IPV6_REQUEST_HEADER_SIZE = 22; } } diff --git a/libi2pd/Transports.cpp b/libi2pd/Transports.cpp index 19e17f52..b452c05e 100644 --- a/libi2pd/Transports.cpp +++ b/libi2pd/Transports.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -14,6 +14,7 @@ #include "Transports.h" #include "Config.h" #include "HTTP.h" +#include "util.h" using namespace i2p::data; @@ -27,20 +28,20 @@ namespace transport { } - template + template EphemeralKeysSupplier::~EphemeralKeysSupplier () { Stop (); } - template + template void EphemeralKeysSupplier::Start () { m_IsRunning = true; m_Thread = new std::thread (std::bind (&EphemeralKeysSupplier::Run, this)); } - template + template void EphemeralKeysSupplier::Stop () { { @@ -56,9 +57,11 @@ namespace transport } } - template + template void EphemeralKeysSupplier::Run () { + i2p::util::SetThreadName("Ephemerals"); + while (m_IsRunning) { int num, total = 0; @@ -81,7 +84,7 @@ namespace transport } } - template + template void EphemeralKeysSupplier::CreateEphemeralKeys (int num) { if (num > 0) @@ -96,7 +99,7 @@ namespace transport } } - template + template std::shared_ptr EphemeralKeysSupplier::Acquire () { { @@ -115,7 +118,7 @@ namespace transport return pair; } - template + template void EphemeralKeysSupplier::Return (std::shared_ptr pair) { if (pair) @@ -125,20 +128,22 @@ namespace transport m_Queue.push (pair); } else - LogPrint(eLogError, "Transports: return null DHKeys"); + LogPrint(eLogError, "Transports: Return null DHKeys"); } Transports transports; Transports::Transports (): - m_IsOnline (true), m_IsRunning (false), m_IsNAT (true), m_Thread (nullptr), m_Service (nullptr), - m_Work (nullptr), m_PeerCleanupTimer (nullptr), m_PeerTestTimer (nullptr), - m_SSUServer (nullptr), m_NTCP2Server (nullptr), - m_DHKeysPairSupplier (5), m_X25519KeysPairSupplier (5), // 5 pre-generated keys - m_TotalSentBytes(0), m_TotalReceivedBytes(0), m_TotalTransitTransmittedBytes (0), - m_InBandwidth (0), m_OutBandwidth (0), m_TransitBandwidth(0), - m_LastInBandwidthUpdateBytes (0), m_LastOutBandwidthUpdateBytes (0), - m_LastTransitBandwidthUpdateBytes (0), m_LastBandwidthUpdateTime (0) + m_IsOnline (true), m_IsRunning (false), m_IsNAT (true), m_CheckReserved(true), m_Thread (nullptr), + m_Service (nullptr), m_Work (nullptr), m_PeerCleanupTimer (nullptr), m_PeerTestTimer (nullptr), + m_UpdateBandwidthTimer (nullptr), m_SSU2Server (nullptr), m_NTCP2Server (nullptr), + m_X25519KeysPairSupplier (15), // 15 pre-generated keys + m_TotalSentBytes (0), m_TotalReceivedBytes (0), m_TotalTransitTransmittedBytes (0), + m_InBandwidth (0), m_OutBandwidth (0), m_TransitBandwidth (0), + m_LastInBandwidthUpdateBytes (0), m_LastOutBandwidthUpdateBytes (0), m_LastTransitBandwidthUpdateBytes (0), + m_InBandwidth15s (0), m_OutBandwidth15s (0), m_TransitBandwidth15s (0), + m_LastInBandwidth15sUpdateBytes (0), m_LastOutBandwidth15sUpdateBytes (0), m_LastTransitBandwidth15sUpdateBytes (0), + m_LastBandwidth15sUpdateTime (0) { } @@ -149,12 +154,13 @@ namespace transport { delete m_PeerCleanupTimer; m_PeerCleanupTimer = nullptr; delete m_PeerTestTimer; m_PeerTestTimer = nullptr; + delete m_UpdateBandwidthTimer; m_UpdateBandwidthTimer = nullptr; delete m_Work; m_Work = nullptr; delete m_Service; m_Service = nullptr; } } - void Transports::Start (bool enableNTCP2, bool enableSSU) + void Transports::Start (bool enableNTCP2, bool enableSSU2) { if (!m_Service) { @@ -162,19 +168,21 @@ namespace transport m_Work = new boost::asio::io_service::work (*m_Service); m_PeerCleanupTimer = new boost::asio::deadline_timer (*m_Service); m_PeerTestTimer = new boost::asio::deadline_timer (*m_Service); + m_UpdateBandwidthTimer = new boost::asio::deadline_timer (*m_Service); } + bool ipv4; i2p::config::GetOption("ipv4", ipv4); + bool ipv6; i2p::config::GetOption("ipv6", ipv6); i2p::config::GetOption("nat", m_IsNAT); - m_DHKeysPairSupplier.Start (); 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; + i2p::http::URL proxyurl; // create NTCP2. TODO: move to acceptor - if (enableNTCP2) + if (enableNTCP2 || i2p::context.SupportsMesh ()) { - if(!ntcp2proxy.empty()) + if(!ntcp2proxy.empty() && enableNTCP2) { if(proxyurl.parse(ntcp2proxy)) { @@ -186,54 +194,121 @@ namespace transport if (proxyurl.schema == "http") proxytype = NTCP2Server::eHTTPProxy; - m_NTCP2Server->UseProxy(proxytype, proxyurl.host, proxyurl.port); - m_NTCP2Server->Start(); + m_NTCP2Server->UseProxy(proxytype, proxyurl.host, proxyurl.port, proxyurl.user, proxyurl.pass); + i2p::context.SetStatus (eRouterStatusProxy); + if (ipv6) + i2p::context.SetStatusV6 (eRouterStatusProxy); } else - LogPrint(eLogError, "Transports: unsupported NTCP2 proxy URL ", ntcp2proxy); + LogPrint(eLogCritical, "Transports: Unsupported NTCP2 proxy URL ", ntcp2proxy); } else - LogPrint(eLogError, "Transports: invalid NTCP2 proxy url ", ntcp2proxy); - return; + LogPrint(eLogCritical, "Transports: Invalid NTCP2 proxy URL ", ntcp2proxy); } else - { m_NTCP2Server = new NTCP2Server (); - m_NTCP2Server->Start (); + } + + // 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); } } - // create acceptors - auto& addresses = context.GetRouterInfo ().GetAddresses (); - for (const auto& address : addresses) + // bind to interfaces + if (ipv4) { - if (!address) continue; - if (address->transportStyle == RouterInfo::eTransportSSU) + std::string address; i2p::config::GetOption("address4", address); + if (!address.empty ()) { - if (m_SSUServer == nullptr && enableSSU) + boost::system::error_code ec; + auto addr = boost::asio::ip::address::from_string (address, ec); + if (!ec) { - if (address->host.is_v4()) - m_SSUServer = new SSUServer (address->port); - else - m_SSUServer = new SSUServer (address->host, address->port); - LogPrint (eLogInfo, "Transports: Start listening UDP port ", address->port); - try { - m_SSUServer->Start (); - } catch ( std::exception & ex ) { - LogPrint(eLogError, "Transports: Failed to bind to UDP port", address->port); - delete m_SSUServer; - m_SSUServer = nullptr; - continue; - } - DetectExternalIP (); + 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); } - else - LogPrint (eLogError, "Transports: SSU server already exists"); } } - m_PeerCleanupTimer->expires_from_now (boost::posix_time::seconds(5*SESSION_CREATION_TIMEOUT)); + + if (ipv6) + { + std::string address; i2p::config::GetOption("address6", address); + if (!address.empty ()) + { + boost::system::error_code ec; + auto addr = boost::asio::ip::address::from_string (address, ec); + if (!ec) + { + if (m_NTCP2Server) m_NTCP2Server->SetLocalAddress (addr); + if (m_SSU2Server) m_SSU2Server->SetLocalAddress (addr); + } + } + + if (enableSSU2) + { + uint16_t mtu; i2p::config::GetOption ("ssu2.mtu6", mtu); + if (mtu) + { + if (mtu < (int)SSU2_MIN_PACKET_SIZE) mtu = SSU2_MIN_PACKET_SIZE; + if (mtu > (int)SSU2_MAX_PACKET_SIZE) mtu = SSU2_MAX_PACKET_SIZE; + i2p::context.SetMTU (mtu, false); + } + } + } + + 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::address::from_string (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)); + m_UpdateBandwidthTimer->expires_from_now (boost::posix_time::seconds(1)); + m_UpdateBandwidthTimer->async_wait (std::bind (&Transports::HandleUpdateBandwidthTimer, this, std::placeholders::_1)); + if (m_IsNAT) { m_PeerTestTimer->expires_from_now (boost::posix_time::minutes(PEER_TEST_INTERVAL)); @@ -246,13 +321,14 @@ namespace transport if (m_PeerCleanupTimer) m_PeerCleanupTimer->cancel (); if (m_PeerTestTimer) m_PeerTestTimer->cancel (); m_Peers.clear (); - if (m_SSUServer) + + if (m_SSU2Server) { - m_SSUServer->Stop (); - delete m_SSUServer; - m_SSUServer = nullptr; + m_SSU2Server->Stop (); + delete m_SSU2Server; + m_SSU2Server = nullptr; } - + if (m_NTCP2Server) { m_NTCP2Server->Stop (); @@ -260,7 +336,6 @@ namespace transport m_NTCP2Server = nullptr; } - m_DHKeysPairSupplier.Stop (); m_X25519KeysPairSupplier.Stop (); m_IsRunning = false; if (m_Service) m_Service->stop (); @@ -274,6 +349,8 @@ namespace transport void Transports::Run () { + i2p::util::SetThreadName("Transports"); + while (m_IsRunning && m_Service) { try @@ -282,34 +359,49 @@ namespace transport } catch (std::exception& ex) { - LogPrint (eLogError, "Transports: runtime exception: ", ex.what ()); + LogPrint (eLogError, "Transports: Runtime exception: ", ex.what ()); } } } - void Transports::UpdateBandwidth () + void Transports::HandleUpdateBandwidthTimer (const boost::system::error_code& ecode) { - uint64_t ts = i2p::util::GetMillisecondsSinceEpoch (); - if (m_LastBandwidthUpdateTime > 0) + if (ecode != boost::asio::error::operation_aborted) { - auto delta = ts - m_LastBandwidthUpdateTime; - if (delta > 0) + uint64_t ts = i2p::util::GetMillisecondsSinceEpoch (); + + // updated every second + m_InBandwidth = m_TotalReceivedBytes - m_LastInBandwidthUpdateBytes; + m_OutBandwidth = m_TotalSentBytes - m_LastOutBandwidthUpdateBytes; + m_TransitBandwidth = m_TotalTransitTransmittedBytes - m_LastTransitBandwidthUpdateBytes; + + m_LastInBandwidthUpdateBytes = m_TotalReceivedBytes; + m_LastOutBandwidthUpdateBytes = m_TotalSentBytes; + m_LastTransitBandwidthUpdateBytes = m_TotalTransitTransmittedBytes; + + // updated every 15 seconds + auto delta = ts - m_LastBandwidth15sUpdateTime; + if (delta > 15 * 1000) { - m_InBandwidth = (m_TotalReceivedBytes - m_LastInBandwidthUpdateBytes)*1000/delta; // per second - m_OutBandwidth = (m_TotalSentBytes - m_LastOutBandwidthUpdateBytes)*1000/delta; // per second - m_TransitBandwidth = (m_TotalTransitTransmittedBytes - m_LastTransitBandwidthUpdateBytes)*1000/delta; + m_InBandwidth15s = (m_TotalReceivedBytes - m_LastInBandwidth15sUpdateBytes) * 1000 / delta; + m_OutBandwidth15s = (m_TotalSentBytes - m_LastOutBandwidth15sUpdateBytes) * 1000 / delta; + m_TransitBandwidth15s = (m_TotalTransitTransmittedBytes - m_LastTransitBandwidth15sUpdateBytes) * 1000 / delta; + + m_LastBandwidth15sUpdateTime = ts; + m_LastInBandwidth15sUpdateBytes = m_TotalReceivedBytes; + m_LastOutBandwidth15sUpdateBytes = m_TotalSentBytes; + m_LastTransitBandwidth15sUpdateBytes = m_TotalTransitTransmittedBytes; } + + m_UpdateBandwidthTimer->expires_from_now (boost::posix_time::seconds(1)); + m_UpdateBandwidthTimer->async_wait (std::bind (&Transports::HandleUpdateBandwidthTimer, this, std::placeholders::_1)); } - m_LastBandwidthUpdateTime = ts; - m_LastInBandwidthUpdateBytes = m_TotalReceivedBytes; - m_LastOutBandwidthUpdateBytes = m_TotalSentBytes; - m_LastTransitBandwidthUpdateBytes = m_TotalTransitTransmittedBytes; } bool Transports::IsBandwidthExceeded () const { auto limit = i2p::context.GetBandwidthLimit() * 1024; // convert to bytes - auto bw = std::max (m_InBandwidth, m_OutBandwidth); + auto bw = std::max (m_InBandwidth15s, m_OutBandwidth15s); return bw > limit; } @@ -321,7 +413,8 @@ namespace transport void Transports::SendMessage (const i2p::data::IdentHash& ident, std::shared_ptr msg) { - SendMessages (ident, std::vector > {msg }); + if (m_IsOnline) + SendMessages (ident, std::vector > {msg }); } void Transports::SendMessages (const i2p::data::IdentHash& ident, const std::vector >& msgs) @@ -335,7 +428,7 @@ namespace transport { // we send it to ourself for (auto& it: msgs) - m_LoopbackHandler.PutNextMessage (it); + m_LoopbackHandler.PutNextMessage (std::move (it)); m_LoopbackHandler.Flush (); return; } @@ -347,10 +440,11 @@ namespace transport try { auto r = netdb.FindRouter (ident); + if (r && (r->IsUnreachable () || !r->IsReachableFrom (i2p::context.GetRouterInfo ()))) return; // router found but non-reachable { + auto ts = i2p::util::GetSecondsSinceEpoch (); std::unique_lock l(m_PeersMutex); - it = m_Peers.insert (std::pair(ident, { 0, r, {}, - i2p::util::GetSecondsSinceEpoch (), {} })).first; + it = m_Peers.insert (std::pair(ident, {r, ts})).first; } connected = ConnectToPeer (ident, it->second); } @@ -364,14 +458,26 @@ namespace transport it->second.sessions.front ()->SendI2NPMessages (msgs); else { - if (it->second.delayedMessages.size () < MAX_NUM_DELAYED_MESSAGES) + auto sz = it->second.delayedMessages.size (); + if (sz < MAX_NUM_DELAYED_MESSAGES) { + if (sz < CHECK_PROFILE_NUM_DELAYED_MESSAGES && sz + msgs.size () >= CHECK_PROFILE_NUM_DELAYED_MESSAGES) + { + auto profile = i2p::data::GetRouterProfile (ident); + if (profile && profile->IsUnreachable ()) + { + LogPrint (eLogWarning, "Transports: Peer profile for ", ident.ToBase64 (), " reports unreachable. Dropped"); + std::unique_lock l(m_PeersMutex); + m_Peers.erase (it); + return; + } + } for (auto& it1: msgs) it->second.delayedMessages.push_back (it1); } else { - LogPrint (eLogWarning, "Transports: delayed messages queue size to ", + LogPrint (eLogWarning, "Transports: Delayed messages queue size to ", ident.ToBase64 (), " exceeds ", MAX_NUM_DELAYED_MESSAGES); std::unique_lock l(m_PeersMutex); m_Peers.erase (it); @@ -382,48 +488,71 @@ namespace transport bool Transports::ConnectToPeer (const i2p::data::IdentHash& ident, Peer& peer) { if (!peer.router) // reconnect - peer.router = netdb.FindRouter (ident); // try to get new one from netdb + peer.SetRouter (netdb.FindRouter (ident)); // try to get new one from netdb if (peer.router) // we have RI already { - if (!peer.numAttempts) // NTCP2 + if (peer.priority.empty ()) + SetPriority (peer); + while (peer.numAttempts < (int)peer.priority.size ()) { + auto tr = peer.priority[peer.numAttempts]; peer.numAttempts++; - if (m_NTCP2Server) // we support NTCP2 + switch (tr) { - // NTCP2 have priority over NTCP - auto address = peer.router->GetNTCP2Address (true, !context.SupportsV6 ()); // published only - if (address && !peer.router->IsUnreachable ()) + case i2p::data::RouterInfo::eNTCP2V4: + case i2p::data::RouterInfo::eNTCP2V6: { - auto s = std::make_shared (*m_NTCP2Server, peer.router); - - if(m_NTCP2Server->UsingProxy()) + if (!m_NTCP2Server) continue; + std::shared_ptr address = (tr == i2p::data::RouterInfo::eNTCP2V6) ? + peer.router->GetPublishedNTCP2V6Address () : peer.router->GetPublishedNTCP2V4Address (); + if (address && m_CheckReserved && i2p::util::net::IsInReservedRange(address->host)) + address = nullptr; + if (address) { - NTCP2Server::RemoteAddressType remote = NTCP2Server::eIP4Address; - std::string addr = address->host.to_string(); - - if(address->host.is_v6()) - remote = NTCP2Server::eIP6Address; - - m_NTCP2Server->ConnectWithProxy(addr, address->port, remote, s); + 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; } - else - m_NTCP2Server->Connect (address->host, address->port, s); - return true; + break; } + case i2p::data::RouterInfo::eSSU2V4: + case i2p::data::RouterInfo::eSSU2V6: + { + if (!m_SSU2Server) continue; + std::shared_ptr address = (tr == i2p::data::RouterInfo::eSSU2V6) ? + peer.router->GetSSU2V6Address () : peer.router->GetSSU2V4Address (); + if (address && m_CheckReserved && i2p::util::net::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); } } - if (peer.numAttempts == 1)// SSU - { - peer.numAttempts++; - if (m_SSUServer && peer.router->IsSSU (!context.SupportsV6 ())) - { - auto address = peer.router->GetSSUAddress (!context.SupportsV6 ()); - m_SSUServer->CreateSession (peer.router, address->host, address->port); - return true; - } - } - LogPrint (eLogInfo, "Transports: No NTCP or SSU addresses available"); - i2p::data::netdb.SetUnreachable (ident, true); // we are here because all connection attempts failed + + LogPrint (eLogInfo, "Transports: No compatible addresses available"); + if (peer.router->IsReachableFrom (i2p::context.GetRouterInfo ())) + i2p::data::netdb.SetUnreachable (ident, true); // we are here because all connection attempts failed but router claimed them peer.Done (); std::unique_lock l(m_PeersMutex); m_Peers.erase (ident); @@ -438,6 +567,37 @@ namespace transport return true; } + void Transports::SetPriority (Peer& peer) const + { + 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.router) return; + auto compatibleTransports = context.GetRouterInfo ().GetCompatibleTransports (false) & + peer.router->GetCompatibleTransports (true); + peer.numAttempts = 0; + peer.priority.clear (); + bool ssu2 = peer.router->GetProfile ()->IsReal () ? (rand () & 1) : false; // try NTCP2 if router is not confirmed real + const auto& priority = ssu2 ? ssu2Priority : ntcp2Priority; + for (auto transport: priority) + if (transport & compatibleTransports) + peer.priority.push_back (transport); + } + void Transports::RequestComplete (std::shared_ptr r, const i2p::data::IdentHash& ident) { m_Service->post (std::bind (&Transports::HandleRequestComplete, this, r, ident)); @@ -450,13 +610,13 @@ namespace transport { if (r) { - LogPrint (eLogDebug, "Transports: RouterInfo for ", ident.ToBase64 (), " found, Trying to connect"); - it->second.router = r; + LogPrint (eLogDebug, "Transports: RouterInfo for ", ident.ToBase64 (), " found, trying to connect"); + it->second.SetRouter (r); ConnectToPeer (ident, it->second); } else { - LogPrint (eLogWarning, "Transports: RouterInfo not found, Failed to send messages"); + LogPrint (eLogWarning, "Transports: RouterInfo not found, failed to send messages"); std::unique_lock l(m_PeersMutex); m_Peers.erase (it); } @@ -467,83 +627,57 @@ namespace transport { if (RoutesRestricted()) { - LogPrint(eLogInfo, "Transports: restricted routes enabled, not detecting ip"); + LogPrint(eLogInfo, "Transports: Restricted routes enabled, not detecting IP"); i2p::context.SetStatus (eRouterStatusOK); return; } - if (m_SSUServer) - { - bool isv4 = i2p::context.SupportsV4 (); - if (m_IsNAT && isv4) - i2p::context.SetStatus (eRouterStatusTesting); - for (int i = 0; i < 5; i++) - { - auto router = i2p::data::netdb.GetRandomPeerTestRouter (isv4); // v4 only if v4 - if (router) - m_SSUServer->CreateSession (router, true, isv4); // peer test - else - { - // if not peer test capable routers found pick any - router = i2p::data::netdb.GetRandomRouter (); - if (router && router->IsSSU ()) - m_SSUServer->CreateSession (router); // no peer test - } - } - if (i2p::context.SupportsV6 ()) - { - // try to connect to few v6 addresses to get our address back - for (int i = 0; i < 3; i++) - { - auto router = i2p::data::netdb.GetRandomSSUV6Router (); - if (router) - { - auto addr = router->GetSSUV6Address (); - if (addr) - m_SSUServer->GetServiceV6 ().post ([this, router, addr] - { - m_SSUServer->CreateDirectSession (router, { addr->host, (uint16_t)addr->port }, false); - }); - } - } - } - } + if (m_SSU2Server) + PeerTest (); else - LogPrint (eLogError, "Transports: Can't detect external IP. SSU is not available"); + LogPrint (eLogWarning, "Transports: Can't detect external IP. SSU or SSU2 is not available"); } - void Transports::PeerTest () + void Transports::PeerTest (bool ipv4, bool ipv6) { - if (RoutesRestricted() || !i2p::context.SupportsV4 ()) return; - if (m_SSUServer) + if (RoutesRestricted() || !m_SSU2Server || m_SSU2Server->UsesProxy ()) return; + if (ipv4 && i2p::context.SupportsV4 ()) { - LogPrint (eLogInfo, "Transports: Started peer test"); - bool statusChanged = false; + LogPrint (eLogInfo, "Transports: Started peer test IPv4"); + std::set excluded; + excluded.insert (i2p::context.GetIdentHash ()); // don't pick own router for (int i = 0; i < 5; i++) { - auto router = i2p::data::netdb.GetRandomPeerTestRouter (true); // v4 only + auto router = i2p::data::netdb.GetRandomSSU2PeerTestRouter (true, excluded); // v4 if (router) { - if (!statusChanged) - { - statusChanged = true; - i2p::context.SetStatus (eRouterStatusTesting); // first time only - } - m_SSUServer->CreateSession (router, true, true); // peer test v4 + if (!i2p::context.GetTesting ()) + i2p::context.SetTesting (true); + m_SSU2Server->StartPeerTest (router, true); + excluded.insert (router->GetIdentHash ()); } } - if (!statusChanged) - LogPrint (eLogWarning, "Transports: Can't find routers for peer test"); + 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::set excluded; + excluded.insert (i2p::context.GetIdentHash ()); // don't pick own router + 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); + 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::GetNextDHKeysPair () - { - return m_DHKeysPairSupplier.Acquire (); - } - - void Transports::ReuseDHKeysPair (std::shared_ptr pair) - { - m_DHKeysPairSupplier.Return (pair); } std::shared_ptr Transports::GetNextX25519KeysPair () @@ -555,7 +689,7 @@ namespace transport { m_X25519KeysPairSupplier.Return (pair); } - + void Transports::PeerConnected (std::shared_ptr session) { m_Service->post([session, this]() @@ -566,6 +700,25 @@ namespace transport auto it = m_Peers.find (ident); if (it != m_Peers.end ()) { + if (it->second.numAttempts > 1) + { + // exclude failed transports + i2p::data::RouterInfo::CompatibleTransports transports = 0; + int numExcluded = it->second.numAttempts - 1; + if (numExcluded > (int)it->second.priority.size ()) numExcluded = it->second.priority.size (); + for (int i = 0; i < numExcluded; i++) + transports |= it->second.priority[i]; + i2p::data::netdb.ExcludeReachableTransports (ident, transports); + } + if (it->second.router && it->second.numAttempts) + { + auto transport = it->second.priority[it->second.numAttempts-1]; + if (transport == i2p::data::RouterInfo::eNTCP2V4 || + transport == i2p::data::RouterInfo::eNTCP2V6 || transport == i2p::data::RouterInfo::eNTCP2V6Mesh) + it->second.router->GetProfile ()->Connected (); // outgoing NTCP2 connection if always real + i2p::data::netdb.SetUnreachable (ident, false); // clear unreachable + } + it->second.numAttempts = 0; it->second.router = nullptr; // we don't need RouterInfo after successive connect bool sendDatabaseStore = true; if (it->second.delayedMessages.size () > 0) @@ -584,17 +737,23 @@ namespace transport session->SendI2NPMessages (it->second.delayedMessages); it->second.delayedMessages.clear (); } - else // incoming connection + else // incoming connection or peer test { if(RoutesRestricted() && ! IsRestrictedPeer(ident)) { // not trusted - LogPrint(eLogWarning, "Transports: closing untrusted inbound connection from ", ident.ToBase64()); + LogPrint(eLogWarning, "Transports: Closing untrusted inbound connection from ", ident.ToBase64()); session->Done(); return; } - session->SendI2NPMessages ({ CreateDatabaseStoreMsg () }); // send DatabaseStore - std::unique_lock l(m_PeersMutex); - m_Peers.insert (std::make_pair (ident, Peer{ 0, nullptr, { session }, i2p::util::GetSecondsSinceEpoch (), {} })); + if (!session->IsOutgoing ()) // incoming + session->SendI2NPMessages ({ CreateDatabaseStoreMsg () }); // send DatabaseStore + auto r = i2p::data::netdb.FindRouter (ident); // router should be in netdb after SessionConfirmed + if (r) r->GetProfile ()->Connected (); + auto ts = i2p::util::GetSecondsSinceEpoch (); + std::unique_lock l(m_PeersMutex); + auto it = m_Peers.insert (std::make_pair (ident, Peer{ r, ts })).first; + it->second.sessions.push_back (session); + it->second.router = nullptr; } }); } @@ -643,24 +802,42 @@ namespace transport auto ts = i2p::util::GetSecondsSinceEpoch (); for (auto it = m_Peers.begin (); it != m_Peers.end (); ) { - if (it->second.sessions.empty () && ts > it->second.creationTime + SESSION_CREATION_TIMEOUT) + it->second.sessions.remove_if ( + [](std::shared_ptr session)->bool + { + return !session || !session->IsEstablished (); + }); + if (it->second.sessions.empty () && ts > it->second.creationTime + SESSION_CREATION_TIMEOUT) { LogPrint (eLogWarning, "Transports: Session to peer ", it->first.ToBase64 (), " has not been created in ", SESSION_CREATION_TIMEOUT, " seconds"); - auto profile = i2p::data::GetRouterProfile(it->first); - if (profile) - { - profile->TunnelNonReplied(); - } + /* 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::unique_lock l(m_PeersMutex); it = m_Peers.erase (it); } else + { + if (ts > it->second.nextRouterInfoUpdateTime) + { + auto session = it->second.sessions.front (); + if (session) + session->SendLocalRouterInfo (true); + it->second.nextRouterInfoUpdateTime = ts + PEER_ROUTER_INFO_UPDATE_INTERVAL + + rand () % PEER_ROUTER_INFO_UPDATE_INTERVAL_VARIANCE; + } ++it; + } } - UpdateBandwidth (); // TODO: use separate timer(s) for it - if (i2p::context.GetStatus () == eRouterStatusTesting) // if still testing, repeat peer test - DetectExternalIP (); - m_PeerCleanupTimer->expires_from_now (boost::posix_time::seconds(5*SESSION_CREATION_TIMEOUT)); + bool ipv4Testing = i2p::context.GetTesting (); + bool ipv6Testing = i2p::context.GetTestingV6 (); + // if still testing, repeat peer test + if (ipv4Testing || ipv6Testing) + PeerTest (ipv4Testing, ipv6Testing); + m_PeerCleanupTimer->expires_from_now (boost::posix_time::seconds(3 * SESSION_CREATION_TIMEOUT)); m_PeerCleanupTimer->async_wait (std::bind (&Transports::HandlePeerCleanupTimer, this, std::placeholders::_1)); } } @@ -675,20 +852,117 @@ namespace transport } } - std::shared_ptr Transports::GetRandomPeer () const + template + std::shared_ptr Transports::GetRandomPeer (Filter filter) const { - if (m_Peers.empty ()) return nullptr; - std::unique_lock l(m_PeersMutex); - auto it = m_Peers.begin (); - std::advance (it, rand () % m_Peers.size ()); - return it != m_Peers.end () ? it->second.router : nullptr; + if (m_Peers.empty()) return nullptr; + bool found = false; + i2p::data::IdentHash ident; + { + uint16_t inds[3]; + RAND_bytes ((uint8_t *)inds, sizeof (inds)); + std::unique_lock 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 (filter (it->second)) + { + ident = it->first; + 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 (filter (it->second)) + { + ident = it->first; + found = true; + break; + } + it++; + } + if (!found) + { + // still not found, try to the beginning + it = it2; + while (it != m_Peers.end ()) + { + if (filter (it->second)) + { + ident = it->first; + found = true; + break; + } + it++; + } + } + } + } + } + return found ? i2p::data::netdb.FindRouter (ident) : nullptr; } - void Transports::RestrictRoutesToFamilies(std::set families) + + std::shared_ptr Transports::GetRandomPeer (bool isHighBandwidth) const + { + return GetRandomPeer ( + [isHighBandwidth](const Peer& peer)->bool + { + // connected, not overloaded and not slow + return !peer.router && !peer.sessions.empty () && peer.isReachable && + peer.sessions.front ()->GetSendQueueSize () <= PEER_ROUTER_INFO_OVERLOAD_QUEUE_SIZE && + !peer.sessions.front ()->IsSlow () && + (!isHighBandwidth || peer.isHighBandwidth); + }); + } + + void Transports::RestrictRoutesToFamilies(const std::set& families) { std::lock_guard lock(m_FamilyMutex); m_TrustedFamilies.clear(); - for ( const auto& fam : families ) - m_TrustedFamilies.push_back(fam); + 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(std::set routers) @@ -710,20 +984,19 @@ namespace transport { { std::lock_guard l(m_FamilyMutex); - std::string fam; + i2p::data::FamilyID fam = 0; auto sz = m_TrustedFamilies.size(); if(sz > 1) { auto it = m_TrustedFamilies.begin (); std::advance(it, rand() % sz); fam = *it; - boost::to_lower(fam); } else if (sz == 1) { fam = m_TrustedFamilies[0]; } - if (fam.size()) + if (fam) return i2p::data::netdb.GetRandomRouterInFamily(fam); } { @@ -756,5 +1029,131 @@ namespace transport } return false; } + + void Transports::SetOnline (bool online) + { + if (m_IsOnline != online) + { + m_IsOnline = online; + if (online) + PeerTest (); + else + i2p::context.SetError (eRouterErrorOffline); + } + } + + 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::address_v6::from_string (yggaddress); + 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::address_v6::from_string (ipv6Addr); + if (!addr.is_unspecified () && addr != boost::asio::ip::address_v6::any ()) + i2p::context.UpdateNTCP2V6Address (addr); // set ipv6 address if configured + } + } + else + i2p::context.PublishNTCP2Address (port, false, ipv4, ipv6, false); // unpublish + } + if (ygg) + { + i2p::context.PublishNTCP2Address (port, true, false, false, true); + i2p::context.UpdateNTCP2V6Address (yggaddr); + if (!ipv4 && !ipv6) + i2p::context.SetStatus (eRouterStatusMesh); + } + bool ssu2; i2p::config::GetOption("ssu2.enabled", ssu2); + if (ssu2 && i2p::config::IsDefault ("ssu2.enabled") && !ipv4 && !ipv6) + ssu2 = false; // don't enable ssu2 for yggdrasil only router + if (ssu2) + { + uint16_t ssu2port; i2p::config::GetOption("ssu2.port", ssu2port); + if (!ssu2port && port) ssu2port = port; + bool published; i2p::config::GetOption("ssu2.published", published); + if (published) + i2p::context.PublishSSU2Address (ssu2port, true, ipv4, ipv6); // publish + else + i2p::context.PublishSSU2Address (ssu2port, false, ipv4, ipv6); // unpublish + } + + } } } diff --git a/libi2pd/Transports.h b/libi2pd/Transports.h index c3008b09..a8f2a16a 100644 --- a/libi2pd/Transports.h +++ b/libi2pd/Transports.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -21,7 +21,7 @@ #include #include #include "TransportSession.h" -#include "SSU.h" +#include "SSU2.h" #include "NTCP2.h" #include "RouterInfo.h" #include "I2NPProtocol.h" @@ -59,27 +59,54 @@ namespace transport std::condition_variable m_Acquired; std::mutex m_AcquiredMutex; }; - typedef EphemeralKeysSupplier DHKeysPairSupplier; 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; struct Peer { int numAttempts; std::shared_ptr router; std::list > sessions; - uint64_t creationTime; + uint64_t creationTime, nextRouterInfoUpdateTime; std::vector > delayedMessages; + std::vector priority; + bool isHighBandwidth, isReachable; + + Peer (std::shared_ptr r, uint64_t ts): + numAttempts (0), router (r), creationTime (ts), + nextRouterInfoUpdateTime (ts + PEER_ROUTER_INFO_UPDATE_INTERVAL), + isHighBandwidth (false), isReachable (false) + { + if (router) + { + isHighBandwidth = router->IsHighBandwidth (); + isReachable = (bool)router->GetCompatibleTransports (true); + } + } void Done () { for (auto& it: sessions) it->Done (); } + + void SetRouter (std::shared_ptr r) + { + router = r; + if (router) + { + isHighBandwidth = router->IsHighBandwidth (); + isReachable = (bool)router->GetCompatibleTransports (true); + } + } }; - const size_t SESSION_CREATION_TIMEOUT = 10; // in seconds + const uint64_t SESSION_CREATION_TIMEOUT = 15; // in seconds const int PEER_TEST_INTERVAL = 71; // in minutes - const int MAX_NUM_DELAYED_MESSAGES = 50; + const int MAX_NUM_DELAYED_MESSAGES = 150; + const int CHECK_PROFILE_NUM_DELAYED_MESSAGES = 15; // check profile after class Transports { public: @@ -87,18 +114,16 @@ namespace transport Transports (); ~Transports (); - void Start (bool enableNTCP2=true, bool enableSSU=true); + void Start (bool enableNTCP2=true, bool enableSSU2=true); void Stop (); - bool IsBoundSSU() const { return m_SSUServer != nullptr; } + bool IsBoundSSU2() const { return m_SSU2Server != nullptr; } bool IsBoundNTCP2() const { return m_NTCP2Server != nullptr; } bool IsOnline() const { return m_IsOnline; }; - void SetOnline (bool online) { m_IsOnline = online; }; + void SetOnline (bool online); boost::asio::io_service& GetService () { return *m_Service; }; - std::shared_ptr GetNextDHKeysPair (); - void ReuseDHKeysPair (std::shared_ptr pair); std::shared_ptr GetNextX25519KeysPair (); void ReuseX25519KeysPair (std::shared_ptr pair); @@ -118,23 +143,29 @@ namespace transport uint32_t GetInBandwidth () const { return m_InBandwidth; }; uint32_t GetOutBandwidth () const { return m_OutBandwidth; }; uint32_t GetTransitBandwidth () const { return m_TransitBandwidth; }; + uint32_t GetInBandwidth15s () const { return m_InBandwidth15s; }; + uint32_t GetOutBandwidth15s () const { return m_OutBandwidth15s; }; + uint32_t GetTransitBandwidth15s () const { return m_TransitBandwidth15s; }; bool IsBandwidthExceeded () const; bool IsTransitBandwidthExceeded () const; size_t GetNumPeers () const { return m_Peers.size (); }; - std::shared_ptr GetRandomPeer () const; + std::shared_ptr GetRandomPeer (bool isHighBandwidth) const; /** get a trusted first hop for restricted routes */ std::shared_ptr GetRestrictedPeer() const; /** do we want to use restricted routes? */ bool RoutesRestricted() const; /** restrict routes to use only these router families for first hops */ - void RestrictRoutesToFamilies(std::set families); + void RestrictRoutesToFamilies(const std::set& families); /** restrict routes to use only these routers for first hops */ void RestrictRoutesToRouters(std::set routers); bool IsRestrictedPeer(const i2p::data::IdentHash & ident) const; - void PeerTest (); + void PeerTest (bool ipv4 = true, bool ipv6 = true); + + void SetCheckReserved (bool check) { m_CheckReserved = check; }; + bool IsCheckReserved () { return m_CheckReserved; }; private: @@ -143,35 +174,45 @@ namespace transport void HandleRequestComplete (std::shared_ptr r, i2p::data::IdentHash ident); void PostMessages (i2p::data::IdentHash ident, std::vector > msgs); bool ConnectToPeer (const i2p::data::IdentHash& ident, Peer& peer); + void SetPriority (Peer& peer) const; 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 UpdateBandwidth (); void DetectExternalIP (); + template + std::shared_ptr GetRandomPeer (Filter filter) const; + private: - bool m_IsOnline, m_IsRunning, m_IsNAT; + volatile bool m_IsOnline; + bool m_IsRunning, m_IsNAT, m_CheckReserved; std::thread * m_Thread; boost::asio::io_service * m_Service; boost::asio::io_service::work * m_Work; - boost::asio::deadline_timer * m_PeerCleanupTimer, * m_PeerTestTimer; + boost::asio::deadline_timer * m_PeerCleanupTimer, * m_PeerTestTimer, * m_UpdateBandwidthTimer; - SSUServer * m_SSUServer; + SSU2Server * m_SSU2Server; NTCP2Server * m_NTCP2Server; mutable std::mutex m_PeersMutex; std::unordered_map m_Peers; - DHKeysPairSupplier m_DHKeysPairSupplier; X25519KeysPairSupplier m_X25519KeysPairSupplier; std::atomic m_TotalSentBytes, m_TotalReceivedBytes, m_TotalTransitTransmittedBytes; - uint32_t m_InBandwidth, m_OutBandwidth, m_TransitBandwidth; // bytes per second + + // Bandwidth per second + uint32_t m_InBandwidth, m_OutBandwidth, m_TransitBandwidth; uint64_t m_LastInBandwidthUpdateBytes, m_LastOutBandwidthUpdateBytes, m_LastTransitBandwidthUpdateBytes; - uint64_t m_LastBandwidthUpdateTime; + + // Bandwidth every 15 seconds + uint32_t m_InBandwidth15s, m_OutBandwidth15s, m_TransitBandwidth15s; + uint64_t m_LastInBandwidth15sUpdateBytes, m_LastOutBandwidth15sUpdateBytes, m_LastTransitBandwidth15sUpdateBytes; + uint64_t m_LastBandwidth15sUpdateTime; /** which router families to trust for first hops */ - std::vector m_TrustedFamilies; + std::vector m_TrustedFamilies; mutable std::mutex m_FamilyMutex; /** which routers for first hop to trust */ @@ -183,12 +224,15 @@ namespace transport public: // for HTTP only - const SSUServer * GetSSUServer () const { return m_SSUServer; }; const NTCP2Server * GetNTCP2Server () const { return m_NTCP2Server; }; + const SSU2Server * GetSSU2Server () const { return m_SSU2Server; }; const decltype(m_Peers)& GetPeers () const { return m_Peers; }; }; extern Transports transports; + + void InitAddressFromIface (); + void InitTransports (); } } diff --git a/libi2pd/Tunnel.cpp b/libi2pd/Tunnel.cpp index fe7e36af..6234ceb4 100644 --- a/libi2pd/Tunnel.cpp +++ b/libi2pd/Tunnel.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -22,6 +22,8 @@ #include "Config.h" #include "Tunnel.h" #include "TunnelPool.h" +#include "util.h" +#include "ECIESX25519AEADRatchetSession.h" namespace i2p { @@ -29,8 +31,9 @@ namespace tunnel { Tunnel::Tunnel (std::shared_ptr config): TunnelBase (config->GetTunnelID (), config->GetNextTunnelID (), config->GetNextIdentHash ()), - m_Config (config), m_Pool (nullptr), m_State (eTunnelStatePending), m_IsRecreated (false), - m_Latency (0) + m_Config (config), m_IsShortBuildMessage (false), m_Pool (nullptr), + m_State (eTunnelStatePending), m_FarEndTransports (i2p::data::RouterInfo::eAllTransports), + m_IsRecreated (false), m_Latency (0) { } @@ -41,10 +44,11 @@ namespace tunnel void Tunnel::Build (uint32_t replyMsgID, std::shared_ptr outboundTunnel) { auto numHops = m_Config->GetNumHops (); - int numRecords = numHops <= STANDARD_NUM_RECORDS ? STANDARD_NUM_RECORDS : numHops; - auto msg = NewI2NPShortMessage (); + const int numRecords = numHops <= STANDARD_NUM_RECORDS ? STANDARD_NUM_RECORDS : MAX_NUM_RECORDS; + auto msg = numRecords <= STANDARD_NUM_RECORDS ? NewI2NPShortMessage () : NewI2NPMessage (); *msg->GetPayload () = numRecords; - msg->len += numRecords*TUNNEL_BUILD_RECORD_SIZE + 1; + 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); @@ -54,7 +58,6 @@ namespace tunnel uint8_t * records = msg->GetPayload () + 1; TunnelHopConfig * hop = m_Config->GetFirstHop (); int i = 0; - BN_CTX * ctx = BN_CTX_new (); while (hop) { uint32_t msgID; @@ -62,79 +65,102 @@ namespace tunnel RAND_bytes ((uint8_t *)&msgID, 4); else msgID = replyMsgID; - int idx = recordIndicies[i]; - hop->CreateBuildRequestRecord (records + idx*TUNNEL_BUILD_RECORD_SIZE, msgID, ctx); - hop->recordIndex = idx; - i++; + hop->recordIndex = recordIndicies[i]; i++; + hop->CreateBuildRequestRecord (records, msgID); hop = hop->next; } - BN_CTX_free (ctx); // fill up fake records with random data for (int i = numHops; i < numRecords; i++) { int idx = recordIndicies[i]; - RAND_bytes (records + idx*TUNNEL_BUILD_RECORD_SIZE, TUNNEL_BUILD_RECORD_SIZE); + RAND_bytes (records + idx*recordSize, recordSize); } // decrypt real records - i2p::crypto::CBCDecryption decryption; hop = m_Config->GetLastHop ()->prev; while (hop) { - decryption.SetKey (hop->replyKey); // decrypt records after current hop TunnelHopConfig * hop1 = hop->next; while (hop1) { - decryption.SetIV (hop->replyIV); - uint8_t * record = records + hop1->recordIndex*TUNNEL_BUILD_RECORD_SIZE; - decryption.Decrypt(record, TUNNEL_BUILD_RECORD_SIZE, record); + hop->DecryptRecord (records, hop1->recordIndex); hop1 = hop1->next; } hop = hop->prev; } - msg->FillI2NPMessageHeader (eI2NPVariableTunnelBuild); + msg->FillI2NPMessageHeader (m_Config->IsShort () ? eI2NPShortTunnelBuild : eI2NPVariableTunnelBuild); // send message if (outboundTunnel) - outboundTunnel->SendTunnelDataMsg (GetNextIdentHash (), 0, msg); + { + if (m_Config->IsShort ()) + { + auto ident = m_Config->GetFirstHop () ? m_Config->GetFirstHop ()->ident : nullptr; + if (ident && ident->GetIdentHash () != outboundTunnel->GetNextIdentHash ()) // don't encrypt if IBGW = OBEP + { + 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.AddECIESx25519Key (key, tag); + } i2p::transport::transports.SendMessage (GetNextIdentHash (), msg); + } } bool Tunnel::HandleTunnelBuildResponse (uint8_t * msg, size_t len) { LogPrint (eLogDebug, "Tunnel: TunnelBuildResponse ", (int)msg[0], " records."); - i2p::crypto::CBCDecryption decryption; TunnelHopConfig * hop = m_Config->GetLastHop (); while (hop) { - decryption.SetKey (hop->replyKey); - // decrypt records before and including current hop - TunnelHopConfig * hop1 = 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 < msg[0]) - { - uint8_t * record = msg + 1 + idx*TUNNEL_BUILD_RECORD_SIZE; - decryption.SetIV (hop->replyIV); - decryption.Decrypt(record, TUNNEL_BUILD_RECORD_SIZE, record); - } + hop->DecryptRecord (msg + 1, idx); else - LogPrint (eLogWarning, "Tunnel: hop index ", idx, " is out of range"); + LogPrint (eLogWarning, "Tunnel: Hop index ", idx, " is out of range"); hop1 = hop1->prev; } hop = hop->prev; } bool established = true; + size_t numHops = 0; hop = m_Config->GetFirstHop (); while (hop) { - const uint8_t * record = msg + 1 + hop->recordIndex*TUNNEL_BUILD_RECORD_SIZE; - uint8_t ret = record[BUILD_RESPONSE_RECORD_RET_OFFSET]; + uint8_t ret = hop->GetRetCode (msg + 1); LogPrint (eLogDebug, "Tunnel: Build response ret code=", (int)ret); auto profile = i2p::data::netdb.FindRouterProfile (hop->ident->GetIdentHash ()); if (profile) @@ -143,19 +169,23 @@ namespace tunnel // 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) { - auto tunnelHop = new TunnelHop; - tunnelHop->ident = hop->ident; - tunnelHop->decryption.SetKeys (hop->layerKey, hop->ivKey); - m_Hops.push_back (std::unique_ptr(tunnelHop)); + 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; @@ -174,7 +204,7 @@ namespace tunnel uint8_t * outPayload = out->GetPayload () + 4; for (auto& it: m_Hops) { - it->decryption.Decrypt (inPayload, outPayload); + it.decryption.Decrypt (inPayload, outPayload); inPayload = outPayload; } } @@ -195,8 +225,8 @@ namespace tunnel { // hops are in inverted order std::vector > ret; - for (auto& it: m_Hops) - ret.push_back (it->ident); + for (const auto& it: m_Hops) + ret.push_back (it.ident); return ret; } @@ -205,30 +235,19 @@ namespace tunnel m_State = state; } - - void Tunnel::PrintHops (std::stringstream& s) const + void Tunnel::VisitTunnelHops(TunnelHopVisitor v) { - // hops are in inverted order, we must print in direct order + // hops are in inverted order, we must return in direct order for (auto it = m_Hops.rbegin (); it != m_Hops.rend (); it++) - { - s << " ⇒ "; - s << i2p::data::GetIdentHashAbbreviation ((*it)->ident->GetIdentHash ()); - } + v((*it).ident); } - void InboundTunnel::HandleTunnelDataMsg (std::shared_ptr msg) + void InboundTunnel::HandleTunnelDataMsg (std::shared_ptr&& msg) { if (IsFailed ()) SetState (eTunnelStateEstablished); // incoming messages means a tunnel is alive - auto newMsg = CreateEmptyTunnelDataMsg (); - EncryptTunnelMsg (msg, newMsg); - newMsg->from = shared_from_this (); - m_Endpoint.HandleDecryptedTunnelDataMsg (newMsg); - } - - void InboundTunnel::Print (std::stringstream& s) const - { - PrintHops (s); - s << " ⇒ " << GetTunnelID () << ":me"; + EncryptTunnelMsg (msg, msg); + msg->from = shared_from_this (); + m_Endpoint.HandleDecryptedTunnelDataMsg (msg); } ZeroHopsInboundTunnel::ZeroHopsInboundTunnel (): @@ -247,12 +266,7 @@ namespace tunnel } } - void ZeroHopsInboundTunnel::Print (std::stringstream& s) const - { - s << " ⇒ " << GetTunnelID () << ":me"; - } - - void OutboundTunnel::SendTunnelDataMsg (const uint8_t * gwHash, uint32_t gwTunnel, std::shared_ptr msg) + void OutboundTunnel::SendTunnelDataMsgTo (const uint8_t * gwHash, uint32_t gwTunnel, std::shared_ptr msg) { TunnelMessageBlock block; if (gwHash) @@ -270,10 +284,10 @@ namespace tunnel block.deliveryType = eDeliveryTypeLocal; block.data = msg; - SendTunnelDataMsg ({block}); + SendTunnelDataMsgs ({block}); } - void OutboundTunnel::SendTunnelDataMsg (const std::vector& msgs) + void OutboundTunnel::SendTunnelDataMsgs (const std::vector& msgs) { std::unique_lock l(m_SendMutex); for (auto& it : msgs) @@ -281,16 +295,9 @@ namespace tunnel m_Gateway.SendBuffer (); } - void OutboundTunnel::HandleTunnelDataMsg (std::shared_ptr tunnelMsg) + void OutboundTunnel::HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg) { - LogPrint (eLogError, "Tunnel: incoming message for outbound tunnel ", GetTunnelID ()); - } - - void OutboundTunnel::Print (std::stringstream& s) const - { - s << GetTunnelID () << ":me"; - PrintHops (s); - s << " ⇒ "; + LogPrint (eLogError, "Tunnel: Incoming message for outbound tunnel ", GetTunnelID ()); } ZeroHopsOutboundTunnel::ZeroHopsOutboundTunnel (): @@ -299,14 +306,16 @@ namespace tunnel { } - void ZeroHopsOutboundTunnel::SendTunnelDataMsg (const std::vector& msgs) + 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: - i2p::HandleI2NPMessage (msg.data); + HandleI2NPMessage (msg.data); break; case eDeliveryTypeTunnel: i2p::transport::transports.SendMessage (msg.hash, i2p::CreateTunnelGatewayMsg (msg.tunnelID, msg.data)); @@ -320,20 +329,17 @@ namespace tunnel } } - void ZeroHopsOutboundTunnel::Print (std::stringstream& s) const - { - s << GetTunnelID () << ":me ⇒ "; - } - Tunnels tunnels; - Tunnels::Tunnels (): m_IsRunning (false), m_Thread (nullptr), - m_NumSuccesiveTunnelCreations (0), m_NumFailedTunnelCreations (0) + Tunnels::Tunnels (): m_IsRunning (false), m_Thread (nullptr), m_MaxNumTransitTunnels (DEFAULT_MAX_NUM_TRANSIT_TUNNELS), + m_TotalNumSuccesiveTunnelCreations (0), m_TotalNumFailedTunnelCreations (0), // for normal average + m_TunnelCreationSuccessRate (TCSR_START_VALUE), m_TunnelCreationAttemptsNum(0) { } Tunnels::~Tunnels () { + DeleteTunnelPool(m_ExploratoryPool); } std::shared_ptr Tunnels::GetTunnel (uint32_t tunnelID) @@ -399,10 +405,10 @@ namespace tunnel return tunnel; } - std::shared_ptr Tunnels::CreateTunnelPool (int numInboundHops, - int numOutboundHops, int numInboundTunnels, int numOutboundTunnels) + std::shared_ptr Tunnels::CreateTunnelPool (int numInboundHops, int numOutboundHops, + int numInboundTunnels, int numOutboundTunnels, int inboundVariance, int outboundVariance) { - auto pool = std::make_shared (numInboundHops, numOutboundHops, numInboundTunnels, numOutboundTunnels); + auto pool = std::make_shared (numInboundHops, numOutboundHops, numInboundTunnels, numOutboundTunnels, inboundVariance, outboundVariance); std::unique_lock l(m_PoolsMutex); m_Pools.push_back (pool); return pool; @@ -429,12 +435,16 @@ namespace tunnel } } - void Tunnels::AddTransitTunnel (std::shared_ptr tunnel) + bool Tunnels::AddTransitTunnel (std::shared_ptr tunnel) { if (m_Tunnels.emplace (tunnel->GetTunnelID (), tunnel).second) m_TransitTunnels.push_back (tunnel); else - LogPrint (eLogError, "Tunnel: tunnel with id ", tunnel->GetTunnelID (), " already exists"); + { + LogPrint (eLogError, "Tunnel: Tunnel with id ", tunnel->GetTunnelID (), " already exists"); + return false; + } + return true; } void Tunnels::Start () @@ -457,9 +467,10 @@ namespace tunnel void Tunnels::Run () { + i2p::util::SetThreadName("Tunnels"); std::this_thread::sleep_for (std::chrono::seconds(1)); // wait for other parts are ready - uint64_t lastTs = 0; + uint64_t lastTs = 0, lastPoolsTs = 0, lastMemoryPoolTs = 0; while (m_IsRunning) { try @@ -467,6 +478,7 @@ namespace tunnel auto msg = m_Queue.GetNextWithTimeout (1000); // 1 sec if (msg) { + int numMsgs = 0; uint32_t prevTunnelID = 0, tunnelID = 0; std::shared_ptr prevTunnel; do @@ -489,30 +501,33 @@ namespace tunnel if (tunnel) { if (typeID == eI2NPTunnelData) - tunnel->HandleTunnelDataMsg (msg); + 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); + LogPrint (eLogWarning, "Tunnel: Tunnel not found, tunnelID=", tunnelID, " previousTunnelID=", prevTunnelID, " type=", (int)typeID); break; } case eI2NPVariableTunnelBuild: case eI2NPVariableTunnelBuildReply: + case eI2NPShortTunnelBuild: + case eI2NPShortTunnelBuildReply: case eI2NPTunnelBuild: case eI2NPTunnelBuildReply: - HandleI2NPMessage (msg->GetBuffer (), msg->GetLength ()); + HandleTunnelBuildI2NPMessage (msg); break; default: - LogPrint (eLogWarning, "Tunnel: unexpected message type ", (int) typeID); + LogPrint (eLogWarning, "Tunnel: Unexpected message type ", (int) typeID); } - msg = m_Queue.Get (); + msg = (numMsgs <= MAX_TUNNEL_MSGS_BATCH_SIZE) ? m_Queue.Get () : nullptr; if (msg) { prevTunnelID = tunnelID; prevTunnel = tunnel; + numMsgs++; } else if (tunnel) tunnel->FlushTunnelDataMsgs (); @@ -520,16 +535,33 @@ namespace tunnel while (msg); } - uint64_t ts = i2p::util::GetSecondsSinceEpoch (); - if (ts - lastTs >= 15) // manage tunnels every 15 seconds + if (i2p::transport::transports.IsOnline()) { - ManageTunnels (); - lastTs = ts; + 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 ()); + LogPrint (eLogError, "Tunnel: Runtime exception: ", ex.what ()); } } } @@ -538,7 +570,7 @@ namespace tunnel { if (!tunnel) { - LogPrint (eLogError, "Tunnel: missing tunnel for gateway"); + LogPrint (eLogError, "Tunnel: Missing tunnel for gateway"); return; } const uint8_t * payload = msg->GetPayload (); @@ -547,49 +579,55 @@ namespace tunnel msg->offset += I2NP_HEADER_SIZE + TUNNEL_GATEWAY_HEADER_SIZE; if (msg->offset + len > msg->len) { - LogPrint (eLogError, "Tunnel: gateway payload ", (int)len, " exceeds message length ", (int)msg->len); + LogPrint (eLogError, "Tunnel: Gateway payload ", (int)len, " exceeds message length ", (int)msg->len); return; } msg->len = msg->offset + len; auto typeID = msg->GetTypeID (); - LogPrint (eLogDebug, "Tunnel: gateway of ", (int) len, " bytes for tunnel ", tunnel->GetTunnelID (), ", msg type ", (int)typeID); + LogPrint (eLogDebug, "Tunnel: Gateway of ", (int) len, " bytes for tunnel ", tunnel->GetTunnelID (), ", msg type ", (int)typeID); - if (IsRouterInfoMsg (msg) || typeID == eI2NPDatabaseSearchReply) - // transit DatabaseStore my contain new/updated RI - // or DatabaseSearchReply with new routers + if (typeID == eI2NPDatabaseSearchReply) + // DatabaseSearchReply with new routers i2p::data::netdb.PostI2NPMsg (CopyI2NPMessage (msg)); + else if (IsRouterInfoMsg (msg)) + { + // transit DatabaseStore might contain new/updated RI + auto m = CopyI2NPMessage (msg); + if (bufbe32toh (m->GetPayload () + DATABASE_STORE_REPLY_TOKEN_OFFSET)) + memset (m->GetPayload () + DATABASE_STORE_REPLY_TOKEN_OFFSET, 0xFF, 4); // fake replyToken meaning no reply + i2p::data::netdb.PostI2NPMsg (m); + } tunnel->SendTunnelDataMsg (msg); } - void Tunnels::ManageTunnels () + void Tunnels::ManageTunnels (uint64_t ts) { - ManagePendingTunnels (); - ManageInboundTunnels (); - ManageOutboundTunnels (); - ManageTransitTunnels (); - ManageTunnelPools (); + ManagePendingTunnels (ts); + ManageInboundTunnels (ts); + ManageOutboundTunnels (ts); + ManageTransitTunnels (ts); } - void Tunnels::ManagePendingTunnels () + void Tunnels::ManagePendingTunnels (uint64_t ts) { - ManagePendingTunnels (m_PendingInboundTunnels); - ManagePendingTunnels (m_PendingOutboundTunnels); + ManagePendingTunnels (m_PendingInboundTunnels, ts); + ManagePendingTunnels (m_PendingOutboundTunnels, ts); } template - void Tunnels::ManagePendingTunnels (PendingTunnels& pendingTunnels) + void Tunnels::ManagePendingTunnels (PendingTunnels& pendingTunnels, uint64_t ts) { // check pending tunnel. delete failed or timeout - uint64_t ts = i2p::util::GetSecondsSinceEpoch (); for (auto it = pendingTunnels.begin (); it != pendingTunnels.end ();) { auto tunnel = it->second; switch (tunnel->GetState ()) { case eTunnelStatePending: - if (ts > tunnel->GetCreationTime () + TUNNEL_CREATION_TIMEOUT) + if (ts > tunnel->GetCreationTime () + TUNNEL_CREATION_TIMEOUT || + ts + TUNNEL_CREATION_TIMEOUT < tunnel->GetCreationTime ()) { - LogPrint (eLogDebug, "Tunnel: pending build request ", it->first, " timeout, deleted"); + LogPrint (eLogDebug, "Tunnel: Pending build request ", it->first, " timeout, deleted"); // update stats auto config = tunnel->GetTunnelConfig (); if (config) @@ -608,15 +646,15 @@ namespace tunnel } // delete it = pendingTunnels.erase (it); - m_NumFailedTunnelCreations++; + FailedTunnelCreation(); } else ++it; break; case eTunnelStateBuildFailed: - LogPrint (eLogDebug, "Tunnel: pending build request ", it->first, " failed, deleted"); + LogPrint (eLogDebug, "Tunnel: Pending build request ", it->first, " failed, deleted"); it = pendingTunnels.erase (it); - m_NumFailedTunnelCreations++; + FailedTunnelCreation(); break; case eTunnelStateBuildReplyReceived: // intermediate state, will be either established of build failed @@ -625,118 +663,113 @@ namespace tunnel default: // success it = pendingTunnels.erase (it); - m_NumSuccesiveTunnelCreations++; + SuccesiveTunnelCreation(); } } } - void Tunnels::ManageOutboundTunnels () + void Tunnels::ManageOutboundTunnels (uint64_t ts) { - uint64_t ts = i2p::util::GetSecondsSinceEpoch (); + for (auto it = m_OutboundTunnels.begin (); it != m_OutboundTunnels.end ();) { - for (auto it = m_OutboundTunnels.begin (); it != m_OutboundTunnels.end ();) + auto tunnel = *it; + if (ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) { - auto tunnel = *it; - if (ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) + LogPrint (eLogDebug, "Tunnel: Tunnel with id ", tunnel->GetTunnelID (), " expired"); + auto pool = tunnel->GetTunnelPool (); + if (pool) + pool->TunnelExpired (tunnel); + // we don't have outbound tunnels in m_Tunnels + it = m_OutboundTunnels.erase (it); + } + else + { + if (tunnel->IsEstablished ()) { - LogPrint (eLogDebug, "Tunnel: tunnel with id ", tunnel->GetTunnelID (), " expired"); - auto pool = tunnel->GetTunnelPool (); - if (pool) - pool->TunnelExpired (tunnel); - // we don't have outbound tunnels in m_Tunnels - it = m_OutboundTunnels.erase (it); - } - else - { - if (tunnel->IsEstablished ()) + if (!tunnel->IsRecreated () && ts + TUNNEL_RECREATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) { - 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()) { - auto pool = tunnel->GetTunnelPool (); - // let it die if the tunnel pool has been reconfigured and this is old - if (pool && tunnel->GetNumHops() == pool->GetNumOutboundHops()) - { - tunnel->SetIsRecreated (); - pool->RecreateOutboundTunnel (tunnel); - } + tunnel->SetRecreated (true); + pool->RecreateOutboundTunnel (tunnel); } - if (ts + TUNNEL_EXPIRATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) - tunnel->SetState (eTunnelStateExpiring); } - ++it; + if (ts + TUNNEL_EXPIRATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) + tunnel->SetState (eTunnelStateExpiring); } + ++it; } } if (m_OutboundTunnels.size () < 3) { - // trying to create one more oubound tunnel + // trying to create one more outbound tunnel auto inboundTunnel = GetNextInboundTunnel (); auto router = i2p::transport::transports.RoutesRestricted() ? i2p::transport::transports.GetRestrictedPeer() : - i2p::data::netdb.GetRandomRouter (); + i2p::data::netdb.GetRandomRouter (i2p::context.GetSharedRouterInfo (), false, true); // reachable by us if (!inboundTunnel || !router) return; - LogPrint (eLogDebug, "Tunnel: creating one hop outbound tunnel"); + LogPrint (eLogDebug, "Tunnel: Creating one hop outbound tunnel"); CreateTunnel ( std::make_shared (std::vector > { router->GetRouterIdentity () }, - inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash ()) + inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash (), false), nullptr ); } } - void Tunnels::ManageInboundTunnels () + void Tunnels::ManageInboundTunnels (uint64_t ts) { - uint64_t ts = i2p::util::GetSecondsSinceEpoch (); + for (auto it = m_InboundTunnels.begin (); it != m_InboundTunnels.end ();) { - for (auto it = m_InboundTunnels.begin (); it != m_InboundTunnels.end ();) + auto tunnel = *it; + if (ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT || + ts + TUNNEL_EXPIRATION_TIMEOUT < tunnel->GetCreationTime ()) { - auto tunnel = *it; - if (ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) + LogPrint (eLogDebug, "Tunnel: Tunnel with id ", tunnel->GetTunnelID (), " expired"); + auto pool = tunnel->GetTunnelPool (); + if (pool) + pool->TunnelExpired (tunnel); + m_Tunnels.erase (tunnel->GetTunnelID ()); + it = m_InboundTunnels.erase (it); + } + else + { + if (tunnel->IsEstablished ()) { - LogPrint (eLogDebug, "Tunnel: tunnel with id ", tunnel->GetTunnelID (), " expired"); - auto pool = tunnel->GetTunnelPool (); - if (pool) - pool->TunnelExpired (tunnel); - m_Tunnels.erase (tunnel->GetTunnelID ()); - it = m_InboundTunnels.erase (it); - } - else - { - if (tunnel->IsEstablished ()) + if (!tunnel->IsRecreated () && ts + TUNNEL_RECREATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) { - 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()) { - auto pool = tunnel->GetTunnelPool (); - // let it die if the tunnel pool was reconfigured and has different number of hops - if (pool && tunnel->GetNumHops() == pool->GetNumInboundHops()) - { - tunnel->SetIsRecreated (); - pool->RecreateInboundTunnel (tunnel); - } + tunnel->SetRecreated (true); + pool->RecreateInboundTunnel (tunnel); } - - if (ts + TUNNEL_EXPIRATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) - tunnel->SetState (eTunnelStateExpiring); - else // we don't need to cleanup expiring tunnels - tunnel->Cleanup (); } - it++; + + 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 (); - CreateZeroHopsOutboundTunnel (); + 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); + m_ExploratoryPool = CreateTunnelPool (ibLen, obLen, ibNum, obNum, 0, 0); m_ExploratoryPool->SetLocalDestination (i2p::context.GetSharedDestination ()); } return; @@ -747,25 +780,26 @@ namespace tunnel // trying to create one more inbound tunnel auto router = i2p::transport::transports.RoutesRestricted() ? i2p::transport::transports.GetRestrictedPeer() : - i2p::data::netdb.GetRandomRouter (); + // should be reachable by us because we send build request directly + i2p::data::netdb.GetRandomRouter (i2p::context.GetSharedRouterInfo (), false, true); if (!router) { - LogPrint (eLogWarning, "Tunnel: can't find any router, skip creating tunnel"); + LogPrint (eLogWarning, "Tunnel: Can't find any router, skip creating tunnel"); return; } - LogPrint (eLogDebug, "Tunnel: creating one hop inbound tunnel"); + LogPrint (eLogDebug, "Tunnel: Creating one hop inbound tunnel"); CreateTunnel ( - std::make_shared (std::vector > { router->GetRouterIdentity () }) + std::make_shared (std::vector > { router->GetRouterIdentity () }, false), nullptr ); } } - void Tunnels::ManageTransitTunnels () + void Tunnels::ManageTransitTunnels (uint64_t ts) { - uint32_t ts = i2p::util::GetSecondsSinceEpoch (); for (auto it = m_TransitTunnels.begin (); it != m_TransitTunnels.end ();) { auto tunnel = *it; - if (ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) + if (ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT || + ts + TUNNEL_EXPIRATION_TIMEOUT < tunnel->GetCreationTime ()) { LogPrint (eLogDebug, "Tunnel: Transit tunnel with id ", tunnel->GetTunnelID (), " expired"); m_Tunnels.erase (tunnel->GetTunnelID ()); @@ -779,16 +813,13 @@ namespace tunnel } } - void Tunnels::ManageTunnelPools () + void Tunnels::ManageTunnelPools (uint64_t ts) { std::unique_lock l(m_PoolsMutex); for (auto& pool : m_Pools) { if (pool && pool->IsActive ()) - { - pool->CreateTunnels (); - pool->TestTunnels (); - } + pool->ManageTunnels (ts); } } @@ -803,9 +834,11 @@ namespace tunnel } template - std::shared_ptr Tunnels::CreateTunnel (std::shared_ptr config, std::shared_ptr outboundTunnel) + 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); @@ -813,20 +846,21 @@ namespace tunnel return newTunnel; } - std::shared_ptr Tunnels::CreateInboundTunnel (std::shared_ptr config, std::shared_ptr outboundTunnel) + std::shared_ptr Tunnels::CreateInboundTunnel (std::shared_ptr config, + std::shared_ptr pool, std::shared_ptr outboundTunnel) { if (config) - return CreateTunnel(config, outboundTunnel); + return CreateTunnel(config, pool, outboundTunnel); else - return CreateZeroHopsInboundTunnel (); + return CreateZeroHopsInboundTunnel (pool); } - std::shared_ptr Tunnels::CreateOutboundTunnel (std::shared_ptr config) + std::shared_ptr Tunnels::CreateOutboundTunnel (std::shared_ptr config, std::shared_ptr pool) { if (config) - return CreateTunnel(config); + return CreateTunnel(config, pool); else - return CreateZeroHopsOutboundTunnel (); + return CreateZeroHopsOutboundTunnel (pool); } void Tunnels::AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr tunnel) @@ -860,7 +894,7 @@ namespace tunnel { // build symmetric outbound tunnel CreateTunnel (std::make_shared(newTunnel->GetInvertedPeers (), - newTunnel->GetNextTunnelID (), newTunnel->GetNextIdentHash ()), + newTunnel->GetNextTunnelID (), newTunnel->GetNextIdentHash (), false), nullptr, GetNextOutboundTunnel ()); } else @@ -872,28 +906,48 @@ namespace tunnel } } else - LogPrint (eLogError, "Tunnel: tunnel with id ", newTunnel->GetTunnelID (), " already exists"); + LogPrint (eLogError, "Tunnel: Tunnel with id ", newTunnel->GetTunnelID (), " already exists"); } - std::shared_ptr Tunnels::CreateZeroHopsInboundTunnel () + std::shared_ptr Tunnels::CreateZeroHopsInboundTunnel (std::shared_ptr pool) { auto inboundTunnel = std::make_shared (); + inboundTunnel->SetTunnelPool (pool); inboundTunnel->SetState (eTunnelStateEstablished); m_InboundTunnels.push_back (inboundTunnel); m_Tunnels[inboundTunnel->GetTunnelID ()] = inboundTunnel; return inboundTunnel; } - std::shared_ptr Tunnels::CreateZeroHopsOutboundTunnel () + 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 () { int timeout = 0; @@ -924,5 +978,14 @@ namespace tunnel // TODO: locking return m_OutboundTunnels.size(); } + + void Tunnels::SetMaxNumTransitTunnels (uint16_t maxNumTransitTunnels) + { + if (maxNumTransitTunnels > 0 && m_MaxNumTransitTunnels != maxNumTransitTunnels) + { + LogPrint (eLogDebug, "Tunnel: Max number of transit tunnels set to ", maxNumTransitTunnels); + m_MaxNumTransitTunnels = maxNumTransitTunnels; + } + } } } diff --git a/libi2pd/Tunnel.h b/libi2pd/Tunnel.h index a50bc31a..e6e3c3a5 100644 --- a/libi2pd/Tunnel.h +++ b/libi2pd/Tunnel.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -18,6 +18,7 @@ #include #include #include +#include "util.h" #include "Queue.h" #include "Crypto.h" #include "TunnelConfig.h" @@ -37,6 +38,19 @@ namespace tunnel 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 HIGH_LATENCY_PER_HOP = 250; // in milliseconds + 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 { @@ -61,6 +75,9 @@ namespace tunnel public: + /** function for visiting a hops stored in a tunnel */ + typedef std::function)> TunnelHopVisitor; + Tunnel (std::shared_ptr config); ~Tunnel (); @@ -69,12 +86,14 @@ namespace tunnel std::shared_ptr GetTunnelConfig () const { return m_Config; } std::vector > GetPeers () const; std::vector > GetInvertedPeers () const; + bool IsShortBuildMessage () const { return m_IsShortBuildMessage; }; + i2p::data::RouterInfo::CompatibleTransports GetFarEndTransports () const { return m_FarEndTransports; }; TunnelState GetState () const { return m_State; }; void SetState (TunnelState state); bool IsEstablished () const { return m_State == eTunnelStateEstablished; }; bool IsFailed () const { return m_State == eTunnelStateFailed; }; bool IsRecreated () const { return m_IsRecreated; }; - void SetIsRecreated () { m_IsRecreated = true; }; + void SetRecreated (bool recreated) { m_IsRecreated = recreated; }; int GetNumHops () const { return m_Hops.size (); }; virtual bool IsInbound() const = 0; @@ -83,11 +102,9 @@ namespace tunnel bool HandleTunnelBuildResponse (uint8_t * msg, size_t len); - virtual void Print (std::stringstream&) const {}; - // implements TunnelBase - void SendTunnelDataMsg (std::shared_ptr msg); - void EncryptTunnelMsg (std::shared_ptr in, std::shared_ptr out); + void SendTunnelDataMsg (std::shared_ptr msg) override; + void EncryptTunnelMsg (std::shared_ptr in, std::shared_ptr out) override; /** @brief add latency sample */ void AddLatencySample(const uint64_t ms) { m_Latency = (m_Latency + ms) >> 1; } @@ -97,17 +114,20 @@ namespace tunnel bool LatencyFitsRange(uint64_t lowerbound, uint64_t upperbound) const; bool LatencyIsKnown() const { return m_Latency > 0; } - protected: + bool IsSlow () const { return LatencyIsKnown() && (int)m_Latency > HIGH_LATENCY_PER_HOP*GetNumHops (); } - void PrintHops (std::stringstream& s) const; + /** visit all hops we currently store */ + void VisitTunnelHops(TunnelHopVisitor v); private: std::shared_ptr m_Config; - std::vector > m_Hops; + std::vector m_Hops; + bool m_IsShortBuildMessage; std::shared_ptr m_Pool; // pool, tunnel belongs to, or null TunnelState m_State; - bool m_IsRecreated; + i2p::data::RouterInfo::CompatibleTransports m_FarEndTransports; + bool m_IsRecreated; // if tunnel is replaced by new, or new tunnel requested to replace uint64_t m_Latency; // in milliseconds }; @@ -118,16 +138,15 @@ namespace tunnel OutboundTunnel (std::shared_ptr config): Tunnel (config), m_Gateway (this), m_EndpointIdentHash (config->GetLastIdentHash ()) {}; - void SendTunnelDataMsg (const uint8_t * gwHash, uint32_t gwTunnel, std::shared_ptr msg); - virtual void SendTunnelDataMsg (const std::vector& msgs); // multiple messages + void SendTunnelDataMsgTo (const uint8_t * gwHash, uint32_t gwTunnel, std::shared_ptr msg); + virtual void SendTunnelDataMsgs (const std::vector& msgs); // multiple messages const i2p::data::IdentHash& GetEndpointIdentHash () const { return m_EndpointIdentHash; }; virtual size_t GetNumSentBytes () const { return m_Gateway.GetNumSentBytes (); }; - void Print (std::stringstream& s) const; // implements TunnelBase - void HandleTunnelDataMsg (std::shared_ptr tunnelMsg); + void HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg) override; - bool IsInbound() const { return false; } + bool IsInbound() const override { return false; } private: @@ -141,13 +160,12 @@ namespace tunnel public: InboundTunnel (std::shared_ptr config): Tunnel (config), m_Endpoint (true) {}; - void HandleTunnelDataMsg (std::shared_ptr msg); + void HandleTunnelDataMsg (std::shared_ptr&& msg) override; virtual size_t GetNumReceivedBytes () const { return m_Endpoint.GetNumReceivedBytes (); }; - void Print (std::stringstream& s) const; - bool IsInbound() const { return true; } + bool IsInbound() const override { return true; } // override TunnelBase - void Cleanup () { m_Endpoint.Cleanup (); }; + void Cleanup () override { m_Endpoint.Cleanup (); }; private: @@ -159,9 +177,8 @@ namespace tunnel public: ZeroHopsInboundTunnel (); - void SendTunnelDataMsg (std::shared_ptr msg); - void Print (std::stringstream& s) const; - size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; }; + void SendTunnelDataMsg (std::shared_ptr msg) override; + size_t GetNumReceivedBytes () const override { return m_NumReceivedBytes; }; private: @@ -173,9 +190,8 @@ namespace tunnel public: ZeroHopsOutboundTunnel (); - void SendTunnelDataMsg (const std::vector& msgs); - void Print (std::stringstream& s) const; - size_t GetNumSentBytes () const { return m_NumSentBytes; }; + void SendTunnelDataMsgs (const std::vector& msgs) override; + size_t GetNumSentBytes () const override { return m_NumSentBytes; }; private: @@ -198,24 +214,31 @@ namespace tunnel std::shared_ptr GetExploratoryPool () const { return m_ExploratoryPool; }; std::shared_ptr GetTunnel (uint32_t tunnelID); int GetTransitTunnelsExpirationTimeout (); - void AddTransitTunnel (std::shared_ptr tunnel); + bool AddTransitTunnel (std::shared_ptr tunnel); void AddOutboundTunnel (std::shared_ptr newTunnel); void AddInboundTunnel (std::shared_ptr newTunnel); - std::shared_ptr CreateInboundTunnel (std::shared_ptr config, std::shared_ptr outboundTunnel); - std::shared_ptr CreateOutboundTunnel (std::shared_ptr config); + std::shared_ptr CreateInboundTunnel (std::shared_ptr config, std::shared_ptr pool, std::shared_ptr outboundTunnel); + std::shared_ptr CreateOutboundTunnel (std::shared_ptr config, std::shared_ptr pool); void PostTunnelData (std::shared_ptr msg); void PostTunnelData (const std::vector >& msgs); void 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); + std::shared_ptr CreateTunnelPool (int numInboundHops, int numOuboundHops, + int numInboundTunnels, int numOutboundTunnels, int inboundVariance, int outboundVariance); void DeleteTunnelPool (std::shared_ptr pool); void StopTunnelPool (std::shared_ptr pool); + std::shared_ptr NewI2NPTunnelMessage (bool endpoint); + + void SetMaxNumTransitTunnels (uint16_t maxNumTransitTunnels); + uint16_t GetMaxNumTransitTunnels () const { return m_MaxNumTransitTunnels; }; + bool IsTooManyTransitTunnels () const { return m_TransitTunnels.size () >= m_MaxNumTransitTunnels; }; + private: template - std::shared_ptr CreateTunnel (std::shared_ptr config, std::shared_ptr outboundTunnel = nullptr); + 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); @@ -223,17 +246,35 @@ namespace tunnel void HandleTunnelGatewayMsg (std::shared_ptr tunnel, std::shared_ptr msg); void Run (); - void ManageTunnels (); - void ManageOutboundTunnels (); - void ManageInboundTunnels (); - void ManageTransitTunnels (); - void ManagePendingTunnels (); + void ManageTunnels (uint64_t ts); + void ManageOutboundTunnels (uint64_t ts); + void ManageInboundTunnels (uint64_t ts); + void ManageTransitTunnels (uint64_t ts); + void ManagePendingTunnels (uint64_t ts); template - void ManagePendingTunnels (PendingTunnels& pendingTunnels); - void ManageTunnelPools (); + void ManagePendingTunnels (PendingTunnels& pendingTunnels, uint64_t ts); + void ManageTunnelPools (uint64_t ts); - std::shared_ptr CreateZeroHopsInboundTunnel (); - std::shared_ptr CreateZeroHopsOutboundTunnel (); + 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: @@ -249,9 +290,13 @@ namespace tunnel std::list> m_Pools; std::shared_ptr m_ExploratoryPool; i2p::util::Queue > m_Queue; - - // some stats - int m_NumSuccesiveTunnelCreations, m_NumFailedTunnelCreations; + i2p::util::MemoryPoolMt > m_I2NPTunnelEndpointMessagesMemoryPool; + i2p::util::MemoryPoolMt > m_I2NPTunnelMessagesMemoryPool; + uint16_t m_MaxNumTransitTunnels; + // count of tunnels for total TCSR algorithm + int m_TotalNumSuccesiveTunnelCreations, m_TotalNumFailedTunnelCreations; + double m_TunnelCreationSuccessRate; + int m_TunnelCreationAttemptsNum; public: @@ -265,10 +310,12 @@ namespace tunnel size_t CountOutboundTunnels() const; int GetQueueSize () { return m_Queue.GetSize (); }; - int GetTunnelCreationSuccessRate () const // in percents + 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_NumSuccesiveTunnelCreations + m_NumFailedTunnelCreations; - return totalNum ? m_NumSuccesiveTunnelCreations*100/totalNum : 0; + int totalNum = m_TotalNumSuccesiveTunnelCreations + m_TotalNumFailedTunnelCreations; + return totalNum ? m_TotalNumSuccesiveTunnelCreations*100/totalNum : 0; } }; diff --git a/libi2pd/TunnelBase.h b/libi2pd/TunnelBase.h index f98066d3..d58ec2d7 100644 --- a/libi2pd/TunnelBase.h +++ b/libi2pd/TunnelBase.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -41,13 +41,13 @@ namespace tunnel { public: - TunnelBase (uint32_t tunnelID, uint32_t nextTunnelID, i2p::data::IdentHash nextIdent): + 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 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; diff --git a/libi2pd/TunnelConfig.cpp b/libi2pd/TunnelConfig.cpp new file mode 100644 index 00000000..e19b515d --- /dev/null +++ b/libi2pd/TunnelConfig.cpp @@ -0,0 +1,250 @@ +/* +* 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.SetIV (replyIV); + decryption.Decrypt(record, TUNNEL_BUILD_RECORD_SIZE, record); + } + + void ECIESTunnelHopConfig::EncryptECIES (const uint8_t * plainText, size_t len, uint8_t * encrypted) + { + if (!ident) return; + i2p::crypto::InitNoiseNState (*this, ident->GetEncryptionPublicKey ()); + auto ephemeralKeys = i2p::transport::transports.GetNextX25519KeysPair (); + memcpy (encrypted, ephemeralKeys->GetPublicKey (), 32); + MixHash (encrypted, 32); // h = SHA256(h || sepk) + encrypted += 32; + uint8_t sharedSecret[32]; + ephemeralKeys->Agree (ident->GetEncryptionPublicKey (), sharedSecret); // x25519(sesk, hepk) + MixKey (sharedSecret); + uint8_t nonce[12]; + memset (nonce, 0, 12); + if (!i2p::crypto::AEADChaCha20Poly1305 (plainText, len, m_H, 32, m_CK + 32, nonce, encrypted, len + 16, true)) // encrypt + { + LogPrint (eLogWarning, "Tunnel: Plaintext AEAD encryption failed"); + return; + } + MixHash (encrypted, len + 16); // h = SHA256(h || ciphertext) + } + + bool ECIESTunnelHopConfig::DecryptECIES (const uint8_t * key, const uint8_t * nonce, const uint8_t * encrypted, size_t len, uint8_t * clearText) const + { + return i2p::crypto::AEADChaCha20Poly1305 (encrypted, len - 16, m_H, 32, key, nonce, clearText, len - 16, false); // decrypt + } + + void LongECIESTunnelHopConfig::CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID) + { + // generate keys + RAND_bytes (layerKey, 32); + RAND_bytes (ivKey, 32); + RAND_bytes (replyKey, 32); + RAND_bytes (replyIV, 16); + // fill clear text + uint8_t flag = 0; + if (isGateway) flag |= TUNNEL_BUILD_RECORD_GATEWAY_FLAG; + if (isEndpoint) flag |= TUNNEL_BUILD_RECORD_ENDPOINT_FLAG; + uint8_t clearText[ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; + htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET, tunnelID); + htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET, nextTunnelID); + memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, nextIdent, 32); + memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET, layerKey, 32); + memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_IV_KEY_OFFSET, ivKey, 32); + memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET, replyKey, 32); + memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_IV_OFFSET, replyIV, 16); + clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] = flag; + memset (clearText + ECIES_BUILD_REQUEST_RECORD_MORE_FLAGS_OFFSET, 0, 3); // set to 0 for compatibility + htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET, i2p::util::GetMinutesSinceEpoch ()); + htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_REQUEST_EXPIRATION_OFFSET, 600); // +10 minutes + htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET, replyMsgID); + memset (clearText + ECIES_BUILD_REQUEST_RECORD_PADDING_OFFSET, 0, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE - ECIES_BUILD_REQUEST_RECORD_PADDING_OFFSET); + // encrypt + uint8_t * record = records + recordIndex*TUNNEL_BUILD_RECORD_SIZE; + EncryptECIES (clearText, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE, record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET); + memcpy (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)ident->GetIdentHash (), 16); + } + + bool LongECIESTunnelHopConfig::DecryptBuildResponseRecord (uint8_t * records) const + { + uint8_t * record = records + recordIndex*TUNNEL_BUILD_RECORD_SIZE; + uint8_t nonce[12]; + memset (nonce, 0, 12); + if (!DecryptECIES (m_CK, nonce, record, TUNNEL_BUILD_RECORD_SIZE, record)) + { + LogPrint (eLogWarning, "Tunnel: Response AEAD decryption failed"); + return false; + } + return true; + } + + void ShortECIESTunnelHopConfig::CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID) + { + // fill clear text + uint8_t flag = 0; + if (isGateway) flag |= TUNNEL_BUILD_RECORD_GATEWAY_FLAG; + if (isEndpoint) flag |= TUNNEL_BUILD_RECORD_ENDPOINT_FLAG; + uint8_t clearText[SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE ]; + htobe32buf (clearText + SHORT_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET, tunnelID); + htobe32buf (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET, nextTunnelID); + memcpy (clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, nextIdent, 32); + clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] = flag; + memset (clearText + SHORT_REQUEST_RECORD_MORE_FLAGS_OFFSET, 0, 2); + clearText[SHORT_REQUEST_RECORD_LAYER_ENCRYPTION_TYPE] = 0; // AES + htobe32buf (clearText + SHORT_REQUEST_RECORD_REQUEST_TIME_OFFSET, i2p::util::GetMinutesSinceEpoch ()); + htobe32buf (clearText + SHORT_REQUEST_RECORD_REQUEST_EXPIRATION_OFFSET , 600); // +10 minutes + htobe32buf (clearText + SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET, replyMsgID); + memset (clearText + SHORT_REQUEST_RECORD_PADDING_OFFSET, 0, SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE - SHORT_REQUEST_RECORD_PADDING_OFFSET); + // encrypt + uint8_t * record = records + recordIndex*SHORT_TUNNEL_BUILD_RECORD_SIZE; + EncryptECIES (clearText, SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE, record + SHORT_REQUEST_RECORD_ENCRYPTED_OFFSET); + // derive keys + i2p::crypto::HKDF (m_CK, nullptr, 0, "SMTunnelReplyKey", m_CK); + memcpy (replyKey, m_CK + 32, 32); + i2p::crypto::HKDF (m_CK, nullptr, 0, "SMTunnelLayerKey", m_CK); + memcpy (layerKey, m_CK + 32, 32); + if (isEndpoint) + { + i2p::crypto::HKDF (m_CK, nullptr, 0, "TunnelLayerIVKey", m_CK); + memcpy (ivKey, m_CK + 32, 32); + i2p::crypto::HKDF (m_CK, nullptr, 0, "RGarlicKeyAndTag", m_CK); // OTBRM garlic key m_CK + 32, tag first 8 bytes of m_CK + } + else + memcpy (ivKey, m_CK, 32); // last HKDF + memcpy (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)ident->GetIdentHash (), 16); + } + + bool ShortECIESTunnelHopConfig::DecryptBuildResponseRecord (uint8_t * records) const + { + uint8_t * record = records + recordIndex*SHORT_TUNNEL_BUILD_RECORD_SIZE; + uint8_t nonce[12]; + memset (nonce, 0, 12); + nonce[4] = recordIndex; // nonce is record index + if (!DecryptECIES (replyKey, nonce, record, SHORT_TUNNEL_BUILD_RECORD_SIZE, record)) + { + LogPrint (eLogWarning, "Tunnel: Response AEAD decryption failed"); + return false; + } + return true; + } + + void ShortECIESTunnelHopConfig::DecryptRecord (uint8_t * records, int index) const + { + uint8_t * record = records + index*SHORT_TUNNEL_BUILD_RECORD_SIZE; + uint8_t nonce[12]; + memset (nonce, 0, 12); + nonce[4] = index; // nonce is index + i2p::crypto::ChaCha20 (record, SHORT_TUNNEL_BUILD_RECORD_SIZE, replyKey, nonce, record); + } + + uint64_t ShortECIESTunnelHopConfig::GetGarlicKey (uint8_t * key) const + { + uint64_t tag; + memcpy (&tag, m_CK, 8); + memcpy (key, m_CK + 32, 32); + return tag; + } + + void TunnelConfig::CreatePeers (const std::vector >& peers) + { + TunnelHopConfig * prev = nullptr; + for (const auto& it: peers) + { + TunnelHopConfig * hop = nullptr; + if (m_IsShort) + hop = new ShortECIESTunnelHopConfig (it); + else + { + if (it->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) + hop = new LongECIESTunnelHopConfig (it); + else + LogPrint (eLogError, "Tunnel: ElGamal router is not supported"); + } + if (hop) + { + if (prev) + prev->SetNext (hop); + else + m_FirstHop = hop; + prev = hop; + } + } + m_LastHop = prev; + } +} +} \ No newline at end of file diff --git a/libi2pd/TunnelConfig.h b/libi2pd/TunnelConfig.h index 0bd8a842..9dcf2c02 100644 --- a/libi2pd/TunnelConfig.h +++ b/libi2pd/TunnelConfig.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -9,13 +9,10 @@ #ifndef TUNNEL_CONFIG_H__ #define TUNNEL_CONFIG_H__ -#include -#include #include -#include #include "Identity.h" #include "RouterContext.h" -#include "Timestamp.h" +#include "Crypto.h" namespace i2p { @@ -35,107 +32,74 @@ namespace tunnel TunnelHopConfig * next, * prev; int recordIndex; // record # in tunnel build message - TunnelHopConfig (std::shared_ptr r) - { - RAND_bytes (layerKey, 32); - RAND_bytes (ivKey, 32); - RAND_bytes (replyKey, 32); - RAND_bytes (replyIV, 16); - RAND_bytes ((uint8_t *)&tunnelID, 4); - if (!tunnelID) tunnelID = 1; // tunnelID can't be zero - isGateway = true; - isEndpoint = true; - ident = r; - //nextRouter = nullptr; - nextTunnelID = 0; + TunnelHopConfig (std::shared_ptr r); + virtual ~TunnelHopConfig () {}; - next = nullptr; - prev = nullptr; - } + 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); - void SetNextIdent (const i2p::data::IdentHash& ident) - { - nextIdent = ident; - isEndpoint = false; - RAND_bytes ((uint8_t *)&nextTunnelID, 4); - if (!nextTunnelID) nextTunnelID = 1; // tunnelID can't be zero - } + virtual uint8_t GetRetCode (const uint8_t * records) const = 0; + virtual void CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID) = 0; + virtual bool DecryptBuildResponseRecord (uint8_t * records) const = 0; + virtual void DecryptRecord (uint8_t * records, int index) const; // AES + virtual uint64_t GetGarlicKey (uint8_t * key) const { return 0; }; // return tag + }; - void SetReplyHop (uint32_t replyTunnelID, const i2p::data::IdentHash& replyIdent) - { - nextIdent = replyIdent; - nextTunnelID = replyTunnelID; - isEndpoint = true; - } + struct ECIESTunnelHopConfig: public TunnelHopConfig, public i2p::crypto::NoiseSymmetricState + { + ECIESTunnelHopConfig (std::shared_ptr r): + TunnelHopConfig (r) {}; + void EncryptECIES (const uint8_t * clearText, size_t len, uint8_t * encrypted); + bool DecryptECIES (const uint8_t * key, const uint8_t * nonce, const uint8_t * encrypted, size_t len, uint8_t * clearText) const; + }; - void SetNext (TunnelHopConfig * n) - { - next = n; - if (next) - { - next->prev = this; - next->isGateway = false; - isEndpoint = false; - nextIdent = next->ident->GetIdentHash (); - nextTunnelID = next->tunnelID; - } - } + struct LongECIESTunnelHopConfig: public ECIESTunnelHopConfig + { + LongECIESTunnelHopConfig (std::shared_ptr r): + ECIESTunnelHopConfig (r) {}; + uint8_t GetRetCode (const uint8_t * records) const override + { return (records + recordIndex*TUNNEL_BUILD_RECORD_SIZE)[ECIES_BUILD_RESPONSE_RECORD_RET_OFFSET]; }; + void CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID) override; + bool DecryptBuildResponseRecord (uint8_t * records) const override; + }; - void SetPrev (TunnelHopConfig * p) - { - prev = p; - if (prev) - { - prev->next = this; - prev->isEndpoint = false; - isGateway = false; - } - } - - void CreateBuildRequestRecord (uint8_t * record, uint32_t replyMsgID, BN_CTX * ctx) const - { - uint8_t clearText[BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; - htobe32buf (clearText + BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET, tunnelID); - memcpy (clearText + BUILD_REQUEST_RECORD_OUR_IDENT_OFFSET, ident->GetIdentHash (), 32); - htobe32buf (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET, nextTunnelID); - memcpy (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, nextIdent, 32); - memcpy (clearText + BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET, layerKey, 32); - memcpy (clearText + BUILD_REQUEST_RECORD_IV_KEY_OFFSET, ivKey, 32); - memcpy (clearText + BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET, replyKey, 32); - memcpy (clearText + BUILD_REQUEST_RECORD_REPLY_IV_OFFSET, replyIV, 16); - uint8_t flag = 0; - if (isGateway) flag |= 0x80; - if (isEndpoint) flag |= 0x40; - clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] = flag; - htobe32buf (clearText + BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET, i2p::util::GetHoursSinceEpoch ()); - htobe32buf (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET, replyMsgID); - RAND_bytes (clearText + BUILD_REQUEST_RECORD_PADDING_OFFSET, BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE - BUILD_REQUEST_RECORD_PADDING_OFFSET); - auto encryptor = ident->CreateEncryptor (nullptr); - if (encryptor) - encryptor->Encrypt (clearText, record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, ctx, false); - memcpy (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)ident->GetIdentHash (), 16); - } + 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 (std::vector > peers) // inbound + 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 (std::vector > peers, - uint32_t replyTunnelID, const i2p::data::IdentHash& replyIdent) // outbound + 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); } - ~TunnelConfig () + virtual ~TunnelConfig () { TunnelHopConfig * hop = m_FirstHop; @@ -147,6 +111,13 @@ namespace tunnel } } + bool IsShort () const { return m_IsShort; } + + i2p::data::RouterInfo::CompatibleTransports GetFarEndTransports () const + { + return m_FarEndTransports; + } + TunnelHopConfig * GetFirstHop () const { return m_FirstHop; @@ -213,31 +184,20 @@ namespace tunnel protected: // this constructor can't be called from outside - TunnelConfig (): m_FirstHop (nullptr), m_LastHop (nullptr) + TunnelConfig (): m_FirstHop (nullptr), m_LastHop (nullptr), m_IsShort (false), + m_FarEndTransports (i2p::data::RouterInfo::eAllTransports) { } private: - template - void CreatePeers (const Peers& peers) - { - TunnelHopConfig * prev = nullptr; - for (const auto& it: peers) - { - auto hop = new TunnelHopConfig (it); - if (prev) - prev->SetNext (hop); - else - m_FirstHop = hop; - prev = hop; - } - m_LastHop = prev; - } + 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 diff --git a/libi2pd/TunnelEndpoint.cpp b/libi2pd/TunnelEndpoint.cpp index eb70bdca..3dc0dc07 100644 --- a/libi2pd/TunnelEndpoint.cpp +++ b/libi2pd/TunnelEndpoint.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -30,7 +30,7 @@ namespace tunnel 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); // witout 4-byte checksum + 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; @@ -40,7 +40,7 @@ namespace tunnel SHA256(fragment, TUNNEL_DATA_MSG_SIZE -(fragment - msg->GetPayload ()) + 16, hash); // payload + iv if (memcmp (hash, decrypted, 4)) { - LogPrint (eLogError, "TunnelMessage: checksum verification failed"); + LogPrint (eLogError, "TunnelMessage: Checksum verification failed"); return; } // process fragments @@ -52,24 +52,25 @@ namespace tunnel bool isFollowOnFragment = flag & 0x80, isLastFragment = true; uint32_t msgID = 0; int fragmentNum = 0; - TunnelMessageBlockEx m; if (!isFollowOnFragment) { // first fragment + if (m_CurrentMsgID) + AddIncompleteCurrentMessage (); // we have got a new message while previous is not complete - m.deliveryType = (TunnelDeliveryType)((flag >> 5) & 0x03); - switch (m.deliveryType) + m_CurrentMessage.deliveryType = (TunnelDeliveryType)((flag >> 5) & 0x03); + switch (m_CurrentMessage.deliveryType) { case eDeliveryTypeLocal: // 0 break; case eDeliveryTypeTunnel: // 1 - m.tunnelID = bufbe32toh (fragment); + m_CurrentMessage.tunnelID = bufbe32toh (fragment); fragment += 4; // tunnelID - m.hash = i2p::data::IdentHash (fragment); + m_CurrentMessage.hash = i2p::data::IdentHash (fragment); fragment += 32; // hash break; case eDeliveryTypeRouter: // 2 - m.hash = i2p::data::IdentHash (fragment); + m_CurrentMessage.hash = i2p::data::IdentHash (fragment); fragment += 32; // to hash break; default: ; @@ -81,6 +82,7 @@ namespace tunnel // Message ID msgID = bufbe32toh (fragment); fragment += 4; + m_CurrentMsgID = msgID; isLastFragment = false; } } @@ -96,78 +98,78 @@ namespace tunnel uint16_t size = bufbe16toh (fragment); fragment += 2; - msg->offset = fragment - msg->buf; - msg->len = msg->offset + size; - if (msg->len > msg->maxLen) + // handle fragment + if (isFollowOnFragment) { - LogPrint (eLogError, "TunnelMessage: fragment is too long ", (int)size); - return; - } - if (fragment + size < decrypted + TUNNEL_DATA_ENCRYPTED_SIZE) - { - // this is not last message. we have to copy it - m.data = NewI2NPTunnelMessage (); - m.data->offset += TUNNEL_GATEWAY_HEADER_SIZE; // reserve room for TunnelGateway header - m.data->len += TUNNEL_GATEWAY_HEADER_SIZE; - *(m.data) = *msg; - } - else - m.data = msg; - - if (!isFollowOnFragment && isLastFragment) - HandleNextMessage (m); - else - { - if (msgID) // msgID is presented, assume message is fragmented + // existing message + if (m_CurrentMsgID && m_CurrentMsgID == msgID && m_CurrentMessage.nextFragmentNum == fragmentNum) + HandleCurrenMessageFollowOnFragment (fragment, size, isLastFragment); // previous + else { - if (!isFollowOnFragment) // create new incomlete message - { - m.nextFragmentNum = 1; - m.receiveTime = i2p::util::GetMillisecondsSinceEpoch (); - auto ret = m_IncompleteMessages.insert (std::pair(msgID, m)); - if (ret.second) - HandleOutOfSequenceFragments (msgID, ret.first->second); - else - LogPrint (eLogError, "TunnelMessage: Incomplete message ", msgID, " already exists"); - } - else - { - m.nextFragmentNum = fragmentNum; - HandleFollowOnFragment (msgID, isLastFragment, m); - } + 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"); + LogPrint (eLogError, "TunnelMessage: Zero not found"); } - void TunnelEndpoint::HandleFollowOnFragment (uint32_t msgID, bool isLastFragment, const TunnelMessageBlockEx& m) + void TunnelEndpoint::HandleFollowOnFragment (uint32_t msgID, bool isLastFragment, + uint8_t fragmentNum, const uint8_t * fragment, size_t size) { - auto fragment = m.data->GetBuffer (); - auto size = m.data->GetLength (); auto it = m_IncompleteMessages.find (msgID); if (it != m_IncompleteMessages.end()) { auto& msg = it->second; - if (m.nextFragmentNum == msg.nextFragmentNum) + if (fragmentNum == msg.nextFragmentNum) { - if (msg.data->len + size < I2NP_MAX_MESSAGE_SIZE) // check if message is not too long + if (ConcatFollowOnFragment (msg, fragment, size)) { - if (msg.data->len + size > msg.data->maxLen) - { - // LogPrint (eLogWarning, "TunnelMessage: I2NP message size ", msg.data->maxLen, " is not enough"); - auto newMsg = NewI2NPMessage (); - *newMsg = *(msg.data); - msg.data = newMsg; - } - if (msg.data->Concat (fragment, size) < size) // concatenate fragment - LogPrint (eLogError, "TunnelMessage: I2NP buffer overflow ", msg.data->maxLen); if (isLastFragment) { // message complete @@ -182,27 +184,87 @@ namespace tunnel } else { - LogPrint (eLogError, "TunnelMessage: Fragment ", m.nextFragmentNum, " of message ", msgID, "exceeds max I2NP message size, message dropped"); + 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)m.nextFragmentNum, " instead ", (int)msg.nextFragmentNum, " of message ", msgID, ", saved"); - AddOutOfSequenceFragment (msgID, m.nextFragmentNum, isLastFragment, m.data); + LogPrint (eLogWarning, "TunnelMessage: Unexpected fragment ", (int)fragmentNum, " instead ", (int)msg.nextFragmentNum, " of message ", msgID, ", saved"); + AddOutOfSequenceFragment (msgID, fragmentNum, isLastFragment, fragment, size); } } else { - LogPrint (eLogWarning, "TunnelMessage: First fragment of message ", msgID, " not found, saved"); - AddOutOfSequenceFragment (msgID, m.nextFragmentNum, isLastFragment, m.data); + LogPrint (eLogDebug, "TunnelMessage: First fragment of message ", msgID, " not found, saved"); + AddOutOfSequenceFragment (msgID, fragmentNum, isLastFragment, fragment, size); } } - void TunnelEndpoint::AddOutOfSequenceFragment (uint32_t msgID, uint8_t fragmentNum, bool isLastFragment, std::shared_ptr data) + bool TunnelEndpoint::ConcatFollowOnFragment (TunnelMessageBlockEx& msg, const uint8_t * fragment, size_t size) const { - if (!m_OutOfSequenceFragments.insert ({{msgID, fragmentNum}, {isLastFragment, data, i2p::util::GetMillisecondsSinceEpoch () }}).second) - LogPrint (eLogInfo, "TunnelMessage: duplicate out-of-sequence fragment ", fragmentNum, " of message ", msgID); + 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) + { + std::unique_ptr f(new Fragment (isLastFragment, i2p::util::GetMillisecondsSinceEpoch (), size)); + memcpy (f->data.data (), fragment, size); + if (!m_OutOfSequenceFragments.emplace ((uint64_t)msgID << 32 | fragmentNum, std::move (f)).second) + LogPrint (eLogInfo, "TunnelMessage: Duplicate out-of-sequence fragment ", fragmentNum, " of message ", msgID); } void TunnelEndpoint::HandleOutOfSequenceFragments (uint32_t msgID, TunnelMessageBlockEx& msg) @@ -212,7 +274,14 @@ namespace tunnel if (!msg.nextFragmentNum) // message complete { HandleNextMessage (msg); - m_IncompleteMessages.erase (msgID); + 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; } } @@ -220,21 +289,21 @@ namespace tunnel bool TunnelEndpoint::ConcatNextOutOfSequenceFragment (uint32_t msgID, TunnelMessageBlockEx& msg) { - auto it = m_OutOfSequenceFragments.find ({msgID, msg.nextFragmentNum}); + 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->GetLength (); + size_t size = it->second->data.size (); if (msg.data->len + size > msg.data->maxLen) { LogPrint (eLogWarning, "TunnelMessage: Tunnel endpoint I2NP message size ", msg.data->maxLen, " is not enough"); - auto newMsg = NewI2NPMessage (); + auto newMsg = NewI2NPMessage (msg.data->len + size); *newMsg = *(msg.data); msg.data = newMsg; } - if (msg.data->Concat (it->second.data->GetBuffer (), size) < size) // concatenate out-of-sync fragment + if (msg.data->Concat (it->second->data.data (), size) < size) // concatenate out-of-sync fragment LogPrint (eLogError, "TunnelMessage: Tunnel endpoint I2NP buffer overflow ", msg.data->maxLen); - if (it->second.isLastFragment) + if (it->second->isLastFragment) // message complete msg.nextFragmentNum = 0; else @@ -249,16 +318,12 @@ namespace tunnel { if (!m_IsInbound && msg.data->IsExpired ()) { - LogPrint (eLogInfo, "TunnelMessage: message expired"); + LogPrint (eLogInfo, "TunnelMessage: Message expired"); return; } uint8_t typeID = msg.data->GetTypeID (); - LogPrint (eLogDebug, "TunnelMessage: handle fragment of ", msg.data->GetLength (), " bytes, msg type ", (int)typeID); - // catch RI or reply with new list of routers - if ((IsRouterInfoMsg (msg.data) || typeID == eI2NPDatabaseSearchReply) && - !m_IsInbound && msg.deliveryType != eDeliveryTypeLocal) - i2p::data::netdb.PostI2NPMsg (CopyI2NPMessage (msg.data)); - + LogPrint (eLogDebug, "TunnelMessage: Handle fragment of ", msg.data->GetLength (), " bytes, msg type ", (int)typeID); + switch (msg.deliveryType) { case eDeliveryTypeLocal: @@ -287,7 +352,7 @@ namespace tunnel // out-of-sequence fragments for (auto it = m_OutOfSequenceFragments.begin (); it != m_OutOfSequenceFragments.end ();) { - if (ts > it->second.receiveTime + i2p::I2NP_MESSAGE_EXPIRATION_TIMEOUT) + if (ts > it->second->receiveTime + i2p::I2NP_MESSAGE_EXPIRATION_TIMEOUT) it = m_OutOfSequenceFragments.erase (it); else ++it; diff --git a/libi2pd/TunnelEndpoint.h b/libi2pd/TunnelEndpoint.h index 43b836f1..17590a5f 100644 --- a/libi2pd/TunnelEndpoint.h +++ b/libi2pd/TunnelEndpoint.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -10,8 +10,9 @@ #define TUNNEL_ENDPOINT_H__ #include -#include +#include #include +#include #include "I2NPProtocol.h" #include "TunnelBase.h" @@ -29,14 +30,15 @@ namespace tunnel struct Fragment { + Fragment (bool last, uint64_t t, size_t size): isLastFragment (last), receiveTime (t), data (size) {}; bool isLastFragment; - std::shared_ptr data; uint64_t receiveTime; // milliseconds since epoch + std::vector data; }; public: - TunnelEndpoint (bool isInbound): m_IsInbound (isInbound), m_NumReceivedBytes (0) {}; + TunnelEndpoint (bool isInbound): m_IsInbound (isInbound), m_NumReceivedBytes (0), m_CurrentMsgID (0) {}; ~TunnelEndpoint (); size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; }; void Cleanup (); @@ -45,19 +47,24 @@ namespace tunnel private: - void HandleFollowOnFragment (uint32_t msgID, bool isLastFragment, const TunnelMessageBlockEx& m); + 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 AddOutOfSequenceFragment (uint32_t msgID, uint8_t fragmentNum, bool isLastFragment, std::shared_ptr data); + 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::map m_IncompleteMessages; - std::map, Fragment> m_OutOfSequenceFragments; // (msgID, fragment#)->fragment + 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; }; } } diff --git a/libi2pd/TunnelGateway.cpp b/libi2pd/TunnelGateway.cpp index 317926ae..12e7652f 100644 --- a/libi2pd/TunnelGateway.cpp +++ b/libi2pd/TunnelGateway.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -19,16 +19,14 @@ namespace i2p namespace tunnel { TunnelGatewayBuffer::TunnelGatewayBuffer (): - m_CurrentTunnelDataMsg (nullptr), m_RemainingSize (0) + m_CurrentTunnelDataMsg (nullptr), m_RemainingSize (0), m_NonZeroRandomBuffer (nullptr) { - RAND_bytes (m_NonZeroRandomBuffer, TUNNEL_DATA_MAX_PAYLOAD_SIZE); - for (size_t i = 0; i < TUNNEL_DATA_MAX_PAYLOAD_SIZE; i++) - if (!m_NonZeroRandomBuffer[i]) m_NonZeroRandomBuffer[i] = 1; } TunnelGatewayBuffer::~TunnelGatewayBuffer () { ClearTunnelDataMsgs (); + if (m_NonZeroRandomBuffer) delete[] m_NonZeroRandomBuffer; } void TunnelGatewayBuffer::PutI2NPMsg (const TunnelMessageBlock& block) @@ -158,8 +156,7 @@ namespace tunnel void TunnelGatewayBuffer::CreateCurrentTunnelDataMessage () { m_CurrentTunnelDataMsg = nullptr; - m_CurrentTunnelDataMsg = NewI2NPShortMessage (); - m_CurrentTunnelDataMsg->Align (12); + 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; @@ -184,6 +181,13 @@ namespace tunnel 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); } @@ -215,7 +219,7 @@ namespace tunnel const auto& tunnelDataMsgs = m_Buffer.GetTunnelDataMsgs (); for (auto& tunnelMsg : tunnelDataMsgs) { - auto newMsg = CreateEmptyTunnelDataMsg (); + auto newMsg = CreateEmptyTunnelDataMsg (false); m_Tunnel->EncryptTunnelMsg (tunnelMsg, newMsg); htobe32buf (newMsg->GetPayload (), m_Tunnel->GetNextTunnelID ()); newMsg->FillI2NPMessageHeader (eI2NPTunnelData); diff --git a/libi2pd/TunnelGateway.h b/libi2pd/TunnelGateway.h index 01101a36..741bbe84 100644 --- a/libi2pd/TunnelGateway.h +++ b/libi2pd/TunnelGateway.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -38,7 +38,7 @@ namespace tunnel std::vector > m_TunnelDataMsgs; std::shared_ptr m_CurrentTunnelDataMsg; size_t m_RemainingSize; - uint8_t m_NonZeroRandomBuffer[TUNNEL_DATA_MAX_PAYLOAD_SIZE]; + uint8_t * m_NonZeroRandomBuffer; }; class TunnelGateway diff --git a/libi2pd/TunnelPool.cpp b/libi2pd/TunnelPool.cpp index d0fd401f..23cc53e3 100644 --- a/libi2pd/TunnelPool.cpp +++ b/libi2pd/TunnelPool.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -24,11 +24,42 @@ namespace i2p { namespace tunnel { - TunnelPool::TunnelPool (int numInboundHops, int numOutboundHops, int numInboundTunnels, int numOutboundTunnels): - m_NumInboundHops (numInboundHops), m_NumOutboundHops (numOutboundHops), - m_NumInboundTunnels (numInboundTunnels), m_NumOutboundTunnels (numOutboundTunnels), m_IsActive (true), - m_CustomPeerSelector(nullptr) + 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): + m_NumInboundHops (numInboundHops), m_NumOutboundHops (numOutboundHops), + m_NumInboundTunnels (numInboundTunnels), m_NumOutboundTunnels (numOutboundTunnels), + m_InboundVariance (inboundVariance), m_OutboundVariance (outboundVariance), + m_IsActive (true), m_CustomPeerSelector(nullptr) + { + if (m_NumInboundTunnels > TUNNEL_POOL_MAX_INBOUND_TUNNELS_QUANTITY) + m_NumInboundTunnels = TUNNEL_POOL_MAX_INBOUND_TUNNELS_QUANTITY; + if (m_NumOutboundTunnels > TUNNEL_POOL_MAX_OUTBOUND_TUNNELS_QUANTITY) + m_NumOutboundTunnels = TUNNEL_POOL_MAX_OUTBOUND_TUNNELS_QUANTITY; + if (m_InboundVariance < 0 && m_NumInboundHops + m_InboundVariance <= 0) + m_InboundVariance = m_NumInboundHops ? -m_NumInboundHops + 1 : 0; + if (m_OutboundVariance < 0 && m_NumOutboundHops + m_OutboundVariance <= 0) + m_OutboundVariance = m_NumOutboundHops ? -m_NumOutboundHops + 1 : 0; + if (m_InboundVariance > 0 && m_NumInboundHops + m_InboundVariance > STANDARD_NUM_RECORDS) + m_InboundVariance = (m_NumInboundHops < STANDARD_NUM_RECORDS) ? STANDARD_NUM_RECORDS - m_NumInboundHops : 0; + if (m_OutboundVariance > 0 && m_NumOutboundHops + m_OutboundVariance > STANDARD_NUM_RECORDS) + m_OutboundVariance = (m_NumOutboundHops < STANDARD_NUM_RECORDS) ? STANDARD_NUM_RECORDS - m_NumOutboundHops : 0; + m_NextManageTime = i2p::util::GetSecondsSinceEpoch () + rand () % TUNNEL_POOL_MANAGE_INTERVAL; } TunnelPool::~TunnelPool () @@ -45,12 +76,12 @@ namespace tunnel if (m_NumInboundHops > size) { m_NumInboundHops = size; - LogPrint (eLogInfo, "Tunnels: Inbound tunnel length has beed adjusted to ", size, " for explicit peers"); + 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 beed adjusted to ", size, " for explicit peers"); + LogPrint (eLogInfo, "Tunnels: Outbound tunnel length has been adjusted to ", size, " for explicit peers"); } m_NumInboundTunnels = 1; m_NumOutboundTunnels = 1; @@ -92,6 +123,17 @@ namespace tunnel 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) @@ -118,7 +160,6 @@ namespace tunnel std::unique_lock l(m_OutboundTunnelsMutex); m_OutboundTunnels.insert (createdTunnel); } - //CreatePairedInboundTunnel (createdTunnel); } void TunnelPool::TunnelExpired (std::shared_ptr expiredTunnel) @@ -138,43 +179,57 @@ namespace tunnel { std::vector > v; int i = 0; + std::shared_ptr slowTunnel; std::unique_lock l(m_InboundTunnelsMutex); for (const auto& it : m_InboundTunnels) { if (i >= num) break; if (it->IsEstablished ()) { - v.push_back (it); - i++; + 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) const + std::shared_ptr TunnelPool::GetNextOutboundTunnel (std::shared_ptr excluded, + i2p::data::RouterInfo::CompatibleTransports compatible) const { std::unique_lock l(m_OutboundTunnelsMutex); - return GetNextTunnel (m_OutboundTunnels, excluded); + return GetNextTunnel (m_OutboundTunnels, excluded, compatible); } - std::shared_ptr TunnelPool::GetNextInboundTunnel (std::shared_ptr excluded) const + std::shared_ptr TunnelPool::GetNextInboundTunnel (std::shared_ptr excluded, + i2p::data::RouterInfo::CompatibleTransports compatible) const { std::unique_lock l(m_InboundTunnelsMutex); - return GetNextTunnel (m_InboundTunnels, excluded); + return GetNextTunnel (m_InboundTunnels, excluded, compatible); } template - typename TTunnels::value_type TunnelPool::GetNextTunnel (TTunnels& tunnels, typename TTunnels::value_type excluded) const + typename TTunnels::value_type TunnelPool::GetNextTunnel (TTunnels& tunnels, + typename TTunnels::value_type excluded, i2p::data::RouterInfo::CompatibleTransports compatible) const { if (tunnels.empty ()) return nullptr; uint32_t ind = rand () % (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) + if (it->IsEstablished () && it != excluded && (compatible & it->GetFarEndTransports ())) { - if(HasLatencyRequirement() && it->LatencyIsKnown() && !it->LatencyFitsRange(m_MinLatency, m_MaxLatency)) { - i ++; + if (it->IsSlow () || (HasLatencyRequirement() && it->LatencyIsKnown() && + !it->LatencyFitsRange(m_MinLatency, m_MaxLatency))) + { + i++; skipped = true; continue; } tunnel = it; @@ -182,7 +237,8 @@ namespace tunnel } if (i > ind && tunnel) break; } - if(HasLatencyRequirement() && !tunnel) { + if (!tunnel && skipped) + { ind = rand () % (tunnels.size ()/2 + 1), i = 0; for (const auto& it: tunnels) { @@ -226,8 +282,13 @@ namespace tunnel for (const auto& it : m_OutboundTunnels) if (it->IsEstablished ()) num++; } - for (int i = num; i < m_NumOutboundTunnels; i++) - CreateOutboundTunnel (); + 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; { @@ -235,8 +296,24 @@ namespace tunnel for (const auto& it : m_InboundTunnels) if (it->IsEstablished ()) num++; } - for (int i = num; i < m_NumInboundTunnels; i++) - CreateInboundTunnel (); + 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 succesive 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 (); // update LeaseSet immediately @@ -252,7 +329,7 @@ namespace tunnel for (auto& it: tests) { - LogPrint (eLogWarning, "Tunnels: test of tunnel ", it.first, " failed"); + LogPrint (eLogWarning, "Tunnels: Test of tunnel ", it.first, " failed"); // if test failed again with another tunnel we consider it failed if (it.second.first) { @@ -283,7 +360,9 @@ namespace tunnel } // new tests + std::unique_lock l1(m_OutboundTunnelsMutex); auto it1 = m_OutboundTunnels.begin (); + std::unique_lock l2(m_InboundTunnelsMutex); auto it2 = m_InboundTunnels.begin (); while (it1 != m_OutboundTunnels.end () && it2 != m_InboundTunnels.end ()) { @@ -306,19 +385,29 @@ namespace tunnel std::unique_lock l(m_TestsMutex); m_Tests[msgID] = std::make_pair (*it1, *it2); } - (*it1)->SendTunnelDataMsg ((*it2)->GetNextIdentHash (), (*it2)->GetNextTunnelID (), + (*it1)->SendTunnelDataMsgTo ((*it2)->GetNextIdentHash (), (*it2)->GetNextTunnelID (), CreateDeliveryStatusMsg (msgID)); ++it1; ++it2; } } } + 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 + (rand () % 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"); + LogPrint (eLogWarning, "Tunnels: Local destination doesn't exist, dropped"); } void TunnelPool::ProcessDeliveryStatus (std::shared_ptr msg) @@ -342,17 +431,32 @@ namespace tunnel } if (found) { - // restore from test failed state if any - if (test.first->GetState () == eTunnelStateTestFailed) - test.first->SetState (eTunnelStateEstablished); - if (test.second->GetState () == eTunnelStateTestFailed) - test.second->SetState (eTunnelStateEstablished); uint64_t dlt = i2p::util::GetMillisecondsSinceEpoch () - timestamp; - LogPrint (eLogDebug, "Tunnels: test of ", msgID, " successful. ", dlt, " milliseconds"); - // update latency - uint64_t latency = dlt / 2; - test.first->AddLatencySample(latency); - test.second->AddLatencySample(latency); + LogPrint (eLogDebug, "Tunnels: Test of ", msgID, " successful. ", dlt, " milliseconds"); + int numHops = 0; + if (test.first) numHops += test.first->GetNumHops (); + if (test.second) numHops += test.second->GetNumHops (); + // restore from test failed state if any + if (test.first) + { + if (test.first->GetState () == eTunnelStateTestFailed) + test.first->SetState (eTunnelStateEstablished); + // update latency + uint64_t 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 () == eTunnelStateTestFailed) + test.second->SetState (eTunnelStateEstablished); + // update latency + uint64_t latency = 0; + if (numHops) latency = dlt*test.second->GetNumHops ()/numHops; + if (!latency) latency = dlt/2; + test.second->AddLatencySample(latency); + } } else { @@ -363,83 +467,130 @@ namespace tunnel } } - std::shared_ptr TunnelPool::SelectNextHop (std::shared_ptr prevHop) const + bool TunnelPool::IsExploratory () const { - bool isExploratory = (i2p::tunnel::tunnels.GetExploratoryPool () == shared_from_this ()); - auto hop = isExploratory ? i2p::data::netdb.GetRandomRouter (prevHop): - i2p::data::netdb.GetHighBandwidthRandomRouter (prevHop); + return i2p::tunnel::tunnels.GetExploratoryPool () == shared_from_this (); + } + + std::shared_ptr TunnelPool::SelectNextHop (std::shared_ptr prevHop, + bool reverse, bool endpoint) const + { + auto hop = IsExploratory () ? i2p::data::netdb.GetRandomRouter (prevHop, reverse, endpoint): + i2p::data::netdb.GetHighBandwidthRandomRouter (prevHop, reverse, endpoint); if (!hop || hop->GetProfile ()->IsBad ()) - hop = i2p::data::netdb.GetRandomRouter (prevHop); + hop = i2p::data::netdb.GetRandomRouter (prevHop, reverse, endpoint); return hop; } - bool StandardSelectPeers(Path & peers, int numHops, bool inbound, SelectHopFunc nextHop) + bool TunnelPool::StandardSelectPeers(Path & path, int numHops, bool inbound, SelectHopFunc nextHop) { - auto prevHop = i2p::context.GetSharedRouterInfo (); + 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; - peers.push_back(hop->GetRouterIdentity()); + path.Add (hop); prevHop = hop; + start++; } - else if (i2p::transport::transports.GetNumPeers () > 25) + else if (i2p::transport::transports.GetNumPeers () > 100 || + (inbound && i2p::transport::transports.GetNumPeers () > 25)) { - auto r = i2p::transport::transports.GetRandomPeer (); - if (r && !r->GetProfile ()->IsBad ()) + auto r = i2p::transport::transports.GetRandomPeer (!IsExploratory ()); + if (r && r->IsECIES () && !r->GetProfile ()->IsBad () && + (numHops > 1 || (r->IsV4 () && (!inbound || r->IsPublished (true))))) // first inbound must be published ipv4 { prevHop = r; - peers.push_back (r->GetRouterIdentity ()); - numHops--; + path.Add (r); + start++; } } - for(int i = 0; i < numHops; i++ ) + for(int i = start; i < numHops; i++ ) { - auto hop = nextHop (prevHop); + 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; - peers.push_back (hop->GetRouterIdentity ()); + path.Add (hop); } + path.farEndTransports = prevHop->GetCompatibleTransports (inbound); // last hop return true; } - bool TunnelPool::SelectPeers (std::vector >& peers, bool isInbound) + bool TunnelPool::SelectPeers (Path& path, bool isInbound) { - int numHops = isInbound ? m_NumInboundHops : m_NumOutboundHops; + // 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 = rand () % (std::abs (m_InboundVariance) + 1); + if (m_InboundVariance < 0) offset = -offset; + numHops += offset; + } + } + else + { + numHops = m_NumOutboundHops; + if (m_OutboundVariance) + { + int offset = rand () % (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(peers, numHops, isInbound); + return m_CustomPeerSelector->SelectPeers(path, numHops, isInbound); } - // explicit peers in use - if (m_ExplicitPeers) return SelectExplicitPeers (peers, isInbound); - return StandardSelectPeers(peers, numHops, isInbound, std::bind(&TunnelPool::SelectNextHop, this, std::placeholders::_1)); + return StandardSelectPeers(path, numHops, isInbound, std::bind(&TunnelPool::SelectNextHop, this, + std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); } - bool TunnelPool::SelectExplicitPeers (std::vector >& peers, bool isInbound) + bool TunnelPool::SelectExplicitPeers (Path& path, bool isInbound) { - int size = m_ExplicitPeers->size (); - std::vector peerIndicies; - for (int i = 0; i < size; i++) peerIndicies.push_back(i); - std::shuffle (peerIndicies.begin(), peerIndicies.end(), std::mt19937(std::random_device()())); - + 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)[peerIndicies[i]]; + auto& ident = (*m_ExplicitPeers)[i]; auto r = i2p::data::netdb.FindRouter (ident); if (r) - peers.push_back (r->GetRouterIdentity ()); + { + 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 ()); @@ -452,21 +603,20 @@ namespace tunnel void TunnelPool::CreateInboundTunnel () { - auto outboundTunnel = GetNextOutboundTunnel (); - if (!outboundTunnel) - outboundTunnel = tunnels.GetNextOutboundTunnel (); LogPrint (eLogDebug, "Tunnels: Creating destination inbound tunnel..."); - std::vector > peers; - if (SelectPeers (peers, true)) + 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) { - std::reverse (peers.begin (), peers.end ()); - config = std::make_shared (peers); + path.Reverse (); + config = std::make_shared (path.peers, path.isShort, path.farEndTransports); } - auto tunnel = tunnels.CreateInboundTunnel (config, outboundTunnel); - tunnel->SetTunnelPool (shared_from_this ()); + auto tunnel = tunnels.CreateInboundTunnel (config, shared_from_this (), outboundTunnel); if (tunnel->IsEstablished ()) // zero hops TunnelCreated (tunnel); } @@ -476,67 +626,96 @@ namespace tunnel void TunnelPool::RecreateInboundTunnel (std::shared_ptr tunnel) { - auto outboundTunnel = GetNextOutboundTunnel (); + 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 && tunnel->GetPeers().size()) + if (m_NumInboundHops > 0) { - config = std::make_shared(tunnel->GetPeers ()); - } - if (m_NumInboundHops == 0 || config) + 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, outboundTunnel); - newTunnel->SetTunnelPool (shared_from_this()); + auto newTunnel = tunnels.CreateInboundTunnel (config, shared_from_this(), outboundTunnel); if (newTunnel->IsEstablished ()) // zero hops TunnelCreated (newTunnel); + else + newTunnel->SetRecreated (true); } } void TunnelPool::CreateOutboundTunnel () { - auto inboundTunnel = GetNextInboundTunnel (); - if (!inboundTunnel) - inboundTunnel = tunnels.GetNextInboundTunnel (); - if (inboundTunnel) + LogPrint (eLogDebug, "Tunnels: Creating destination outbound tunnel..."); + Path path; + if (SelectPeers (path, false)) { - LogPrint (eLogDebug, "Tunnels: Creating destination outbound tunnel..."); - std::vector > peers; - if (SelectPeers (peers, false)) + auto inboundTunnel = GetNextInboundTunnel (nullptr, path.farEndTransports); + if (!inboundTunnel) + inboundTunnel = tunnels.GetNextInboundTunnel (); + if (!inboundTunnel) { - std::shared_ptr config; - if (m_NumOutboundHops > 0) - config = std::make_shared(peers, inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash ()); - auto tunnel = tunnels.CreateOutboundTunnel (config); + 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 ()); - if (tunnel->IsEstablished ()) // zero hops - TunnelCreated (tunnel); } else - LogPrint (eLogError, "Tunnels: Can't create outbound tunnel, no peers available"); + 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 inbound tunnels found"); + LogPrint (eLogError, "Tunnels: Can't create outbound tunnel, no peers available"); } void TunnelPool::RecreateOutboundTunnel (std::shared_ptr tunnel) { - auto inboundTunnel = GetNextInboundTunnel (); + 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 && tunnel->GetPeers().size()) + if (m_NumOutboundHops > 0) { - config = std::make_shared(tunnel->GetPeers (), inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash ()); + 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 == 0 || config) + if (!m_NumOutboundHops || config) { - auto newTunnel = tunnels.CreateOutboundTunnel (config); - newTunnel->SetTunnelPool (shared_from_this ()); + auto newTunnel = tunnels.CreateOutboundTunnel (config, shared_from_this ()); if (newTunnel->IsEstablished ()) // zero hops TunnelCreated (newTunnel); } @@ -548,8 +727,12 @@ namespace tunnel void TunnelPool::CreatePairedInboundTunnel (std::shared_ptr outboundTunnel) { LogPrint (eLogDebug, "Tunnels: Creating paired inbound tunnel..."); - auto tunnel = tunnels.CreateInboundTunnel (std::make_shared(outboundTunnel->GetInvertedPeers ()), outboundTunnel); - tunnel->SetTunnelPool (shared_from_this ()); + 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) @@ -569,6 +752,21 @@ namespace tunnel return m_CustomPeerSelector != nullptr; } + bool TunnelPool::ValidatePeers (std::vector >& peers) const + { + bool highBandwidth = !IsExploratory (); + for (auto it: peers) + { + auto r = i2p::data::netdb.FindRouter (it->GetIdentHash ()); + if (r) + { + if (r->IsHighCongestion (highBandwidth)) return false; + it = r->GetIdentity (); // use identity from updated RouterInfo + } + } + return true; + } + std::shared_ptr TunnelPool::GetLowestLatencyInboundTunnel(std::shared_ptr exclude) const { std::shared_ptr tun = nullptr; diff --git a/libi2pd/TunnelPool.h b/libi2pd/TunnelPool.h index 04ff4ae7..c1fd19cd 100644 --- a/libi2pd/TunnelPool.h +++ b/libi2pd/TunnelPool.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -27,12 +27,25 @@ 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 = 2; + class Tunnel; class InboundTunnel; class OutboundTunnel; typedef std::shared_ptr Peer; - typedef std::vector Path; + 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 @@ -41,16 +54,13 @@ namespace tunnel virtual bool SelectPeers(Path & peers, int hops, bool isInbound) = 0; }; - - typedef std::function(std::shared_ptr)> SelectHopFunc; - // standard peer selection algorithm - bool StandardSelectPeers(Path & path, int hops, bool inbound, SelectHopFunc nextHop); - class TunnelPool: public std::enable_shared_from_this // per local destination { + typedef std::function(std::shared_ptr, bool, bool)> SelectHopFunc; public: - TunnelPool (int numInboundHops, int numOutboundHops, int numInboundTunnels, int numOutboundTunnels); + TunnelPool (int numInboundHops, int numOutboundHops, int numInboundTunnels, + int numOutboundTunnels, int inboundVariance, int outboundVariance); ~TunnelPool (); std::shared_ptr GetLocalDestination () const { return m_LocalDestination; }; @@ -65,13 +75,16 @@ namespace tunnel void RecreateInboundTunnel (std::shared_ptr tunnel); void RecreateOutboundTunnel (std::shared_ptr tunnel); std::vector > GetInboundTunnels (int num) const; - std::shared_ptr GetNextOutboundTunnel (std::shared_ptr excluded = nullptr) const; - std::shared_ptr GetNextInboundTunnel (std::shared_ptr excluded = nullptr) const; + std::shared_ptr GetNextOutboundTunnel (std::shared_ptr excluded = nullptr, + i2p::data::RouterInfo::CompatibleTransports compatible = i2p::data::RouterInfo::eAllTransports) const; + std::shared_ptr GetNextInboundTunnel (std::shared_ptr excluded = nullptr, + i2p::data::RouterInfo::CompatibleTransports compatible = i2p::data::RouterInfo::eAllTransports) const; std::shared_ptr GetNewOutboundTunnel (std::shared_ptr old) const; - void TestTunnels (); + void ManageTunnels (uint64_t ts); void ProcessGarlicMessage (std::shared_ptr msg); void ProcessDeliveryStatus (std::shared_ptr msg); + bool IsExploratory () const; bool IsActive () const { return m_IsActive; }; void SetActive (bool isActive) { m_IsActive = isActive; }; void DetachTunnels (); @@ -99,22 +112,27 @@ namespace tunnel std::shared_ptr GetLowestLatencyOutboundTunnel(std::shared_ptr exclude = nullptr) const; // for overriding tunnel peer selection - std::shared_ptr SelectNextHop (std::shared_ptr prevHop) const; + std::shared_ptr SelectNextHop (std::shared_ptr prevHop, bool reverse, bool endpoint) const; + bool StandardSelectPeers(Path & path, int numHops, bool inbound, SelectHopFunc nextHop); 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) const; - bool SelectPeers (std::vector >& hops, bool isInbound); - bool SelectExplicitPeers (std::vector >& hops, bool isInbound); + typename TTunnels::value_type GetNextTunnel (TTunnels& tunnels, + typename TTunnels::value_type excluded, i2p::data::RouterInfo::CompatibleTransports compatible) const; + bool SelectPeers (Path& path, bool isInbound); + bool SelectExplicitPeers (Path& path, bool isInbound); + bool ValidatePeers (std::vector >& peers) const; private: std::shared_ptr m_LocalDestination; - int m_NumInboundHops, m_NumOutboundHops, m_NumInboundTunnels, m_NumOutboundTunnels; + 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 @@ -123,6 +141,7 @@ namespace tunnel mutable std::mutex m_TestsMutex; std::map, std::shared_ptr > > m_Tests; bool m_IsActive; + uint64_t m_NextManageTime; // in seconds std::mutex m_CustomPeerSelectorMutex; ITunnelPeerSelector * m_CustomPeerSelector; diff --git a/libi2pd/api.cpp b/libi2pd/api.cpp index 569fbd8c..ad24519f 100644 --- a/libi2pd/api.cpp +++ b/libi2pd/api.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -37,7 +37,9 @@ namespace api i2p::fs::Init(); bool precomputation; i2p::config::GetOption("precomputation.elgamal", precomputation); - i2p::crypto::InitCrypto (precomputation); + bool aesni; i2p::config::GetOption("cpuext.aesni", aesni); + bool forceCpuExt; i2p::config::GetOption("cpuext.force", forceCpuExt); + i2p::crypto::InitCrypto (precomputation, aesni, forceCpuExt); int netID; i2p::config::GetOption("netid", netID); i2p::context.SetNetID (netID); @@ -57,22 +59,27 @@ namespace api else i2p::log::Logger().SendTo (i2p::fs::DataDirPath (i2p::fs::GetAppName () + ".log")); i2p::log::Logger().Start (); - LogPrint(eLogInfo, "API: starting NetDB"); + i2p::transport::InitTransports (); + LogPrint(eLogInfo, "API: Starting NetDB"); i2p::data::netdb.Start(); - LogPrint(eLogInfo, "API: starting Transports"); + LogPrint(eLogInfo, "API: Starting Transports"); i2p::transport::transports.Start(); - LogPrint(eLogInfo, "API: starting Tunnels"); + LogPrint(eLogInfo, "API: Starting Tunnels"); i2p::tunnel::tunnels.Start(); + LogPrint(eLogInfo, "API: Starting Router context"); + i2p::context.Start(); } void StopI2P () { - LogPrint(eLogInfo, "API: shutting down"); - LogPrint(eLogInfo, "API: stopping Tunnels"); + 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"); + LogPrint(eLogInfo, "API: Stopping Transports"); i2p::transport::transports.Stop(); - LogPrint(eLogInfo, "API: stopping NetDB"); + LogPrint(eLogInfo, "API: Stopping NetDB"); i2p::data::netdb.Stop(); i2p::log::Logger().Stop (); } diff --git a/libi2pd/util.cpp b/libi2pd/util.cpp index f5204a50..088a7af5 100644 --- a/libi2pd/util.cpp +++ b/libi2pd/util.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -8,12 +8,26 @@ #include #include +#include #include #include "util.h" #include "Log.h" +#include "I2PEndian.h" -#ifdef WIN32 +#if !defined (__FreeBSD__) && !defined(_MSC_VER) +#include +#endif + +#if defined(__OpenBSD__) || defined(__FreeBSD__) +#include +#endif + +#if defined(__APPLE__) +# include +#endif + +#ifdef _WIN32 #include #include #include @@ -23,16 +37,25 @@ #include #include -#ifdef _MSC_VER -#pragma comment(lib, "IPHLPAPI.lib") -#endif // _MSC_VER +#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 exists Windows since Vista, but XP doesn't have that function! +// inet_pton and inet_ntop have been in Windows since Vista, but XP doesn't have these functions! // This function was written by Petar Korponai?. See http://stackoverflow.com/questions/15660203/inet-pton-identifier-not-found -int inet_pton_xp(int af, const char *src, void *dst) +int inet_pton_xp (int af, const char *src, void *dst) { struct sockaddr_storage ss; int size = sizeof (ss); @@ -56,10 +79,41 @@ int inet_pton_xp(int af, const char *src, void *dst) } return 0; } -#else /* !WIN32 => UNIX */ + +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::address_v4::from_string (a).to_ulong (), boost::asio::ip::address_v4::from_string (b).to_ulong () } +#define address_pair_v6(a,b) { boost::asio::ip::address_v6::from_string (a).to_bytes (), boost::asio::ip::address_v6::from_string (b).to_bytes () } namespace i2p { @@ -91,6 +145,8 @@ namespace util void RunnableService::Run () { + SetThreadName(m_Name.c_str()); + while (m_IsRunning) { try @@ -99,41 +155,61 @@ namespace util } catch (std::exception& ex) { - LogPrint (eLogError, m_Name, ": runtime exception: ", ex.what ()); + LogPrint (eLogError, m_Name, ": Runtime exception: ", ex.what ()); } } } -namespace net -{ -#ifdef WIN32 - bool IsWindowsXPorLater() - { - static bool isRequested = false; - static bool isXP = false; - if (!isRequested) - { - // request - OSVERSIONINFO osvi; - - ZeroMemory(&osvi, sizeof(OSVERSIONINFO)); - osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); - GetVersionEx(&osvi); - - isXP = osvi.dwMajorVersion <= 5; - isRequested = true; + 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); } - return isXP; + __except (EXCEPTION_EXECUTE_HANDLER) { + } + #pragma warning(pop) + #else + pthread_setname_np(pthread_self(), name); + #endif +#endif } - int GetMTUWindowsIpv4(sockaddr_in inputAddress, int fallback) +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) + if (GetAdaptersAddresses(AF_INET, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen) == ERROR_BUFFER_OVERFLOW) { FREE(pAddresses); @@ -144,30 +220,33 @@ namespace net AF_INET, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen ); - if(dwRetVal != NO_ERROR) + if (dwRetVal != NO_ERROR) { - LogPrint(eLogError, "NetIface: GetMTU(): enclosed GetAdaptersAddresses() call has failed"); + LogPrint(eLogError, "NetIface: GetMTU: Enclosed GetAdaptersAddresses() call has failed"); FREE(pAddresses); return fallback; } pCurrAddresses = pAddresses; - while(pCurrAddresses) + while (pCurrAddresses) { - PIP_ADAPTER_UNICAST_ADDRESS firstUnicastAddress = pCurrAddresses->FirstUnicastAddress; - pUnicast = pCurrAddresses->FirstUnicastAddress; - if(pUnicast == nullptr) - LogPrint(eLogError, "NetIface: GetMTU(): not a unicast ipv4 address, this is not supported"); + if (pUnicast == nullptr) + LogPrint(eLogError, "NetIface: GetMTU: Not a unicast IPv4 address, this is not supported"); - for(int i = 0; pUnicast != nullptr; ++i) + while (pUnicast != nullptr) { LPSOCKADDR lpAddr = pUnicast->Address.lpSockaddr; sockaddr_in* localInterfaceAddress = (sockaddr_in*) lpAddr; - if(localInterfaceAddress->sin_addr.S_un.S_addr == inputAddress.sin_addr.S_un.S_addr) + if (localInterfaceAddress->sin_addr.S_un.S_addr == inputAddress.sin_addr.S_un.S_addr) { - auto result = pAddresses->Mtu; + 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; @@ -175,19 +254,23 @@ namespace net pCurrAddresses = pCurrAddresses->Next; } - LogPrint(eLogError, "NetIface: GetMTU(): no usable unicast ipv4 addresses found"); + LogPrint(eLogError, "NetIface: GetMTU: No usable unicast IPv4 addresses found"); FREE(pAddresses); return fallback; } - int GetMTUWindowsIpv6(sockaddr_in6 inputAddress, int fallback) + int GetMTUWindowsIpv6 (sockaddr_in6 inputAddress, int fallback) { + typedef const char *(* IPN)(int af, const void *src, char *dst, socklen_t size); + IPN inetntop = (IPN)(void*)GetProcAddress (GetModuleHandle ("ws2_32.dll"), "InetNtop"); + if (!inetntop) inetntop = inet_ntop_xp; // use own implementation if not found + ULONG outBufLen = 0; PIP_ADAPTER_ADDRESSES pAddresses = nullptr; PIP_ADAPTER_ADDRESSES pCurrAddresses = nullptr; PIP_ADAPTER_UNICAST_ADDRESS pUnicast = nullptr; - if(GetAdaptersAddresses(AF_INET6, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen) + if (GetAdaptersAddresses(AF_INET6, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen) == ERROR_BUFFER_OVERFLOW) { FREE(pAddresses); @@ -198,23 +281,22 @@ namespace net AF_INET6, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen ); - if(dwRetVal != NO_ERROR) + if (dwRetVal != NO_ERROR) { - LogPrint(eLogError, "NetIface: GetMTU(): enclosed GetAdaptersAddresses() call has failed"); + LogPrint(eLogError, "NetIface: GetMTU: Enclosed GetAdaptersAddresses() call has failed"); FREE(pAddresses); return fallback; } bool found_address = false; pCurrAddresses = pAddresses; - while(pCurrAddresses) + while (pCurrAddresses) { - PIP_ADAPTER_UNICAST_ADDRESS firstUnicastAddress = pCurrAddresses->FirstUnicastAddress; pUnicast = pCurrAddresses->FirstUnicastAddress; - if(pUnicast == nullptr) - LogPrint(eLogError, "NetIface: GetMTU(): not a unicast ipv6 address, this is not supported"); + if (pUnicast == nullptr) + LogPrint(eLogError, "NetIface: GetMTU: Not a unicast IPv6 address, this is not supported"); - for(int i = 0; pUnicast != nullptr; ++i) + while (pUnicast != nullptr) { LPSOCKADDR lpAddr = pUnicast->Address.lpSockaddr; sockaddr_in6 *localInterfaceAddress = (sockaddr_in6*) lpAddr; @@ -229,9 +311,13 @@ namespace net if (found_address) { - auto result = pAddresses->Mtu; + 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; @@ -240,12 +326,12 @@ namespace net pCurrAddresses = pCurrAddresses->Next; } - LogPrint(eLogError, "NetIface: GetMTU(): no usable unicast ipv6 addresses found"); + LogPrint(eLogError, "NetIface: GetMTU: No usable unicast IPv6 addresses found"); FREE(pAddresses); return fallback; } - int GetMTUWindows(const boost::asio::ip::address& localAddress, int fallback) + int GetMTUWindows (const boost::asio::ip::address& localAddress, int fallback) { #ifdef UNICODE string localAddress_temporary = localAddress.to_string(); @@ -255,16 +341,16 @@ namespace net #endif typedef int (* IPN)(int af, const char *src, void *dst); - IPN inetpton = (IPN)GetProcAddress (GetModuleHandle ("ws2_32.dll"), "InetPton"); + IPN inetpton = (IPN)(void*)GetProcAddress (GetModuleHandle ("ws2_32.dll"), "InetPton"); if (!inetpton) inetpton = inet_pton_xp; // use own implementation if not found - if(localAddress.is_v4()) + if (localAddress.is_v4()) { sockaddr_in inputAddress; inetpton(AF_INET, localAddressUniversal.c_str(), &(inputAddress.sin_addr)); return GetMTUWindowsIpv4(inputAddress, fallback); } - else if(localAddress.is_v6()) + else if (localAddress.is_v6()) { sockaddr_in6 inputAddress; inetpton(AF_INET6, localAddressUniversal.c_str(), &(inputAddress.sin6_addr)); @@ -272,15 +358,15 @@ namespace net } else { - LogPrint(eLogError, "NetIface: GetMTU(): address family is not supported"); + LogPrint(eLogError, "NetIface: GetMTU: Address family is not supported"); return fallback; } } #else // assume unix - int GetMTUUnix(const boost::asio::ip::address& localAddress, int fallback) + int GetMTUUnix (const boost::asio::ip::address& localAddress, int fallback) { ifaddrs* ifaddr, *ifa = nullptr; - if(getifaddrs(&ifaddr) == -1) + if (getifaddrs(&ifaddr) == -1) { LogPrint(eLogError, "NetIface: Can't call getifaddrs(): ", strerror(errno)); return fallback; @@ -288,34 +374,34 @@ namespace net int family = 0; // look for interface matching local address - for(ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) + for (ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) { - if(!ifa->ifa_addr) + if (!ifa->ifa_addr) continue; family = ifa->ifa_addr->sa_family; - if(family == AF_INET && localAddress.is_v4()) + if (family == AF_INET && localAddress.is_v4()) { sockaddr_in* sa = (sockaddr_in*) ifa->ifa_addr; - if(!memcmp(&sa->sin_addr, localAddress.to_v4().to_bytes().data(), 4)) + if (!memcmp(&sa->sin_addr, localAddress.to_v4().to_bytes().data(), 4)) break; // address matches } - else if(family == AF_INET6 && localAddress.is_v6()) + else if (family == AF_INET6 && localAddress.is_v6()) { sockaddr_in6* sa = (sockaddr_in6*) ifa->ifa_addr; - if(!memcmp(&sa->sin6_addr, localAddress.to_v6().to_bytes().data(), 16)) + if (!memcmp(&sa->sin6_addr, localAddress.to_v6().to_bytes().data(), 16)) break; // address matches } } int mtu = fallback; - if(ifa && family) + if (ifa && family) { // interface found? int fd = socket(family, SOCK_DGRAM, 0); - if(fd > 0) + if (fd > 0) { ifreq ifr; - strncpy(ifr.ifr_name, ifa->ifa_name, IFNAMSIZ); // set interface for query - if(ioctl(fd, SIOCGIFMTU, &ifr) >= 0) + 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)); @@ -325,18 +411,18 @@ namespace net LogPrint(eLogError, "NetIface: Failed to create datagram socket"); } else - LogPrint(eLogWarning, "NetIface: interface for local address", localAddress.to_string(), " not found"); + LogPrint(eLogWarning, "NetIface: Interface for local address", localAddress.to_string(), " not found"); freeifaddrs(ifaddr); return mtu; } -#endif // WIN32 +#endif // _WIN32 - int GetMTU(const boost::asio::ip::address& localAddress) + int GetMTU (const boost::asio::ip::address& localAddress) { int fallback = localAddress.is_v6 () ? 1280 : 620; // fallback MTU -#ifdef WIN32 +#ifdef _WIN32 return GetMTUWindows(localAddress, fallback); #else return GetMTUUnix(localAddress, fallback); @@ -344,53 +430,248 @@ namespace net return fallback; } - const boost::asio::ip::address GetInterfaceAddress(const std::string & ifname, bool ipv6) + const boost::asio::ip::address GetInterfaceAddress (const std::string & ifname, bool ipv6) { -#ifdef WIN32 - LogPrint(eLogError, "NetIface: cannot get address by interface name, not implemented on WIN32"); - if(ipv6) +#ifdef _WIN32 + LogPrint(eLogError, "NetIface: Cannot get address by interface name, not implemented on WIN32"); + if (ipv6) return boost::asio::ip::address::from_string("::1"); else return boost::asio::ip::address::from_string("127.0.0.1"); #else int af = (ipv6 ? AF_INET6 : AF_INET); - ifaddrs * addrs = nullptr; - if(getifaddrs(&addrs) == 0) + ifaddrs *addrs; + try { - // got ifaddrs - ifaddrs * cur = addrs; - while(cur) + if (!getifaddrs(&addrs)) { - std::string cur_ifname(cur->ifa_name); - if (cur_ifname == ifname && cur->ifa_addr && cur->ifa_addr->sa_family == af) + for (auto cur = addrs; cur; cur = cur->ifa_next) { - // 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::address::from_string(cur_ifaddr); + 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::address::from_string(cur_ifaddr); + } } - cur = cur->ifa_next; } } - if(addrs) freeifaddrs(addrs); + catch (std::exception& ex) + { + LogPrint(eLogError, "NetIface: Exception while searching address using ifaddr: ", ex.what()); + } + + if (addrs) freeifaddrs(addrs); std::string fallback; - if(ipv6) + if (ipv6) { fallback = "::1"; - LogPrint(eLogWarning, "NetIface: cannot find ipv6 address for interface ", ifname); + LogPrint(eLogWarning, "NetIface: Cannot find IPv6 address for interface ", ifname); } else { fallback = "127.0.0.1"; - LogPrint(eLogWarning, "NetIface: cannot find ipv4 address for interface ", ifname); + LogPrint(eLogWarning, "NetIface: Cannot find IPv4 address for interface ", ifname); } return boost::asio::ip::address::from_string(fallback); #endif } -} + + int GetMaxMTU (const boost::asio::ip::address_v6& localAddress) + { + uint32_t prefix = bufbe32toh (localAddress.to_bytes ().data ()); + switch (prefix) + { + case 0x20010470: + case 0x260070ff: + // Hurricane Electric + return 1480; + break; + case 0x2a06a003: + case 0x2a06a004: + case 0x2a06a005: + // route48 + return 1420; + break; + default: ; + } + return 1500; + } + + static bool IsYggdrasilAddress (const uint8_t addr[16]) + { + return addr[0] == 0x02 || addr[0] == 0x03; + } + + bool IsYggdrasilAddress (const boost::asio::ip::address& addr) + { + if (!addr.is_v6 ()) return false; + return IsYggdrasilAddress (addr.to_v6 ().to_bytes ().data ()); + } + + bool IsPortInReservedRange (const uint16_t port) noexcept + { + // https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers (Feb. 3, 2023) + Tor browser (9150) + static const std::unordered_set reservedPorts{ + 9119,9150,9306,9312,9389,9418,9535,9536,9695, + 9800,9899,10000,10050,10051,10110,10212, + 10933,11001,11112,11235,11371,12222,12223, + 13075,13400,13720,13721,13724,13782,13783, + 13785,13786,15345,17224,17225,17500,18104, + 19788,19812,19813,19814,19999,20000,24465, + 24554,26000,27000,27001,27002,27003,27004, + 27005,27006,27007,27008,27009,28000}; + + return (reservedPorts.find(port) != reservedPorts.end()); + } + + boost::asio::ip::address_v6 GetYggdrasilAddress () + { +#if defined(_WIN32) + ULONG outBufLen = 0; + PIP_ADAPTER_ADDRESSES pAddresses = nullptr; + PIP_ADAPTER_ADDRESSES pCurrAddresses = nullptr; + PIP_ADAPTER_UNICAST_ADDRESS pUnicast = nullptr; + + if (GetAdaptersAddresses(AF_INET6, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen) + == ERROR_BUFFER_OVERFLOW) + { + FREE(pAddresses); + pAddresses = (IP_ADAPTER_ADDRESSES*) MALLOC(outBufLen); + } + + DWORD dwRetVal = GetAdaptersAddresses( + AF_INET6, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen + ); + + if (dwRetVal != NO_ERROR) + { + LogPrint(eLogError, "NetIface: GetYggdrasilAddress(): enclosed GetAdaptersAddresses() call has failed"); + FREE(pAddresses); + return boost::asio::ip::address_v6 (); + } + + pCurrAddresses = pAddresses; + while (pCurrAddresses) + { + pUnicast = pCurrAddresses->FirstUnicastAddress; + + while (pUnicast != nullptr) + { + LPSOCKADDR lpAddr = pUnicast->Address.lpSockaddr; + sockaddr_in6 *localInterfaceAddress = (sockaddr_in6*) lpAddr; + if (IsYggdrasilAddress(localInterfaceAddress->sin6_addr.u.Byte)) { + boost::asio::ip::address_v6::bytes_type bytes; + memcpy (bytes.data (), &localInterfaceAddress->sin6_addr.u.Byte, 16); + FREE(pAddresses); + return boost::asio::ip::address_v6 (bytes); + } + pUnicast = pUnicast->Next; + } + pCurrAddresses = pCurrAddresses->Next; + } + LogPrint(eLogWarning, "NetIface: Interface with Yggdrasil network address not found"); + FREE(pAddresses); + return boost::asio::ip::address_v6 (); +#else + ifaddrs *addrs; + try + { + if (!getifaddrs(&addrs)) + { + for (auto cur = addrs; cur; cur = cur->ifa_next) + { + if (cur->ifa_addr && cur->ifa_addr->sa_family == AF_INET6) + { + sockaddr_in6* sa = (sockaddr_in6*)cur->ifa_addr; + if (IsYggdrasilAddress(sa->sin6_addr.s6_addr)) + { + boost::asio::ip::address_v6::bytes_type bytes; + memcpy (bytes.data (), &sa->sin6_addr, 16); + freeifaddrs(addrs); + return boost::asio::ip::address_v6 (bytes); + } + } + } + } + } + catch (std::exception& ex) + { + LogPrint(eLogError, "NetIface: Exception while searching Yggdrasill address using ifaddr: ", ex.what()); + } + LogPrint(eLogWarning, "NetIface: Interface with Yggdrasil network address not found"); + if (addrs) freeifaddrs(addrs); + return boost::asio::ip::address_v6 (); +#endif + } + + bool IsLocalAddress (const boost::asio::ip::address& addr) + { + auto mtu = // TODO: implement better +#ifdef _WIN32 + GetMTUWindows(addr, 0); +#else + GetMTUUnix(addr, 0); +#endif + return mtu > 0; + } + + bool IsInReservedRange (const boost::asio::ip::address& host) + { + // https://en.wikipedia.org/wiki/Reserved_IP_addresses + if (host.is_unspecified ()) return false; + if (host.is_v4()) + { + static const std::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_ulong (); + 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("2001:db8::", "2001:db8:ffff:ffff:ffff:ffff:ffff:ffff"), + address_pair_v6("fc00::", "fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"), + address_pair_v6("fe80::", "febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff"), + address_pair_v6("ff00::", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"), + address_pair_v6("::", "::"), + address_pair_v6("::1", "::1") + }; + + boost::asio::ip::address_v6::bytes_type ipv6_address = host.to_v6 ().to_bytes (); + for (const auto& it : reservedIPv6Ranges) { + if (ipv6_address >= it.first && ipv6_address <= it.second) + return true; + } + if (IsYggdrasilAddress (ipv6_address.data ())) // yggdrasil? + return true; + } + return false; + } +} // net } // util } // i2p diff --git a/libi2pd/util.h b/libi2pd/util.h index cb8fd8f1..e2037212 100644 --- a/libi2pd/util.h +++ b/libi2pd/util.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -51,12 +51,13 @@ namespace util MemoryPool (): m_Head (nullptr) {} ~MemoryPool () { - while (m_Head) - { - auto tmp = m_Head; - m_Head = static_cast(*(void * *)m_Head); // next - ::operator delete ((void *)tmp); - } + CleanUp (); + } + + void CleanUp () + { + CleanUp (m_Head); + m_Head = nullptr; } template @@ -93,13 +94,25 @@ namespace util std::bind (&MemoryPool::Release, this, std::placeholders::_1)); } + protected: + + void CleanUp (T * head) + { + while (head) + { + auto tmp = head; + head = static_cast(*(void * *)head); // next + ::operator delete ((void *)tmp); + } + } + protected: T * m_Head; }; template - class MemoryPoolMt: public MemoryPool + class MemoryPoolMt: private MemoryPool { public: @@ -126,6 +139,24 @@ namespace util this->Release (it); } + template + std::shared_ptr AcquireSharedMt (TArgs&&... args) + { + return std::shared_ptr(AcquireMt (std::forward(args)...), + std::bind::*)(T *)> (&MemoryPoolMt::ReleaseMt, this, std::placeholders::_1)); + } + + void CleanUpMt () + { + T * head; + { + std::lock_guard l(m_Mutex); + head = this->m_Head; + this->m_Head = nullptr; + } + if (head) this->CleanUp (head); + } + private: std::mutex m_Mutex; @@ -168,10 +199,32 @@ namespace util boost::asio::io_service::work 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); - const boost::asio::ip::address GetInterfaceAddress(const std::string & ifname, bool ipv6=false); + int GetMaxMTU (const boost::asio::ip::address_v6& localAddress); // check tunnel broker for ipv6 address + const boost::asio::ip::address GetInterfaceAddress (const std::string & ifname, bool ipv6=false); + boost::asio::ip::address_v6 GetYggdrasilAddress (); + bool IsLocalAddress (const boost::asio::ip::address& addr); + bool IsInReservedRange (const boost::asio::ip::address& host); + bool IsYggdrasilAddress (const boost::asio::ip::address& addr); + bool IsPortInReservedRange (const uint16_t port) noexcept; } } } diff --git a/libi2pd/version.h b/libi2pd/version.h index db30eee8..903ceebc 100644 --- a/libi2pd/version.h +++ b/libi2pd/version.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,26 +11,29 @@ #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 33 +#define I2PD_VERSION_MINOR 48 #define I2PD_VERSION_MICRO 0 #define I2PD_VERSION_PATCH 0 -#define I2PD_VERSION MAKE_VERSION(I2PD_VERSION_MAJOR, I2PD_VERSION_MINOR, I2PD_VERSION_MICRO) +#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 -#ifdef MESHNET -#define I2PD_NET_ID 3 -#else #define I2PD_NET_ID 2 -#endif #define I2P_VERSION_MAJOR 0 #define I2P_VERSION_MINOR 9 -#define I2P_VERSION_MICRO 47 +#define I2P_VERSION_MICRO 59 #define I2P_VERSION_PATCH 0 #define I2P_VERSION MAKE_VERSION(I2P_VERSION_MAJOR, I2P_VERSION_MINOR, I2P_VERSION_MICRO) #define I2P_VERSION_NUMBER MAKE_VERSION_NUMBER(I2P_VERSION_MAJOR, I2P_VERSION_MINOR, I2P_VERSION_MICRO) diff --git a/libi2pd_client/AddressBook.cpp b/libi2pd_client/AddressBook.cpp index 5c1cffbb..055311a9 100644 --- a/libi2pd_client/AddressBook.cpp +++ b/libi2pd_client/AddressBook.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -34,14 +34,13 @@ namespace client // TODO: this is actually proxy class class AddressBookFilesystemStorage: public AddressBookStorage { - private: - i2p::fs::HashedStorage storage; - std::string etagsPath, indexPath, localPath; - 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) const; void AddAddress (std::shared_ptr address); @@ -62,7 +61,10 @@ namespace client 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 }; bool AddressBookFilesystemStorage::Init() @@ -117,7 +119,7 @@ namespace client 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); + LogPrint (eLogError, "Addressbook: Can't open file ", path); return; } size_t len = address->GetFullLen (); @@ -167,7 +169,7 @@ namespace client LogPrint(eLogWarning, "Addressbook: Can't open ", indexPath); return 0; } - LogPrint(eLogInfo, "Addressbook: using index file ", indexPath); + LogPrint(eLogInfo, "Addressbook: Using index file ", indexPath); LogPrint (eLogInfo, "Addressbook: ", num, " addresses loaded from storage"); return num; @@ -183,35 +185,59 @@ namespace client int AddressBookFilesystemStorage::Save (const std::map >& addresses) { - if (addresses.empty()) { - LogPrint(eLogWarning, "Addressbook: not saving empty addressbook"); + if (addresses.empty()) + { + LogPrint(eLogWarning, "Addressbook: Not saving empty addressbook"); return 0; } int num = 0; - std::ofstream f (indexPath, std::ofstream::out); // in text mode - - if (!f.is_open ()) { - LogPrint (eLogWarning, "Addressbook: Can't open ", indexPath); - return 0; - } - - for (const auto& it: addresses) { - 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++; - } + // 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: invalid address ", it.first); + LogPrint (eLogWarning, "Addressbook: Can't open ", indexPath); } - LogPrint (eLogInfo, "Addressbook: ", num, " addresses saved"); + 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; } @@ -239,7 +265,7 @@ namespace client void AddressBookFilesystemStorage::ResetEtags () { - LogPrint (eLogError, "Addressbook: resetting eTags"); + LogPrint (eLogError, "Addressbook: Resetting eTags"); for (boost::filesystem::directory_iterator it (etagsPath); it != boost::filesystem::directory_iterator (); ++it) { if (!boost::filesystem::is_regular_file (it->status ())) @@ -273,7 +299,8 @@ namespace client } AddressBook::AddressBook (): m_Storage(nullptr), m_IsLoaded (false), m_IsDownloading (false), - m_NumRetries (0), m_DefaultSubscription (nullptr), m_SubscriptionsUpdateTimer (nullptr) + m_NumRetries (0), m_DefaultSubscription (nullptr), m_SubscriptionsUpdateTimer (nullptr), + m_IsEnabled (true) { } @@ -284,12 +311,16 @@ namespace client void AddressBook::Start () { - if (!m_Storage) - m_Storage = new AddressBookFilesystemStorage; - m_Storage->Init(); - LoadHosts (); /* try storage, then hosts.txt, then download */ - StartSubscriptions (); - StartLookups (); + 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 (); + } } void AddressBook::StartResolvers () @@ -308,17 +339,17 @@ namespace client } if (m_IsDownloading) { - LogPrint (eLogInfo, "Addressbook: subscriptions are downloading, abort"); + LogPrint (eLogInfo, "Addressbook: Subscriptions are downloading, abort"); for (int i = 0; i < 30; i++) { if (!m_IsDownloading) { - LogPrint (eLogInfo, "Addressbook: subscriptions download complete"); + LogPrint (eLogInfo, "Addressbook: Subscriptions download complete"); break; } std::this_thread::sleep_for (std::chrono::seconds (1)); // wait for 1 seconds } - LogPrint (eLogError, "Addressbook: subscription download timeout"); + LogPrint (eLogError, "Addressbook: Subscription download timeout"); m_IsDownloading = false; } if (m_Storage) @@ -344,9 +375,10 @@ namespace client pos = address.find (".i2p"); if (pos != std::string::npos) { + if (!m_IsEnabled) return nullptr; auto addr = FindAddress (address); if (!addr) - LookupAddress (address); // TODO: + LookupAddress (address); // TODO: return addr; } } @@ -365,13 +397,26 @@ namespace client return nullptr; } + bool AddressBook::RecordExists (const std::string& address, const std::string& jump) + { + auto addr = FindAddress(address); + if (!addr) + return false; + + i2p::data::IdentityEx ident; + if (ident.FromBase64 (jump) && ident.GetIdentHash () == addr->identHash) + return true; + + return false; + } + void AddressBook::InsertAddress (const std::string& address, const std::string& jump) { auto pos = jump.find(".b32.i2p"); if (pos != std::string::npos) { m_Addresses[address] = std::make_shared
(jump.substr (0, pos)); - LogPrint (eLogInfo, "Addressbook: added ", address," -> ", jump); + LogPrint (eLogInfo, "Addressbook: Added ", address," -> ", jump); } else { @@ -381,10 +426,10 @@ namespace client { m_Storage->AddAddress (ident); m_Addresses[address] = std::make_shared
(ident->GetIdentHash ()); - LogPrint (eLogInfo, "Addressbook: added ", address," -> ", ToAddress(ident->GetIdentHash ())); + LogPrint (eLogInfo, "Addressbook: Added ", address," -> ", ToAddress(ident->GetIdentHash ())); } else - LogPrint (eLogError, "Addressbook: malformed address ", jump); + LogPrint (eLogError, "Addressbook: Malformed address ", jump); } } @@ -444,9 +489,23 @@ namespace client 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); + LogPrint (eLogError, "Addressbook: Malformed address ", addr, " for ", name); incomplete = f.eof (); continue; } @@ -454,20 +513,21 @@ namespace client auto it = m_Addresses.find (name); if (it != m_Addresses.end ()) // already exists ? { - if (it->second->IsIdentHash () && it->second->identHash != ident->GetIdentHash ()) // address changed? + 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 (); m_Storage->AddAddress (ident); - LogPrint (eLogInfo, "Addressbook: updated host: ", name); + m_Storage->RemoveAddress (it->second->identHash); + LogPrint (eLogInfo, "Addressbook: Updated host: ", name); } } else { - //m_Addresses.emplace (name, std::make_shared
(ident->GetIdentHash ())); - m_Addresses[name] = std::make_shared
(ident->GetIdentHash ()); // for gcc 4.7 + m_Addresses.emplace (name, std::make_shared
(ident->GetIdentHash ())); m_Storage->AddAddress (ident); if (is_update) - LogPrint (eLogInfo, "Addressbook: added new host: ", name); + LogPrint (eLogInfo, "Addressbook: Added new host: ", name); } } else @@ -493,32 +553,34 @@ namespace client while (!f.eof ()) { getline(f, s); - if (!s.length()) continue; // skip empty line + 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 if (!i2p::config::IsDefault("addressbook.subscriptions")) + else { + LogPrint (eLogInfo, "Addressbook: Loading subscriptions from config file"); // using config file items std::string subscriptionURLs; i2p::config::GetOption("addressbook.subscriptions", subscriptionURLs); std::vector subsList; boost::split(subsList, subscriptionURLs, boost::is_any_of(","), boost::token_compress_on); - for (size_t i = 0; i < subsList.size (); i++) + for (const auto& s: subsList) { - m_Subscriptions.push_back (std::make_shared (*this, subsList[i])); + 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"); + LogPrint (eLogError, "Addressbook: Subscriptions already loaded"); } void AddressBook::LoadLocal () { + if (!m_Storage) return; std::map> localAddresses; m_Storage->LoadLocal (localAddresses); for (const auto& it: localAddresses) @@ -599,7 +661,7 @@ namespace client this, std::placeholders::_1)); } else - LogPrint (eLogError, "Addressbook: can't start subscriptions: missing shared local destination"); + LogPrint (eLogCritical, "Addressbook: Can't start subscriptions: missing shared local destination"); } void AddressBook::StopSubscriptions () @@ -614,7 +676,7 @@ namespace client { auto dest = i2p::client::context.GetSharedLocalDestination (); if (!dest) { - LogPrint(eLogWarning, "Addressbook: missing local destination, skip subscription update"); + LogPrint(eLogWarning, "Addressbook: Missing local destination, skip subscription update"); return; } if (!m_IsDownloading && dest->IsReady ()) @@ -622,7 +684,7 @@ namespace client if (!m_IsLoaded) { // download it from default subscription - LogPrint (eLogInfo, "Addressbook: trying to 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); @@ -747,6 +809,8 @@ namespace client void AddressBookSubscription::CheckUpdates () { + i2p::util::SetThreadName("Addressbook"); + bool result = MakeRequest (); m_Book.DownloadComplete (result, m_Ident, m_Etag, m_LastModified); } @@ -758,7 +822,7 @@ namespace client LogPrint (eLogInfo, "Addressbook: Downloading hosts database from ", m_Link); if (!url.parse(m_Link)) { - LogPrint(eLogError, "Addressbook: failed to parse url: ", m_Link); + LogPrint(eLogError, "Addressbook: Failed to parse url: ", m_Link); return false; } auto addr = m_Book.GetAddress (url.host); @@ -769,40 +833,22 @@ namespace client } else m_Ident = addr->identHash; - /* this code block still needs some love */ - std::condition_variable newDataReceived; - std::mutex newDataReceivedMutex; - auto leaseSet = i2p::client::context.GetSharedLocalDestination ()->FindLeaseSet (m_Ident); - if (!leaseSet) + // 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) { - std::unique_lock l(newDataReceivedMutex); - i2p::client::context.GetSharedLocalDestination ()->RequestDestination (m_Ident, - [&newDataReceived, &leaseSet, &newDataReceivedMutex](std::shared_ptr ls) - { - leaseSet = ls; - std::unique_lock l1(newDataReceivedMutex); - newDataReceived.notify_all (); - }); - if (newDataReceived.wait_for (l, std::chrono::seconds (SUBSCRIPTION_REQUEST_TIMEOUT)) == std::cv_status::timeout) - { - LogPrint (eLogError, "Addressbook: Subscription LeaseSet request timeout expired"); - i2p::client::context.GetSharedLocalDestination ()->CancelDestinationRequest (m_Ident, false); // don't notify, because we know it already - return false; - } - } - if (!leaseSet) { - /* still no leaseset found */ LogPrint (eLogError, "Addressbook: LeaseSet for address ", url.host, " not found"); return false; } - if (m_Etag.empty() && m_LastModified.empty()) { + if (m_Etag.empty() && m_LastModified.empty()) + { m_Book.GetEtag (m_Ident, m_Etag, m_LastModified); - LogPrint (eLogDebug, "Addressbook: loaded for ", url.host, ": ETag: ", m_Etag, ", Last-Modified: ", m_LastModified); + LogPrint (eLogDebug, "Addressbook: Loaded for ", url.host, ": ETag: ", m_Etag, ", Last-Modified: ", m_LastModified); } - /* save url parts for later use */ - std::string dest_host = url.host; - int dest_port = url.port ? url.port : 80; - /* create http request & send it */ + // create http request & send it i2p::http::HTTPReq req; req.AddHeader("Host", dest_host); req.AddHeader("User-Agent", "Wget/1.11.4"); @@ -813,36 +859,31 @@ namespace client 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(); + // convert url to relative + url.schema = ""; + url.host = ""; + req.uri = url.to_string(); req.version = "HTTP/1.1"; - auto stream = i2p::client::context.GetSharedLocalDestination ()->CreateStream (leaseSet, dest_port); std::string request = req.to_string(); stream->Send ((const uint8_t *) request.data(), request.length()); - /* read response */ + // read response std::string response; uint8_t recv_buf[4096]; bool end = false; int numAttempts = 0; while (!end) { - stream->AsyncReceive (boost::asio::buffer (recv_buf, 4096), - [&](const boost::system::error_code& ecode, std::size_t bytes_transferred) - { - if (bytes_transferred) - response.append ((char *)recv_buf, bytes_transferred); - if (ecode == boost::asio::error::timed_out || !stream->IsOpen ()) - end = true; - newDataReceived.notify_all (); - }, - SUBSCRIPTION_REQUEST_TIMEOUT); - std::unique_lock l(newDataReceivedMutex); - // wait 1 more second - if (newDataReceived.wait_for (l, std::chrono::seconds (SUBSCRIPTION_REQUEST_TIMEOUT + 1)) == std::cv_status::timeout) + size_t received = stream->Receive (recv_buf, 4096, SUBSCRIPTION_REQUEST_TIMEOUT); + if (received) { - LogPrint (eLogError, "Addressbook: subscriptions request timeout expired"); + 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; } @@ -850,43 +891,43 @@ namespace client // process remaining buffer while (size_t len = stream->ReadSome (recv_buf, sizeof(recv_buf))) response.append ((char *)recv_buf, len); - /* parse response */ + // parse response i2p::http::HTTPRes res; int res_head_len = res.parse(response); if (res_head_len < 0) { - LogPrint(eLogError, "Addressbook: can't parse http response from ", dest_host); + LogPrint(eLogError, "Addressbook: Can't parse http response from ", dest_host); return false; } if (res_head_len == 0) { - LogPrint(eLogError, "Addressbook: incomplete http response from ", dest_host, ", interrupted by timeout"); + LogPrint(eLogError, "Addressbook: Incomplete http response from ", dest_host, ", interrupted by timeout"); return false; } - /* assert: res_head_len > 0 */ + // assert: res_head_len > 0 response.erase(0, res_head_len); if (res.code == 304) { - LogPrint (eLogInfo, "Addressbook: no updates from ", dest_host, ", code 304"); + LogPrint (eLogInfo, "Addressbook: No updates from ", dest_host, ", code 304"); return false; } if (res.code != 200) { - LogPrint (eLogWarning, "Adressbook: can't get updates from ", dest_host, ", response code ", res.code); + LogPrint (eLogWarning, "Adressbook: Can't get updates from ", dest_host, ", response code ", res.code); return false; } int len = res.content_length(); if (response.empty()) { - LogPrint(eLogError, "Addressbook: empty response from ", dest_host, ", expected ", len, " bytes"); + LogPrint(eLogError, "Addressbook: Empty response from ", dest_host, ", expected ", len, " bytes"); return false; } if (!res.is_gzipped () && len > 0 && len != (int) response.length()) { - LogPrint(eLogError, "Addressbook: response size mismatch, expected: ", len, ", got: ", response.length(), "bytes"); + LogPrint(eLogError, "Addressbook: Response size mismatch, expected: ", len, ", got: ", response.length(), "bytes"); return false; } - /* assert: res.code == 200 */ + // assert: res.code == 200 auto it = res.headers.find("ETag"); if (it != res.headers.end()) m_Etag = it->second; it = res.headers.find("Last-Modified"); @@ -904,13 +945,13 @@ namespace client inflator.Inflate ((const uint8_t *) response.data(), response.length(), out); if (out.fail()) { - LogPrint(eLogError, "Addressbook: can't gunzip http response"); + LogPrint(eLogError, "Addressbook: Can't gunzip http response"); return false; } response = out.str(); } std::stringstream ss(response); - LogPrint (eLogInfo, "Addressbook: got update from ", dest_host); + LogPrint (eLogInfo, "Addressbook: Got update from ", dest_host); m_Book.LoadHostsFromStream (ss, true); return true; } diff --git a/libi2pd_client/AddressBook.h b/libi2pd_client/AddressBook.h index 04600792..9b2c7e7e 100644 --- a/libi2pd_client/AddressBook.h +++ b/libi2pd_client/AddressBook.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -90,6 +90,8 @@ namespace client 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 @@ -116,7 +118,7 @@ namespace client private: std::mutex m_AddressBookMutex; - std::map > m_Addresses; + std::map > m_Addresses; std::map > m_Resolvers; // local destination->resolver std::mutex m_LookupsMutex; std::map m_Lookups; // nonce -> address @@ -126,6 +128,7 @@ namespace client std::vector > m_Subscriptions; std::shared_ptr m_DefaultSubscription; // in case if we don't know any addresses yet boost::asio::deadline_timer * m_SubscriptionsUpdateTimer; + bool m_IsEnabled; }; class AddressBookSubscription @@ -162,7 +165,7 @@ namespace client private: std::shared_ptr m_LocalDestination; - std::map m_LocalAddresses; + std::map m_LocalAddresses; }; } } diff --git a/libi2pd_client/BOB.cpp b/libi2pd_client/BOB.cpp index c4447808..23c3b72f 100644 --- a/libi2pd_client/BOB.cpp +++ b/libi2pd_client/BOB.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -68,7 +68,7 @@ namespace client std::shared_ptr receiver) { if (ecode) - LogPrint (eLogError, "BOB: inbound tunnel read error: ", ecode.message ()); + LogPrint (eLogError, "BOB: Inbound tunnel read error: ", ecode.message ()); else { receiver->bufferOffset += bytes_transferred; @@ -83,7 +83,7 @@ namespace client auto addr = context.GetAddressBook ().GetAddress (receiver->buffer); if (!addr) { - LogPrint (eLogError, "BOB: address ", receiver->buffer, " not found"); + LogPrint (eLogError, "BOB: Address ", receiver->buffer, " not found"); return; } if (addr->IsIdentHash ()) @@ -106,7 +106,7 @@ namespace client if (receiver->bufferOffset < BOB_COMMAND_BUFFER_SIZE) ReceiveAddress (receiver); else - LogPrint (eLogError, "BOB: missing inbound address"); + LogPrint (eLogError, "BOB: Missing inbound address"); } } } @@ -127,7 +127,7 @@ namespace client connection->I2PConnect (receiver->data, receiver->dataLen); } - BOBI2POutboundTunnel::BOBI2POutboundTunnel (const std::string& outhost, int port, + BOBI2POutboundTunnel::BOBI2POutboundTunnel (const std::string& outhost, uint16_t port, std::shared_ptr localDestination, bool quiet): BOBI2PTunnel (localDestination), m_Endpoint (boost::asio::ip::address::from_string (outhost), port), m_IsQuiet (quiet) { @@ -156,7 +156,7 @@ namespace client { if (stream) { - auto conn = std::make_shared (this, stream, std::make_shared (GetService ()), m_Endpoint, m_IsQuiet); + auto conn = std::make_shared (this, stream, m_Endpoint, m_IsQuiet); AddHandler (conn); conn->Connect (); } @@ -164,11 +164,11 @@ namespace client BOBDestination::BOBDestination (std::shared_ptr localDestination, const std::string &nickname, const std::string &inhost, const std::string &outhost, - const int inport, const int outport, const bool quiet): + const uint16_t inport, const uint16_t outport, const bool quiet): m_LocalDestination (localDestination), m_OutboundTunnel (nullptr), m_InboundTunnel (nullptr), m_Nickname(nickname), m_InHost(inhost), m_OutHost(outhost), - m_InPort(inport), m_OutPort(outport), m_Quiet(quiet) + m_InPort(inport), m_OutPort(outport), m_Quiet(quiet), m_IsRunning(false) { } @@ -183,6 +183,7 @@ namespace client { if (m_OutboundTunnel) m_OutboundTunnel->Start (); if (m_InboundTunnel) m_InboundTunnel->Start (); + m_IsRunning = true; } void BOBDestination::Stop () @@ -193,6 +194,7 @@ namespace client void BOBDestination::StopTunnels () { + m_IsRunning = false; if (m_OutboundTunnel) { m_OutboundTunnel->Stop (); @@ -207,7 +209,7 @@ namespace client } } - void BOBDestination::CreateInboundTunnel (int port, const std::string& inhost) + void BOBDestination::CreateInboundTunnel (uint16_t port, const std::string& inhost) { if (!m_InboundTunnel) { @@ -228,7 +230,7 @@ namespace client } } - void BOBDestination::CreateOutboundTunnel (const std::string& outhost, int port, bool quiet) + void BOBDestination::CreateOutboundTunnel (const std::string& outhost, uint16_t port, bool quiet) { if (!m_OutboundTunnel) { @@ -268,7 +270,7 @@ namespace client { if(ecode) { - LogPrint (eLogError, "BOB: command channel read error: ", ecode.message()); + LogPrint (eLogError, "BOB: Command channel read error: ", ecode.message()); if (ecode != boost::asio::error::operation_aborted) Terminate (); } @@ -292,7 +294,7 @@ namespace client } else { - LogPrint (eLogError, "BOB: unknown command ", command.c_str()); + LogPrint (eLogError, "BOB: Unknown command ", command.c_str()); SendReplyError ("unknown command"); } } @@ -310,7 +312,7 @@ namespace client { if (ecode) { - LogPrint (eLogError, "BOB: command channel send error: ", ecode.message ()); + LogPrint (eLogError, "BOB: Command channel send error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate (); } @@ -355,13 +357,13 @@ namespace client os << data << std::endl; } - void BOBCommandSession::BuildStatusLine(bool currentTunnel, BOBDestination *dest, std::string &out) + void BOBCommandSession::BuildStatusLine(bool currentTunnel, std::shared_ptr dest, std::string &out) { // helper lambdas const auto issetStr = [](const std::string &str) { return str.empty() ? "not_set" : str; }; // for inhost, outhost const auto issetNum = [&issetStr](const int p) { return issetStr(p == 0 ? "" : std::to_string(p)); }; // for inport, outport const auto destExists = [](const BOBDestination * const dest) { return dest != nullptr; }; - const auto destReady = [](const BOBDestination * const dest) { return dest->GetLocalDestination()->IsReady(); }; + 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 @@ -371,9 +373,9 @@ namespace client const std::string outhost = issetStr(currentTunnel ? m_OutHost : dest->GetOutHost()); const std::string inport = issetNum(currentTunnel ? m_InPort : dest->GetInPort()); const std::string outport = issetNum(currentTunnel ? m_OutPort : dest->GetOutPort()); - const bool keys = destExists(dest); // key must exist when destination is created - const bool starting = destExists(dest) && !destReady(dest); - const bool running = destExists(dest) && destReady(dest); + const bool keys = destExists(dest.get ()); // key must exist when destination is created + const bool starting = destExists(dest.get ()) && !destReady(dest.get ()); + const bool running = destExists(dest.get ()) && destReady(dest.get ()); const bool stopping = false; // build line @@ -444,7 +446,7 @@ namespace client if (!m_CurrentDestination) { - m_CurrentDestination = new BOBDestination (i2p::client::context.CreateNewLocalDestination (m_Keys, true, &m_Options), // deleted in clear command + m_CurrentDestination = std::make_shared (i2p::client::context.CreateNewLocalDestination (m_Keys, true, &m_Options), // deleted in clear command m_Nickname, m_InHost, m_OutHost, m_InPort, m_OutPort, m_IsQuiet); m_Owner.AddDestination (m_Nickname, m_CurrentDestination); } @@ -479,26 +481,43 @@ namespace client void BOBCommandSession::SetNickCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: setnick ", operand); - m_Nickname = operand; - std::string msg ("Nickname set to "); - msg += m_Nickname; - SendReplyOK (msg.c_str ()); + 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); - m_CurrentDestination = m_Owner.FindDestination (operand); - if (m_CurrentDestination) + if(*operand) { - m_Keys = m_CurrentDestination->GetKeys (); - m_Nickname = operand; - } - if (m_Nickname == operand) - { - std::string msg ("Nickname set to "); - msg += m_Nickname; - SendReplyOK (msg.c_str ()); + 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"); @@ -523,19 +542,19 @@ namespace client } catch (std::invalid_argument& ex) { - LogPrint (eLogWarning, "BOB: newkeys ", ex.what ()); + LogPrint (eLogWarning, "BOB: Error on newkeys: ", ex.what ()); } } - m_Keys = i2p::data::PrivateKeys::CreateRandomKeys (signatureType, cryptoType); + 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 (m_Keys.FromBase64 (operand)) + if (*operand && m_Keys.FromBase64 (operand)) SendReplyOK (m_Keys.GetPublic ()->ToBase64 ().c_str ()); else SendReplyError ("invalid keys"); @@ -562,35 +581,61 @@ namespace client void BOBCommandSession::OuthostCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: outhost ", operand); - m_OutHost = operand; - SendReplyOK ("outhost set"); + 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); - m_OutPort = std::stoi(operand); - if (m_OutPort >= 0) - SendReplyOK ("outbound port set"); + 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 ("port out of range"); + SendReplyError ("empty outport"); } void BOBCommandSession::InhostCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: inhost ", operand); - m_InHost = operand; - SendReplyOK ("inhost set"); + 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); - m_InPort = std::stoi(operand); - if (m_InPort >= 0) - SendReplyOK ("inbound port set"); + 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 ("port out of range"); + SendReplyError ("empty inport"); } void BOBCommandSession::QuietCommandHandler (const char * operand, size_t len) @@ -613,36 +658,68 @@ namespace client void BOBCommandSession::LookupCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: lookup ", operand); - auto addr = context.GetAddressBook ().GetAddress (operand); - if (!addr) + if (*operand) { - SendReplyError ("Address Not found"); - return; - } - auto localDestination = m_CurrentDestination ? m_CurrentDestination->GetLocalDestination () : i2p::client::context.GetSharedLocalDestination (); - if (addr->IsIdentHash ()) - { - // we might have leaseset already - auto leaseSet = localDestination->FindLeaseSet (addr->identHash); - if (leaseSet) + auto addr = context.GetAddressBook ().GetAddress (operand); + if (!addr) { - SendReplyOK (leaseSet->GetIdentity ()->ToBase64 ().c_str ()); + SendReplyError ("Address Not found"); return; } - } - // trying to request - auto s = shared_from_this (); - auto requstCallback = [s](std::shared_ptr ls) + auto localDestination = (m_CurrentDestination && m_CurrentDestination->IsRunning ()) ? + m_CurrentDestination->GetLocalDestination () : i2p::client::context.GetSharedLocalDestination (); + if (!localDestination) { - if (ls) - s->SendReplyOK (ls->GetIdentity ()->ToBase64 ().c_str ()); - else - s->SendReplyError ("LeaseSet Not found"); - }; - if (addr->IsIdentHash ()) - localDestination->RequestDestination (addr->identHash, requstCallback); + 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 - localDestination->RequestDestinationWithEncryptedLeaseSet (addr->blindedPublicKey, requstCallback); + 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) @@ -688,7 +765,7 @@ namespace client msg += operand; *(const_cast(value)) = '='; msg += " set to "; - msg += value; + msg += value + 1; SendReplyOK (msg.c_str ()); } else @@ -702,11 +779,11 @@ namespace client std::string statusLine; // always prefer destination - auto ptr = m_Owner.FindDestination(name); - if(ptr != nullptr) + auto dest = m_Owner.FindDestination(name); + if(dest) { // tunnel destination exists - BuildStatusLine(false, ptr, statusLine); + BuildStatusLine(false, dest, statusLine); SendReplyOK(statusLine.c_str()); } else @@ -726,7 +803,7 @@ namespace client void BOBCommandSession::HelpCommandHandler (const char * operand, size_t len) { auto helpStrings = m_Owner.GetHelpStrings(); - if(len == 0) + if(!*operand) { std::stringstream ss; ss << "COMMANDS:"; @@ -749,7 +826,7 @@ namespace client } } - BOBCommandChannel::BOBCommandChannel (const std::string& address, int port): + BOBCommandChannel::BOBCommandChannel (const std::string& address, uint16_t port): RunnableService ("BOB"), m_Acceptor (GetIOService (), boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(address), port)) { @@ -770,6 +847,7 @@ namespace client m_CommandHandlers[BOB_COMMAND_INPORT] = &BOBCommandSession::InportCommandHandler; m_CommandHandlers[BOB_COMMAND_QUIET] = &BOBCommandSession::QuietCommandHandler; m_CommandHandlers[BOB_COMMAND_LOOKUP] = &BOBCommandSession::LookupCommandHandler; + m_CommandHandlers[BOB_COMMAND_LOOKUP_LOCAL] = &BOBCommandSession::LookupLocalCommandHandler; m_CommandHandlers[BOB_COMMAND_CLEAR] = &BOBCommandSession::ClearCommandHandler; m_CommandHandlers[BOB_COMMAND_LIST] = &BOBCommandSession::ListCommandHandler; m_CommandHandlers[BOB_COMMAND_OPTION] = &BOBCommandSession::OptionCommandHandler; @@ -803,8 +881,6 @@ namespace client { if (IsRunning ()) Stop (); - for (const auto& it: m_Destinations) - delete it.second; } void BOBCommandChannel::Start () @@ -821,9 +897,9 @@ namespace client StopIOService (); } - void BOBCommandChannel::AddDestination (const std::string& name, BOBDestination * dest) + void BOBCommandChannel::AddDestination (const std::string& name, std::shared_ptr dest) { - m_Destinations[name] = dest; + m_Destinations.emplace (name, dest); } void BOBCommandChannel::DeleteDestination (const std::string& name) @@ -832,12 +908,11 @@ namespace client if (it != m_Destinations.end ()) { it->second->Stop (); - delete it->second; m_Destinations.erase (it); } } - BOBDestination * BOBCommandChannel::FindDestination (const std::string& name) + std::shared_ptr BOBCommandChannel::FindDestination (const std::string& name) { auto it = m_Destinations.find (name); if (it != m_Destinations.end ()) @@ -863,7 +938,7 @@ namespace client session->SendVersion (); } else - LogPrint (eLogError, "BOB: accept error: ", ecode.message ()); + LogPrint (eLogError, "BOB: Accept error: ", ecode.message ()); } } } diff --git a/libi2pd_client/BOB.h b/libi2pd_client/BOB.h index 74418011..1f5fda5f 100644 --- a/libi2pd_client/BOB.h +++ b/libi2pd_client/BOB.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -42,6 +42,7 @@ namespace client const char BOB_COMMAND_INPORT[] = "inport"; const char BOB_COMMAND_QUIET[] = "quiet"; const char BOB_COMMAND_LOOKUP[] = "lookup"; + const char BOB_COMMAND_LOOKUP_LOCAL[] = "lookuplocal"; const char BOB_COMMAND_CLEAR[] = "clear"; const char BOB_COMMAND_LIST[] = "list"; const char BOB_COMMAND_OPTION[] = "option"; @@ -62,7 +63,7 @@ namespace client 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 - Wether to send the incoming destination."; + 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."; @@ -123,7 +124,7 @@ namespace client { public: - BOBI2POutboundTunnel (const std::string& outhost, int port, std::shared_ptr localDestination, bool quiet); + BOBI2POutboundTunnel (const std::string& outhost, uint16_t port, std::shared_ptr localDestination, bool quiet); void Start (); void Stop (); @@ -148,20 +149,21 @@ namespace client BOBDestination (std::shared_ptr localDestination, const std::string &nickname, const std::string &inhost, const std::string &outhost, - const int inport, const int outport, const bool quiet); + const uint16_t inport, const uint16_t outport, const bool quiet); ~BOBDestination (); void Start (); void Stop (); void StopTunnels (); - void CreateInboundTunnel (int port, const std::string& inhost); - void CreateOutboundTunnel (const std::string& outhost, int port, bool quiet); + void CreateInboundTunnel (uint16_t port, const std::string& inhost); + void CreateOutboundTunnel (const std::string& outhost, uint16_t port, bool quiet); const std::string& GetNickname() const { return m_Nickname; } const std::string& GetInHost() const { return m_InHost; } const std::string& GetOutHost() const { return m_OutHost; } - int GetInPort() const { return m_InPort; } - int GetOutPort() const { return m_OutPort; } + uint16_t GetInPort() const { return m_InPort; } + uint16_t GetOutPort() const { return m_OutPort; } bool GetQuiet() const { return m_Quiet; } + bool IsRunning() const { return m_IsRunning; } const i2p::data::PrivateKeys& GetKeys () const { return m_LocalDestination->GetPrivateKeys (); }; std::shared_ptr GetLocalDestination () const { return m_LocalDestination; }; @@ -173,8 +175,9 @@ namespace client std::string m_Nickname; std::string m_InHost, m_OutHost; - int m_InPort, m_OutPort; + uint16_t m_InPort, m_OutPort; bool m_Quiet; + bool m_IsRunning; }; class BOBCommandChannel; @@ -206,6 +209,7 @@ namespace client void InportCommandHandler (const char * operand, size_t len); void QuietCommandHandler (const char * operand, size_t len); void LookupCommandHandler (const char * operand, size_t len); + void LookupLocalCommandHandler (const char * operand, size_t len); void ClearCommandHandler (const char * operand, size_t len); void ListCommandHandler (const char * operand, size_t len); void OptionCommandHandler (const char * operand, size_t len); @@ -224,7 +228,7 @@ namespace client void SendReplyError (const char * msg); void SendRaw (const char * data); - void BuildStatusLine(bool currentTunnel, BOBDestination *destination, std::string &out); + void BuildStatusLine(bool currentTunnel, std::shared_ptr destination, std::string &out); private: @@ -233,10 +237,10 @@ namespace client boost::asio::streambuf m_ReceiveBuffer, m_SendBuffer; bool m_IsOpen, m_IsQuiet, m_IsActive; std::string m_Nickname, m_InHost, m_OutHost; - int m_InPort, m_OutPort; + uint16_t m_InPort, m_OutPort; i2p::data::PrivateKeys m_Keys; std::map m_Options; - BOBDestination * m_CurrentDestination; + std::shared_ptr m_CurrentDestination; }; typedef void (BOBCommandSession::*BOBCommandHandler)(const char * operand, size_t len); @@ -244,16 +248,16 @@ namespace client { public: - BOBCommandChannel (const std::string& address, int port); + BOBCommandChannel (const std::string& address, uint16_t port); ~BOBCommandChannel (); void Start (); void Stop (); boost::asio::io_service& GetService () { return GetIOService (); }; - void AddDestination (const std::string& name, BOBDestination * dest); + void AddDestination (const std::string& name, std::shared_ptr dest); void DeleteDestination (const std::string& name); - BOBDestination * FindDestination (const std::string& name); + std::shared_ptr FindDestination (const std::string& name); private: @@ -263,7 +267,7 @@ namespace client private: boost::asio::ip::tcp::acceptor m_Acceptor; - std::map m_Destinations; + std::map > m_Destinations; std::map m_CommandHandlers; std::map m_HelpStrings; diff --git a/libi2pd_client/ClientContext.cpp b/libi2pd_client/ClientContext.cpp index 04f50e6f..d8c0bd2d 100644 --- a/libi2pd_client/ClientContext.cpp +++ b/libi2pd_client/ClientContext.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -63,18 +63,19 @@ namespace client if (sam) { std::string samAddr; i2p::config::GetOption("sam.address", samAddr); - uint16_t samPort; i2p::config::GetOption("sam.port", samPort); + uint16_t samPortTCP; i2p::config::GetOption("sam.port", samPortTCP); + uint16_t samPortUDP; i2p::config::GetOption("sam.portudp", samPortUDP); bool singleThread; i2p::config::GetOption("sam.singlethread", singleThread); - LogPrint(eLogInfo, "Clients: starting SAM bridge at ", samAddr, ":", samPort); + LogPrint(eLogInfo, "Clients: Starting SAM bridge at ", samAddr, ":[", samPortTCP, "|", samPortUDP, "]"); try { - m_SamBridge = new SAMBridge (samAddr, samPort, singleThread); + m_SamBridge = new SAMBridge (samAddr, samPortTCP, samPortUDP, singleThread); m_SamBridge->Start (); } catch (std::exception& e) { - LogPrint(eLogError, "Clients: Exception in SAM bridge: ", e.what()); - ThrowFatal ("Unable to start SAM bridge at ", samAddr, ":", samPort, ": ", e.what ()); + LogPrint(eLogCritical, "Clients: Exception in SAM bridge: ", e.what()); + ThrowFatal ("Unable to start SAM bridge at ", samAddr, ":[", samPortTCP, "|", samPortUDP,"]: ", e.what ()); } } @@ -83,7 +84,7 @@ namespace client if (bob) { std::string bobAddr; i2p::config::GetOption("bob.address", bobAddr); uint16_t bobPort; i2p::config::GetOption("bob.port", bobPort); - LogPrint(eLogInfo, "Clients: starting BOB command channel at ", bobAddr, ":", bobPort); + LogPrint(eLogInfo, "Clients: Starting BOB command channel at ", bobAddr, ":", bobPort); try { m_BOBCommandChannel = new BOBCommandChannel (bobAddr, bobPort); @@ -91,7 +92,7 @@ namespace client } catch (std::exception& e) { - LogPrint(eLogError, "Clients: Exception in BOB bridge: ", e.what()); + LogPrint(eLogCritical, "Clients: Exception in BOB bridge: ", e.what()); ThrowFatal ("Unable to start BOB bridge at ", bobAddr, ":", bobPort, ": ", e.what ()); } } @@ -103,7 +104,7 @@ namespace client std::string i2cpAddr; i2p::config::GetOption("i2cp.address", i2cpAddr); uint16_t i2cpPort; i2p::config::GetOption("i2cp.port", i2cpPort); bool singleThread; i2p::config::GetOption("i2cp.singlethread", singleThread); - LogPrint(eLogInfo, "Clients: starting I2CP at ", i2cpAddr, ":", i2cpPort); + LogPrint(eLogInfo, "Clients: Starting I2CP at ", i2cpAddr, ":", i2cpPort); try { m_I2CPServer = new I2CPServer (i2cpAddr, i2cpPort, singleThread); @@ -111,7 +112,7 @@ namespace client } catch (std::exception& e) { - LogPrint(eLogError, "Clients: Exception in I2CP: ", e.what()); + LogPrint(eLogCritical, "Clients: Exception in I2CP: ", e.what()); ThrowFatal ("Unable to start I2CP at ", i2cpAddr, ":", i2cpPort, ": ", e.what ()); } } @@ -130,7 +131,7 @@ namespace client { if (m_HttpProxy) { - LogPrint(eLogInfo, "Clients: stopping HTTP Proxy"); + LogPrint(eLogInfo, "Clients: Stopping HTTP Proxy"); m_HttpProxy->Stop(); delete m_HttpProxy; m_HttpProxy = nullptr; @@ -138,7 +139,7 @@ namespace client if (m_SocksProxy) { - LogPrint(eLogInfo, "Clients: stopping SOCKS Proxy"); + LogPrint(eLogInfo, "Clients: Stopping SOCKS Proxy"); m_SocksProxy->Stop(); delete m_SocksProxy; m_SocksProxy = nullptr; @@ -146,21 +147,21 @@ namespace client for (auto& it: m_ClientTunnels) { - LogPrint(eLogInfo, "Clients: stopping I2P client tunnel on port ", it.first); + LogPrint(eLogInfo, "Clients: Stopping I2P client tunnel on port ", it.first); it.second->Stop (); } m_ClientTunnels.clear (); for (auto& it: m_ServerTunnels) { - LogPrint(eLogInfo, "Clients: stopping I2P server tunnel"); + LogPrint(eLogInfo, "Clients: Stopping I2P server tunnel"); it.second->Stop (); } m_ServerTunnels.clear (); if (m_SamBridge) { - LogPrint(eLogInfo, "Clients: stopping SAM bridge"); + LogPrint(eLogInfo, "Clients: Stopping SAM bridge"); m_SamBridge->Stop (); delete m_SamBridge; m_SamBridge = nullptr; @@ -168,7 +169,7 @@ namespace client if (m_BOBCommandChannel) { - LogPrint(eLogInfo, "Clients: stopping BOB command channel"); + LogPrint(eLogInfo, "Clients: Stopping BOB command channel"); m_BOBCommandChannel->Stop (); delete m_BOBCommandChannel; m_BOBCommandChannel = nullptr; @@ -176,30 +177,40 @@ namespace client if (m_I2CPServer) { - LogPrint(eLogInfo, "Clients: stopping I2CP"); + LogPrint(eLogInfo, "Clients: Stopping I2CP"); m_I2CPServer->Stop (); delete m_I2CPServer; m_I2CPServer = nullptr; } - LogPrint(eLogInfo, "Clients: stopping AddressBook"); + LogPrint(eLogInfo, "Clients: Stopping AddressBook"); m_AddressBook.Stop (); - { + LogPrint(eLogInfo, "Clients: Stopping UDP Tunnels"); + { std::lock_guard lock(m_ForwardsMutex); m_ServerForwards.clear(); m_ClientForwards.clear(); } + LogPrint(eLogInfo, "Clients: Stopping UDP Tunnels timers"); if (m_CleanupUDPTimer) { m_CleanupUDPTimer->cancel (); m_CleanupUDPTimer = nullptr; } - for (auto& it: m_Destinations) - it.second->Stop (); - m_Destinations.clear (); + { + LogPrint(eLogInfo, "Clients: Stopping Destinations"); + std::lock_guard lock(m_DestinationsMutex); + for (auto& it: m_Destinations) + it.second->Stop (); + LogPrint(eLogInfo, "Clients: Stopping Destinations - Clear"); + m_Destinations.clear (); + } + + LogPrint(eLogInfo, "Clients: Stopping SharedLocalDestination"); + m_SharedLocalDestination->Release (); m_SharedLocalDestination = nullptr; } @@ -209,14 +220,6 @@ namespace client /*std::string config; i2p::config::GetOption("conf", config); i2p::config::ParseConfig(config);*/ - // handle tunnels - // reset isUpdated for each tunnel - VisitTunnels ([](I2PService * s)->bool { s->isUpdated = false; return true; }); - // reload tunnels - ReadTunnels(); - // delete not updated tunnels (not in config anymore) - VisitTunnels ([](I2PService * s)->bool { return s->isUpdated; }); - // change shared local destination m_SharedLocalDestination->Release (); CreateNewSharedLocalDestination (); @@ -225,6 +228,7 @@ namespace client if (m_HttpProxy) { m_HttpProxy->Stop (); + delete m_HttpProxy; m_HttpProxy = nullptr; } ReadHttpProxy (); @@ -233,10 +237,19 @@ namespace client if (m_SocksProxy) { m_SocksProxy->Stop (); + delete m_SocksProxy; m_SocksProxy = nullptr; } ReadSocksProxy (); + // handle tunnels + // reset isUpdated for each tunnel + VisitTunnels (false); + // reload tunnels + ReadTunnels(); + // delete not updated tunnels (not in config anymore) + VisitTunnels (true); + // delete unused destinations std::unique_lock l(m_DestinationsMutex); for (auto it = m_Destinations.begin (); it != m_Destinations.end ();) @@ -257,7 +270,7 @@ namespace client static const std::string transient("transient"); if (!filename.compare (0, transient.length (), transient)) // starts with transient { - keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType, cryptoType); + keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType, cryptoType, true); LogPrint (eLogInfo, "Clients: New transient keys address ", m_AddressBook.ToAddress(keys.GetPublic ()->GetIdentHash ()), " created"); return true; } @@ -274,7 +287,7 @@ namespace client s.read ((char *)buf, len); if(!keys.FromBuffer (buf, len)) { - LogPrint (eLogError, "Clients: failed to load keyfile ", filename); + LogPrint (eLogCritical, "Clients: Failed to load keyfile ", filename); success = false; } else @@ -283,8 +296,8 @@ namespace client } else { - LogPrint (eLogError, "Clients: can't open file ", fullPath, " Creating new one with signature type ", sigType, " crypto type ", cryptoType); - keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType, cryptoType); + 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]; @@ -324,7 +337,7 @@ namespace client i2p::data::SigningKeyType sigType, i2p::data::CryptoKeyType cryptoType, const std::map * params) { - i2p::data::PrivateKeys keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType, cryptoType); + i2p::data::PrivateKeys keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType, cryptoType, true); auto localDestination = std::make_shared (keys, isPublic, params); AddLocalDestination (localDestination); return localDestination; @@ -335,7 +348,7 @@ namespace client i2p::data::SigningKeyType sigType, i2p::data::CryptoKeyType cryptoType, const std::map * params) { - i2p::data::PrivateKeys keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType, cryptoType); + i2p::data::PrivateKeys keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType, cryptoType, true); auto localDestination = std::make_shared (service, keys, isPublic, params); AddLocalDestination (localDestination); return localDestination; @@ -402,11 +415,12 @@ namespace client void ClientContext::CreateNewSharedLocalDestination () { - std::map params + std::map params { - { I2CP_PARAM_INBOUND_TUNNELS_QUANTITY, "2" }, - { I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY, "2" }, - { I2CP_PARAM_LEASESET_TYPE, "3" } + { I2CP_PARAM_INBOUND_TUNNELS_QUANTITY, "3" }, + { I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY, "3" }, + { I2CP_PARAM_LEASESET_TYPE, "3" }, + { I2CP_PARAM_LEASESET_ENCRYPTION_TYPE, "0,4" } }; m_SharedLocalDestination = CreateNewLocalDestination (false, i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519, i2p::data::CRYPTO_KEY_TYPE_ELGAMAL, ¶ms); // non-public, EDDSA @@ -450,13 +464,15 @@ namespace client 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_ANSWER_PINGS] = GetI2CPOption(section, I2CP_PARAM_STREAMING_ANSWER_PINGS, isServer ? DEFAULT_ANSWER_PINGS : false); options[I2CP_PARAM_LEASESET_TYPE] = GetI2CPOption(section, I2CP_PARAM_LEASESET_TYPE, DEFAULT_LEASESET_TYPE); - std::string encType = GetI2CPStringOption(section, I2CP_PARAM_LEASESET_ENCRYPTION_TYPE, ""); + std::string encType = GetI2CPStringOption(section, I2CP_PARAM_LEASESET_ENCRYPTION_TYPE, "0,4"); if (encType.length () > 0) options[I2CP_PARAM_LEASESET_ENCRYPTION_TYPE] = encType; std::string privKey = GetI2CPStringOption(section, I2CP_PARAM_LEASESET_PRIV_KEY, ""); if (privKey.length () > 0) options[I2CP_PARAM_LEASESET_PRIV_KEY] = privKey; @@ -482,10 +498,14 @@ namespace client options[I2CP_PARAM_INBOUND_TUNNEL_LENGTH] = value; if (i2p::config::GetOption(prefix + I2CP_PARAM_INBOUND_TUNNELS_QUANTITY, value)) options[I2CP_PARAM_INBOUND_TUNNELS_QUANTITY] = value; + if (i2p::config::GetOption(prefix + I2CP_PARAM_INBOUND_TUNNELS_LENGTH_VARIANCE, value)) + options[I2CP_PARAM_INBOUND_TUNNELS_LENGTH_VARIANCE] = value; if (i2p::config::GetOption(prefix + I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH, value)) options[I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH] = value; if (i2p::config::GetOption(prefix + I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY, value)) options[I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY] = value; + if (i2p::config::GetOption(prefix + I2CP_PARAM_OUTBOUND_TUNNELS_LENGTH_VARIANCE, value)) + options[I2CP_PARAM_OUTBOUND_TUNNELS_LENGTH_VARIANCE] = value; if (i2p::config::GetOption(prefix + I2CP_PARAM_MIN_TUNNEL_LATENCY, value)) options[I2CP_PARAM_MIN_TUNNEL_LATENCY] = value; if (i2p::config::GetOption(prefix + I2CP_PARAM_MAX_TUNNEL_LATENCY, value)) @@ -494,6 +514,8 @@ namespace client 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; } void ClientContext::ReadTunnels () @@ -501,20 +523,15 @@ namespace client int numClientTunnels = 0, numServerTunnels = 0; std::string tunConf; i2p::config::GetOption("tunconf", tunConf); if (tunConf.empty ()) - { - // TODO: cleanup this in 2.8.0 - tunConf = i2p::fs::DataDirPath ("tunnels.cfg"); - if (i2p::fs::Exists(tunConf)) - LogPrint(eLogWarning, "Clients: please rename tunnels.cfg -> tunnels.conf here: ", tunConf); - else - tunConf = i2p::fs::DataDirPath ("tunnels.conf"); - } - LogPrint(eLogDebug, "Clients: tunnels config file: ", tunConf); + 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; @@ -523,7 +540,7 @@ namespace client 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); + LogPrint(eLogDebug, "Clients: Tunnels extra config file: ", it); ReadTunnels (it, numClientTunnels, numServerTunnels); } } @@ -560,12 +577,12 @@ namespace client std::string dest; if (type == I2P_TUNNELS_SECTION_TYPE_CLIENT || type == I2P_TUNNELS_SECTION_TYPE_UDPCLIENT) dest = section.second.get (I2P_CLIENT_TUNNEL_DESTINATION); - int port = section.second.get (I2P_CLIENT_TUNNEL_PORT); + uint16_t port = section.second.get (I2P_CLIENT_TUNNEL_PORT); // optional params - bool matchTunnels = section.second.get(I2P_CLIENT_TUNNEL_MATCH_TUNNELS, false); - std::string keys = section.second.get (I2P_CLIENT_TUNNEL_KEYS, "transient"); - std::string address = section.second.get (I2P_CLIENT_TUNNEL_ADDRESS, "127.0.0.1"); - int destinationPort = section.second.get (I2P_CLIENT_TUNNEL_DESTINATION_PORT, 0); + bool matchTunnels = section.second.get (I2P_CLIENT_TUNNEL_MATCH_TUNNELS, false); + std::string keys = section.second.get (I2P_CLIENT_TUNNEL_KEYS, "transient"); + std::string address = section.second.get (I2P_CLIENT_TUNNEL_ADDRESS, "127.0.0.1"); + uint16_t destinationPort = section.second.get (I2P_CLIENT_TUNNEL_DESTINATION_PORT, 0); i2p::data::SigningKeyType sigType = section.second.get (I2P_CLIENT_TUNNEL_SIGNATURE_TYPE, i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519); i2p::data::CryptoKeyType cryptoType = section.second.get (I2P_CLIENT_TUNNEL_CRYPTO_TYPE, i2p::data::CRYPTO_KEY_TYPE_ELGAMAL); // I2CP @@ -579,7 +596,7 @@ namespace client if (it != destinations.end ()) localDestination = it->second; else - { + { i2p::data::PrivateKeys k; if(LoadPrivateKeys (k, keys, sigType, cryptoType)) { @@ -590,25 +607,42 @@ namespace client localDestination = CreateNewMatchedTunnelDestination(k, dest, &options); else localDestination = CreateNewLocalDestination (k, type == I2P_TUNNELS_SECTION_TYPE_UDPCLIENT, &options); - destinations[keys] = localDestination; + 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::address::from_string(address), port); + boost::asio::ip::udp::endpoint end (boost::asio::ip::address::from_string(address), port); if (!localDestination) localDestination = m_SharedLocalDestination; bool gzip = section.second.get (I2P_CLIENT_TUNNEL_GZIP, true); - auto clientTunnel = std::make_shared(name, dest, end, localDestination, destinationPort, gzip); - if(m_ClientForwards.insert(std::make_pair(end, clientTunnel)).second) - clientTunnel->Start(); + 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; @@ -641,6 +675,13 @@ namespace client auto tun = std::make_shared (name, dest, address, port, localDestination, destinationPort); clientTunnel = tun; clientEndpoint = tun->GetLocalEndpoint (); + + uint32_t keepAlive = section.second.get(I2P_CLIENT_TUNNEL_KEEP_ALIVE_INTERVAL, 0); + if (keepAlive) + { + tun->SetKeepAliveInterval (keepAlive); + LogPrint(eLogInfo, "Clients: I2P Client tunnel keep alive interval set to ", keepAlive); + } } uint32_t timeout = section.second.get(I2P_CLIENT_TUNNEL_CONNECT_TIMEOUT, 0); @@ -662,13 +703,16 @@ namespace client if (ins.first->second->GetLocalDestination () != clientTunnel->GetLocalDestination ()) { LogPrint (eLogInfo, "Clients: I2P client tunnel destination updated"); + ins.first->second->Stop (); ins.first->second->SetLocalDestination (clientTunnel->GetLocalDestination ()); + ins.first->second->Start (); } ins.first->second->isUpdated = true; LogPrint (eLogInfo, "Clients: I2P client tunnel for endpoint ", clientEndpoint, " already exists"); } } } + else if (type == I2P_TUNNELS_SECTION_TYPE_SERVER || type == I2P_TUNNELS_SECTION_TYPE_HTTP || type == I2P_TUNNELS_SECTION_TYPE_IRC @@ -676,21 +720,22 @@ namespace client { // mandatory params std::string host = section.second.get (I2P_SERVER_TUNNEL_HOST); - int port = section.second.get (I2P_SERVER_TUNNEL_PORT); + uint16_t port = section.second.get (I2P_SERVER_TUNNEL_PORT); std::string keys = section.second.get (I2P_SERVER_TUNNEL_KEYS); // optional params - int inPort = section.second.get (I2P_SERVER_TUNNEL_INPORT, 0); - std::string accessList = section.second.get (I2P_SERVER_TUNNEL_ACCESS_LIST, ""); + uint16_t inPort = section.second.get (I2P_SERVER_TUNNEL_INPORT, port); + std::string accessList = section.second.get (I2P_SERVER_TUNNEL_ACCESS_LIST, ""); if(accessList == "") - accessList=section.second.get (I2P_SERVER_TUNNEL_WHITE_LIST, ""); - std::string hostOverride = section.second.get (I2P_SERVER_TUNNEL_HOST_OVERRIDE, ""); + accessList = section.second.get (I2P_SERVER_TUNNEL_WHITE_LIST, ""); + std::string hostOverride = section.second.get (I2P_SERVER_TUNNEL_HOST_OVERRIDE, ""); std::string webircpass = section.second.get (I2P_SERVER_TUNNEL_WEBIRC_PASSWORD, ""); - bool gzip = section.second.get (I2P_SERVER_TUNNEL_GZIP, true); + 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, "127.0.0.1"); - bool isUniqueLocal = section.second.get(I2P_SERVER_TUNNEL_ENABLE_UNIQUE_LOCAL, true); + 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; @@ -699,43 +744,57 @@ namespace client 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 - auto localAddress = boost::asio::ip::address::from_string(address); boost::asio::ip::udp::endpoint endpoint(boost::asio::ip::address::from_string(host), port); + if (address.empty ()) + { + if (!endpoint.address ().is_unspecified () && endpoint.address ().is_v6 ()) + address = "::1"; + else + address = "127.0.0.1"; + } + auto localAddress = boost::asio::ip::address::from_string(address); auto serverTunnel = std::make_shared(name, localDestination, localAddress, endpoint, port, gzip); if(!isUniqueLocal) { - LogPrint(eLogInfo, "Clients: disabling loopback address mapping"); + LogPrint(eLogInfo, "Clients: Disabling loopback address mapping"); serverTunnel->SetUniqueLocal(isUniqueLocal); } std::lock_guard lock(m_ForwardsMutex); - if(m_ServerForwards.insert( - std::make_pair( - std::make_pair( - localDestination->GetIdentHash(), port), - serverTunnel)).second) + 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 - LogPrint(eLogError, "Clients: I2P Server Forward for destination/port ", m_AddressBook.ToAddress(localDestination->GetIdentHash()), "/", port, "already exists"); + { + ins.first->second->isUpdated = true; + LogPrint(eLogError, "Clients: I2P Server Forward for destination/port ", m_AddressBook.ToAddress(localDestination->GetIdentHash()), "/", port, " already exists"); + } continue; } @@ -748,12 +807,15 @@ namespace client else // regular server tunnel by default serverTunnel = std::make_shared (name, host, port, localDestination, inPort, gzip); - if(!isUniqueLocal) + if (!address.empty ()) + serverTunnel->SetLocalAddress (address); + if (!isUniqueLocal) { - LogPrint(eLogInfo, "Clients: disabling loopback address mapping"); + LogPrint(eLogInfo, "Clients: Disabling loopback address mapping"); serverTunnel->SetUniqueLocal(isUniqueLocal); } - + if (ssl) + serverTunnel->SetSSL (true); if (accessList.length () > 0) { std::set idents; @@ -783,7 +845,9 @@ namespace client if (ins.first->second->GetLocalDestination () != serverTunnel->GetLocalDestination ()) { LogPrint (eLogInfo, "Clients: I2P server tunnel destination updated"); + ins.first->second->Stop (); ins.first->second->SetLocalDestination (serverTunnel->GetLocalDestination ()); + ins.first->second->Start (); } ins.first->second->isUpdated = true; LogPrint (eLogInfo, "Clients: I2P server tunnel for destination/port ", m_AddressBook.ToAddress(localDestination->GetIdentHash ()), "/", inPort, " already exists"); @@ -795,7 +859,7 @@ namespace client } catch (std::exception& ex) { - LogPrint (eLogError, "Clients: Can't read tunnel ", name, " params: ", ex.what ()); + LogPrint (eLogCritical, "Clients: Can't read tunnel ", name, " params: ", ex.what ()); ThrowFatal ("Unable to start tunnel ", name, ": ", ex.what ()); } } @@ -812,8 +876,10 @@ namespace client uint16_t httpProxyPort; i2p::config::GetOption("httpproxy.port", httpProxyPort); std::string httpOutProxyURL; i2p::config::GetOption("httpproxy.outproxy", httpOutProxyURL); bool httpAddresshelper; i2p::config::GetOption("httpproxy.addresshelper", httpAddresshelper); + 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); + LogPrint(eLogInfo, "Clients: Starting HTTP Proxy at ", httpProxyAddr, ":", httpProxyPort); if (httpProxyKeys.length () > 0) { i2p::data::PrivateKeys keys; @@ -825,7 +891,7 @@ namespace client if (localDestination) localDestination->Acquire (); } else - LogPrint(eLogError, "Clients: failed to load HTTP Proxy key"); + LogPrint(eLogCritical, "Clients: Failed to load HTTP Proxy key"); } try { @@ -834,7 +900,7 @@ namespace client } catch (std::exception& e) { - LogPrint(eLogError, "Clients: Exception in HTTP Proxy: ", e.what()); + LogPrint(eLogCritical, "Clients: Exception in HTTP Proxy: ", e.what()); ThrowFatal ("Unable to start HTTP Proxy at ", httpProxyAddr, ":", httpProxyPort, ": ", e.what ()); } } @@ -846,7 +912,7 @@ namespace client bool socksproxy; i2p::config::GetOption("socksproxy.enabled", socksproxy); if (socksproxy) { - std::string httpProxyKeys; i2p::config::GetOption("httpproxy.keys", httpProxyKeys); + 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); @@ -855,12 +921,12 @@ namespace client std::string socksOutProxyAddr; i2p::config::GetOption("socksproxy.outproxy", socksOutProxyAddr); uint16_t socksOutProxyPort; i2p::config::GetOption("socksproxy.outproxyport", socksOutProxyPort); i2p::data::SigningKeyType sigType; i2p::config::GetOption("socksproxy.signaturetype", sigType); - LogPrint(eLogInfo, "Clients: starting SOCKS Proxy at ", socksProxyAddr, ":", socksProxyPort); + 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; @@ -872,7 +938,7 @@ namespace client if (localDestination) localDestination->Acquire (); } else - LogPrint(eLogError, "Clients: failed to load SOCKS Proxy key"); + LogPrint(eLogCritical, "Clients: Failed to load SOCKS Proxy key"); } try { @@ -882,7 +948,7 @@ namespace client } catch (std::exception& e) { - LogPrint(eLogError, "Clients: Exception in SOCKS Proxy: ", e.what()); + LogPrint(eLogCritical, "Clients: Exception in SOCKS Proxy: ", e.what()); ThrowFatal ("Unable to start SOCKS Proxy at ", socksProxyAddr, ":", socksProxyPort, ": ", e.what ()); } } @@ -908,27 +974,52 @@ namespace client } } - template - void VisitTunnelsContainer (Container& c, Visitor v) + void ClientContext::VisitTunnels (bool clean) { - for (auto it = c.begin (); it != c.end ();) + for (auto it = m_ClientTunnels.begin (); it != m_ClientTunnels.end ();) { - if (!v (it->second.get ())) - { + if(clean && !it->second->isUpdated) { it->second->Stop (); - it = c.erase (it); - } - else + 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++; + } } } - - template - void ClientContext::VisitTunnels (Visitor v) - { - VisitTunnelsContainer (m_ClientTunnels, v); - VisitTunnelsContainer (m_ServerTunnels, v); - // TODO: implement UDP forwards - } } } diff --git a/libi2pd_client/ClientContext.h b/libi2pd_client/ClientContext.h index 076aaa5f..4e969a6b 100644 --- a/libi2pd_client/ClientContext.h +++ b/libi2pd_client/ClientContext.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -18,10 +18,12 @@ #include "HTTPProxy.h" #include "SOCKS.h" #include "I2PTunnel.h" +#include "UDPTunnel.h" #include "SAM.h" #include "BOB.h" #include "I2CP.h" #include "AddressBook.h" +#include "I18N_langs.h" namespace i2p { @@ -47,6 +49,7 @@ namespace client 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"; @@ -59,7 +62,7 @@ namespace client const char I2P_SERVER_TUNNEL_WEBIRC_PASSWORD[] = "webircpassword"; const char I2P_SERVER_TUNNEL_ADDRESS[] = "address"; const char I2P_SERVER_TUNNEL_ENABLE_UNIQUE_LOCAL[] = "enableuniquelocal"; - + const char I2P_SERVER_TUNNEL_SSL[] = "ssl"; class ClientContext { @@ -102,6 +105,10 @@ namespace client std::vector > GetForwardInfosFor(const i2p::data::IdentHash & destination); + // i18n + std::shared_ptr GetLanguage () { return m_Language; }; + void SetLanguage (const std::shared_ptr language) { m_Language = language; }; + private: void ReadTunnels (); @@ -121,8 +128,7 @@ namespace client void CleanupUDP(const boost::system::error_code & ecode); void ScheduleCleanupUDP(); - template - void VisitTunnels (Visitor v); // Visitor: (I2PService *) -> bool, true means retain + void VisitTunnels (bool clean); void CreateNewSharedLocalDestination (); void AddLocalDestination (std::shared_ptr localDestination); @@ -137,8 +143,8 @@ namespace client i2p::proxy::HTTPProxy * m_HttpProxy; i2p::proxy::SOCKSProxy * m_SocksProxy; - std::map > m_ClientTunnels; // local endpoint->tunnel - std::map, std::shared_ptr > m_ServerTunnels; // ->tunnel + std::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 @@ -150,6 +156,9 @@ namespace client std::unique_ptr m_CleanupUDPTimer; + // i18n + std::shared_ptr m_Language; + public: // for HTTP diff --git a/libi2pd_client/HTTPProxy.cpp b/libi2pd_client/HTTPProxy.cpp index 64080752..26b47d8b 100644 --- a/libi2pd_client/HTTPProxy.cpp +++ b/libi2pd_client/HTTPProxy.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -28,20 +28,32 @@ #include "I2PTunnel.h" #include "Config.h" #include "HTTP.h" +#include "I18N.h" namespace i2p { namespace proxy { - std::map jumpservices = { - { "inr.i2p", "http://joajgazyztfssty4w2on5oaqksz6tqoxbduy553y34mf4byv6gpq.b32.i2p/search/?q=" }, - { "stats.i2p", "http://7tbay5p4kzeekxvyvbf6v7eauazemsnnl2aoyqhg5jzpr5eke7tq.b32.i2p/cgi-bin/jump.cgi?a=" }, + 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" ; @@ -68,10 +80,11 @@ namespace proxy { void SentHTTPFailed(const boost::system::error_code & ecode); void HandleStreamRequestComplete (std::shared_ptr stream); /* error helpers */ - void GenericProxyError(const char *title, const char *description); - void GenericProxyInfo(const char *title, const char *description); - void HostNotFound(std::string & host); - void SendProxyError(std::string & content); + void GenericProxyError(const std::string& title, const std::string& description); + void GenericProxyInfo(const std::string& title, const std::string& description); + void HostNotFound(std::string& host); + void SendProxyError(std::string& content); + void SendRedirect(std::string& address); void ForwardToUpstreamProxy(); void HandleUpstreamHTTPProxyConnect(const boost::system::error_code & ec); @@ -100,7 +113,7 @@ namespace proxy { i2p::http::URL m_ProxyURL; i2p::http::URL m_RequestURL; uint8_t m_socks_buf[255+8]; // for socks request/response - ssize_t m_req_len; + int m_req_len; i2p::http::URL m_ClientRequestURL; i2p::http::HTTPReq m_ClientRequest; i2p::http::HTTPRes m_ClientResponse; @@ -120,9 +133,9 @@ namespace proxy { void HTTPReqHandler::AsyncSockRead() { - LogPrint(eLogDebug, "HTTPProxy: async sock read"); + LogPrint(eLogDebug, "HTTPProxy: Async sock read"); if (!m_sock) { - LogPrint(eLogError, "HTTPProxy: no socket for read"); + LogPrint(eLogError, "HTTPProxy: No socket for read"); return; } m_sock->async_read_some(boost::asio::buffer(m_recv_chunk, sizeof(m_recv_chunk)), @@ -134,13 +147,13 @@ namespace proxy { if (Kill()) return; if (m_sock) { - LogPrint(eLogDebug, "HTTPProxy: close sock"); + LogPrint(eLogDebug, "HTTPProxy: Close sock"); m_sock->close(); m_sock = nullptr; } if(m_proxysock) { - LogPrint(eLogDebug, "HTTPProxy: close proxysock"); + LogPrint(eLogDebug, "HTTPProxy: Close proxysock"); if(m_proxysock->is_open()) m_proxysock->close(); m_proxysock = nullptr; @@ -148,37 +161,40 @@ namespace proxy { Done(shared_from_this()); } - void HTTPReqHandler::GenericProxyError(const char *title, const char *description) { + void HTTPReqHandler::GenericProxyError(const std::string& title, const std::string& description) { std::stringstream ss; - ss << "

Proxy error: " << title << "

\r\n"; + ss << "

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

\r\n"; ss << "

" << description << "

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

Proxy info: " << title << "

\r\n"; + ss << "

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

\r\n"; ss << "

" << description << "

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

Proxy error: Host not found

\r\n" - << "

Remote host not found in router's addressbook

\r\n" - << "

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

\r\n" + 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"; std::string content = ss.str(); SendProxyError(content); } - void HTTPReqHandler::SendProxyError(std::string & content) + void HTTPReqHandler::SendProxyError(std::string& content) { i2p::http::HTTPRes res; res.code = 500; @@ -194,6 +210,17 @@ namespace proxy { std::bind(&HTTPReqHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); } + void HTTPReqHandler::SendRedirect(std::string& address) + { + i2p::http::HTTPRes res; + res.code = 302; + res.add_header("Location", address); + res.add_header("Connection", "close"); + std::string response = res.to_string(); + boost::asio::async_write(*m_sock, boost::asio::buffer(response), boost::asio::transfer_all(), + std::bind(&HTTPReqHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); + } + bool HTTPReqHandler::ExtractAddressHelper(i2p::http::URL & url, std::string & b64, bool & confirm) { confirm = false; @@ -211,8 +238,33 @@ namespace proxy { std::string value = params["i2paddresshelper"]; len += value.length(); b64 = i2p::http::UrlDecode(value); + // if we need update exists, request formed with update param - if (params["update"] == "true") { len += std::strlen("&update=true"); confirm = true; } + if (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; } @@ -223,6 +275,7 @@ namespace proxy { 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"); @@ -230,16 +283,28 @@ namespace proxy { /* replace headers */ 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 Referrer if requested URL with same schema, host and port, + * leave Referer if requested URL with same schema, host and port, * otherwise, drop it. */ - if(req.GetHeader("Referrer") != "") { + if(req.GetHeader("Referer") != "") { i2p::http::URL reqURL; reqURL.parse(req.uri); - i2p::http::URL refURL; refURL.parse(req.GetHeader("Referrer")); + 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("Referrer"); + req.RemoveHeader("Referer"); } /* add headers */ @@ -264,13 +329,13 @@ namespace proxy { return false; /* need more data */ if (m_req_len < 0) { - LogPrint(eLogError, "HTTPProxy: unable to parse request"); - GenericProxyError("Invalid request", "Proxy unable to parse your request"); + 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); + LogPrint(eLogDebug, "HTTPProxy: Requested: ", m_ClientRequest.uri); m_RequestURL.parse(m_ClientRequest.uri); bool m_Confirm; @@ -279,27 +344,64 @@ namespace proxy { { if (!m_Addresshelper) { - LogPrint(eLogWarning, "HTTPProxy: addresshelper request rejected"); - GenericProxyError("Invalid request", "addresshelper is not supported"); + LogPrint(eLogWarning, "HTTPProxy: Addresshelper request rejected"); + GenericProxyError(tr("Invalid request"), tr("Addresshelper is not supported")); return true; } - if (!i2p::client::context.GetAddressBook ().FindAddress (m_RequestURL.host) || m_Confirm) + + 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); + LogPrint (eLogInfo, "HTTPProxy: Added address from addresshelper for ", m_RequestURL.host); std::string full_url = m_RequestURL.to_string(); std::stringstream ss; - ss << "Host " << m_RequestURL.host << " added to router's addressbook from helper. " - << "Click here to proceed."; - GenericProxyInfo("Addresshelper found", ss.str().c_str()); + 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 << "Host " << m_RequestURL.host << " already in router's addressbook. " - << "Click here to update record."; - GenericProxyInfo("Addresshelper found", ss.str().c_str()); + 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 */ } } @@ -312,7 +414,7 @@ namespace proxy { auto pos = uri.find(":"); if(pos == std::string::npos || pos == uri.size() - 1) { - GenericProxyError("Invalid Request", "invalid request uri"); + GenericProxyError(tr("Invalid request"), tr("Invalid request URI")); return true; } else @@ -355,7 +457,7 @@ namespace proxy { else { /* relative url and missing 'Host' header */ - GenericProxyError("Invalid request", "Can't detect destination host from request"); + GenericProxyError(tr("Invalid request"), tr("Can't detect destination host from request")); return true; } } @@ -368,15 +470,15 @@ namespace proxy { } } else { if(m_OutproxyUrl.size()) { - LogPrint (eLogDebug, "HTTPProxy: use outproxy ", m_OutproxyUrl); + LogPrint (eLogDebug, "HTTPProxy: Using outproxy ", m_OutproxyUrl); if(m_ProxyURL.parse(m_OutproxyUrl)) ForwardToUpstreamProxy(); else - GenericProxyError("Outproxy failure", "bad outproxy settings"); + GenericProxyError(tr("Outproxy failure"), tr("Bad outproxy settings")); } else { - LogPrint (eLogWarning, "HTTPProxy: outproxy failure for ", dest_host, ": no outproxy enabled"); - std::string message = "Host " + dest_host + " not inside I2P network, but outproxy is not enabled"; - GenericProxyError("Outproxy failure", message.c_str()); + 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; } @@ -397,7 +499,7 @@ namespace proxy { m_send_buf = m_ClientRequest.to_string(); m_send_buf.append(m_recv_buf); /* connect to destination */ - LogPrint(eLogDebug, "HTTPProxy: connecting to host ", dest_host, ":", dest_port); + LogPrint(eLogDebug, "HTTPProxy: Connecting to host ", dest_host, ":", dest_port); GetOwner()->CreateStream (std::bind (&HTTPReqHandler::HandleStreamRequestComplete, shared_from_this(), std::placeholders::_1), dest_host, dest_port); return true; @@ -405,9 +507,9 @@ namespace proxy { void HTTPReqHandler::ForwardToUpstreamProxy() { - LogPrint(eLogDebug, "HTTPProxy: forward to upstream"); - // build http request + LogPrint(eLogDebug, "HTTPProxy: Forwarded to upstream"); + /* build http request */ m_ClientRequestURL = m_RequestURL; LogPrint(eLogDebug, "HTTPProxy: ", m_ClientRequestURL.host); m_ClientRequestURL.schema = ""; @@ -415,28 +517,28 @@ namespace proxy { std::string origURI = m_ClientRequest.uri; // TODO: what do we need to change uri for? m_ClientRequest.uri = m_ClientRequestURL.to_string(); - // update User-Agent to ESR version of Firefox, same as Tor Browser below version 8, for non-HTTPS connections + /* update User-Agent to ESR version of Firefox, same as Tor Browser below version 8, for non-HTTPS connections */ if(m_ClientRequest.method != "CONNECT") m_ClientRequest.UpdateHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; rv:60.0) Gecko/20100101 Firefox/60.0"); m_ClientRequest.write(m_ClientRequestBuffer); m_ClientRequestBuffer << m_recv_buf.substr(m_req_len); - // assume http if empty schema + /* assume http if empty schema */ if (m_ProxyURL.schema == "" || m_ProxyURL.schema == "http") { - // handle upstream http proxy + /* handle upstream http proxy */ if (!m_ProxyURL.port) m_ProxyURL.port = 80; if (m_ProxyURL.is_i2p()) { m_ClientRequest.uri = origURI; - if (!m_ProxyURL.user.empty () || !m_ProxyURL.pass.empty ()) + auto auth = i2p::http::CreateBasicAuthorizationString (m_ProxyURL.user, m_ProxyURL.pass); + if (!auth.empty ()) { - // remove existing authorization if any + /* remove existing authorization if any */ m_ClientRequest.RemoveHeader("Proxy-"); - // add own http proxy authorization - std::string s = "Basic " + i2p::data::ToBase64Standard (m_ProxyURL.user + ":" + m_ProxyURL.pass); - m_ClientRequest.AddHeader("Proxy-Authorization", s); + /* 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); @@ -454,7 +556,7 @@ namespace proxy { } else if (m_ProxyURL.schema == "socks") { - // handle upstream socks proxy + /* handle upstream socks proxy */ if (!m_ProxyURL.port) m_ProxyURL.port = 9050; // default to tor default if not specified boost::asio::ip::tcp::resolver::query q(m_ProxyURL.host, std::to_string(m_ProxyURL.port)); m_proxy_resolver.async_resolve(q, std::bind(&HTTPReqHandler::HandleUpstreamProxyResolved, this, std::placeholders::_1, std::placeholders::_2, [&](boost::asio::ip::tcp::endpoint ep) { @@ -463,14 +565,14 @@ namespace proxy { } else { - // unknown type, complain - GenericProxyError("unknown outproxy url", m_ProxyURL.to_string().c_str()); + /* 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::iterator it, ProxyResolvedHandler handler) { - if(ec) GenericProxyError("cannot resolve upstream proxy", ec.message().c_str()); + if(ec) GenericProxyError(tr("Cannot resolve upstream proxy"), ec.message()); else handler(*it); } @@ -478,12 +580,12 @@ namespace proxy { { if(!ec) { if(m_RequestURL.host.size() > 255) { - GenericProxyError("hostname too long", m_RequestURL.host.c_str()); + 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"); + LogPrint(eLogDebug, "HTTPProxy: Connected to SOCKS upstream"); std::string host = m_RequestURL.host; std::size_t reqsize = 0; @@ -505,19 +607,19 @@ namespace proxy { reqsize += host.size(); m_socks_buf[++reqsize] = 0; boost::asio::async_write(*m_proxysock, boost::asio::buffer(m_socks_buf, reqsize), boost::asio::transfer_all(), std::bind(&HTTPReqHandler::HandleSocksProxySendHandshake, this, std::placeholders::_1, std::placeholders::_2)); - } else GenericProxyError("cannot connect to upstream socks proxy", ec.message().c_str()); + } else GenericProxyError(tr("Cannot connect to upstream SOCKS proxy"), ec.message()); } void HTTPReqHandler::HandleSocksProxySendHandshake(const boost::system::error_code & ec, std::size_t bytes_transferred) { - LogPrint(eLogDebug, "HTTPProxy: upstream socks handshake sent"); - if(ec) GenericProxyError("Cannot negotiate with socks proxy", ec.message().c_str()); + LogPrint(eLogDebug, "HTTPProxy: Upstream SOCKS handshake sent"); + if(ec) GenericProxyError(tr("Cannot negotiate with SOCKS proxy"), ec.message()); else m_proxysock->async_read_some(boost::asio::buffer(m_socks_buf, 8), std::bind(&HTTPReqHandler::HandleSocksProxyReply, this, std::placeholders::_1, std::placeholders::_2)); } void HTTPReqHandler::HandoverToUpstreamProxy() { - LogPrint(eLogDebug, "HTTPProxy: handover to socks proxy"); + LogPrint(eLogDebug, "HTTPProxy: Handover to SOCKS proxy"); auto connection = std::make_shared(GetOwner(), m_proxysock, m_sock); m_sock = nullptr; m_proxysock = nullptr; @@ -553,7 +655,7 @@ namespace proxy { } else { - GenericProxyError("CONNECT error", "Failed to Connect"); + GenericProxyError(tr("CONNECT error"), tr("Failed to connect")); } } @@ -564,15 +666,15 @@ namespace proxy { m_send_buf = m_ClientResponse.to_string(); boost::asio::async_write(*m_sock, boost::asio::buffer(m_send_buf), boost::asio::transfer_all(), [&] (const boost::system::error_code & ec, std::size_t transferred) { - if(ec) GenericProxyError("socks proxy error", ec.message().c_str()); + 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"); + LogPrint(eLogDebug, "HTTPProxy: Send ", m_send_buf.size(), " bytes"); boost::asio::async_write(*m_proxysock, boost::asio::buffer(m_send_buf), boost::asio::transfer_all(), [&](const boost::system::error_code & ec, std::size_t transferred) { - if(ec) GenericProxyError("failed to send request to upstream", ec.message().c_str()); + if(ec) GenericProxyError(tr("Failed to send request to upstream"), ec.message()); else HandoverToUpstreamProxy(); }); } @@ -590,27 +692,27 @@ namespace proxy { ss << "error code: "; ss << (int) m_socks_buf[1]; std::string msg = ss.str(); - GenericProxyError("Socks Proxy error", msg.c_str()); + GenericProxyError(tr("SOCKS proxy error"), msg); } } - else GenericProxyError("No Reply From socks proxy", ec.message().c_str()); + else GenericProxyError(tr("No reply from SOCKS proxy"), ec.message()); } void HTTPReqHandler::HandleUpstreamHTTPProxyConnect(const boost::system::error_code & ec) { if(!ec) { - LogPrint(eLogDebug, "HTTPProxy: connected to http upstream"); - GenericProxyError("cannot connect", "http out proxy not implemented"); - } else GenericProxyError("cannot connect to upstream http proxy", ec.message().c_str()); + 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()); + LogPrint(eLogDebug, "HTTPProxy: Sock recv: ", len, " bytes, recv buf: ", m_recv_buf.length(), ", send buf: ", m_send_buf.length()); if(ecode) { - LogPrint(eLogWarning, "HTTPProxy: sock recv got error: ", ecode); + LogPrint(eLogWarning, "HTTPProxy: Sock recv got error: ", ecode); Terminate(); return; } @@ -633,8 +735,8 @@ namespace proxy { void HTTPReqHandler::HandleStreamRequestComplete (std::shared_ptr stream) { if (!stream) { - LogPrint (eLogError, "HTTPProxy: error when creating the stream, check the previous warnings for more info"); - GenericProxyError("Host is down", "Can't create connection to requested host, it may be down. Please try again later."); + LogPrint (eLogError, "HTTPProxy: Error when creating the stream, check the previous warnings for more info"); + GenericProxyError(tr("Host is down"), tr("Can't create connection to requested host, it may be down. Please try again later.")); return; } if (Kill()) @@ -646,7 +748,7 @@ namespace proxy { Done (shared_from_this()); } - HTTPProxy::HTTPProxy(const std::string& name, const std::string& address, int port, const std::string & outproxy, bool addresshelper, std::shared_ptr localDestination): + HTTPProxy::HTTPProxy(const std::string& name, const std::string& address, uint16_t port, const std::string & outproxy, bool addresshelper, std::shared_ptr localDestination): TCPIPAcceptor (address, port, localDestination ? localDestination : i2p::client::context.GetSharedLocalDestination ()), m_Name (name), m_OutproxyUrl (outproxy), m_Addresshelper (addresshelper) { diff --git a/libi2pd_client/HTTPProxy.h b/libi2pd_client/HTTPProxy.h index 69ed4cef..d819a53c 100644 --- a/libi2pd_client/HTTPProxy.h +++ b/libi2pd_client/HTTPProxy.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -15,8 +15,8 @@ namespace proxy { { public: - HTTPProxy(const std::string& name, const std::string& address, int port, const std::string & outproxy, bool addresshelper, std::shared_ptr localDestination); - HTTPProxy(const std::string& name, const std::string& address, int port, std::shared_ptr localDestination = nullptr) : + HTTPProxy(const std::string& name, const std::string& address, uint16_t port, const std::string & outproxy, bool addresshelper, 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, localDestination) {} ; ~HTTPProxy() {}; diff --git a/libi2pd_client/I2CP.cpp b/libi2pd_client/I2CP.cpp index 24c2496b..2c53e766 100644 --- a/libi2pd_client/I2CP.cpp +++ b/libi2pd_client/I2CP.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -23,13 +23,21 @@ namespace i2p namespace client { - I2CPDestination::I2CPDestination (boost::asio::io_service& service, std::shared_ptr owner, - std::shared_ptr identity, bool isPublic, const std::map& params): + I2CPDestination::I2CPDestination (boost::asio::io_service& service, std::shared_ptr owner, + std::shared_ptr identity, bool isPublic, const std::map& params): LeaseSetDestination (service, isPublic, ¶ms), - m_Owner (owner), m_Identity (identity), m_EncryptionKeyType (m_Identity->GetCryptoKeyType ()) + m_Owner (owner), m_Identity (identity), m_EncryptionKeyType (m_Identity->GetCryptoKeyType ()), + m_IsCreatingLeaseSet (false), m_LeaseSetCreationTimer (service) { } + void I2CPDestination::Stop () + { + LeaseSetDestination::Stop (); + m_Owner = nullptr; + m_LeaseSetCreationTimer.cancel (); + } + void I2CPDestination::SetEncryptionPrivateKey (const uint8_t * key) { m_Decryptor = i2p::data::PrivateKeys::CreateDecryptor (m_Identity->GetCryptoKeyType (), key); @@ -38,57 +46,88 @@ namespace client 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, BN_CTX * ctx, i2p::data::CryptoKeyType preferredCrypto) const + } + } + + 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_RATCHET && m_ECIESx25519Decryptor) - return m_ECIESx25519Decryptor->Decrypt (encrypted, data, ctx, true); + 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, ctx, true); + return m_Decryptor->Decrypt (encrypted, data); else - LogPrint (eLogError, "I2CP: decryptor is not set"); + LogPrint (eLogError, "I2CP: Decryptor is not set"); return false; } const uint8_t * I2CPDestination::GetEncryptionPublicKey (i2p::data::CryptoKeyType keyType) const { - if (keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET && m_ECIESx25519Decryptor) + 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_RATCHET ? (bool)m_ECIESx25519Decryptor : m_EncryptionKeyType == keyType; } - - + + 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; - m_Owner->SendMessagePayloadMessage (buf + 4, length); + if (m_Owner) + m_Owner->SendMessagePayloadMessage (buf + 4, length); } - void I2CPDestination::CreateNewLeaseSet (std::vector > tunnels) + void I2CPDestination::CreateNewLeaseSet (const std::vector >& tunnels) { + GetService ().post (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 (); leases[-1] = tunnels.size (); - htobe16buf (leases - 3, m_Owner->GetSessionID ()); - size_t l = 2/*sessionID*/ + 1/*num leases*/ + i2p::data::LEASE_SIZE*tunnels.size (); - m_Owner->SendI2CPMessage (I2CP_REQUEST_VARIABLE_LEASESET_MESSAGE, leases - 3, l); + if (m_Owner) + { + uint16_t sessionID = m_Owner->GetSessionID (); + if (sessionID != 0xFFFF) + { + m_IsCreatingLeaseSet = true; + htobe16buf (leases - 3, sessionID); + size_t l = 2/*sessionID*/ + 1/*num leases*/ + i2p::data::LEASE_SIZE*tunnels.size (); + m_Owner->SendI2CPMessage (I2CP_REQUEST_VARIABLE_LEASESET_MESSAGE, leases - 3, l); + m_LeaseSetCreationTimer.expires_from_now (boost::posix_time::seconds (I2CP_LEASESET_CREATION_TIMEOUT)); + auto s = GetSharedFromThis (); + 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 (); + } + }); + } + } } 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); @@ -96,6 +135,8 @@ namespace client void I2CPDestination::LeaseSet2Created (uint8_t storeType, const uint8_t * buf, size_t len) { + m_IsCreatingLeaseSet = false; + m_LeaseSetCreationTimer.cancel (); auto ls = (storeType == i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2) ? std::make_shared (m_Identity, buf, len): std::make_shared (storeType, m_Identity, buf, len); @@ -105,7 +146,7 @@ namespace client void I2CPDestination::SendMsgTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash& ident, uint32_t nonce) { - auto msg = NewI2NPMessage (); + auto msg = m_I2NPMsgsPool.AcquireSharedMt (); uint8_t * buf = msg->GetPayload (); htobe32buf (buf, len); memcpy (buf + 4, payload, len); @@ -119,7 +160,8 @@ namespace client [s, msg, remote, nonce]() { bool sent = s->SendMsg (msg, remote); - s->m_Owner->SendMessageStatusMessage (nonce, sent ? eI2CPMessageStatusGuaranteedSuccess : eI2CPMessageStatusGuaranteedFailure); + if (s->m_Owner) + s->m_Owner->SendMessageStatusMessage (nonce, sent ? eI2CPMessageStatusGuaranteedSuccess : eI2CPMessageStatusGuaranteedFailure); }); } else @@ -130,9 +172,10 @@ namespace client if (ls) { bool sent = s->SendMsg (msg, ls); - s->m_Owner->SendMessageStatusMessage (nonce, sent ? eI2CPMessageStatusGuaranteedSuccess : eI2CPMessageStatusGuaranteedFailure); + if (s->m_Owner) + s->m_Owner->SendMessageStatusMessage (nonce, sent ? eI2CPMessageStatusGuaranteedSuccess : eI2CPMessageStatusGuaranteedFailure); } - else + else if (s->m_Owner) s->m_Owner->SendMessageStatusMessage (nonce, eI2CPMessageStatusNoLeaseSet); }); } @@ -161,10 +204,16 @@ namespace client } else { - outboundTunnel = GetTunnelPool ()->GetNextOutboundTunnel (); - auto leases = remote->GetNonExpiredLeases (); + auto leases = remote->GetNonExpiredLeases (false); // without threshold + if (leases.empty ()) + leases = remote->GetNonExpiredLeases (true); // with threshold if (!leases.empty ()) + { remoteLease = leases[rand () % leases.size ()]; + auto leaseRouter = i2p::data::netdb.FindRouter (remoteLease->tunnelGateway); + outboundTunnel = GetTunnelPool ()->GetNextOutboundTunnel (nullptr, + leaseRouter ? leaseRouter->GetCompatibleTransports (false) : (i2p::data::RouterInfo::CompatibleTransports)i2p::data::RouterInfo::eAllTransports); + } if (remoteLease && outboundTunnel) remoteSession->SetSharedRoutingPath (std::make_shared ( i2p::garlic::GarlicRoutingPath{outboundTunnel, remoteLease, 10000, 0, 0})); // 10 secs RTT @@ -181,7 +230,7 @@ namespace client remoteLease->tunnelGateway, remoteLease->tunnelID, garlic }); - outboundTunnel->SendTunnelDataMsg (msgs); + outboundTunnel->SendTunnelDataMsgs (msgs); return true; } else @@ -194,18 +243,18 @@ namespace client } } - RunnableI2CPDestination::RunnableI2CPDestination (std::shared_ptr owner, + RunnableI2CPDestination::RunnableI2CPDestination (std::shared_ptr owner, std::shared_ptr identity, bool isPublic, const std::map& params): RunnableService ("I2CP"), I2CPDestination (GetIOService (), owner, identity, isPublic, params) { - } + } RunnableI2CPDestination::~RunnableI2CPDestination () { if (IsRunning ()) Stop (); - } + } void RunnableI2CPDestination::Start () { @@ -224,16 +273,16 @@ namespace client StopIOService (); } } - - I2CPSession::I2CPSession (I2CPServer& owner, std::shared_ptr socket): - m_Owner (owner), m_Socket (socket), m_Payload (nullptr), - m_SessionID (0xFFFF), m_MessageID (0), m_IsSendAccepted (true) + + I2CPSession::I2CPSession (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 () { - delete[] m_Payload; + Terminate (); } void I2CPSession::Start () @@ -264,6 +313,11 @@ namespace client void I2CPSession::ReceiveHeader () { + if (!m_Socket) + { + LogPrint (eLogError, "I2CP: Can't receive header"); + return; + } boost::asio::async_read (*m_Socket, boost::asio::buffer (m_Header, I2CP_HEADER_SIZE), boost::asio::transfer_all (), std::bind (&I2CPSession::HandleReceivedHeader, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); @@ -279,10 +333,7 @@ namespace client if (m_PayloadLen > 0) { if (m_PayloadLen <= I2CP_MAX_MESSAGE_LENGTH) - { - m_Payload = new uint8_t[m_PayloadLen]; ReceivePayload (); - } else { LogPrint (eLogError, "I2CP: Unexpected payload length ", m_PayloadLen); @@ -299,6 +350,11 @@ namespace client void I2CPSession::ReceivePayload () { + if (!m_Socket) + { + LogPrint (eLogError, "I2CP: Can't receive payload"); + return; + } boost::asio::async_read (*m_Socket, boost::asio::buffer (m_Payload, m_PayloadLen), boost::asio::transfer_all (), std::bind (&I2CPSession::HandleReceivedPayload, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); @@ -311,8 +367,6 @@ namespace client else { HandleMessage (); - delete[] m_Payload; - m_Payload = nullptr; m_PayloadLen = 0; ReceiveHeader (); // next message } @@ -339,33 +393,74 @@ namespace client m_Socket->close (); m_Socket = nullptr; } - m_Owner.RemoveSession (GetSessionID ()); - LogPrint (eLogDebug, "I2CP: session ", m_SessionID, " terminated"); + 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 socket = m_Socket; - if (socket) + auto l = len + I2CP_HEADER_SIZE; + if (l > I2CP_MAX_MESSAGE_LENGTH) { - auto l = len + I2CP_HEADER_SIZE; - uint8_t * buf = new uint8_t[l]; - htobe32buf (buf + I2CP_HEADER_LENGTH_OFFSET, len); - buf[I2CP_HEADER_TYPE_OFFSET] = type; - memcpy (buf + I2CP_HEADER_SIZE, payload, len); - boost::asio::async_write (*socket, boost::asio::buffer (buf, l), boost::asio::transfer_all (), - std::bind(&I2CPSession::HandleI2CPMessageSent, shared_from_this (), - std::placeholders::_1, std::placeholders::_2, buf)); + 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 - LogPrint (eLogError, "I2CP: Can't write to the socket"); + { + 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, const uint8_t * buf) + void I2CPSession::HandleI2CPMessageSent (const boost::system::error_code& ecode, std::size_t bytes_transferred) { - delete[] buf; - if (ecode && ecode != boost::asio::error::operation_aborted) - Terminate (); + 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 I2CPSession::ExtractString (const uint8_t * buf, size_t len) @@ -430,21 +525,26 @@ namespace client void I2CPSession::CreateSessionMessageHandler (const uint8_t * buf, size_t len) { RAND_bytes ((uint8_t *)&m_SessionID, 2); - m_Owner.InsertSession (shared_from_this ()); auto identity = std::make_shared(); size_t offset = identity->FromBuffer (buf, len); if (!offset) { - LogPrint (eLogError, "I2CP: create session malformed identity"); - SendSessionStatusMessage (3); // invalid + 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 (3); // invalid + LogPrint (eLogError, "I2CP: Options size ", optionsSize, "exceeds message size"); + SendSessionStatusMessage (eI2CPSessionStatusInvalid); // invalid return; } std::map params; @@ -455,44 +555,46 @@ namespace client offset += 8; // date if (identity->Verify (buf, offset, buf + offset)) // signature { - bool isPublic = true; - if (params[I2CP_PARAM_DONT_PUBLISH_LEASESET] == "true") isPublic = false; if (!m_Destination) { m_Destination = m_Owner.IsSingleThread () ? - std::make_shared(m_Owner.GetService (), shared_from_this (), identity, isPublic, params): - std::make_shared(shared_from_this (), identity, isPublic, params); - SendSessionStatusMessage (1); // created - LogPrint (eLogDebug, "I2CP: session ", m_SessionID, " created"); - m_Destination->Start (); + std::make_shared(m_Owner.GetService (), shared_from_this (), identity, true, params): + std::make_shared(shared_from_this (), identity, true, params); + if (m_Owner.InsertSession (shared_from_this ())) + { + SendSessionStatusMessage (eI2CPSessionStatusCreated); // created + LogPrint (eLogDebug, "I2CP: Session ", m_SessionID, " created"); + m_Destination->Start (); + } + else + { + LogPrint (eLogError, "I2CP: Session already exists"); + SendSessionStatusMessage (eI2CPSessionStatusRefused); + } } else { - LogPrint (eLogError, "I2CP: session already exists"); - SendSessionStatusMessage (4); // refused + LogPrint (eLogError, "I2CP: Session already exists"); + SendSessionStatusMessage (eI2CPSessionStatusRefused); // refused } } else { - LogPrint (eLogError, "I2CP: create session signature verification failed"); - SendSessionStatusMessage (3); // invalid + LogPrint (eLogError, "I2CP: Create session signature verification failed"); + SendSessionStatusMessage (eI2CPSessionStatusInvalid); // invalid } } void I2CPSession::DestroySessionMessageHandler (const uint8_t * buf, size_t len) { - SendSessionStatusMessage (0); // destroy - LogPrint (eLogDebug, "I2CP: session ", m_SessionID, " destroyed"); - if (m_Destination) - { - m_Destination->Stop (); - m_Destination = 0; - } + SendSessionStatusMessage (eI2CPSessionStatusDestroyed); // destroy + LogPrint (eLogDebug, "I2CP: Session ", m_SessionID, " destroyed"); + Terminate (); } void I2CPSession::ReconfigureSessionMessageHandler (const uint8_t * buf, size_t len) { - uint8_t status = 3; // rejected + I2CPSessionStatus status = eI2CPSessionStatusInvalid; // rejected if(len > sizeof(uint16_t)) { uint16_t sessionID = bufbe16toh(buf); @@ -521,37 +623,37 @@ namespace client { if(m_Destination->Reconfigure(opts)) { - LogPrint(eLogInfo, "I2CP: reconfigured destination"); - status = 2; // updated + LogPrint(eLogInfo, "I2CP: Reconfigured destination"); + status = eI2CPSessionStatusUpdated; // updated } else - LogPrint(eLogWarning, "I2CP: failed to reconfigure destination"); + LogPrint(eLogWarning, "I2CP: Failed to reconfigure destination"); } else - LogPrint(eLogError, "I2CP: invalid reconfigure message signature"); + LogPrint(eLogError, "I2CP: Invalid reconfigure message signature"); } else - LogPrint(eLogError, "I2CP: mapping size mismatch"); + LogPrint(eLogError, "I2CP: Mapping size mismatch"); } else - LogPrint(eLogError, "I2CP: destination mismatch"); + LogPrint(eLogError, "I2CP: Destination mismatch"); } else - LogPrint(eLogError, "I2CP: malfromed destination"); + LogPrint(eLogError, "I2CP: Malfromed destination"); } else - LogPrint(eLogError, "I2CP: session mismatch"); + LogPrint(eLogError, "I2CP: Session mismatch"); } else - LogPrint(eLogError, "I2CP: short message"); + LogPrint(eLogError, "I2CP: Short message"); SendSessionStatusMessage (status); } - void I2CPSession::SendSessionStatusMessage (uint8_t status) + void I2CPSession::SendSessionStatusMessage (I2CPSessionStatus status) { uint8_t buf[3]; htobe16buf (buf, m_SessionID); - buf[2] = status; + buf[2] = (uint8_t)status; SendI2CPMessage (I2CP_SESSION_STATUS_MESSAGE, buf, 3); } @@ -585,7 +687,7 @@ namespace client } } else - LogPrint (eLogError, "I2CP: unexpected sessionID ", sessionID); + LogPrint (eLogError, "I2CP: Unexpected sessionID ", sessionID); } void I2CPSession::CreateLeaseSet2MessageHandler (const uint8_t * buf, size_t len) @@ -600,7 +702,7 @@ namespace client i2p::data::LeaseSet2 ls (storeType, buf + offset, len - offset); // outer layer only for encrypted if (!ls.IsValid ()) { - LogPrint (eLogError, "I2CP: invalid LeaseSet2 of type ", storeType); + LogPrint (eLogError, "I2CP: Invalid LeaseSet2 of type ", storeType); return; } offset += ls.GetBufferLen (); @@ -610,23 +712,23 @@ namespace client { if (offset + 4 > len) return; uint16_t keyType = bufbe16toh (buf + offset); offset += 2; // encryption type - uint16_t keyLen = bufbe16toh (buf + offset); offset += 2; // private key length + uint16_t keyLen = bufbe16toh (buf + offset); offset += 2; // private key length if (offset + keyLen > len) return; - if (keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET) + if (keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) m_Destination->SetECIESx25519EncryptionPrivateKey (buf + offset); else { - m_Destination->SetEncryptionType (keyType); + m_Destination->SetEncryptionType (keyType); m_Destination->SetEncryptionPrivateKey (buf + offset); } offset += keyLen; } - + m_Destination->LeaseSet2Created (storeType, ls.GetBuffer (), ls.GetBufferLen ()); } } else - LogPrint (eLogError, "I2CP: unexpected sessionID ", sessionID); + LogPrint (eLogError, "I2CP: Unexpected sessionID ", sessionID); } void I2CPSession::SendMessageMessageHandler (const uint8_t * buf, size_t len) @@ -652,14 +754,14 @@ namespace client m_Destination->SendMsgTo (buf + offset, payloadLen, identity.GetIdentHash (), nonce); } else - LogPrint(eLogError, "I2CP: cannot send message, too big"); + LogPrint(eLogError, "I2CP: Cannot send message, too big"); } else - LogPrint(eLogError, "I2CP: invalid identity"); + LogPrint(eLogError, "I2CP: Invalid identity"); } } else - LogPrint (eLogError, "I2CP: unexpected sessionID ", sessionID); + LogPrint (eLogError, "I2CP: Unexpected sessionID ", sessionID); } void I2CPSession::SendMessageExpiresMessageHandler (const uint8_t * buf, size_t len) @@ -687,7 +789,7 @@ namespace client if (!addr || !addr->IsIdentHash ()) { // TODO: handle blinded addresses - LogPrint (eLogError, "I2CP: address ", name, " not found"); + LogPrint (eLogError, "I2CP: Address ", name, " not found"); SendHostReplyMessage (requestID, nullptr); return; } @@ -696,7 +798,7 @@ namespace client break; } default: - LogPrint (eLogError, "I2CP: request type ", (int)buf[10], " is not supported"); + LogPrint (eLogError, "I2CP: Request type ", (int)buf[10], " is not supported"); SendHostReplyMessage (requestID, nullptr); return; } @@ -722,7 +824,7 @@ namespace client SendHostReplyMessage (requestID, nullptr); } else - LogPrint (eLogError, "I2CP: unexpected sessionID ", sessionID); + LogPrint (eLogError, "I2CP: Unexpected sessionID ", sessionID); } void I2CPSession::SendHostReplyMessage (uint32_t requestID, std::shared_ptr identity) @@ -798,26 +900,46 @@ namespace client { // we don't use SendI2CPMessage to eliminate additional copy auto l = len + 10 + I2CP_HEADER_SIZE; - uint8_t * buf = new uint8_t[l]; + 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); - boost::asio::async_write (*m_Socket, boost::asio::buffer (buf, l), boost::asio::transfer_all (), - std::bind(&I2CPSession::HandleI2CPMessageSent, shared_from_this (), - std::placeholders::_1, std::placeholders::_2, buf)); + 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, int port, bool isSingleThread): + I2CPServer::I2CPServer (const std::string& interface, uint16_t port, bool isSingleThread): RunnableService ("I2CP"), m_IsSingleThread (isSingleThread), m_Acceptor (GetIOService (), -#ifdef ANDROID - I2CPSession::proto::endpoint(std::string (1, '\0') + interface)) // leading 0 for abstract address -#else - I2CPSession::proto::endpoint(boost::asio::ip::address::from_string(interface), port)) -#endif + boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(interface), port)) { memset (m_MessagesHandlers, 0, sizeof (m_MessagesHandlers)); m_MessagesHandlers[I2CP_GET_DATE_MESSAGE] = &I2CPSession::GetDateMessageHandler; @@ -859,13 +981,13 @@ namespace client void I2CPServer::Accept () { - auto newSocket = std::make_shared (GetIOService ()); + 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) + std::shared_ptr socket) { if (!ecode && socket) { @@ -873,15 +995,15 @@ namespace client auto ep = socket->remote_endpoint (ec); if (!ec) { - LogPrint (eLogDebug, "I2CP: new connection from ", ep); + LogPrint (eLogDebug, "I2CP: New connection from ", ep); auto session = std::make_shared(*this, socket); session->Start (); } else - LogPrint (eLogError, "I2CP: incoming connection error ", ec.message ()); + LogPrint (eLogError, "I2CP: Incoming connection error ", ec.message ()); } else - LogPrint (eLogError, "I2CP: accept error: ", ecode.message ()); + LogPrint (eLogError, "I2CP: Accept error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Accept (); @@ -892,7 +1014,7 @@ namespace client if (!session) return false; if (!m_Sessions.insert({session->GetSessionID (), session}).second) { - LogPrint (eLogError, "I2CP: duplicate session id ", session->GetSessionID ()); + LogPrint (eLogError, "I2CP: Duplicate session id ", session->GetSessionID ()); return false; } return true; @@ -902,5 +1024,19 @@ namespace client { m_Sessions.erase (sessionID); } + + std::shared_ptr I2CPServer::FindSessionByIdentHash (const i2p::data::IdentHash& ident) const + { + for (const auto& it: m_Sessions) + { + if (it.second) + { + auto dest = it.second->GetDestination (); + if (dest && dest->GetIdentHash () == ident) + return it.second; + } + } + return nullptr; + } } } diff --git a/libi2pd_client/I2CP.h b/libi2pd_client/I2CP.h index 4c6b7531..f0081ef6 100644 --- a/libi2pd_client/I2CP.h +++ b/libi2pd_client/I2CP.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -17,6 +17,7 @@ #include #include "util.h" #include "Destination.h" +#include "Streaming.h" namespace i2p { @@ -25,6 +26,8 @@ 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 size_t I2CP_HEADER_LENGTH_OFFSET = 0; const size_t I2CP_HEADER_TYPE_OFFSET = I2CP_HEADER_LENGTH_OFFSET + 4; @@ -58,8 +61,16 @@ namespace client eI2CPMessageStatusNoLeaseSet = 21 }; + enum I2CPSessionStatus + { + eI2CPSessionStatusDestroyed = 0, + eI2CPSessionStatusCreated = 1, + eI2CPSessionStatusUpdated = 2, + eI2CPSessionStatusInvalid = 3, + eI2CPSessionStatusRefused = 4 + }; + // params - const char I2CP_PARAM_DONT_PUBLISH_LEASESET[] = "i2cp.dontPublishLeaseSet"; const char I2CP_PARAM_MESSAGE_RELIABILITY[] = "i2cp.messageReliability"; class I2CPSession; @@ -67,10 +78,12 @@ namespace client { public: - I2CPDestination (boost::asio::io_service& service, std::shared_ptr owner, + I2CPDestination (boost::asio::io_service& service, std::shared_ptr owner, std::shared_ptr identity, bool isPublic, 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); @@ -79,8 +92,8 @@ namespace client void SendMsgTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash& ident, uint32_t nonce); // called from I2CPSession // implements LocalDestination - bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, i2p::data::CryptoKeyType preferredCrypto) const; - bool SupportsEncryptionType (i2p::data::CryptoKeyType keyType) const; + 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; }; @@ -88,7 +101,7 @@ namespace client // I2CP void HandleDataMessage (const uint8_t * buf, size_t len); - void CreateNewLeaseSet (std::vector > tunnels); + void CreateNewLeaseSet (const std::vector >& tunnels); private: @@ -96,6 +109,8 @@ namespace client { return std::static_pointer_cast(shared_from_this ()); } bool SendMsg (std::shared_ptr msg, std::shared_ptr remote); + void PostCreateNewLeaseSet (std::vector > tunnels); + private: std::shared_ptr m_Owner; @@ -105,32 +120,29 @@ namespace client std::shared_ptr m_ECIESx25519Decryptor; uint8_t m_ECIESx25519PrivateKey[32]; uint64_t m_LeaseSetExpirationTime; + bool m_IsCreatingLeaseSet; + boost::asio::deadline_timer m_LeaseSetCreationTimer; + i2p::util::MemoryPoolMt > m_I2NPMsgsPool; }; - class RunnableI2CPDestination: private i2p::util::RunnableService, public I2CPDestination + 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 (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: -#ifdef ANDROID - typedef boost::asio::local::stream_protocol proto; -#else - typedef boost::asio::ip::tcp proto; -#endif - - I2CPSession (I2CPServer& owner, std::shared_ptr socket); + I2CPSession (I2CPServer& owner, std::shared_ptr socket); ~I2CPSession (); @@ -167,25 +179,30 @@ namespace client void HandleMessage (); void Terminate (); - void HandleI2CPMessageSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, const uint8_t * buf); + void HandleI2CPMessageSent (const boost::system::error_code& ecode, std::size_t bytes_transferred); + std::string ExtractString (const uint8_t * buf, size_t len); size_t PutString (uint8_t * buf, size_t len, const std::string& str); void ExtractMapping (const uint8_t * buf, size_t len, std::map& mapping); - - void SendSessionStatusMessage (uint8_t status); + 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; + 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; 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); @@ -193,16 +210,17 @@ namespace client { public: - I2CPServer (const std::string& interface, int port, bool isSingleThread); + I2CPServer (const std::string& interface, uint16_t port, bool isSingleThread); ~I2CPServer (); void Start (); void Stop (); boost::asio::io_service& GetService () { return GetIOService (); }; 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: @@ -210,7 +228,7 @@ namespace client void Accept (); - void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr socket); + void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr socket); private: @@ -218,7 +236,7 @@ namespace client I2CPMessageHandler m_MessagesHandlers[256]; std::map > m_Sessions; - I2CPSession::proto::acceptor m_Acceptor; + boost::asio::ip::tcp::acceptor m_Acceptor; public: diff --git a/libi2pd_client/I2PService.cpp b/libi2pd_client/I2PService.cpp index 83838106..c30b7c9f 100644 --- a/libi2pd_client/I2PService.cpp +++ b/libi2pd_client/I2PService.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -107,7 +107,7 @@ namespace client m_ReadyTimerTriggered = false; } - void I2PService::CreateStream (StreamRequestComplete streamRequestComplete, const std::string& dest, int port) { + void I2PService::CreateStream (StreamRequestComplete streamRequestComplete, const std::string& dest, uint16_t port) { assert(streamRequestComplete); auto address = i2p::client::context.GetAddressBook ().GetAddress (dest); if (address) @@ -119,7 +119,7 @@ namespace client } } - void I2PService::CreateStream(StreamRequestComplete streamRequestComplete, std::shared_ptr address, int port) + void I2PService::CreateStream(StreamRequestComplete streamRequestComplete, std::shared_ptr address, uint16_t port) { if(m_ConnectTimeout && !m_LocalDestination->IsReady()) { @@ -193,7 +193,7 @@ namespace client std::placeholders::_1, std::placeholders::_2)); } else - LogPrint(eLogError, "TCPIPPipe: upstream receive: no socket"); + LogPrint(eLogError, "TCPIPPipe: Upstream receive: No socket"); } void TCPIPPipe::AsyncReceiveDownstream() @@ -204,14 +204,14 @@ namespace client std::placeholders::_1, std::placeholders::_2)); } else - LogPrint(eLogError, "TCPIPPipe: downstream receive: no socket"); + LogPrint(eLogError, "TCPIPPipe: Downstream receive: No socket"); } void TCPIPPipe::UpstreamWrite(size_t len) { if (m_up) { - LogPrint(eLogDebug, "TCPIPPipe: upstream: ", (int) len, " bytes written"); + LogPrint(eLogDebug, "TCPIPPipe: Upstream: ", (int) len, " bytes written"); boost::asio::async_write(*m_up, boost::asio::buffer(m_upstream_buf, len), boost::asio::transfer_all(), std::bind(&TCPIPPipe::HandleUpstreamWrite, @@ -219,14 +219,14 @@ namespace client std::placeholders::_1)); } else - LogPrint(eLogError, "TCPIPPipe: upstream write: no socket"); + LogPrint(eLogError, "TCPIPPipe: Upstream write: no socket"); } void TCPIPPipe::DownstreamWrite(size_t len) { if (m_down) { - LogPrint(eLogDebug, "TCPIPPipe: downstream: ", (int) len, " bytes written"); + LogPrint(eLogDebug, "TCPIPPipe: Downstream: ", (int) len, " bytes written"); boost::asio::async_write(*m_down, boost::asio::buffer(m_downstream_buf, len), boost::asio::transfer_all(), std::bind(&TCPIPPipe::HandleDownstreamWrite, @@ -234,16 +234,16 @@ namespace client std::placeholders::_1)); } else - LogPrint(eLogError, "TCPIPPipe: downstream write: no socket"); + LogPrint(eLogError, "TCPIPPipe: Downstream write: No socket"); } void TCPIPPipe::HandleDownstreamReceived(const boost::system::error_code & ecode, std::size_t bytes_transfered) { - LogPrint(eLogDebug, "TCPIPPipe: downstream: ", (int) bytes_transfered, " bytes received"); + LogPrint(eLogDebug, "TCPIPPipe: Downstream: ", (int) bytes_transfered, " bytes received"); if (ecode) { - LogPrint(eLogError, "TCPIPPipe: downstream read error:" , ecode.message()); + LogPrint(eLogError, "TCPIPPipe: Downstream read error:" , ecode.message()); if (ecode != boost::asio::error::operation_aborted) Terminate(); } else { @@ -256,7 +256,7 @@ namespace client void TCPIPPipe::HandleDownstreamWrite(const boost::system::error_code & ecode) { if (ecode) { - LogPrint(eLogError, "TCPIPPipe: downstream write error:" , ecode.message()); + LogPrint(eLogError, "TCPIPPipe: Downstream write error:" , ecode.message()); if (ecode != boost::asio::error::operation_aborted) Terminate(); } @@ -267,7 +267,7 @@ namespace client void TCPIPPipe::HandleUpstreamWrite(const boost::system::error_code & ecode) { if (ecode) { - LogPrint(eLogError, "TCPIPPipe: upstream write error:" , ecode.message()); + LogPrint(eLogError, "TCPIPPipe: Upstream write error:" , ecode.message()); if (ecode != boost::asio::error::operation_aborted) Terminate(); } @@ -277,10 +277,10 @@ namespace client void TCPIPPipe::HandleUpstreamReceived(const boost::system::error_code & ecode, std::size_t bytes_transfered) { - LogPrint(eLogDebug, "TCPIPPipe: upstream ", (int)bytes_transfered, " bytes received"); + LogPrint(eLogDebug, "TCPIPPipe: Upstream ", (int)bytes_transfered, " bytes received"); if (ecode) { - LogPrint(eLogError, "TCPIPPipe: upstream read error:" , ecode.message()); + LogPrint(eLogError, "TCPIPPipe: Upstream read error:" , ecode.message()); if (ecode != boost::asio::error::operation_aborted) Terminate(); } else { diff --git a/libi2pd_client/I2PService.h b/libi2pd_client/I2PService.h index e14f85c1..4f67e19c 100644 --- a/libi2pd_client/I2PService.h +++ b/libi2pd_client/I2PService.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -59,8 +59,8 @@ namespace client if (dest) dest->Acquire (); m_LocalDestination = dest; } - void CreateStream (StreamRequestComplete streamRequestComplete, const std::string& dest, int port = 0); - void CreateStream(StreamRequestComplete complete, std::shared_ptr address, int port); + void CreateStream (StreamRequestComplete streamRequestComplete, const std::string& dest, uint16_t port = 0); + void CreateStream(StreamRequestComplete complete, std::shared_ptr address, uint16_t port); inline boost::asio::io_service& GetService () { return m_LocalDestination->GetService (); } virtual void Start () = 0; @@ -155,11 +155,11 @@ namespace client { public: - TCPIPAcceptor (const std::string& address, int port, std::shared_ptr localDestination = nullptr) : + TCPIPAcceptor (const std::string& address, uint16_t port, std::shared_ptr localDestination = nullptr) : I2PService(localDestination), m_LocalEndpoint (boost::asio::ip::address::from_string(address), port), m_Timer (GetService ()) {} - TCPIPAcceptor (const std::string& address, int port, i2p::data::SigningKeyType kt) : + TCPIPAcceptor (const std::string& address, uint16_t port, i2p::data::SigningKeyType kt) : I2PService(kt), m_LocalEndpoint (boost::asio::ip::address::from_string(address), port), m_Timer (GetService ()) {} diff --git a/libi2pd_client/I2PTunnel.cpp b/libi2pd_client/I2PTunnel.cpp index 081294c6..ad4e14b8 100644 --- a/libi2pd_client/I2PTunnel.cpp +++ b/libi2pd_client/I2PTunnel.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -7,11 +7,13 @@ */ #include +#include #include "Base.h" #include "Log.h" #include "Destination.h" #include "ClientContext.h" #include "I2PTunnel.h" +#include "util.h" namespace i2p { @@ -19,7 +21,7 @@ namespace client { /** set standard socket options */ - static void I2PTunnelSetSocketOptions(std::shared_ptr socket) + static void I2PTunnelSetSocketOptions (std::shared_ptr socket) { if (socket && socket->is_open()) { @@ -29,7 +31,7 @@ namespace client } I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, std::shared_ptr socket, - std::shared_ptr leaseSet, int port): + std::shared_ptr leaseSet, uint16_t port): I2PServiceHandler(owner), m_Socket (socket), m_RemoteEndpoint (socket->remote_endpoint ()), m_IsQuiet (true) { @@ -44,10 +46,13 @@ namespace client } I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, std::shared_ptr stream, - std::shared_ptr socket, const boost::asio::ip::tcp::endpoint& target, bool quiet): - I2PServiceHandler(owner), m_Socket (socket), m_Stream (stream), - m_RemoteEndpoint (target), m_IsQuiet (quiet) + 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 () @@ -67,7 +72,7 @@ namespace client Receive (); } - static boost::asio::ip::address GetLoopbackAddressFor(const i2p::data::IdentHash & addr) + boost::asio::ip::address GetLoopbackAddressFor(const i2p::data::IdentHash & addr) { boost::asio::ip::address_v4::bytes_type bytes; const uint8_t * ident = addr; @@ -78,20 +83,26 @@ namespace client } #ifdef __linux__ - static void MapToLoopback(const std::shared_ptr & sock, const i2p::data::IdentHash & addr) + static void MapToLoopback(std::shared_ptr sock, const i2p::data::IdentHash & addr) { - // bind to 127.x.x.x address - // where x.x.x are first three bytes from ident - auto ourIP = GetLoopbackAddressFor(addr); - sock->bind (boost::asio::ip::tcp::endpoint (ourIP, 0)); + 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) { - I2PTunnelSetSocketOptions(m_Socket); if (m_Socket) { + I2PTunnelSetSocketOptions (m_Socket); #ifdef __linux__ if (isUniqueLocal && m_RemoteEndpoint.address ().is_v4 () && m_RemoteEndpoint.address ().to_v4 ().to_bytes ()[0] == 127) @@ -106,9 +117,26 @@ namespace client } } + void I2PTunnelConnection::Connect (const boost::asio::ip::address& localAddress) + { + if (m_Socket) + { + if (m_RemoteEndpoint.address().is_v6 ()) + m_Socket->open (boost::asio::ip::tcp::v6 ()); + else + m_Socket->open (boost::asio::ip::tcp::v4 ()); + boost::system::error_code ec; + m_Socket->bind (boost::asio::ip::tcp::endpoint (localAddress, 0), ec); + if (ec) + LogPrint (eLogError, "I2PTunnel: Can't bind to ", localAddress.to_string (), ": ", ec.message ()); + } + Connect (false); + } + void I2PTunnelConnection::Terminate () { if (Kill()) return; + if (m_SSL) m_SSL = nullptr; if (m_Stream) { m_Stream->Close (); @@ -123,18 +151,23 @@ namespace client void I2PTunnelConnection::Receive () { - m_Socket->async_read_some (boost::asio::buffer(m_Buffer, I2P_TUNNEL_CONNECTION_BUFFER_SIZE), - std::bind(&I2PTunnelConnection::HandleReceived, shared_from_this (), - std::placeholders::_1, std::placeholders::_2)); + 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::HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred) + 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 ()); + LogPrint (eLogError, "I2PTunnel: Read error: ", ecode.message ()); Terminate (); } } @@ -156,13 +189,13 @@ namespace client s->Terminate (); }); } - } - + } + void I2PTunnelConnection::HandleWrite (const boost::system::error_code& ecode) { if (ecode) { - LogPrint (eLogError, "I2PTunnel: write error: ", ecode.message ()); + LogPrint (eLogError, "I2PTunnel: Write error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate (); } @@ -184,7 +217,7 @@ namespace client } else // closed by peer { - // get remaning data + // 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); @@ -200,7 +233,7 @@ namespace client { if (ecode != boost::asio::error::operation_aborted) { - LogPrint (eLogError, "I2PTunnel: stream read error: ", ecode.message ()); + LogPrint (eLogError, "I2PTunnel: Stream read error: ", ecode.message ()); if (bytes_transferred > 0) Write (m_StreamBuffer, bytes_transferred); // postpone termination else if (ecode == boost::asio::error::timed_out && m_Stream && m_Stream->IsOpen ()) @@ -217,36 +250,63 @@ namespace client void I2PTunnelConnection::Write (const uint8_t * buf, size_t len) { - 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)); + 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 ()); + LogPrint (eLogError, "I2PTunnel: Connect error: ", ecode.message ()); Terminate (); } else { - LogPrint (eLogDebug, "I2PTunnel: connected"); - if (m_IsQuiet) - StreamReceive (); + 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 - { - // 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 (); + 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) @@ -282,11 +342,16 @@ namespace client m_ProxyConnectionSent = true; } else - m_OutHeader << line << "\n"; + m_OutHeader << line << "\n"; } } else + { + // insert incomplete line back + m_InHeader.clear (); + m_InHeader << line; break; + } } if (endOfHeader) @@ -299,15 +364,24 @@ namespace client m_HeaderSent = true; I2PTunnelConnection::Write ((uint8_t *)m_OutHeader.str ().c_str (), m_OutHeader.str ().length ()); } + else if (m_OutHeader.tellp () < I2P_TUNNEL_HTTP_MAX_HEADER_SIZE) + StreamReceive (); // read more header + else + { + LogPrint (eLogError, "I2PTunnel: HTTP header exceeds max size ", I2P_TUNNEL_HTTP_MAX_HEADER_SIZE); + Terminate (); + } } } I2PServerTunnelConnectionHTTP::I2PServerTunnelConnectionHTTP (I2PService * owner, std::shared_ptr stream, - std::shared_ptr socket, - const boost::asio::ip::tcp::endpoint& target, const std::string& host): - I2PTunnelConnection (owner, stream, socket, target), m_Host (host), + const boost::asio::ip::tcp::endpoint& target, const std::string& host, + std::shared_ptr sslCtx): + I2PTunnelConnection (owner, stream, target, true, sslCtx), m_Host (host), m_HeaderSent (false), m_ResponseHeaderSent (false), m_From (stream->GetRemoteIdentity ()) { + if (sslCtx) + SSL_set_tlsext_host_name(GetSSL ()->native_handle(), host.c_str ()); } void I2PServerTunnelConnectionHTTP::Write (const uint8_t * buf, size_t len) @@ -319,27 +393,60 @@ namespace client m_InHeader.clear (); m_InHeader.write ((const char *)buf, len); std::string line; - bool endOfHeader = false; + bool endOfHeader = false, connection = false; while (!endOfHeader) { std::getline(m_InHeader, line); - if (!m_InHeader.fail ()) + if (m_InHeader.fail ()) break; + if (!m_InHeader.eof ()) { if (line == "\r") endOfHeader = true; else { - if (m_Host.length () > 0 && !line.compare(0, 5, "Host:")) + // 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 + 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 if (m_From) { @@ -347,7 +454,7 @@ namespace client m_OutHeader << X_I2P_DEST_HASH << ": " << m_From->GetIdentHash ().ToBase64 () << "\r\n"; m_OutHeader << X_I2P_DEST_B64 << ": " << m_From->ToBase64 () << "\r\n"; } - + m_OutHeader << "\r\n"; // end of header m_OutHeader << m_InHeader.str ().substr (m_InHeader.tellg ()); // data right after header m_InHeader.str (""); @@ -355,6 +462,13 @@ namespace client m_HeaderSent = true; I2PTunnelConnection::Write ((uint8_t *)m_OutHeader.str ().c_str (), m_OutHeader.str ().length ()); } + else if (m_OutHeader.tellp () < I2P_TUNNEL_HTTP_MAX_HEADER_SIZE) + StreamReceive (); // read more header + else + { + LogPrint (eLogError, "I2PTunnel: HTTP header exceeds max size ", I2P_TUNNEL_HTTP_MAX_HEADER_SIZE); + Terminate (); + } } } @@ -372,7 +486,8 @@ namespace client while (!endOfHeader) { std::getline(m_InHeader, line); - if (!m_InHeader.fail ()) + if (m_InHeader.fail ()) break; + if (!m_InHeader.eof ()) { if (line == "\r") endOfHeader = true; else @@ -383,17 +498,22 @@ namespace client }; bool matched = false; for (const auto& it: excluded) - if (!line.compare(0, it.length (), it)) + if (!line.compare(0, it.length (), it)) { matched = true; - break; - } + break; + } if (!matched) m_OutHeader << line << "\n"; } } else + { + // insert incomplete line back + m_InHeader.clear (); + m_InHeader << line; break; + } } if (endOfHeader) @@ -404,16 +524,16 @@ namespace client 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, - std::shared_ptr socket, - const boost::asio::ip::tcp::endpoint& target, const std::string& webircpass): - I2PTunnelConnection (owner, stream, socket, target), m_From (stream->GetRemoteIdentity ()), + 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) { } @@ -424,7 +544,8 @@ namespace client if (m_NeedsWebIrc) { m_NeedsWebIrc = false; - m_OutPacket << "WEBIRC " << m_WebircPass << " cgiirc " << context.GetAddressBook ().ToAddress (m_From->GetIdentHash ()) << " " << GetSocket ()->local_endpoint ().address () << std::endl; + m_OutPacket << "WEBIRC " << m_WebircPass << " cgiirc " << context.GetAddressBook ().ToAddress (m_From->GetIdentHash ()) + << " " << GetSocket ()->local_endpoint ().address () << std::endl; } m_InPacket.clear (); @@ -460,7 +581,7 @@ namespace client { public: I2PClientTunnelHandler (I2PClientTunnel * parent, std::shared_ptr address, - int destinationPort, std::shared_ptr socket): + uint16_t destinationPort, std::shared_ptr socket): I2PServiceHandler(parent), m_Address(address), m_DestinationPort (destinationPort), m_Socket(socket) {}; void Handle(); @@ -468,7 +589,7 @@ namespace client private: void HandleStreamRequestComplete (std::shared_ptr stream); std::shared_ptr m_Address; - int m_DestinationPort; + uint16_t m_DestinationPort; std::shared_ptr m_Socket; }; @@ -484,7 +605,7 @@ namespace client if (stream) { if (Kill()) return; - LogPrint (eLogDebug, "I2PTunnel: new connection"); + LogPrint (eLogDebug, "I2PTunnel: New connection"); auto connection = std::make_shared(GetOwner(), m_Socket, stream); GetOwner()->AddHandler (connection); connection->I2PConnect (); @@ -509,9 +630,9 @@ namespace client } I2PClientTunnel::I2PClientTunnel (const std::string& name, const std::string& destination, - const std::string& address, int port, std::shared_ptr localDestination, int destinationPort): + const std::string& address, uint16_t port, std::shared_ptr localDestination, uint16_t destinationPort): TCPIPAcceptor (address, port, localDestination), m_Name (name), m_Destination (destination), - m_DestinationPort (destinationPort) + m_DestinationPort (destinationPort), m_KeepAliveInterval (0) { } @@ -519,12 +640,22 @@ namespace client { TCPIPAcceptor::Start (); GetAddress (); + if (m_KeepAliveInterval) + ScheduleKeepAliveTimer (); } void I2PClientTunnel::Stop () { TCPIPAcceptor::Stop(); m_Address = nullptr; + if (m_KeepAliveTimer) m_KeepAliveTimer->cancel (); + } + + void I2PClientTunnel::SetKeepAliveInterval (uint32_t keepAliveInterval) + { + m_KeepAliveInterval = keepAliveInterval; + if (m_KeepAliveInterval) + m_KeepAliveTimer.reset (new boost::asio::deadline_timer (GetLocalDestination ()->GetService ())); } /* HACK: maybe we should create a caching IdentHash provider in AddressBook */ @@ -548,11 +679,38 @@ namespace client 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, - int port, std::shared_ptr localDestination, int inport, bool gzip): + uint16_t port, std::shared_ptr localDestination, uint16_t inport, bool gzip): I2PService (localDestination), m_IsUniqueLocal(true), m_Name (name), m_Address (address), m_Port (port), m_IsAccessList (false) { - m_PortDestination = localDestination->CreateStreamingDestination (inport > 0 ? inport : port, gzip); + m_PortDestination = localDestination->GetStreamingDestination (inport); + if (!m_PortDestination) // default destination + m_PortDestination = localDestination->CreateStreamingDestination (inport, gzip); } void I2PServerTunnel::Start () @@ -576,6 +734,12 @@ namespace client void I2PServerTunnel::Stop () { + if (m_PortDestination) + m_PortDestination->ResetAcceptor (); + auto localDestination = GetLocalDestination (); + if (localDestination) + localDestination->StopAcceptingStreams (); + ClearHandlers (); } @@ -584,8 +748,48 @@ namespace client { if (!ecode) { - auto addr = (*it).endpoint ().address (); - LogPrint (eLogInfo, "I2PTunnel: server tunnel ", (*it).host_name (), " has been resolved to ", addr); + bool found = false; + boost::asio::ip::tcp::endpoint ep; + if (m_LocalAddress) + { + boost::asio::ip::tcp::resolver::iterator end; + while (it != end) + { + 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; + it++; + } + } + else + { + found = true; + ep = *it; // first available + } + if (!found) + { + LogPrint (eLogError, "I2PTunnel: Unable to resolve to compatible address"); + return; + } + + auto addr = ep.address (); + LogPrint (eLogInfo, "I2PTunnel: Server tunnel ", (*it).host_name (), " has been resolved to ", addr); m_Endpoint.address (addr); Accept (); } @@ -599,6 +803,27 @@ namespace client m_IsAccessList = true; } + void I2PServerTunnel::SetLocalAddress (const std::string& localAddress) + { + boost::system::error_code ec; + auto addr = boost::asio::ip::address::from_string(localAddress, ec); + if (!ec) + m_LocalAddress.reset (new boost::asio::ip::address (addr)); + else + LogPrint (eLogError, "I2PTunnel: Can't set local address ", localAddress); + } + + void I2PServerTunnel::SetSSL (bool ssl) + { + if (ssl) + { + m_SSLCtx = std::make_shared (boost::asio::ssl::context::sslv23); + m_SSLCtx->set_verify_mode(boost::asio::ssl::context::verify_none); + } + else + m_SSLCtx = nullptr; + } + void I2PServerTunnel::Accept () { if (m_PortDestination) @@ -630,19 +855,22 @@ namespace client // new connection auto conn = CreateI2PConnection (stream); AddHandler (conn); - conn->Connect (m_IsUniqueLocal); + 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, std::make_shared (GetService ()), GetEndpoint ()); + return std::make_shared (this, stream, GetEndpoint (), true, m_SSLCtx); } I2PServerTunnelHTTP::I2PServerTunnelHTTP (const std::string& name, const std::string& address, - int port, std::shared_ptr localDestination, - const std::string& host, int inport, bool gzip): + uint16_t port, std::shared_ptr localDestination, + const std::string& host, uint16_t inport, bool gzip): I2PServerTunnel (name, address, port, localDestination, inport, gzip), m_Host (host) { @@ -650,13 +878,12 @@ namespace client std::shared_ptr I2PServerTunnelHTTP::CreateI2PConnection (std::shared_ptr stream) { - return std::make_shared (this, stream, - std::make_shared (GetService ()), GetEndpoint (), m_Host); + return std::make_shared (this, stream, GetEndpoint (), m_Host, GetSSLCtx ()); } I2PServerTunnelIRC::I2PServerTunnelIRC (const std::string& name, const std::string& address, - int port, std::shared_ptr localDestination, - const std::string& webircpass, int inport, bool gzip): + uint16_t port, std::shared_ptr localDestination, + const std::string& webircpass, uint16_t inport, bool gzip): I2PServerTunnel (name, address, port, localDestination, inport, gzip), m_WebircPass (webircpass) { @@ -664,350 +891,8 @@ namespace client std::shared_ptr I2PServerTunnelIRC::CreateI2PConnection (std::shared_ptr stream) { - return std::make_shared (this, stream, std::make_shared (GetService ()), GetEndpoint (), this->m_WebircPass); - } - - void I2PUDPServerTunnel::HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) - { - if (!m_LastSession || m_LastSession->Identity.GetLL()[0] != from.GetIdentHash ().GetLL()[0] || fromPort != m_LastSession->RemotePort) - { - std::lock_guard lock(m_SessionsMutex); - m_LastSession = ObtainUDPSession(from, toPort, fromPort); - } - m_LastSession->IPSocket.send_to(boost::asio::buffer(buf, len), m_RemoteEndpoint); - m_LastSession->LastActivity = i2p::util::GetMillisecondsSinceEpoch(); - } - - void I2PUDPServerTunnel::HandleRecvFromI2PRaw (uint16_t, uint16_t, const uint8_t * buf, size_t len) - { - if (m_LastSession) - { - m_LastSession->IPSocket.send_to(boost::asio::buffer(buf, len), m_RemoteEndpoint); - m_LastSession->LastActivity = i2p::util::GetMillisecondsSinceEpoch(); - } - } - - void I2PUDPServerTunnel::ExpireStale(const uint64_t delta) { - std::lock_guard lock(m_SessionsMutex); - uint64_t now = i2p::util::GetMillisecondsSinceEpoch(); - auto itr = m_Sessions.begin(); - while(itr != m_Sessions.end()) { - if(now - (*itr)->LastActivity >= delta ) - itr = m_Sessions.erase(itr); - else - ++itr; - } - } - - void I2PUDPClientTunnel::ExpireStale(const uint64_t delta) { - std::lock_guard lock(m_SessionsMutex); - uint64_t now = i2p::util::GetMillisecondsSinceEpoch(); - std::vector removePorts; - for (const auto & s : m_Sessions) { - if (now - s.second->second >= delta) - removePorts.push_back(s.first); - } - for(auto port : removePorts) { - m_Sessions.erase(port); - } - } - - UDPSessionPtr I2PUDPServerTunnel::ObtainUDPSession(const i2p::data::IdentityEx& from, uint16_t localPort, uint16_t remotePort) - { - auto ih = from.GetIdentHash(); - for (auto & s : m_Sessions ) - { - if (s->Identity.GetLL()[0] == ih.GetLL()[0] && remotePort == s->RemotePort) - { - /** found existing session */ - LogPrint(eLogDebug, "UDPServer: found session ", s->IPSocket.local_endpoint(), " ", ih.ToBase32()); - return s; - } - } - boost::asio::ip::address addr; - /** create new udp session */ - if(m_IsUniqueLocal && m_LocalAddress.is_loopback()) - { - auto ident = from.GetIdentHash(); - addr = GetLoopbackAddressFor(ident); - } - else - addr = m_LocalAddress; - boost::asio::ip::udp::endpoint ep(addr, 0); - m_Sessions.push_back(std::make_shared(ep, m_LocalDest, m_RemoteEndpoint, &ih, localPort, remotePort)); - auto & back = m_Sessions.back(); - return back; - } - - UDPSession::UDPSession(boost::asio::ip::udp::endpoint localEndpoint, - const std::shared_ptr & localDestination, - boost::asio::ip::udp::endpoint endpoint, const i2p::data::IdentHash * to, - uint16_t ourPort, uint16_t theirPort) : - m_Destination(localDestination->GetDatagramDestination()), - IPSocket(localDestination->GetService(), localEndpoint), - SendEndpoint(endpoint), - LastActivity(i2p::util::GetMillisecondsSinceEpoch()), - LocalPort(ourPort), - RemotePort(theirPort) - { - IPSocket.set_option (boost::asio::socket_base::receive_buffer_size (I2P_UDP_MAX_MTU )); - memcpy(Identity, to->data(), 32); - Receive(); - } - - void UDPSession::Receive() { - LogPrint(eLogDebug, "UDPSession: Receive"); - IPSocket.async_receive_from(boost::asio::buffer(m_Buffer, I2P_UDP_MAX_MTU), - FromEndpoint, std::bind(&UDPSession::HandleReceived, this, std::placeholders::_1, std::placeholders::_2)); - } - - void UDPSession::HandleReceived(const boost::system::error_code & ecode, std::size_t len) - { - if(!ecode) - { - LogPrint(eLogDebug, "UDPSession: forward ", len, "B from ", FromEndpoint); - auto ts = i2p::util::GetMillisecondsSinceEpoch(); - auto session = m_Destination->GetSession (Identity); - if (ts > LastActivity + I2P_UDP_REPLIABLE_DATAGRAM_INTERVAL) - m_Destination->SendDatagram(session, m_Buffer, len, LocalPort, RemotePort); - else - m_Destination->SendRawDatagram(session, m_Buffer, len, LocalPort, RemotePort); - size_t numPackets = 0; - while (numPackets < i2p::datagram::DATAGRAM_SEND_QUEUE_MAX_SIZE) - { - boost::system::error_code ec; - size_t moreBytes = IPSocket.available(ec); - if (ec || !moreBytes) break; - len = IPSocket.receive_from (boost::asio::buffer (m_Buffer, I2P_UDP_MAX_MTU), FromEndpoint, 0, ec); - m_Destination->SendRawDatagram (session, m_Buffer, len, LocalPort, RemotePort); - numPackets++; - } - if (numPackets > 0) - LogPrint(eLogDebug, "UDPSession: forward more ", numPackets, "packets B from ", FromEndpoint); - m_Destination->FlushSendQueue (session); - LastActivity = ts; - Receive(); - } - else - LogPrint(eLogError, "UDPSession: ", ecode.message()); - } - - I2PUDPServerTunnel::I2PUDPServerTunnel(const std::string & name, std::shared_ptr localDestination, - boost::asio::ip::address localAddress, boost::asio::ip::udp::endpoint forwardTo, uint16_t port, bool gzip) : - m_IsUniqueLocal(true), - m_Name(name), - m_LocalAddress(localAddress), - m_RemoteEndpoint(forwardTo) - { - m_LocalDest = localDestination; - m_LocalDest->Start(); - auto dgram = m_LocalDest->CreateDatagramDestination(gzip); - dgram->SetReceiver(std::bind(&I2PUDPServerTunnel::HandleRecvFromI2P, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5)); - dgram->SetRawReceiver(std::bind(&I2PUDPServerTunnel::HandleRecvFromI2PRaw, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); - } - - I2PUDPServerTunnel::~I2PUDPServerTunnel() - { - auto dgram = m_LocalDest->GetDatagramDestination(); - if (dgram) dgram->ResetReceiver(); - - LogPrint(eLogInfo, "UDPServer: done"); - } - - void I2PUDPServerTunnel::Start() { - m_LocalDest->Start(); - } - - std::vector > I2PUDPServerTunnel::GetSessions() - { - std::vector > sessions; - std::lock_guard lock(m_SessionsMutex); - - for ( UDPSessionPtr s : m_Sessions ) - { - if (!s->m_Destination) continue; - auto info = s->m_Destination->GetInfoForRemote(s->Identity); - if(!info) continue; - - auto sinfo = std::make_shared(); - sinfo->Name = m_Name; - sinfo->LocalIdent = std::make_shared(m_LocalDest->GetIdentHash().data()); - sinfo->RemoteIdent = std::make_shared(s->Identity.data()); - sinfo->CurrentIBGW = info->IBGW; - sinfo->CurrentOBEP = info->OBEP; - sessions.push_back(sinfo); - } - return sessions; - } - - I2PUDPClientTunnel::I2PUDPClientTunnel(const std::string & name, const std::string &remoteDest, - boost::asio::ip::udp::endpoint localEndpoint, - std::shared_ptr localDestination, - uint16_t remotePort, bool gzip) : - m_Name(name), - m_RemoteDest(remoteDest), - m_LocalDest(localDestination), - m_LocalEndpoint(localEndpoint), - m_RemoteIdent(nullptr), - m_ResolveThread(nullptr), - m_LocalSocket(localDestination->GetService(), localEndpoint), - RemotePort(remotePort), m_LastPort (0), - m_cancel_resolve(false) - { - m_LocalSocket.set_option (boost::asio::socket_base::receive_buffer_size (I2P_UDP_MAX_MTU )); - - auto dgram = m_LocalDest->CreateDatagramDestination(gzip); - dgram->SetReceiver(std::bind(&I2PUDPClientTunnel::HandleRecvFromI2P, this, - std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3, std::placeholders::_4, - std::placeholders::_5)); - dgram->SetRawReceiver(std::bind(&I2PUDPClientTunnel::HandleRecvFromI2PRaw, this, - std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); - } - - void I2PUDPClientTunnel::Start() { - m_LocalDest->Start(); - if (m_ResolveThread == nullptr) - m_ResolveThread = new std::thread(std::bind(&I2PUDPClientTunnel::TryResolving, this)); - RecvFromLocal(); - } - - void I2PUDPClientTunnel::RecvFromLocal() - { - m_LocalSocket.async_receive_from(boost::asio::buffer(m_RecvBuff, I2P_UDP_MAX_MTU), - m_RecvEndpoint, std::bind(&I2PUDPClientTunnel::HandleRecvFromLocal, this, std::placeholders::_1, std::placeholders::_2)); - } - - void I2PUDPClientTunnel::HandleRecvFromLocal(const boost::system::error_code & ec, std::size_t transferred) - { - if(ec) { - LogPrint(eLogError, "UDP Client: ", ec.message()); - return; - } - if(!m_RemoteIdent) { - LogPrint(eLogWarning, "UDP Client: remote endpoint not resolved yet"); - RecvFromLocal(); - return; // drop, remote not resolved - } - auto remotePort = m_RecvEndpoint.port(); - if (!m_LastPort || m_LastPort != remotePort) - { - auto itr = m_Sessions.find(remotePort); - if (itr != m_Sessions.end()) - m_LastSession = itr->second; - else - { - m_LastSession = std::make_shared(boost::asio::ip::udp::endpoint(m_RecvEndpoint), 0); - m_Sessions.emplace (remotePort, m_LastSession); - } - m_LastPort = remotePort; - } - // send off to remote i2p destination - auto ts = i2p::util::GetMillisecondsSinceEpoch(); - LogPrint(eLogDebug, "UDP Client: send ", transferred, " to ", m_RemoteIdent->ToBase32(), ":", RemotePort); - auto session = m_LocalDest->GetDatagramDestination()->GetSession (*m_RemoteIdent); - if (ts > m_LastSession->second + I2P_UDP_REPLIABLE_DATAGRAM_INTERVAL) - m_LocalDest->GetDatagramDestination()->SendDatagram (session, m_RecvBuff, transferred, remotePort, RemotePort); - else - m_LocalDest->GetDatagramDestination()->SendRawDatagram (session, m_RecvBuff, transferred, remotePort, RemotePort); - size_t numPackets = 0; - while (numPackets < i2p::datagram::DATAGRAM_SEND_QUEUE_MAX_SIZE) - { - boost::system::error_code ec; - size_t moreBytes = m_LocalSocket.available(ec); - if (ec || !moreBytes) break; - transferred = m_LocalSocket.receive_from (boost::asio::buffer (m_RecvBuff, I2P_UDP_MAX_MTU), m_RecvEndpoint, 0, ec); - remotePort = m_RecvEndpoint.port(); - // TODO: check remotePort - m_LocalDest->GetDatagramDestination()->SendRawDatagram (session, m_RecvBuff, transferred, remotePort, RemotePort); - numPackets++; - } - if (numPackets) - LogPrint(eLogDebug, "UDP Client: sent ", numPackets, " more packets to ", m_RemoteIdent->ToBase32()); - m_LocalDest->GetDatagramDestination()->FlushSendQueue (session); - - // mark convo as active - if (m_LastSession) - m_LastSession->second = ts; - RecvFromLocal(); - } - - std::vector > I2PUDPClientTunnel::GetSessions() - { - // TODO: implement - std::vector > infos; - return infos; - } - - void I2PUDPClientTunnel::TryResolving() { - LogPrint(eLogInfo, "UDP Tunnel: Trying to resolve ", m_RemoteDest); - - std::shared_ptr addr; - while(!(addr = context.GetAddressBook().GetAddress(m_RemoteDest)) && !m_cancel_resolve) - { - LogPrint(eLogWarning, "UDP Tunnel: failed to lookup ", m_RemoteDest); - std::this_thread::sleep_for(std::chrono::seconds(1)); - } - if(m_cancel_resolve) - { - LogPrint(eLogError, "UDP Tunnel: lookup of ", m_RemoteDest, " was cancelled"); - return; - } - if (!addr || !addr->IsIdentHash ()) - { - LogPrint(eLogError, "UDP Tunnel: ", m_RemoteDest, " not found"); - return; - } - m_RemoteIdent = new i2p::data::IdentHash; - *m_RemoteIdent = addr->identHash; - LogPrint(eLogInfo, "UDP Tunnel: resolved ", m_RemoteDest, " to ", m_RemoteIdent->ToBase32()); - } - - void I2PUDPClientTunnel::HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) - { - if(m_RemoteIdent && from.GetIdentHash() == *m_RemoteIdent) - HandleRecvFromI2PRaw (fromPort, toPort, buf, len); - else - LogPrint(eLogWarning, "UDP Client: unwarranted traffic from ", from.GetIdentHash().ToBase32()); - } - - void I2PUDPClientTunnel::HandleRecvFromI2PRaw(uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) - { - auto itr = m_Sessions.find(toPort); - // found convo ? - if(itr != m_Sessions.end()) - { - // found convo - if (len > 0) - { - LogPrint(eLogDebug, "UDP Client: got ", len, "B from ", m_RemoteIdent ? m_RemoteIdent->ToBase32() : ""); - m_LocalSocket.send_to(boost::asio::buffer(buf, len), itr->second->first); - // mark convo as active - itr->second->second = i2p::util::GetMillisecondsSinceEpoch(); - } - } - else - LogPrint(eLogWarning, "UDP Client: not tracking udp session using port ", (int) toPort); - } - - I2PUDPClientTunnel::~I2PUDPClientTunnel() { - auto dgram = m_LocalDest->GetDatagramDestination(); - if (dgram) dgram->ResetReceiver(); - - m_Sessions.clear(); - - if(m_LocalSocket.is_open()) - m_LocalSocket.close(); - - m_cancel_resolve = true; - - if(m_ResolveThread) - { - m_ResolveThread->join(); - delete m_ResolveThread; - m_ResolveThread = nullptr; - } - if (m_RemoteIdent) delete m_RemoteIdent; + return std::make_shared (this, stream, GetEndpoint (), m_WebircPass, GetSSLCtx ()); } } } + diff --git a/libi2pd_client/I2PTunnel.h b/libi2pd_client/I2PTunnel.h index f7185dfd..b94eb9e4 100644 --- a/libi2pd_client/I2PTunnel.h +++ b/libi2pd_client/I2PTunnel.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -16,9 +16,9 @@ #include #include #include +#include #include "Identity.h" #include "Destination.h" -#include "Datagram.h" #include "Streaming.h" #include "I2PService.h" #include "AddressBook.h" @@ -31,44 +31,53 @@ namespace client const int I2P_TUNNEL_CONNECTION_MAX_IDLE = 3600; // in seconds const int I2P_TUNNEL_DESTINATION_REQUEST_TIMEOUT = 10; // in seconds // for HTTP tunnels - const char X_I2P_DEST_HASH[] = "X-I2P-DestHash"; // hash in base64 + const char X_I2P_DEST_HASH[] = "X-I2P-DestHash"; // hash in base64 const char X_I2P_DEST_B64[] = "X-I2P-DestB64"; // full address in base64 const char X_I2P_DEST_B32[] = "X-I2P-DestB32"; // .b32.i2p address + const int I2P_TUNNEL_HTTP_MAX_HEADER_SIZE = 8192; class I2PTunnelConnection: public I2PServiceHandler, public std::enable_shared_from_this { public: I2PTunnelConnection (I2PService * owner, std::shared_ptr socket, - std::shared_ptr leaseSet, int port = 0); // to I2P + std::shared_ptr leaseSet, uint16_t port = 0); // to I2P I2PTunnelConnection (I2PService * owner, std::shared_ptr socket, std::shared_ptr stream); // to I2P using simplified API - I2PTunnelConnection (I2PService * owner, std::shared_ptr stream, std::shared_ptr socket, - const boost::asio::ip::tcp::endpoint& target, bool quiet = true); // from I2P + 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 HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred); - virtual void Write (const uint8_t * buf, size_t len); // can be overloaded - void HandleWrite (const boost::system::error_code& ecode); - virtual void WriteToStream (const uint8_t * buf, size_t len); // can be overloaded - void StreamReceive (); - void HandleStreamReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred); - void HandleConnect (const boost::system::error_code& ecode); + 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 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 @@ -98,13 +107,13 @@ namespace client public: I2PServerTunnelConnectionHTTP (I2PService * owner, std::shared_ptr stream, - std::shared_ptr socket, - const boost::asio::ip::tcp::endpoint& target, const std::string& host); + const boost::asio::ip::tcp::endpoint& target, const std::string& host, + std::shared_ptr sslCtx = nullptr); protected: void Write (const uint8_t * buf, size_t len); - void WriteToStream (const uint8_t * buf, size_t len); + void WriteToStream (const uint8_t * buf, size_t len); private: @@ -119,8 +128,8 @@ namespace client public: I2PTunnelConnectionIRC (I2PService * owner, std::shared_ptr stream, - std::shared_ptr socket, - const boost::asio::ip::tcp::endpoint& target, const std::string& m_WebircPass); + const boost::asio::ip::tcp::endpoint& target, const std::string& m_WebircPass, + std::shared_ptr sslCtx = nullptr); protected: @@ -145,166 +154,37 @@ namespace client public: I2PClientTunnel (const std::string& name, const std::string& destination, - const std::string& address, int port, std::shared_ptr localDestination, int destinationPort = 0); + const std::string& address, uint16_t port, std::shared_ptr localDestination, uint16_t destinationPort = 0); ~I2PClientTunnel () {} void Start (); 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; - int m_DestinationPort; - }; - - - /** 2 minute timeout for udp sessions */ - const uint64_t I2P_UDP_SESSION_TIMEOUT = 1000 * 60 * 2; - const uint64_t I2P_UDP_REPLIABLE_DATAGRAM_INTERVAL = 100; // in milliseconds - - /** max size for i2p udp */ - const size_t I2P_UDP_MAX_MTU = 64*1024; - - struct UDPSession - { - i2p::datagram::DatagramDestination * m_Destination; - boost::asio::ip::udp::socket IPSocket; - i2p::data::IdentHash Identity; - boost::asio::ip::udp::endpoint FromEndpoint; - boost::asio::ip::udp::endpoint SendEndpoint; - uint64_t LastActivity; - - uint16_t LocalPort; - uint16_t RemotePort; - - uint8_t m_Buffer[I2P_UDP_MAX_MTU]; - - UDPSession(boost::asio::ip::udp::endpoint localEndpoint, - const std::shared_ptr & localDestination, - boost::asio::ip::udp::endpoint remote, const i2p::data::IdentHash * ident, - uint16_t ourPort, uint16_t theirPort); - void HandleReceived(const boost::system::error_code & ecode, std::size_t len); - void Receive(); - }; - - - /** read only info about a datagram session */ - struct DatagramSessionInfo - { - /** the name of this forward */ - std::string Name; - /** ident hash of local destination */ - std::shared_ptr LocalIdent; - /** ident hash of remote destination */ - std::shared_ptr RemoteIdent; - /** ident hash of IBGW in use currently in this session or nullptr if none is set */ - std::shared_ptr CurrentIBGW; - /** ident hash of OBEP in use for this session or nullptr if none is set */ - std::shared_ptr CurrentOBEP; - /** i2p router's udp endpoint */ - boost::asio::ip::udp::endpoint LocalEndpoint; - /** client's udp endpoint */ - boost::asio::ip::udp::endpoint RemoteEndpoint; - /** how long has this converstation been idle in ms */ - uint64_t idle; - }; - - typedef std::shared_ptr UDPSessionPtr; - - /** server side udp tunnel, many i2p inbound to 1 ip outbound */ - class I2PUDPServerTunnel - { - public: - - I2PUDPServerTunnel(const std::string & name, - std::shared_ptr localDestination, - boost::asio::ip::address localAddress, - boost::asio::ip::udp::endpoint forwardTo, uint16_t port, bool gzip); - ~I2PUDPServerTunnel(); - /** expire stale udp conversations */ - void ExpireStale(const uint64_t delta=I2P_UDP_SESSION_TIMEOUT); - void Start(); - const char * GetName() const { return m_Name.c_str(); } - std::vector > GetSessions(); - std::shared_ptr GetLocalDestination () const { return m_LocalDest; } - - void SetUniqueLocal(bool isUniqueLocal = true) { m_IsUniqueLocal = isUniqueLocal; } - - private: - - void HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); - void HandleRecvFromI2PRaw (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); - UDPSessionPtr ObtainUDPSession(const i2p::data::IdentityEx& from, uint16_t localPort, uint16_t remotePort); - - private: - - bool m_IsUniqueLocal; - const std::string m_Name; - boost::asio::ip::address m_LocalAddress; - boost::asio::ip::udp::endpoint m_RemoteEndpoint; - std::mutex m_SessionsMutex; - std::vector m_Sessions; - std::shared_ptr m_LocalDest; - UDPSessionPtr m_LastSession; - }; - - class I2PUDPClientTunnel - { - public: - - I2PUDPClientTunnel(const std::string & name, const std::string &remoteDest, - boost::asio::ip::udp::endpoint localEndpoint, std::shared_ptr localDestination, - uint16_t remotePort, bool gzip); - ~I2PUDPClientTunnel(); - void Start(); - const char * GetName() const { return m_Name.c_str(); } - std::vector > GetSessions(); - - bool IsLocalDestination(const i2p::data::IdentHash & destination) const { return destination == m_LocalDest->GetIdentHash(); } - - std::shared_ptr GetLocalDestination () const { return m_LocalDest; } - void ExpireStale(const uint64_t delta=I2P_UDP_SESSION_TIMEOUT); - - private: - - typedef std::pair UDPConvo; - void RecvFromLocal(); - void HandleRecvFromLocal(const boost::system::error_code & e, std::size_t transferred); - void HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); - void HandleRecvFromI2PRaw(uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); - void TryResolving(); - - private: - - const std::string m_Name; - std::mutex m_SessionsMutex; - std::unordered_map > m_Sessions; // maps i2p port -> local udp convo - const std::string m_RemoteDest; - std::shared_ptr m_LocalDest; - const boost::asio::ip::udp::endpoint m_LocalEndpoint; - i2p::data::IdentHash * m_RemoteIdent; - std::thread * m_ResolveThread; - boost::asio::ip::udp::socket m_LocalSocket; - boost::asio::ip::udp::endpoint m_RecvEndpoint; - uint8_t m_RecvBuff[I2P_UDP_MAX_MTU]; - uint16_t RemotePort, m_LastPort; - bool m_cancel_resolve; - std::shared_ptr m_LastSession; + 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, int port, - std::shared_ptr localDestination, int inport = 0, bool gzip = true); + I2PServerTunnel (const std::string& name, const std::string& address, uint16_t port, + std::shared_ptr localDestination, uint16_t inport = 0, bool gzip = true); void Start (); void Stop (); @@ -314,8 +194,13 @@ namespace client void SetUniqueLocal (bool isUniqueLocal) { m_IsUniqueLocal = isUniqueLocal; } bool IsUniqueLocal () const { return m_IsUniqueLocal; } + void SetSSL (bool ssl); + std::shared_ptr GetSSLCtx () const { return m_SSLCtx; }; + + void SetLocalAddress (const std::string& localAddress); + const std::string& GetAddress() const { return m_Address; } - int GetPort () const { return m_Port; }; + uint16_t GetPort () const { return m_Port; }; uint16_t GetLocalPort () const { return m_PortDestination->GetLocalPort (); }; const boost::asio::ip::tcp::endpoint& GetEndpoint () const { return m_Endpoint; } @@ -334,20 +219,22 @@ namespace client bool m_IsUniqueLocal; std::string m_Name, m_Address; - int m_Port; + uint16_t m_Port; boost::asio::ip::tcp::endpoint m_Endpoint; std::shared_ptr m_PortDestination; std::set m_AccessList; bool m_IsAccessList; + std::unique_ptr m_LocalAddress; + std::shared_ptr m_SSLCtx; }; class I2PServerTunnelHTTP: public I2PServerTunnel { public: - I2PServerTunnelHTTP (const std::string& name, const std::string& address, int port, + I2PServerTunnelHTTP (const std::string& name, const std::string& address, uint16_t port, std::shared_ptr localDestination, const std::string& host, - int inport = 0, bool gzip = true); + uint16_t inport = 0, bool gzip = true); private: @@ -362,9 +249,9 @@ namespace client { public: - I2PServerTunnelIRC (const std::string& name, const std::string& address, int port, + I2PServerTunnelIRC (const std::string& name, const std::string& address, uint16_t port, std::shared_ptr localDestination, const std::string& webircpass, - int inport = 0, bool gzip = true); + uint16_t inport = 0, bool gzip = true); private: @@ -374,6 +261,8 @@ namespace client std::string m_WebircPass; }; + + boost::asio::ip::address GetLoopbackAddressFor(const i2p::data::IdentHash & addr); } } diff --git a/libi2pd_client/MatchedDestination.cpp b/libi2pd_client/MatchedDestination.cpp index 4ffa7442..40752f5b 100644 --- a/libi2pd_client/MatchedDestination.cpp +++ b/libi2pd_client/MatchedDestination.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -33,14 +33,14 @@ namespace client RequestDestination(m_RemoteIdent, std::bind(&MatchedTunnelDestination::HandleFoundCurrentLeaseSet, this, std::placeholders::_1)); } else - LogPrint(eLogWarning, "Destination: failed to resolve ", m_RemoteName); + LogPrint(eLogWarning, "Destination: Failed to resolve ", m_RemoteName); } void MatchedTunnelDestination::HandleFoundCurrentLeaseSet(std::shared_ptr ls) { if(ls) { - LogPrint(eLogDebug, "Destination: resolved remote lease set for ", m_RemoteName); + LogPrint(eLogDebug, "Destination: Resolved remote lease set for ", m_RemoteName); m_RemoteLeaseSet = ls; } else @@ -72,32 +72,34 @@ namespace client bool MatchedTunnelDestination::SelectPeers(i2p::tunnel::Path & path, int hops, bool inbound) { auto pool = GetTunnelPool(); - if(!i2p::tunnel::StandardSelectPeers(path, hops, inbound, std::bind(&i2p::tunnel::TunnelPool::SelectNextHop, pool, std::placeholders::_1))) + 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) { + 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.push_back(obep->GetRouterIdentity()); - LogPrint(eLogDebug, "Destination: found OBEP matching IBGW"); + 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"); + LogPrint(eLogWarning, "Destination: Could not find proper IBGW for matched outbound tunnel"); } } return true; diff --git a/libi2pd_client/SAM.cpp b/libi2pd_client/SAM.cpp index 63b9c7ed..bb9d6a6b 100644 --- a/libi2pd_client/SAM.cpp +++ b/libi2pd_client/SAM.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -54,11 +54,12 @@ namespace client break; } case eSAMSocketTypeAcceptor: + case eSAMSocketTypeForward: { if (Session) { - if (m_IsAccepting && Session->localDestination) - Session->localDestination->StopAcceptingStreams (); + if (m_IsAccepting && Session->GetLocalDestination ()) + Session->GetLocalDestination ()->StopAcceptingStreams (); } break; } @@ -100,7 +101,7 @@ namespace client { if (ecode) { - LogPrint (eLogError, "SAM: handshake read error: ", ecode.message ()); + LogPrint (eLogError, "SAM: Handshake read error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate ("SAM: handshake read error"); } @@ -110,7 +111,7 @@ namespace client char * eol = (char *)memchr (m_Buffer, '\n', bytes_transferred); if (eol) *eol = 0; - LogPrint (eLogDebug, "SAM: handshake ", m_Buffer); + LogPrint (eLogDebug, "SAM: Handshake ", m_Buffer); char * separator = strchr (m_Buffer, ' '); if (separator) { @@ -167,7 +168,7 @@ namespace client } else { - LogPrint (eLogError, "SAM: handshake mismatch"); + LogPrint (eLogError, "SAM: Handshake mismatch"); Terminate ("SAM: handshake mismatch"); } } @@ -182,7 +183,7 @@ namespace client { if (ecode) { - LogPrint (eLogError, "SAM: handshake reply send error: ", ecode.message ()); + LogPrint (eLogError, "SAM: Handshake reply send error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate ("SAM: handshake reply send error"); } @@ -198,7 +199,7 @@ namespace client { LogPrint (eLogDebug, "SAMSocket::SendMessageReply, close=",close?"true":"false", " reason: ", msg); - if (!m_IsSilent) + 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)); @@ -215,7 +216,7 @@ namespace client { if (ecode) { - LogPrint (eLogError, "SAM: reply send error: ", ecode.message ()); + LogPrint (eLogError, "SAM: Reply send error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate ("SAM: reply send error"); } @@ -232,7 +233,7 @@ namespace client { if (ecode) { - LogPrint (eLogError, "SAM: read error: ", ecode.message ()); + LogPrint (eLogError, "SAM: Read error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate ("SAM: read error"); } @@ -263,10 +264,16 @@ namespace client ProcessStreamConnect (separator + 1, bytes_transferred - (separator - m_Buffer) - 1, bytes_transferred - (eol - m_Buffer) - 1); else if (!strcmp (m_Buffer, SAM_STREAM_ACCEPT)) ProcessStreamAccept (separator + 1, bytes_transferred - (separator - m_Buffer) - 1); + else if (!strcmp (m_Buffer, SAM_STREAM_FORWARD)) + ProcessStreamForward (separator + 1, bytes_transferred - (separator - m_Buffer) - 1); else if (!strcmp (m_Buffer, SAM_DEST_GENERATE)) ProcessDestGenerate (separator + 1, bytes_transferred - (separator - m_Buffer) - 1); else if (!strcmp (m_Buffer, SAM_NAMING_LOOKUP)) ProcessNamingLookup (separator + 1, bytes_transferred - (separator - m_Buffer) - 1); + else if (!strcmp (m_Buffer, SAM_SESSION_ADD)) + ProcessSessionAdd (separator + 1, bytes_transferred - (separator - m_Buffer) - 1); + else if (!strcmp (m_Buffer, SAM_SESSION_REMOVE)) + ProcessSessionRemove (separator + 1, bytes_transferred - (separator - m_Buffer) - 1); else if (!strcmp (m_Buffer, SAM_DATAGRAM_SEND) || !strcmp (m_Buffer, SAM_RAW_SEND)) { size_t len = bytes_transferred - (separator - m_Buffer) - 1; @@ -288,20 +295,20 @@ namespace client } else { - LogPrint (eLogError, "SAM: unexpected message ", m_Buffer); + LogPrint (eLogError, "SAM: Unexpected message ", m_Buffer); Terminate ("SAM: unexpected message"); } } else { - LogPrint (eLogError, "SAM: malformed message ", m_Buffer); + LogPrint (eLogError, "SAM: Malformed message ", m_Buffer); Terminate ("malformed message"); } } else { - LogPrint (eLogWarning, "SAM: incomplete message ", bytes_transferred); + LogPrint (eLogWarning, "SAM: Incomplete message ", bytes_transferred); m_BufferOffset = bytes_transferred; // try to receive remaining message Receive (); @@ -324,7 +331,7 @@ namespace client void SAMSocket::ProcessSessionCreate (char * buf, size_t len) { - LogPrint (eLogDebug, "SAM: session create: ", buf); + LogPrint (eLogDebug, "SAM: Session create: ", buf); std::map params; ExtractParams (buf, params); std::string& style = params[SAM_PARAM_STYLE]; @@ -349,6 +356,7 @@ namespace client 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 @@ -358,12 +366,12 @@ namespace client std::shared_ptr forward = nullptr; if ((type == eSAMSessionTypeDatagram || type == eSAMSessionTypeRaw) && - params.find(SAM_VALUE_HOST) != params.end() && params.find(SAM_VALUE_PORT) != params.end()) + params.find(SAM_PARAM_HOST) != params.end() && params.find(SAM_PARAM_PORT) != params.end()) { // udp forward selected boost::system::error_code e; // TODO: support hostnames in udp forward - auto addr = boost::asio::ip::address::from_string(params[SAM_VALUE_HOST], e); + auto addr = boost::asio::ip::address::from_string(params[SAM_PARAM_HOST], e); if (e) { // not an ip address @@ -371,7 +379,7 @@ namespace client return; } - auto port = std::stoi(params[SAM_VALUE_PORT]); + auto port = std::stoi(params[SAM_PARAM_PORT]); if (port == -1) { SendI2PError("Invalid port"); @@ -406,7 +414,7 @@ namespace client if (type == eSAMSessionTypeDatagram || type == eSAMSessionTypeRaw) { session->UDPEndpoint = forward; - auto dest = session->localDestination->CreateDatagramDestination (); + auto dest = session->GetLocalDestination ()->CreateDatagramDestination (); if (type == eSAMSessionTypeDatagram) dest->SetReceiver (std::bind (&SAMSocket::HandleI2PDatagramReceive, shared_from_this (), std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5)); @@ -415,7 +423,7 @@ namespace client std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); } - if (session->localDestination->IsReady ()) + if (session->GetLocalDestination ()->IsReady ()) SendSessionCreateReplyOk (); else { @@ -432,18 +440,23 @@ namespace client { if (ecode != boost::asio::error::operation_aborted) { - auto session = m_Owner.FindSession(m_ID); - if(session) + if (m_Socket.is_open ()) { - if (session->localDestination->IsReady ()) - SendSessionCreateReplyOk (); - else + auto session = m_Owner.FindSession(m_ID); + if(session) { - 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)); + 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"); } } @@ -454,7 +467,7 @@ namespace client { uint8_t buf[1024]; char priv[1024]; - size_t l = session->localDestination->GetPrivateKeys ().ToBuffer (buf, 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 @@ -468,7 +481,12 @@ namespace client void SAMSocket::ProcessStreamConnect (char * buf, size_t len, size_t rem) { - LogPrint (eLogDebug, "SAM: stream connect: ", buf); + LogPrint (eLogDebug, "SAM: Stream connect: ", buf); + if ( m_SocketType != eSAMSocketTypeUnknown) + { + SendI2PError ("Socket already in use"); + return; + } std::map params; ExtractParams (buf, params); std::string& id = params[SAM_PARAM_ID]; @@ -487,20 +505,38 @@ namespace client else m_BufferOffset = 0; - auto dest = std::make_shared (); - size_t l = dest->FromBase64(destination); - if (l > 0) + std::shared_ptr addr; + if (destination.find(".i2p") != std::string::npos) + addr = context.GetAddressBook().GetAddress (destination); + else { - context.GetAddressBook().InsertFullAddress(dest); - auto leaseSet = session->localDestination->FindLeaseSet(dest->GetIdentHash()); - if (leaseSet) - Connect(leaseSet, session); - else + auto dest = std::make_shared (); + size_t l = dest->FromBase64(destination); + if (l > 0) { - session->localDestination->RequestDestination(dest->GetIdentHash(), + context.GetAddressBook().InsertFullAddress(dest); + addr = std::make_shared
(dest->GetIdentHash ()); + } + } + + if (addr && addr->IsValid ()) + { + if (addr->IsIdentHash ()) + { + 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 // 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); @@ -515,14 +551,14 @@ namespace client if (session) { m_SocketType = eSAMSocketTypeStream; - m_Stream = session->localDestination->CreateStream (remote); + 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); } @@ -536,14 +572,19 @@ namespace client Connect (leaseSet); else { - LogPrint (eLogError, "SAM: destination to connect not found"); + LogPrint (eLogError, "SAM: Destination to connect not found"); SendMessageReply (SAM_STREAM_STATUS_CANT_REACH_PEER, strlen(SAM_STREAM_STATUS_CANT_REACH_PEER), true); } } void SAMSocket::ProcessStreamAccept (char * buf, size_t len) { - LogPrint (eLogDebug, "SAM: stream accept: ", buf); + LogPrint (eLogDebug, "SAM: Stream accept: ", buf); + if ( m_SocketType != eSAMSocketTypeUnknown) + { + SendI2PError ("Socket already in use"); + return; + } std::map params; ExtractParams (buf, params); std::string& id = params[SAM_PARAM_ID]; @@ -554,10 +595,10 @@ namespace client if (session) { m_SocketType = eSAMSocketTypeAcceptor; - if (!session->localDestination->IsAcceptingStreams ()) + if (!session->GetLocalDestination ()->IsAcceptingStreams ()) { m_IsAccepting = true; - session->localDestination->AcceptOnce (std::bind (&SAMSocket::HandleI2PAccept, shared_from_this (), std::placeholders::_1)); + session->GetLocalDestination ()->AcceptOnce (std::bind (&SAMSocket::HandleI2PAccept, shared_from_this (), std::placeholders::_1)); } SendMessageReply (SAM_STREAM_STATUS_OK, strlen(SAM_STREAM_STATUS_OK), false); } @@ -565,9 +606,56 @@ namespace client 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 ()) + { + SendI2PError ("Already accepting"); + return; + } + auto it = params.find (SAM_PARAM_PORT); + if (it == params.end ()) + { + SendI2PError ("PORT is missing"); + return; + } + auto port = std::stoi (it->second); + if (port <= 0 || port >= 0xFFFF) + { + SendI2PError ("Invalid PORT"); + return; + } + boost::system::error_code ec; + auto ep = m_Socket.remote_endpoint (ec); + if (ec) + { + SendI2PError ("Socket error"); + return; + } + ep.port (port); + m_SocketType = eSAMSocketTypeForward; + m_ID = id; + m_IsAccepting = true; + std::string& silent = params[SAM_PARAM_SILENT]; + if (silent == SAM_VALUE_TRUE) m_IsSilent = true; + session->GetLocalDestination ()->AcceptStreams (std::bind (&SAMSocket::HandleI2PForward, + shared_from_this (), std::placeholders::_1, ep)); + SendMessageReply (SAM_STREAM_STATUS_OK, strlen(SAM_STREAM_STATUS_OK), false); + } + size_t SAMSocket::ProcessDatagramSend (char * buf, size_t len, const char * data) { - LogPrint (eLogDebug, "SAM: datagram send: ", buf, " ", len); + LogPrint (eLogDebug, "SAM: Datagram send: ", buf, " ", len); std::map params; ExtractParams (buf, params); size_t size = std::stoi(params[SAM_PARAM_SIZE]), offset = data - buf; @@ -576,7 +664,7 @@ namespace client auto session = m_Owner.FindSession(m_ID); if (session) { - auto d = session->localDestination->GetDatagramDestination (); + auto d = session->GetLocalDestination ()->GetDatagramDestination (); if (d) { i2p::data::IdentityEx dest; @@ -587,14 +675,14 @@ namespace client d->SendRawDatagramTo ((const uint8_t *)data, size, dest.GetIdentHash ()); } else - LogPrint (eLogError, "SAM: missing datagram destination"); + LogPrint (eLogError, "SAM: Missing datagram destination"); } else - LogPrint (eLogError, "SAM: session is not created from DATAGRAM SEND"); + LogPrint (eLogError, "SAM: Session is not created from DATAGRAM SEND"); } else { - LogPrint (eLogWarning, "SAM: sent datagram size ", size, " exceeds buffer ", len - offset); + LogPrint (eLogWarning, "SAM: Sent datagram size ", size, " exceeds buffer ", len - offset); return 0; // try to receive more } return offset + size; @@ -602,7 +690,7 @@ namespace client void SAMSocket::ProcessDestGenerate (char * buf, size_t len) { - LogPrint (eLogDebug, "SAM: dest generate"); + LogPrint (eLogDebug, "SAM: Dest generate"); std::map params; ExtractParams (buf, params); // extract signature type @@ -626,7 +714,7 @@ namespace client LogPrint (eLogWarning, "SAM: ", SAM_PARAM_CRYPTO_TYPE, "error: ", ex.what ()); } } - auto keys = i2p::data::PrivateKeys::CreateRandomKeys (signatureType, cryptoType); + 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 ()); @@ -639,25 +727,25 @@ namespace client void SAMSocket::ProcessNamingLookup (char * buf, size_t len) { - LogPrint (eLogDebug, "SAM: naming lookup: ", buf); + LogPrint (eLogDebug, "SAM: Naming lookup: ", buf); std::map params; ExtractParams (buf, params); std::string& name = params[SAM_PARAM_NAME]; std::shared_ptr identity; std::shared_ptr addr; auto session = m_Owner.FindSession(m_ID); - auto dest = session == nullptr ? context.GetSharedLocalDestination() : session->localDestination; + auto dest = session == nullptr ? context.GetSharedLocalDestination() : session->GetLocalDestination (); if (name == "ME") - SendNamingLookupReply (dest->GetIdentity ()); + SendNamingLookupReply (name, dest->GetIdentity ()); else if ((identity = context.GetAddressBook ().GetFullAddress (name)) != nullptr) - SendNamingLookupReply (identity); + SendNamingLookupReply (name, identity); else if ((addr = context.GetAddressBook ().GetAddress (name))) { if (addr->IsIdentHash ()) { auto leaseSet = dest->FindLeaseSet (addr->identHash); if (leaseSet) - SendNamingLookupReply (leaseSet->GetIdentity ()); + SendNamingLookupReply (name, leaseSet->GetIdentity ()); else dest->RequestDestination (addr->identHash, std::bind (&SAMSocket::HandleNamingLookupLeaseSetRequestComplete, @@ -670,7 +758,7 @@ namespace client } else { - LogPrint (eLogError, "SAM: naming failed, unknown address ", name); + LogPrint (eLogError, "SAM: Naming failed, unknown address ", name); #ifdef _MSC_VER size_t len = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY_INVALID_KEY, name.c_str()); #else @@ -680,9 +768,76 @@ namespace client } } + void SAMSocket::ProcessSessionAdd (char * buf, size_t len) + { + auto session = m_Owner.FindSession(m_ID); + if (session && session->Type == eSAMSessionTypeMaster) + { + LogPrint (eLogDebug, "SAM: Subsession add: ", buf); + auto masterSession = std::static_pointer_cast(session); + std::map params; + ExtractParams (buf, params); + std::string& id = params[SAM_PARAM_ID]; + if (masterSession->subsessions.count (id) > 1) + { + // session exists + SendMessageReply (SAM_SESSION_CREATE_DUPLICATED_ID, strlen(SAM_SESSION_CREATE_DUPLICATED_ID), false); + return; + } + std::string& style = params[SAM_PARAM_STYLE]; + SAMSessionType type = eSAMSessionTypeUnknown; + if (style == SAM_VALUE_STREAM) type = eSAMSessionTypeStream; + // TODO: implement other styles + if (type == eSAMSessionTypeUnknown) + { + // unknown style + SendI2PError("Unsupported STYLE"); + return; + } + auto fromPort = std::stoi(params[SAM_PARAM_FROM_PORT]); + if (fromPort == -1) + { + SendI2PError("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 + SendI2PError ("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 + SendI2PError ("Wrong session type"); + } + void SAMSocket::SendI2PError(const std::string & msg) { - LogPrint (eLogError, "SAM: i2p error ", msg); + LogPrint (eLogError, "SAM: I2P error: ", msg); #ifdef _MSC_VER size_t len = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_SESSION_STATUS_I2P_ERROR, msg.c_str()); #else @@ -696,11 +851,11 @@ namespace client if (leaseSet) { context.GetAddressBook ().InsertFullAddress (leaseSet->GetIdentity ()); - SendNamingLookupReply (leaseSet->GetIdentity ()); + SendNamingLookupReply (name, leaseSet->GetIdentity ()); } else { - LogPrint (eLogError, "SAM: naming lookup failed. LeaseSet for ", name, " not found"); + LogPrint (eLogError, "SAM: Naming lookup failed. LeaseSet for ", name, " not found"); #ifdef _MSC_VER size_t len = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY_INVALID_KEY, name.c_str()); #else @@ -710,13 +865,13 @@ namespace client } } - void SAMSocket::SendNamingLookupReply (std::shared_ptr identity) + 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, base64.c_str ()); + 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, base64.c_str ()); + 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); } @@ -751,7 +906,7 @@ namespace client { if (ecode) { - LogPrint (eLogError, "SAM: read error: ", ecode.message ()); + LogPrint (eLogError, "SAM: Read error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate ("read error"); } @@ -786,7 +941,7 @@ namespace client else // closed by peer { uint8_t * buff = new uint8_t[SAM_SOCKET_BUFFER_SIZE]; - // get remaning data + // get remaining data auto len = m_Stream->ReadSome (buff, SAM_SOCKET_BUFFER_SIZE); if (len > 0) // still some data { @@ -828,7 +983,7 @@ namespace client { if (ecode) { - LogPrint (eLogError, "SAM: stream read error: ", ecode.message ()); + LogPrint (eLogError, "SAM: Stream read error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) { if (bytes_transferred > 0) @@ -865,7 +1020,7 @@ namespace client { if (ecode) { - LogPrint (eLogError, "SAM: socket write error: ", ecode.message ()); + LogPrint (eLogError, "SAM: Socket write error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate ("socket write error at HandleWriteI2PData"); } @@ -879,7 +1034,7 @@ namespace client { if (stream) { - LogPrint (eLogDebug, "SAM: incoming I2P connection for session ", m_ID); + LogPrint (eLogDebug, "SAM: Incoming I2P connection for session ", m_ID); m_SocketType = eSAMSocketTypeStream; m_IsAccepting = false; m_Stream = stream; @@ -892,7 +1047,7 @@ namespace client if (it->m_SocketType == eSAMSocketTypeAcceptor) { it->m_IsAccepting = true; - session->localDestination->AcceptOnce (std::bind (&SAMSocket::HandleI2PAccept, it, std::placeholders::_1)); + session->GetLocalDestination ()->AcceptOnce (std::bind (&SAMSocket::HandleI2PAccept, it, std::placeholders::_1)); break; } } @@ -917,9 +1072,46 @@ namespace client LogPrint (eLogWarning, "SAM: I2P acceptor has been reset"); } + void SAMSocket::HandleI2PForward (std::shared_ptr stream, + boost::asio::ip::tcp::endpoint ep) + { + if (stream) + { + LogPrint (eLogDebug, "SAM: Incoming forward I2P connection for session ", m_ID); + auto newSocket = std::make_shared(m_Owner); + newSocket->SetSocketType (eSAMSocketTypeStream); + auto s = shared_from_this (); + newSocket->GetSocket ().async_connect (ep, + [s, newSocket, stream](const boost::system::error_code& ecode) + { + if (!ecode) + { + s->m_Owner.AddSocket (newSocket); + newSocket->Receive (); + newSocket->m_Stream = stream; + newSocket->m_ID = s->m_ID; + if (!s->m_IsSilent) + { + // get remote peer address + auto dest = stream->GetRemoteIdentity()->ToBase64 (); + memcpy (newSocket->m_StreamBuffer, dest.c_str (), dest.length ()); + newSocket->m_StreamBuffer[dest.length ()] = '\n'; + newSocket->HandleI2PReceive (boost::system::error_code (),dest.length () + 1); // we send identity like it has been received from stream + } + else + newSocket->I2PReceive (); + } + else + stream->AsyncClose (); + }); + } + else + LogPrint (eLogWarning, "SAM: I2P forward acceptor has been reset"); + } + void SAMSocket::HandleI2PDatagramReceive (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) { - LogPrint (eLogDebug, "SAM: datagram received ", len); + LogPrint (eLogDebug, "SAM: Datagram received ", len); auto base64 = from.ToBase64 (); auto session = m_Owner.FindSession(m_ID); if(session) @@ -928,19 +1120,9 @@ namespace client if (ep) { // udp forward enabled - size_t bsz = base64.size(); - size_t sz = bsz + 1 + len; - // build datagram body - uint8_t * data = new uint8_t[sz]; - // Destination - memcpy(data, base64.c_str(), bsz); - // linefeed - data[bsz] = '\n'; - // Payload - memcpy(data+bsz+1, buf, len); - // send to remote endpoint - m_Owner.SendTo(data, sz, ep); - delete [] data; + 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 { @@ -955,21 +1137,21 @@ namespace client WriteI2PData(len + l); } else - LogPrint (eLogWarning, "SAM: received datagram size ", len," exceeds buffer"); + LogPrint (eLogWarning, "SAM: Received datagram size ", len," exceeds buffer"); } } } void SAMSocket::HandleI2PRawDatagramReceive (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) { - LogPrint (eLogDebug, "SAM: raw datagram received ", 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); + m_Owner.SendTo({ {buf, len} }, *ep); else { #ifdef _MSC_VER @@ -983,7 +1165,7 @@ namespace client WriteI2PData(len + l); } else - LogPrint (eLogWarning, "SAM: received raw datagram size ", len," exceeds buffer"); + LogPrint (eLogWarning, "SAM: Received raw datagram size ", len," exceeds buffer"); } } } @@ -993,19 +1175,11 @@ namespace client m_Owner.GetService ().post (std::bind( !ec ? &SAMSocket::Receive : &SAMSocket::TerminateClose, shared_from_this())); } - SAMSession::SAMSession (SAMBridge & parent, const std::string & id, SAMSessionType type, std::shared_ptr dest): - m_Bridge(parent), - localDestination (dest), - UDPEndpoint(nullptr), - Name(id), Type (type) + SAMSession::SAMSession (SAMBridge & parent, const std::string & id, SAMSessionType type): + m_Bridge(parent), Name(id), Type (type), UDPEndpoint(nullptr) { } - SAMSession::~SAMSession () - { - i2p::client::context.DeleteLocalDestination (localDestination); - } - void SAMSession::CloseStreams () { for(const auto & itr : m_Bridge.ListSockets(Name)) @@ -1014,10 +1188,66 @@ namespace client } } - SAMBridge::SAMBridge (const std::string& address, int port, bool singleThread): + 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::address::from_string(address), port)), - m_DatagramEndpoint (boost::asio::ip::address::from_string(address), port-1), m_DatagramSocket (GetIOService (), m_DatagramEndpoint), + m_Acceptor (GetIOService (), boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(address), portTCP)), + m_DatagramEndpoint (boost::asio::ip::address::from_string(address), (!portUDP) ? portTCP-1 : portUDP), m_DatagramSocket (GetIOService (), m_DatagramEndpoint), m_SignatureTypes { {"DSA_SHA1", i2p::data::SIGNING_KEY_TYPE_DSA_SHA1}, @@ -1053,13 +1283,13 @@ namespace client } catch (const std::exception& ex) { - LogPrint (eLogError, "SAM: runtime exception: ", ex.what ()); + LogPrint (eLogError, "SAM: Runtime exception: ", ex.what ()); } { std::unique_lock l(m_SessionsMutex); for (auto& it: m_Sessions) - it.second->CloseStreams (); + it.second->Close (); m_Sessions.clear (); } StopIOService (); @@ -1072,6 +1302,12 @@ namespace client std::placeholders::_1, newSocket)); } + void SAMBridge::AddSocket(std::shared_ptr socket) + { + std::unique_lock lock(m_OpenSocketsMutex); + m_OpenSockets.push_back(socket); + } + void SAMBridge::RemoveSocket(const std::shared_ptr & socket) { std::unique_lock lock(m_OpenSocketsMutex); @@ -1086,18 +1322,15 @@ namespace client auto ep = socket->GetSocket ().remote_endpoint (ec); if (!ec) { - LogPrint (eLogDebug, "SAM: new connection from ", ep); - { - std::unique_lock l(m_OpenSocketsMutex); - m_OpenSockets.push_back(socket); - } + LogPrint (eLogDebug, "SAM: New connection from ", ep); + AddSocket (socket); socket->ReceiveHandshake (); } else - LogPrint (eLogError, "SAM: incoming connection error ", ec.message ()); + LogPrint (eLogError, "SAM: Incoming connection error: ", ec.message ()); } else - LogPrint (eLogError, "SAM: accept error: ", ecode.message ()); + LogPrint (eLogError, "SAM: Accept error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Accept (); @@ -1148,7 +1381,8 @@ namespace client if (localDestination) { localDestination->Acquire (); - auto session = std::make_shared(*this, id, type, localDestination); + auto session = (type == eSAMSessionTypeMaster) ? std::make_shared(*this, id, localDestination) : + std::make_shared(*this, id, type, localDestination); std::unique_lock l(m_SessionsMutex); auto ret = m_Sessions.insert (std::make_pair(id, session)); if (!ret.second) @@ -1158,6 +1392,13 @@ namespace client return nullptr; } + bool SAMBridge::AddSession (std::shared_ptr session) + { + if (!session) return false; + auto ret = m_Sessions.emplace (session->Name, session); + return ret.second; + } + void SAMBridge::CloseSession (const std::string& id) { std::shared_ptr session; @@ -1172,9 +1413,8 @@ namespace client } if (session) { - session->localDestination->Release (); - session->localDestination->StopAcceptingStreams (); - session->CloseStreams (); + session->StopLocalDestination (); + session->Close (); if (m_IsSingleThread) { auto timer = std::make_shared(GetService ()); @@ -1208,12 +1448,9 @@ namespace client return list; } - void SAMBridge::SendTo(const uint8_t * buf, size_t len, std::shared_ptr remote) + void SAMBridge::SendTo (const std::vector& bufs, const boost::asio::ip::udp::endpoint& ep) { - if(remote) - { - m_DatagramSocket.send_to(boost::asio::buffer(buf, len), *remote); - } + m_DatagramSocket.send_to (bufs, ep); } void SAMBridge::ReceiveDatagram () @@ -1234,7 +1471,7 @@ namespace client { *eol = 0; eol++; size_t payloadLen = bytes_transferred - ((uint8_t *)eol - m_DatagramReceiveBuffer); - LogPrint (eLogDebug, "SAM: datagram received ", m_DatagramReceiveBuffer," size=", payloadLen); + LogPrint (eLogDebug, "SAM: Datagram received ", m_DatagramReceiveBuffer," size=", payloadLen); char * sessionID = strchr ((char *)m_DatagramReceiveBuffer, ' '); if (sessionID) { @@ -1246,14 +1483,21 @@ namespace client auto session = FindSession (sessionID); if (session) { - i2p::data::IdentityEx dest; - dest.FromBase64 (destination); - if (session->Type == eSAMSessionTypeDatagram) - session->localDestination->GetDatagramDestination ()-> - SendDatagramTo ((uint8_t *)eol, payloadLen, dest.GetIdentHash ()); - else // raw - session->localDestination->GetDatagramDestination ()-> - SendRawDatagramTo ((uint8_t *)eol, payloadLen, dest.GetIdentHash ()); + 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"); @@ -1265,11 +1509,11 @@ namespace client LogPrint (eLogError, "SAM: Missing sessionID"); } else - LogPrint(eLogError, "SAM: invalid datagram"); + LogPrint(eLogError, "SAM: Invalid datagram"); ReceiveDatagram (); } else - LogPrint (eLogError, "SAM: datagram receive error: ", ecode.message ()); + LogPrint (eLogError, "SAM: Datagram receive error: ", ecode.message ()); } bool SAMBridge::ResolveSignatureType (const std::string& name, i2p::data::SigningKeyType& type) const diff --git a/libi2pd_client/SAM.h b/libi2pd_client/SAM.h index 7b1702f5..b4c72754 100644 --- a/libi2pd_client/SAM.h +++ b/libi2pd_client/SAM.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -29,7 +30,7 @@ namespace client { const size_t SAM_SOCKET_BUFFER_SIZE = 8192; const int SAM_SOCKET_CONNECTION_MAX_IDLE = 3600; // in seconds - const int SAM_SESSION_READINESS_CHECK_INTERVAL = 20; // in seconds + const int SAM_SESSION_READINESS_CHECK_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"; @@ -40,7 +41,9 @@ namespace client const char SAM_SESSION_CREATE_DUPLICATED_DEST[] = "SESSION STATUS RESULT=DUPLICATED_DEST\n"; const char SAM_SESSION_CREATE_INVALID_ID[] = "SESSION STATUS RESULT=INVALID_ID\n"; const char SAM_SESSION_STATUS_INVALID_KEY[] = "SESSION STATUS RESULT=INVALID_KEY\n"; - const char SAM_SESSION_STATUS_I2P_ERROR[] = "SESSION STATUS RESULT=I2P_ERROR MESSAGE=%s\n"; + const char SAM_SESSION_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"; @@ -48,13 +51,14 @@ namespace client const char SAM_STREAM_STATUS_CANT_REACH_PEER[] = "STREAM STATUS RESULT=CANT_REACH_PEER\n"; const char SAM_STREAM_STATUS_I2P_ERROR[] = "STREAM STATUS RESULT=I2P_ERROR\n"; const char SAM_STREAM_ACCEPT[] = "STREAM ACCEPT"; + const char SAM_STREAM_FORWARD[] = "STREAM FORWARD"; const char SAM_DATAGRAM_SEND[] = "DATAGRAM SEND"; const char SAM_RAW_SEND[] = "RAW SEND"; const char SAM_DEST_GENERATE[] = "DEST GENERATE"; const char SAM_DEST_REPLY[] = "DEST REPLY PUB=%s PRIV=%s\n"; const char SAM_DEST_REPLY_I2P_ERROR[] = "DEST REPLY RESULT=I2P_ERROR\n"; const char SAM_NAMING_LOOKUP[] = "NAMING LOOKUP"; - const char SAM_NAMING_REPLY[] = "NAMING REPLY RESULT=OK NAME=ME VALUE=%s\n"; + 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"; @@ -69,14 +73,16 @@ namespace client const char SAM_PARAM_SIGNATURE_TYPE[] = "SIGNATURE_TYPE"; const char SAM_PARAM_CRYPTO_TYPE[] = "CRYPTO_TYPE"; const char SAM_PARAM_SIZE[] = "SIZE"; + const char SAM_PARAM_HOST[] = "HOST"; + const char SAM_PARAM_PORT[] = "PORT"; + const char SAM_PARAM_FROM_PORT[] = "FROM_PORT"; const char SAM_VALUE_TRANSIENT[] = "TRANSIENT"; const char SAM_VALUE_STREAM[] = "STREAM"; const char SAM_VALUE_DATAGRAM[] = "DATAGRAM"; const char SAM_VALUE_RAW[] = "RAW"; + const char SAM_VALUE_MASTER[] = "MASTER"; const char SAM_VALUE_TRUE[] = "true"; const char SAM_VALUE_FALSE[] = "false"; - const char SAM_VALUE_HOST[] = "HOST"; - const char SAM_VALUE_PORT[] = "PORT"; enum SAMSocketType { @@ -84,6 +90,7 @@ namespace client eSAMSocketTypeSession, eSAMSocketTypeStream, eSAMSocketTypeAcceptor, + eSAMSocketTypeForward, eSAMSocketTypeTerminated }; @@ -121,6 +128,7 @@ namespace client void I2PReceive (); void HandleI2PReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred); void HandleI2PAccept (std::shared_ptr stream); + void HandleI2PForward (std::shared_ptr stream, boost::asio::ip::tcp::endpoint ep); void HandleWriteI2PData (const boost::system::error_code& ecode, size_t sz); void HandleI2PDatagramReceive (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); void HandleI2PRawDatagramReceive (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); @@ -128,15 +136,18 @@ namespace client 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 SendI2PError(const std::string & msg); size_t ProcessDatagramSend (char * buf, size_t len, const char * data); // from SAM 1.0 void ExtractParams (char * buf, std::map& params); void Connect (std::shared_ptr remote, std::shared_ptr session = nullptr); void HandleConnectLeaseSetRequestComplete (std::shared_ptr leaseSet); - void SendNamingLookupReply (std::shared_ptr identity); + 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 (); @@ -167,28 +178,62 @@ namespace client eSAMSessionTypeUnknown, eSAMSessionTypeStream, eSAMSessionTypeDatagram, - eSAMSessionTypeRaw + eSAMSessionTypeRaw, + eSAMSessionTypeMaster }; struct SAMSession { SAMBridge & m_Bridge; - std::shared_ptr localDestination; - std::shared_ptr UDPEndpoint; std::string Name; SAMSessionType Type; + std::shared_ptr UDPEndpoint; // TODO: move - SAMSession (SAMBridge & parent, const std::string & name, SAMSessionType type, std::shared_ptr dest); - ~SAMSession (); + 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, int port, bool singleThread); + SAMBridge (const std::string& address, uint16_t portTCP, uint16_t portUDP, bool singleThread); ~SAMBridge (); void Start (); @@ -197,14 +242,16 @@ namespace client boost::asio::io_service& GetService () { return GetIOService (); }; std::shared_ptr CreateSession (const std::string& id, SAMSessionType type, const std::string& destination, // empty string means transient const std::map * params); + bool AddSession (std::shared_ptr session); void CloseSession (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 uint8_t * buf, size_t len, std::shared_ptr remote); + 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; diff --git a/libi2pd_client/SOCKS.cpp b/libi2pd_client/SOCKS.cpp index c5428c86..547a2da2 100644 --- a/libi2pd_client/SOCKS.cpp +++ b/libi2pd_client/SOCKS.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -27,7 +27,7 @@ namespace proxy static const size_t socks_buffer_size = 8192; static const size_t max_socks_hostname_size = 255; // Limit for socks5 and bad idea to traverse - static const size_t SOCKS_FORWARDER_BUFFER_SIZE = 8192; + //static const size_t SOCKS_FORWARDER_BUFFER_SIZE = 8192; static const size_t SOCKS_UPSTREAM_SOCKS4A_REPLY_SIZE = 8; @@ -66,6 +66,11 @@ namespace proxy GET5_IPV6, GET5_HOST_SIZE, GET5_HOST, + GET5_USERPASSWD, + GET5_USER_SIZE, + GET5_USER, + GET5_PASSWD_SIZE, + GET5_PASSWD, READY, UPSTREAM_RESOLVE, UPSTREAM_CONNECT, @@ -129,6 +134,7 @@ namespace proxy boost::asio::const_buffers_1 GenerateSOCKS5Response(errTypes error, addrTypes type, const address &addr, uint16_t port); boost::asio::const_buffers_1 GenerateUpstreamRequest(); bool Socks5ChooseAuth(); + void Socks5UserPasswdResponse (); void SocksRequestFailed(errTypes error); void SocksRequestSuccess(); void SentSocksFailed(const boost::system::error_code & ecode); @@ -191,13 +197,13 @@ namespace proxy void SOCKSHandler::AsyncSockRead() { - LogPrint(eLogDebug, "SOCKS: async sock read"); + LogPrint(eLogDebug, "SOCKS: Async sock read"); if (m_sock) { m_sock->async_receive(boost::asio::buffer(m_sock_buff, socks_buffer_size), std::bind(&SOCKSHandler::HandleSockRecv, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); } else { - LogPrint(eLogError,"SOCKS: no socket for read"); + LogPrint(eLogError,"SOCKS: No socket for read"); } } @@ -206,19 +212,19 @@ namespace proxy if (Kill()) return; if (m_sock) { - LogPrint(eLogDebug, "SOCKS: closing socket"); + LogPrint(eLogDebug, "SOCKS: Closing socket"); m_sock->close(); m_sock = nullptr; } if (m_upstreamSock) { - LogPrint(eLogDebug, "SOCKS: closing upstream socket"); + LogPrint(eLogDebug, "SOCKS: Closing upstream socket"); m_upstreamSock->close(); m_upstreamSock = nullptr; } if (m_stream) { - LogPrint(eLogDebug, "SOCKS: closing stream"); + LogPrint(eLogDebug, "SOCKS: Closing stream"); m_stream.reset (); } Done(shared_from_this()); @@ -324,6 +330,15 @@ namespace proxy } } + void SOCKSHandler::Socks5UserPasswdResponse () + { + m_response[0] = 1; // Version of the subnegotiation + m_response[1] = 0; // Response code + LogPrint(eLogDebug, "SOCKS: v5 user/password response"); + boost::asio::async_write(*m_sock, boost::asio::const_buffers_1(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) { @@ -386,7 +401,7 @@ namespace proxy if ( m_cmd != CMD_CONNECT ) { // TODO: we need to support binds and other shit! - LogPrint(eLogError, "SOCKS: unsupported command: ", m_cmd); + LogPrint(eLogError, "SOCKS: Unsupported command: ", m_cmd); SocksRequestFailed(SOCKS5_CMD_UNSUP); return false; } @@ -399,7 +414,7 @@ namespace proxy LogPrint(eLogError, "SOCKS: v5 unsupported address type: ", m_addrtype); break; case SOCKS4: - LogPrint(eLogError, "SOCKS: request with v4a rejected because it's actually SOCKS4"); + LogPrint(eLogError, "SOCKS: Request with v4a rejected because it's actually SOCKS4"); break; } SocksRequestFailed(SOCKS5_ADDR_UNSUP); @@ -426,7 +441,7 @@ namespace proxy EnterState(GET5_AUTHNUM); //Initialize the parser at the right position break; default: - LogPrint(eLogError, "SOCKS: rejected invalid version: ", ((int)*sock_buff)); + LogPrint(eLogError, "SOCKS: Rejected invalid version: ", ((int)*sock_buff)); Terminate(); return false; } @@ -438,10 +453,15 @@ namespace proxy m_parseleft --; if (*sock_buff == AUTH_NONE) m_authchosen = AUTH_NONE; + else if (*sock_buff == AUTH_USERPASSWD) + m_authchosen = AUTH_USERPASSWD; if ( m_parseleft == 0 ) { if (!Socks5ChooseAuth()) return false; - EnterState(GET5_REQUESTV); + if (m_authchosen == AUTH_USERPASSWD) + EnterState(GET5_USERPASSWD); + else + EnterState(GET5_REQUESTV); } break; case GET_COMMAND: @@ -456,7 +476,7 @@ namespace proxy [[fallthrough]]; #endif default: - LogPrint(eLogError, "SOCKS: invalid command: ", ((int)*sock_buff)); + LogPrint(eLogError, "SOCKS: Invalid command: ", ((int)*sock_buff)); SocksRequestFailed(SOCKS5_GEN_FAIL); return false; } @@ -557,8 +577,46 @@ namespace proxy m_parseleft--; if (m_parseleft == 0) EnterState(GET_PORT); break; + case GET5_USERPASSWD: + if (*sock_buff != 1) + { + LogPrint(eLogError,"SOCKS: v5 rejected invalid username/password subnegotiation: ", ((int)*sock_buff)); + SocksRequestFailed(SOCKS5_GEN_FAIL); + return false; + } + EnterState(GET5_USER_SIZE); + break; + case GET5_USER_SIZE: + if (*sock_buff) + EnterState(GET5_USER, *sock_buff); + else // empty user + EnterState(GET5_PASSWD_SIZE); + break; + case GET5_USER: + // skip user for now + m_parseleft--; + if (m_parseleft == 0) EnterState(GET5_PASSWD_SIZE); + break; + case GET5_PASSWD_SIZE: + if (*sock_buff) + EnterState(GET5_PASSWD, *sock_buff); + else // empty password + { + Socks5UserPasswdResponse (); + EnterState(GET5_REQUESTV); + } + break; + case GET5_PASSWD: + // skip passwd for now + m_parseleft--; + if (m_parseleft == 0) + { + Socks5UserPasswdResponse (); + EnterState(GET5_REQUESTV); + } + break; default: - LogPrint(eLogError, "SOCKS: parse state?? ", m_state); + LogPrint(eLogError, "SOCKS: Parse state?? ", m_state); Terminate(); return false; } @@ -576,10 +634,10 @@ namespace proxy void SOCKSHandler::HandleSockRecv(const boost::system::error_code & ecode, std::size_t len) { - LogPrint(eLogDebug, "SOCKS: received ", len, " bytes"); + LogPrint(eLogDebug, "SOCKS: Received ", len, " bytes"); if(ecode) { - LogPrint(eLogWarning, "SOCKS: recv got error: ", ecode); + LogPrint(eLogWarning, "SOCKS: Recv got error: ", ecode); Terminate(); return; } @@ -589,7 +647,7 @@ namespace proxy if (m_state == READY) { const std::string addr = m_address.dns.ToString(); - LogPrint(eLogInfo, "SOCKS: requested ", addr, ":" , m_port); + LogPrint(eLogInfo, "SOCKS: Requested ", addr, ":" , m_port); const size_t addrlen = addr.size(); // does it end with .i2p? if ( addr.rfind(".i2p") == addrlen - 4) { @@ -612,7 +670,7 @@ namespace proxy void SOCKSHandler::SentSocksFailed(const boost::system::error_code & ecode) { if (ecode) - LogPrint (eLogError, "SOCKS: closing socket after sending failure because: ", ecode.message ()); + LogPrint (eLogError, "SOCKS: Closing socket after sending failure because: ", ecode.message ()); Terminate(); } @@ -621,7 +679,7 @@ namespace proxy if (!ecode) { if (Kill()) return; - LogPrint (eLogInfo, "SOCKS: new I2PTunnel connection"); + LogPrint (eLogInfo, "SOCKS: New I2PTunnel connection"); auto connection = std::make_shared(GetOwner(), m_sock, m_stream); GetOwner()->AddHandler (connection); connection->I2PConnect (m_remaining_data,m_remaining_data_len); @@ -629,7 +687,7 @@ namespace proxy } else { - LogPrint (eLogError, "SOCKS: closing socket after completion reply because: ", ecode.message ()); + LogPrint (eLogError, "SOCKS: Closing socket after completion reply because: ", ecode.message ()); Terminate(); } } @@ -638,7 +696,7 @@ namespace proxy { if (ecode) { - LogPrint (eLogError, "SOCKS: closing socket after sending reply because: ", ecode.message ()); + LogPrint (eLogError, "SOCKS: Closing socket after sending reply because: ", ecode.message ()); Terminate(); } } @@ -652,14 +710,14 @@ namespace proxy } else { - LogPrint (eLogError, "SOCKS: error when creating the stream, check the previous warnings for more info"); + LogPrint (eLogError, "SOCKS: Error when creating the stream, check the previous warnings for more info"); SocksRequestFailed(SOCKS5_HOST_UNREACH); } } void SOCKSHandler::ForwardSOCKS() { - LogPrint(eLogInfo, "SOCKS: forwarding to upstream"); + LogPrint(eLogInfo, "SOCKS: Forwarding to upstream"); EnterState(UPSTREAM_RESOLVE); boost::asio::ip::tcp::resolver::query q(m_UpstreamProxyAddress, std::to_string(m_UpstreamProxyPort)); m_proxy_resolver.async_resolve(q, std::bind(&SOCKSHandler::HandleUpstreamResolved, shared_from_this(), @@ -668,12 +726,12 @@ namespace proxy void SOCKSHandler::AsyncUpstreamSockRead() { - LogPrint(eLogDebug, "SOCKS: async upstream sock read"); + LogPrint(eLogDebug, "SOCKS: Async upstream sock read"); if (m_upstreamSock) { m_upstreamSock->async_read_some(boost::asio::buffer(m_upstream_response, SOCKS_UPSTREAM_SOCKS4A_REPLY_SIZE), std::bind(&SOCKSHandler::HandleUpstreamSockRecv, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); } else { - LogPrint(eLogError, "SOCKS: no upstream socket for read"); + LogPrint(eLogError, "SOCKS: No upstream socket for read"); SocksRequestFailed(SOCKS5_GEN_FAIL); } } @@ -685,7 +743,7 @@ namespace proxy // we are trying to handshake but it failed SocksRequestFailed(SOCKS5_NET_UNREACH); } else { - LogPrint(eLogError, "SOCKS: bad state when reading from upstream: ", (int) m_state); + LogPrint(eLogError, "SOCKS: Bad state when reading from upstream: ", (int) m_state); } return; } @@ -694,7 +752,7 @@ namespace proxy void SOCKSHandler::SocksUpstreamSuccess() { - LogPrint(eLogInfo, "SOCKS: upstream success"); + LogPrint(eLogInfo, "SOCKS: Upstream success"); boost::asio::const_buffers_1 response(nullptr, 0); switch (m_socksv) { @@ -734,7 +792,7 @@ namespace proxy SocksUpstreamSuccess(); } else { // upstream failure - LogPrint(eLogError, "SOCKS: upstream proxy failure: ", (int) resp); + LogPrint(eLogError, "SOCKS: Upstream proxy failure: ", (int) resp); // TODO: runtime error? SocksRequestFailed(SOCKS5_GEN_FAIL); } @@ -744,30 +802,30 @@ namespace proxy } } else { // invalid state - LogPrint(eLogError, "SOCKS: invalid state reading from upstream: ", (int) m_state); + LogPrint(eLogError, "SOCKS: Invalid state reading from upstream: ", (int) m_state); } } void SOCKSHandler::SendUpstreamRequest() { - LogPrint(eLogInfo, "SOCKS: negotiating with upstream proxy"); + LogPrint(eLogInfo, "SOCKS: Negotiating with upstream proxy"); EnterState(UPSTREAM_HANDSHAKE); if (m_upstreamSock) { boost::asio::write(*m_upstreamSock, GenerateUpstreamRequest()); AsyncUpstreamSockRead(); } else { - LogPrint(eLogError, "SOCKS: no upstream socket to send handshake to"); + LogPrint(eLogError, "SOCKS: No upstream socket to send handshake to"); } } void SOCKSHandler::HandleUpstreamConnected(const boost::system::error_code & ecode, boost::asio::ip::tcp::resolver::iterator itr) { if (ecode) { - LogPrint(eLogWarning, "SOCKS: could not connect to upstream proxy: ", ecode.message()); + LogPrint(eLogWarning, "SOCKS: Could not connect to upstream proxy: ", ecode.message()); SocksRequestFailed(SOCKS5_NET_UNREACH); return; } - LogPrint(eLogInfo, "SOCKS: connected to upstream proxy"); + LogPrint(eLogInfo, "SOCKS: Connected to upstream proxy"); SendUpstreamRequest(); } @@ -775,11 +833,11 @@ namespace proxy { if (ecode) { // error resolving - LogPrint(eLogWarning, "SOCKS: upstream proxy", m_UpstreamProxyAddress, " not resolved: ", ecode.message()); + LogPrint(eLogWarning, "SOCKS: Upstream proxy", m_UpstreamProxyAddress, " not resolved: ", ecode.message()); SocksRequestFailed(SOCKS5_NET_UNREACH); return; } - LogPrint(eLogInfo, "SOCKS: upstream proxy resolved"); + LogPrint(eLogInfo, "SOCKS: Upstream proxy resolved"); EnterState(UPSTREAM_CONNECT); auto & service = GetOwner()->GetService(); m_upstreamSock = std::make_shared(service); @@ -788,7 +846,7 @@ namespace proxy shared_from_this(), std::placeholders::_1, std::placeholders::_2)); } - SOCKSServer::SOCKSServer(const std::string& name, const std::string& address, int port, + SOCKSServer::SOCKSServer(const std::string& name, const std::string& address, uint16_t port, bool outEnable, const std::string& outAddress, uint16_t outPort, std::shared_ptr localDestination) : TCPIPAcceptor (address, port, localDestination ? localDestination : i2p::client::context.GetSharedLocalDestination ()), m_Name (name) diff --git a/libi2pd_client/SOCKS.h b/libi2pd_client/SOCKS.h index f41cfd72..bd88d6e6 100644 --- a/libi2pd_client/SOCKS.h +++ b/libi2pd_client/SOCKS.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2023, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -23,7 +23,7 @@ namespace proxy { public: - SOCKSServer(const std::string& name, const std::string& address, int port, bool outEnable, const std::string& outAddress, uint16_t outPort, + SOCKSServer(const std::string& name, const std::string& address, uint16_t port, bool outEnable, const std::string& outAddress, uint16_t outPort, std::shared_ptr localDestination = nullptr); ~SOCKSServer() {}; diff --git a/libi2pd_client/UDPTunnel.cpp b/libi2pd_client/UDPTunnel.cpp new file mode 100644 index 00000000..1e4b3d7c --- /dev/null +++ b/libi2pd_client/UDPTunnel.cpp @@ -0,0 +1,377 @@ +/* +* 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 "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) + { + std::lock_guard lock(m_SessionsMutex); + m_LastSession = ObtainUDPSession(from, toPort, fromPort); + } + m_LastSession->IPSocket.send_to(boost::asio::buffer(buf, len), m_RemoteEndpoint); + m_LastSession->LastActivity = i2p::util::GetMillisecondsSinceEpoch(); + } + + void I2PUDPServerTunnel::HandleRecvFromI2PRaw (uint16_t, uint16_t, const uint8_t * buf, size_t len) + { + if (m_LastSession) + { + m_LastSession->IPSocket.send_to(boost::asio::buffer(buf, len), m_RemoteEndpoint); + m_LastSession->LastActivity = i2p::util::GetMillisecondsSinceEpoch(); + } + } + + void I2PUDPServerTunnel::ExpireStale(const uint64_t delta) + { + std::lock_guard lock(m_SessionsMutex); + uint64_t now = i2p::util::GetMillisecondsSinceEpoch(); + auto itr = m_Sessions.begin(); + while(itr != m_Sessions.end()) { + if(now - (*itr)->LastActivity >= delta ) + itr = m_Sessions.erase(itr); + else + ++itr; + } + } + + void I2PUDPClientTunnel::ExpireStale(const uint64_t delta) + { + std::lock_guard lock(m_SessionsMutex); + uint64_t now = i2p::util::GetMillisecondsSinceEpoch(); + std::vector removePorts; + for (const auto & s : m_Sessions) { + if (now - s.second->second >= delta) + removePorts.push_back(s.first); + } + for(auto port : removePorts) { + m_Sessions.erase(port); + } + } + + UDPSessionPtr I2PUDPServerTunnel::ObtainUDPSession(const i2p::data::IdentityEx& from, uint16_t localPort, uint16_t remotePort) + { + auto ih = from.GetIdentHash(); + for (auto & s : m_Sessions ) + { + if (s->Identity.GetLL()[0] == ih.GetLL()[0] && remotePort == s->RemotePort) + { + /** found existing session */ + LogPrint(eLogDebug, "UDPServer: Found session ", s->IPSocket.local_endpoint(), " ", ih.ToBase32()); + return s; + } + } + boost::asio::ip::address addr; + /** create new udp session */ + if(m_IsUniqueLocal && m_LocalAddress.is_loopback()) + { + auto ident = from.GetIdentHash(); + addr = GetLoopbackAddressFor(ident); + } + else + addr = m_LocalAddress; + boost::asio::ip::udp::endpoint ep(addr, 0); + m_Sessions.push_back(std::make_shared(ep, m_LocalDest, m_RemoteEndpoint, ih, localPort, remotePort)); + auto & back = m_Sessions.back(); + return back; + } + + UDPSession::UDPSession(boost::asio::ip::udp::endpoint localEndpoint, + const std::shared_ptr & localDestination, + 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 port, bool gzip) : + m_IsUniqueLocal (true), m_Name (name), m_LocalAddress (localAddress), + m_RemoteEndpoint (forwardTo), m_LocalDest (localDestination), m_Gzip (gzip) + { + } + + I2PUDPServerTunnel::~I2PUDPServerTunnel () + { + Stop (); + } + + void I2PUDPServerTunnel::Start () + { + m_LocalDest->Start (); + + auto dgram = m_LocalDest->CreateDatagramDestination (m_Gzip); + dgram->SetReceiver (std::bind (&I2PUDPServerTunnel::HandleRecvFromI2P, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5)); + dgram->SetRawReceiver (std::bind (&I2PUDPServerTunnel::HandleRecvFromI2PRaw, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); + } + + void I2PUDPServerTunnel::Stop () + { + auto dgram = m_LocalDest->GetDatagramDestination (); + if (dgram) dgram->ResetReceiver (); + } + + std::vector > I2PUDPServerTunnel::GetSessions () + { + std::vector > sessions; + std::lock_guard lock (m_SessionsMutex); + + for (UDPSessionPtr s: m_Sessions) + { + if (!s->m_Destination) continue; + auto info = s->m_Destination->GetInfoForRemote (s->Identity); + if (!info) continue; + + auto sinfo = std::make_shared (); + sinfo->Name = m_Name; + sinfo->LocalIdent = std::make_shared (m_LocalDest->GetIdentHash ().data ()); + sinfo->RemoteIdent = std::make_shared (s->Identity.data ()); + sinfo->CurrentIBGW = info->IBGW; + sinfo->CurrentOBEP = info->OBEP; + sessions.push_back (sinfo); + } + return sessions; + } + + I2PUDPClientTunnel::I2PUDPClientTunnel (const std::string & name, const std::string &remoteDest, + 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)); + dgram->SetRawReceiver (std::bind (&I2PUDPClientTunnel::HandleRecvFromI2PRaw, this, + std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); + + m_LocalDest->Start (); + if (m_ResolveThread == nullptr) + m_ResolveThread = new std::thread (std::bind (&I2PUDPClientTunnel::TryResolving, this)); + RecvFromLocal (); + } + + void I2PUDPClientTunnel::Stop () + { + auto dgram = m_LocalDest->GetDatagramDestination (); + if (dgram) dgram->ResetReceiver (); + m_cancel_resolve = true; + + m_Sessions.clear(); + + if(m_LocalSocket && m_LocalSocket->is_open ()) + m_LocalSocket->close (); + + if(m_ResolveThread) + { + m_ResolveThread->join (); + delete m_ResolveThread; + m_ResolveThread = nullptr; + } + m_RemoteAddr = nullptr; + } + + void I2PUDPClientTunnel::RecvFromLocal () + { + m_LocalSocket->async_receive_from (boost::asio::buffer (m_RecvBuff, I2P_UDP_MAX_MTU), + m_RecvEndpoint, std::bind (&I2PUDPClientTunnel::HandleRecvFromLocal, this, std::placeholders::_1, std::placeholders::_2)); + } + + void I2PUDPClientTunnel::HandleRecvFromLocal (const boost::system::error_code & ec, std::size_t transferred) + { + if (m_cancel_resolve) { + LogPrint (eLogDebug, "UDP Client: Ignoring incoming data: stopping"); + return; + } + if (ec) { + LogPrint (eLogError, "UDP Client: Reading from socket error: ", ec.message (), ". Restarting listener..."); + RecvFromLocal (); // Restart listener and continue work + return; + } + if (!m_RemoteAddr || !m_RemoteAddr->IsIdentHash ()) // TODO: handle B33 + { + LogPrint (eLogWarning, "UDP Client: Remote endpoint not resolved yet"); + RecvFromLocal (); + return; // drop, remote not resolved + } + auto remotePort = m_RecvEndpoint.port (); + if (!m_LastPort || m_LastPort != remotePort) + { + auto itr = m_Sessions.find (remotePort); + if (itr != m_Sessions.end ()) + m_LastSession = itr->second; + else + { + m_LastSession = std::make_shared (boost::asio::ip::udp::endpoint (m_RecvEndpoint), 0); + m_Sessions.emplace (remotePort, m_LastSession); + } + m_LastPort = remotePort; + } + // send off to remote i2p destination + auto ts = i2p::util::GetMillisecondsSinceEpoch (); + LogPrint (eLogDebug, "UDP Client: Send ", transferred, " to ", m_RemoteAddr->identHash.ToBase32 (), ":", RemotePort); + auto session = m_LocalDest->GetDatagramDestination ()->GetSession (m_RemoteAddr->identHash); + if (ts > m_LastSession->second + I2P_UDP_REPLIABLE_DATAGRAM_INTERVAL) + m_LocalDest->GetDatagramDestination ()->SendDatagram (session, m_RecvBuff, transferred, remotePort, RemotePort); + else + m_LocalDest->GetDatagramDestination ()->SendRawDatagram (session, m_RecvBuff, transferred, remotePort, RemotePort); + size_t numPackets = 0; + while (numPackets < i2p::datagram::DATAGRAM_SEND_QUEUE_MAX_SIZE) + { + boost::system::error_code ec; + size_t moreBytes = m_LocalSocket->available (ec); + if (ec || !moreBytes) break; + transferred = m_LocalSocket->receive_from (boost::asio::buffer (m_RecvBuff, I2P_UDP_MAX_MTU), m_RecvEndpoint, 0, ec); + remotePort = m_RecvEndpoint.port (); + // TODO: check remotePort + m_LocalDest->GetDatagramDestination ()->SendRawDatagram (session, m_RecvBuff, transferred, remotePort, RemotePort); + numPackets++; + } + if (numPackets) + LogPrint (eLogDebug, "UDP Client: Sent ", numPackets, " more packets to ", m_RemoteAddr->identHash.ToBase32 ()); + m_LocalDest->GetDatagramDestination ()->FlushSendQueue (session); + + // mark convo as active + if (m_LastSession) + m_LastSession->second = ts; + RecvFromLocal (); + } + + std::vector > I2PUDPClientTunnel::GetSessions () + { + // TODO: implement + std::vector > infos; + return infos; + } + + void I2PUDPClientTunnel::TryResolving () + { + i2p::util::SetThreadName ("UDP Resolver"); + LogPrint (eLogInfo, "UDP Tunnel: Trying to resolve ", m_RemoteDest); + + while (!(m_RemoteAddr = context.GetAddressBook().GetAddress(m_RemoteDest)) && !m_cancel_resolve) + { + LogPrint (eLogWarning, "UDP Tunnel: Failed to lookup ", m_RemoteDest); + std::this_thread::sleep_for (std::chrono::seconds (1)); + } + if (m_cancel_resolve) + { + LogPrint(eLogError, "UDP Tunnel: Lookup of ", m_RemoteDest, " was cancelled"); + return; + } + if (!m_RemoteAddr) + { + LogPrint (eLogError, "UDP Tunnel: ", m_RemoteDest, " not found"); + return; + } + LogPrint(eLogInfo, "UDP Tunnel: Resolved ", m_RemoteDest, " to ", m_RemoteAddr->identHash.ToBase32 ()); + } + + void I2PUDPClientTunnel::HandleRecvFromI2P (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) + { + if (m_RemoteAddr && from.GetIdentHash() == m_RemoteAddr->identHash) + HandleRecvFromI2PRaw (fromPort, toPort, buf, len); + else + LogPrint(eLogWarning, "UDP Client: Unwarranted traffic from ", from.GetIdentHash().ToBase32 ()); + } + + void I2PUDPClientTunnel::HandleRecvFromI2PRaw (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) + { + auto itr = m_Sessions.find (toPort); + // found convo ? + if (itr != m_Sessions.end ()) + { + // found convo + if (len > 0) + { + LogPrint (eLogDebug, "UDP Client: Got ", len, "B from ", m_RemoteAddr ? m_RemoteAddr->identHash.ToBase32 () : ""); + m_LocalSocket->send_to (boost::asio::buffer (buf, len), itr->second->first); + // mark convo as active + itr->second->second = i2p::util::GetMillisecondsSinceEpoch (); + } + } + else + LogPrint (eLogWarning, "UDP Client: Not tracking udp session using port ", (int) toPort); + } + +} +} diff --git a/libi2pd_client/UDPTunnel.h b/libi2pd_client/UDPTunnel.h new file mode 100644 index 00000000..862ce216 --- /dev/null +++ b/libi2pd_client/UDPTunnel.h @@ -0,0 +1,187 @@ +/* +* 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 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 converstation been idle in ms */ + uint64_t idle; + }; + + typedef std::shared_ptr UDPSessionPtr; + + /** server side udp tunnel, many i2p inbound to 1 ip outbound */ + class I2PUDPServerTunnel + { + public: + + I2PUDPServerTunnel (const std::string & name, + std::shared_ptr localDestination, + 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); + + private: + + bool m_IsUniqueLocal; + const std::string m_Name; + boost::asio::ip::address m_LocalAddress; + boost::asio::ip::udp::endpoint m_RemoteEndpoint; + std::mutex m_SessionsMutex; + std::vector m_Sessions; + std::shared_ptr m_LocalDest; + UDPSessionPtr m_LastSession; + bool m_Gzip; + + public: + + bool isUpdated; // transient, used during reload only + }; + + class I2PUDPClientTunnel + { + public: + + I2PUDPClientTunnel (const std::string & name, const std::string &remoteDest, + const boost::asio::ip::udp::endpoint& localEndpoint, std::shared_ptr localDestination, + uint16_t remotePort, bool gzip); + ~I2PUDPClientTunnel (); + + void Start (); + void Stop (); + const char * GetName () const { return m_Name.c_str(); } + std::vector > GetSessions (); + + bool IsLocalDestination (const i2p::data::IdentHash & destination) const { return destination == m_LocalDest->GetIdentHash(); } + + std::shared_ptr GetLocalDestination () const { return m_LocalDest; } + inline void SetLocalDestination (std::shared_ptr dest) + { + if (m_LocalDest) m_LocalDest->Release (); + if (dest) dest->Acquire (); + m_LocalDest = dest; + } + + void ExpireStale (const uint64_t delta=I2P_UDP_SESSION_TIMEOUT); + + private: + + typedef std::pair UDPConvo; + void RecvFromLocal (); + void HandleRecvFromLocal (const boost::system::error_code & e, std::size_t transferred); + void HandleRecvFromI2P (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); + void HandleRecvFromI2PRaw (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); + void TryResolving (); + + private: + + const std::string m_Name; + std::mutex m_SessionsMutex; + std::unordered_map > m_Sessions; // maps i2p port -> local udp convo + const std::string m_RemoteDest; + std::shared_ptr m_LocalDest; + const boost::asio::ip::udp::endpoint m_LocalEndpoint; + std::shared_ptr m_RemoteAddr; + std::thread * m_ResolveThread; + std::unique_ptr m_LocalSocket; + boost::asio::ip::udp::endpoint m_RecvEndpoint; + uint8_t m_RecvBuff[I2P_UDP_MAX_MTU]; + uint16_t RemotePort, m_LastPort; + bool m_cancel_resolve; + bool m_Gzip; + std::shared_ptr m_LastSession; + + public: + + bool isUpdated; // transient, used during reload only + }; + + +} +} + +#endif diff --git a/libi2pd_wrapper/api.go b/libi2pd_wrapper/api.go new file mode 100644 index 00000000..8c215f13 --- /dev/null +++ b/libi2pd_wrapper/api.go @@ -0,0 +1,15 @@ +package api + +/* +* Copyright (c) 2021-2022, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree + */ + +/* +#cgo CXXFLAGS: -I${SRCDIR}/../i18n -I${SRCDIR}/../libi2pd_client -I${SRCDIR}/../libi2pd -g -Wall -Wextra -Wno-unused-parameter -pedantic -Wno-psabi -fPIC -D__AES__ -maes +#cgo LDFLAGS: -L${SRCDIR}/ -l:../libi2pdwrapper.a -l:../libi2pd.a -l:../libi2pdlang.a -latomic -lcrypto -lssl -lz -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread -lstdc++ +*/ +import "C" diff --git a/libi2pd_wrapper/api.swigcxx b/libi2pd_wrapper/api.swigcxx new file mode 100644 index 00000000..e1d18eef --- /dev/null +++ b/libi2pd_wrapper/api.swigcxx @@ -0,0 +1,8 @@ +// See swig.org for more interface options, +// e.g. map std::string to Go string + +%{ +#include "capi.h" +%} + +%include "capi.h" diff --git a/libi2pd_wrapper/capi.cpp b/libi2pd_wrapper/capi.cpp new file mode 100644 index 00000000..af4765da --- /dev/null +++ b/libi2pd_wrapper/capi.cpp @@ -0,0 +1,50 @@ +/* +* Copyright (c) 2021-2022, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#include "../libi2pd/api.h" +#include "capi.h" +#include +#include +#include +#include + + +#ifdef __cplusplus +extern "C" { +#endif + +void C_InitI2P (int argc, char *argv[], const char * appName) +{ + std::cout << argv; + return i2p::api::InitI2P(argc, argv, appName); +} + +void C_TerminateI2P () +{ + return i2p::api::TerminateI2P(); +} + +void C_StartI2P () +{ + std::shared_ptr logStream; + return i2p::api::StartI2P(logStream); +} + +void C_StopI2P () +{ + return i2p::api::StopI2P(); +} + +void C_RunPeerTest () +{ + return i2p::api::RunPeerTest(); +} + +#ifdef __cplusplus +} +#endif diff --git a/libi2pd_wrapper/capi.h b/libi2pd_wrapper/capi.h new file mode 100644 index 00000000..aefd89f3 --- /dev/null +++ b/libi2pd_wrapper/capi.h @@ -0,0 +1,29 @@ +/* +* Copyright (c) 2021-2022, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#ifndef CAPI_H__ +#define CAPI_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +// initialization start and stop +void C_InitI2P (int argc, char *argv[], const char * appName); +//void C_InitI2P (int argc, char** argv, const char * appName); +void C_TerminateI2P (); +void C_StartI2P (); +// write system log to logStream, if not specified to .log in application's folder +void C_StopI2P (); +void C_RunPeerTest (); // should be called after UPnP + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/qt/.gitignore b/qt/.gitignore deleted file mode 100644 index a0155cb2..00000000 --- a/qt/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build*/ diff --git a/qt/i2pd_qt/.gitignore b/qt/i2pd_qt/.gitignore deleted file mode 100644 index f1d57c58..00000000 --- a/qt/i2pd_qt/.gitignore +++ /dev/null @@ -1,11 +0,0 @@ -i2pd_qt.pro.user* -moc_* -ui_* -qrc_* -i2pd_qt -Makefile* -*.stash -object_script.* -i2pd_qt_plugin_import.cpp -i2pd_qt.pro.autosave* - diff --git a/qt/i2pd_qt/ClientTunnelPane.cpp b/qt/i2pd_qt/ClientTunnelPane.cpp deleted file mode 100644 index 256d0510..00000000 --- a/qt/i2pd_qt/ClientTunnelPane.cpp +++ /dev/null @@ -1,203 +0,0 @@ -#include "ClientTunnelPane.h" -#include "ClientContext.h" -#include "SignatureTypeComboboxFactory.h" -#include "QVBoxLayout" - -ClientTunnelPane::ClientTunnelPane(TunnelsPageUpdateListener* tunnelsPageUpdateListener, ClientTunnelConfig* tunconf, QWidget* wrongInputPane_, QLabel* wrongInputLabel_, MainWindow* mainWindow): - TunnelPane(tunnelsPageUpdateListener, tunconf, wrongInputPane_, wrongInputLabel_, mainWindow) {} - -void ClientTunnelPane::setGroupBoxTitle(const QString & title) { - clientTunnelNameGroupBox->setTitle(title); -} - -void ClientTunnelPane::deleteClientTunnelForm() { - TunnelPane::deleteTunnelForm(); - delete clientTunnelNameGroupBox; - clientTunnelNameGroupBox=nullptr; - - //gridLayoutWidget_2->deleteLater(); - //gridLayoutWidget_2=nullptr; -} - -int ClientTunnelPane::appendClientTunnelForm( - ClientTunnelConfig* tunnelConfig, QWidget *tunnelsFormGridLayoutWidget, int tunnelsRow, int height) { - - ClientTunnelPane& ui = *this; - - clientTunnelNameGroupBox = new QGroupBox(tunnelsFormGridLayoutWidget); - clientTunnelNameGroupBox->setObjectName(QStringLiteral("clientTunnelNameGroupBox")); - - //tunnel - gridLayoutWidget_2 = new QWidget(clientTunnelNameGroupBox); - - QComboBox *tunnelTypeComboBox = new QComboBox(gridLayoutWidget_2); - tunnelTypeComboBox->setObjectName(QStringLiteral("tunnelTypeComboBox")); - tunnelTypeComboBox->addItem("Client", i2p::client::I2P_TUNNELS_SECTION_TYPE_CLIENT); - tunnelTypeComboBox->addItem("Socks", i2p::client::I2P_TUNNELS_SECTION_TYPE_SOCKS); - tunnelTypeComboBox->addItem("Websocks", i2p::client::I2P_TUNNELS_SECTION_TYPE_WEBSOCKS); - tunnelTypeComboBox->addItem("HTTP Proxy", i2p::client::I2P_TUNNELS_SECTION_TYPE_HTTPPROXY); - tunnelTypeComboBox->addItem("UDP Client", i2p::client::I2P_TUNNELS_SECTION_TYPE_UDPCLIENT); - - int h=(7+4)*60; - gridLayoutWidget_2->setGeometry(QRect(0, 0, 561, h)); - clientTunnelNameGroupBox->setGeometry(QRect(0, 0, 561, h)); - - { - const QString& type = tunnelConfig->getType(); - int index=0; - if(type==i2p::client::I2P_TUNNELS_SECTION_TYPE_CLIENT)tunnelTypeComboBox->setCurrentIndex(index); - ++index; - if(type==i2p::client::I2P_TUNNELS_SECTION_TYPE_SOCKS)tunnelTypeComboBox->setCurrentIndex(index); - ++index; - if(type==i2p::client::I2P_TUNNELS_SECTION_TYPE_WEBSOCKS)tunnelTypeComboBox->setCurrentIndex(index); - ++index; - if(type==i2p::client::I2P_TUNNELS_SECTION_TYPE_HTTPPROXY)tunnelTypeComboBox->setCurrentIndex(index); - ++index; - if(type==i2p::client::I2P_TUNNELS_SECTION_TYPE_UDPCLIENT)tunnelTypeComboBox->setCurrentIndex(index); - ++index; - } - - setupTunnelPane(tunnelConfig, - clientTunnelNameGroupBox, - gridLayoutWidget_2, tunnelTypeComboBox, - tunnelsFormGridLayoutWidget, tunnelsRow, height, h); - //this->tunnelGroupBox->setGeometry(QRect(0, tunnelsFormGridLayoutWidget->height()+10, 561, (7+5)*40+10)); - - /* - std::string destination; - */ - - //host - ui.horizontalLayout_2 = new QHBoxLayout(); - horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); - ui.destinationLabel = new QLabel(gridLayoutWidget_2); - destinationLabel->setObjectName(QStringLiteral("destinationLabel")); - horizontalLayout_2->addWidget(destinationLabel); - ui.destinationLineEdit = new QLineEdit(gridLayoutWidget_2); - destinationLineEdit->setObjectName(QStringLiteral("destinationLineEdit")); - destinationLineEdit->setText(tunnelConfig->getdest().c_str()); - QObject::connect(destinationLineEdit, SIGNAL(textChanged(const QString &)), - this, SLOT(updated())); - horizontalLayout_2->addWidget(destinationLineEdit); - ui.destinationHorizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); - horizontalLayout_2->addItem(destinationHorizontalSpacer); - tunnelGridLayout->addLayout(horizontalLayout_2); - - /* - * int port; - */ - int gridIndex = 2; - { - int port = tunnelConfig->getport(); - QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); - horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); - ui.portLabel = new QLabel(gridLayoutWidget_2); - portLabel->setObjectName(QStringLiteral("portLabel")); - horizontalLayout_2->addWidget(portLabel); - ui.portLineEdit = new QLineEdit(gridLayoutWidget_2); - portLineEdit->setObjectName(QStringLiteral("portLineEdit")); - portLineEdit->setText(QString::number(port)); - portLineEdit->setMaximumWidth(80); - QObject::connect(portLineEdit, SIGNAL(textChanged(const QString &)), - this, SLOT(updated())); - horizontalLayout_2->addWidget(portLineEdit); - QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); - horizontalLayout_2->addItem(horizontalSpacer); - tunnelGridLayout->addLayout(horizontalLayout_2); - } - /* - * std::string keys; -*/ - { - std::string keys = tunnelConfig->getkeys(); - QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); - horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); - ui.keysLabel = new QLabel(gridLayoutWidget_2); - keysLabel->setObjectName(QStringLiteral("keysLabel")); - horizontalLayout_2->addWidget(keysLabel); - ui.keysLineEdit = new QLineEdit(gridLayoutWidget_2); - keysLineEdit->setObjectName(QStringLiteral("keysLineEdit")); - keysLineEdit->setText(keys.c_str()); - QObject::connect(keysLineEdit, SIGNAL(textChanged(const QString &)), - this, SLOT(updated())); - horizontalLayout_2->addWidget(keysLineEdit); - QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); - horizontalLayout_2->addItem(horizontalSpacer); - tunnelGridLayout->addLayout(horizontalLayout_2); - } - /* - * std::string address; - */ - { - std::string address = tunnelConfig->getaddress(); - QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); - horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); - ui.addressLabel = new QLabel(gridLayoutWidget_2); - addressLabel->setObjectName(QStringLiteral("addressLabel")); - horizontalLayout_2->addWidget(addressLabel); - ui.addressLineEdit = new QLineEdit(gridLayoutWidget_2); - addressLineEdit->setObjectName(QStringLiteral("addressLineEdit")); - addressLineEdit->setText(address.c_str()); - QObject::connect(addressLineEdit, SIGNAL(textChanged(const QString &)), - this, SLOT(updated())); - horizontalLayout_2->addWidget(addressLineEdit); - QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); - horizontalLayout_2->addItem(horizontalSpacer); - tunnelGridLayout->addLayout(horizontalLayout_2); - } - - /* - int destinationPort; - i2p::data::SigningKeyType sigType; -*/ - { - int destinationPort = tunnelConfig->getdestinationPort(); - QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); - horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); - ui.destinationPortLabel = new QLabel(gridLayoutWidget_2); - destinationPortLabel->setObjectName(QStringLiteral("destinationPortLabel")); - horizontalLayout_2->addWidget(destinationPortLabel); - ui.destinationPortLineEdit = new QLineEdit(gridLayoutWidget_2); - destinationPortLineEdit->setObjectName(QStringLiteral("destinationPortLineEdit")); - destinationPortLineEdit->setText(QString::number(destinationPort)); - destinationPortLineEdit->setMaximumWidth(80); - QObject::connect(destinationPortLineEdit, SIGNAL(textChanged(const QString &)), - this, SLOT(updated())); - horizontalLayout_2->addWidget(destinationPortLineEdit); - QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); - horizontalLayout_2->addItem(horizontalSpacer); - tunnelGridLayout->addLayout(horizontalLayout_2); - } - { - i2p::data::SigningKeyType sigType = tunnelConfig->getsigType(); - QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); - horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); - ui.sigTypeLabel = new QLabel(gridLayoutWidget_2); - sigTypeLabel->setObjectName(QStringLiteral("sigTypeLabel")); - horizontalLayout_2->addWidget(sigTypeLabel); - ui.sigTypeComboBox = SignatureTypeComboBoxFactory::createSignatureTypeComboBox(gridLayoutWidget_2, sigType); - sigTypeComboBox->setObjectName(QStringLiteral("sigTypeComboBox")); - QObject::connect(sigTypeComboBox, SIGNAL(currentIndexChanged(int)), - this, SLOT(updated())); - horizontalLayout_2->addWidget(sigTypeComboBox); - QPushButton * lockButton2 = new QPushButton(gridLayoutWidget_2); - horizontalLayout_2->addWidget(lockButton2); - widgetlocks.add(new widgetlock(sigTypeComboBox, lockButton2)); - QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); - horizontalLayout_2->addItem(horizontalSpacer); - tunnelGridLayout->addLayout(horizontalLayout_2); - } - { - I2CPParameters& i2cpParameters = tunnelConfig->getI2cpParameters(); - appendControlsForI2CPParameters(i2cpParameters, gridIndex); - } - - retranslateClientTunnelForm(ui); - - tunnelGridLayout->invalidate(); - - return h; -} - -ServerTunnelPane* ClientTunnelPane::asServerTunnelPane(){return nullptr;} -ClientTunnelPane* ClientTunnelPane::asClientTunnelPane(){return this;} diff --git a/qt/i2pd_qt/ClientTunnelPane.h b/qt/i2pd_qt/ClientTunnelPane.h deleted file mode 100644 index c2e076b7..00000000 --- a/qt/i2pd_qt/ClientTunnelPane.h +++ /dev/null @@ -1,106 +0,0 @@ -#ifndef CLIENTTUNNELPANE_H -#define CLIENTTUNNELPANE_H - -#include "QGridLayout" -#include "QVBoxLayout" - -#include "TunnelPane.h" - -class ClientTunnelConfig; - -class ServerTunnelPane; -class TunnelPane; - -class ClientTunnelPane : public TunnelPane { - Q_OBJECT -public: - ClientTunnelPane(TunnelsPageUpdateListener* tunnelsPageUpdateListener, ClientTunnelConfig* tunconf, QWidget* wrongInputPane_, QLabel* wrongInputLabel_, MainWindow* mainWindow); - virtual ~ClientTunnelPane(){} - virtual ServerTunnelPane* asServerTunnelPane(); - virtual ClientTunnelPane* asClientTunnelPane(); - int appendClientTunnelForm(ClientTunnelConfig* tunnelConfig, QWidget *tunnelsFormGridLayoutWidget, - int tunnelsRow, int height); - void deleteClientTunnelForm(); -private: - QGroupBox *clientTunnelNameGroupBox; - - //tunnel - QWidget *gridLayoutWidget_2; - - //destination - QHBoxLayout *horizontalLayout_2; - QLabel *destinationLabel; - QLineEdit *destinationLineEdit; - QSpacerItem *destinationHorizontalSpacer; - - //port - QLabel * portLabel; - QLineEdit * portLineEdit; - - //keys - QLabel * keysLabel; - QLineEdit * keysLineEdit; - - //address - QLabel * addressLabel; - QLineEdit * addressLineEdit; - - //destinationPort - QLabel * destinationPortLabel; - QLineEdit * destinationPortLineEdit; - - //sigType - QLabel * sigTypeLabel; - QComboBox * sigTypeComboBox; - -protected slots: - virtual void setGroupBoxTitle(const QString & title); - -private: - void retranslateClientTunnelForm(ClientTunnelPane& /*ui*/) { - typeLabel->setText(QApplication::translate("cltTunForm", "Client tunnel type:", 0)); - destinationLabel->setText(QApplication::translate("cltTunForm", "Destination:", 0)); - portLabel->setText(QApplication::translate("cltTunForm", "Port:", 0)); - keysLabel->setText(QApplication::translate("cltTunForm", "Keys:", 0)); - destinationPortLabel->setText(QApplication::translate("cltTunForm", "Destination port:", 0)); - addressLabel->setText(QApplication::translate("cltTunForm", "Address:", 0)); - sigTypeLabel->setText(QApplication::translate("cltTunForm", "Signature type:", 0)); - } -protected: - virtual bool applyDataFromUIToTunnelConfig() { - QString cannotSaveSettings = QApplication::tr("Cannot save settings."); - bool ok=TunnelPane::applyDataFromUIToTunnelConfig(); - if(!ok)return false; - ClientTunnelConfig* ctc=tunnelConfig->asClientTunnelConfig(); - assert(ctc!=nullptr); - - //destination - ctc->setdest(destinationLineEdit->text().toStdString()); - - auto portStr=portLineEdit->text(); - int portInt=portStr.toInt(&ok); - - if(!ok){ - highlightWrongInput(QApplication::tr("Bad port, must be int.")+" "+cannotSaveSettings,portLineEdit); - return false; - } - ctc->setport(portInt); - - ctc->setkeys(keysLineEdit->text().toStdString()); - - ctc->setaddress(addressLineEdit->text().toStdString()); - - auto dportStr=destinationPortLineEdit->text(); - int dportInt=dportStr.toInt(&ok); - if(!ok){ - highlightWrongInput(QApplication::tr("Bad destinationPort, must be int.")+" "+cannotSaveSettings,destinationPortLineEdit); - return false; - } - ctc->setdestinationPort(dportInt); - - ctc->setsigType(readSigTypeComboboxUI(sigTypeComboBox)); - return true; - } -}; - -#endif // CLIENTTUNNELPANE_H diff --git a/qt/i2pd_qt/DaemonQT.cpp b/qt/i2pd_qt/DaemonQT.cpp deleted file mode 100644 index 1e45c583..00000000 --- a/qt/i2pd_qt/DaemonQT.cpp +++ /dev/null @@ -1,195 +0,0 @@ -#include - -#include "DaemonQT.h" -#include "Daemon.h" -#include "mainwindow.h" - -#include "Log.h" - -#include -#include -#include -#include - -//#define DEBUG_WITH_DEFAULT_LOGGING (1) - -namespace i2p -{ -namespace qt -{ - Worker::Worker (DaemonQTImpl& daemon): - m_Daemon (daemon) - { - } - - void Worker::startDaemon() - { - qDebug("Performing daemon start..."); - //try{ - m_Daemon.start(); - qDebug("Daemon started."); - emit resultReady(false, ""); - /*}catch(std::exception ex){ - emit resultReady(true, ex.what()); - }catch(...){ - emit resultReady(true, QObject::tr("Error: unknown exception")); - }*/ - } - void Worker::restartDaemon() - { - qDebug("Performing daemon restart..."); - //try{ - m_Daemon.restart(); - qDebug("Daemon restarted."); - emit resultReady(false, ""); - /*}catch(std::exception ex){ - emit resultReady(true, ex.what()); - }catch(...){ - emit resultReady(true, QObject::tr("Error: unknown exception")); - }*/ - } - void Worker::stopDaemon() { - qDebug("Performing daemon stop..."); - //try{ - m_Daemon.stop(); - qDebug("Daemon stopped."); - emit resultReady(false, ""); - /*}catch(std::exception ex){ - emit resultReady(true, ex.what()); - }catch(...){ - emit resultReady(true, QObject::tr("Error: unknown exception")); - }*/ - } - - Controller::Controller(DaemonQTImpl& daemon): - m_Daemon (daemon) - { - Worker *worker = new Worker (m_Daemon); - worker->moveToThread(&workerThread); - connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater); - connect(this, &Controller::startDaemon, worker, &Worker::startDaemon); - connect(this, &Controller::stopDaemon, worker, &Worker::stopDaemon); - connect(this, &Controller::restartDaemon, worker, &Worker::restartDaemon); - connect(worker, &Worker::resultReady, this, &Controller::handleResults); - workerThread.start(); - } - Controller::~Controller() - { - qDebug("Closing and waiting for daemon worker thread..."); - workerThread.quit(); - workerThread.wait(); - qDebug("Waiting for daemon worker thread finished."); - if(m_Daemon.isRunning()) - { - qDebug("Stopping the daemon..."); - m_Daemon.stop(); - qDebug("Stopped the daemon."); - } - } - - DaemonQTImpl::DaemonQTImpl (): - mutex(nullptr), m_IsRunning(nullptr), m_RunningChangedCallback(nullptr) - { - } - - DaemonQTImpl::~DaemonQTImpl () - { - delete mutex; - } - - bool DaemonQTImpl::init(int argc, char* argv[], std::shared_ptr logstream) - { - mutex=new QMutex(QMutex::Recursive); - setRunningCallback(0); - m_IsRunning=false; - return Daemon.init(argc,argv,logstream); - } - - void DaemonQTImpl::start() - { - QMutexLocker locker(mutex); - setRunning(true); - Daemon.start(); - } - - void DaemonQTImpl::stop() - { - QMutexLocker locker(mutex); - Daemon.stop(); - setRunning(false); - } - - void DaemonQTImpl::restart() - { - QMutexLocker locker(mutex); - stop(); - start(); - } - - void DaemonQTImpl::setRunningCallback(runningChangedCallback cb) - { - m_RunningChangedCallback = cb; - } - - bool DaemonQTImpl::isRunning() - { - return m_IsRunning; - } - - void DaemonQTImpl::setRunning(bool newValue) - { - bool oldValue = m_IsRunning; - if(oldValue!=newValue) - { - m_IsRunning = newValue; - if(m_RunningChangedCallback) - m_RunningChangedCallback(); - } - } - - int RunQT (int argc, char* argv[]) - { - QApplication app(argc, argv); - int result; - - { - std::shared_ptr logstreamptr= -#ifdef DEBUG_WITH_DEFAULT_LOGGING - nullptr -#else - std::make_shared() -#endif - ; - //TODO move daemon init deinit to a bg thread - DaemonQTImpl daemon; - if(logstreamptr) (*logstreamptr) << "Initialising the daemon..." << std::endl; - bool daemonInitSuccess = daemon.init(argc, argv, logstreamptr); - if(!daemonInitSuccess) - { - QMessageBox::critical(0, "Error", "Daemon init failed"); - return 1; - } - LogPrint(eLogDebug, "Initialised, creating the main window..."); - MainWindow w(logstreamptr); - LogPrint(eLogDebug, "Before main window.show()..."); - w.show (); - - { - i2p::qt::Controller daemonQtController(daemon); - w.setI2PController(&daemonQtController); - LogPrint(eLogDebug, "Starting the daemon..."); - emit daemonQtController.startDaemon(); - //daemon.start (); - LogPrint(eLogDebug, "Starting GUI event loop..."); - result = app.exec(); - //daemon.stop (); - } - } - - //QMessageBox::information(&w, "Debug", "demon stopped"); - LogPrint(eLogDebug, "Exiting the application"); - return result; - } -} -} - diff --git a/qt/i2pd_qt/DaemonQT.h b/qt/i2pd_qt/DaemonQT.h deleted file mode 100644 index aa329f56..00000000 --- a/qt/i2pd_qt/DaemonQT.h +++ /dev/null @@ -1,88 +0,0 @@ -#ifndef DAEMONQT_H -#define DAEMONQT_H - -#include -#include -#include -#include -#include - -namespace i2p -{ -namespace qt -{ - class DaemonQTImpl - { - public: - - DaemonQTImpl (); - ~DaemonQTImpl (); - - typedef void (*runningChangedCallback)(); - - /** - * @brief init - * @param argc - * @param argv - * @return success - */ - bool init(int argc, char* argv[], std::shared_ptr logstream); - void start(); - void stop(); - void restart(); - void setRunningCallback(runningChangedCallback cb); - bool isRunning(); - private: - void setRunning(bool running); - void showError(std::string errorMsg); - private: - QMutex* mutex; - bool m_IsRunning; - runningChangedCallback m_RunningChangedCallback; - }; - - class Worker : public QObject - { - Q_OBJECT - public: - - Worker (DaemonQTImpl& daemon); - - private: - - DaemonQTImpl& m_Daemon; - - public slots: - void startDaemon(); - void restartDaemon(); - void stopDaemon(); - - signals: - void resultReady(bool failed, QString failureMessage); - }; - - class Controller : public QObject - { - Q_OBJECT - QThread workerThread; - public: - Controller(DaemonQTImpl& daemon); - ~Controller(); - private: - DaemonQTImpl& m_Daemon; - - public slots: - void handleResults(bool failed, QString failureMessage){ - if(failed){ - QMessageBox::critical(0, QObject::tr("Error"), failureMessage); - } - } - signals: - void startDaemon(); - void stopDaemon(); - void restartDaemon(); - }; -} -} - -#endif // DAEMONQT_H diff --git a/qt/i2pd_qt/DelayedSaveManager.cpp b/qt/i2pd_qt/DelayedSaveManager.cpp deleted file mode 100644 index 8e17d111..00000000 --- a/qt/i2pd_qt/DelayedSaveManager.cpp +++ /dev/null @@ -1,3 +0,0 @@ -#include "DelayedSaveManager.h" - -DelayedSaveManager::DelayedSaveManager(){} diff --git a/qt/i2pd_qt/DelayedSaveManager.h b/qt/i2pd_qt/DelayedSaveManager.h deleted file mode 100644 index b09aa28f..00000000 --- a/qt/i2pd_qt/DelayedSaveManager.h +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef DELAYEDSAVEMANAGER_H -#define DELAYEDSAVEMANAGER_H - -#include "Saver.h" - -class DelayedSaveManager -{ -public: - DelayedSaveManager(); - - virtual void setSaver(Saver* saver)=0; - - typedef unsigned int DATA_SERIAL_TYPE; - - virtual void delayedSave(DATA_SERIAL_TYPE dataSerial, bool needsTunnelFocus, std::string tunnelNameToFocus)=0; - - //returns false iff save failed - virtual bool appExiting()=0; - - virtual bool needsFocusOnTunnel()=0; - virtual std::string& getTunnelNameToFocus()=0; -}; - -#endif // DELAYEDSAVEMANAGER_H diff --git a/qt/i2pd_qt/DelayedSaveManagerImpl.cpp b/qt/i2pd_qt/DelayedSaveManagerImpl.cpp deleted file mode 100644 index f6704c17..00000000 --- a/qt/i2pd_qt/DelayedSaveManagerImpl.cpp +++ /dev/null @@ -1,140 +0,0 @@ -#include "DelayedSaveManagerImpl.h" - -DelayedSaveManagerImpl::DelayedSaveManagerImpl() : - saver(nullptr), - lastDataSerialSeen(DelayedSaveManagerImpl::INITIAL_DATA_SERIAL), - lastSaveStartedTimestamp(A_VERY_OBSOLETE_TIMESTAMP), - exiting(false), - thread(new DelayedSaveThread(this)) -{ -} - -void DelayedSaveManagerImpl::setSaver(Saver* saver) { - this->saver = saver; -} - -void DelayedSaveManagerImpl::start() { - thread->start(); -} - -bool DelayedSaveManagerImpl::isSaverValid() { - return saver != nullptr; -} - -void DelayedSaveManagerImpl::delayedSave(DATA_SERIAL_TYPE dataSerial, bool focusOnTunnel, std::string tunnelNameToFocus_) { - if(lastDataSerialSeen==dataSerial)return; - this->focusOnTunnel = focusOnTunnel; - tunnelNameToFocus = tunnelNameToFocus_; - lastDataSerialSeen=dataSerial; - assert(isSaverValid()); - TIMESTAMP_TYPE now = getTime(); - TIMESTAMP_TYPE wakeTime = lastSaveStartedTimestamp + DelayedSaveThread::WAIT_TIME_MILLIS; - if(now < wakeTime) { - //defer save until lastSaveStartedTimestamp + DelayedSaveThread::WAIT_TIME_MILLIS - thread->deferSaveUntil(wakeTime); - return; - } - lastSaveStartedTimestamp = now; - thread->startSavingNow(); -} - -bool DelayedSaveManagerImpl::appExiting() { - exiting=true; - thread->wakeThreadAndJoinThread(); - assert(isSaverValid()); - saver->save(false, ""); - return true; -} - -DelayedSaveThread::DelayedSaveThread(DelayedSaveManagerImpl* delayedSaveManagerImpl_): - delayedSaveManagerImpl(delayedSaveManagerImpl_), - mutex(new QMutex()), - waitCondition(new QWaitCondition()), - saveNow(false), - defer(false) -{ - mutex->lock(); -} - -DelayedSaveThread::~DelayedSaveThread(){ - mutex->unlock(); - delete mutex; - delete waitCondition; -} - -void DelayedSaveThread::run() { - forever { - if(delayedSaveManagerImpl->isExiting())return; - waitCondition->wait(mutex, WAIT_TIME_MILLIS); - if(delayedSaveManagerImpl->isExiting())return; - Saver* saver = delayedSaveManagerImpl->getSaver(); - assert(saver!=nullptr); - if(saveNow) { - saveNow = false; - const bool focusOnTunnel = delayedSaveManagerImpl->needsFocusOnTunnel(); - const std::string tunnelNameToFocus = delayedSaveManagerImpl->getTunnelNameToFocus(); - saver->save(focusOnTunnel, tunnelNameToFocus); - continue; - } - if(defer) { - defer=false; -#define max(a,b) (((a)>(b))?(a):(b)) - forever { - TIMESTAMP_TYPE now = DelayedSaveManagerImpl::getTime(); - TIMESTAMP_TYPE millisToWait = max(wakeTime-now, 0); - if(millisToWait>0) { - waitCondition->wait(mutex, millisToWait); - if(delayedSaveManagerImpl->isExiting())return; - continue; - } - const bool focusOnTunnel = delayedSaveManagerImpl->needsFocusOnTunnel(); - const std::string tunnelNameToFocus = delayedSaveManagerImpl->getTunnelNameToFocus(); - saver->save(focusOnTunnel, tunnelNameToFocus); - break; //break inner loop - } - } - } -} - -void DelayedSaveThread::wakeThreadAndJoinThread() { - waitCondition->wakeAll(); - quit(); - wait();//join //"similar to the POSIX pthread_join()" -} - -DelayedSaveManagerImpl::TIMESTAMP_TYPE DelayedSaveManagerImpl::getTime() { - return QDateTime::currentMSecsSinceEpoch(); -} - -void DelayedSaveThread::deferSaveUntil(TIMESTAMP_TYPE wakeTime_) { - wakeTime = wakeTime_; - defer = true; - waitCondition->wakeAll(); -} - -void DelayedSaveThread::startSavingNow() { - //mutex->lock(); - saveNow=true; - waitCondition->wakeAll(); - //mutex->unlock(); -} - -DelayedSaveManagerImpl::~DelayedSaveManagerImpl() { - thread->wakeThreadAndJoinThread(); - delete thread; -} - -bool DelayedSaveManagerImpl::isExiting() { - return exiting; -} -Saver* DelayedSaveManagerImpl::getSaver() { - return saver; -} - -bool DelayedSaveManagerImpl::needsFocusOnTunnel() { - return focusOnTunnel; -} - -std::string& DelayedSaveManagerImpl::getTunnelNameToFocus() { - return tunnelNameToFocus; -} diff --git a/qt/i2pd_qt/DelayedSaveManagerImpl.h b/qt/i2pd_qt/DelayedSaveManagerImpl.h deleted file mode 100644 index cb1f7568..00000000 --- a/qt/i2pd_qt/DelayedSaveManagerImpl.h +++ /dev/null @@ -1,82 +0,0 @@ -#ifndef DELAYEDSAVEMANAGERIMPL_H -#define DELAYEDSAVEMANAGERIMPL_H - -#include -#include -#include -#include -#include - -#include "mainwindow.h" -#include "DelayedSaveManager.h" -#include "Saver.h" - -class DelayedSaveManagerImpl; - -class DelayedSaveThread : public QThread -{ - Q_OBJECT - -public: - static constexpr unsigned long WAIT_TIME_MILLIS = 1000L; - - typedef qint64 TIMESTAMP_TYPE; - static constexpr TIMESTAMP_TYPE A_VERY_OBSOLETE_TIMESTAMP=0; - - DelayedSaveThread(DelayedSaveManagerImpl* delayedSaveManagerImpl); - virtual ~DelayedSaveThread(); - - void run() override; - - void deferSaveUntil(TIMESTAMP_TYPE wakeTime); - void startSavingNow(); - - void wakeThreadAndJoinThread(); - -private: - DelayedSaveManagerImpl* delayedSaveManagerImpl; - QMutex* mutex; - QWaitCondition* waitCondition; - volatile bool saveNow; - volatile bool defer; - volatile TIMESTAMP_TYPE wakeTime; -}; - -class DelayedSaveManagerImpl : public DelayedSaveManager -{ -public: - DelayedSaveManagerImpl(); - virtual ~DelayedSaveManagerImpl(); - virtual void setSaver(Saver* saver); - virtual void start(); - virtual void delayedSave(DATA_SERIAL_TYPE dataSerial, bool focusOnTunnel, std::string tunnelNameToFocus); - virtual bool appExiting(); - - typedef DelayedSaveThread::TIMESTAMP_TYPE TIMESTAMP_TYPE; - - static constexpr DATA_SERIAL_TYPE INITIAL_DATA_SERIAL=0; - bool isExiting(); - Saver* getSaver(); - static TIMESTAMP_TYPE getTime(); - - bool needsFocusOnTunnel(); - std::string& getTunnelNameToFocus(); - -private: - Saver* saver; - bool isSaverValid(); - - DATA_SERIAL_TYPE lastDataSerialSeen; - - static constexpr TIMESTAMP_TYPE A_VERY_OBSOLETE_TIMESTAMP=DelayedSaveThread::A_VERY_OBSOLETE_TIMESTAMP; - TIMESTAMP_TYPE lastSaveStartedTimestamp; - - bool exiting; - DelayedSaveThread* thread; - void wakeThreadAndJoinThread(); - - bool focusOnTunnel; - std::string tunnelNameToFocus; -}; - -#endif // DELAYEDSAVEMANAGERIMPL_H diff --git a/qt/i2pd_qt/MainWindowItems.cpp b/qt/i2pd_qt/MainWindowItems.cpp deleted file mode 100644 index c1e1ab0a..00000000 --- a/qt/i2pd_qt/MainWindowItems.cpp +++ /dev/null @@ -1,2 +0,0 @@ -#include "MainWindowItems.h" - diff --git a/qt/i2pd_qt/MainWindowItems.h b/qt/i2pd_qt/MainWindowItems.h deleted file mode 100644 index a4be5fe0..00000000 --- a/qt/i2pd_qt/MainWindowItems.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef MAINWINDOWITEMS_H -#define MAINWINDOWITEMS_H - -#include -#include -#include -#include -#include - -#include -#include - -#include "mainwindow.h" - -class MainWindow; - -#endif // MAINWINDOWITEMS_H diff --git a/qt/i2pd_qt/README.md b/qt/i2pd_qt/README.md deleted file mode 100644 index 94186ecf..00000000 --- a/qt/i2pd_qt/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Build Requirements - - * Qt 5 is necessary (because Qt4 lacks QtWidgets/ folder) diff --git a/qt/i2pd_qt/Saver.cpp b/qt/i2pd_qt/Saver.cpp deleted file mode 100644 index 4841acb0..00000000 --- a/qt/i2pd_qt/Saver.cpp +++ /dev/null @@ -1,6 +0,0 @@ -#include "Saver.h" - -Saver::Saver() -{ - -} diff --git a/qt/i2pd_qt/Saver.h b/qt/i2pd_qt/Saver.h deleted file mode 100644 index cefe0220..00000000 --- a/qt/i2pd_qt/Saver.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef SAVER_H -#define SAVER_H - -#include -#include -#include - -class Saver : public QObject -{ - Q_OBJECT - -public: - Saver(); - //false iff failures - virtual bool save(const bool focusOnTunnel, const std::string& tunnelNameToFocus)=0; - -signals: - void reloadTunnelsConfigAndUISignal(const QString); - -}; - -#endif // SAVER_H diff --git a/qt/i2pd_qt/SaverImpl.cpp b/qt/i2pd_qt/SaverImpl.cpp deleted file mode 100644 index f35ef5b7..00000000 --- a/qt/i2pd_qt/SaverImpl.cpp +++ /dev/null @@ -1,79 +0,0 @@ -#include "SaverImpl.h" - -#include -#include -#include - -#include "QList" -#include "QString" - -#include "mainwindow.h" - -SaverImpl::SaverImpl(MainWindow *mainWindowPtr_, QList * configItems_, std::map* tunnelConfigs_) : - configItems(configItems_), tunnelConfigs(tunnelConfigs_), confpath(), tunconfpath(), mainWindowPtr(mainWindowPtr_) -{} - -SaverImpl::~SaverImpl() {} - -bool SaverImpl::save(const bool focusOnTunnel, const std::string& tunnelNameToFocus) { - //save main config - { - std::stringstream out; - for(QList::iterator it = configItems->begin(); it!= configItems->end(); ++it) { - MainWindowItem* item = *it; - item->saveToStringStream(out); - } - - using namespace std; - - - QString backup=confpath+"~"; - if(QFile::exists(backup)) QFile::remove(backup);//TODO handle errors - if(QFile::exists(confpath)) QFile::rename(confpath, backup);//TODO handle errors - ofstream outfile; - outfile.open(confpath.toStdString());//TODO handle errors - outfile << out.str().c_str(); - outfile.close(); - } - - //save tunnels config - { - std::stringstream out; - - for (std::map::iterator it=tunnelConfigs->begin(); it!=tunnelConfigs->end(); ++it) { - //const std::string& name = it->first; - TunnelConfig* tunconf = it->second; - tunconf->saveHeaderToStringStream(out); - tunconf->saveToStringStream(out); - tunconf->saveI2CPParametersToStringStream(out); - } - - using namespace std; - - QString backup=tunconfpath+"~"; - if(QFile::exists(backup)) QFile::remove(backup);//TODO handle errors - if(QFile::exists(tunconfpath)) QFile::rename(tunconfpath, backup);//TODO handle errors - ofstream outfile; - outfile.open(tunconfpath.toStdString());//TODO handle errors - outfile << out.str().c_str(); - outfile.close(); - } - - //reload saved configs -#if 0 - i2p::client::context.ReloadConfig(); -#endif - - if(focusOnTunnel) emit reloadTunnelsConfigAndUISignal(QString::fromStdString(tunnelNameToFocus)); - - return true; -} - -void SaverImpl::setConfPath(QString& confpath_) { confpath = confpath_; } - -void SaverImpl::setTunnelsConfPath(QString& tunconfpath_) { tunconfpath = tunconfpath_; } - -/*void SaverImpl::setTunnelFocus(bool focusOnTunnel, std::string tunnelNameToFocus) { - this->focusOnTunnel=focusOnTunnel; - this->tunnelNameToFocus=tunnelNameToFocus; -}*/ diff --git a/qt/i2pd_qt/SaverImpl.h b/qt/i2pd_qt/SaverImpl.h deleted file mode 100644 index c44877a2..00000000 --- a/qt/i2pd_qt/SaverImpl.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef SAVERIMPL_H -#define SAVERIMPL_H - -#include -#include - -#include -#include "QList" - -#include "mainwindow.h" -#include "TunnelConfig.h" -#include "Saver.h" - -class MainWindowItem; -class TunnelConfig; - -class SaverImpl : public Saver -{ -public: - SaverImpl(MainWindow *mainWindowPtr_, QList * configItems_, std::map* tunnelConfigs_); - virtual ~SaverImpl(); - virtual bool save(const bool focusOnTunnel, const std::string& tunnelNameToFocus); - void setConfPath(QString& confpath_); - void setTunnelsConfPath(QString& tunconfpath_); -private: - QList * configItems; - std::map* tunnelConfigs; - QString confpath; - QString tunconfpath; - MainWindow* mainWindowPtr; -}; - -#endif // SAVERIMPL_H diff --git a/qt/i2pd_qt/ServerTunnelPane.cpp b/qt/i2pd_qt/ServerTunnelPane.cpp deleted file mode 100644 index bc6389a9..00000000 --- a/qt/i2pd_qt/ServerTunnelPane.cpp +++ /dev/null @@ -1,261 +0,0 @@ -#include "ServerTunnelPane.h" -#include "ClientContext.h" -#include "SignatureTypeComboboxFactory.h" - -ServerTunnelPane::ServerTunnelPane(TunnelsPageUpdateListener* tunnelsPageUpdateListener, ServerTunnelConfig* tunconf, QWidget* wrongInputPane_, QLabel* wrongInputLabel_, MainWindow* mainWindow): - TunnelPane(tunnelsPageUpdateListener, tunconf, wrongInputPane_, wrongInputLabel_, mainWindow) {} - -void ServerTunnelPane::setGroupBoxTitle(const QString & title) { - serverTunnelNameGroupBox->setTitle(title); -} - -int ServerTunnelPane::appendServerTunnelForm( - ServerTunnelConfig* tunnelConfig, QWidget *tunnelsFormGridLayoutWidget, int tunnelsRow, int height) { - - ServerTunnelPane& ui = *this; - - serverTunnelNameGroupBox = new QGroupBox(tunnelsFormGridLayoutWidget); - serverTunnelNameGroupBox->setObjectName(QStringLiteral("serverTunnelNameGroupBox")); - - //tunnel - gridLayoutWidget_2 = new QWidget(serverTunnelNameGroupBox); - - QComboBox *tunnelTypeComboBox = new QComboBox(gridLayoutWidget_2); - tunnelTypeComboBox->setObjectName(QStringLiteral("tunnelTypeComboBox")); - tunnelTypeComboBox->addItem("Server", i2p::client::I2P_TUNNELS_SECTION_TYPE_SERVER); - tunnelTypeComboBox->addItem("HTTP", i2p::client::I2P_TUNNELS_SECTION_TYPE_HTTP); - tunnelTypeComboBox->addItem("IRC", i2p::client::I2P_TUNNELS_SECTION_TYPE_IRC); - tunnelTypeComboBox->addItem("UDP Server", i2p::client::I2P_TUNNELS_SECTION_TYPE_UDPSERVER); - - int h=19*60; - gridLayoutWidget_2->setGeometry(QRect(0, 0, 561, h)); - serverTunnelNameGroupBox->setGeometry(QRect(0, 0, 561, h)); - - { - const QString& type = tunnelConfig->getType(); - int index=0; - if(type==i2p::client::I2P_TUNNELS_SECTION_TYPE_SERVER)tunnelTypeComboBox->setCurrentIndex(index); - ++index; - if(type==i2p::client::I2P_TUNNELS_SECTION_TYPE_HTTP)tunnelTypeComboBox->setCurrentIndex(index); - ++index; - if(type==i2p::client::I2P_TUNNELS_SECTION_TYPE_IRC)tunnelTypeComboBox->setCurrentIndex(index); - ++index; - if(type==i2p::client::I2P_TUNNELS_SECTION_TYPE_UDPSERVER)tunnelTypeComboBox->setCurrentIndex(index); - ++index; - } - - setupTunnelPane(tunnelConfig, - serverTunnelNameGroupBox, - gridLayoutWidget_2, tunnelTypeComboBox, - tunnelsFormGridLayoutWidget, tunnelsRow, height, h); - //this->tunnelGroupBox->setGeometry(QRect(0, tunnelsFormGridLayoutWidget->height()+10, 561, 18*40+10)); - - //host - ui.horizontalLayout_2 = new QHBoxLayout(); - horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); - ui.hostLabel = new QLabel(gridLayoutWidget_2); - hostLabel->setObjectName(QStringLiteral("hostLabel")); - horizontalLayout_2->addWidget(hostLabel); - ui.hostLineEdit = new QLineEdit(gridLayoutWidget_2); - hostLineEdit->setObjectName(QStringLiteral("hostLineEdit")); - hostLineEdit->setText(tunnelConfig->gethost().c_str()); - QObject::connect(hostLineEdit, SIGNAL(textChanged(const QString &)), - this, SLOT(updated())); - horizontalLayout_2->addWidget(hostLineEdit); - ui.hostHorizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); - horizontalLayout_2->addItem(hostHorizontalSpacer); - tunnelGridLayout->addLayout(horizontalLayout_2); - - int gridIndex = 2; - { - int port = tunnelConfig->getport(); - QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); - horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); - ui.portLabel = new QLabel(gridLayoutWidget_2); - portLabel->setObjectName(QStringLiteral("portLabel")); - horizontalLayout_2->addWidget(portLabel); - ui.portLineEdit = new QLineEdit(gridLayoutWidget_2); - portLineEdit->setObjectName(QStringLiteral("portLineEdit")); - portLineEdit->setText(QString::number(port)); - portLineEdit->setMaximumWidth(80); - QObject::connect(portLineEdit, SIGNAL(textChanged(const QString &)), - this, SLOT(updated())); - horizontalLayout_2->addWidget(portLineEdit); - QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); - horizontalLayout_2->addItem(horizontalSpacer); - tunnelGridLayout->addLayout(horizontalLayout_2); - } - { - std::string keys = tunnelConfig->getkeys(); - QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); - horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); - ui.keysLabel = new QLabel(gridLayoutWidget_2); - keysLabel->setObjectName(QStringLiteral("keysLabel")); - horizontalLayout_2->addWidget(keysLabel); - ui.keysLineEdit = new QLineEdit(gridLayoutWidget_2); - keysLineEdit->setObjectName(QStringLiteral("keysLineEdit")); - keysLineEdit->setText(keys.c_str()); - QObject::connect(keysLineEdit, SIGNAL(textChanged(const QString &)), - this, SLOT(updated())); - horizontalLayout_2->addWidget(keysLineEdit); - QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); - horizontalLayout_2->addItem(horizontalSpacer); - tunnelGridLayout->addLayout(horizontalLayout_2); - } - { - int inPort = tunnelConfig->getinPort(); - QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); - horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); - ui.inPortLabel = new QLabel(gridLayoutWidget_2); - inPortLabel->setObjectName(QStringLiteral("inPortLabel")); - horizontalLayout_2->addWidget(inPortLabel); - ui.inPortLineEdit = new QLineEdit(gridLayoutWidget_2); - inPortLineEdit->setObjectName(QStringLiteral("inPortLineEdit")); - inPortLineEdit->setText(QString::number(inPort)); - inPortLineEdit->setMaximumWidth(80); - QObject::connect(inPortLineEdit, SIGNAL(textChanged(const QString &)), - this, SLOT(updated())); - horizontalLayout_2->addWidget(inPortLineEdit); - QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); - horizontalLayout_2->addItem(horizontalSpacer); - tunnelGridLayout->addLayout(horizontalLayout_2); - } - { - std::string accessList = tunnelConfig->getaccessList(); - QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); - horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); - ui.accessListLabel = new QLabel(gridLayoutWidget_2); - accessListLabel->setObjectName(QStringLiteral("accessListLabel")); - horizontalLayout_2->addWidget(accessListLabel); - ui.accessListLineEdit = new QLineEdit(gridLayoutWidget_2); - accessListLineEdit->setObjectName(QStringLiteral("accessListLineEdit")); - accessListLineEdit->setText(accessList.c_str()); - QObject::connect(accessListLineEdit, SIGNAL(textChanged(const QString &)), - this, SLOT(updated())); - horizontalLayout_2->addWidget(accessListLineEdit); - QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); - horizontalLayout_2->addItem(horizontalSpacer); - tunnelGridLayout->addLayout(horizontalLayout_2); - } - { - std::string hostOverride = tunnelConfig->gethostOverride(); - QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); - horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); - ui.hostOverrideLabel = new QLabel(gridLayoutWidget_2); - hostOverrideLabel->setObjectName(QStringLiteral("hostOverrideLabel")); - horizontalLayout_2->addWidget(hostOverrideLabel); - ui.hostOverrideLineEdit = new QLineEdit(gridLayoutWidget_2); - hostOverrideLineEdit->setObjectName(QStringLiteral("hostOverrideLineEdit")); - hostOverrideLineEdit->setText(hostOverride.c_str()); - QObject::connect(hostOverrideLineEdit, SIGNAL(textChanged(const QString &)), - this, SLOT(updated())); - horizontalLayout_2->addWidget(hostOverrideLineEdit); - QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); - horizontalLayout_2->addItem(horizontalSpacer); - tunnelGridLayout->addLayout(horizontalLayout_2); - } - { - std::string webIRCPass = tunnelConfig->getwebircpass(); - QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); - horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); - ui.webIRCPassLabel = new QLabel(gridLayoutWidget_2); - webIRCPassLabel->setObjectName(QStringLiteral("webIRCPassLabel")); - horizontalLayout_2->addWidget(webIRCPassLabel); - ui.webIRCPassLineEdit = new QLineEdit(gridLayoutWidget_2); - webIRCPassLineEdit->setObjectName(QStringLiteral("webIRCPassLineEdit")); - webIRCPassLineEdit->setText(webIRCPass.c_str()); - QObject::connect(webIRCPassLineEdit, SIGNAL(textChanged(const QString &)), - this, SLOT(updated())); - horizontalLayout_2->addWidget(webIRCPassLineEdit); - QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); - horizontalLayout_2->addItem(horizontalSpacer); - tunnelGridLayout->addLayout(horizontalLayout_2); - } - { - bool gzip = tunnelConfig->getgzip(); - QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); - horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); - ui.gzipCheckBox = new QCheckBox(gridLayoutWidget_2); - gzipCheckBox->setObjectName(QStringLiteral("gzipCheckBox")); - gzipCheckBox->setChecked(gzip); - QObject::connect(gzipCheckBox, SIGNAL(stateChanged(int)), - this, SLOT(updated())); - horizontalLayout_2->addWidget(gzipCheckBox); - QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); - horizontalLayout_2->addItem(horizontalSpacer); - tunnelGridLayout->addLayout(horizontalLayout_2); - } - { - i2p::data::SigningKeyType sigType = tunnelConfig->getsigType(); - QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); - horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); - ui.sigTypeLabel = new QLabel(gridLayoutWidget_2); - sigTypeLabel->setObjectName(QStringLiteral("sigTypeLabel")); - horizontalLayout_2->addWidget(sigTypeLabel); - ui.sigTypeComboBox = SignatureTypeComboBoxFactory::createSignatureTypeComboBox(gridLayoutWidget_2, sigType); - sigTypeComboBox->setObjectName(QStringLiteral("sigTypeComboBox")); - QObject::connect(sigTypeComboBox, SIGNAL(currentIndexChanged(int)), - this, SLOT(updated())); - horizontalLayout_2->addWidget(sigTypeComboBox); - QPushButton * lockButton2 = new QPushButton(gridLayoutWidget_2); - horizontalLayout_2->addWidget(lockButton2); - widgetlocks.add(new widgetlock(sigTypeComboBox, lockButton2)); - QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); - horizontalLayout_2->addItem(horizontalSpacer); - tunnelGridLayout->addLayout(horizontalLayout_2); - } - { - std::string address = tunnelConfig->getaddress(); - QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); - horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); - ui.addressLabel = new QLabel(gridLayoutWidget_2); - addressLabel->setObjectName(QStringLiteral("addressLabel")); - horizontalLayout_2->addWidget(addressLabel); - ui.addressLineEdit = new QLineEdit(gridLayoutWidget_2); - addressLineEdit->setObjectName(QStringLiteral("addressLineEdit")); - addressLineEdit->setText(address.c_str()); - QObject::connect(addressLineEdit, SIGNAL(textChanged(const QString &)), - this, SLOT(updated())); - horizontalLayout_2->addWidget(addressLineEdit); - QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); - horizontalLayout_2->addItem(horizontalSpacer); - tunnelGridLayout->addLayout(horizontalLayout_2); - } - { - bool isUniqueLocal = tunnelConfig->getisUniqueLocal(); - QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); - horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); - ui.isUniqueLocalCheckBox = new QCheckBox(gridLayoutWidget_2); - isUniqueLocalCheckBox->setObjectName(QStringLiteral("isUniqueLocalCheckBox")); - isUniqueLocalCheckBox->setChecked(isUniqueLocal); - QObject::connect(gzipCheckBox, SIGNAL(stateChanged(int)), - this, SLOT(updated())); - horizontalLayout_2->addWidget(isUniqueLocalCheckBox); - QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); - horizontalLayout_2->addItem(horizontalSpacer); - tunnelGridLayout->addLayout(horizontalLayout_2); - } - { - I2CPParameters& i2cpParameters = tunnelConfig->getI2cpParameters(); - appendControlsForI2CPParameters(i2cpParameters, gridIndex); - } - - retranslateServerTunnelForm(ui); - - tunnelGridLayout->invalidate(); - - return h; -} - -void ServerTunnelPane::deleteServerTunnelForm() { - TunnelPane::deleteTunnelForm(); - delete serverTunnelNameGroupBox;//->deleteLater(); - serverTunnelNameGroupBox=nullptr; - - //gridLayoutWidget_2->deleteLater(); - //gridLayoutWidget_2=nullptr; -} - - -ServerTunnelPane* ServerTunnelPane::asServerTunnelPane(){return this;} -ClientTunnelPane* ServerTunnelPane::asClientTunnelPane(){return nullptr;} diff --git a/qt/i2pd_qt/ServerTunnelPane.h b/qt/i2pd_qt/ServerTunnelPane.h deleted file mode 100644 index 0a07267b..00000000 --- a/qt/i2pd_qt/ServerTunnelPane.h +++ /dev/null @@ -1,159 +0,0 @@ -#ifndef SERVERTUNNELPANE_H -#define SERVERTUNNELPANE_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "QVBoxLayout" -#include "QCheckBox" - -#include "assert.h" - -#include "TunnelPane.h" -#include "TunnelsPageUpdateListener.h" - -class ServerTunnelConfig; - -class ClientTunnelPane; - -class ServerTunnelPane : public TunnelPane { - Q_OBJECT - -public: - ServerTunnelPane(TunnelsPageUpdateListener* tunnelsPageUpdateListener, ServerTunnelConfig* tunconf, QWidget* wrongInputPane_, QLabel* wrongInputLabel_, MainWindow* mainWindow); - virtual ~ServerTunnelPane(){} - - virtual ServerTunnelPane* asServerTunnelPane(); - virtual ClientTunnelPane* asClientTunnelPane(); - - int appendServerTunnelForm(ServerTunnelConfig* tunnelConfig, QWidget *tunnelsFormGridLayoutWidget, - int tunnelsRow, int height); - void deleteServerTunnelForm(); - -private: - QGroupBox *serverTunnelNameGroupBox; - - //tunnel - QWidget *gridLayoutWidget_2; - - //host - QHBoxLayout *horizontalLayout_2; - QLabel *hostLabel; - QLineEdit *hostLineEdit; - QSpacerItem *hostHorizontalSpacer; - - //port - QLabel * portLabel; - QLineEdit * portLineEdit; - - //keys - QLabel * keysLabel; - QLineEdit * keysLineEdit; - - //inPort - QLabel * inPortLabel; - QLineEdit * inPortLineEdit; - - //accessList - QLabel * accessListLabel; - QLineEdit * accessListLineEdit; - - //hostOverride - QLabel * hostOverrideLabel; - QLineEdit * hostOverrideLineEdit; - - //webIRCPass - QLabel * webIRCPassLabel; - QLineEdit * webIRCPassLineEdit; - - //address - QLabel * addressLabel; - QLineEdit * addressLineEdit; - - //gzip - QCheckBox * gzipCheckBox; - - //isUniqueLocal - QCheckBox * isUniqueLocalCheckBox; - - //sigType - QLabel * sigTypeLabel; - QComboBox * sigTypeComboBox; - -protected slots: - virtual void setGroupBoxTitle(const QString & title); - -private: - void retranslateServerTunnelForm(ServerTunnelPane& /*ui*/) { - typeLabel->setText(QApplication::translate("srvTunForm", "Server tunnel type:", 0)); - hostLabel->setText(QApplication::translate("srvTunForm", "Host:", 0)); - portLabel->setText(QApplication::translate("srvTunForm", "Port:", 0)); - keysLabel->setText(QApplication::translate("srvTunForm", "Keys:", 0)); - inPortLabel->setText(QApplication::translate("srvTunForm", "InPort:", 0)); - accessListLabel->setText(QApplication::translate("srvTunForm", "Access list:", 0)); - hostOverrideLabel->setText(QApplication::translate("srvTunForm", "Host override:", 0)); - webIRCPassLabel->setText(QApplication::translate("srvTunForm", "WebIRC password:", 0)); - addressLabel->setText(QApplication::translate("srvTunForm", "Address:", 0)); - - gzipCheckBox->setText(QApplication::translate("srvTunForm", "GZip", 0)); - isUniqueLocalCheckBox->setText(QApplication::translate("srvTunForm", "Is unique local", 0)); - - sigTypeLabel->setText(QApplication::translate("cltTunForm", "Signature type:", 0)); - } - -protected: - virtual bool applyDataFromUIToTunnelConfig() { - QString cannotSaveSettings = QApplication::tr("Cannot save settings."); - bool ok=TunnelPane::applyDataFromUIToTunnelConfig(); - if(!ok)return false; - ServerTunnelConfig* stc=tunnelConfig->asServerTunnelConfig(); - assert(stc!=nullptr); - stc->sethost(hostLineEdit->text().toStdString()); - - auto portStr=portLineEdit->text(); - int portInt=portStr.toInt(&ok); - if(!ok){ - highlightWrongInput(QApplication::tr("Bad port, must be int.")+" "+cannotSaveSettings,portLineEdit); - return false; - } - stc->setport(portInt); - - stc->setkeys(keysLineEdit->text().toStdString()); - - auto str=inPortLineEdit->text(); - int inPortInt=str.toInt(&ok); - if(!ok){ - highlightWrongInput(QApplication::tr("Bad inPort, must be int.")+" "+cannotSaveSettings,inPortLineEdit); - return false; - } - stc->setinPort(inPortInt); - - stc->setaccessList(accessListLineEdit->text().toStdString()); - - stc->sethostOverride(hostOverrideLineEdit->text().toStdString()); - - stc->setwebircpass(webIRCPassLineEdit->text().toStdString()); - - stc->setaddress(addressLineEdit->text().toStdString()); - - stc->setgzip(gzipCheckBox->isChecked()); - - stc->setisUniqueLocal(isUniqueLocalCheckBox->isChecked()); - - stc->setsigType(readSigTypeComboboxUI(sigTypeComboBox)); - return true; - } -}; - -#endif // SERVERTUNNELPANE_H diff --git a/qt/i2pd_qt/SignatureTypeComboboxFactory.cpp b/qt/i2pd_qt/SignatureTypeComboboxFactory.cpp deleted file mode 100644 index 9313741a..00000000 --- a/qt/i2pd_qt/SignatureTypeComboboxFactory.cpp +++ /dev/null @@ -1,2 +0,0 @@ -#include "SignatureTypeComboboxFactory.h" - diff --git a/qt/i2pd_qt/SignatureTypeComboboxFactory.h b/qt/i2pd_qt/SignatureTypeComboboxFactory.h deleted file mode 100644 index f7cac658..00000000 --- a/qt/i2pd_qt/SignatureTypeComboboxFactory.h +++ /dev/null @@ -1,86 +0,0 @@ -#ifndef SIGNATURETYPECOMBOBOXFACTORY_H -#define SIGNATURETYPECOMBOBOXFACTORY_H - -#include -#include -#include -#include "Identity.h" - -class SignatureTypeComboBoxFactory -{ - static const QVariant createUserData(const uint16_t sigType) { - return QVariant::fromValue((uint)sigType); - } - - static void addItem(QComboBox* signatureTypeCombobox, QString text, const uint16_t sigType) { - const QVariant userData = createUserData(sigType); - signatureTypeCombobox->addItem(text, userData); - } - -public: - static uint16_t getSigType(const QVariant& var) { - return (uint16_t)var.toInt(); - } - - static void fillComboBox(QComboBox* signatureTypeCombobox, uint16_t selectedSigType) { - /* - https://geti2p.net/spec/common-structures#certificate - все коды перечислены - это таблица "The defined Signing Public Key types are:" ? - да - - see also: Identity.h line 55 - */ - int index=0; - bool foundSelected=false; - - using namespace i2p::data; - - addItem(signatureTypeCombobox, QApplication::translate("signatureTypeCombobox", "DSA_SHA1", 0), SIGNING_KEY_TYPE_DSA_SHA1); //0 - if(selectedSigType==SIGNING_KEY_TYPE_DSA_SHA1){signatureTypeCombobox->setCurrentIndex(index);foundSelected=true;} - ++index; - addItem(signatureTypeCombobox, QApplication::translate("signatureTypeCombobox", "ECDSA_SHA256_P256", 0), SIGNING_KEY_TYPE_ECDSA_SHA256_P256); //1 - if(selectedSigType==SIGNING_KEY_TYPE_ECDSA_SHA256_P256){signatureTypeCombobox->setCurrentIndex(index);foundSelected=true;} - ++index; - addItem(signatureTypeCombobox, QApplication::translate("signatureTypeCombobox", "ECDSA_SHA384_P384", 0), SIGNING_KEY_TYPE_ECDSA_SHA384_P384); //2 - if(selectedSigType==SIGNING_KEY_TYPE_ECDSA_SHA384_P384){signatureTypeCombobox->setCurrentIndex(index);foundSelected=true;} - ++index; - addItem(signatureTypeCombobox, QApplication::translate("signatureTypeCombobox", "ECDSA_SHA512_P521", 0), SIGNING_KEY_TYPE_ECDSA_SHA512_P521); //3 - if(selectedSigType==SIGNING_KEY_TYPE_ECDSA_SHA512_P521){signatureTypeCombobox->setCurrentIndex(index);foundSelected=true;} - ++index; - addItem(signatureTypeCombobox, QApplication::translate("signatureTypeCombobox", "RSA_SHA256_2048", 0), SIGNING_KEY_TYPE_RSA_SHA256_2048); //4 - if(selectedSigType==SIGNING_KEY_TYPE_RSA_SHA256_2048){signatureTypeCombobox->setCurrentIndex(index);foundSelected=true;} - ++index; - addItem(signatureTypeCombobox, QApplication::translate("signatureTypeCombobox", "RSA_SHA384_3072", 0), SIGNING_KEY_TYPE_RSA_SHA384_3072); //5 - if(selectedSigType==SIGNING_KEY_TYPE_RSA_SHA384_3072){signatureTypeCombobox->setCurrentIndex(index);foundSelected=true;} - ++index; - addItem(signatureTypeCombobox, QApplication::translate("signatureTypeCombobox", "RSA_SHA512_4096", 0), SIGNING_KEY_TYPE_RSA_SHA512_4096); //6 - if(selectedSigType==SIGNING_KEY_TYPE_RSA_SHA512_4096){signatureTypeCombobox->setCurrentIndex(index);foundSelected=true;} - ++index; - addItem(signatureTypeCombobox, QApplication::translate("signatureTypeCombobox", "EDDSA_SHA512_ED25519", 0), SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519); //7 - if(selectedSigType==SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519){signatureTypeCombobox->setCurrentIndex(index);foundSelected=true;} - ++index; - addItem(signatureTypeCombobox, QApplication::translate("signatureTypeCombobox", "EDDSA_SHA512_ED25519PH", 0), SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519ph); //8 - if(selectedSigType==SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519ph){signatureTypeCombobox->setCurrentIndex(index);foundSelected=true;} - ++index; - // the following signature type should never appear in netid=2 - addItem(signatureTypeCombobox, QApplication::translate("signatureTypeCombobox", "GOSTR3410_CRYPTO_PRO_A_GOSTR3411_256", 0), SIGNING_KEY_TYPE_GOSTR3410_CRYPTO_PRO_A_GOSTR3411_256); //9 - if(selectedSigType==SIGNING_KEY_TYPE_GOSTR3410_CRYPTO_PRO_A_GOSTR3411_256){signatureTypeCombobox->setCurrentIndex(index);foundSelected=true;} - ++index; - addItem(signatureTypeCombobox, QApplication::translate("signatureTypeCombobox", "GOSTR3410_TC26_A_512_GOSTR3411_512", 0), SIGNING_KEY_TYPE_GOSTR3410_TC26_A_512_GOSTR3411_512); //10 - if(selectedSigType==SIGNING_KEY_TYPE_GOSTR3410_TC26_A_512_GOSTR3411_512){signatureTypeCombobox->setCurrentIndex(index);foundSelected=true;} - ++index; - if(!foundSelected){ - addItem(signatureTypeCombobox, QString::number(selectedSigType), selectedSigType); //unknown sigtype - signatureTypeCombobox->setCurrentIndex(index); - } - } - - static QComboBox* createSignatureTypeComboBox(QWidget* parent, uint16_t selectedSigType) { - QComboBox* signatureTypeCombobox = new QComboBox(parent); - fillComboBox(signatureTypeCombobox, selectedSigType); - return signatureTypeCombobox; - } -}; - -#endif // SIGNATURETYPECOMBOBOXFACTORY_H diff --git a/qt/i2pd_qt/TunnelConfig.cpp b/qt/i2pd_qt/TunnelConfig.cpp deleted file mode 100644 index 8ed72930..00000000 --- a/qt/i2pd_qt/TunnelConfig.cpp +++ /dev/null @@ -1,53 +0,0 @@ -#include "TunnelConfig.h" - -void TunnelConfig::saveHeaderToStringStream(std::stringstream& out) { - out << "[" << name << "]\n" - << "type=" << type.toStdString() << "\n"; -} - -void TunnelConfig::saveI2CPParametersToStringStream(std::stringstream& out) { - if (i2cpParameters.getInbound_length().toUShort() != i2p::client::DEFAULT_INBOUND_TUNNEL_LENGTH) - out << i2p::client::I2CP_PARAM_INBOUND_TUNNEL_LENGTH << "=" - << i2cpParameters.getInbound_length().toStdString() << "\n"; - if (i2cpParameters.getOutbound_length().toUShort() != i2p::client::DEFAULT_OUTBOUND_TUNNEL_LENGTH) - out << i2p::client::I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH << "=" - << i2cpParameters.getOutbound_length().toStdString() << "\n"; - if (i2cpParameters.getInbound_quantity().toUShort() != i2p::client::DEFAULT_INBOUND_TUNNELS_QUANTITY) - out << i2p::client::I2CP_PARAM_INBOUND_TUNNELS_QUANTITY << "=" - << i2cpParameters.getInbound_quantity().toStdString() << "\n"; - if (i2cpParameters.getOutbound_quantity().toUShort() != i2p::client::DEFAULT_OUTBOUND_TUNNELS_QUANTITY) - out << i2p::client::I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY << "=" - << i2cpParameters.getOutbound_quantity().toStdString() << "\n"; - if (i2cpParameters.getCrypto_tagsToSend().toUShort() != i2p::client::DEFAULT_TAGS_TO_SEND) - out << i2p::client::I2CP_PARAM_TAGS_TO_SEND << "=" - << i2cpParameters.getCrypto_tagsToSend().toStdString() << "\n"; - if (!i2cpParameters.getExplicitPeers().isEmpty()) //todo #947 - out << i2p::client::I2CP_PARAM_EXPLICIT_PEERS << "=" - << i2cpParameters.getExplicitPeers().toStdString() << "\n"; - out << "\n"; -} - -void ClientTunnelConfig::saveToStringStream(std::stringstream& out) { - out << "address=" << address << "\n" - << "port=" << port << "\n" - << "destination=" << dest << "\n" - << "destinationport=" << destinationPort << "\n" - << "signaturetype=" << sigType << "\n"; - if(!keys.empty()) out << "keys=" << keys << "\n"; -} - - -void ServerTunnelConfig::saveToStringStream(std::stringstream& out) { - out << "host=" << host << "\n" - << "port=" << port << "\n" - << "signaturetype=" << sigType << "\n" - << "inport=" << inPort << "\n" - << "accesslist=" << accessList << "\n" - << "gzip=" << (gzip?"true":"false") << "\n" - << "enableuniquelocal=" << (isUniqueLocal?"true":"false") << "\n" - << "address=" << address << "\n" - << "hostoverride=" << hostOverride << "\n" - << "webircpassword=" << webircpass << "\n"; - if(!keys.empty()) out << "keys=" << keys << "\n"; -} - diff --git a/qt/i2pd_qt/TunnelConfig.h b/qt/i2pd_qt/TunnelConfig.h deleted file mode 100644 index 059d48b5..00000000 --- a/qt/i2pd_qt/TunnelConfig.h +++ /dev/null @@ -1,231 +0,0 @@ -#ifndef TUNNELCONFIG_H -#define TUNNELCONFIG_H - -#include "QString" -#include - -#include "../../libi2pd_client/ClientContext.h" -#include "../../libi2pd/Destination.h" -#include "TunnelsPageUpdateListener.h" - - -class I2CPParameters{ - QString inbound_length;//number of hops of an inbound tunnel. 3 by default; lower value is faster but dangerous - QString outbound_length;//number of hops of an outbound tunnel. 3 by default; lower value is faster but dangerous - QString inbound_quantity; //number of inbound tunnels. 5 by default - QString outbound_quantity; //number of outbound tunnels. 5 by default - QString crypto_tagsToSend; //number of ElGamal/AES tags to send. 40 by default; too low value may cause problems with tunnel building - QString explicitPeers; //list of comma-separated b64 addresses of peers to use, default: unset -public: - I2CPParameters(): inbound_length(), - outbound_length(), - inbound_quantity(), - outbound_quantity(), - crypto_tagsToSend(), - explicitPeers() {} - const QString& getInbound_length(){return inbound_length;} - const QString& getOutbound_length(){return outbound_length;} - const QString& getInbound_quantity(){return inbound_quantity;} - const QString& getOutbound_quantity(){return outbound_quantity;} - const QString& getCrypto_tagsToSend(){return crypto_tagsToSend;} - const QString& getExplicitPeers(){return explicitPeers;} - void setInbound_length(QString inbound_length_){inbound_length=inbound_length_;} - void setOutbound_length(QString outbound_length_){outbound_length=outbound_length_;} - void setInbound_quantity(QString inbound_quantity_){inbound_quantity=inbound_quantity_;} - void setOutbound_quantity(QString outbound_quantity_){outbound_quantity=outbound_quantity_;} - void setCrypto_tagsToSend(QString crypto_tagsToSend_){crypto_tagsToSend=crypto_tagsToSend_;} - void setExplicitPeers(QString explicitPeers_){explicitPeers=explicitPeers_;} -}; - - -class ClientTunnelConfig; -class ServerTunnelConfig; - -class TunnelPane; - -class TunnelConfig { - /* - const char I2P_TUNNELS_SECTION_TYPE_CLIENT[] = "client"; - const char I2P_TUNNELS_SECTION_TYPE_SERVER[] = "server"; - const char I2P_TUNNELS_SECTION_TYPE_HTTP[] = "http"; - const char I2P_TUNNELS_SECTION_TYPE_IRC[] = "irc"; - const char I2P_TUNNELS_SECTION_TYPE_UDPCLIENT[] = "udpclient"; - const char I2P_TUNNELS_SECTION_TYPE_UDPSERVER[] = "udpserver"; - const char I2P_TUNNELS_SECTION_TYPE_SOCKS[] = "socks"; - const char I2P_TUNNELS_SECTION_TYPE_WEBSOCKS[] = "websocks"; - const char I2P_TUNNELS_SECTION_TYPE_HTTPPROXY[] = "httpproxy"; - */ - QString type; - std::string name; - TunnelPane* tunnelPane; -public: - TunnelConfig(std::string name_, QString& type_, I2CPParameters& i2cpParameters_): - type(type_), name(name_), i2cpParameters(i2cpParameters_) {} - virtual ~TunnelConfig(){} - const QString& getType(){return type;} - const std::string& getName(){return name;} - void setType(const QString& type_){type=type_;} - void setName(const std::string& name_){name=name_;} - I2CPParameters& getI2cpParameters(){return i2cpParameters;} - void saveHeaderToStringStream(std::stringstream& out); - void saveI2CPParametersToStringStream(std::stringstream& out); - virtual void saveToStringStream(std::stringstream& out)=0; - virtual ClientTunnelConfig* asClientTunnelConfig()=0; - virtual ServerTunnelConfig* asServerTunnelConfig()=0; - void setTunnelPane(TunnelPane* tp){this->tunnelPane = tp;} - TunnelPane* getTunnelPane() {return tunnelPane;} -private: - I2CPParameters i2cpParameters; -}; - -/* -# mandatory parameters: - std::string dest; - if (type == I2P_TUNNELS_SECTION_TYPE_CLIENT || type == I2P_TUNNELS_SECTION_TYPE_UDPCLIENT) - dest = section.second.get (I2P_CLIENT_TUNNEL_DESTINATION); - int port = section.second.get (I2P_CLIENT_TUNNEL_PORT); -# optional parameters (may be omitted) - std::string keys = section.second.get (I2P_CLIENT_TUNNEL_KEYS, ""); - std::string address = section.second.get (I2P_CLIENT_TUNNEL_ADDRESS, "127.0.0.1"); - int destinationPort = section.second.get (I2P_CLIENT_TUNNEL_DESTINATION_PORT, 0); - i2p::data::SigningKeyType sigType = section.second.get (I2P_CLIENT_TUNNEL_SIGNATURE_TYPE, i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256); -# * keys -- our identity, if unset, will be generated on every startup, -# if set and file missing, keys will be generated and placed to this file -# * address -- local interface to bind -# * signaturetype -- signature type for new destination. 0 (DSA/SHA1), 1 (EcDSA/SHA256) or 7 (EdDSA/SHA512) -[somelabel] -type = client -address = 127.0.0.1 -port = 6668 -destination = irc.postman.i2p -keys = irc-keys.dat -*/ -class ClientTunnelConfig : public TunnelConfig { - std::string dest; - int port; - std::string keys; - std::string address; - int destinationPort; - i2p::data::SigningKeyType sigType; -public: - ClientTunnelConfig(std::string name_, QString type_, I2CPParameters& i2cpParameters_, - std::string dest_, - int port_, - std::string keys_, - std::string address_, - int destinationPort_, - i2p::data::SigningKeyType sigType_): TunnelConfig(name_, type_, i2cpParameters_), - dest(dest_), - port(port_), - keys(keys_), - address(address_), - destinationPort(destinationPort_), - sigType(sigType_){} - std::string& getdest(){return dest;} - int getport(){return port;} - std::string & getkeys(){return keys;} - std::string & getaddress(){return address;} - int getdestinationPort(){return destinationPort;} - i2p::data::SigningKeyType getsigType(){return sigType;} - void setdest(const std::string& dest_){dest=dest_;} - void setport(int port_){port=port_;} - void setkeys(const std::string & keys_){keys=keys_;} - void setaddress(const std::string & address_){address=address_;} - void setdestinationPort(int destinationPort_){destinationPort=destinationPort_;} - void setsigType(i2p::data::SigningKeyType sigType_){sigType=sigType_;} - virtual void saveToStringStream(std::stringstream& out); - virtual ClientTunnelConfig* asClientTunnelConfig(){return this;} - virtual ServerTunnelConfig* asServerTunnelConfig(){return nullptr;} -}; - -/* -# mandatory parameters: -# * host -- ip address of our service -# * port -- port of our service -# * keys -- file with LeaseSet of address in i2p - std::string host = section.second.get (I2P_SERVER_TUNNEL_HOST); - int port = section.second.get (I2P_SERVER_TUNNEL_PORT); - std::string keys = section.second.get (I2P_SERVER_TUNNEL_KEYS); -# optional parameters (may be omitted) - int inPort = section.second.get (I2P_SERVER_TUNNEL_INPORT, 0); - std::string accessList = section.second.get (I2P_SERVER_TUNNEL_ACCESS_LIST, ""); - std::string hostOverride = section.second.get (I2P_SERVER_TUNNEL_HOST_OVERRIDE, ""); - std::string webircpass = section.second.get (I2P_SERVER_TUNNEL_WEBIRC_PASSWORD, ""); - bool gzip = section.second.get (I2P_SERVER_TUNNEL_GZIP, true); - i2p::data::SigningKeyType sigType = section.second.get (I2P_SERVER_TUNNEL_SIGNATURE_TYPE, i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256); - std::string address = section.second.get (I2P_SERVER_TUNNEL_ADDRESS, "127.0.0.1"); - bool isUniqueLocal = section.second.get(I2P_SERVER_TUNNEL_ENABLE_UNIQUE_LOCAL, true); -# * inport -- optional, i2p service port, if unset - the same as 'port' -# * accesslist -- comma-separated list of i2p addresses, allowed to connect -# every address is b32 without '.b32.i2p' part -[somelabel] -type = server -host = 127.0.0.1 -port = 6667 -keys = irc.dat -*/ -class ServerTunnelConfig : public TunnelConfig { - std::string host; - int port; - std::string keys; - int inPort; - std::string accessList; - std::string hostOverride; - std::string webircpass; - bool gzip; - i2p::data::SigningKeyType sigType; - std::string address; - bool isUniqueLocal; -public: - ServerTunnelConfig(std::string name_, QString type_, I2CPParameters& i2cpParameters_, - std::string host_, - int port_, - std::string keys_, - int inPort_, - std::string accessList_, - std::string hostOverride_, - std::string webircpass_, - bool gzip_, - i2p::data::SigningKeyType sigType_, - std::string address_, - bool isUniqueLocal_): TunnelConfig(name_, type_, i2cpParameters_), - host(host_), - port(port_), - keys(keys_), - inPort(inPort_), - accessList(accessList_), - hostOverride(hostOverride_), - webircpass(webircpass_), - gzip(gzip_), - sigType(sigType_), - address(address_), - isUniqueLocal(isUniqueLocal_) {} - std::string& gethost(){return host;} - int getport(){return port;} - std::string& getkeys(){return keys;} - int getinPort(){return inPort;} - std::string& getaccessList(){return accessList;} - std::string& gethostOverride(){return hostOverride;} - std::string& getwebircpass(){return webircpass;} - bool getgzip(){return gzip;} - i2p::data::SigningKeyType getsigType(){return sigType;} - std::string& getaddress(){return address;} - bool getisUniqueLocal(){return isUniqueLocal;} - void sethost(const std::string& host_){host=host_;} - void setport(int port_){port=port_;} - void setkeys(const std::string& keys_){keys=keys_;} - void setinPort(int inPort_){inPort=inPort_;} - void setaccessList(const std::string& accessList_){accessList=accessList_;} - void sethostOverride(const std::string& hostOverride_){hostOverride=hostOverride_;} - void setwebircpass(const std::string& webircpass_){webircpass=webircpass_;} - void setgzip(bool gzip_){gzip=gzip_;} - void setsigType(i2p::data::SigningKeyType sigType_){sigType=sigType_;} - void setaddress(const std::string& address_){address=address_;} - void setisUniqueLocal(bool isUniqueLocal_){isUniqueLocal=isUniqueLocal_;} - virtual void saveToStringStream(std::stringstream& out); - virtual ClientTunnelConfig* asClientTunnelConfig(){return nullptr;} - virtual ServerTunnelConfig* asServerTunnelConfig(){return this;} -}; - - -#endif // TUNNELCONFIG_H diff --git a/qt/i2pd_qt/TunnelPane.cpp b/qt/i2pd_qt/TunnelPane.cpp deleted file mode 100644 index c64b37ab..00000000 --- a/qt/i2pd_qt/TunnelPane.cpp +++ /dev/null @@ -1,254 +0,0 @@ -#include "TunnelPane.h" - -#include "QMessageBox" -#include "mainwindow.h" -#include "ui_mainwindow.h" - -TunnelPane::TunnelPane(TunnelsPageUpdateListener* tunnelsPageUpdateListener_, TunnelConfig* tunnelConfig_, QWidget* wrongInputPane_, QLabel* wrongInputLabel_, MainWindow* mainWindow_): - QObject(), - mainWindow(mainWindow_), - wrongInputPane(wrongInputPane_), - wrongInputLabel(wrongInputLabel_), - tunnelConfig(tunnelConfig_), - tunnelsPageUpdateListener(tunnelsPageUpdateListener_), - gridLayoutWidget_2(nullptr) {} - -void TunnelPane::setupTunnelPane( - TunnelConfig* tunnelConfig, - QGroupBox *tunnelGroupBox, - QWidget* gridLayoutWidget_2, QComboBox * tunnelTypeComboBox, - QWidget *tunnelsFormGridLayoutWidget, int tunnelsRow, int height, int h) { - tunnelGroupBox->setGeometry(0, tunnelsFormGridLayoutWidget->height(), gridLayoutWidget_2->width(), h); - tunnelsFormGridLayoutWidget->resize(527, tunnelsFormGridLayoutWidget->height()+h); - - QObject::connect(tunnelTypeComboBox, SIGNAL(currentIndexChanged(int)), - this, SLOT(updated())); - - - this->tunnelGroupBox=tunnelGroupBox; - - gridLayoutWidget_2->setObjectName(QStringLiteral("gridLayoutWidget_2")); - this->gridLayoutWidget_2=gridLayoutWidget_2; - tunnelGridLayout = new QVBoxLayout(gridLayoutWidget_2); - tunnelGridLayout->setObjectName(QStringLiteral("tunnelGridLayout")); - tunnelGridLayout->setContentsMargins(5, 5, 5, 5); - tunnelGridLayout->setSpacing(5); - - //header - QHBoxLayout *headerHorizontalLayout = new QHBoxLayout(); - headerHorizontalLayout->setObjectName(QStringLiteral("headerHorizontalLayout")); - - nameLabel = new QLabel(gridLayoutWidget_2); - nameLabel->setObjectName(QStringLiteral("nameLabel")); - headerHorizontalLayout->addWidget(nameLabel); - nameLineEdit = new QLineEdit(gridLayoutWidget_2); - nameLineEdit->setObjectName(QStringLiteral("nameLineEdit")); - const QString& tunnelName=tunnelConfig->getName().c_str(); - nameLineEdit->setText(tunnelName); - setGroupBoxTitle(tunnelName); - - QObject::connect(nameLineEdit, SIGNAL(textChanged(const QString &)), - this, SLOT(setGroupBoxTitle(const QString &))); - QObject::connect(nameLineEdit, SIGNAL(textChanged(const QString &)), - this, SLOT(updated())); - - headerHorizontalLayout->addWidget(nameLineEdit); - headerHorizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); - headerHorizontalLayout->addItem(headerHorizontalSpacer); - deletePushButton = new QPushButton(gridLayoutWidget_2); - deletePushButton->setObjectName(QStringLiteral("deletePushButton")); - QObject::connect(deletePushButton, SIGNAL(released()), - this, SLOT(deleteButtonReleased()));//MainWindow::DeleteTunnelNamed(std::string name) { - headerHorizontalLayout->addWidget(deletePushButton); - tunnelGridLayout->addLayout(headerHorizontalLayout); - - //type - { - //const QString& type = tunnelConfig->getType(); - QHBoxLayout * horizontalLayout_ = new QHBoxLayout(); - horizontalLayout_->setObjectName(QStringLiteral("horizontalLayout_")); - typeLabel = new QLabel(gridLayoutWidget_2); - typeLabel->setObjectName(QStringLiteral("typeLabel")); - horizontalLayout_->addWidget(typeLabel); - horizontalLayout_->addWidget(tunnelTypeComboBox); - QPushButton * lockButton1 = new QPushButton(gridLayoutWidget_2); - horizontalLayout_->addWidget(lockButton1); - widgetlocks.add(new widgetlock(tunnelTypeComboBox, lockButton1)); - this->tunnelTypeComboBox=tunnelTypeComboBox; - QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); - horizontalLayout_->addItem(horizontalSpacer); - tunnelGridLayout->addLayout(horizontalLayout_); - } - - retranslateTunnelForm(*this); -} - -void TunnelPane::deleteWidget() { - //gridLayoutWidget_2->deleteLater(); - tunnelGroupBox->deleteLater(); -} - -void TunnelPane::appendControlsForI2CPParameters(I2CPParameters& i2cpParameters, int& gridIndex) { - { - //number of hops of an inbound tunnel - const QString& inbound_length=i2cpParameters.getInbound_length(); - QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); - horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); - inbound_lengthLabel = new QLabel(gridLayoutWidget_2); - inbound_lengthLabel->setObjectName(QStringLiteral("inbound_lengthLabel")); - horizontalLayout_2->addWidget(inbound_lengthLabel); - inbound_lengthLineEdit = new QLineEdit(gridLayoutWidget_2); - inbound_lengthLineEdit->setObjectName(QStringLiteral("inbound_lengthLineEdit")); - inbound_lengthLineEdit->setText(inbound_length); - inbound_lengthLineEdit->setMaximumWidth(80); - QObject::connect(inbound_lengthLineEdit, SIGNAL(textChanged(const QString &)), - this, SLOT(updated())); - horizontalLayout_2->addWidget(inbound_lengthLineEdit); - QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); - horizontalLayout_2->addItem(horizontalSpacer); - tunnelGridLayout->addLayout(horizontalLayout_2); - } - { - //number of hops of an outbound tunnel - const QString& outbound_length=i2cpParameters.getOutbound_length(); - QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); - horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); - outbound_lengthLabel = new QLabel(gridLayoutWidget_2); - outbound_lengthLabel->setObjectName(QStringLiteral("outbound_lengthLabel")); - horizontalLayout_2->addWidget(outbound_lengthLabel); - outbound_lengthLineEdit = new QLineEdit(gridLayoutWidget_2); - outbound_lengthLineEdit->setObjectName(QStringLiteral("outbound_lengthLineEdit")); - outbound_lengthLineEdit->setText(outbound_length); - outbound_lengthLineEdit->setMaximumWidth(80); - QObject::connect(outbound_lengthLineEdit, SIGNAL(textChanged(const QString &)), - this, SLOT(updated())); - horizontalLayout_2->addWidget(outbound_lengthLineEdit); - QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); - horizontalLayout_2->addItem(horizontalSpacer); - tunnelGridLayout->addLayout(horizontalLayout_2); - } - { - //number of inbound tunnels - const QString& inbound_quantity=i2cpParameters.getInbound_quantity(); - QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); - horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); - inbound_quantityLabel = new QLabel(gridLayoutWidget_2); - inbound_quantityLabel->setObjectName(QStringLiteral("inbound_quantityLabel")); - horizontalLayout_2->addWidget(inbound_quantityLabel); - inbound_quantityLineEdit = new QLineEdit(gridLayoutWidget_2); - inbound_quantityLineEdit->setObjectName(QStringLiteral("inbound_quantityLineEdit")); - inbound_quantityLineEdit->setText(inbound_quantity); - inbound_quantityLineEdit->setMaximumWidth(80); - QObject::connect(inbound_quantityLineEdit, SIGNAL(textChanged(const QString &)), - this, SLOT(updated())); - horizontalLayout_2->addWidget(inbound_quantityLineEdit); - QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); - horizontalLayout_2->addItem(horizontalSpacer); - tunnelGridLayout->addLayout(horizontalLayout_2); - } - { - //number of outbound tunnels - const QString& outbound_quantity=i2cpParameters.getOutbound_quantity(); - QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); - horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); - outbound_quantityLabel = new QLabel(gridLayoutWidget_2); - outbound_quantityLabel->setObjectName(QStringLiteral("outbound_quantityLabel")); - horizontalLayout_2->addWidget(outbound_quantityLabel); - outbound_quantityLineEdit = new QLineEdit(gridLayoutWidget_2); - outbound_quantityLineEdit->setObjectName(QStringLiteral("outbound_quantityLineEdit")); - outbound_quantityLineEdit->setText(outbound_quantity); - outbound_quantityLineEdit->setMaximumWidth(80); - QObject::connect(outbound_quantityLineEdit, SIGNAL(textChanged(const QString &)), - this, SLOT(updated())); - horizontalLayout_2->addWidget(outbound_quantityLineEdit); - QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); - horizontalLayout_2->addItem(horizontalSpacer); - tunnelGridLayout->addLayout(horizontalLayout_2); - } - { - //number of ElGamal/AES tags to send - const QString& crypto_tagsToSend=i2cpParameters.getCrypto_tagsToSend(); - QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); - horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); - crypto_tagsToSendLabel = new QLabel(gridLayoutWidget_2); - crypto_tagsToSendLabel->setObjectName(QStringLiteral("crypto_tagsToSendLabel")); - horizontalLayout_2->addWidget(crypto_tagsToSendLabel); - crypto_tagsToSendLineEdit = new QLineEdit(gridLayoutWidget_2); - crypto_tagsToSendLineEdit->setObjectName(QStringLiteral("crypto_tagsToSendLineEdit")); - crypto_tagsToSendLineEdit->setText(crypto_tagsToSend); - crypto_tagsToSendLineEdit->setMaximumWidth(80); - QObject::connect(crypto_tagsToSendLineEdit, SIGNAL(textChanged(const QString &)), - this, SLOT(updated())); - horizontalLayout_2->addWidget(crypto_tagsToSendLineEdit); - QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); - horizontalLayout_2->addItem(horizontalSpacer); - tunnelGridLayout->addLayout(horizontalLayout_2); - } - - retranslateI2CPParameters(); -} - -void TunnelPane::updated() { - std::string oldName=tunnelConfig->getName(); - //validate and show red if invalid - hideWrongInputLabel(); - if(!mainWindow->applyTunnelsUiToConfigs())return; - tunnelsPageUpdateListener->updated(oldName, tunnelConfig); -} - -void TunnelPane::deleteButtonReleased() { - QMessageBox msgBox; - msgBox.setText(QApplication::tr("Are you sure to delete this tunnel?")); - msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); - msgBox.setDefaultButton(QMessageBox::Cancel); - int ret = msgBox.exec(); - switch (ret) { - case QMessageBox::Ok: - // OK was clicked - hideWrongInputLabel(); - tunnelsPageUpdateListener->needsDeleting(tunnelConfig->getName()); - break; - case QMessageBox::Cancel: - // Cancel was clicked - return; - } -} - -/* -const char I2P_TUNNELS_SECTION_TYPE_CLIENT[] = "client"; -const char I2P_TUNNELS_SECTION_TYPE_SERVER[] = "server"; -const char I2P_TUNNELS_SECTION_TYPE_HTTP[] = "http"; -const char I2P_TUNNELS_SECTION_TYPE_IRC[] = "irc"; -const char I2P_TUNNELS_SECTION_TYPE_UDPCLIENT[] = "udpclient"; -const char I2P_TUNNELS_SECTION_TYPE_UDPSERVER[] = "udpserver"; -const char I2P_TUNNELS_SECTION_TYPE_SOCKS[] = "socks"; -const char I2P_TUNNELS_SECTION_TYPE_WEBSOCKS[] = "websocks"; -const char I2P_TUNNELS_SECTION_TYPE_HTTPPROXY[] = "httpproxy"; -*/ -QString TunnelPane::readTunnelTypeComboboxData() { - return tunnelTypeComboBox->currentData().toString(); -} - -i2p::data::SigningKeyType TunnelPane::readSigTypeComboboxUI(QComboBox* sigTypeComboBox) { - return (i2p::data::SigningKeyType) sigTypeComboBox->currentData().toInt(); -} - -void TunnelPane::deleteTunnelForm() { - widgetlocks.deleteListeners(); -} - -void TunnelPane::highlightWrongInput(QString warningText, QWidget* controlWithWrongInput) { - wrongInputPane->setVisible(true); - wrongInputLabel->setText(warningText); - mainWindow->adjustSizesAccordingToWrongLabel(); - if(controlWithWrongInput){ - mainWindow->ui->tunnelsScrollArea->ensureWidgetVisible(controlWithWrongInput); - controlWithWrongInput->setFocus(); - } - mainWindow->showTunnelsPage(); -} - -void TunnelPane::hideWrongInputLabel() const { - wrongInputPane->setVisible(false); - mainWindow->adjustSizesAccordingToWrongLabel(); -} diff --git a/qt/i2pd_qt/TunnelPane.h b/qt/i2pd_qt/TunnelPane.h deleted file mode 100644 index 71331b4e..00000000 --- a/qt/i2pd_qt/TunnelPane.h +++ /dev/null @@ -1,139 +0,0 @@ -#ifndef TUNNELPANE_H -#define TUNNELPANE_H - -#include "QObject" -#include "QWidget" -#include "QComboBox" -#include "QGridLayout" -#include "QLabel" -#include "QPushButton" -#include "QApplication" -#include "QLineEdit" -#include "QGroupBox" -#include "QVBoxLayout" - -#include "TunnelConfig.h" - -#include -#include - -class ServerTunnelPane; -class ClientTunnelPane; - -class TunnelConfig; -class I2CPParameters; - -class MainWindow; - -class TunnelPane : public QObject { - - Q_OBJECT - -public: - TunnelPane(TunnelsPageUpdateListener* tunnelsPageUpdateListener_, TunnelConfig* tunconf, QWidget* wrongInputPane_, QLabel* wrongInputLabel_, MainWindow* mainWindow_); - virtual ~TunnelPane(){} - - void deleteTunnelForm(); - - void hideWrongInputLabel() const; - void highlightWrongInput(QString warningText, QWidget* controlWithWrongInput); - - virtual ServerTunnelPane* asServerTunnelPane()=0; - virtual ClientTunnelPane* asClientTunnelPane()=0; - - void deleteWidget(); - -protected: - MainWindow* mainWindow; - QWidget * wrongInputPane; - QLabel* wrongInputLabel; - TunnelConfig* tunnelConfig; - widgetlockregistry widgetlocks; - TunnelsPageUpdateListener* tunnelsPageUpdateListener; - QVBoxLayout *tunnelGridLayout; - QGroupBox *tunnelGroupBox; - QWidget* gridLayoutWidget_2; - - //header - QLabel *nameLabel; - QLineEdit *nameLineEdit; -public: - QLineEdit * getNameLineEdit() { return nameLineEdit; } - -public slots: - void updated(); - void deleteButtonReleased(); - -protected: - QSpacerItem *headerHorizontalSpacer; - QPushButton *deletePushButton; - - //type - QComboBox *tunnelTypeComboBox; - QLabel *typeLabel; - - //i2cp - - QLabel * inbound_lengthLabel; - QLineEdit * inbound_lengthLineEdit; - - QLabel * outbound_lengthLabel; - QLineEdit * outbound_lengthLineEdit; - - QLabel * inbound_quantityLabel; - QLineEdit * inbound_quantityLineEdit; - - QLabel * outbound_quantityLabel; - QLineEdit * outbound_quantityLineEdit; - - QLabel * crypto_tagsToSendLabel; - QLineEdit * crypto_tagsToSendLineEdit; - - QString readTunnelTypeComboboxData(); - - //should be created by factory - i2p::data::SigningKeyType readSigTypeComboboxUI(QComboBox* sigTypeComboBox); - -public: - //returns false when invalid data at UI - virtual bool applyDataFromUIToTunnelConfig() { - tunnelConfig->setName(nameLineEdit->text().toStdString()); - tunnelConfig->setType(readTunnelTypeComboboxData()); - I2CPParameters& i2cpParams=tunnelConfig->getI2cpParameters(); - i2cpParams.setInbound_length(inbound_lengthLineEdit->text()); - i2cpParams.setInbound_quantity(inbound_quantityLineEdit->text()); - i2cpParams.setOutbound_length(outbound_lengthLineEdit->text()); - i2cpParams.setOutbound_quantity(outbound_quantityLineEdit->text()); - i2cpParams.setCrypto_tagsToSend(crypto_tagsToSendLineEdit->text()); - return true; - } -protected: - void setupTunnelPane( - TunnelConfig* tunnelConfig, - QGroupBox *tunnelGroupBox, - QWidget* gridLayoutWidget_2, QComboBox * tunnelTypeComboBox, - QWidget *tunnelsFormGridLayoutWidget, int tunnelsRow, int height, int h); - void appendControlsForI2CPParameters(I2CPParameters& i2cpParameters, int& gridIndex); -public: - int height() { - return gridLayoutWidget_2?gridLayoutWidget_2->height():0; - } - -protected slots: - virtual void setGroupBoxTitle(const QString & title)=0; -private: - void retranslateTunnelForm(TunnelPane& ui) { - ui.deletePushButton->setText(QApplication::translate("tunForm", "Delete Tunnel", 0)); - ui.nameLabel->setText(QApplication::translate("tunForm", "Tunnel name:", 0)); - } - - void retranslateI2CPParameters() { - inbound_lengthLabel->setText(QApplication::translate("tunForm", "Number of hops of an inbound tunnel:", 0));; - outbound_lengthLabel->setText(QApplication::translate("tunForm", "Number of hops of an outbound tunnel:", 0));; - inbound_quantityLabel->setText(QApplication::translate("tunForm", "Number of inbound tunnels:", 0));; - outbound_quantityLabel->setText(QApplication::translate("tunForm", "Number of outbound tunnels:", 0));; - crypto_tagsToSendLabel->setText(QApplication::translate("tunForm", "Number of ElGamal/AES tags to send:", 0));; - } -}; - -#endif // TUNNELPANE_H diff --git a/qt/i2pd_qt/TunnelsPageUpdateListener.h b/qt/i2pd_qt/TunnelsPageUpdateListener.h deleted file mode 100644 index 83b4e9fe..00000000 --- a/qt/i2pd_qt/TunnelsPageUpdateListener.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef TUNNELSPAGEUPDATELISTENER_H -#define TUNNELSPAGEUPDATELISTENER_H - -class TunnelConfig; - -class TunnelsPageUpdateListener { -public: - virtual void updated(std::string oldName, TunnelConfig* tunConf)=0; - virtual void needsDeleting(std::string oldName)=0; -}; - -#endif // TUNNELSPAGEUPDATELISTENER_H diff --git a/qt/i2pd_qt/data/icons/128x128/website.i2pd.i2pd.png b/qt/i2pd_qt/data/icons/128x128/website.i2pd.i2pd.png deleted file mode 100644 index ee5400c4..00000000 Binary files a/qt/i2pd_qt/data/icons/128x128/website.i2pd.i2pd.png and /dev/null differ diff --git a/qt/i2pd_qt/data/icons/16x16/website.i2pd.i2pd.png b/qt/i2pd_qt/data/icons/16x16/website.i2pd.i2pd.png deleted file mode 100644 index bc020282..00000000 Binary files a/qt/i2pd_qt/data/icons/16x16/website.i2pd.i2pd.png and /dev/null differ diff --git a/qt/i2pd_qt/data/icons/22x22/website.i2pd.i2pd.png b/qt/i2pd_qt/data/icons/22x22/website.i2pd.i2pd.png deleted file mode 100644 index 5aed761e..00000000 Binary files a/qt/i2pd_qt/data/icons/22x22/website.i2pd.i2pd.png and /dev/null differ diff --git a/qt/i2pd_qt/data/icons/24x24/website.i2pd.i2pd.png b/qt/i2pd_qt/data/icons/24x24/website.i2pd.i2pd.png deleted file mode 100644 index 996e1296..00000000 Binary files a/qt/i2pd_qt/data/icons/24x24/website.i2pd.i2pd.png and /dev/null differ diff --git a/qt/i2pd_qt/data/icons/256x256/website.i2pd.i2pd.png b/qt/i2pd_qt/data/icons/256x256/website.i2pd.i2pd.png deleted file mode 100644 index 3ed9b518..00000000 Binary files a/qt/i2pd_qt/data/icons/256x256/website.i2pd.i2pd.png and /dev/null differ diff --git a/qt/i2pd_qt/data/icons/32x32/website.i2pd.i2pd.png b/qt/i2pd_qt/data/icons/32x32/website.i2pd.i2pd.png deleted file mode 100644 index 922846c6..00000000 Binary files a/qt/i2pd_qt/data/icons/32x32/website.i2pd.i2pd.png and /dev/null differ diff --git a/qt/i2pd_qt/data/icons/48x48/website.i2pd.i2pd.png b/qt/i2pd_qt/data/icons/48x48/website.i2pd.i2pd.png deleted file mode 100644 index 4b7c81ea..00000000 Binary files a/qt/i2pd_qt/data/icons/48x48/website.i2pd.i2pd.png and /dev/null differ diff --git a/qt/i2pd_qt/data/icons/512x512/website.i2pd.i2pd.png b/qt/i2pd_qt/data/icons/512x512/website.i2pd.i2pd.png deleted file mode 100644 index 6336627c..00000000 Binary files a/qt/i2pd_qt/data/icons/512x512/website.i2pd.i2pd.png and /dev/null differ diff --git a/qt/i2pd_qt/data/icons/64x64/website.i2pd.i2pd.png b/qt/i2pd_qt/data/icons/64x64/website.i2pd.i2pd.png deleted file mode 100644 index a291cab5..00000000 Binary files a/qt/i2pd_qt/data/icons/64x64/website.i2pd.i2pd.png and /dev/null differ diff --git a/qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml b/qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml deleted file mode 100644 index d3fa2e9e..00000000 --- a/qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml +++ /dev/null @@ -1,55 +0,0 @@ - - - - website.i2pd.i2pd - website.i2pd.i2pd.desktop - CC0-1.0 - BSD-3-Clause - i2pd - Invisible Internet - -

i2pd (I2P Daemon) is a full-featured C++ implementation of I2P client.

-

I2P (Invisible Internet Protocol) is a universal anonymous network layer. - All communications over I2P are anonymous and end-to-end encrypted, participants - don't reveal their real IP addresses.

-

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

-

Features:

-
    -
  • Distributed anonymous networking framework
  • -
  • End-to-end encrypted communications
  • -
  • Small footprint, simple dependencies, fast performance
  • -
  • Rich set of APIs for developers of secure applications
  • -
-
- - - https://i2pd.website/images/i2pd_qt.png - - - https://i2pd.website/ - https://github.com/PurpleI2P/i2pd/issues - https://i2pd.readthedocs.io/en/latest/ - supervillain@riseup.net - PurpleI2P Team - - - - - - - - - - - - - - - - - - - - -
diff --git a/qt/i2pd_qt/data/website.i2pd.i2pd.desktop b/qt/i2pd_qt/data/website.i2pd.i2pd.desktop deleted file mode 100644 index 33cb3214..00000000 --- a/qt/i2pd_qt/data/website.i2pd.i2pd.desktop +++ /dev/null @@ -1,11 +0,0 @@ -[Desktop Entry] -Categories=Network;P2P;Qt; -Exec=i2pd_qt -GenericName=Invisible Internet -Comment=A universal anonymous network layer -Icon=website.i2pd.i2pd -Name=i2pd -Terminal=false -Type=Application -StartupNotify=false -Keywords=i2p;i2pd;vpn;p2p; diff --git a/qt/i2pd_qt/generalsettingswidget.ui b/qt/i2pd_qt/generalsettingswidget.ui deleted file mode 100644 index 9e59132f..00000000 --- a/qt/i2pd_qt/generalsettingswidget.ui +++ /dev/null @@ -1,2760 +0,0 @@ - - - GeneralSettingsContentsForm - - - - 0 - 0 - 679 - 3033 - - - - - 0 - 0 - - - - GeneralSettingsContentsForm - - - - - 0 - 0 - 679 - 3052 - - - - - QLayout::SetMinAndMaxSize - - - - - - 0 - 0 - - - - - 0 - 51 - - - - - 16777215 - 51 - - - - Configuration file: - - - - - 0 - 18 - 661 - 31 - - - - - QLayout::SetMaximumSize - - - - - - - - - 0 - 0 - - - - - 0 - 27 - - - - - 16777215 - 27 - - - - Browse… - - - - - - - - - - - - 0 - 0 - - - - - 0 - 51 - - - - - 16777215 - 51 - - - - Tunnels configuration file: - - - - - 0 - 18 - 661 - 31 - - - - - QLayout::SetMaximumSize - - - - - - - - - 0 - 0 - - - - - 0 - 27 - - - - - 16777215 - 27 - - - - Browse… - - - - - - - - - - - - 0 - 0 - - - - - 0 - 51 - - - - - 16777215 - 51 - - - - Pid file: - - - - - 0 - 18 - 661 - 31 - - - - - QLayout::SetMaximumSize - - - - - - - - - 0 - 0 - - - - - 0 - 27 - - - - - 16777215 - 27 - - - - Browse… - - - - - - - - - - - - 0 - 98 - - - - - 16777215 - 98 - - - - SAM interface - - - - - 0 - 20 - 97 - 22 - - - - Enabled - - - - - - 0 - 40 - 661 - 31 - - - - - - - IP address to listen on: - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - 0 - 70 - 661 - 31 - - - - - - - Port to listen on: - - - - - - - - 80 - 16777215 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - 0 - 60 - - - - - 16777215 - 60 - - - - - 13 - - - - Windows-specific options - - - - - - - - 0 - 44 - - - - - 16777215 - 44 - - - - Cryptography - - - - - 0 - 20 - 661 - 22 - - - - Use ElGamal precomputed tables - - - - - - - - - 0 - 107 - - - - - 16777215 - 107 - - - - Logging - - - Qt::AlignJustify|Qt::AlignTop - - - - - -1 - 19 - 661 - 91 - - - - - QLayout::SetMinimumSize - - - - - QLayout::SetMaximumSize - - - - - Destination: - - - - - - - - - - Edit - - - - - - - Log file: - - - - - - - - - - Browse… - - - - - - - - - QLayout::SetMinimumSize - - - - - - 0 - 0 - - - - Log level: - - - - - - - - Error - - - - - Warn - - - - - Info - - - - - Debug - - - - - - - - Edit - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - - - 0 - 68 - - - - - 16777215 - 68 - - - - UPnP - - - - - 0 - 20 - 97 - 22 - - - - Enable - - - - - - 0 - 40 - 661 - 31 - - - - - - - Name i2pd appears in UPnP forwardings list: - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - 0 - 98 - - - - - 16777215 - 98 - - - - I2CP interface - - - - - 0 - 20 - 97 - 22 - - - - Enabled - - - - - - 0 - 40 - 661 - 31 - - - - - - - IP address to listen on: - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - 0 - 70 - 661 - 31 - - - - - - - Port to listen on: - - - - - - - - 80 - 16777215 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - 0 - 98 - - - - - 16777215 - 98 - - - - BOB interface - - - - - 0 - 20 - 97 - 22 - - - - Enabled - - - - - - 0 - 40 - 661 - 31 - - - - - - - IP address to listen on: - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - 0 - 70 - 661 - 31 - - - - - - - Port to listen on: - - - - - - - - 80 - 16777215 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - 0 - 60 - - - - - 16777215 - 60 - - - - - 13 - - - - General options - - - - - - - - 0 - 0 - - - - - 0 - 98 - - - - - 16777215 - 98 - - - - Router external address (for incoming connections) - - - Qt::AlignJustify|Qt::AlignTop - - - - - 0 - 20 - 661 - 81 - - - - - QLayout::SetMinAndMaxSize - - - - - QLayout::SetMinAndMaxSize - - - - - Host: - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - QLayout::SetMinAndMaxSize - - - - - Port (leave 0 to auto-assign): - - - - - - - - 80 - 16777215 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - - - 0 - 78 - - - - - 16777215 - 78 - - - - Addressbook settings - - - - - 0 - 20 - 661 - 31 - - - - - - - Addressbook default subscription URL for initial setup: - - - - - - - - - - - - 0 - 50 - 661 - 31 - - - - - - - Addressbook subscriptions URLs, separated by comma: - - - - - - - - - - - - - - - 0 - 280 - - - - - 16777215 - 280 - - - - HTTP proxy - - - - - 0 - 20 - 97 - 22 - - - - Enabled - - - - - - 0 - 40 - 661 - 31 - - - - - - - IP address to listen on: - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - 0 - 70 - 661 - 31 - - - - - - - Port to listen on: - - - - - - - - 80 - 16777215 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - 0 - 100 - 661 - 31 - - - - - - - Keys file: - - - - - - - - - - Browse… - - - - - - - - - 0 - 160 - 661 - 31 - - - - - - - Inbound tunnels length: - - - - - - - - 80 - 16777215 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - 0 - 190 - 661 - 31 - - - - - - - Inbound tunnels quantity: - - - - - - - - 80 - 16777215 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - 0 - 220 - 661 - 31 - - - - - - - Outbound tunnels length: - - - - - - - - 80 - 16777215 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - 0 - 250 - 661 - 31 - - - - - - - Outbound tunnels quantity: - - - - - - - - 80 - 16777215 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - 0 - 130 - 661 - 31 - - - - - - - Signature type: - - - - - - - - - - Edit - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - 0 - 60 - - - - - 16777215 - 60 - - - - - 13 - - - - Various options - - - - - - - - 0 - 51 - - - - - 16777215 - 51 - - - - Data folder (for storage of i2pd data — RI, keys, peer profiles, …): - - - - - 0 - 20 - 661 - 31 - - - - - QLayout::SetMaximumSize - - - - - - - - Browse… - - - - - - - - - - - - 0 - 0 - - - - - 0 - 215 - - - - - 16777215 - 215 - - - - Router options - - - - - 0 - 20 - 661 - 188 - - - - - - - Enable communication through ipv6 - - - - - - - Router will not accept transit tunnels at startup - - - - - - - Router will be floodfill - - - - - - - - - Bandwidth limit (integer or a letter): - - - - - - - - - - KBps - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - Family (name of a family router belongs to): - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - QLayout::SetMaximumSize - - - - - NetID (network ID router belongs to. The main I2P ID is 2): - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - - - 0 - 108 - - - - - 16777215 - 108 - - - - Limits - - - - - 0 - 20 - 661 - 31 - - - - - - - Maximum number of transit tunnels: - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - 0 - 50 - 661 - 31 - - - - - - - Maximum number of open files (0 — use system limit): - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - 0 - 80 - 661 - 31 - - - - - - - Maximum size of core file in Kb (0 — use system limit): - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - 0 - 98 - - - - - 16777215 - 98 - - - - Reseeding - - - - - 0 - 20 - 661 - 22 - - - - Request SU3 signature verification - - - - - - 0 - 40 - 661 - 31 - - - - - - - SU3 file to reseed from: - - - - - - - - - - Browse… - - - - - - - - - 0 - 70 - 661 - 31 - - - - - - - Reseed URLs, separated by comma: - - - - - - - - - - - - - - - 0 - 170 - - - - - 16777215 - 170 - - - - Trust options - - - - - 0 - 20 - 661 - 21 - - - - Enable explicit trust options - - - - - - 390 - 40 - 271 - 23 - - - - - - - 0 - 40 - 391 - 42 - - - - Make direct I2P connections only to -routers in specified Family: - - - - - - 0 - 82 - 661 - 42 - - - - Make direct I2P connections only to routers specified here. -Comma separated list of base64 identities: - - - - - - 0 - 124 - 661 - 23 - - - - - - - 0 - 147 - 661 - 21 - - - - Should we hide our router from other routers? - - - - - - - - - 0 - 60 - - - - - 16777215 - 60 - - - - - 13 - - - - Ports - - - - - - - - 0 - 22 - - - - - 16777215 - 22 - - - - Insomnia (prevent system from sleeping) - - - - - - - - 0 - 189 - - - - - 16777215 - 189 - - - - I2PControl interface - - - - - 0 - 20 - 97 - 22 - - - - Enabled - - - - - - 0 - 40 - 661 - 31 - - - - - - - IP address to listen on: - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - 0 - 70 - 661 - 31 - - - - - - - Port to listen on: - - - - - - - - 80 - 16777215 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - 0 - 100 - 661 - 31 - - - - - - - Password: - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - 0 - 130 - 661 - 31 - - - - - - - Certificate file: - - - - - - - - - - Browse… - - - - - - - - - 0 - 160 - 661 - 31 - - - - - - - Key file: - - - - - - - - - - Browse… - - - - - - - - - - - - 0 - 179 - - - - - 16777215 - 179 - - - - HTTP webconsole - - - - - 0 - 20 - 97 - 22 - - - - Enabled - - - - - - 0 - 40 - 661 - 31 - - - - - - - IP address to listen on: - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - 0 - 70 - 661 - 31 - - - - - - - Port to listen on: - - - - - - - - 80 - 16777215 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - 0 - 100 - 321 - 22 - - - - Enable basic HTTP auth - - - - - - 60 - 120 - 601 - 31 - - - - - - - Username: - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - 60 - 150 - 601 - 31 - - - - - - - Password: - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - 0 - 335 - - - - - 16777215 - 335 - - - - Socks proxy - - - - - 0 - 20 - 97 - 22 - - - - Enabled - - - - - - 0 - 40 - 661 - 31 - - - - - - - IP address to listen on: - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - 0 - 70 - 661 - 31 - - - - - - - Port to listen on: - - - - - - - - 80 - 16777215 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - 0 - 100 - 661 - 31 - - - - - - - Keys file: - - - - - - - - - - Browse… - - - - - - - - - 0 - 160 - 661 - 31 - - - - - - - Inbound tunnels length: - - - - - - - - 80 - 16777215 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - 0 - 190 - 661 - 31 - - - - - - - Inbound tunnels quantity: - - - - - - - - 80 - 16777215 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - 0 - 220 - 661 - 31 - - - - - - - Outbound tunnels length: - - - - - - - - 80 - 16777215 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - 0 - 250 - 661 - 31 - - - - - - - Outbound tunnels quantity: - - - - - - - - 80 - 16777215 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - 0 - 280 - 661 - 31 - - - - - - - Outproxy address: - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - 0 - 310 - 661 - 31 - - - - - - - Outproxy port: - - - - - - - - 80 - 16777215 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - 0 - 130 - 661 - 31 - - - - - - - Signature type: - - - - - - - - - - Edit - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - - diff --git a/qt/i2pd_qt/i2pd.qrc b/qt/i2pd_qt/i2pd.qrc deleted file mode 100644 index 4e5523e9..00000000 --- a/qt/i2pd_qt/i2pd.qrc +++ /dev/null @@ -1,6 +0,0 @@ - - - resources/icons/mask.ico - resources/images/icon.png - - diff --git a/qt/i2pd_qt/i2pd.rc b/qt/i2pd_qt/i2pd.rc deleted file mode 100644 index bebdf1d6..00000000 --- a/qt/i2pd_qt/i2pd.rc +++ /dev/null @@ -1,32 +0,0 @@ -IDI_ICON1 ICON DISCARDABLE "resources/icons/mask.ico" - -#include // needed for VERSIONINFO -#include "../../libi2pd/version.h" - -VS_VERSION_INFO VERSIONINFO -FILEVERSION I2PD_VERSION_MAJOR,I2PD_VERSION_MINOR,I2PD_VERSION_MICRO,I2PD_VERSION_PATCH -PRODUCTVERSION I2P_VERSION_MAJOR,I2P_VERSION_MINOR,I2P_VERSION_MICRO,I2P_VERSION_PATCH -FILEOS VOS_NT_WINDOWS32 -FILETYPE VFT_APP -BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "040904E4" // U.S. English - multilingual (hex) - BEGIN - VALUE "CompanyName", "PurpleI2P" - VALUE "FileDescription", "I2Pd Qt" - VALUE "FileVersion", I2PD_VERSION - VALUE "InternalName", "i2pd-qt" - VALUE "LegalCopyright", "Copyright (C) 2013-2018, The PurpleI2P Project" - VALUE "LegalTrademarks1", "Distributed under the BSD 3-Clause software license, see the accompanying file COPYING or https://opensource.org/licenses/BSD-3-Clause." - VALUE "OriginalFilename", "i2pd_qt.exe" - VALUE "ProductName", "i2pd-qt" - VALUE "ProductVersion", I2P_VERSION - END - END - - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x0, 1252 // language neutral - multilingual (decimal) - END -END diff --git a/qt/i2pd_qt/i2pd_qt.pro b/qt/i2pd_qt/i2pd_qt.pro deleted file mode 100644 index 468d2d00..00000000 --- a/qt/i2pd_qt/i2pd_qt.pro +++ /dev/null @@ -1,259 +0,0 @@ -QT += core gui - -greaterThan(QT_MAJOR_VERSION, 4): QT += widgets - -TARGET = i2pd_qt -TEMPLATE = app -QMAKE_CXXFLAGS *= -std=c++11 -Wno-unused-parameter -Wno-maybe-uninitialized - -DEFINES += USE_UPNP - -CONFIG(debug, debug|release) { - message(Debug build) - DEFINES += DEBUG_WITH_DEFAULT_LOGGING -} else { - message(Release build) -} - -SOURCES += DaemonQT.cpp mainwindow.cpp \ - ../../libi2pd/api.cpp \ - ../../libi2pd/Base.cpp \ - ../../libi2pd/Blinding.cpp \ - ../../libi2pd/BloomFilter.cpp \ - ../../libi2pd/ChaCha20.cpp \ - ../../libi2pd/Config.cpp \ - ../../libi2pd/CPU.cpp \ - ../../libi2pd/Crypto.cpp \ - ../../libi2pd/CryptoKey.cpp \ - ../../libi2pd/Datagram.cpp \ - ../../libi2pd/Destination.cpp \ - ../../libi2pd/Ed25519.cpp \ - ../../libi2pd/Family.cpp \ - ../../libi2pd/FS.cpp \ - ../../libi2pd/Garlic.cpp \ - ../../libi2pd/Gost.cpp \ - ../../libi2pd/Gzip.cpp \ - ../../libi2pd/HTTP.cpp \ - ../../libi2pd/I2NPProtocol.cpp \ - ../../libi2pd/I2PEndian.cpp \ - ../../libi2pd/Identity.cpp \ - ../../libi2pd/LeaseSet.cpp \ - ../../libi2pd/Log.cpp \ - ../../libi2pd/NetDb.cpp \ - ../../libi2pd/NetDbRequests.cpp \ - ../../libi2pd/NTCP2.cpp \ - ../../libi2pd/Poly1305.cpp \ - ../../libi2pd/Profiling.cpp \ - ../../libi2pd/Reseed.cpp \ - ../../libi2pd/RouterContext.cpp \ - ../../libi2pd/RouterInfo.cpp \ - ../../libi2pd/Signature.cpp \ - ../../libi2pd/SSU.cpp \ - ../../libi2pd/SSUData.cpp \ - ../../libi2pd/SSUSession.cpp \ - ../../libi2pd/Streaming.cpp \ - ../../libi2pd/Timestamp.cpp \ - ../../libi2pd/TransitTunnel.cpp \ - ../../libi2pd/Transports.cpp \ - ../../libi2pd/Tunnel.cpp \ - ../../libi2pd/TunnelEndpoint.cpp \ - ../../libi2pd/TunnelGateway.cpp \ - ../../libi2pd/TunnelPool.cpp \ - ../../libi2pd/util.cpp \ - ../../libi2pd/Elligator.cpp \ - ../../libi2pd/ECIESX25519AEADRatchetSession.cpp \ - ../../libi2pd_client/AddressBook.cpp \ - ../../libi2pd_client/BOB.cpp \ - ../../libi2pd_client/ClientContext.cpp \ - ../../libi2pd_client/HTTPProxy.cpp \ - ../../libi2pd_client/I2CP.cpp \ - ../../libi2pd_client/I2PService.cpp \ - ../../libi2pd_client/I2PTunnel.cpp \ - ../../libi2pd_client/MatchedDestination.cpp \ - ../../libi2pd_client/SAM.cpp \ - ../../libi2pd_client/SOCKS.cpp \ - ../../daemon/Daemon.cpp \ - ../../daemon/HTTPServer.cpp \ - ../../daemon/I2PControl.cpp \ - ../../daemon/i2pd.cpp \ - ../../daemon/UPnP.cpp \ - ClientTunnelPane.cpp \ - MainWindowItems.cpp \ - ServerTunnelPane.cpp \ - SignatureTypeComboboxFactory.cpp \ - TunnelConfig.cpp \ - TunnelPane.cpp \ - textbrowsertweaked1.cpp \ - pagewithbackbutton.cpp \ - widgetlock.cpp \ - widgetlockregistry.cpp \ - logviewermanager.cpp \ - DelayedSaveManager.cpp \ - Saver.cpp \ - DelayedSaveManagerImpl.cpp \ - SaverImpl.cpp - -HEADERS += DaemonQT.h mainwindow.h \ - ../../libi2pd/api.h \ - ../../libi2pd/Base.h \ - ../../libi2pd/Blinding.h \ - ../../libi2pd/BloomFilter.h \ - ../../libi2pd/ChaCha20.h \ - ../../libi2pd/Config.h \ - ../../libi2pd/CPU.h \ - ../../libi2pd/Crypto.h \ - ../../libi2pd/CryptoKey.h \ - ../../libi2pd/CryptoWorker.h \ - ../../libi2pd/Datagram.h \ - ../../libi2pd/Destination.h \ - ../../libi2pd/Ed25519.h \ - ../../libi2pd/Family.h \ - ../../libi2pd/FS.h \ - ../../libi2pd/Garlic.h \ - ../../libi2pd/Gost.h \ - ../../libi2pd/Gzip.h \ - ../../libi2pd/HTTP.h \ - ../../libi2pd/I2NPProtocol.h \ - ../../libi2pd/I2PEndian.h \ - ../../libi2pd/Identity.h \ - ../../libi2pd/LeaseSet.h \ - ../../libi2pd/LittleBigEndian.h \ - ../../libi2pd/Log.h \ - ../../libi2pd/NetDb.hpp \ - ../../libi2pd/NetDbRequests.h \ - ../../libi2pd/NTCP2.h \ - ../../libi2pd/Poly1305.h \ - ../../libi2pd/Profiling.h \ - ../../libi2pd/Queue.h \ - ../../libi2pd/Reseed.h \ - ../../libi2pd/RouterContext.h \ - ../../libi2pd/RouterInfo.h \ - ../../libi2pd/Signature.h \ - ../../libi2pd/Siphash.h \ - ../../libi2pd/SSU.h \ - ../../libi2pd/SSUData.h \ - ../../libi2pd/SSUSession.h \ - ../../libi2pd/Streaming.h \ - ../../libi2pd/Tag.h \ - ../../libi2pd/Timestamp.h \ - ../../libi2pd/TransitTunnel.h \ - ../../libi2pd/Transports.h \ - ../../libi2pd/TransportSession.h \ - ../../libi2pd/Tunnel.h \ - ../../libi2pd/TunnelBase.h \ - ../../libi2pd/TunnelConfig.h \ - ../../libi2pd/TunnelEndpoint.h \ - ../../libi2pd/TunnelGateway.h \ - ../../libi2pd/TunnelPool.h \ - ../../libi2pd/util.h \ - ../../libi2pd/version.h \ - ../../libi2pd/Elligator.h \ - ../../libi2pd/ECIESX25519AEADRatchetSession.h \ - ../../libi2pd_client/AddressBook.h \ - ../../libi2pd_client/BOB.h \ - ../../libi2pd_client/ClientContext.h \ - ../../libi2pd_client/HTTPProxy.h \ - ../../libi2pd_client/I2CP.h \ - ../../libi2pd_client/I2PService.h \ - ../../libi2pd_client/I2PTunnel.h \ - ../../libi2pd_client/MatchedDestination.h \ - ../../libi2pd_client/SAM.h \ - ../../libi2pd_client/SOCKS.h \ - ../../daemon/Daemon.h \ - ../../daemon/HTTPServer.h \ - ../../daemon/I2PControl.h \ - ../../daemon/UPnP.h \ - ClientTunnelPane.h \ - MainWindowItems.h \ - ServerTunnelPane.h \ - SignatureTypeComboboxFactory.h \ - TunnelConfig.h \ - TunnelPane.h \ - TunnelsPageUpdateListener.h \ - textbrowsertweaked1.h \ - pagewithbackbutton.h \ - widgetlock.h \ - widgetlockregistry.h \ - i2pd.rc \ - logviewermanager.h \ - DelayedSaveManager.h \ - Saver.h \ - DelayedSaveManagerImpl.h \ - SaverImpl.h - -INCLUDEPATH += ../../libi2pd -INCLUDEPATH += ../../libi2pd_client -INCLUDEPATH += ../../daemon -INCLUDEPATH += . - -FORMS += mainwindow.ui \ - tunnelform.ui \ - statusbuttons.ui \ - routercommandswidget.ui \ - generalsettingswidget.ui - -LIBS += -lz - -macx { - message("using mac os x target") - BREWROOT=/usr/local - BOOSTROOT=$$BREWROOT/opt/boost - SSLROOT=$$BREWROOT/opt/libressl - UPNPROOT=$$BREWROOT/opt/miniupnpc - INCLUDEPATH += $$BOOSTROOT/include - INCLUDEPATH += $$SSLROOT/include - INCLUDEPATH += $$UPNPROOT/include - LIBS += $$SSLROOT/lib/libcrypto.a - LIBS += $$SSLROOT/lib/libssl.a - LIBS += $$BOOSTROOT/lib/libboost_system.a - LIBS += $$BOOSTROOT/lib/libboost_date_time.a - LIBS += $$BOOSTROOT/lib/libboost_filesystem.a - LIBS += $$BOOSTROOT/lib/libboost_program_options.a - LIBS += $$UPNPROOT/lib/libminiupnpc.a - LIBS += -Wl,-dead_strip - LIBS += -Wl,-dead_strip_dylibs - LIBS += -Wl,-bind_at_load -} - -linux:!android { - message("Using Linux settings") - LIBS += -lcrypto -lssl -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread -lminiupnpc -} - -windows { - message("Using Windows settings") - RC_FILE = i2pd.rc - DEFINES += BOOST_USE_WINDOWS_H WINDOWS _WINDOWS WIN32_LEAN_AND_MEAN MINIUPNP_STATICLIB - DEFINES -= UNICODE _UNICODE - BOOST_SUFFIX = -mt - QMAKE_CXXFLAGS_RELEASE = -Os - QMAKE_LFLAGS = -Wl,-Bstatic -static-libgcc -static-libstdc++ -mwindows - - #linker's -s means "strip" - QMAKE_LFLAGS_RELEASE += -s - - LIBS = -lminiupnpc \ - -lboost_system$$BOOST_SUFFIX \ - -lboost_date_time$$BOOST_SUFFIX \ - -lboost_filesystem$$BOOST_SUFFIX \ - -lboost_program_options$$BOOST_SUFFIX \ - -lssl \ - -lcrypto \ - -lz \ - -lwsock32 \ - -lws2_32 \ - -lgdi32 \ - -liphlpapi \ - -lstdc++ \ - -lpthread -} - -!android:!symbian:!maemo5:!simulator { - message("Build with a system tray icon") - # see also http://doc.qt.io/qt-4.8/qt-desktop-systray-systray-pro.html for example on wince* - #sources.files = $$SOURCES $$HEADERS $$RESOURCES $$FORMS i2pd_qt.pro resources images - RESOURCES = i2pd.qrc - QT += xml - #INSTALLS += sources -} - diff --git a/qt/i2pd_qt/logviewermanager.cpp b/qt/i2pd_qt/logviewermanager.cpp deleted file mode 100644 index 823956b2..00000000 --- a/qt/i2pd_qt/logviewermanager.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include "logviewermanager.h" - -LogViewerManager::LogViewerManager(std::shared_ptr logStream_, - QPlainTextEdit* logTextEdit_, - QObject *parent) : - QObject(parent), - logStream(logStream_), - logTextEdit(logTextEdit_), - controllerForBgThread(nullptr) -{ - assert(logTextEdit!=nullptr); - controllerForBgThread=new i2pd::qt::logviewer::Controller(*this); -} - -namespace i2pd { -namespace qt { -namespace logviewer { - -QString Worker::pollAndShootATimerForInfiniteRetries() { - std::shared_ptr logStream=logViewerManager.getLogStream(); - if(!logStream)return ""; - std::streamsize MAX_SZ=64*1024; - char*buf=(char*)malloc(MAX_SZ*sizeof(char)); - if(buf==nullptr)return ""; - std::streamsize read=logStream->readsome(buf, MAX_SZ); - if(read<0)read=0; - QString ret=QString::fromUtf8(buf, read); - free(buf); - return ret; -} - -Controller::Controller(LogViewerManager ¶meter1):logViewerManager(parameter1) { - Worker *worker = new Worker(parameter1); - worker->moveToThread(&workerThread); - connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater); - connect(this, &Controller::operate1, worker, &Worker::doWork1); - connect(worker, &Worker::resultReady, - ¶meter1, &LogViewerManager::appendPlainText_atGuiThread); - workerThread.start(); - timerId=startTimer(100/*millis*/); -} - -} -} -} diff --git a/qt/i2pd_qt/logviewermanager.h b/qt/i2pd_qt/logviewermanager.h deleted file mode 100644 index e9ede79f..00000000 --- a/qt/i2pd_qt/logviewermanager.h +++ /dev/null @@ -1,130 +0,0 @@ -#ifndef LOGVIEWERMANAGER_H -#define LOGVIEWERMANAGER_H - -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "FS.h" -#include "Log.h" - -class LogViewerManager; - -namespace i2pd { -namespace qt { -namespace logviewer { - -class Worker : public QObject -{ - Q_OBJECT -private: - LogViewerManager &logViewerManager; -public: - Worker(LogViewerManager ¶meter1):logViewerManager(parameter1){} -private: - QString pollAndShootATimerForInfiniteRetries(); - -public slots: - void doWork1() { - /* ... here is the expensive or blocking operation ... */ - QString read=pollAndShootATimerForInfiniteRetries(); - emit resultReady(read); - } - -signals: - void resultReady(QString read); -}; - -class Controller : public QObject -{ - Q_OBJECT - QThread workerThread; - LogViewerManager& logViewerManager; - int timerId; -public: - Controller(LogViewerManager ¶meter1); - ~Controller() { - if(timerId!=0)killTimer(timerId); - workerThread.quit(); - workerThread.wait(); - } -signals: - void operate1(); -protected: - void timerEvent(QTimerEvent */*event*/) { - emit operate1(); - } -}; - -} -} -} - -class LogViewerManager : public QObject -{ - Q_OBJECT -private: - std::shared_ptr logStream; - QPlainTextEdit* logTextEdit; - i2pd::qt::logviewer::Controller* controllerForBgThread; -public: - //also starts a bg thread (QTimer) polling logStream->readsome(buf, n) - explicit LogViewerManager(std::shared_ptr logStream_, - QPlainTextEdit* logTextEdit_, - QObject *parent); - //also deallocs the bg thread (QTimer) - virtual ~LogViewerManager(){} - const i2pd::qt::logviewer::Controller& getControllerForBgThread() { - assert(controllerForBgThread!=nullptr); - return *controllerForBgThread; - } - const QPlainTextEdit* getLogTextEdit(){ return logTextEdit; } - const std::shared_ptr getLogStream(){ return logStream; } -signals: - -public slots: - //void appendFromNonGuiThread(std::string read) { - //} -public slots: - void appendPlainText_atGuiThread(QString plainText) { - if(plainText.length()==0)return; - assert(logTextEdit!=nullptr); - int scrollPosVert =logTextEdit->verticalScrollBar()->value(); - int scrollPosHoriz=logTextEdit->horizontalScrollBar()->value(); - int scrollPosVertMax =logTextEdit->verticalScrollBar()->maximum(); - const int MAX_LINES=10*1024; - logTextEdit->setMaximumBlockCount(MAX_LINES); - //logTextEdit->appendPlainText(plainText); - //navigate the window to the end - //QTextCursor cursor = logTextEdit->textCursor(); - //cursor.movePosition(QTextCursor::MoveOperation::End); - //logTextEdit->setTextCursor(cursor); - //QTextCursor prev_cursor = logTextEdit->textCursor(); - logTextEdit->moveCursor(QTextCursor::End); - logTextEdit->insertPlainText(plainText); - if(/*prev_cursor.atEnd()*/scrollPosVert==scrollPosVertMax){ - //logTextEdit->moveCursor(QTextCursor::End); - scrollPosVert =logTextEdit->verticalScrollBar()->maximum(); - scrollPosHoriz=logTextEdit->horizontalScrollBar()->minimum(); - } - //else - // logTextEdit->setTextCursor(prev_cursor); - logTextEdit->verticalScrollBar()->setValue(scrollPosVert); - logTextEdit->horizontalScrollBar()->setValue(scrollPosHoriz); - } - /* - void replaceText_atGuiThread() { - assert(logTextEdit!=nullptr); - logTextEdit->setText(QString::fromStdString(nav.getContent())); - } - */ -}; - -#endif // LOGVIEWERMANAGER_H diff --git a/qt/i2pd_qt/mainwindow.cpp b/qt/i2pd_qt/mainwindow.cpp deleted file mode 100644 index 0ce0e2f8..00000000 --- a/qt/i2pd_qt/mainwindow.cpp +++ /dev/null @@ -1,932 +0,0 @@ -#include "mainwindow.h" -#include "ui_mainwindow.h" -#include "ui_statusbuttons.h" -#include "ui_routercommandswidget.h" -#include "ui_generalsettingswidget.h" -#include -#include -#include -#include -#include -#include "RouterContext.h" -#include "Config.h" -#include "FS.h" -#include "Log.h" -#include "RouterContext.h" -#include "Transports.h" - -#include "HTTPServer.h" - -#ifndef ANDROID -# include -#endif - -#include "DaemonQT.h" -#include "SignatureTypeComboboxFactory.h" - -#include "logviewermanager.h" - -#include "DelayedSaveManagerImpl.h" -#include "SaverImpl.h" - -std::string programOptionsWriterCurrentSection; - -MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *parent) : - QMainWindow(parent) - ,logStream(logStream_) - ,delayedSaveManagerPtr(new DelayedSaveManagerImpl()) - ,dataSerial(DelayedSaveManagerImpl::INITIAL_DATA_SERIAL) - ,wasSelectingAtStatusMainPage(false) - ,showHiddenInfoStatusMainPage(false) - ,logViewerManagerPtr(nullptr) -#ifndef ANDROID - ,quitting(false) -#endif - ,ui(new Ui::MainWindow) - ,statusButtonsUI(new Ui::StatusButtonsForm) - ,routerCommandsUI(new Ui::routerCommandsWidget) - ,uiSettings(new Ui::GeneralSettingsContentsForm) - ,routerCommandsParent(new QWidget(this)) - ,widgetlocks() - ,i2pController(nullptr) - ,configItems() - ,datadir() - ,confpath() - ,tunconfpath() - ,tunnelConfigs() - ,tunnelsPageUpdateListener(this) - ,saverPtr(new SaverImpl(this, &configItems, &tunnelConfigs)) - -{ - assert(delayedSaveManagerPtr!=nullptr); - assert(saverPtr!=nullptr); - - ui->setupUi(this); - statusButtonsUI->setupUi(ui->statusButtonsPane); - routerCommandsUI->setupUi(routerCommandsParent); - uiSettings->setupUi(ui->settingsContents); - routerCommandsParent->hide(); - ui->verticalLayout_2->addWidget(routerCommandsParent); - //,statusHtmlUI(new Ui::StatusHtmlPaneForm) - //statusHtmlUI->setupUi(lastStatusWidgetui->statusWidget); - ui->statusButtonsPane->setFixedSize(171,300); - ui->verticalLayout->setGeometry(QRect(0,0,171,ui->verticalLayout->geometry().height())); - //ui->statusButtonsPane->adjustSize(); - //ui->centralWidget->adjustSize(); - setWindowTitle(QApplication::translate("AppTitle","I2PD")); - - //TODO handle resizes and change the below into resize() call - setFixedHeight(550); - ui->centralWidget->setFixedHeight(550); - onResize(); - - ui->stackedWidget->setCurrentIndex(0); - ui->settingsScrollArea->resize(uiSettings->settingsContentsGridLayout->sizeHint().width()+10,380); - //QScrollBar* const barSett = ui->settingsScrollArea->verticalScrollBar(); - int w = 683; - int h = 3060; - ui->settingsContents->setFixedSize(w, h); - ui->settingsContents->setGeometry(QRect(0,0,w,h)); - - /* - QPalette pal(palette()); - pal.setColor(QPalette::Background, Qt::red); - ui->settingsContents->setAutoFillBackground(true); - ui->settingsContents->setPalette(pal); - */ - QPalette pal(palette()); - pal.setColor(QPalette::Background, Qt::red); - ui->wrongInputLabel->setAutoFillBackground(true); - ui->wrongInputLabel->setPalette(pal); - ui->wrongInputLabel->setMaximumHeight(ui->wrongInputLabel->sizeHint().height()); - ui->wrongInputLabel->setVisible(false); - - settingsTitleLabelNominalHeight = ui->settingsTitleLabel->height(); -#ifndef ANDROID - createActions(); - createTrayIcon(); -#endif - - textBrowser = new TextBrowserTweaked1(this); - //textBrowser->setOpenExternalLinks(false); - textBrowser->setOpenLinks(false); - /*textBrowser->setTextInteractionFlags(textBrowser->textInteractionFlags()| - Qt::LinksAccessibleByMouse|Qt::LinksAccessibleByKeyboard| - Qt::TextSelectableByMouse|Qt::TextSelectableByKeyboard);*/ - ui->verticalLayout_2->addWidget(textBrowser); - childTextBrowser = new TextBrowserTweaked1(this); - //childTextBrowser->setOpenExternalLinks(false); - childTextBrowser->setOpenLinks(false); - connect(textBrowser, SIGNAL(anchorClicked(const QUrl&)), this, SLOT(anchorClickedHandler(const QUrl&))); - pageWithBackButton = new PageWithBackButton(this, childTextBrowser); - ui->verticalLayout_2->addWidget(pageWithBackButton); - pageWithBackButton->hide(); - connect(pageWithBackButton, SIGNAL(backReleased()), this, SLOT(backClickedFromChild())); - scheduleStatusPageUpdates(); - - QObject::connect(ui->statusPagePushButton, SIGNAL(released()), this, SLOT(showStatusMainPage())); - showStatusMainPage(); - QObject::connect(statusButtonsUI->mainPagePushButton, SIGNAL(released()), this, SLOT(showStatusMainPage())); - QObject::connect(statusButtonsUI->routerCommandsPushButton, SIGNAL(released()), this, SLOT(showStatus_commands_Page())); - QObject::connect(statusButtonsUI->localDestinationsPushButton, SIGNAL(released()), this, SLOT(showStatus_local_destinations_Page())); - QObject::connect(statusButtonsUI->leasesetsPushButton, SIGNAL(released()), this, SLOT(showStatus_leasesets_Page())); - QObject::connect(statusButtonsUI->tunnelsPushButton, SIGNAL(released()), this, SLOT(showStatus_tunnels_Page())); - QObject::connect(statusButtonsUI->transitTunnelsPushButton, SIGNAL(released()), this, SLOT(showStatus_transit_tunnels_Page())); - QObject::connect(statusButtonsUI->transportsPushButton, SIGNAL(released()), this, SLOT(showStatus_transports_Page())); - QObject::connect(statusButtonsUI->i2pTunnelsPushButton, SIGNAL(released()), this, SLOT(showStatus_i2p_tunnels_Page())); - QObject::connect(statusButtonsUI->samSessionsPushButton, SIGNAL(released()), this, SLOT(showStatus_sam_sessions_Page())); - - QObject::connect(textBrowser, SIGNAL(mouseReleased()), this, SLOT(statusHtmlPageMouseReleased())); - QObject::connect(textBrowser, SIGNAL(selectionChanged()), this, SLOT(statusHtmlPageSelectionChanged())); - - QObject::connect(routerCommandsUI->runPeerTestPushButton, SIGNAL(released()), this, SLOT(runPeerTest())); - QObject::connect(routerCommandsUI->acceptTransitTunnelsPushButton, SIGNAL(released()), this, SLOT(enableTransit())); - QObject::connect(routerCommandsUI->declineTransitTunnelsPushButton, SIGNAL(released()), this, SLOT(disableTransit())); - - QObject::connect(ui->logViewerPushButton, SIGNAL(released()), this, SLOT(showLogViewerPage())); - - QObject::connect(ui->settingsPagePushButton, SIGNAL(released()), this, SLOT(showSettingsPage())); - - QObject::connect(ui->tunnelsPagePushButton, SIGNAL(released()), this, SLOT(showTunnelsPage())); - QObject::connect(ui->restartPagePushButton, SIGNAL(released()), this, SLOT(showRestartPage())); - QObject::connect(ui->quitPagePushButton, SIGNAL(released()), this, SLOT(showQuitPage())); - - QObject::connect(ui->fastQuitPushButton, SIGNAL(released()), this, SLOT(handleQuitButton())); - QObject::connect(ui->gracefulQuitPushButton, SIGNAL(released()), this, SLOT(handleGracefulQuitButton())); - - QObject::connect(ui->doRestartI2PDPushButton, SIGNAL(released()), this, SLOT(handleDoRestartButton())); - -# define OPTION(section,option,defaultValueGetter) ConfigOption(QString(section),QString(option)) - - initFileChooser( OPTION("","conf",[](){return "";}), uiSettings->configFileLineEdit, uiSettings->configFileBrowsePushButton); - initFolderChooser( OPTION("","datadir",[]{return "";}), uiSettings->dataFolderLineEdit, uiSettings->dataFolderBrowsePushButton); - initFileChooser( OPTION("","tunconf",[](){return "";}), uiSettings->tunnelsConfigFileLineEdit, uiSettings->tunnelsConfigFileBrowsePushButton); - - initFileChooser( OPTION("","pidfile",[]{return "";}), uiSettings->pidFileLineEdit, uiSettings->pidFileBrowsePushButton); - daemonOption=initNonGUIOption( OPTION("","daemon",[]{return "";})); - serviceOption=initNonGUIOption( OPTION("","service",[]{return "";})); - - uiSettings->logDestinationComboBox->clear(); - uiSettings->logDestinationComboBox->insertItems(0, QStringList() - << QApplication::translate("MainWindow", "syslog", 0) - << QApplication::translate("MainWindow", "stdout", 0) - << QApplication::translate("MainWindow", "file", 0) - ); - initLogDestinationCombobox( OPTION("","log",[]{return "";}), uiSettings->logDestinationComboBox); - - logFileNameOption=initFileChooser( OPTION("","logfile",[]{return "";}), uiSettings->logFileLineEdit, uiSettings->logFileBrowsePushButton); - initLogLevelCombobox(OPTION("","loglevel",[]{return "";}), uiSettings->logLevelComboBox); - - initIPAddressBox( OPTION("","host",[]{return "";}), uiSettings->routerExternalHostLineEdit, tr("Router external address -> Host")); - initTCPPortBox( OPTION("","port",[]{return "";}), uiSettings->routerExternalPortLineEdit, tr("Router external address -> Port")); - - initCheckBox( OPTION("","ipv6",[]{return "false";}), uiSettings->ipv6CheckBox); - initCheckBox( OPTION("","notransit",[]{return "false";}), uiSettings->notransitCheckBox); - initCheckBox( OPTION("","floodfill",[]{return "false";}), uiSettings->floodfillCheckBox); - initStringBox( OPTION("","bandwidth",[]{return "";}), uiSettings->bandwidthLineEdit); - initStringBox( OPTION("","family",[]{return "";}), uiSettings->familyLineEdit); - initIntegerBox( OPTION("","netid",[]{return "2";}), uiSettings->netIdLineEdit, tr("NetID")); - -#ifdef Q_OS_WIN - initCheckBox( OPTION("","insomnia",[]{return "";}), uiSettings->insomniaCheckBox); - initNonGUIOption( OPTION("","svcctl",[]{return "";})); - initNonGUIOption( OPTION("","close",[]{return "";})); -#else - uiSettings->insomniaCheckBox->setEnabled(false); -#endif - - initCheckBox( OPTION("http","enabled",[]{return "true";}), uiSettings->webconsoleEnabledCheckBox); - initIPAddressBox( OPTION("http","address",[]{return "";}), uiSettings->webconsoleAddrLineEdit, tr("HTTP webconsole -> IP address")); - initTCPPortBox( OPTION("http","port",[]{return "7070";}), uiSettings->webconsolePortLineEdit, tr("HTTP webconsole -> Port")); - initCheckBox( OPTION("http","auth",[]{return "";}), uiSettings->webconsoleBasicAuthCheckBox); - initStringBox( OPTION("http","user",[]{return "i2pd";}), uiSettings->webconsoleUserNameLineEditBasicAuth); - initStringBox( OPTION("http","pass",[]{return "";}), uiSettings->webconsolePasswordLineEditBasicAuth); - - initCheckBox( OPTION("httpproxy","enabled",[]{return "";}), uiSettings->httpProxyEnabledCheckBox); - initIPAddressBox( OPTION("httpproxy","address",[]{return "";}), uiSettings->httpProxyAddressLineEdit, tr("HTTP proxy -> IP address")); - initTCPPortBox( OPTION("httpproxy","port",[]{return "4444";}), uiSettings->httpProxyPortLineEdit, tr("HTTP proxy -> Port")); - initFileChooser( OPTION("httpproxy","keys",[]{return "";}), uiSettings->httpProxyKeyFileLineEdit, uiSettings->httpProxyKeyFilePushButton); - - initSignatureTypeCombobox(OPTION("httpproxy","signaturetype",[]{return "7";}), uiSettings->comboBox_httpPorxySignatureType); - initStringBox( OPTION("httpproxy","inbound.length",[]{return "3";}), uiSettings->httpProxyInboundTunnelsLenLineEdit); - initStringBox( OPTION("httpproxy","inbound.quantity",[]{return "5";}), uiSettings->httpProxyInboundTunnQuantityLineEdit); - initStringBox( OPTION("httpproxy","outbound.length",[]{return "3";}), uiSettings->httpProxyOutBoundTunnLenLineEdit); - initStringBox( OPTION("httpproxy","outbound.quantity",[]{return "5";}), uiSettings->httpProxyOutboundTunnQuantityLineEdit); - - initCheckBox( OPTION("socksproxy","enabled",[]{return "";}), uiSettings->socksProxyEnabledCheckBox); - initIPAddressBox( OPTION("socksproxy","address",[]{return "";}), uiSettings->socksProxyAddressLineEdit, tr("Socks proxy -> IP address")); - initTCPPortBox( OPTION("socksproxy","port",[]{return "4447";}), uiSettings->socksProxyPortLineEdit, tr("Socks proxy -> Port")); - initFileChooser( OPTION("socksproxy","keys",[]{return "";}), uiSettings->socksProxyKeyFileLineEdit, uiSettings->socksProxyKeyFilePushButton); - initSignatureTypeCombobox(OPTION("socksproxy","signaturetype",[]{return "7";}), uiSettings->comboBox_socksProxySignatureType); - initStringBox( OPTION("socksproxy","inbound.length",[]{return "";}), uiSettings->socksProxyInboundTunnelsLenLineEdit); - initStringBox( OPTION("socksproxy","inbound.quantity",[]{return "";}), uiSettings->socksProxyInboundTunnQuantityLineEdit); - initStringBox( OPTION("socksproxy","outbound.length",[]{return "";}), uiSettings->socksProxyOutBoundTunnLenLineEdit); - initStringBox( OPTION("socksproxy","outbound.quantity",[]{return "";}), uiSettings->socksProxyOutboundTunnQuantityLineEdit); - initIPAddressBox( OPTION("socksproxy","outproxy",[]{return "";}), uiSettings->outproxyAddressLineEdit, tr("Socks proxy -> Outproxy address")); - initTCPPortBox( OPTION("socksproxy","outproxyport",[]{return "";}), uiSettings->outproxyPortLineEdit, tr("Socks proxy -> Outproxy port")); - - initCheckBox( OPTION("sam","enabled",[]{return "false";}), uiSettings->samEnabledCheckBox); - initIPAddressBox( OPTION("sam","address",[]{return "";}), uiSettings->samAddressLineEdit, tr("SAM -> IP address")); - initTCPPortBox( OPTION("sam","port",[]{return "7656";}), uiSettings->samPortLineEdit, tr("SAM -> Port")); - - initCheckBox( OPTION("bob","enabled",[]{return "false";}), uiSettings->bobEnabledCheckBox); - initIPAddressBox( OPTION("bob","address",[]{return "";}), uiSettings->bobAddressLineEdit, tr("BOB -> IP address")); - initTCPPortBox( OPTION("bob","port",[]{return "2827";}), uiSettings->bobPortLineEdit, tr("BOB -> Port")); - - initCheckBox( OPTION("i2cp","enabled",[]{return "false";}), uiSettings->i2cpEnabledCheckBox); - initIPAddressBox( OPTION("i2cp","address",[]{return "";}), uiSettings->i2cpAddressLineEdit, tr("I2CP -> IP address")); - initTCPPortBox( OPTION("i2cp","port",[]{return "7654";}), uiSettings->i2cpPortLineEdit, tr("I2CP -> Port")); - - initCheckBox( OPTION("i2pcontrol","enabled",[]{return "false";}), uiSettings->i2pControlEnabledCheckBox); - initIPAddressBox( OPTION("i2pcontrol","address",[]{return "";}), uiSettings->i2pControlAddressLineEdit, tr("I2PControl -> IP address")); - initTCPPortBox( OPTION("i2pcontrol","port",[]{return "7650";}), uiSettings->i2pControlPortLineEdit, tr("I2PControl -> Port")); - initStringBox( OPTION("i2pcontrol","password",[]{return "";}), uiSettings->i2pControlPasswordLineEdit); - initFileChooser( OPTION("i2pcontrol","cert",[]{return "i2pcontrol.crt.pem";}), uiSettings->i2pControlCertFileLineEdit, uiSettings->i2pControlCertFileBrowsePushButton); - initFileChooser( OPTION("i2pcontrol","key",[]{return "i2pcontrol.key.pem";}), uiSettings->i2pControlKeyFileLineEdit, uiSettings->i2pControlKeyFileBrowsePushButton); - - initCheckBox( OPTION("upnp","enabled",[]{return "true";}), uiSettings->enableUPnPCheckBox); - initStringBox( OPTION("upnp","name",[]{return "I2Pd";}), uiSettings->upnpNameLineEdit); - - initCheckBox( OPTION("precomputation","elgamal",[]{return "false";}), uiSettings->useElGamalPrecomputedTablesCheckBox); - - initCheckBox( OPTION("reseed","verify",[]{return "";}), uiSettings->reseedVerifyCheckBox); - initFileChooser( OPTION("reseed","file",[]{return "";}), uiSettings->reseedFileLineEdit, uiSettings->reseedFileBrowsePushButton); - initStringBox( OPTION("reseed","urls",[]{return "";}), uiSettings->reseedURLsLineEdit); - - initStringBox( OPTION("addressbook","defaulturl",[]{return "";}), uiSettings->addressbookDefaultURLLineEdit); - initStringBox( OPTION("addressbook","subscriptions",[]{return "";}), uiSettings->addressbookSubscriptionsURLslineEdit); - - initUInt16Box( OPTION("limits","transittunnels",[]{return "2500";}), uiSettings->maxNumOfTransitTunnelsLineEdit, tr("maxNumberOfTransitTunnels")); - initUInt16Box( OPTION("limits","openfiles",[]{return "0";}), uiSettings->maxNumOfOpenFilesLineEdit, tr("maxNumberOfOpenFiles")); - initUInt32Box( OPTION("limits","coresize",[]{return "0";}), uiSettings->coreFileMaxSizeNumberLineEdit, tr("coreFileMaxSize")); - - initCheckBox( OPTION("trust","enabled",[]{return "false";}), uiSettings->checkBoxTrustEnable); - initStringBox( OPTION("trust","family",[]{return "";}), uiSettings->lineEditTrustFamily); - initStringBox( OPTION("trust","routers",[]{return "";}), uiSettings->lineEditTrustRouters); - initCheckBox( OPTION("trust","hidden",[]{return "false";}), uiSettings->checkBoxTrustHidden); - -# undef OPTION - - //widgetlocks.add(new widgetlock(widget,lockbtn)); - widgetlocks.add(new widgetlock(uiSettings->logDestinationComboBox,uiSettings->logDestComboEditPushButton)); - widgetlocks.add(new widgetlock(uiSettings->logLevelComboBox,uiSettings->logLevelComboEditPushButton)); - widgetlocks.add(new widgetlock(uiSettings->comboBox_httpPorxySignatureType,uiSettings->httpProxySignTypeComboEditPushButton)); - widgetlocks.add(new widgetlock(uiSettings->comboBox_socksProxySignatureType,uiSettings->socksProxySignTypeComboEditPushButton)); - - loadAllConfigs(saverPtr); - - QObject::connect(saverPtr, SIGNAL(reloadTunnelsConfigAndUISignal(const QString)), - this, SLOT(reloadTunnelsConfigAndUI_QString(const QString))); - - delayedSaveManagerPtr->setSaver(saverPtr); - delayedSaveManagerPtr->start(); - - QObject::connect(uiSettings->logDestinationComboBox, SIGNAL(currentIndexChanged(const QString &)), - this, SLOT(logDestinationComboBoxValueChanged(const QString &))); - logDestinationComboBoxValueChanged(uiSettings->logDestinationComboBox->currentText()); - - ui->tunnelsScrollAreaWidgetContents->setGeometry(QRect(0, 0, 621, 451)); - - appendTunnelForms(""); - - uiSettings->configFileLineEdit->setEnabled(false); - uiSettings->configFileBrowsePushButton->setEnabled(false); - uiSettings->configFileLineEdit->setText(confpath); - uiSettings->tunnelsConfigFileLineEdit->setText(tunconfpath); - - for(QList::iterator it = configItems.begin(); it!= configItems.end(); ++it) { - MainWindowItem* item = *it; - item->installListeners(this); - } - - QObject::connect(uiSettings->tunnelsConfigFileLineEdit, SIGNAL(textChanged(const QString &)), - this, SLOT(reloadTunnelsConfigAndUI())); - QObject::connect(ui->addServerTunnelPushButton, SIGNAL(released()), this, SLOT(addServerTunnelPushButtonReleased())); - QObject::connect(ui->addClientTunnelPushButton, SIGNAL(released()), this, SLOT(addClientTunnelPushButtonReleased())); - - -#ifndef ANDROID - QObject::connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), - this, SLOT(iconActivated(QSystemTrayIcon::ActivationReason))); - - setIcon(); - trayIcon->show(); -#endif - - logViewerManagerPtr=new LogViewerManager(logStream_,ui->logViewerTextEdit,this); - assert(logViewerManagerPtr!=nullptr); - //onLoggingOptionsChange(); - //QMetaObject::connectSlotsByName(this); -} - -void MainWindow::logDestinationComboBoxValueChanged(const QString & text) { - bool fileEnabled = text==QString("file"); - uiSettings->logFileLineEdit->setEnabled(fileEnabled); - uiSettings->logFileBrowsePushButton->setEnabled(fileEnabled); -} - - -void MainWindow::updateRouterCommandsButtons() { - bool acceptsTunnels = i2p::context.AcceptsTunnels (); - routerCommandsUI->declineTransitTunnelsPushButton->setEnabled(acceptsTunnels); - routerCommandsUI->acceptTransitTunnelsPushButton->setEnabled(!acceptsTunnels); -} - -void MainWindow::showStatusPage(StatusPage newStatusPage){ - ui->stackedWidget->setCurrentIndex(0); - setStatusButtonsVisible(true); - statusPage=newStatusPage; - showHiddenInfoStatusMainPage=false; - if(newStatusPage!=StatusPage::commands){ - textBrowser->setHtml(getStatusPageHtml(false)); - textBrowser->show(); - routerCommandsParent->hide(); - pageWithBackButton->hide(); - }else{ - routerCommandsParent->show(); - textBrowser->hide(); - pageWithBackButton->hide(); - updateRouterCommandsButtons(); - } - wasSelectingAtStatusMainPage=false; -} -void MainWindow::showLogViewerPage(){ui->stackedWidget->setCurrentIndex(1);setStatusButtonsVisible(false);} -void MainWindow::showSettingsPage(){ui->stackedWidget->setCurrentIndex(2);setStatusButtonsVisible(false);} -void MainWindow::showTunnelsPage(){ui->stackedWidget->setCurrentIndex(3);setStatusButtonsVisible(false);} -void MainWindow::showRestartPage(){ui->stackedWidget->setCurrentIndex(4);setStatusButtonsVisible(false);} -void MainWindow::showQuitPage(){ui->stackedWidget->setCurrentIndex(5);setStatusButtonsVisible(false);} - -void MainWindow::setStatusButtonsVisible(bool visible) { - ui->statusButtonsPane->setVisible(visible); -} - -// see also: HTTPServer.cpp -QString MainWindow::getStatusPageHtml(bool showHiddenInfo) { - std::stringstream s; - - s << ""; - - switch (statusPage) { - case main_page: - i2p::http::ShowStatus(s, showHiddenInfo, i2p::http::OutputFormatEnum::forQtUi); - break; - case commands: break; - case local_destinations: i2p::http::ShowLocalDestinations(s);break; - case leasesets: i2p::http::ShowLeasesSets(s); break; - case tunnels: i2p::http::ShowTunnels(s); break; - case transit_tunnels: i2p::http::ShowTransitTunnels(s); break; - case transports: i2p::http::ShowTransports(s); break; - case i2p_tunnels: i2p::http::ShowI2PTunnels(s); break; - case sam_sessions: i2p::http::ShowSAMSessions(s); break; - default: assert(false); break; - } - - std::string str = s.str(); - return QString::fromStdString(str); -} - -void MainWindow::showStatusMainPage() { showStatusPage(StatusPage::main_page); } -void MainWindow::showStatus_commands_Page() { showStatusPage(StatusPage::commands); } -void MainWindow::showStatus_local_destinations_Page() { showStatusPage(StatusPage::local_destinations); } -void MainWindow::showStatus_leasesets_Page() { showStatusPage(StatusPage::leasesets); } -void MainWindow::showStatus_tunnels_Page() { showStatusPage(StatusPage::tunnels); } -void MainWindow::showStatus_transit_tunnels_Page() { showStatusPage(StatusPage::transit_tunnels); } -void MainWindow::showStatus_transports_Page() { showStatusPage(StatusPage::transports); } -void MainWindow::showStatus_i2p_tunnels_Page() { showStatusPage(StatusPage::i2p_tunnels); } -void MainWindow::showStatus_sam_sessions_Page() { showStatusPage(StatusPage::sam_sessions); } - - -void MainWindow::scheduleStatusPageUpdates() { - statusPageUpdateTimer = new QTimer(this); - connect(statusPageUpdateTimer, SIGNAL(timeout()), this, SLOT(updateStatusPage())); - statusPageUpdateTimer->start(10*1000/*millis*/); -} - -void MainWindow::statusHtmlPageMouseReleased() { - if(wasSelectingAtStatusMainPage){ - QString selection = textBrowser->textCursor().selectedText(); - if(!selection.isEmpty()&&!selection.isNull())return; - } - showHiddenInfoStatusMainPage=!showHiddenInfoStatusMainPage; - textBrowser->setHtml(getStatusPageHtml(showHiddenInfoStatusMainPage)); -} - -void MainWindow::statusHtmlPageSelectionChanged() { - wasSelectingAtStatusMainPage=true; -} - -void MainWindow::updateStatusPage() { - showHiddenInfoStatusMainPage=false; - textBrowser->setHtml(getStatusPageHtml(showHiddenInfoStatusMainPage)); -} - - -//TODO -void MainWindow::resizeEvent(QResizeEvent *event) -{ - QMainWindow::resizeEvent(event); - onResize(); -} - -//TODO -void MainWindow::onResize() -{ - if(isVisible()){ - ui->horizontalLayoutWidget->resize(ui->horizontalLayoutWidget->width(), height()); - - //status - ui->statusPage->resize(ui->statusPage->width(), height()); - - //tunnels - ui->tunnelsPage->resize(ui->tunnelsPage->width(), height()); - ui->verticalLayoutWidget_6->resize(ui->verticalLayoutWidget_6->width(), height()-20); - /*ui->tunnelsScrollArea->resize(ui->tunnelsScrollArea->width(), - ui->verticalLayoutWidget_6->height()-ui->label_5->height());*/ - } -} - -#ifndef ANDROID -void MainWindow::createActions() { - toggleWindowVisibleAction = new QAction(tr("&Toggle the window"), this); - connect(toggleWindowVisibleAction, SIGNAL(triggered()), this, SLOT(toggleVisibilitySlot())); - - //quitAction = new QAction(tr("&Quit"), this); - //connect(quitAction, SIGNAL(triggered()), QApplication::instance(), SLOT(quit())); -} - -void MainWindow::toggleVisibilitySlot() { - setVisible(!isVisible()); -} - -void MainWindow::createTrayIcon() { - trayIconMenu = new QMenu(this); - trayIconMenu->addAction(toggleWindowVisibleAction); - //trayIconMenu->addSeparator(); - //trayIconMenu->addAction(quitAction); - - trayIcon = new QSystemTrayIcon(this); - trayIcon->setContextMenu(trayIconMenu); -} - -void MainWindow::setIcon() { - QIcon icon(":icons/mask"); - trayIcon->setIcon(icon); - setWindowIcon(icon); - - trayIcon->setToolTip(QApplication::translate("MainWindow", "i2pd", 0)); -} - -void MainWindow::iconActivated(QSystemTrayIcon::ActivationReason reason) { - switch (reason) { - case QSystemTrayIcon::Trigger: - case QSystemTrayIcon::DoubleClick: - case QSystemTrayIcon::MiddleClick: - setVisible(!isVisible()); - break; - default: - qDebug() << "MainWindow::iconActivated(): unknown reason: " << reason << endl; - break; - } -} - -void MainWindow::closeEvent(QCloseEvent *event) { - if(quitting){ QMainWindow::closeEvent(event); return; } - if (trayIcon->isVisible()) { - QMessageBox::information(this, tr("i2pd"), - tr("The program will keep running in the " - "system tray. To gracefully terminate the program, " - "choose Graceful Quit at the main i2pd window.")); - hide(); - event->ignore(); - } -} -#endif - -void MainWindow::handleQuitButton() { - qDebug("Quit pressed. Hiding the main window"); -#ifndef ANDROID - quitting=true; -#endif - close(); - delayedSaveManagerPtr->appExiting(); - qDebug("Performing quit"); - QApplication::instance()->quit(); -} - -void MainWindow::handleGracefulQuitButton() { - qDebug("Graceful Quit pressed."); - ui->gracefulQuitPushButton->setText(QApplication::translate("MainWindow", "Graceful quit is in progress", 0)); - ui->gracefulQuitPushButton->setEnabled(false); - ui->gracefulQuitPushButton->adjustSize(); - ui->quitPage->adjustSize(); - i2p::context.SetAcceptsTunnels (false); // stop accpting tunnels - QTimer::singleShot(10*60*1000//millis - , this, SLOT(handleGracefulQuitTimerEvent())); -} - -void MainWindow::handleDoRestartButton() { - qDebug()<<"Do Restart pressed."; - emit i2pController->restartDaemon(); -} - - -void MainWindow::handleGracefulQuitTimerEvent() { - qDebug("Hiding the main window"); -#ifndef ANDROID - quitting=true; -#endif - close(); - delayedSaveManagerPtr->appExiting(); - qDebug("Performing quit"); - QApplication::instance()->quit(); -} - -MainWindow::~MainWindow() -{ - qDebug("Destroying main window"); - delete statusPageUpdateTimer; - delete delayedSaveManagerPtr; - delete saverPtr; - for(QList::iterator it = configItems.begin(); it!= configItems.end(); ++it) { - MainWindowItem* item = *it; - item->deleteLater(); - } - configItems.clear(); - //QMessageBox::information(0, "Debug", "mw destructor 1"); - //delete ui; - //QMessageBox::information(0, "Debug", "mw destructor 2"); -} - -FileChooserItem* MainWindow::initFileChooser(ConfigOption option, QLineEdit* fileNameLineEdit, QPushButton* fileBrowsePushButton){ - FileChooserItem* retVal; - retVal=new FileChooserItem(option, fileNameLineEdit, fileBrowsePushButton); - MainWindowItem* super=retVal; - configItems.append(super); - return retVal; -} -void MainWindow::initFolderChooser(ConfigOption option, QLineEdit* folderLineEdit, QPushButton* folderBrowsePushButton){ - configItems.append(new FolderChooserItem(option, folderLineEdit, folderBrowsePushButton)); -} -/*void MainWindow::initCombobox(ConfigOption option, QComboBox* comboBox){ - configItems.append(new ComboBoxItem(option, comboBox)); - QObject::connect(comboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(saveAllConfigs())); -}*/ -void MainWindow::initLogDestinationCombobox(ConfigOption option, QComboBox* comboBox){ - configItems.append(new LogDestinationComboBoxItem(option, comboBox)); -} -void MainWindow::initLogLevelCombobox(ConfigOption option, QComboBox* comboBox){ - configItems.append(new LogLevelComboBoxItem(option, comboBox)); -} -void MainWindow::initSignatureTypeCombobox(ConfigOption option, QComboBox* comboBox){ - configItems.append(new SignatureTypeComboBoxItem(option, comboBox)); -} -void MainWindow::initIPAddressBox(ConfigOption option, QLineEdit* addressLineEdit, QString fieldNameTranslated){ - configItems.append(new IPAddressStringItem(option, addressLineEdit, fieldNameTranslated)); -} -void MainWindow::initTCPPortBox(ConfigOption option, QLineEdit* portLineEdit, QString fieldNameTranslated){ - configItems.append(new TCPPortStringItem(option, portLineEdit, fieldNameTranslated)); -} -void MainWindow::initCheckBox(ConfigOption option, QCheckBox* checkBox) { - configItems.append(new CheckBoxItem(option, checkBox)); -} -void MainWindow::initIntegerBox(ConfigOption option, QLineEdit* numberLineEdit, QString fieldNameTranslated){ - configItems.append(new IntegerStringItem(option, numberLineEdit, fieldNameTranslated)); -} -void MainWindow::initUInt32Box(ConfigOption option, QLineEdit* numberLineEdit, QString fieldNameTranslated){ - configItems.append(new UInt32StringItem(option, numberLineEdit, fieldNameTranslated)); -} -void MainWindow::initUInt16Box(ConfigOption option, QLineEdit* numberLineEdit, QString fieldNameTranslated){ - configItems.append(new UInt16StringItem(option, numberLineEdit, fieldNameTranslated)); -} -void MainWindow::initStringBox(ConfigOption option, QLineEdit* lineEdit){ - configItems.append(new BaseStringItem(option, lineEdit, QString())); -} -NonGUIOptionItem* MainWindow::initNonGUIOption(ConfigOption option) { - NonGUIOptionItem * retValue; - configItems.append(retValue=new NonGUIOptionItem(option)); - return retValue; -} - -void MainWindow::loadAllConfigs(SaverImpl* saverPtr){ - - //BORROWED FROM ??? //TODO move this code into single location - std::string config; i2p::config::GetOption("conf", config); - std::string datadir; i2p::config::GetOption("datadir", datadir); - bool service = false; -#ifndef _WIN32 - i2p::config::GetOption("service", service); -#endif - i2p::fs::DetectDataDir(datadir, service); - i2p::fs::Init(); - - datadir = i2p::fs::GetDataDir(); - // TODO: drop old name detection in v2.8.0 - if (config == "") - { - config = i2p::fs::DataDirPath("i2p.conf"); - if (i2p::fs::Exists (config)) { - LogPrint(eLogWarning, "Daemon: please rename i2p.conf to i2pd.conf here: ", config); - } else { - config = i2p::fs::DataDirPath("i2pd.conf"); - /*if (!i2p::fs::Exists (config)) {}*/ - } - } - - //BORROWED FROM ClientContext.cpp //TODO move this code into single location - std::string tunConf; i2p::config::GetOption("tunconf", tunConf); - if (tunConf == "") { - // TODO: cleanup this in 2.8.0 - tunConf = i2p::fs::DataDirPath ("tunnels.cfg"); - if (i2p::fs::Exists(tunConf)) { - LogPrint(eLogWarning, "FS: please rename tunnels.cfg -> tunnels.conf here: ", tunConf); - } else { - tunConf = i2p::fs::DataDirPath ("tunnels.conf"); - } - } - - this->confpath = config.c_str(); - this->datadir = datadir.c_str(); - this->tunconfpath = tunConf.c_str(); - - saverPtr->setConfPath(this->confpath); - saverPtr->setTunnelsConfPath(this->tunconfpath); - - for(QList::iterator it = configItems.begin(); it!= configItems.end(); ++it) { - MainWindowItem* item = *it; - item->loadFromConfigOption(); - } - - ReadTunnelsConfig(); - - //onLoggingOptionsChange(); -} - -void MainWindow::layoutTunnels() { - - int height=0; - ui->tunnelsScrollAreaWidgetContents->setGeometry(0,0,0,0); - for(std::map::iterator it = tunnelConfigs.begin(); it != tunnelConfigs.end(); ++it) { - const std::string& name=it->first; - TunnelConfig* tunconf = it->second; - TunnelPane * tunnelPane=tunconf->getTunnelPane(); - if(!tunnelPane)continue; - int h=tunnelPane->height(); - height+=h; - //qDebug() << "tun.height:" << height << "sz:" << tunnelPanes.size(); - //int h=tunnelPane->appendClientTunnelForm(ctc, ui->tunnelsScrollAreaWidgetContents, tunnelPanes.size(), height); - } - //qDebug() << "tun.setting height:" << height; - ui->tunnelsScrollAreaWidgetContents->setGeometry(QRect(0, 0, 621, height)); - /*QList childWidgets = ui->tunnelsScrollAreaWidgetContents->findChildren(); - foreach(QWidget* widget, childWidgets) - widget->show();*/ -} - -void MainWindow::deleteTunnelFromUI(std::string tunnelName, TunnelConfig* cnf) { - TunnelPane* tp = cnf->getTunnelPane(); - if(!tp)return; - tunnelPanes.remove(tp); - tp->deleteWidget(); - layoutTunnels(); -} - -/** returns false iff not valid items present and save was aborted */ -bool MainWindow::saveAllConfigs(bool focusOnTunnel, std::string tunnelNameToFocus){ - QString cannotSaveSettings = QApplication::tr("Cannot save settings."); - programOptionsWriterCurrentSection=""; - /*if(!logFileNameOption->lineEdit->text().trimmed().isEmpty())logOption->optionValue=boost::any(std::string("file")); - else logOption->optionValue=boost::any(std::string("stdout"));*/ - daemonOption->optionValue=boost::any(false); - serviceOption->optionValue=boost::any(false); - - for(QList::iterator it = configItems.begin(); it!= configItems.end(); ++it) { - MainWindowItem* item = *it; - if(!item->isValid()){ - highlightWrongInput(QApplication::tr("Invalid value for")+" "+item->getConfigOption().section+"::"+item->getConfigOption().option+". "+item->getRequirementToBeValid()+" "+cannotSaveSettings, item->getWidgetToFocus()); - return false; - } - } - delayedSaveManagerPtr->delayedSave(++dataSerial, focusOnTunnel, tunnelNameToFocus); - - //onLoggingOptionsChange(); - return true; -} - -void FileChooserItem::pushButtonReleased() { - QString fileName = lineEdit->text().trimmed(); - fileName = QFileDialog::getOpenFileName(nullptr, tr("Open File"), fileName, tr("All Files (*.*)")); - if(fileName.length()>0)lineEdit->setText(fileName); -} -void FolderChooserItem::pushButtonReleased() { - QString fileName = lineEdit->text().trimmed(); - fileName = QFileDialog::getExistingDirectory(nullptr, tr("Open Folder"), fileName); - if(fileName.length()>0)lineEdit->setText(fileName); -} - -void BaseStringItem::installListeners(MainWindow *mainWindow) { - QObject::connect(lineEdit, SIGNAL(textChanged(const QString &)), mainWindow, SLOT(updated())); -} -void ComboBoxItem::installListeners(MainWindow *mainWindow) { - QObject::connect(comboBox, SIGNAL(currentIndexChanged(int)), mainWindow, SLOT(updated())); -} -void CheckBoxItem::installListeners(MainWindow *mainWindow) { - QObject::connect(checkBox, SIGNAL(stateChanged(int)), mainWindow, SLOT(updated())); -} - -void MainWindow::updated() { - ui->wrongInputLabel->setVisible(false); - adjustSizesAccordingToWrongLabel(); - - applyTunnelsUiToConfigs(); - saveAllConfigs(false); -} - -void MainWindowItem::installListeners(MainWindow *mainWindow) {} - -void MainWindow::appendTunnelForms(std::string tunnelNameToFocus) { - int height=0; - ui->tunnelsScrollAreaWidgetContents->setGeometry(0,0,0,0); - for(std::map::iterator it = tunnelConfigs.begin(); it != tunnelConfigs.end(); ++it) { - const std::string& name=it->first; - TunnelConfig* tunconf = it->second; - ServerTunnelConfig* stc = tunconf->asServerTunnelConfig(); - if(stc){ - ServerTunnelPane * tunnelPane=new ServerTunnelPane(&tunnelsPageUpdateListener, stc, ui->wrongInputLabel, ui->wrongInputLabel, this); - tunconf->setTunnelPane(tunnelPane); - int h=tunnelPane->appendServerTunnelForm(stc, ui->tunnelsScrollAreaWidgetContents, tunnelPanes.size(), height); - height+=h; - //qDebug() << "tun.height:" << height << "sz:" << tunnelPanes.size(); - tunnelPanes.push_back(tunnelPane); - if(name==tunnelNameToFocus){ - tunnelPane->getNameLineEdit()->setFocus(); - ui->tunnelsScrollArea->ensureWidgetVisible(tunnelPane->getNameLineEdit()); - } - continue; - } - ClientTunnelConfig* ctc = tunconf->asClientTunnelConfig(); - if(ctc){ - ClientTunnelPane * tunnelPane=new ClientTunnelPane(&tunnelsPageUpdateListener, ctc, ui->wrongInputLabel, ui->wrongInputLabel, this); - tunconf->setTunnelPane(tunnelPane); - int h=tunnelPane->appendClientTunnelForm(ctc, ui->tunnelsScrollAreaWidgetContents, tunnelPanes.size(), height); - height+=h; - //qDebug() << "tun.height:" << height << "sz:" << tunnelPanes.size(); - tunnelPanes.push_back(tunnelPane); - if(name==tunnelNameToFocus){ - tunnelPane->getNameLineEdit()->setFocus(); - ui->tunnelsScrollArea->ensureWidgetVisible(tunnelPane->getNameLineEdit()); - } - continue; - } - throw "unknown TunnelConfig subtype"; - } - //qDebug() << "tun.setting height:" << height; - ui->tunnelsScrollAreaWidgetContents->setGeometry(QRect(0, 0, 621, height)); - QList childWidgets = ui->tunnelsScrollAreaWidgetContents->findChildren(); - foreach(QWidget* widget, childWidgets) - widget->show(); -} -void MainWindow::deleteTunnelForms() { - for(std::list::iterator it = tunnelPanes.begin(); it != tunnelPanes.end(); ++it) { - TunnelPane* tp = *it; - ServerTunnelPane* stp = tp->asServerTunnelPane(); - if(stp){ - stp->deleteServerTunnelForm(); - delete stp; - continue; - } - ClientTunnelPane* ctp = tp->asClientTunnelPane(); - if(ctp){ - ctp->deleteClientTunnelForm(); - delete ctp; - continue; - } - throw "unknown TunnelPane subtype"; - } - tunnelPanes.clear(); -} - -bool MainWindow::applyTunnelsUiToConfigs() { - for(std::list::iterator it = tunnelPanes.begin(); it != tunnelPanes.end(); ++it) { - TunnelPane* tp = *it; - if(!tp->applyDataFromUIToTunnelConfig())return false; - } - return true; -} - -void MainWindow::reloadTunnelsConfigAndUI_QString(const QString tunnelNameToFocus) { - reloadTunnelsConfigAndUI(tunnelNameToFocus.toStdString()); -} - -void MainWindow::reloadTunnelsConfigAndUI(std::string tunnelNameToFocus) { - deleteTunnelForms(); - for (std::map::iterator it=tunnelConfigs.begin(); it!=tunnelConfigs.end(); ++it) { - TunnelConfig* tunconf = it->second; - delete tunconf; - } - tunnelConfigs.clear(); - ReadTunnelsConfig(); - appendTunnelForms(tunnelNameToFocus); -} - -void MainWindow::TunnelsPageUpdateListenerMainWindowImpl::updated(std::string oldName, TunnelConfig* tunConf) { - if(oldName!=tunConf->getName()) { - //name has changed - std::map::const_iterator it=mainWindow->tunnelConfigs.find(oldName); - if(it!=mainWindow->tunnelConfigs.end())mainWindow->tunnelConfigs.erase(it); - mainWindow->tunnelConfigs[tunConf->getName()]=tunConf; - } - mainWindow->saveAllConfigs(true, tunConf->getName()); -} - -void MainWindow::TunnelsPageUpdateListenerMainWindowImpl::needsDeleting(std::string oldName){ - mainWindow->DeleteTunnelNamed(oldName); -} - -void MainWindow::addServerTunnelPushButtonReleased() { - CreateDefaultServerTunnel(); -} - -void MainWindow::addClientTunnelPushButtonReleased() { - CreateDefaultClientTunnel(); -} - -void MainWindow::setI2PController(i2p::qt::Controller* controller_) { - this->i2pController = controller_; -} - -void MainWindow::runPeerTest() { - i2p::transport::transports.PeerTest(); -} - -void MainWindow::enableTransit() { - i2p::context.SetAcceptsTunnels(true); - updateRouterCommandsButtons(); -} - -void MainWindow::disableTransit() { - i2p::context.SetAcceptsTunnels(false); - updateRouterCommandsButtons(); -} - -void MainWindow::anchorClickedHandler(const QUrl & link) { - QString debugStr=QString()+"anchorClicked: "+"\""+link.toString()+"\""; - qDebug()<show(); - textBrowser->hide(); - std::stringstream s; - std::string strstd = str.toStdString(); - i2p::http::ShowLocalDestination(s,strstd,0); - childTextBrowser->setHtml(QString::fromStdString(s.str())); - } -} - -void MainWindow::backClickedFromChild() { - showStatusPage(statusPage); -} - -void MainWindow::adjustSizesAccordingToWrongLabel() { - if(ui->wrongInputLabel->isVisible()) { - int dh = ui->wrongInputLabel->height()+ui->verticalLayout_7->layout()->spacing(); - ui->verticalLayout_7->invalidate(); - ui->wrongInputLabel->adjustSize(); - ui->stackedWidget->adjustSize(); - ui->stackedWidget->setFixedHeight(531-dh); - ui->settingsPage->setFixedHeight(531-dh); - ui->verticalLayoutWidget_4->setGeometry(QRect(0, 0, 711, 531-dh)); - ui->stackedWidget->setFixedHeight(531-dh); - ui->settingsScrollArea->setFixedHeight(531-dh-settingsTitleLabelNominalHeight-ui->verticalLayout_4->spacing()); - ui->settingsTitleLabel->setFixedHeight(settingsTitleLabelNominalHeight); - ui->tunnelsScrollArea->setFixedHeight(531-dh-settingsTitleLabelNominalHeight-ui->horizontalLayout_42->geometry().height()-2*ui->verticalLayout_4->spacing()); - ui->tunnelsTitleLabel->setFixedHeight(settingsTitleLabelNominalHeight); - }else{ - ui->verticalLayout_7->invalidate(); - ui->wrongInputLabel->adjustSize(); - ui->stackedWidget->adjustSize(); - ui->stackedWidget->setFixedHeight(531); - ui->settingsPage->setFixedHeight(531); - ui->verticalLayoutWidget_4->setGeometry(QRect(0, 0, 711, 531)); - ui->stackedWidget->setFixedHeight(531); - ui->settingsScrollArea->setFixedHeight(531-settingsTitleLabelNominalHeight-ui->verticalLayout_4->spacing()); - ui->settingsTitleLabel->setFixedHeight(settingsTitleLabelNominalHeight); - ui->tunnelsScrollArea->setFixedHeight(531-settingsTitleLabelNominalHeight-ui->horizontalLayout_42->geometry().height()-2*ui->verticalLayout_4->spacing()); - ui->tunnelsTitleLabel->setFixedHeight(settingsTitleLabelNominalHeight); - } -} - -void MainWindow::highlightWrongInput(QString warningText, QWidget* widgetToFocus) { - bool redVisible = ui->wrongInputLabel->isVisible(); - ui->wrongInputLabel->setVisible(true); - ui->wrongInputLabel->setText(warningText); - if(!redVisible)adjustSizesAccordingToWrongLabel(); - if(widgetToFocus){ui->settingsScrollArea->ensureWidgetVisible(widgetToFocus);widgetToFocus->setFocus();} - showSettingsPage(); -} diff --git a/qt/i2pd_qt/mainwindow.h b/qt/i2pd_qt/mainwindow.h deleted file mode 100644 index 91c99122..00000000 --- a/qt/i2pd_qt/mainwindow.h +++ /dev/null @@ -1,811 +0,0 @@ -#ifndef MAINWINDOW_H -#define MAINWINDOW_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "QVBoxLayout" -#include "QUrl" - -#ifndef ANDROID -# include -# include -# include -#endif - -#include - -#include - -#include "MainWindowItems.h" -#include "TunnelPane.h" -#include "ServerTunnelPane.h" -#include "ClientTunnelPane.h" -#include "TunnelConfig.h" -#include "textbrowsertweaked1.h" - -#include "Config.h" -#include "FS.h" - -#include - -#include -#include - -#include "TunnelsPageUpdateListener.h" - -#include "DaemonQT.h" -#include "SignatureTypeComboboxFactory.h" -#include "pagewithbackbutton.h" - -#include - -#include "widgetlockregistry.h" -#include "widgetlock.h" - -#include "DelayedSaveManager.h" -#include "DelayedSaveManagerImpl.h" -#include "SaverImpl.h" - -class SaverImpl; - -class LogViewerManager; - -template -bool isType(boost::any& a) { - return -#ifdef BOOST_AUX_ANY_TYPE_ID_NAME - std::strcmp(a.type().name(), typeid(ValueType).name()) == 0 -#else - a.type() == typeid(ValueType) -#endif - ; -} - -class ConfigOption { -public: - QString section; - QString option; - //MainWindow::DefaultValueGetter defaultValueGetter; - ConfigOption(QString section_, QString option_/*, DefaultValueGetter defaultValueGetter_*/): - section(section_) - , option(option_) - //, defaultValueGetter(defaultValueGetter_) - {} - -}; - -extern std::string programOptionsWriterCurrentSection; - -class MainWindow; - -class MainWindowItem : public QObject { - Q_OBJECT - ConfigOption option; - QWidget* widgetToFocus; - QString requirementToBeValid; -public: - MainWindowItem(ConfigOption option_, QWidget* widgetToFocus_, QString requirementToBeValid_) : option(option_), widgetToFocus(widgetToFocus_), requirementToBeValid(requirementToBeValid_) {} - QWidget* getWidgetToFocus(){return widgetToFocus;} - QString& getRequirementToBeValid() { return requirementToBeValid; } - ConfigOption& getConfigOption() { return option; } - boost::any optionValue; - virtual ~MainWindowItem(){} - virtual void installListeners(MainWindow *mainWindow); - virtual void loadFromConfigOption(){ - std::string optName=""; - if(!option.section.isEmpty())optName=option.section.toStdString()+std::string("."); - optName+=option.option.toStdString(); - //qDebug() << "loadFromConfigOption[" << optName.c_str() << "]"; - boost::any programOption; - i2p::config::GetOptionAsAny(optName, programOption); - optionValue=programOption.empty()?boost::any(std::string("")) - :boost::any_cast(programOption).value(); - } - virtual void saveToStringStream(std::stringstream& out){ - if(isType(optionValue)) { - std::string v = boost::any_cast(optionValue); - if(v.empty())return; - } - if(optionValue.empty())return; - std::string rtti = optionValue.type().name(); - std::string optName=""; - if(!option.section.isEmpty())optName=option.section.toStdString()+std::string("."); - optName+=option.option.toStdString(); - qDebug() << "Writing option" << optName.c_str() << "of type" << rtti.c_str(); - std::string sectionAsStdStr = option.section.toStdString(); - if(!option.section.isEmpty() && - sectionAsStdStr!=programOptionsWriterCurrentSection) { - out << "[" << sectionAsStdStr << "]\n"; - programOptionsWriterCurrentSection=sectionAsStdStr; - } - out << option.option.toStdString() << "="; - if(isType(optionValue)) { - out << boost::any_cast(optionValue); - }else if(isType(optionValue)) { - out << (boost::any_cast(optionValue) ? "true" : "false"); - }else if(isType(optionValue)) { - out << boost::any_cast(optionValue); - }else if(isType(optionValue)) { - out << boost::any_cast(optionValue); - }else if(isType(optionValue)) { - out << boost::any_cast(optionValue); - }else if(isType(optionValue)) { - out << boost::any_cast(optionValue); - }else out << boost::any_cast(optionValue); //let it throw - out << "\n\n"; - } - virtual bool isValid(){return true;} -}; -class NonGUIOptionItem : public MainWindowItem { -public: - NonGUIOptionItem(ConfigOption option_) : MainWindowItem(option_, nullptr, QString()) {}; - virtual ~NonGUIOptionItem(){} - virtual bool isValid() { return true; } -}; -class BaseStringItem : public MainWindowItem { - Q_OBJECT -public: - QLineEdit* lineEdit; - BaseStringItem(ConfigOption option_, QLineEdit* lineEdit_, QString requirementToBeValid_) : MainWindowItem(option_, lineEdit_, requirementToBeValid_), lineEdit(lineEdit_){}; - virtual ~BaseStringItem(){} - virtual void installListeners(MainWindow *mainWindow); - virtual QString toString(){ - return boost::any_cast(optionValue).c_str(); - } - virtual boost::any fromString(QString s){return boost::any(s.toStdString());} - virtual void loadFromConfigOption(){ - MainWindowItem::loadFromConfigOption(); - lineEdit->setText(toString()); - } - - virtual void saveToStringStream(std::stringstream& out){ - optionValue=fromString(lineEdit->text()); - MainWindowItem::saveToStringStream(out); - } - virtual bool isValid() { return true; } -}; -class FileOrFolderChooserItem : public BaseStringItem { -public: - QPushButton* browsePushButton; - FileOrFolderChooserItem(ConfigOption option_, QLineEdit* lineEdit_, QPushButton* browsePushButton_) : - BaseStringItem(option_, lineEdit_, QString()), browsePushButton(browsePushButton_) {} - virtual ~FileOrFolderChooserItem(){} -}; -class FileChooserItem : public FileOrFolderChooserItem { - Q_OBJECT -private slots: - void pushButtonReleased(); -public: - FileChooserItem(ConfigOption option_, QLineEdit* lineEdit_, QPushButton* browsePushButton_) : - FileOrFolderChooserItem(option_, lineEdit_, browsePushButton_) { - QObject::connect(browsePushButton, SIGNAL(released()), this, SLOT(pushButtonReleased())); - } -}; -class FolderChooserItem : public FileOrFolderChooserItem{ - Q_OBJECT -private slots: - void pushButtonReleased(); -public: - FolderChooserItem(ConfigOption option_, QLineEdit* lineEdit_, QPushButton* browsePushButton_) : - FileOrFolderChooserItem(option_, lineEdit_, browsePushButton_) { - QObject::connect(browsePushButton, SIGNAL(released()), this, SLOT(pushButtonReleased())); - } -}; -class ComboBoxItem : public MainWindowItem { -public: - QComboBox* comboBox; - ComboBoxItem(ConfigOption option_, QComboBox* comboBox_) : MainWindowItem(option_,comboBox_,QString()), comboBox(comboBox_){}; - virtual ~ComboBoxItem(){} - virtual void installListeners(MainWindow *mainWindow); - virtual void loadFromConfigOption()=0; - virtual void saveToStringStream(std::stringstream& out)=0; - virtual bool isValid() { return true; } -}; -class LogDestinationComboBoxItem : public ComboBoxItem { -public: - LogDestinationComboBoxItem(ConfigOption option_, QComboBox* comboBox_) : - ComboBoxItem(option_, comboBox_) {} - virtual ~LogDestinationComboBoxItem(){} - virtual void loadFromConfigOption(){ - MainWindowItem::loadFromConfigOption(); - const char * ld = boost::any_cast(optionValue).c_str(); - comboBox->setCurrentText(QString(ld)); - } - virtual void saveToStringStream(std::stringstream& out){ - std::string logDest = comboBox->currentText().toStdString(); - optionValue=logDest; - MainWindowItem::saveToStringStream(out); - } - virtual bool isValid() { return true; } - - Q_OBJECT -}; -class LogLevelComboBoxItem : public ComboBoxItem { -public: - LogLevelComboBoxItem(ConfigOption option_, QComboBox* comboBox_) : ComboBoxItem(option_, comboBox_) {}; - virtual ~LogLevelComboBoxItem(){} - virtual void loadFromConfigOption(){ - MainWindowItem::loadFromConfigOption(); - const char * ll = boost::any_cast(optionValue).c_str(); - comboBox->setCurrentText(QString(ll)); - } - virtual void saveToStringStream(std::stringstream& out){ - optionValue=comboBox->currentText().toStdString(); - MainWindowItem::saveToStringStream(out); - } - virtual bool isValid() { return true; } -}; -class SignatureTypeComboBoxItem : public ComboBoxItem { -public: - SignatureTypeComboBoxItem(ConfigOption option_, QComboBox* comboBox_) : ComboBoxItem(option_, comboBox_) {}; - virtual ~SignatureTypeComboBoxItem(){} - virtual void loadFromConfigOption(){ - MainWindowItem::loadFromConfigOption(); - while(comboBox->count()>0)comboBox->removeItem(0); - uint16_t selected = (uint16_t) boost::any_cast(optionValue); - SignatureTypeComboBoxFactory::fillComboBox(comboBox, selected); - } - virtual void saveToStringStream(std::stringstream& out){ - uint16_t selected = SignatureTypeComboBoxFactory::getSigType(comboBox->currentData()); - optionValue=(unsigned short)selected; - MainWindowItem::saveToStringStream(out); - } - virtual bool isValid() { return true; } -}; -class CheckBoxItem : public MainWindowItem { -public: - QCheckBox* checkBox; - CheckBoxItem(ConfigOption option_, QCheckBox* checkBox_) : MainWindowItem(option_,checkBox_,QString()), checkBox(checkBox_){}; - virtual ~CheckBoxItem(){} - virtual void installListeners(MainWindow *mainWindow); - virtual void loadFromConfigOption(){ - MainWindowItem::loadFromConfigOption(); - checkBox->setChecked(boost::any_cast(optionValue)); - } - virtual void saveToStringStream(std::stringstream& out){ - optionValue=checkBox->isChecked(); - MainWindowItem::saveToStringStream(out); - } - virtual bool isValid() { return true; } -}; -class BaseFormattedStringItem : public BaseStringItem { -public: - QString fieldNameTranslated; - BaseFormattedStringItem(ConfigOption option_, QLineEdit* lineEdit_, QString fieldNameTranslated_, QString requirementToBeValid_) : - BaseStringItem(option_, lineEdit_, requirementToBeValid_), fieldNameTranslated(fieldNameTranslated_) {}; - virtual ~BaseFormattedStringItem(){} - virtual bool isValid()=0; -}; -class IntegerStringItem : public BaseFormattedStringItem { -public: - IntegerStringItem(ConfigOption option_, QLineEdit* lineEdit_, QString fieldNameTranslated_) : - BaseFormattedStringItem(option_, lineEdit_, fieldNameTranslated_, QApplication::tr("Must be a valid integer.")) {}; - virtual ~IntegerStringItem(){} - virtual bool isValid(){ - auto str=lineEdit->text(); - bool ok; - str.toInt(&ok); - return ok; - } - virtual QString toString(){return QString::number(boost::any_cast(optionValue));} - virtual boost::any fromString(QString s){return boost::any(std::stoi(s.toStdString()));} -}; -class UShortStringItem : public BaseFormattedStringItem { -public: - UShortStringItem(ConfigOption option_, QLineEdit* lineEdit_, QString fieldNameTranslated_) : - BaseFormattedStringItem(option_, lineEdit_, fieldNameTranslated_, QApplication::tr("Must be unsigned short integer.")) {}; - virtual ~UShortStringItem(){} - virtual bool isValid(){ - auto str=lineEdit->text(); - bool ok; - str.toUShort(&ok); - return ok; - } - virtual QString toString(){return QString::number(boost::any_cast(optionValue));} - virtual boost::any fromString(QString s){return boost::any((unsigned short)std::stoi(s.toStdString()));} -}; -class UInt32StringItem : public BaseFormattedStringItem { -public: - UInt32StringItem(ConfigOption option_, QLineEdit* lineEdit_, QString fieldNameTranslated_) : - BaseFormattedStringItem(option_, lineEdit_, fieldNameTranslated_, QApplication::tr("Must be unsigned 32-bit integer.")) {}; - virtual ~UInt32StringItem(){} - virtual bool isValid(){ - auto str=lineEdit->text(); - bool ok; - str.toUInt(&ok); - return ok; - } - virtual QString toString(){return QString::number(boost::any_cast(optionValue));} - virtual boost::any fromString(QString s){return boost::any((uint32_t)std::stoi(s.toStdString()));} -}; -class UInt16StringItem : public BaseFormattedStringItem { -public: - UInt16StringItem(ConfigOption option_, QLineEdit* lineEdit_, QString fieldNameTranslated_) : - BaseFormattedStringItem(option_, lineEdit_, fieldNameTranslated_, QApplication::tr("Must be unsigned 16-bit integer.")) {}; - virtual ~UInt16StringItem(){} - virtual bool isValid(){ - auto str=lineEdit->text(); - bool ok; - str.toUShort(&ok); - return ok; - } - virtual QString toString(){return QString::number(boost::any_cast(optionValue));} - virtual boost::any fromString(QString s){return boost::any((uint16_t)std::stoi(s.toStdString()));} -}; -class IPAddressStringItem : public BaseFormattedStringItem { -public: - IPAddressStringItem(ConfigOption option_, QLineEdit* lineEdit_, QString fieldNameTranslated_) : - BaseFormattedStringItem(option_, lineEdit_, fieldNameTranslated_, QApplication::tr("Must be an IPv4 address")) {}; - virtual bool isValid(){return true;}//todo -}; -class TCPPortStringItem : public UShortStringItem { -public: - TCPPortStringItem(ConfigOption option_, QLineEdit* lineEdit_, QString fieldNameTranslated_) : - UShortStringItem(option_, lineEdit_, fieldNameTranslated_) {}; -}; - -namespace Ui { - class MainWindow; - class StatusButtonsForm; - class routerCommandsWidget; - class GeneralSettingsContentsForm; -} - -using namespace i2p::client; - -class TunnelPane; - -using namespace i2p::qt; - -class Controller; - -class DelayedSaveManagerImpl; - -class MainWindow : public QMainWindow { - Q_OBJECT -private: - std::shared_ptr logStream; - DelayedSaveManagerImpl* delayedSaveManagerPtr; - DelayedSaveManager::DATA_SERIAL_TYPE dataSerial; -public: - explicit MainWindow(std::shared_ptr logStream_, QWidget *parent=nullptr); - ~MainWindow(); - - void setI2PController(i2p::qt::Controller* controller_); - - void highlightWrongInput(QString warningText, QWidget* widgetToFocus); - - //typedef std::function DefaultValueGetter; - -//#ifndef ANDROID -// void setVisible(bool visible); -//#endif - -private: - enum StatusPage {main_page, commands, local_destinations, leasesets, tunnels, transit_tunnels, - transports, i2p_tunnels, sam_sessions}; -private slots: - void updated(); - - void handleQuitButton(); - void handleGracefulQuitButton(); - void handleDoRestartButton(); - void handleGracefulQuitTimerEvent(); -#ifndef ANDROID - void setIcon(); - void iconActivated(QSystemTrayIcon::ActivationReason reason); - void toggleVisibilitySlot(); -#endif - void scheduleStatusPageUpdates(); - void statusHtmlPageMouseReleased(); - void statusHtmlPageSelectionChanged(); - void updateStatusPage(); - - void showStatusMainPage(); - void showStatus_commands_Page(); - void runPeerTest(); - void enableTransit(); - void disableTransit(); -public slots: - void showStatus_local_destinations_Page(); - void showStatus_leasesets_Page(); - void showStatus_tunnels_Page(); - void showStatus_transit_tunnels_Page(); - void showStatus_transports_Page(); - void showStatus_i2p_tunnels_Page(); - void showStatus_sam_sessions_Page(); - - void showLogViewerPage(); - void showSettingsPage(); - void showTunnelsPage(); - void showRestartPage(); - void showQuitPage(); - -private: - StatusPage statusPage; - QTimer * statusPageUpdateTimer; - bool wasSelectingAtStatusMainPage; - bool showHiddenInfoStatusMainPage; - - LogViewerManager *logViewerManagerPtr; - - void showStatusPage(StatusPage newStatusPage); -#ifndef ANDROID - void createActions(); - void createTrayIcon(); - bool quitting; - QAction *toggleWindowVisibleAction; - QSystemTrayIcon *trayIcon; - QMenu *trayIconMenu; -#endif - -public: - Ui::MainWindow* ui; - Ui::StatusButtonsForm* statusButtonsUI; - Ui::routerCommandsWidget* routerCommandsUI; - Ui::GeneralSettingsContentsForm* uiSettings; - void adjustSizesAccordingToWrongLabel(); - bool applyTunnelsUiToConfigs(); -private: - int settingsTitleLabelNominalHeight; - TextBrowserTweaked1 * textBrowser; - QWidget * routerCommandsParent; - PageWithBackButton * pageWithBackButton; - TextBrowserTweaked1 * childTextBrowser; - - widgetlockregistry widgetlocks; - - i2p::qt::Controller* i2pController; - -protected: - - void updateRouterCommandsButtons(); - -#ifndef ANDROID - void closeEvent(QCloseEvent *event); -#endif - void resizeEvent(QResizeEvent* event); - void onResize(); - - void setStatusButtonsVisible(bool visible); - - QString getStatusPageHtml(bool showHiddenInfo); - - QList configItems; - NonGUIOptionItem* daemonOption; - NonGUIOptionItem* serviceOption; - //LogDestinationComboBoxItem* logOption; - FileChooserItem* logFileNameOption; - - FileChooserItem* initFileChooser(ConfigOption option, QLineEdit* fileNameLineEdit, QPushButton* fileBrowsePushButton); - void initFolderChooser(ConfigOption option, QLineEdit* folderLineEdit, QPushButton* folderBrowsePushButton); - //void initCombobox(ConfigOption option, QComboBox* comboBox); - void initLogDestinationCombobox(ConfigOption option, QComboBox* comboBox); - void initLogLevelCombobox(ConfigOption option, QComboBox* comboBox); - void initSignatureTypeCombobox(ConfigOption option, QComboBox* comboBox); - void initIPAddressBox(ConfigOption option, QLineEdit* addressLineEdit, QString fieldNameTranslated); - void initTCPPortBox(ConfigOption option, QLineEdit* portLineEdit, QString fieldNameTranslated); - void initCheckBox(ConfigOption option, QCheckBox* checkBox); - void initIntegerBox(ConfigOption option, QLineEdit* numberLineEdit, QString fieldNameTranslated); - void initUInt32Box(ConfigOption option, QLineEdit* numberLineEdit, QString fieldNameTranslated); - void initUInt16Box(ConfigOption option, QLineEdit* numberLineEdit, QString fieldNameTranslated); - void initStringBox(ConfigOption option, QLineEdit* lineEdit); - NonGUIOptionItem* initNonGUIOption(ConfigOption option); - - void loadAllConfigs(SaverImpl* saverPtr); - void layoutTunnels(); - -public slots: - /** returns false iff not valid items present and save was aborted */ - bool saveAllConfigs(bool focusOnTunnel, std::string tunnelNameToFocus=""); - void reloadTunnelsConfigAndUI(std::string tunnelNameToFocus); - - //focus none - void reloadTunnelsConfigAndUI() { reloadTunnelsConfigAndUI(""); } - void reloadTunnelsConfigAndUI_QString(const QString tunnelNameToFocus); - void addServerTunnelPushButtonReleased(); - void addClientTunnelPushButtonReleased(); - - void anchorClickedHandler(const QUrl & link); - void backClickedFromChild(); - - void logDestinationComboBoxValueChanged(const QString & text); - -private: - QString datadir; - QString confpath; - QString tunconfpath; - - std::map tunnelConfigs; - std::list tunnelPanes; - - void appendTunnelForms(std::string tunnelNameToFocus); - void deleteTunnelForms(); - void deleteTunnelFromUI(std::string tunnelName, TunnelConfig* cnf); - - template - std::string GetI2CPOption (const Section& section, const std::string& name, const Type& value) const - { - return section.second.get (boost::property_tree::ptree::path_type (name, '/'), std::to_string (value)); - } - - template - void ReadI2CPOptions (const Section& section, std::map& options, I2CPParameters& param - /*TODO fill param*/) const - { - std::string _INBOUND_TUNNEL_LENGTH = options[I2CP_PARAM_INBOUND_TUNNEL_LENGTH] = GetI2CPOption (section, I2CP_PARAM_INBOUND_TUNNEL_LENGTH, DEFAULT_INBOUND_TUNNEL_LENGTH); - param.setInbound_length(QString(_INBOUND_TUNNEL_LENGTH.c_str())); - std::string _OUTBOUND_TUNNEL_LENGTH = options[I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH] = GetI2CPOption (section, I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH, DEFAULT_OUTBOUND_TUNNEL_LENGTH); - param.setOutbound_length(QString(_OUTBOUND_TUNNEL_LENGTH.c_str())); - std::string _INBOUND_TUNNELS_QUANTITY = options[I2CP_PARAM_INBOUND_TUNNELS_QUANTITY] = GetI2CPOption (section, I2CP_PARAM_INBOUND_TUNNELS_QUANTITY, DEFAULT_INBOUND_TUNNELS_QUANTITY); - param.setInbound_quantity( QString(_INBOUND_TUNNELS_QUANTITY.c_str())); - std::string _OUTBOUND_TUNNELS_QUANTITY = options[I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY] = GetI2CPOption (section, I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY, DEFAULT_OUTBOUND_TUNNELS_QUANTITY); - param.setOutbound_quantity(QString(_OUTBOUND_TUNNELS_QUANTITY.c_str())); - std::string _TAGS_TO_SEND = options[I2CP_PARAM_TAGS_TO_SEND] = GetI2CPOption (section, I2CP_PARAM_TAGS_TO_SEND, DEFAULT_TAGS_TO_SEND); - param.setCrypto_tagsToSend(QString(_TAGS_TO_SEND.c_str())); - options[I2CP_PARAM_MIN_TUNNEL_LATENCY] = GetI2CPOption(section, I2CP_PARAM_MIN_TUNNEL_LATENCY, DEFAULT_MIN_TUNNEL_LATENCY);//TODO include into param - options[I2CP_PARAM_MAX_TUNNEL_LATENCY] = GetI2CPOption(section, I2CP_PARAM_MAX_TUNNEL_LATENCY, DEFAULT_MAX_TUNNEL_LATENCY);//TODO include into param - } - - void CreateDefaultI2CPOptions (I2CPParameters& param - /*TODO fill param*/) const - { - const int _INBOUND_TUNNEL_LENGTH = DEFAULT_INBOUND_TUNNEL_LENGTH; - param.setInbound_length(QString::number(_INBOUND_TUNNEL_LENGTH)); - const int _OUTBOUND_TUNNEL_LENGTH = DEFAULT_OUTBOUND_TUNNEL_LENGTH; - param.setOutbound_length(QString::number(_OUTBOUND_TUNNEL_LENGTH)); - const int _INBOUND_TUNNELS_QUANTITY = DEFAULT_INBOUND_TUNNELS_QUANTITY; - param.setInbound_quantity( QString::number(_INBOUND_TUNNELS_QUANTITY)); - const int _OUTBOUND_TUNNELS_QUANTITY = DEFAULT_OUTBOUND_TUNNELS_QUANTITY; - param.setOutbound_quantity(QString::number(_OUTBOUND_TUNNELS_QUANTITY)); - const int _TAGS_TO_SEND = DEFAULT_TAGS_TO_SEND; - param.setCrypto_tagsToSend(QString::number(_TAGS_TO_SEND)); - } - - - void DeleteTunnelNamed(std::string name) { - std::map::const_iterator it=tunnelConfigs.find(name); - if(it!=tunnelConfigs.end()){ - TunnelConfig* tc=it->second; - deleteTunnelFromUI(name, tc); - tunnelConfigs.erase(it); - delete tc; - } - saveAllConfigs(false); - } - - std::string GenerateNewTunnelName() { - int i=1; - while(true){ - std::stringstream name; - name << "name" << i; - const std::string& str=name.str(); - if(tunnelConfigs.find(str)==tunnelConfigs.end())return str; - ++i; - } - } - - void CreateDefaultClientTunnel() {//TODO dedup default values with ReadTunnelsConfig() and with ClientContext.cpp::ReadTunnels () - std::string name=GenerateNewTunnelName(); - std::string type = I2P_TUNNELS_SECTION_TYPE_CLIENT; - std::string dest = "127.0.0.1"; - int port = 0; - std::string keys = ""; - std::string address = "127.0.0.1"; - int destinationPort = 0; - i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256; - // I2CP - I2CPParameters i2cpParameters; - CreateDefaultI2CPOptions (i2cpParameters); - - tunnelConfigs[name]=new ClientTunnelConfig(name, QString(type.c_str()), i2cpParameters, - dest, - port, - keys, - address, - destinationPort, - sigType); - - saveAllConfigs(true, name); - } - - void CreateDefaultServerTunnel() {//TODO dedup default values with ReadTunnelsConfig() and with ClientContext.cpp::ReadTunnels () - std::string name=GenerateNewTunnelName(); - std::string type=I2P_TUNNELS_SECTION_TYPE_SERVER; - std::string host = "127.0.0.1"; - int port = 0; - std::string keys = ""; - int inPort = 0; - std::string accessList = ""; - std::string hostOverride = ""; - std::string webircpass = ""; - bool gzip = true; - i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256; - std::string address = "127.0.0.1"; - bool isUniqueLocal = true; - - // I2CP - I2CPParameters i2cpParameters; - CreateDefaultI2CPOptions (i2cpParameters); - - tunnelConfigs[name]=new ServerTunnelConfig(name, QString(type.c_str()), i2cpParameters, - host, - port, - keys, - inPort, - accessList, - hostOverride, - webircpass, - gzip, - sigType, - address, - isUniqueLocal); - - - saveAllConfigs(true, name); - } - - void ReadTunnelsConfig() //TODO deduplicate the code with ClientContext.cpp::ReadTunnels () - { - boost::property_tree::ptree pt; - std::string tunConf=tunconfpath.toStdString(); - if (tunConf == "") { - // TODO: cleanup this in 2.8.0 - tunConf = i2p::fs::DataDirPath ("tunnels.cfg"); - if (i2p::fs::Exists(tunConf)) { - LogPrint(eLogWarning, "FS: please rename tunnels.cfg -> tunnels.conf here: ", tunConf); - } else { - tunConf = i2p::fs::DataDirPath ("tunnels.conf"); - } - } - LogPrint(eLogDebug, "tunnels config file: ", tunConf); - try - { - boost::property_tree::read_ini (tunConf, pt); - } - catch (std::exception& ex) - { - LogPrint (eLogWarning, "Clients: Can't read ", tunConf, ": ", ex.what ());//TODO show err box and disable tunn.page - return; - } - - for (auto& section: pt) - { - std::string name = section.first; - try - { - std::string type = section.second.get (I2P_TUNNELS_SECTION_TYPE); - if (type == I2P_TUNNELS_SECTION_TYPE_CLIENT - || type == I2P_TUNNELS_SECTION_TYPE_SOCKS - || type == I2P_TUNNELS_SECTION_TYPE_WEBSOCKS - || type == I2P_TUNNELS_SECTION_TYPE_HTTPPROXY - || type == I2P_TUNNELS_SECTION_TYPE_UDPCLIENT) - { - // mandatory params - std::string dest; - if (type == I2P_TUNNELS_SECTION_TYPE_CLIENT || type == I2P_TUNNELS_SECTION_TYPE_UDPCLIENT) { - dest = section.second.get (I2P_CLIENT_TUNNEL_DESTINATION); - std::cout << "had read tunnel dest: " << dest << std::endl; - } - int port = section.second.get (I2P_CLIENT_TUNNEL_PORT); - std::cout << "had read tunnel port: " << port << std::endl; - // optional params - std::string keys = section.second.get (I2P_CLIENT_TUNNEL_KEYS, ""); - std::string address = section.second.get (I2P_CLIENT_TUNNEL_ADDRESS, "127.0.0.1"); - int destinationPort = section.second.get(I2P_CLIENT_TUNNEL_DESTINATION_PORT, 0); - std::cout << "had read tunnel destinationPort: " << destinationPort << std::endl; - i2p::data::SigningKeyType sigType = section.second.get (I2P_CLIENT_TUNNEL_SIGNATURE_TYPE, i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256); - // I2CP - std::map options; - I2CPParameters i2cpParameters; - ReadI2CPOptions (section, options, i2cpParameters); - - tunnelConfigs[name]=new ClientTunnelConfig(name, QString(type.c_str()), i2cpParameters, - dest, - port, - keys, - address, - destinationPort, - sigType); - } - else if (type == I2P_TUNNELS_SECTION_TYPE_SERVER - || type == I2P_TUNNELS_SECTION_TYPE_HTTP - || type == I2P_TUNNELS_SECTION_TYPE_IRC - || type == I2P_TUNNELS_SECTION_TYPE_UDPSERVER) - { - // mandatory params - std::string host = section.second.get (I2P_SERVER_TUNNEL_HOST); - int port = section.second.get (I2P_SERVER_TUNNEL_PORT); - // optional params - std::string keys = section.second.get (I2P_SERVER_TUNNEL_KEYS, ""); - int inPort = section.second.get (I2P_SERVER_TUNNEL_INPORT, 0); - std::string accessList = section.second.get (I2P_SERVER_TUNNEL_ACCESS_LIST, ""); - std::string hostOverride = section.second.get (I2P_SERVER_TUNNEL_HOST_OVERRIDE, ""); - std::string webircpass = section.second.get (I2P_SERVER_TUNNEL_WEBIRC_PASSWORD, ""); - bool gzip = section.second.get (I2P_SERVER_TUNNEL_GZIP, true); - i2p::data::SigningKeyType sigType = section.second.get (I2P_SERVER_TUNNEL_SIGNATURE_TYPE, i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256); - std::string address = section.second.get (I2P_SERVER_TUNNEL_ADDRESS, "127.0.0.1"); - bool isUniqueLocal = section.second.get(I2P_SERVER_TUNNEL_ENABLE_UNIQUE_LOCAL, true); - - // I2CP - std::map options; - I2CPParameters i2cpParameters; - ReadI2CPOptions (section, options, i2cpParameters); - - /* - std::set idents; - if (accessList.length () > 0) - { - size_t pos = 0, comma; - do - { - comma = accessList.find (',', pos); - i2p::data::IdentHash ident; - ident.FromBase32 (accessList.substr (pos, comma != std::string::npos ? comma - pos : std::string::npos)); - idents.insert (ident); - pos = comma + 1; - } - while (comma != std::string::npos); - } - */ - tunnelConfigs[name]=new ServerTunnelConfig(name, QString(type.c_str()), i2cpParameters, - host, - port, - keys, - inPort, - accessList, - hostOverride, - webircpass, - gzip, - sigType, - address, - isUniqueLocal); - } - else - LogPrint (eLogWarning, "Clients: Unknown section type=", type, " of ", name, " in ", tunConf);//TODO show err box and disable the tunn gui - - } - catch (std::exception& ex) - { - LogPrint (eLogError, "Clients: Can't read tunnel ", name, " params: ", ex.what ());//TODO show err box and disable the tunn gui - } - } - } - -private: - class TunnelsPageUpdateListenerMainWindowImpl : public TunnelsPageUpdateListener { - MainWindow* mainWindow; - public: - TunnelsPageUpdateListenerMainWindowImpl(MainWindow* mainWindow_):mainWindow(mainWindow_){} - virtual void updated(std::string oldName, TunnelConfig* tunConf); - virtual void needsDeleting(std::string oldName); - }; - - TunnelsPageUpdateListenerMainWindowImpl tunnelsPageUpdateListener; - - //void onLoggingOptionsChange() {} - - SaverImpl* saverPtr; -}; - -#endif // MAINWINDOW_H diff --git a/qt/i2pd_qt/mainwindow.ui b/qt/i2pd_qt/mainwindow.ui deleted file mode 100644 index dcdf88bd..00000000 --- a/qt/i2pd_qt/mainwindow.ui +++ /dev/null @@ -1,1026 +0,0 @@ - - - MainWindow - - - - 0 - 0 - 908 - 554 - - - - - 908 - 0 - - - - - 908 - 16777215 - - - - MainWindow - - - - - 0 - 0 - - - - - 908 - 550 - - - - - 908 - 550 - - - - - - 10 - 10 - 888 - 531 - - - - - QLayout::SetMaximumSize - - - - - QLayout::SetMinimumSize - - - - 0 - 0 - 170 - 496 - - - - - - true - - - Status - - - - - - - - 0 - 0 - - - - - 172 - 0 - - - - - - - - Log - - - - - - - true - - - General settings - - - - - - - true - - - Tunnels settings - - - - - - - true - - - Restart - - - - - - - true - - - Quit - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 171 - 0 - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - QLayout::SetMinAndMaxSize - - - - - - 0 - 30 - - - - - - - - - 0 - 0 - 0 - - - - - - - 255 - 0 - 0 - - - - - - - 255 - 127 - 127 - - - - - - - 255 - 63 - 63 - - - - - - - 127 - 0 - 0 - - - - - - - 170 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 255 - 255 - 255 - - - - - - - 0 - 0 - 0 - - - - - - - 255 - 255 - 255 - - - - - - - 255 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 255 - 127 - 127 - - - - - - - 255 - 255 - 220 - - - - - - - 0 - 0 - 0 - - - - - - - - - 0 - 0 - 0 - - - - - - - 255 - 0 - 0 - - - - - - - 255 - 127 - 127 - - - - - - - 255 - 63 - 63 - - - - - - - 127 - 0 - 0 - - - - - - - 170 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 255 - 255 - 255 - - - - - - - 0 - 0 - 0 - - - - - - - 255 - 255 - 255 - - - - - - - 255 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 255 - 127 - 127 - - - - - - - 255 - 255 - 220 - - - - - - - 0 - 0 - 0 - - - - - - - - - 127 - 0 - 0 - - - - - - - 255 - 0 - 0 - - - - - - - 255 - 127 - 127 - - - - - - - 255 - 63 - 63 - - - - - - - 127 - 0 - 0 - - - - - - - 170 - 0 - 0 - - - - - - - 127 - 0 - 0 - - - - - - - 255 - 255 - 255 - - - - - - - 127 - 0 - 0 - - - - - - - 255 - 0 - 0 - - - - - - - 255 - 0 - 0 - - - - - - - 0 - 0 - 0 - - - - - - - 255 - 0 - 0 - - - - - - - 255 - 255 - 220 - - - - - - - 0 - 0 - 0 - - - - - - - - wrongInputMessageLabel - - - true - - - 10 - - - - - - - - 0 - 0 - - - - - 0 - 0 - - - - - 713 - 713 - - - - 1 - - - - - 0 - 0 - - - - - - 0 - 0 - 713 - 531 - - - - - QLayout::SetMaximumSize - - - - - - 15 - - - - Status - - - - - - - QLayout::SetMaximumSize - - - - - - - - - - 0 - 0 - - - - - - 0 - 0 - 711 - 531 - - - - - QLayout::SetMinAndMaxSize - - - - - - 15 - - - - Log - - - - - - - - 0 - 0 - - - - Qt::ScrollBarAlwaysOn - - - Qt::ScrollBarAsNeeded - - - QAbstractScrollArea::AdjustIgnored - - - 10000 - - - false - - - true - - - - - - - - - - 0 - 0 - - - - - - 0 - 0 - 711 - 531 - - - - - QLayout::SetMinAndMaxSize - - - - - - 15 - - - - General settings - - - - - - - - 0 - 0 - - - - Qt::ScrollBarAlwaysOn - - - Qt::ScrollBarAsNeeded - - - QAbstractScrollArea::AdjustIgnored - - - true - - - - - 0 - 0 - 81 - 28 - - - - - 0 - 0 - - - - - - - - - - - - - 0 - 0 - 711 - 531 - - - - - QLayout::SetMinAndMaxSize - - - - - - 15 - - - - Tunnels settings - - - - - - - - - Add Client Tunnel - - - - - - - Add Server Tunnel - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - Qt::ScrollBarAlwaysOn - - - false - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - - - 0 - 0 - 699 - 425 - - - - - - - - - - - - - 0 - 0 - 711 - 531 - - - - - QLayout::SetMinAndMaxSize - - - - - - 15 - - - - Restart - - - - - - - Restart i2pd - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - 0 - 0 - - - - - - 0 - 0 - 711 - 531 - - - - - QLayout::SetMinAndMaxSize - - - - - - 15 - - - - Quit - - - - - - - Quit Now - - - - - - - Graceful Quit - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - - - - - - - - - handleQuitButton() - handleGracefulQuitButton() - - diff --git a/qt/i2pd_qt/pagewithbackbutton.cpp b/qt/i2pd_qt/pagewithbackbutton.cpp deleted file mode 100644 index bc297ac2..00000000 --- a/qt/i2pd_qt/pagewithbackbutton.cpp +++ /dev/null @@ -1,24 +0,0 @@ -#include "pagewithbackbutton.h" -#include "QVBoxLayout" -#include "QHBoxLayout" -#include "QPushButton" - -PageWithBackButton::PageWithBackButton(QWidget *parent, QWidget* child) : QWidget(parent) -{ - QVBoxLayout * layout = new QVBoxLayout(); - setLayout(layout); - QWidget * topBar = new QWidget(); - QHBoxLayout * topBarLayout = new QHBoxLayout(); - topBar->setLayout(topBarLayout); - layout->addWidget(topBar); - layout->addWidget(child); - - QPushButton * backButton = new QPushButton(topBar); - backButton->setText("< Back"); - topBarLayout->addWidget(backButton); - connect(backButton, SIGNAL(released()), this, SLOT(backReleasedSlot())); -} - -void PageWithBackButton::backReleasedSlot() { - emit backReleased(); -} diff --git a/qt/i2pd_qt/pagewithbackbutton.h b/qt/i2pd_qt/pagewithbackbutton.h deleted file mode 100644 index 60779f80..00000000 --- a/qt/i2pd_qt/pagewithbackbutton.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef PAGEWITHBACKBUTTON_H -#define PAGEWITHBACKBUTTON_H - -#include - -class PageWithBackButton : public QWidget -{ - Q_OBJECT -public: - explicit PageWithBackButton(QWidget *parent, QWidget* child); - -signals: - - void backReleased(); - -private slots: - - void backReleasedSlot(); -}; - -#endif // PAGEWITHBACKBUTTON_H diff --git a/qt/i2pd_qt/resources/icons/mask.ico b/qt/i2pd_qt/resources/icons/mask.ico deleted file mode 100644 index f5807de5..00000000 Binary files a/qt/i2pd_qt/resources/icons/mask.ico and /dev/null differ diff --git a/qt/i2pd_qt/resources/images/icon.png b/qt/i2pd_qt/resources/images/icon.png deleted file mode 100644 index a5dc7b68..00000000 Binary files a/qt/i2pd_qt/resources/images/icon.png and /dev/null differ diff --git a/qt/i2pd_qt/routercommandswidget.ui b/qt/i2pd_qt/routercommandswidget.ui deleted file mode 100644 index c5098e8e..00000000 --- a/qt/i2pd_qt/routercommandswidget.ui +++ /dev/null @@ -1,127 +0,0 @@ - - - routerCommandsWidget - - - - 0 - 0 - 711 - 300 - - - - - 0 - 0 - - - - Form - - - - - 0 - 0 - 711 - 301 - - - - - QLayout::SetMaximumSize - - - - - - 0 - 0 - - - - - 75 - true - - - - Router Commands - - - - - - - - 0 - 0 - - - - Run peer test - - - - - - - - 0 - 0 - - - - Decline transit tunnels - - - - - - - - 0 - 0 - - - - Accept transit tunnels - - - - - - - false - - - - 0 - 0 - - - - Cancel graceful quit - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - diff --git a/qt/i2pd_qt/statusbuttons.ui b/qt/i2pd_qt/statusbuttons.ui deleted file mode 100644 index edf5a90c..00000000 --- a/qt/i2pd_qt/statusbuttons.ui +++ /dev/null @@ -1,163 +0,0 @@ - - - StatusButtonsForm - - - - 0 - 0 - 171 - 295 - - - - - 0 - 0 - - - - - 171 - 295 - - - - Form - - - - - 21 - 0 - 171 - 300 - - - - - QLayout::SetDefaultConstraint - - - - - - 150 - 16777215 - - - - Main page - - - - - - - - 150 - 16777215 - - - - Router commands - - - - - - - - 150 - 16777215 - - - - Local destinations - - - - - - - - 150 - 16777215 - - - - Leasesets - - - - - - - - 150 - 16777215 - - - - Tunnels - - - - - - - - 150 - 16777215 - - - - Transit tunnels - - - - - - - - 150 - 16777215 - - - - Transports - - - - - - - - 150 - 16777215 - - - - I2P tunnels - - - - - - - - 150 - 16777215 - - - - SAM sessions - - - - - - - - - diff --git a/qt/i2pd_qt/textbrowsertweaked1.cpp b/qt/i2pd_qt/textbrowsertweaked1.cpp deleted file mode 100644 index f8802061..00000000 --- a/qt/i2pd_qt/textbrowsertweaked1.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#include "textbrowsertweaked1.h" - -TextBrowserTweaked1::TextBrowserTweaked1(QWidget * parent): QTextBrowser(parent) -{ -} - -/*void TextBrowserTweaked1::setSource(const QUrl & url) { - emit navigatedTo(url); -}*/ diff --git a/qt/i2pd_qt/textbrowsertweaked1.h b/qt/i2pd_qt/textbrowsertweaked1.h deleted file mode 100644 index 288a3c32..00000000 --- a/qt/i2pd_qt/textbrowsertweaked1.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef TEXTBROWSERTWEAKED1_H -#define TEXTBROWSERTWEAKED1_H - -#include -#include - -class TextBrowserTweaked1 : public QTextBrowser -{ - Q_OBJECT - -public: - TextBrowserTweaked1(QWidget * parent); - //virtual void setSource(const QUrl & url); - -signals: - void mouseReleased(); - //void navigatedTo(const QUrl & link); - -protected: - void mouseReleaseEvent(QMouseEvent *event) { - QTextBrowser::mouseReleaseEvent(event); - emit mouseReleased(); - } -}; - -#endif // TEXTBROWSERTWEAKED1_H diff --git a/qt/i2pd_qt/tunnelform.ui b/qt/i2pd_qt/tunnelform.ui deleted file mode 100644 index 61e54770..00000000 --- a/qt/i2pd_qt/tunnelform.ui +++ /dev/null @@ -1,104 +0,0 @@ - - - Form - - - - 0 - 0 - 527 - 452 - - - - Form - - - - - 0 - 0 - 521 - 451 - - - - - - - server_tunnel_name - - - - - 0 - 20 - 511 - 421 - - - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Delete - - - - - - - - - - - Host: - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - - - - diff --git a/qt/i2pd_qt/widgetlock.cpp b/qt/i2pd_qt/widgetlock.cpp deleted file mode 100644 index a601fb36..00000000 --- a/qt/i2pd_qt/widgetlock.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "widgetlock.h" diff --git a/qt/i2pd_qt/widgetlock.h b/qt/i2pd_qt/widgetlock.h deleted file mode 100644 index 5b21125c..00000000 --- a/qt/i2pd_qt/widgetlock.h +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef WIDGETLOCK_H -#define WIDGETLOCK_H - -#include -#include -#include -#include - -class widgetlock : public QObject { - Q_OBJECT - -private: - QWidget* widget; - QPushButton* lockButton; - -public slots: - void lockButtonClicked(bool) { - bool wasEnabled = widget->isEnabled(); - widget->setEnabled(!wasEnabled); - lockButton->setText(widget->isEnabled()?lockButton->tr("Lock"):lockButton->tr("Edit")); - } - -public: - widgetlock(QWidget* widget_, QPushButton* lockButton_): widget(widget_),lockButton(lockButton_) { - widget->setEnabled(false); - lockButton->setText(lockButton->tr("Edit")); - QObject::connect(lockButton,SIGNAL(clicked(bool)), this, SLOT(lockButtonClicked(bool))); - } - virtual ~widgetlock() {} - void deleteListener() { - QObject::disconnect(lockButton,SIGNAL(clicked(bool)), this, SLOT(lockButtonClicked(bool))); - } -}; - -#endif // WIDGETLOCK_H diff --git a/qt/i2pd_qt/widgetlockregistry.cpp b/qt/i2pd_qt/widgetlockregistry.cpp deleted file mode 100644 index 0d66d327..00000000 --- a/qt/i2pd_qt/widgetlockregistry.cpp +++ /dev/null @@ -1,2 +0,0 @@ -#include "widgetlockregistry.h" - diff --git a/qt/i2pd_qt/widgetlockregistry.h b/qt/i2pd_qt/widgetlockregistry.h deleted file mode 100644 index 78ee142a..00000000 --- a/qt/i2pd_qt/widgetlockregistry.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef WIDGETLOCKREGISTRY_H -#define WIDGETLOCKREGISTRY_H - -#include -#include - -class widgetlockregistry { - std::vector locks; - -public: - widgetlockregistry() : locks() {} - virtual ~widgetlockregistry() {} - void add(widgetlock* lock) { - locks.push_back(lock); - } - - void deleteListeners() { - while(!locks.empty()) { - widgetlock* lock = locks.back(); - lock->deleteListener(); - delete lock; - locks.pop_back(); - } - } -}; - -#endif // WIDGETLOCKREGISTRY_H diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 00000000..90457c23 --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1,12 @@ +/test-http-merge_chunked +/test-http-req +/test-http-res +/test-http-url +/test-http-url_decode +/test-gost +/test-gost-sig +/test-base-64 +/test-x25519 +/test-aeadchacha20poly1305 +/test-blinding +/test-elligator diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 00000000..21daadd9 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,116 @@ +enable_testing() +find_package(Check 0.9.10 REQUIRED) +include_directories(${CHECK_INCLUDE_DIRS}) + +# Compiler flags: +if(APPLE) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -g -Wl,-undefined,dynamic_lookup") +else() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -g -Wl,--unresolved-symbols=ignore-in-object-files") +endif() + +set(TEST_PATH ${CMAKE_CURRENT_BINARY_DIR}) + +include_directories( + ../libi2pd + ${Boost_INCLUDE_DIRS} + ${OPENSSL_INCLUDE_DIR} +) + +set(test-http-merge_chunked_SRCS + test-http-merge_chunked.cpp +) + +set(test-http-req_SRCS + test-http-req.cpp +) + +set(test-http-res_SRCS + test-http-res.cpp +) + +set(test-http-url_decode_SRCS + test-http-url_decode.cpp +) + +set(test-http-url_SRCS + test-http-url.cpp +) + +set(test-base-64_SRCS + test-base-64.cpp +) + +set(test-gost_SRCS + test-gost.cpp +) + +set(test-gost-sig_SRCS + test-gost-sig.cpp +) + +set(test-x25519_SRCS + test-x25519.cpp +) + +set(test-aeadchacha20poly1305_SRCS + test-aeadchacha20poly1305.cpp +) + +set(test-blinding_SRCS + test-blinding.cpp +) + +SET(test-elligator_SRCS + test-elligator.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-x25519 ${test-x25519_SRCS}) +add_executable(test-aeadchacha20poly1305 ${test-aeadchacha20poly1305_SRCS}) +add_executable(test-blinding ${test-blinding_SRCS}) +add_executable(test-elligator ${test-elligator_SRCS}) + +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-x25519 ${LIBS}) +target_link_libraries(test-aeadchacha20poly1305 ${LIBS}) +target_link_libraries(test-blinding ${LIBS}) +target_link_libraries(test-elligator ${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-x25519 ${TEST_PATH}/test-x25519) +add_test(test-aeadchacha20poly1305 ${TEST_PATH}/test-aeadchacha20poly1305) +add_test(test-blinding ${TEST_PATH}/test-blinding) +add_test(test-elligator ${TEST_PATH}/test-elligator) diff --git a/tests/Makefile b/tests/Makefile index 4c80c37c..9c5711e2 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -1,35 +1,62 @@ -CXXFLAGS += -Wall -Wextra -pedantic -O0 -g -std=c++11 -D_GLIBCXX_USE_NANOSLEEP=1 -I../libi2pd/ -pthread -Wl,--unresolved-symbols=ignore-in-object-files +SYS := $(shell $(CXX) -dumpmachine) + +CXXFLAGS += -Wall -Wno-unused-parameter -Wextra -pedantic -O0 -g -std=c++11 -D_GLIBCXX_USE_NANOSLEEP=1 -DOPENSSL_SUPPRESS_DEPRECATED -pthread -Wl,--unresolved-symbols=ignore-in-object-files +INCFLAGS += -I../libi2pd + +LIBI2PD = ../libi2pd.a + +TESTS = \ + test-http-merge_chunked test-http-req test-http-res test-http-url test-http-url_decode \ + test-gost test-gost-sig test-base-64 test-x25519 test-aeadchacha20poly1305 test-blinding test-elligator + +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_filesystem$(BOOST_SUFFIX) \ + -lboost_program_options$(BOOST_SUFFIX) \ + -lssl \ + -lcrypto \ + -lz \ + $(NEEDED_LDLIBS) \ + -lpthread -TESTS = test-gost test-gost-sig test-base-64 test-x25519 test-aeadchacha20poly1305 test-blinding test-elligator all: $(TESTS) run -test-http-%: ../libi2pd/HTTP.cpp test-http-%.cpp - $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -o $@ $^ +$(LIBI2PD): + @echo "Building libi2pd.a ..." && cd .. && $(MAKE) libi2pd.a -test-base-%: ../libi2pd/Base.cpp test-base-%.cpp - $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -o $@ $^ +test-http-%: test-http-%.cpp $(LIBI2PD) + $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS) -test-gost: ../libi2pd/Gost.cpp ../libi2pd/I2PEndian.cpp test-gost.cpp - $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -o $@ $^ -lcrypto +test-base-%: test-base-%.cpp $(LIBI2PD) + $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS) -test-gost-sig: ../libi2pd/Gost.cpp ../libi2pd/I2PEndian.cpp ../libi2pd/Crypto.cpp ../libi2pd/Log.cpp test-gost-sig.cpp - $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -o $@ $^ -lcrypto -lssl -lboost_system +test-gost: test-gost.cpp $(LIBI2PD) + $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS) -test-x25519: ../libi2pd/Ed25519.cpp ../libi2pd/I2PEndian.cpp ../libi2pd/Log.cpp ../libi2pd/Crypto.cpp test-x25519.cpp - $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -o $@ $^ -lcrypto -lssl -lboost_system +test-gost-sig: test-gost-sig.cpp $(LIBI2PD) + $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS) -test-aeadchacha20poly1305: ../libi2pd/Crypto.cpp ../libi2pd/ChaCha20.cpp ../libi2pd/Poly1305.cpp test-aeadchacha20poly1305.cpp - $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -o $@ $^ -lcrypto -lssl -lboost_system +test-x25519: test-x25519.cpp $(LIBI2PD) + $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS) -test-blinding: ../libi2pd/Crypto.cpp ../libi2pd/Blinding.cpp ../libi2pd/Ed25519.cpp ../libi2pd/I2PEndian.cpp ../libi2pd/Log.cpp ../libi2pd/util.cpp ../libi2pd/Identity.cpp ../libi2pd/Signature.cpp ../libi2pd/Timestamp.cpp test-blinding.cpp - $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -o $@ $^ -lcrypto -lssl -lboost_system +test-aeadchacha20poly1305: test-aeadchacha20poly1305.cpp $(LIBI2PD) + $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS) -test-elligator: ../libi2pd/Elligator.cpp ../libi2pd/Crypto.cpp test-elligator.cpp - $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -o $@ $^ -lcrypto -lssl -lboost_system +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) run: $(TESTS) - @for TEST in $(TESTS); do ./$$TEST ; done + @for TEST in $(TESTS); do echo Running $$TEST; ./$$TEST ; done clean: rm -f $(TESTS) diff --git a/tests/test-aeadchacha20poly1305.cpp b/tests/test-aeadchacha20poly1305.cpp index de9f1db2..64a0f358 100644 --- a/tests/test-aeadchacha20poly1305.cpp +++ b/tests/test-aeadchacha20poly1305.cpp @@ -7,28 +7,28 @@ char text[] = "Ladies and Gentlemen of the class of '99: If I could offer you " "only one tip for the future, sunscreen would be it."; // 114 bytes -uint8_t key[32] = +uint8_t key[32] = { 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f }; -uint8_t ad[12] = +uint8_t ad[12] = { 0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7 }; -uint8_t nonce[12] = +uint8_t nonce[12] = { 0x07, 0x00, 0x00, 0x00, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47 }; -uint8_t tag[16] = +uint8_t tag[16] = { 0x1a, 0xe1, 0x0b, 0x59, 0x4f, 0x09, 0xe2, 0x6a, 0x7e, 0x90, 0x2e, 0xcb, 0xd0, 0x60, 0x06, 0x91 }; -uint8_t encrypted[114] = +uint8_t encrypted[114] = { 0xd3, 0x1a, 0x8d, 0x34, 0x64, 0x8e, 0x60, 0xdb, 0x7b, 0x86, 0xaf, 0xbc, 0x53, 0xef, 0x7e, 0xc2, 0xa4, 0xad, 0xed, 0x51, 0x29, 0x6e, 0x08, 0xfe, 0xa9, 0xe2, 0xb5, 0xa7, 0x36, 0xee, 0x62, 0xd6, @@ -53,7 +53,7 @@ int main () 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) }; + std::vector > bufs{ std::make_pair (buf, 20), std::make_pair (buf + 20, 10), std::make_pair (buf + 30, 70), std::make_pair (buf + 100, 14) }; i2p::crypto::AEADChaCha20Poly1305Encrypt (bufs, key, nonce, buf + 114); i2p::crypto::AEADChaCha20Poly1305 (buf, 114, nullptr, 0, key, nonce, buf1, 114, false); assert (memcmp (buf1, text, 114) == 0); diff --git a/tests/test-blinding.cpp b/tests/test-blinding.cpp index 5490acd4..10b72e4f 100644 --- a/tests/test-blinding.cpp +++ b/tests/test-blinding.cpp @@ -13,31 +13,29 @@ void BlindTest (SigningKeyType sigType) { auto keys = PrivateKeys::CreateRandomKeys (sigType); BlindedPublicKey blindedKey (keys.GetPublic ()); - auto timestamp = GetSecondsSinceEpoch (); + auto timestamp = GetSecondsSinceEpoch (); char date[9]; GetDateString (timestamp, date); - uint8_t blindedPriv[64], blindedPub[128]; + uint8_t blindedPriv[32], blindedPub[32]; auto publicKeyLen = blindedKey.BlindPrivateKey (keys.GetSigningPrivateKey (), date, blindedPriv, blindedPub); - uint8_t blindedPub1[128]; + 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 (sigType, blindedPriv)); - uint8_t buf[100], signature[128]; + 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 (sigType)); - blindedVerifier->SetPublicKey (blindedPub1); - assert (blindedVerifier->Verify (buf, 100, signature)); + blindedSigner->Sign (buf, 100, signature); + std::unique_ptr blindedVerifier (IdentityEx::CreateVerifier (blindedKey.GetBlindedSigType ())); + blindedVerifier->SetPublicKey (blindedPub); + assert (blindedVerifier->Verify (buf, 100, signature)); } int main () { - // RedDSA test + // EdDSA test + BlindTest (SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519); + // RedDSA test BlindTest (SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519); - // P256 test - BlindTest (SIGNING_KEY_TYPE_ECDSA_SHA256_P256); - // P384 test - BlindTest (SIGNING_KEY_TYPE_ECDSA_SHA384_P384); } diff --git a/tests/test-elligator.cpp b/tests/test-elligator.cpp index 48c9e31a..359c71c5 100644 --- a/tests/test-elligator.cpp +++ b/tests/test-elligator.cpp @@ -4,19 +4,19 @@ #include "Elligator.h" -const uint8_t key[32] = +const uint8_t key[32] = { 0x33, 0x95, 0x19, 0x64, 0x00, 0x3c, 0x94, 0x08, 0x78, 0x06, 0x3c, 0xcf, 0xd0, 0x34, 0x8a, 0xf4, 0x21, 0x50, 0xca, 0x16, 0xd2, 0x64, 0x6f, 0x2c, 0x58, 0x56, 0xe8, 0x33, 0x83, 0x77, 0xd8, 0x80 }; -const uint8_t encoded_key[32] = +const uint8_t encoded_key[32] = { 0x28, 0x20, 0xb6, 0xb2, 0x41, 0xe0, 0xf6, 0x8a, 0x6c, 0x4a, 0x7f, 0xee, 0x3d, 0x97, 0x82, 0x28, 0xef, 0x3a, 0xe4, 0x55, 0x33, 0xcd, 0x41, 0x0a, 0xa9, 0x1a, 0x41, 0x53, 0x31, 0xd8, 0x61, 0x2d }; -const uint8_t encoded_key_high_y[32] = +const uint8_t encoded_key_high_y[32] = { 0x3c, 0xfb, 0x87, 0xc4, 0x6c, 0x0b, 0x45, 0x75, 0xca, 0x81, 0x75, 0xe0, 0xed, 0x1c, 0x0a, 0xe9, 0xda, 0xe7, 0x9d, 0xb7, 0x8d, 0xf8, 0x69, 0x97, 0xc4, 0x84, 0x7b, 0x9f, 0x20, 0xb2, 0x77, 0x18 @@ -28,7 +28,7 @@ const uint8_t encoded1[32] = 0x14, 0x50, 0x95, 0x89, 0x28, 0x84, 0x57, 0x99, 0x5a, 0x2b, 0x4c, 0xa3, 0x49, 0x0a, 0xa2, 0x07 }; -const uint8_t key1[32] = +const uint8_t key1[32] = { 0x1e, 0x8a, 0xff, 0xfe, 0xd6, 0xbf, 0x53, 0xfe, 0x27, 0x1a, 0xd5, 0x72, 0x47, 0x32, 0x62, 0xde, 0xd8, 0xfa, 0xec, 0x68, 0xe5, 0xe6, 0x7e, 0xf4, 0x5e, 0xbb, 0x82, 0xee, 0xba, 0x52, 0x60, 0x4f @@ -40,7 +40,7 @@ const uint8_t encoded2[32] = 0xd9, 0x03, 0x65, 0xf2, 0x4a, 0x38, 0xaa, 0x7a, 0xef, 0x1b, 0x97, 0xe2, 0x39, 0x54, 0x10, 0x1b }; -const uint8_t key2[32] = +const uint8_t key2[32] = { 0x79, 0x4f, 0x05, 0xba, 0x3e, 0x3a, 0x72, 0x95, 0x80, 0x22, 0x46, 0x8c, 0x88, 0x98, 0x1e, 0x0b, 0xe5, 0x78, 0x2b, 0xe1, 0xe1, 0x14, 0x5c, 0xe2, 0xc3, 0xc6, 0xfd, 0xe1, 0x6d, 0xed, 0x53, 0x63 @@ -52,7 +52,7 @@ const uint8_t encoded3[32] = 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f }; -const uint8_t key3[32] = +const uint8_t key3[32] = { 0x9c, 0xdb, 0x52, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55 diff --git a/tests/test-http-merge_chunked.cpp b/tests/test-http-merge_chunked.cpp index ba587a45..31b6a298 100644 --- a/tests/test-http-merge_chunked.cpp +++ b/tests/test-http-merge_chunked.cpp @@ -1,5 +1,5 @@ #include -#include "../HTTP.h" +#include "HTTP.h" using namespace i2p::http; diff --git a/tests/test-http-req.cpp b/tests/test-http-req.cpp index c857ca24..db973a2e 100644 --- a/tests/test-http-req.cpp +++ b/tests/test-http-req.cpp @@ -1,5 +1,5 @@ #include -#include "../HTTP.h" +#include "HTTP.h" using namespace i2p::http; @@ -22,13 +22,13 @@ int main() { assert(req->version == "HTTP/1.0"); assert(req->method == "GET"); assert(req->uri == "/"); - assert(req->headers.size() == 3); - assert(req->headers.count("Host") == 1); - assert(req->headers.count("Accept") == 1); - assert(req->headers.count("User-Agent") == 1); - assert(req->headers.find("Host")->second == "inr.i2p"); - assert(req->headers.find("Accept")->second == "*/*"); - assert(req->headers.find("User-Agent")->second == "curl/7.26.0"); + 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 */ @@ -41,7 +41,7 @@ int main() { assert(req->version == "HTTP/1.0"); assert(req->method == "GET"); assert(req->uri == "/"); - assert(req->headers.size() == 0); + assert(req->GetNumHeaders () == 0); delete req; /* test: parsing request without body */ @@ -74,13 +74,13 @@ int main() { assert((ret = req->parse(buf, len)) == len); /* no host header */ assert(req->method == "GET"); assert(req->uri == "http://inr.i2p"); - assert(req->headers.size() == 3); - assert(req->headers.count("Host") == 1); - assert(req->headers.count("Accept") == 1); - assert(req->headers.count("Accept-Encoding") == 1); - assert(req->headers["Host"] == "stats.i2p"); - assert(req->headers["Accept"] == "*/*"); - assert(req->headers["Accept-Encoding"] == ""); + 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; diff --git a/tests/test-http-res.cpp b/tests/test-http-res.cpp index 896a4403..270f32a3 100644 --- a/tests/test-http-res.cpp +++ b/tests/test-http-res.cpp @@ -1,5 +1,5 @@ #include -#include "../HTTP.h" +#include "HTTP.h" using namespace i2p::http; diff --git a/tests/test-http-url.cpp b/tests/test-http-url.cpp index 37e9c45e..a5021c43 100644 --- a/tests/test-http-url.cpp +++ b/tests/test-http-url.cpp @@ -1,5 +1,5 @@ #include -#include "../HTTP.h" +#include "HTTP.h" using namespace i2p::http; @@ -15,6 +15,7 @@ int main() { assert(url->host == "127.0.0.1"); assert(url->port == 7070); assert(url->path == "/asdasd"); + assert(url->hasquery == true); assert(url->query == "12345"); assert(url->to_string() == "https://127.0.0.1:7070/asdasd?12345"); delete url; @@ -27,6 +28,7 @@ int main() { assert(url->host == "site.com"); assert(url->port == 8080); assert(url->path == "/asdasd"); + assert(url->hasquery == true); assert(url->query == "123456"); delete url; @@ -38,6 +40,7 @@ int main() { assert(url->host == "site.com"); assert(url->port == 0); assert(url->path == "/asdasd"); + assert(url->hasquery == true); assert(url->query == "name=value"); delete url; @@ -49,6 +52,7 @@ int main() { assert(url->host == "site.com"); assert(url->port == 0); assert(url->path == "/asdasd"); + assert(url->hasquery == true); assert(url->query == "name=value1&name=value2"); delete url; @@ -60,6 +64,7 @@ int main() { assert(url->host == "site.com"); assert(url->port == 0); assert(url->path == "/asdasd"); + assert(url->hasquery == true); assert(url->query == "name1=value1&name2&name3=value2"); assert(url->parse_query(params)); assert(params.size() == 3); @@ -79,6 +84,7 @@ int main() { assert(url->host == "site.com"); assert(url->port == 800); assert(url->path == "/asdasd"); + assert(url->hasquery == true); assert(url->query == ""); delete url; @@ -90,6 +96,7 @@ int main() { assert(url->host == "site.com"); assert(url->port == 17); assert(url->path == ""); + assert(url->hasquery == false); assert(url->query == ""); delete url; @@ -101,6 +108,7 @@ int main() { assert(url->host == "site.com"); assert(url->port == 0); assert(url->path == ""); + assert(url->hasquery == false); assert(url->query == ""); delete url; @@ -112,6 +120,7 @@ int main() { assert(url->host == "site.com"); assert(url->port == 84); assert(url->path == "/asdasd/@17"); + assert(url->hasquery == false); assert(url->query == ""); assert(url->frag == "frag"); delete url; diff --git a/tests/test-http-url_decode.cpp b/tests/test-http-url_decode.cpp index f72b2c50..7f08bbc6 100644 --- a/tests/test-http-url_decode.cpp +++ b/tests/test-http-url_decode.cpp @@ -1,5 +1,5 @@ #include -#include "../HTTP.h" +#include "HTTP.h" using namespace i2p::http; diff --git a/tests/test-x25519.cpp b/tests/test-x25519.cpp index 2ab8ad6a..a1f3f424 100644 --- a/tests/test-x25519.cpp +++ b/tests/test-x25519.cpp @@ -4,21 +4,21 @@ #include "Ed25519.h" -const uint8_t k[32] = +const uint8_t k[32] = { 0xa5, 0x46, 0xe3, 0x6b, 0xf0, 0x52, 0x7c, 0x9d, 0x3b, 0x16, 0x15, 0x4b, 0x82, 0x46, 0x5e, 0xdd, 0x62, 0x14, 0x4c, 0x0a, 0xc1, 0xfc, 0x5a, 0x18, 0x50, 0x6a, 0x22, 0x44, 0xba, 0x44, 0x9a, 0xc4 }; -const uint8_t u[32] = +const uint8_t u[32] = { 0xe6, 0xdb, 0x68, 0x67, 0x58, 0x30, 0x30, 0xdb, 0x35, 0x94, 0xc1, 0xa4, 0x24, 0xb1, 0x5f, 0x7c, 0x72, 0x66, 0x24, 0xec, 0x26, 0xb3, 0x35, 0x3b, 0x10, 0xa9, 0x03, 0xa6, 0xd0, 0xab, 0x1c, 0x4c }; -uint8_t p[32] = +uint8_t p[32] = { 0xc3, 0xda, 0x55, 0x37, 0x9d, 0xe9, 0xc6, 0x90, 0x8e, 0x94, 0xea, 0x4d, 0xf2, 0x8d, 0x08, 0x4f, 0x32, 0xec, 0xcf, 0x03, 0x49, 0x1c, @@ -36,4 +36,3 @@ int main () assert(memcmp (buf, p, 32) == 0); #endif } -