diff --git a/.github/workflows/build-deb.yml b/.github/workflows/build-deb.yml new file mode 100644 index 00000000..bda12dbc --- /dev/null +++ b/.github/workflows/build-deb.yml @@ -0,0 +1,32 @@ +name: Build Debian packages + +on: [push, pull_request] + +jobs: + build: + name: ${{ matrix.dist }} + runs-on: ubuntu-latest + strategy: + matrix: + dist: ['buster', 'bullseye', 'bookworm'] + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: change debian changelog + run: | + sudo apt-get update + sudo apt-get install devscripts + debchange -v "`git describe --tags`-${{ matrix.dist }}" -b -M --distribution ${{ matrix.dist }} "trunk build" + - uses: jtdor/build-deb-action@v1 + with: + docker-image: debian:${{ matrix.dist }}-slim + buildpackage-opts: --build=binary --no-sign + - uses: actions/upload-artifact@v3 + with: + name: i2pd_${{ matrix.dist }} + path: debian/artifacts/i2pd_*.deb + - 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 index b11569b7..557a9b13 100644 --- a/.github/workflows/build-freebsd.yml +++ b/.github/workflows/build-freebsd.yml @@ -4,18 +4,25 @@ on: [push, pull_request] jobs: build: - runs-on: macos-10.15 + runs-on: macos-12 name: with UPnP steps: - uses: actions/checkout@v2 - name: Test in FreeBSD id: test - uses: vmactions/freebsd-vm@v0.1.5 + uses: vmactions/freebsd-vm@v0.3.0 with: usesh: true mem: 2048 - prepare: pkg install -y devel/cmake devel/gmake devel/boost-libs security/openssl net/miniupnpc + 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@v2 + with: + name: i2pd-freebsd + path: build/i2pd \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index affa0b8b..d8828f61 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -38,51 +38,3 @@ jobs: cd build cmake -DWITH_UPNP=${{ matrix.with_upnp }} . make -j3 - build-deb-stretch: - name: Build package for stretch - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - name: change debian changelog - run: | - sudo apt-get update - sudo apt-get install devscripts - debchange -v "`git describe --tags`-stretch" -b -M --distribution stretch "trunk build" - - uses: singingwolfboy/build-dpkg-stretch@v1 - id: build - with: - args: --unsigned-source --unsigned-changes -b - - uses: actions/upload-artifact@v1 - with: - name: ${{ steps.build.outputs.filename }} - path: ${{ steps.build.outputs.filename }} - - uses: actions/upload-artifact@v1 - with: - name: ${{ steps.build.outputs.filename-dbgsym }} - path: ${{ steps.build.outputs.filename-dbgsym }} - build-deb-buster: - name: Build package for buster - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - name: change debian changelog - run: | - sudo apt-get update - sudo apt-get install devscripts - debchange -v "`git describe --tags`-buster" -b -M --distribution buster "trunk build" - - uses: singingwolfboy/build-dpkg-buster@v1 - id: build - with: - args: --unsigned-source --unsigned-changes -b - - uses: actions/upload-artifact@v1 - with: - name: ${{ steps.build.outputs.filename }} - path: ${{ steps.build.outputs.filename }} - - uses: actions/upload-artifact@v1 - with: - name: ${{ steps.build.outputs.filename-dbgsym }} - path: ${{ steps.build.outputs.filename-dbgsym }} diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 1ac5b552..899a7eec 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -4,67 +4,137 @@ on: push: branches: - openssl + - docker tags: - '*' jobs: - docker: + build: 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@v2 - name: Set up QEMU - uses: docker/setup-qemu-action@v1 + uses: docker/setup-qemu-action@v2 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 + uses: docker/setup-buildx-action@v2 - name: Login to DockerHub - uses: docker/login-action@v1 + uses: docker/login-action@v2 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Login to GitHub Container registry - uses: docker/login-action@v1 + uses: docker/login-action@v2 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Build and push trunk container - if: ${{ !startsWith(github.ref, 'refs/tags/') }} - uses: docker/build-push-action@v2 + - name: Build container for ${{ matrix.archname }} + uses: docker/build-push-action@v3 with: context: ./contrib/docker file: ./contrib/docker/Dockerfile - platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v7 + platforms: ${{ matrix.platform }} push: true tags: | - purplei2p/i2pd:latest - ghcr.io/purplei2p/i2pd:latest + purplei2p/i2pd:latest-${{ matrix.archname }} + ghcr.io/purplei2p/i2pd:latest-${{ matrix.archname }} - - name: Set env + push: + runs-on: ubuntu-latest + permissions: + packages: write + contents: read + + needs: build + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - 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 + uses: Noelware/docker-manifest-action@master + with: + base-image: purplei2p/i2pd:latest + extra-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 + uses: Noelware/docker-manifest-action@master + with: + base-image: ghcr.io/purplei2p/i2pd:latest + extra-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: Build and push release container + - name: Create and push release manifest image to Docker Hub if: ${{ startsWith(github.ref, 'refs/tags/') }} - uses: docker/build-push-action@v2 + uses: Noelware/docker-manifest-action@master with: - context: ./contrib/docker - file: ./contrib/docker/Dockerfile - platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v7 + base-image: purplei2p/i2pd:latest-release + extra-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 image to GHCR + if: ${{ startsWith(github.ref, 'refs/tags/') }} + uses: Noelware/docker-manifest-action@master + with: + base-image: ghcr.io/purplei2p/i2pd:latest-release + extra-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: Create and push versioned manifest image to Docker Hub + if: ${{ startsWith(github.ref, 'refs/tags/') }} + uses: Noelware/docker-manifest-action@master + with: + base-image: purplei2p/i2pd:release-${{ env.RELEASE_VERSION }} + extra-images: purplei2p/i2pd:latest-amd64,purplei2p/i2pd:latest-i386,purplei2p/i2pd:latest-arm64,purplei2p/i2pd:latest-armv7 + push: true + + - name: Create and push versioned manifest image to GHCR + if: ${{ startsWith(github.ref, 'refs/tags/') }} + uses: Noelware/docker-manifest-action@master + with: + base-image: ghcr.io/purplei2p/i2pd:release-${{ env.RELEASE_VERSION }} + extra-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 - tags: | - purplei2p/i2pd:latest - purplei2p/i2pd:latest-release - purplei2p/i2pd:release-${{ env.RELEASE_VERSION }} - ghcr.io/purplei2p/i2pd:latest - ghcr.io/purplei2p/i2pd:latest-release - ghcr.io/purplei2p/i2pd:release-${{ env.RELEASE_VERSION }} diff --git a/ChangeLog b/ChangeLog index b4e815e4..9f3faf11 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,66 @@ # for this file format description, # see https://github.com/olivierlacan/keep-a-changelog +## [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 enbaled 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 @@ -381,7 +441,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 @@ -685,7 +745,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/Win32/Win32App.cpp b/Win32/Win32App.cpp index 377e1076..37458046 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 * @@ -154,25 +154,23 @@ namespace win32 case eRouterStatusUnknown: s << "Unk"; break; case eRouterStatusProxy: s << "Proxy"; break; case eRouterStatusMesh: s << "Mesh"; break; - case eRouterStatusError: - { - s << "Err"; - switch (i2p::context.GetError ()) - { - case eRouterErrorClockSkew: - s << " - Clock skew"; - break; - case eRouterErrorOffline: - s << " - Offline"; - break; - case eRouterErrorSymmetricNAT: - s << " - Symmetric NAT"; - break; - default: ; - } - break; - } default: s << "Unk"; + }; + 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: ; + } } } diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index d35bd526..11036f16 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -85,6 +85,7 @@ set(DAEMON_SRC "${DAEMON_SRC_DIR}/Daemon.cpp" "${DAEMON_SRC_DIR}/HTTPServer.cpp" "${DAEMON_SRC_DIR}/I2PControl.cpp" + "${DAEMON_SRC_DIR}/I2PControlHandlers.cpp" "${DAEMON_SRC_DIR}/i2pd.cpp" "${DAEMON_SRC_DIR}/UPnP.cpp" ) @@ -171,6 +172,13 @@ if(WITH_THREADSANITIZER) endif() +# Enable usage of STD's Atomic instead of Boost's on PowerPC +# For more information refer to https://github.com/PurpleI2P/i2pd/issues/1726#issuecomment-1306335111 +if(ARCHITECTURE MATCHES "ppc") + add_definitions(-DBOOST_SP_USE_STD_ATOMIC) +endif() + + # libraries set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) diff --git a/build/cmake_modules/TargetArch.cmake b/build/cmake_modules/TargetArch.cmake index d9ebe7c9..ac7275b1 100644 --- a/build/cmake_modules/TargetArch.cmake +++ b/build/cmake_modules/TargetArch.cmake @@ -61,7 +61,7 @@ set(archdetect_c_code " #else #error cmake_ARCH mips #endif -#elif defined(__ppc__) || defined(__ppc) || defined(__powerpc__) \\ +#elif defined(__ppc__) || defined(__ppc) || defined(__powerpc__) || defined(__POWERPC__) \\ || defined(_ARCH_COM) || defined(_ARCH_PWR) || defined(_ARCH_PPC) \\ || defined(_M_MPPC) || defined(_M_PPC) #if defined(__ppc64__) || defined(__powerpc64__) || defined(__64BIT__) diff --git a/contrib/docker/Dockerfile b/contrib/docker/Dockerfile index 71af141e..129c5ff3 100644 --- a/contrib/docker/Dockerfile +++ b/contrib/docker/Dockerfile @@ -1,5 +1,18 @@ +# +# Copyright (c) 2017-2022, The PurpleI2P Project +# +# This file is part of Purple i2pd project and licensed under BSD3 +# +# See full license text in LICENSE file at top of project tree +# + FROM alpine:latest -LABEL authors "Mikal Villa , Darknet Villain " +LABEL authors="Mikal Villa , Darknet Villain " +LABEL maintainer="R4SAS " + +LABEL org.opencontainers.image.source=https://github.com/PurpleI2P/i2pd +LABEL org.opencontainers.image.documentation=https://i2pd.readthedocs.io/en/latest/ +LABEL org.opencontainers.image.licenses=BSD3 # Expose git branch, tag and URL variables as arguments ARG GIT_BRANCH="openssl" @@ -11,27 +24,28 @@ ENV REPO_URL=${REPO_URL} ENV I2PD_HOME="/home/i2pd" ENV DATA_DIR="${I2PD_HOME}/data" -ENV DEFAULT_ARGS=" --datadir=$DATA_DIR --reseed.verify=true --upnp.enabled=false --http.enabled=true --http.address=0.0.0.0 --httpproxy.enabled=true --httpproxy.address=0.0.0.0 --socksproxy.enabled=true --socksproxy.address=0.0.0.0 --sam.enabled=true --sam.address=0.0.0.0" +ENV DEFAULT_ARGS=" --datadir=$DATA_DIR" RUN mkdir -p "$I2PD_HOME" "$DATA_DIR" \ && adduser -S -h "$I2PD_HOME" i2pd \ && chown -R i2pd:nobody "$I2PD_HOME" -# -# Each RUN is a layer, adding the dependencies and building i2pd in one layer takes around 8-900Mb, so to keep the -# image under 20mb we need to remove all the build dependencies in the same "RUN" / layer. -# -# 1. install deps, clone and build. -# 2. strip binaries. -# 3. Purge all dependencies and other unrelated packages, including build directory. +# 1. Building binary +# Each RUN is a layer, adding the dependencies and building i2pd in one layer takes around 8-900Mb, so to keep the +# image under 20mb we need to remove all the build dependencies in the same "RUN" / layer. +# +# 1. install deps, clone and build. +# 2. strip binaries. +# 3. Purge all dependencies and other unrelated packages, including build directory. + RUN apk update \ && apk --no-cache --virtual build-dependendencies add make gcc g++ libtool zlib-dev boost-dev build-base openssl-dev openssl miniupnpc-dev git \ && mkdir -p /tmp/build \ && cd /tmp/build && git clone -b ${GIT_BRANCH} ${REPO_URL} \ && cd i2pd \ && if [ -n "${GIT_TAG}" ]; then git checkout tags/${GIT_TAG}; fi \ - && make USE_UPNP=yes \ + && make -j$(nproc) USE_UPNP=yes \ && cp -R contrib/certificates /i2pd_certificates \ && mkdir -p /usr/local/bin \ && mv i2pd /usr/local/bin \ @@ -45,6 +59,9 @@ RUN apk update \ # 2. Adding required libraries to run i2pd to ensure it will run. RUN apk --no-cache add boost-filesystem boost-system boost-program_options boost-date_time boost-thread boost-iostreams openssl miniupnpc musl-utils libstdc++ +# 3. Copy preconfigured config file and entrypoint +COPY i2pd-docker.conf "$I2PD_HOME/i2pd.conf" +RUN chown i2pd:nobody "$I2PD_HOME/i2pd.conf" COPY entrypoint.sh /entrypoint.sh RUN chmod a+x /entrypoint.sh diff --git a/contrib/docker/i2pd-docker.conf b/contrib/docker/i2pd-docker.conf new file mode 100644 index 00000000..3b91a235 --- /dev/null +++ b/contrib/docker/i2pd-docker.conf @@ -0,0 +1,52 @@ +## Preconfigured i2pd configuration file for a Docker container +## See https://i2pd.readthedocs.io/en/latest/user-guide/configuration/ +## for more options you can use in this file. + +## Note that for exposing ports outside of container you need to bind all services to 0.0.0.0 + +log = file +loglevel = none + +ipv4 = true +ipv6 = false + +# bandwidth = L +# notransit = false +# floodfill = false + +[ntcp2] +enabled = true +published = true + +[ssu2] +enabled = true +published = true + +[http] +enabled = true +address = 0.0.0.0 +port = 7070 + +[httpproxy] +enabled = true +address = 0.0.0.0 +port = 4444 + +[socksproxy] +enabled = true +address = 0.0.0.0 +port = 4447 + +[sam] +enabled = true +address = 0.0.0.0 +port = 7656 + +[upnp] +enabled = false + +[reseed] +verify = true + +[limits] +# transittunnels = 2500 diff --git a/contrib/i18n/English.po b/contrib/i18n/English.po index 25378f82..c9ded966 100644 --- a/contrib/i18n/English.po +++ b/contrib/i18n/English.po @@ -1,13 +1,13 @@ # i2pd -# Copyright (C) 2021 PurpleI2P team +# Copyright (C) 2021-2022 PurpleI2P team # This file is distributed under the same license as the i2pd package. -# R4SAS , 2021. +# R4SAS , 2021-2022. # msgid "" msgstr "" "Project-Id-Version: i2pd\n" "Report-Msgid-Bugs-To: https://github.com/PurpleI2P/i2pd/issues\n" -"POT-Creation-Date: 2021-08-06 17:12\n" +"POT-Creation-Date: 2022-07-26 21:22\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -18,706 +18,712 @@ msgstr "" "X-Poedit-SearchPath-0: daemon/HTTPServer.cpp\n" "X-Poedit-SearchPath-1: libi2pd_client/HTTPProxy.cpp\n" -#: daemon/HTTPServer.cpp:177 +#: daemon/HTTPServer.cpp:108 msgid "day" msgid_plural "days" msgstr[0] "" msgstr[1] "" -#: daemon/HTTPServer.cpp:181 +#: daemon/HTTPServer.cpp:112 msgid "hour" msgid_plural "hours" msgstr[0] "" msgstr[1] "" -#: daemon/HTTPServer.cpp:185 +#: daemon/HTTPServer.cpp:116 msgid "minute" msgid_plural "minutes" msgstr[0] "" msgstr[1] "" -#: daemon/HTTPServer.cpp:188 +#: daemon/HTTPServer.cpp:119 msgid "second" msgid_plural "seconds" msgstr[0] "" msgstr[1] "" #. tr: Kibibit -#: daemon/HTTPServer.cpp:196 daemon/HTTPServer.cpp:224 +#: daemon/HTTPServer.cpp:127 daemon/HTTPServer.cpp:155 msgid "KiB" msgstr "" #. tr: Mebibit -#: daemon/HTTPServer.cpp:198 +#: daemon/HTTPServer.cpp:129 msgid "MiB" msgstr "" #. tr: Gibibit -#: daemon/HTTPServer.cpp:200 +#: daemon/HTTPServer.cpp:131 msgid "GiB" msgstr "" -#: daemon/HTTPServer.cpp:217 +#: daemon/HTTPServer.cpp:148 msgid "building" msgstr "" -#: daemon/HTTPServer.cpp:218 +#: daemon/HTTPServer.cpp:149 msgid "failed" msgstr "" -#: daemon/HTTPServer.cpp:219 +#: daemon/HTTPServer.cpp:150 msgid "expiring" msgstr "" -#: daemon/HTTPServer.cpp:220 +#: daemon/HTTPServer.cpp:151 msgid "established" msgstr "" -#: daemon/HTTPServer.cpp:221 +#: daemon/HTTPServer.cpp:152 msgid "unknown" msgstr "" -#: daemon/HTTPServer.cpp:223 +#: daemon/HTTPServer.cpp:154 msgid "exploratory" msgstr "" -#: daemon/HTTPServer.cpp:259 +#. tr: Webconsole page title +#: daemon/HTTPServer.cpp:185 +msgid "Purple I2P Webconsole" +msgstr "" + +#: daemon/HTTPServer.cpp:190 msgid "i2pd webconsole" msgstr "" -#: daemon/HTTPServer.cpp:262 +#: daemon/HTTPServer.cpp:193 msgid "Main page" msgstr "" -#: daemon/HTTPServer.cpp:263 daemon/HTTPServer.cpp:725 +#: daemon/HTTPServer.cpp:194 daemon/HTTPServer.cpp:700 msgid "Router commands" msgstr "" -#: daemon/HTTPServer.cpp:264 daemon/HTTPServer.cpp:448 -#: daemon/HTTPServer.cpp:460 +#: daemon/HTTPServer.cpp:195 daemon/HTTPServer.cpp:382 +#: daemon/HTTPServer.cpp:394 msgid "Local Destinations" msgstr "" -#: daemon/HTTPServer.cpp:266 daemon/HTTPServer.cpp:418 -#: daemon/HTTPServer.cpp:504 daemon/HTTPServer.cpp:510 -#: daemon/HTTPServer.cpp:641 daemon/HTTPServer.cpp:684 -#: daemon/HTTPServer.cpp:688 +#: daemon/HTTPServer.cpp:197 daemon/HTTPServer.cpp:352 +#: daemon/HTTPServer.cpp:438 daemon/HTTPServer.cpp:444 +#: daemon/HTTPServer.cpp:597 daemon/HTTPServer.cpp:640 +#: daemon/HTTPServer.cpp:644 msgid "LeaseSets" msgstr "" -#: daemon/HTTPServer.cpp:268 daemon/HTTPServer.cpp:694 +#: daemon/HTTPServer.cpp:199 daemon/HTTPServer.cpp:650 msgid "Tunnels" msgstr "" -#: daemon/HTTPServer.cpp:269 daemon/HTTPServer.cpp:425 -#: daemon/HTTPServer.cpp:787 daemon/HTTPServer.cpp:803 +#: daemon/HTTPServer.cpp:201 daemon/HTTPServer.cpp:359 +#: daemon/HTTPServer.cpp:770 daemon/HTTPServer.cpp:786 msgid "Transit Tunnels" msgstr "" -#: daemon/HTTPServer.cpp:270 daemon/HTTPServer.cpp:852 +#: daemon/HTTPServer.cpp:203 daemon/HTTPServer.cpp:839 msgid "Transports" msgstr "" -#: daemon/HTTPServer.cpp:271 +#: daemon/HTTPServer.cpp:204 msgid "I2P tunnels" msgstr "" -#: daemon/HTTPServer.cpp:273 daemon/HTTPServer.cpp:914 -#: daemon/HTTPServer.cpp:924 +#: daemon/HTTPServer.cpp:206 daemon/HTTPServer.cpp:908 +#: daemon/HTTPServer.cpp:918 msgid "SAM sessions" msgstr "" -#: daemon/HTTPServer.cpp:289 daemon/HTTPServer.cpp:1306 -#: daemon/HTTPServer.cpp:1309 daemon/HTTPServer.cpp:1312 -#: daemon/HTTPServer.cpp:1326 daemon/HTTPServer.cpp:1371 -#: daemon/HTTPServer.cpp:1374 daemon/HTTPServer.cpp:1377 +#: daemon/HTTPServer.cpp:222 daemon/HTTPServer.cpp:1302 +#: daemon/HTTPServer.cpp:1305 daemon/HTTPServer.cpp:1308 +#: daemon/HTTPServer.cpp:1322 daemon/HTTPServer.cpp:1367 +#: daemon/HTTPServer.cpp:1370 daemon/HTTPServer.cpp:1373 msgid "ERROR" msgstr "" -#: daemon/HTTPServer.cpp:296 +#: daemon/HTTPServer.cpp:229 msgid "OK" msgstr "" -#: daemon/HTTPServer.cpp:297 +#: daemon/HTTPServer.cpp:230 msgid "Testing" msgstr "" -#: daemon/HTTPServer.cpp:298 +#: daemon/HTTPServer.cpp:231 msgid "Firewalled" msgstr "" -#: daemon/HTTPServer.cpp:299 daemon/HTTPServer.cpp:320 -#: daemon/HTTPServer.cpp:406 +#: daemon/HTTPServer.cpp:232 daemon/HTTPServer.cpp:253 +#: daemon/HTTPServer.cpp:325 msgid "Unknown" msgstr "" -#: daemon/HTTPServer.cpp:300 daemon/HTTPServer.cpp:435 -#: daemon/HTTPServer.cpp:436 daemon/HTTPServer.cpp:982 -#: daemon/HTTPServer.cpp:991 +#: daemon/HTTPServer.cpp:233 daemon/HTTPServer.cpp:369 +#: daemon/HTTPServer.cpp:370 daemon/HTTPServer.cpp:976 +#: daemon/HTTPServer.cpp:985 msgid "Proxy" msgstr "" -#: daemon/HTTPServer.cpp:301 +#: daemon/HTTPServer.cpp:234 msgid "Mesh" msgstr "" -#: daemon/HTTPServer.cpp:304 +#: daemon/HTTPServer.cpp:237 msgid "Error" msgstr "" -#: daemon/HTTPServer.cpp:308 +#: daemon/HTTPServer.cpp:241 msgid "Clock skew" msgstr "" -#: daemon/HTTPServer.cpp:311 +#: daemon/HTTPServer.cpp:244 msgid "Offline" msgstr "" -#: daemon/HTTPServer.cpp:314 +#: daemon/HTTPServer.cpp:247 msgid "Symmetric NAT" msgstr "" -#: daemon/HTTPServer.cpp:326 +#: daemon/HTTPServer.cpp:259 msgid "Uptime" msgstr "" -#: daemon/HTTPServer.cpp:329 +#: daemon/HTTPServer.cpp:262 msgid "Network status" msgstr "" -#: daemon/HTTPServer.cpp:334 +#: daemon/HTTPServer.cpp:267 msgid "Network status v6" msgstr "" -#: daemon/HTTPServer.cpp:340 daemon/HTTPServer.cpp:347 +#: daemon/HTTPServer.cpp:273 daemon/HTTPServer.cpp:280 msgid "Stopping in" msgstr "" -#: daemon/HTTPServer.cpp:354 +#: daemon/HTTPServer.cpp:287 msgid "Family" msgstr "" -#: daemon/HTTPServer.cpp:355 +#: daemon/HTTPServer.cpp:288 msgid "Tunnel creation success rate" msgstr "" -#: daemon/HTTPServer.cpp:356 +#: daemon/HTTPServer.cpp:289 msgid "Received" msgstr "" #. tr: Kibibit/s -#: daemon/HTTPServer.cpp:358 daemon/HTTPServer.cpp:361 -#: daemon/HTTPServer.cpp:364 +#: daemon/HTTPServer.cpp:291 daemon/HTTPServer.cpp:294 +#: daemon/HTTPServer.cpp:297 msgid "KiB/s" msgstr "" -#: daemon/HTTPServer.cpp:359 +#: daemon/HTTPServer.cpp:292 msgid "Sent" msgstr "" -#: daemon/HTTPServer.cpp:362 +#: daemon/HTTPServer.cpp:295 msgid "Transit" msgstr "" -#: daemon/HTTPServer.cpp:365 +#: daemon/HTTPServer.cpp:298 msgid "Data path" msgstr "" -#: daemon/HTTPServer.cpp:368 +#: daemon/HTTPServer.cpp:301 msgid "Hidden content. Press on text to see." msgstr "" -#: daemon/HTTPServer.cpp:371 +#: daemon/HTTPServer.cpp:304 msgid "Router Ident" msgstr "" -#: daemon/HTTPServer.cpp:373 +#: daemon/HTTPServer.cpp:306 msgid "Router Family" msgstr "" -#: daemon/HTTPServer.cpp:374 +#: daemon/HTTPServer.cpp:307 msgid "Router Caps" msgstr "" -#: daemon/HTTPServer.cpp:375 +#: daemon/HTTPServer.cpp:308 msgid "Version" msgstr "" -#: daemon/HTTPServer.cpp:376 +#: daemon/HTTPServer.cpp:309 msgid "Our external address" msgstr "" -#: daemon/HTTPServer.cpp:384 +#: daemon/HTTPServer.cpp:337 msgid "supported" msgstr "" -#: daemon/HTTPServer.cpp:416 +#: daemon/HTTPServer.cpp:350 msgid "Routers" msgstr "" -#: daemon/HTTPServer.cpp:417 +#: daemon/HTTPServer.cpp:351 msgid "Floodfills" msgstr "" -#: daemon/HTTPServer.cpp:424 daemon/HTTPServer.cpp:968 +#: daemon/HTTPServer.cpp:358 daemon/HTTPServer.cpp:962 msgid "Client Tunnels" msgstr "" -#: daemon/HTTPServer.cpp:434 +#: daemon/HTTPServer.cpp:368 msgid "Services" msgstr "" -#: daemon/HTTPServer.cpp:435 daemon/HTTPServer.cpp:436 -#: daemon/HTTPServer.cpp:437 daemon/HTTPServer.cpp:438 -#: daemon/HTTPServer.cpp:439 daemon/HTTPServer.cpp:440 +#: daemon/HTTPServer.cpp:369 daemon/HTTPServer.cpp:370 +#: daemon/HTTPServer.cpp:371 daemon/HTTPServer.cpp:372 +#: daemon/HTTPServer.cpp:373 daemon/HTTPServer.cpp:374 msgid "Enabled" msgstr "" -#: daemon/HTTPServer.cpp:435 daemon/HTTPServer.cpp:436 -#: daemon/HTTPServer.cpp:437 daemon/HTTPServer.cpp:438 -#: daemon/HTTPServer.cpp:439 daemon/HTTPServer.cpp:440 +#: daemon/HTTPServer.cpp:369 daemon/HTTPServer.cpp:370 +#: daemon/HTTPServer.cpp:371 daemon/HTTPServer.cpp:372 +#: daemon/HTTPServer.cpp:373 daemon/HTTPServer.cpp:374 msgid "Disabled" msgstr "" -#: daemon/HTTPServer.cpp:483 +#: daemon/HTTPServer.cpp:417 msgid "Encrypted B33 address" msgstr "" -#: daemon/HTTPServer.cpp:492 +#: daemon/HTTPServer.cpp:426 msgid "Address registration line" msgstr "" -#: daemon/HTTPServer.cpp:497 +#: daemon/HTTPServer.cpp:431 msgid "Domain" msgstr "" -#: daemon/HTTPServer.cpp:498 +#: daemon/HTTPServer.cpp:432 msgid "Generate" msgstr "" -#: daemon/HTTPServer.cpp:499 +#: daemon/HTTPServer.cpp:433 msgid "" "Note: result string can be used only for registering 2LD domains " "(example.i2p). For registering subdomains please use i2pd-tools." msgstr "" -#: daemon/HTTPServer.cpp:505 +#: daemon/HTTPServer.cpp:439 msgid "Address" msgstr "" -#: daemon/HTTPServer.cpp:505 +#: daemon/HTTPServer.cpp:439 msgid "Type" msgstr "" -#: daemon/HTTPServer.cpp:505 +#: daemon/HTTPServer.cpp:439 msgid "EncType" msgstr "" -#: daemon/HTTPServer.cpp:515 daemon/HTTPServer.cpp:699 +#: daemon/HTTPServer.cpp:449 daemon/HTTPServer.cpp:655 msgid "Inbound tunnels" msgstr "" #. tr: Milliseconds -#: daemon/HTTPServer.cpp:520 daemon/HTTPServer.cpp:530 -#: daemon/HTTPServer.cpp:704 daemon/HTTPServer.cpp:714 +#: daemon/HTTPServer.cpp:464 daemon/HTTPServer.cpp:484 +#: daemon/HTTPServer.cpp:669 daemon/HTTPServer.cpp:689 msgid "ms" msgstr "" -#: daemon/HTTPServer.cpp:525 daemon/HTTPServer.cpp:709 +#: daemon/HTTPServer.cpp:469 daemon/HTTPServer.cpp:674 msgid "Outbound tunnels" msgstr "" -#: daemon/HTTPServer.cpp:537 +#: daemon/HTTPServer.cpp:491 msgid "Tags" msgstr "" -#: daemon/HTTPServer.cpp:537 +#: daemon/HTTPServer.cpp:491 msgid "Incoming" msgstr "" -#: daemon/HTTPServer.cpp:544 daemon/HTTPServer.cpp:547 +#: daemon/HTTPServer.cpp:498 daemon/HTTPServer.cpp:501 msgid "Outgoing" msgstr "" -#: daemon/HTTPServer.cpp:545 daemon/HTTPServer.cpp:561 +#: daemon/HTTPServer.cpp:499 daemon/HTTPServer.cpp:515 msgid "Destination" msgstr "" -#: daemon/HTTPServer.cpp:545 +#: daemon/HTTPServer.cpp:499 msgid "Amount" msgstr "" -#: daemon/HTTPServer.cpp:552 +#: daemon/HTTPServer.cpp:506 msgid "Incoming Tags" msgstr "" -#: daemon/HTTPServer.cpp:560 daemon/HTTPServer.cpp:563 +#: daemon/HTTPServer.cpp:514 daemon/HTTPServer.cpp:517 msgid "Tags sessions" msgstr "" -#: daemon/HTTPServer.cpp:561 +#: daemon/HTTPServer.cpp:515 msgid "Status" msgstr "" -#: daemon/HTTPServer.cpp:570 daemon/HTTPServer.cpp:626 +#: daemon/HTTPServer.cpp:524 daemon/HTTPServer.cpp:582 msgid "Local Destination" msgstr "" -#: daemon/HTTPServer.cpp:580 daemon/HTTPServer.cpp:947 +#: daemon/HTTPServer.cpp:535 daemon/HTTPServer.cpp:941 msgid "Streams" msgstr "" -#: daemon/HTTPServer.cpp:602 +#: daemon/HTTPServer.cpp:558 msgid "Close stream" msgstr "" -#: daemon/HTTPServer.cpp:631 +#: daemon/HTTPServer.cpp:587 msgid "I2CP session not found" msgstr "" -#: daemon/HTTPServer.cpp:634 +#: daemon/HTTPServer.cpp:590 msgid "I2CP is not enabled" msgstr "" -#: daemon/HTTPServer.cpp:660 +#: daemon/HTTPServer.cpp:616 msgid "Invalid" msgstr "" -#: daemon/HTTPServer.cpp:663 +#: daemon/HTTPServer.cpp:619 msgid "Store type" msgstr "" -#: daemon/HTTPServer.cpp:664 +#: daemon/HTTPServer.cpp:620 msgid "Expires" msgstr "" -#: daemon/HTTPServer.cpp:669 +#: daemon/HTTPServer.cpp:625 msgid "Non Expired Leases" msgstr "" -#: daemon/HTTPServer.cpp:672 +#: daemon/HTTPServer.cpp:628 msgid "Gateway" msgstr "" -#: daemon/HTTPServer.cpp:673 +#: daemon/HTTPServer.cpp:629 msgid "TunnelID" msgstr "" -#: daemon/HTTPServer.cpp:674 +#: daemon/HTTPServer.cpp:630 msgid "EndDate" msgstr "" -#: daemon/HTTPServer.cpp:684 +#: daemon/HTTPServer.cpp:640 msgid "not floodfill" msgstr "" -#: daemon/HTTPServer.cpp:695 +#: daemon/HTTPServer.cpp:651 msgid "Queue size" msgstr "" -#: daemon/HTTPServer.cpp:726 +#: daemon/HTTPServer.cpp:701 msgid "Run peer test" msgstr "" -#: daemon/HTTPServer.cpp:731 +#: daemon/HTTPServer.cpp:706 msgid "Decline transit tunnels" msgstr "" -#: daemon/HTTPServer.cpp:733 +#: daemon/HTTPServer.cpp:708 msgid "Accept transit tunnels" msgstr "" -#: daemon/HTTPServer.cpp:737 daemon/HTTPServer.cpp:742 +#: daemon/HTTPServer.cpp:712 daemon/HTTPServer.cpp:717 msgid "Cancel graceful shutdown" msgstr "" -#: daemon/HTTPServer.cpp:739 daemon/HTTPServer.cpp:744 +#: daemon/HTTPServer.cpp:714 daemon/HTTPServer.cpp:719 msgid "Start graceful shutdown" msgstr "" -#: daemon/HTTPServer.cpp:747 +#: daemon/HTTPServer.cpp:722 msgid "Force shutdown" msgstr "" -#: daemon/HTTPServer.cpp:748 +#: daemon/HTTPServer.cpp:723 msgid "Reload external CSS styles" msgstr "" -#: daemon/HTTPServer.cpp:751 +#: daemon/HTTPServer.cpp:726 msgid "" "Note: any action done here are not persistent and not changes your " "config files." msgstr "" -#: daemon/HTTPServer.cpp:753 +#: daemon/HTTPServer.cpp:728 msgid "Logging level" msgstr "" -#: daemon/HTTPServer.cpp:761 +#: daemon/HTTPServer.cpp:736 msgid "Transit tunnels limit" msgstr "" -#: daemon/HTTPServer.cpp:766 daemon/HTTPServer.cpp:778 +#: daemon/HTTPServer.cpp:741 daemon/HTTPServer.cpp:760 msgid "Change" msgstr "" -#: daemon/HTTPServer.cpp:770 +#: daemon/HTTPServer.cpp:748 msgid "Change language" msgstr "" -#: daemon/HTTPServer.cpp:803 +#: daemon/HTTPServer.cpp:786 msgid "no transit tunnels currently built" msgstr "" -#: daemon/HTTPServer.cpp:908 daemon/HTTPServer.cpp:931 +#: daemon/HTTPServer.cpp:902 daemon/HTTPServer.cpp:925 msgid "SAM disabled" msgstr "" -#: daemon/HTTPServer.cpp:924 +#: daemon/HTTPServer.cpp:918 msgid "no sessions currently running" msgstr "" -#: daemon/HTTPServer.cpp:937 +#: daemon/HTTPServer.cpp:931 msgid "SAM session not found" msgstr "" -#: daemon/HTTPServer.cpp:942 +#: daemon/HTTPServer.cpp:936 msgid "SAM Session" msgstr "" -#: daemon/HTTPServer.cpp:999 +#: daemon/HTTPServer.cpp:993 msgid "Server Tunnels" msgstr "" -#: daemon/HTTPServer.cpp:1015 +#: daemon/HTTPServer.cpp:1009 msgid "Client Forwards" msgstr "" -#: daemon/HTTPServer.cpp:1029 +#: daemon/HTTPServer.cpp:1023 msgid "Server Forwards" msgstr "" -#: daemon/HTTPServer.cpp:1227 +#: daemon/HTTPServer.cpp:1223 msgid "Unknown page" msgstr "" -#: daemon/HTTPServer.cpp:1246 +#: daemon/HTTPServer.cpp:1242 msgid "Invalid token" msgstr "" -#: daemon/HTTPServer.cpp:1304 daemon/HTTPServer.cpp:1361 -#: daemon/HTTPServer.cpp:1401 +#: daemon/HTTPServer.cpp:1300 daemon/HTTPServer.cpp:1357 +#: daemon/HTTPServer.cpp:1397 msgid "SUCCESS" msgstr "" -#: daemon/HTTPServer.cpp:1304 +#: daemon/HTTPServer.cpp:1300 msgid "Stream closed" msgstr "" -#: daemon/HTTPServer.cpp:1306 +#: daemon/HTTPServer.cpp:1302 msgid "Stream not found or already was closed" msgstr "" -#: daemon/HTTPServer.cpp:1309 +#: daemon/HTTPServer.cpp:1305 msgid "Destination not found" msgstr "" -#: daemon/HTTPServer.cpp:1312 +#: daemon/HTTPServer.cpp:1308 msgid "StreamID can't be null" msgstr "" -#: daemon/HTTPServer.cpp:1314 daemon/HTTPServer.cpp:1379 +#: daemon/HTTPServer.cpp:1310 daemon/HTTPServer.cpp:1375 msgid "Return to destination page" msgstr "" -#: daemon/HTTPServer.cpp:1315 daemon/HTTPServer.cpp:1328 -#: daemon/HTTPServer.cpp:1403 +#: daemon/HTTPServer.cpp:1311 daemon/HTTPServer.cpp:1324 +#: daemon/HTTPServer.cpp:1399 msgid "You will be redirected in 5 seconds" msgstr "" -#: daemon/HTTPServer.cpp:1326 +#: daemon/HTTPServer.cpp:1322 msgid "Transit tunnels count must not exceed 65535" msgstr "" -#: daemon/HTTPServer.cpp:1327 daemon/HTTPServer.cpp:1402 +#: daemon/HTTPServer.cpp:1323 daemon/HTTPServer.cpp:1398 msgid "Back to commands list" msgstr "" -#: daemon/HTTPServer.cpp:1363 +#: daemon/HTTPServer.cpp:1359 msgid "Register at reg.i2p" msgstr "" -#: daemon/HTTPServer.cpp:1364 +#: daemon/HTTPServer.cpp:1360 msgid "Description" msgstr "" -#: daemon/HTTPServer.cpp:1364 +#: daemon/HTTPServer.cpp:1360 msgid "A bit information about service on domain" msgstr "" -#: daemon/HTTPServer.cpp:1365 +#: daemon/HTTPServer.cpp:1361 msgid "Submit" msgstr "" -#: daemon/HTTPServer.cpp:1371 +#: daemon/HTTPServer.cpp:1367 msgid "Domain can't end with .b32.i2p" msgstr "" -#: daemon/HTTPServer.cpp:1374 +#: daemon/HTTPServer.cpp:1370 msgid "Domain must end with .i2p" msgstr "" -#: daemon/HTTPServer.cpp:1377 +#: daemon/HTTPServer.cpp:1373 msgid "Such destination is not found" msgstr "" -#: daemon/HTTPServer.cpp:1397 +#: daemon/HTTPServer.cpp:1393 msgid "Unknown command" msgstr "" -#: daemon/HTTPServer.cpp:1401 +#: daemon/HTTPServer.cpp:1397 msgid "Command accepted" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:157 +#: libi2pd_client/HTTPProxy.cpp:163 msgid "Proxy error" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:165 +#: libi2pd_client/HTTPProxy.cpp:171 msgid "Proxy info" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:173 +#: libi2pd_client/HTTPProxy.cpp:179 msgid "Proxy error: Host not found" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:174 +#: libi2pd_client/HTTPProxy.cpp:180 msgid "Remote host not found in router's addressbook" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:175 +#: libi2pd_client/HTTPProxy.cpp:181 msgid "You may try to find this host on jump services below" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:273 libi2pd_client/HTTPProxy.cpp:288 -#: libi2pd_client/HTTPProxy.cpp:322 libi2pd_client/HTTPProxy.cpp:365 +#: libi2pd_client/HTTPProxy.cpp:282 libi2pd_client/HTTPProxy.cpp:297 +#: libi2pd_client/HTTPProxy.cpp:331 libi2pd_client/HTTPProxy.cpp:372 msgid "Invalid request" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:273 +#: libi2pd_client/HTTPProxy.cpp:282 msgid "Proxy unable to parse your request" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:288 +#: libi2pd_client/HTTPProxy.cpp:297 msgid "addresshelper is not supported" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:297 libi2pd_client/HTTPProxy.cpp:306 -#: libi2pd_client/HTTPProxy.cpp:385 +#: libi2pd_client/HTTPProxy.cpp:306 libi2pd_client/HTTPProxy.cpp:315 +#: libi2pd_client/HTTPProxy.cpp:392 msgid "Host" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:297 +#: libi2pd_client/HTTPProxy.cpp:306 msgid "added to router's addressbook from helper" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:298 +#: libi2pd_client/HTTPProxy.cpp:307 msgid "Click here to proceed:" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:298 libi2pd_client/HTTPProxy.cpp:308 +#: libi2pd_client/HTTPProxy.cpp:307 libi2pd_client/HTTPProxy.cpp:317 msgid "Continue" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:299 libi2pd_client/HTTPProxy.cpp:309 +#: libi2pd_client/HTTPProxy.cpp:308 libi2pd_client/HTTPProxy.cpp:318 msgid "Addresshelper found" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:306 +#: libi2pd_client/HTTPProxy.cpp:315 msgid "already in router's addressbook" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:307 +#. tr: The "record" means addressbook's record. That message appears when domain was already added to addressbook, but helper link is opened for it. +#: libi2pd_client/HTTPProxy.cpp:316 msgid "Click here to update record:" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:322 +#: libi2pd_client/HTTPProxy.cpp:331 msgid "invalid request uri" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:365 +#: libi2pd_client/HTTPProxy.cpp:372 msgid "Can't detect destination host from request" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:382 libi2pd_client/HTTPProxy.cpp:386 +#: libi2pd_client/HTTPProxy.cpp:389 libi2pd_client/HTTPProxy.cpp:393 msgid "Outproxy failure" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:382 +#: libi2pd_client/HTTPProxy.cpp:389 msgid "bad outproxy settings" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:385 +#: libi2pd_client/HTTPProxy.cpp:392 msgid "not inside I2P network, but outproxy is not enabled" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:474 +#: libi2pd_client/HTTPProxy.cpp:482 msgid "unknown outproxy url" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:480 +#: libi2pd_client/HTTPProxy.cpp:490 msgid "cannot resolve upstream proxy" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:488 +#: libi2pd_client/HTTPProxy.cpp:498 msgid "hostname too long" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:515 +#: libi2pd_client/HTTPProxy.cpp:525 msgid "cannot connect to upstream socks proxy" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:521 +#: libi2pd_client/HTTPProxy.cpp:531 msgid "Cannot negotiate with socks proxy" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:563 +#: libi2pd_client/HTTPProxy.cpp:573 msgid "CONNECT error" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:563 +#: libi2pd_client/HTTPProxy.cpp:573 msgid "Failed to Connect" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:574 libi2pd_client/HTTPProxy.cpp:600 +#: libi2pd_client/HTTPProxy.cpp:584 libi2pd_client/HTTPProxy.cpp:610 msgid "socks proxy error" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:582 +#: libi2pd_client/HTTPProxy.cpp:592 msgid "failed to send request to upstream" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:603 +#: libi2pd_client/HTTPProxy.cpp:613 msgid "No Reply From socks proxy" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:610 +#: libi2pd_client/HTTPProxy.cpp:620 msgid "cannot connect" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:610 +#: libi2pd_client/HTTPProxy.cpp:620 msgid "http out proxy not implemented" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:611 +#: libi2pd_client/HTTPProxy.cpp:621 msgid "cannot connect to upstream http proxy" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:644 +#: libi2pd_client/HTTPProxy.cpp:654 msgid "Host is down" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:644 +#: libi2pd_client/HTTPProxy.cpp:654 msgid "" "Can't create connection to requested host, it may be down. Please try again " "later." diff --git a/contrib/i18n/README.md b/contrib/i18n/README.md index 04779473..57021f60 100644 --- a/contrib/i18n/README.md +++ b/contrib/i18n/README.md @@ -9,8 +9,8 @@ Regex for transforming gettext translations to our format: --- ``` -in: msgid\ \"(.*)\"\nmsgid_plural\ \"(.*)\"\nmsgstr\[0\]\ \"(.*)\"\nmsgstr\[1\]\ \"(.*)\"\n(msgstr\[2\]\ \"(.*)\"\n)?(msgstr\[3\]\ \"(.*)\"\n)?(msgstr\[4\]\ \"(.*)\"\n)?(msgstr\[5\]\ \"(.*)\"\n)? -out: #{"$2", {"$3", "$4", "$6", "$8", "$10"}},\n +in: msgid\ \"(.*)\"\nmsgid_plural\ \"(.*)\"\nmsgstr\[0\]\ \"(.*)\"\n(msgstr\[1\]\ \"(.*)\"\n)?(msgstr\[2\]\ \"(.*)\"\n)?(msgstr\[3\]\ \"(.*)\"\n)?(msgstr\[4\]\ \"(.*)\"\n)?(msgstr\[5\]\ \"(.*)\"\n)? +out: #{"$2", {"$3", "$5", "$7", "$9", "$11"}},\n ``` ``` diff --git a/contrib/i2pd.conf b/contrib/i2pd.conf index 43cf7ddd..b281ce8f 100644 --- a/contrib/i2pd.conf +++ b/contrib/i2pd.conf @@ -75,8 +75,8 @@ ipv4 = true ## Enable communication through ipv6 ipv6 = false -## Enable SSU transport (default = true) -# ssu = true +## Enable SSU transport +ssu = false ## Bandwidth configuration ## L limit bandwidth to 32KBs/sec, O - to 256KBs/sec, P - to 2048KBs/sec, @@ -96,6 +96,22 @@ ipv6 = false ## Note: that mode uses much more network connections and CPU! # floodfill = true +[ntcp2] +## Enable NTCP2 transport (default = true) +# enabled = true +## Publish address in RouterInfo (default = true) +# published = true +## Port for incoming connections (default is global port option value) +# port = 4567 + +[ssu2] +## Enable SSU2 transport +# enabled = true +## Publish address in RouterInfo +# published = true +## Port for incoming connections (default is global port option value or port + 1 if SSU is enabled) +# port = 4567 + [http] ## Web Console settings ## Uncomment and set to 'false' to disable Web Console @@ -110,8 +126,8 @@ port = 7070 # user = i2pd # pass = changeme ## Select webconsole language -## Currently supported english (default), afrikaans, armenian, french, german, -## russian, turkmen, ukrainian and uzbek languages +## Currently supported english (default), afrikaans, armenian, chinese, french, +## german, italian, russian, spanish, turkmen, ukrainian and uzbek languages # lang = english [httpproxy] diff --git a/contrib/rpm/i2pd-git.spec b/contrib/rpm/i2pd-git.spec index dc6aae70..9183b529 100644 --- a/contrib/rpm/i2pd-git.spec +++ b/contrib/rpm/i2pd-git.spec @@ -1,7 +1,7 @@ %define git_hash %(git rev-parse HEAD | cut -c -7) Name: i2pd-git -Version: 2.42.1 +Version: 2.44.0 Release: git%{git_hash}%{?dist} Summary: I2P router written in C++ Conflicts: i2pd @@ -62,9 +62,7 @@ pushd redhat-linux-build %endif %if 0%{?fedora} >= 35 -%if 0%{?fedora} < 37 pushd redhat-linux-build -%endif %else %if 0%{?fedora} >= 33 pushd %{_target_platform} @@ -82,10 +80,8 @@ popd %endif %if 0%{?fedora} >= 33 -%if 0%{?fedora} < 37 popd %endif -%endif %if 0%{?mageia} > 7 popd @@ -99,9 +95,7 @@ pushd redhat-linux-build %endif %if 0%{?fedora} >= 35 -%if 0%{?fedora} < 37 pushd redhat-linux-build -%endif %else %if 0%{?fedora} >= 33 pushd %{_target_platform} @@ -164,6 +158,12 @@ getent passwd i2pd >/dev/null || \ %changelog +* Sun Nov 20 2022 orignal - 2.44.0 +- update to 2.44.0 + +* Mon Aug 22 2022 orignal - 2.43.0 +- update to 2.43.0 + * Tue May 24 2022 r4sas - 2.42.1 - update to 2.42.1 diff --git a/contrib/rpm/i2pd.spec b/contrib/rpm/i2pd.spec index 98c804c6..16734f3f 100644 --- a/contrib/rpm/i2pd.spec +++ b/contrib/rpm/i2pd.spec @@ -1,5 +1,5 @@ Name: i2pd -Version: 2.42.1 +Version: 2.44.0 Release: 1%{?dist} Summary: I2P router written in C++ Conflicts: i2pd-git @@ -59,9 +59,7 @@ pushd redhat-linux-build %endif %if 0%{?fedora} >= 35 -%if 0%{?fedora} < 37 pushd redhat-linux-build -%endif %else %if 0%{?fedora} >= 33 pushd %{_target_platform} @@ -79,10 +77,8 @@ popd %endif %if 0%{?fedora} >= 33 -%if 0%{?fedora} < 37 popd %endif -%endif %if 0%{?mageia} > 7 popd @@ -96,9 +92,7 @@ pushd redhat-linux-build %endif %if 0%{?fedora} >= 35 -%if 0%{?fedora} < 37 pushd redhat-linux-build -%endif %else %if 0%{?fedora} >= 33 pushd %{_target_platform} @@ -161,6 +155,12 @@ getent passwd i2pd >/dev/null || \ %changelog +* Sun Nov 20 2022 orignal - 2.44.0 +- update to 2.44.0 + +* Mon Aug 22 2022 orignal - 2.43.0 +- update to 2.43.0 + * Tue May 24 2022 r4sas - 2.42.1 - update to 2.42.1 diff --git a/daemon/Daemon.cpp b/daemon/Daemon.cpp index c6e6465a..7b09feeb 100644 --- a/daemon/Daemon.cpp +++ b/daemon/Daemon.cpp @@ -31,7 +31,6 @@ #include "Crypto.h" #include "UPnP.h" #include "Timestamp.h" -#include "util.h" #include "I18N.h" namespace i2p @@ -153,115 +152,18 @@ namespace util bool aesni; i2p::config::GetOption("cpuext.aesni", aesni); bool avx; i2p::config::GetOption("cpuext.avx", avx); bool forceCpuExt; i2p::config::GetOption("cpuext.force", forceCpuExt); + bool ssu; i2p::config::GetOption("ssu", ssu); + if (!ssu && i2p::config::IsDefault ("precomputation.elgamal")) + precomputation = false; // we don't elgamal table if no ssu, unless it's specified explicitly i2p::crypto::InitCrypto (precomputation, aesni, avx, forceCpuExt); + i2p::transport::InitAddressFromIface (); // get address4/6 from interfaces + int netID; i2p::config::GetOption("netid", netID); i2p::context.SetNetID (netID); i2p::context.Init (); - bool ipv6; i2p::config::GetOption("ipv6", ipv6); - bool ipv4; i2p::config::GetOption("ipv4", ipv4); - - // ifname -> address - std::string ifname; i2p::config::GetOption("ifname", ifname); - if (ipv4 && i2p::config::IsDefault ("address4")) - { - std::string ifname4; i2p::config::GetOption("ifname4", ifname4); - if (!ifname4.empty ()) - i2p::config::SetOption ("address4", i2p::util::net::GetInterfaceAddress(ifname4, false).to_string ()); // v4 - else if (!ifname.empty ()) - i2p::config::SetOption ("address4", i2p::util::net::GetInterfaceAddress(ifname, false).to_string ()); // v4 - } - if (ipv6 && i2p::config::IsDefault ("address6")) - { - std::string ifname6; i2p::config::GetOption("ifname6", ifname6); - if (!ifname6.empty ()) - i2p::config::SetOption ("address6", i2p::util::net::GetInterfaceAddress(ifname6, true).to_string ()); // v6 - else if (!ifname.empty ()) - i2p::config::SetOption ("address6", i2p::util::net::GetInterfaceAddress(ifname, true).to_string ()); // v6 - } - - bool ygg; i2p::config::GetOption("meshnets.yggdrasil", ygg); - boost::asio::ip::address_v6 yggaddr; - if (ygg) - { - std::string yggaddress; i2p::config::GetOption ("meshnets.yggaddress", yggaddress); - if (!yggaddress.empty ()) - { - yggaddr = boost::asio::ip::address_v6::from_string (yggaddress); - if (yggaddr.is_unspecified () || !i2p::util::net::IsYggdrasilAddress (yggaddr) || - !i2p::util::net::IsLocalAddress (yggaddr)) - { - LogPrint(eLogWarning, "Daemon: Can't find Yggdrasil address ", yggaddress); - ygg = false; - } - } - else - { - yggaddr = i2p::util::net::GetYggdrasilAddress (); - if (yggaddr.is_unspecified ()) - { - LogPrint(eLogWarning, "Daemon: Yggdrasil is not running. Disabled"); - ygg = false; - } - } - } - - uint16_t port; i2p::config::GetOption("port", port); - if (!i2p::config::IsDefault("port")) - { - LogPrint(eLogInfo, "Daemon: Accepting incoming connections at port ", port); - i2p::context.UpdatePort (port); - } - i2p::context.SetSupportsV6 (ipv6); - i2p::context.SetSupportsV4 (ipv4); - i2p::context.SetSupportsMesh (ygg, yggaddr); - - i2p::context.RemoveNTCPAddress (!ipv6); // TODO: remove later - bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); - if (ntcp2) - { - bool published; i2p::config::GetOption("ntcp2.published", published); - if (published) - { - std::string ntcp2proxy; i2p::config::GetOption("ntcp2.proxy", ntcp2proxy); - if (!ntcp2proxy.empty ()) published = false; - } - if (published) - { - uint16_t ntcp2port; i2p::config::GetOption("ntcp2.port", ntcp2port); - if (!ntcp2port) ntcp2port = port; // use standard port - i2p::context.PublishNTCP2Address (ntcp2port, true, ipv4, ipv6, false); // publish - if (ipv6) - { - std::string ipv6Addr; i2p::config::GetOption("ntcp2.addressv6", ipv6Addr); - auto addr = boost::asio::ip::address_v6::from_string (ipv6Addr); - if (!addr.is_unspecified () && addr != boost::asio::ip::address_v6::any ()) - i2p::context.UpdateNTCP2V6Address (addr); // set ipv6 address if configured - } - } - else - i2p::context.PublishNTCP2Address (port, false, ipv4, ipv6, false); // unpublish - } - if (ygg) - { - i2p::context.PublishNTCP2Address (port, true, false, false, true); - i2p::context.UpdateNTCP2V6Address (yggaddr); - if (!ipv4 && !ipv6) - i2p::context.SetStatus (eRouterStatusMesh); - } - bool ssu2; i2p::config::GetOption("ssu2.enabled", ssu2); - if (ssu2) - { - bool published; i2p::config::GetOption("ssu2.published", published); - if (published) - { - uint16_t ssu2port; i2p::config::GetOption("ssu2.port", ssu2port); - i2p::context.PublishSSU2Address (ssu2port, true, ipv4, ipv6); // publish - } - else - i2p::context.PublishSSU2Address (0, false, ipv4, ipv6); // unpublish - } + i2p::transport::InitTransports (); bool transit; i2p::config::GetOption("notransit", transit); i2p::context.SetAcceptsTunnels (!transit); @@ -404,7 +306,7 @@ namespace util i2p::transport::transports.SetCheckReserved(checkInReserved); i2p::transport::transports.Start(ntcp2, ssu, ssu2); - if (i2p::transport::transports.IsBoundSSU() || i2p::transport::transports.IsBoundNTCP2()) + if (i2p::transport::transports.IsBoundSSU() || i2p::transport::transports.IsBoundSSU2() || i2p::transport::transports.IsBoundNTCP2()) LogPrint(eLogInfo, "Daemon: Transports started"); else { diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index a325ad4b..8d57dedb 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -80,7 +80,7 @@ namespace http { const char HTTP_COMMAND_SHUTDOWN_CANCEL[] = "shutdown_cancel"; const char HTTP_COMMAND_SHUTDOWN_NOW[] = "terminate"; const char HTTP_COMMAND_RUN_PEER_TEST[] = "run_peer_test"; - const char HTTP_COMMAND_RELOAD_CONFIG[] = "reload_config"; + const char HTTP_COMMAND_RELOAD_TUNNELS_CONFIG[] = "reload_tunnels_config"; const char HTTP_COMMAND_LOGLEVEL[] = "set_loglevel"; const char HTTP_COMMAND_KILLSTREAM[] = "closestream"; const char HTTP_COMMAND_LIMITTRANSIT[] = "limittransit"; @@ -182,7 +182,7 @@ namespace http { " \r\n" " \r\n" " \r\n" - " Purple I2P Webconsole\r\n"; + " " << tr(/* tr: Webconsole page title */ "Purple I2P Webconsole") << "\r\n"; GetStyles(s); s << "\r\n" @@ -222,7 +222,7 @@ namespace http { s << "" << tr("ERROR") << ": " << string << "
\r\n"; } - static void ShowNetworkStatus (std::stringstream& s, RouterStatus status) + static void ShowNetworkStatus (std::stringstream& s, RouterStatus status, RouterError error) { switch (status) { @@ -232,26 +232,25 @@ namespace http { case eRouterStatusUnknown: s << tr("Unknown"); break; case eRouterStatusProxy: s << tr("Proxy"); break; case eRouterStatusMesh: s << tr("Mesh"); break; - case eRouterStatusError: - { - s << tr("Error"); - switch (i2p::context.GetError ()) - { - case eRouterErrorClockSkew: - s << " - " << tr("Clock skew"); - break; - case eRouterErrorOffline: - s << " - " << tr("Offline"); - break; - case eRouterErrorSymmetricNAT: - s << " - " << tr("Symmetric NAT"); - break; - default: ; - } - break; - } default: s << tr("Unknown"); } + if (error != eRouterErrorNone) + { + s << "
"; + switch (error) + { + case eRouterErrorClockSkew: + s << " - " << tr("Clock skew"); + break; + case eRouterErrorOffline: + s << " - " << tr("Offline"); + break; + case eRouterErrorSymmetricNAT: + s << " - " << tr("Symmetric NAT"); + break; + default: ; + } + } } void ShowStatus (std::stringstream& s, bool includeHiddenContent, i2p::http::OutputFormatEnum outputFormat) @@ -260,12 +259,12 @@ namespace http { ShowUptime(s, i2p::context.GetUptime ()); s << "
\r\n"; s << "" << tr("Network status") << ": "; - ShowNetworkStatus (s, i2p::context.GetStatus ()); + ShowNetworkStatus (s, i2p::context.GetStatus (), i2p::context.GetError ()); s << "
\r\n"; if (i2p::context.SupportsV6 ()) { s << "" << tr("Network status v6") << ": "; - ShowNetworkStatus (s, i2p::context.GetStatusV6 ()); + ShowNetworkStatus (s, i2p::context.GetStatusV6 (), i2p::context.GetErrorV6 ()); s << "
\r\n"; } #if ((!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) || defined(ANDROID_BINARY)) @@ -531,19 +530,21 @@ namespace http { ShowLeaseSetDestination (s, dest, token); // Print table with streams information - s << "\r\n\r\n\r\n"; - s << ""; - s << ""; - s << ""; - s << ""; - s << ""; - s << ""; - s << ""; - s << ""; - s << ""; - s << ""; - s << "\r\n\r\n\r\n"; + s << "
" << tr("Streams") << "
StreamID"; // Stream closing button column - s << "DestinationSentReceivedOutInBufRTTWindowStatus
\r\n\r\n\r\n" + << "" + << "" + << "" + << "" + << "" + << "" + << "" + << "" + << "" + << "" + << "\r\n\r\n\r\n"; for (const auto& it: dest->GetAllStreams ()) { @@ -697,8 +698,7 @@ namespace http { s << "" << tr("Router commands") << "
\r\n
\r\n
\r\n"; s << " " << tr("Run peer test") << "
\r\n"; - - // s << " Reload config
\r\n"; + s << " " << tr("Reload tunnels configuration") << "
\r\n"; if (i2p::context.AcceptsTunnels ()) s << " " << tr("Decline transit tunnels") << "
\r\n"; @@ -739,17 +739,25 @@ namespace http { s << " \r\n"; s << "\r\n
\r\n"; - std::string currLang = i2p::client::context.GetLanguage ()->GetLanguage(); // get current used language - s << "" << tr("Change language") << "
\r\n"; - s << "
\r\n"; - s << " \r\n"; - s << " \r\n"; - s << " \r\n" + << " \r\n" + << " \r\n"; - s << " \r\n"; - s << "\r\n
\r\n"; + + s << " \r\n" + << " \r\n" + << "\r\n
\r\n"; } @@ -783,12 +791,13 @@ namespace http { std::stringstream tmp_s, tmp_s6; uint16_t cnt = 0, cnt6 = 0; for (const auto& it: sessions ) { - if (it.second && it.second->IsEstablished () && !it.second->GetRemoteEndpoint ().address ().is_v6 ()) + auto endpoint = it.second->GetRemoteEndpoint (); + if (it.second && it.second->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->GetRemoteEndpoint ().address ().to_string (); + << endpoint.address ().to_string () << ":" << endpoint.port (); if (!it.second->IsOutgoing ()) tmp_s << " ⇒ "; tmp_s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; if (it.second->GetRelayTag ()) @@ -796,12 +805,12 @@ namespace http { tmp_s << "
\r\n" << std::endl; cnt++; } - if (it.second && it.second->IsEstablished () && it.second->GetRemoteEndpoint ().address ().is_v6 ()) + if (it.second && it.second->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->GetRemoteEndpoint ().address ().to_string () << "]"; + << "[" << endpoint.address ().to_string () << "]:" << endpoint.port (); if (!it.second->IsOutgoing ()) tmp_s6 << " ⇒ "; tmp_s6 << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; if (it.second->GetRelayTag ()) @@ -1236,7 +1245,7 @@ namespace http { std::string cmd = params["cmd"]; if (cmd == HTTP_COMMAND_RUN_PEER_TEST) i2p::transport::transports.PeerTest (); - else if (cmd == HTTP_COMMAND_RELOAD_CONFIG) + else if (cmd == HTTP_COMMAND_RELOAD_TUNNELS_CONFIG) i2p::client::context.ReloadConfig (); else if (cmd == HTTP_COMMAND_ENABLE_TRANSIT) i2p::context.SetAcceptsTunnels (true); diff --git a/daemon/I2PControl.cpp b/daemon/I2PControl.cpp index 46a219bf..da2443fd 100644 --- a/daemon/I2PControl.cpp +++ b/daemon/I2PControl.cpp @@ -14,25 +14,17 @@ // Use global placeholders from boost introduced when local_time.hpp is loaded #define BOOST_BIND_GLOBAL_PLACEHOLDERS -#include #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 @@ -69,44 +61,18 @@ namespace client m_MethodHandlers["Authenticate"] = &I2PControlService::AuthenticateHandler; m_MethodHandlers["Echo"] = &I2PControlService::EchoHandler; m_MethodHandlers["I2PControl"] = &I2PControlService::I2PControlHandler; - m_MethodHandlers["RouterInfo"] = &I2PControlService::RouterInfoHandler; + m_MethodHandlers["RouterInfo"] = &I2PControlHandlers::RouterInfoHandler; m_MethodHandlers["RouterManager"] = &I2PControlService::RouterManagerHandler; - m_MethodHandlers["NetworkSetting"] = &I2PControlService::NetworkSettingHandler; - m_MethodHandlers["ClientServicesInfo"] = &I2PControlService::ClientServicesInfoHandler; + 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 () @@ -280,37 +246,6 @@ namespace client } } - void I2PControlService::InsertParam (std::ostringstream& ss, const std::string& name, int value) const - { - ss << "\"" << name << "\":" << value; - } - - void I2PControlService::InsertParam (std::ostringstream& ss, const std::string& name, const std::string& value, bool quotes) const - { - ss << "\"" << name << "\":"; - if (value.length () > 0) - { - if (quotes) - ss << "\"" << value << "\""; - else - ss << value; - } - else - ss << "null"; - } - - void I2PControlService::InsertParam (std::ostringstream& ss, const std::string& name, double value) const - { - ss << "\"" << name << "\":" << std::fixed << std::setprecision(2) << value; - } - - void I2PControlService::InsertParam (std::ostringstream& ss, const std::string& name, const boost::property_tree::ptree& value) const - { - std::ostringstream buf; - boost::property_tree::write_json (buf, value, false); - ss << "\"" << name << "\":" << buf.str(); - } - void I2PControlService::SendResponse (std::shared_ptr socket, std::shared_ptr buf, std::ostringstream& response, bool isHtml) { @@ -396,91 +331,6 @@ namespace client m_Tokens.clear (); } -// RouterInfo - - void I2PControlService::RouterInfoHandler (const boost::property_tree::ptree& params, std::ostringstream& results) - { - bool first = true; - for (auto it = params.begin (); it != params.end (); it++) - { - LogPrint (eLogDebug, "I2PControl: RouterInfo request: ", it->first); - auto it1 = m_RouterInfoHandlers.find (it->first); - if (it1 != m_RouterInfoHandlers.end ()) - { - if (!first) results << ","; - else first = false; - (this->*(it1->second))(results); - } - else - LogPrint (eLogError, "I2PControl: RouterInfo unknown request ", it->first); - } - } - - void I2PControlService::UptimeHandler (std::ostringstream& results) - { - InsertParam (results, "i2p.router.uptime", std::to_string (i2p::context.GetUptime ()*1000LL), false); - } - - void I2PControlService::VersionHandler (std::ostringstream& results) - { - InsertParam (results, "i2p.router.version", VERSION); - } - - void I2PControlService::StatusHandler (std::ostringstream& results) - { - auto dest = i2p::client::context.GetSharedLocalDestination (); - InsertParam (results, "i2p.router.status", (dest && dest->IsReady ()) ? "1" : "0"); - } - - void I2PControlService::NetDbKnownPeersHandler (std::ostringstream& results) - { - InsertParam (results, "i2p.router.netdb.knownpeers", i2p::data::netdb.GetNumRouters ()); - } - - void I2PControlService::NetDbActivePeersHandler (std::ostringstream& results) - { - InsertParam (results, "i2p.router.netdb.activepeers", (int)i2p::transport::transports.GetPeers ().size ()); - } - - void I2PControlService::NetStatusHandler (std::ostringstream& results) - { - InsertParam (results, "i2p.router.net.status", (int)i2p::context.GetStatus ()); - } - - void I2PControlService::TunnelsParticipatingHandler (std::ostringstream& results) - { - int transit = i2p::tunnel::tunnels.GetTransitTunnels ().size (); - InsertParam (results, "i2p.router.net.tunnels.participating", transit); - } - - void I2PControlService::TunnelsSuccessRateHandler (std::ostringstream& results) - { - int rate = i2p::tunnel::tunnels.GetTunnelCreationSuccessRate (); - InsertParam (results, "i2p.router.net.tunnels.successrate", rate); - } - - void I2PControlService::InboundBandwidth1S (std::ostringstream& results) - { - double bw = i2p::transport::transports.GetInBandwidth (); - InsertParam (results, "i2p.router.net.bw.inbound.1s", bw); - } - - void I2PControlService::OutboundBandwidth1S (std::ostringstream& results) - { - double bw = i2p::transport::transports.GetOutBandwidth (); - InsertParam (results, "i2p.router.net.bw.outbound.1s", bw); - } - - void I2PControlService::NetTotalReceivedBytes (std::ostringstream& results) - { - InsertParam (results, "i2p.router.net.total.received.bytes", (double)i2p::transport::transports.GetTotalReceivedBytes ()); - } - - void I2PControlService::NetTotalSentBytes (std::ostringstream& results) - { - InsertParam (results, "i2p.router.net.total.sent.bytes", (double)i2p::transport::transports.GetTotalSentBytes ()); - } - // RouterManager @@ -532,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) { @@ -611,178 +430,5 @@ namespace client } 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->GetLocalDestination ()->GetNickname (); - auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); - sam_session.put("name", name); - sam_session.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident)); - - for (const auto& socket: sam->ListSockets(it.first)) - { - boost::property_tree::ptree stream; - stream.put("type", socket->GetSocketType ()); - stream.put("peer", socket->GetSocket ().remote_endpoint()); - - sam_session_sockets.push_back(std::make_pair("", stream)); - } - sam_session.add_child("sockets", sam_session_sockets); - sam_sessions.add_child(it.first, sam_session); - } - - pt.add_child("sessions", sam_sessions); - } - else - pt.put("enabled", false); - - InsertParam (results, "SAM", pt); - } - - void I2PControlService::BOBInfoHandler (std::ostringstream& results) - { - boost::property_tree::ptree pt; - auto bob = i2p::client::context.GetBOBCommandChannel (); - if (bob) - { - /* TODO more info */ - pt.put("enabled", true); - } - else - pt.put("enabled", false); - - InsertParam (results, "BOB", pt); - } - - void I2PControlService::I2CPInfoHandler (std::ostringstream& results) - { - boost::property_tree::ptree pt; - auto i2cp = i2p::client::context.GetI2CPServer (); - if (i2cp) - { - /* TODO more info */ - pt.put("enabled", true); - } - else - pt.put("enabled", false); - - InsertParam (results, "I2CP", pt); - } } } diff --git a/daemon/I2PControl.h b/daemon/I2PControl.h index 32b8933c..af152631 100644 --- a/daemon/I2PControl.h +++ b/daemon/I2PControl.h @@ -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, bool quotes = true) const; - void InsertParam (std::ostringstream& ss, const std::string& name, const boost::property_tree::ptree& value) const; - // methods typedef void (I2PControlService::*MethodHandler)(const boost::property_tree::ptree& params, std::ostringstream& results); void AuthenticateHandler (const boost::property_tree::ptree& params, std::ostringstream& results); void EchoHandler (const boost::property_tree::ptree& params, std::ostringstream& results); void I2PControlHandler (const boost::property_tree::ptree& params, std::ostringstream& results); - void RouterInfoHandler (const boost::property_tree::ptree& params, std::ostringstream& results); void RouterManagerHandler (const boost::property_tree::ptree& params, std::ostringstream& results); - void NetworkSettingHandler (const boost::property_tree::ptree& params, std::ostringstream& results); - void ClientServicesInfoHandler (const boost::property_tree::ptree& params, std::ostringstream& results); // I2PControl typedef void (I2PControlService::*I2PControlRequestHandler)(const std::string& value); void PasswordHandler (const std::string& value); - // RouterInfo - typedef void (I2PControlService::*RouterInfoRequestHandler)(std::ostringstream& results); - void UptimeHandler (std::ostringstream& results); - void VersionHandler (std::ostringstream& results); - void StatusHandler (std::ostringstream& results); - void NetDbKnownPeersHandler (std::ostringstream& results); - void NetDbActivePeersHandler (std::ostringstream& results); - void NetStatusHandler (std::ostringstream& results); - void TunnelsParticipatingHandler (std::ostringstream& results); - void TunnelsSuccessRateHandler (std::ostringstream& results); - void InboundBandwidth1S (std::ostringstream& results); - void OutboundBandwidth1S (std::ostringstream& results); - void NetTotalReceivedBytes (std::ostringstream& results); - void NetTotalSentBytes (std::ostringstream& results); - // RouterManager typedef void (I2PControlService::*RouterManagerRequestHandler)(std::ostringstream& results); void ShutdownHandler (std::ostringstream& results); void ShutdownGracefulHandler (std::ostringstream& results); void ReseedHandler (std::ostringstream& results); - // NetworkSetting - typedef void (I2PControlService::*NetworkSettingRequestHandler)(const std::string& value, std::ostringstream& results); - void InboundBandwidthLimit (const std::string& value, std::ostringstream& results); - void OutboundBandwidthLimit (const std::string& value, std::ostringstream& results); - - // ClientServicesInfo - typedef void (I2PControlService::*ClientServicesInfoRequestHandler)(std::ostringstream& results); - void I2PTunnelInfoHandler (std::ostringstream& results); - void HTTPProxyInfoHandler (std::ostringstream& results); - void SOCKSInfoHandler (std::ostringstream& results); - void SAMInfoHandler (std::ostringstream& results); - void BOBInfoHandler (std::ostringstream& results); - void I2CPInfoHandler (std::ostringstream& results); - private: std::string m_Password; @@ -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..15763948 --- /dev/null +++ b/daemon/I2PControlHandlers.cpp @@ -0,0 +1,376 @@ +/* +* 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.outbound.1s"] = &I2PControlHandlers::OutboundBandwidth1S; + 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::OutboundBandwidth1S (std::ostringstream& results) + { + double bw = i2p::transport::transports.GetOutBandwidth (); + InsertParam (results, "i2p.router.net.bw.outbound.1s", 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..e33a19fc --- /dev/null +++ b/daemon/I2PControlHandlers.h @@ -0,0 +1,80 @@ +/* +* 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 OutboundBandwidth1S (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/debian/changelog b/debian/changelog index 3b4d88f6..accbf46a 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,15 @@ +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 diff --git a/i18n/Chinese.cpp b/i18n/Chinese.cpp new file mode 100644 index 00000000..2f6c14e2 --- /dev/null +++ b/i18n/Chinese.cpp @@ -0,0 +1,217 @@ +/* +* Copyright (c) 2022, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#include +#include +#include +#include +#include "I18N.h" + +// Simplified Chinese localization file +// This is an example translation file without strings in it. + +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 + { + {"KiB", "KiB"}, + {"MiB", "MiB"}, + {"GiB", "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", "Mesh组网"}, + {"Error", "错误"}, + {"Clock skew", "时钟偏移"}, + {"Offline", "离线"}, + {"Symmetric NAT", "对称 NAT"}, + {"Uptime", "运行时间"}, + {"Network status", "IPv4 网络状态"}, + {"Network status v6", "IPv6 网络状态"}, + {"Stopping in", "距停止还有:"}, + {"Family", "家族"}, + {"Tunnel creation success rate", "隧道创建成功率"}, + {"Received", "已接收"}, + {"KiB/s", "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", "入站隧道"}, + {"ms", "毫秒"}, + {"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", "未到期的租约"}, + {"Gateway", "网关"}, + {"TunnelID", "隧道 ID"}, + {"EndDate", "结束日期"}, + {"not floodfill", "非洪泛"}, + {"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", "返回目标页面"}, + {"You will be redirected in 5 seconds", "您将在5秒内被重定向"}, + {"Transit tunnels count must not exceed 65535", "中转隧道数量不能超过 65535"}, + {"Back to commands list", "返回命令列表"}, + {"Register at reg.i2p", "在 reg.i2p 注册域名"}, + {"Description", "描述"}, + {"A bit information about service on domain", "在此域名上运行的服务的一些信息"}, + {"Submit", "提交"}, + {"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", "主机"}, + {"added to router's addressbook from helper", "将此地址从地址助手添加到路由地址簿"}, + {"Click here to proceed:", "点击此处继续:"}, + {"Continue", "继续"}, + {"Addresshelper found", "已找到地址助手"}, + {"already in router's addressbook", "已在路由地址簿中"}, + {"Click here to update record:", "点击此处更新地址簿记录"}, + {"invalid request uri", "无效的 URL 请求"}, + {"Can't detect destination host from request", "无法从请求中检测到目标主机"}, + {"Outproxy failure", "出口代理故障"}, + {"bad outproxy settings", "错误的出口代理设置"}, + {"not inside I2P network, but outproxy is not enabled", "该地址不在 I2P 网络内,但未启用出口代理"}, + {"unknown outproxy url", "未知的出口代理地址"}, + {"cannot resolve upstream proxy", "无法解析上游代理"}, + {"hostname 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 + { + {"days", {"日"}}, + {"hours", {"时"}}, + {"minutes", {"分"}}, + {"seconds", {"秒"}}, + {"", {""}}, + }; + + 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 index d41d215b..1a49ddfc 100644 --- a/i18n/French.cpp +++ b/i18n/French.cpp @@ -35,24 +35,33 @@ namespace french // language namespace {"MiB", "Mio"}, {"GiB", "Gio"}, {"building", "En construction"}, - {"failed", "echoué"}, + {"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é"}, {"Error", "Erreur"}, + {"Clock skew", "Horloge décalée"}, {"Offline", "Hors ligne"}, + {"Symmetric NAT", "NAT symétrique"}, {"Uptime", "Temps de fonctionnement"}, {"Network status", "État du réseau"}, {"Network status v6", "État du réseau v6"}, @@ -62,24 +71,124 @@ namespace french // language namespace {"Received", "Reçu"}, {"KiB/s", "kio/s"}, {"Sent", "Envoyé"}, - {"Transit", "Transit"}, - {"Hidden content. Press on text to see.", "Contenu caché. Cliquez sur le texte pour regarder."}, + {"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"}, {"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"}, + {"Inbound tunnels", "Tunnels entrants"}, {"ms", "ms"}, {"Outbound tunnels", "Tunnels sortants"}, + {"Tags", "Balises"}, + {"Incoming", "Entrant"}, + {"Outgoing", "Sortant"}, {"Destination", "Destination"}, + {"Amount", "Quantité"}, + {"Incoming Tags", "Balises entrantes"}, + {"Tags sessions", "Sessions des balises"}, + {"Status", "Statut"}, {"Local Destination", "Destination locale"}, + {"Streams", "Flux"}, + {"Close stream", "Fermer le flux"}, + {"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"}, + {"Queue size", "Longueur de la file"}, + {"Run peer test", "Lancer test des pairs"}, + {"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"}, + {"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 5 seconds", "Vous allez être redirigé dans cinq secondes"}, + {"Transit tunnels count must not exceed 65535", "Le nombre de tunnels transitoires ne doit pas dépasser 65535"}, + {"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"}, + {"Such destination is not found", "Cette destination est introuvable"}, + {"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", "Hôte"}, + {"added to router's addressbook from helper", "Ajouté au carnet d'adresse du routeur par l'assistant"}, + {"Click here to proceed:", "Cliquez ici pour continuer:"}, + {"Continue", "Continuer"}, + {"Addresshelper found", "Assistant d'adresse trouvé"}, + {"already in router's addressbook", "déjà dans le carnet d'adresses du routeur"}, + {"Click here to update record:", "Cliquez ici pour mettre à jour le carnet 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"}, + {"not inside I2P network, but outproxy is not enabled", "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 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."}, {"", ""}, }; diff --git a/i18n/German.cpp b/i18n/German.cpp index fcef4cff..489a93a7 100644 --- a/i18n/German.cpp +++ b/i18n/German.cpp @@ -36,14 +36,15 @@ namespace german // language namespace {"GiB", "GiB"}, {"building", "In Bau"}, {"failed", "fehlgeschlagen"}, - {"expiring", "läuft ab in"}, + {"expiring", "läuft ab"}, {"established", "hergestellt"}, {"unknown", "Unbekannt"}, - {"exploratory", "erforschende"}, - {"i2pd webconsole", "i2pd Webkonsole"}, + {"exploratory", "erforschend"}, + {"Purple I2P Webconsole", "Purple I2P-Webkonsole"}, + {"i2pd webconsole", "i2pd-Webkonsole"}, {"Main page", "Startseite"}, - {"Router commands", "Router Befehle"}, - {"Local Destinations", "Lokale Destination"}, + {"Router commands", "Routerbefehle"}, + {"Local Destinations", "Lokale Ziele"}, {"LeaseSets", "LeaseSets"}, {"Tunnels", "Tunnel"}, {"Transit Tunnels", "Transittunnel"}, @@ -53,7 +54,7 @@ namespace german // language namespace {"ERROR", "FEHLER"}, {"OK", "OK"}, {"Testing", "Testen"}, - {"Firewalled", "Hinter eine Firewall"}, + {"Firewalled", "Hinter einer Firewall"}, {"Unknown", "Unbekannt"}, {"Proxy", "Proxy"}, {"Mesh", "Mesh"}, @@ -81,15 +82,15 @@ namespace german // language namespace {"supported", "unterstützt"}, {"Routers", "Router"}, {"Floodfills", "Floodfills"}, - {"Client Tunnels", "Klienttunnel"}, + {"Client Tunnels", "Clienttunnel"}, {"Services", "Services"}, {"Enabled", "Aktiviert"}, {"Disabled", "Deaktiviert"}, - {"Encrypted B33 address", "Verschlüsselte B33 Adresse"}, - {"Address registration line", "Adresseregistrierungszeile"}, + {"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."}, + {"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"}, @@ -99,15 +100,15 @@ namespace german // language namespace {"Tags", "Tags"}, {"Incoming", "Eingehend"}, {"Outgoing", "Ausgehend"}, - {"Destination", "Destination"}, + {"Destination", "Ziel"}, {"Amount", "Anzahl"}, {"Incoming Tags", "Eingehende Tags"}, - {"Tags sessions", "Tags Sitzungen"}, + {"Tags sessions", "Tags-Sitzungen"}, {"Status", "Status"}, - {"Local Destination", "Lokale Destination"}, + {"Local Destination", "Lokales Ziel"}, {"Streams", "Streams"}, {"Close stream", "Stream schließen"}, - {"I2CP session not found", "I2CP Sitzung nicht gefunden"}, + {"I2CP session not found", "I2CP-Sitzung nicht gefunden"}, {"I2CP is not enabled", "I2CP ist nicht aktiviert"}, {"Invalid", "Ungültig"}, {"Store type", "Speichertyp"}, @@ -117,67 +118,67 @@ namespace german // language namespace {"TunnelID", "TunnelID"}, {"EndDate", "Enddatum"}, {"not floodfill", "kein Floodfill"}, - {"Queue size", "Warteschlangengröße"}, - {"Run peer test", "Peer-Test ausführen"}, + {"Queue size", "Größe der Warteschlange"}, + {"Run peer test", "Peer-Test durchführen"}, {"Decline transit tunnels", "Transittunnel ablehnen"}, {"Accept transit tunnels", "Transittunnel akzeptieren"}, - {"Cancel graceful shutdown", "Beende das kontrollierte herunterfahren"}, + {"Cancel graceful shutdown", "Beende das kontrollierte Herunterfahren"}, {"Start graceful shutdown", "Starte das kontrollierte Herunterfahren"}, {"Force shutdown", "Herunterfahren erzwingen"}, - {"Reload external CSS styles", "Lade externe CSS-Styles neu"}, + {"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", "Verändern"}, + {"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"}, + {"SAM session not found", "SAM-Sitzung nicht gefunden"}, + {"SAM Session", "SAM-Sitzung"}, {"Server Tunnels", "Servertunnel"}, - {"Client Forwards", "Klient-Weiterleitungen"}, + {"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", "Destination nicht gefunden"}, + {"Destination not found", "Ziel nicht gefunden"}, {"StreamID can't be null", "StreamID kann nicht null sein"}, - {"Return to destination page", "Zurück zur Destination-Seite"}, + {"Return to destination page", "Zurück zur Ziel-Seite"}, {"You will be redirected in 5 seconds", "Du wirst in 5 Sekunden weitergeleitet"}, {"Transit tunnels count must not exceed 65535", "Es darf maximal 65535 Transittunnel geben"}, - {"Back to commands list", "Zurück zur Kommandoliste"}, + {"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 bisschen Informationen über den Service auf der Domain"}, - {"Submit", "Einreichen"}, - {"Domain can't end with .b32.i2p", "Domain kann nicht mit .b32.i2p enden"}, - {"Domain must end with .i2p", "Domain muss mit .i2p enden"}, - {"Such destination is not found", "Eine solche Destination konnte nicht gefunden werden"}, + {"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 einen der Jump-Services unten finden"}, + {"Remote host not found in router's addressbook", "Remote-Host nicht im Router-Adressbuch gefunden"}, + {"You may try to find this host on jump services below", "Vielleicht kannst du diesen Host auf einem der nachfolgenden Jump-Services finden"}, {"Invalid request", "Ungültige Anfrage"}, - {"Proxy unable to parse your request", "Proxy konnte die Anfrage nicht interpretieren"}, - {"addresshelper is not supported", "addresshelper wird nicht unterstützt"}, + {"Proxy unable to parse your request", "Proxy konnte die Anfrage nicht verarbeiten"}, + {"addresshelper is not supported", "Addresshelfer wird nicht unterstützt"}, {"Host", "Host"}, - {"added to router's addressbook from helper", "vom Helfer zum Router Adressbuch hinzugefügt"}, + {"added to router's addressbook from helper", "vom Helfer zum Router-Adressbuch hinzugefügt"}, {"Click here to proceed:", "Klicke hier um fortzufahren:"}, {"Continue", "Fortsetzen"}, {"Addresshelper found", "Adresshelfer gefunden"}, {"already in router's addressbook", "bereits im Adressbuch des Routers"}, {"Click here to update record:", "Klicke hier, um den Eintrag zu aktualisieren:"}, {"invalid request uri", "ungültige Anfrage-URI"}, - {"Can't detect destination host from request", "Kann Anhand der Anfrage den Destination-Host nicht erkennen"}, + {"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"}, - {"not inside I2P network, but outproxy is not enabled", "nicht innerhalb des I2P-Netzwerks, aber Outproxy ist nicht aktiviert"}, + {"not inside I2P network, but outproxy is not enabled", "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 too long", "Hostname zu lang"}, @@ -192,7 +193,7 @@ namespace german // language namespace {"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 aufbaunen, vielleicht ist es offline. Versuche es später noch einmal."}, + {"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."}, {"", ""}, }; diff --git a/i18n/I18N_langs.h b/i18n/I18N_langs.h index f3fc6691..da70e578 100644 --- a/i18n/I18N_langs.h +++ b/i18n/I18N_langs.h @@ -73,10 +73,13 @@ namespace i18n // Add localization here with language name as namespace namespace afrikaans { std::shared_ptr GetLocale (); } namespace armenian { std::shared_ptr GetLocale (); } + namespace chinese { std::shared_ptr GetLocale (); } namespace english { std::shared_ptr GetLocale (); } namespace french { std::shared_ptr GetLocale (); } namespace german { std::shared_ptr GetLocale (); } + namespace italian { std::shared_ptr GetLocale (); } namespace russian { std::shared_ptr GetLocale (); } + namespace spanish { std::shared_ptr GetLocale (); } namespace turkmen { std::shared_ptr GetLocale (); } namespace ukrainian { std::shared_ptr GetLocale (); } namespace uzbek { std::shared_ptr GetLocale (); } @@ -87,13 +90,16 @@ namespace i18n static std::map languages { { "afrikaans", {"Afrikaans", "af", i2p::i18n::afrikaans::GetLocale} }, - { "armenian", {"հայերէն", "hy", i2p::i18n::armenian::GetLocale} }, + { "armenian", {"hայերէն", "hy", i2p::i18n::armenian::GetLocale} }, + { "chinese", {"简体字", "zh-CN", i2p::i18n::chinese::GetLocale} }, { "english", {"English", "en", i2p::i18n::english::GetLocale} }, { "french", {"Français", "fr", i2p::i18n::french::GetLocale} }, { "german", {"Deutsch", "de", i2p::i18n::german::GetLocale} }, - { "russian", {"русский язык", "ru", i2p::i18n::russian::GetLocale} }, - { "turkmen", {"türkmen dili", "tk", i2p::i18n::turkmen::GetLocale} }, - { "ukrainian", {"украї́нська мо́ва", "uk", i2p::i18n::ukrainian::GetLocale} }, + { "italian", {"Italiano", "it", i2p::i18n::italian::GetLocale} }, + { "russian", {"Русский язык", "ru", i2p::i18n::russian::GetLocale} }, + { "spanish", {"Español", "es", i2p::i18n::spanish::GetLocale} }, + { "turkmen", {"Türkmen dili", "tk", i2p::i18n::turkmen::GetLocale} }, + { "ukrainian", {"Украї́нська мо́ва", "uk", i2p::i18n::ukrainian::GetLocale} }, { "uzbek", {"Oʻzbek", "uz", i2p::i18n::uzbek::GetLocale} }, }; diff --git a/i18n/Italian.cpp b/i18n/Italian.cpp new file mode 100644 index 00000000..ef2e26d0 --- /dev/null +++ b/i18n/Italian.cpp @@ -0,0 +1,216 @@ +/* +* Copyright (c) 2022, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#include +#include +#include +#include +#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 + { + {"KiB", "KiB"}, + {"MiB", "MiB"}, + {"GiB", "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"}, + {"Error", "Errore"}, + {"Clock skew", "Orologio disallineato"}, + {"Offline", "Disconnesso"}, + {"Symmetric NAT", "NAT simmetrico"}, + {"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"}, + {"KiB/s", "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"}, + {"ms", "ms"}, + {"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"}, + {"not floodfill", "no floodfill"}, + {"Queue size", "Dimensione della coda"}, + {"Run peer test", "Esegui il test dei peer"}, + {"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 5 seconds", "Verrai reindirizzato in 5 secondi"}, + {"Transit tunnels count must not exceed 65535", "Il numero di tunnel di transito non può superare i 65535"}, + {"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", "Host"}, + {"added to router's addressbook from helper", "aggiunto alla rubrica tramite l'helper"}, + {"Click here to proceed:", "Clicca qui per procedere:"}, + {"Continue", "Continua"}, + {"Addresshelper found", "Addresshelper trovato"}, + {"already in router's addressbook", "già presente nella rubrica del router"}, + {"Click here to update record:", "Clicca qui per aggiornare l'elemento:"}, + {"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"}, + {"not inside I2P network, but outproxy is not enabled", "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 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 proxy http a monte"}, + {"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 + { + {"days", {"giorno", "giorni"}}, + {"hours", {"ora", "ore"}}, + {"minutes", {"minuto", "minuti"}}, + {"seconds", {"secondo", "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/Spanish.cpp b/i18n/Spanish.cpp new file mode 100644 index 00000000..a2f53927 --- /dev/null +++ b/i18n/Spanish.cpp @@ -0,0 +1,216 @@ +/* +* Copyright (c) 2022, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#include +#include +#include +#include +#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 + { + {"KiB", "KiB"}, + {"MiB", "MiB"}, + {"GiB", "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"}, + {"Error", "Error"}, + {"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"}, + {"KiB/s", "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"}, + {"ms", "ms"}, + {"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"}, + {"not floodfill", "no inundado"}, + {"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"}, + {"You will be redirected in 5 seconds", "Serás redirigido en 5 segundos"}, + {"Transit tunnels count must not exceed 65535", "La cantidad de túneles de tránsito no puede exceder 65535"}, + {"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"}, + {"addresshelper is not supported", "ayudante de dirección no soportado"}, + {"Host", "Dominio"}, + {"added to router's addressbook from helper", "añadido a la libreta de direcciones desde el ayudante"}, + {"Click here to proceed:", "Haga clic aquí para continuar:"}, + {"Continue", "Continuar"}, + {"Addresshelper found", "Se encontró ayudante de dirección"}, + {"already in router's addressbook", "ya se encontró en libreta de direcciones"}, + {"Click here to update record:", "Haga clic aquí para actualizar el registro:"}, + {"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"}, + {"not inside I2P network, but outproxy is not enabled", "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 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 + { + {"days", {"día", "días"}}, + {"hours", {"hora", "horas"}}, + {"minutes", {"minuto", "minutos"}}, + {"seconds", {"segundo", "segundos"}}, + {"", {"", ""}}, + }; + + std::shared_ptr GetLocale() + { + return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); + } + +} // language +} // i18n +} // i2p diff --git a/libi2pd/Config.cpp b/libi2pd/Config.cpp index 56da9b7c..b3563c9d 100644 --- a/libi2pd/Config.cpp +++ b/libi2pd/Config.cpp @@ -64,7 +64,7 @@ namespace config { ("bandwidth", value()->default_value(""), "Transit traffic bandwidth limit: integer in KBps or letters: L (32), O (256), P (2048), X (>9000)") ("share", value()->default_value(100), "Limit of transit traffic from max bandwidth in percents. (default: 100)") ("ntcp", bool_switch()->default_value(false), "Ignored. Always false") - ("ssu", bool_switch()->default_value(true), "Enable SSU transport (default: enabled)") + ("ssu", bool_switch()->default_value(false), "Enable SSU transport (default: disabled)") ("ntcpproxy", value()->default_value(""), "Ignored") #ifdef _WIN32 ("svcctl", value()->default_value(""), "Ignored") @@ -274,9 +274,12 @@ namespace config { options_description ssu2("SSU2 Options"); ssu2.add_options() - ("ssu2.enabled", value()->default_value(false), "Enable SSU2 (default: disabled)") - ("ssu2.published", value()->default_value(false), "Publish SSU2 (default: disabled)") + ("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"); diff --git a/libi2pd/Datagram.h b/libi2pd/Datagram.h index a55c8edf..8f3d5ceb 100644 --- a/libi2pd/Datagram.h +++ b/libi2pd/Datagram.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2021, 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" diff --git a/libi2pd/Destination.cpp b/libi2pd/Destination.cpp index 719f5830..c39a2533 100644 --- a/libi2pd/Destination.cpp +++ b/libi2pd/Destination.cpp @@ -1096,7 +1096,13 @@ namespace client } 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 (); @@ -1129,6 +1135,35 @@ namespace client }); } + template + std::shared_ptr ClientDestination::CreateStreamSync (const Dest& dest, int port) + { + std::shared_ptr stream; + std::condition_variable streamRequestComplete; + std::mutex streamRequestCompleteMutex; + std::unique_lock l(streamRequestCompleteMutex); + CreateStream ( + [&streamRequestComplete, &streamRequestCompleteMutex, &stream](std::shared_ptr s) + { + stream = s; + std::unique_lock l(streamRequestCompleteMutex); + streamRequestComplete.notify_all (); + }, + dest, port); + streamRequestComplete.wait (l); + return stream; + } + + std::shared_ptr ClientDestination::CreateStream (const i2p::data::IdentHash& dest, int port) + { + return CreateStreamSync (dest, port); + } + + std::shared_ptr ClientDestination::CreateStream (std::shared_ptr dest, int port) + { + return CreateStreamSync (dest, port); + } + std::shared_ptr ClientDestination::CreateStream (std::shared_ptr remote, int port) { if (m_StreamingDestination) diff --git a/libi2pd/Destination.h b/libi2pd/Destination.h index 4b08ec51..9d369a92 100644 --- a/libi2pd/Destination.h +++ b/libi2pd/Destination.h @@ -247,6 +247,8 @@ namespace client // 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 (const i2p::data::IdentHash& dest, int port = 0); // sync + std::shared_ptr CreateStream (std::shared_ptr dest, int port = 0); // sync std::shared_ptr CreateStream (std::shared_ptr remote, int port = 0); void SendPing (const i2p::data::IdentHash& to); void SendPing (std::shared_ptr to); @@ -282,6 +284,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, int port); + private: i2p::data::PrivateKeys m_Keys; diff --git a/libi2pd/HTTP.cpp b/libi2pd/HTTP.cpp index e994b9b3..8791eadb 100644 --- a/libi2pd/HTTP.cpp +++ b/libi2pd/HTTP.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2021, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -135,6 +135,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; diff --git a/libi2pd/Identity.cpp b/libi2pd/Identity.cpp index cff0c37d..64d87f74 100644 --- a/libi2pd/Identity.cpp +++ b/libi2pd/Identity.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 * @@ -49,13 +49,30 @@ namespace data IdentityEx::IdentityEx(const uint8_t * publicKey, const uint8_t * signingKey, SigningKeyType type, CryptoKeyType cryptoType) { + /*uint8_t randomPaddingBlock[32]; + RAND_bytes (randomPaddingBlock, 32);*/ if (cryptoType == CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) { - memcpy (m_StandardIdentity.publicKey, publicKey, 32); - RAND_bytes (m_StandardIdentity.publicKey + 32, 224); + /*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);*/ + if (publicKey) + { + memcpy (m_StandardIdentity.publicKey, publicKey, 32); + RAND_bytes (m_StandardIdentity.publicKey + 32, 224); + } + else + RAND_bytes (m_StandardIdentity.publicKey, 256); } else - memcpy (m_StandardIdentity.publicKey, publicKey, 256); + { + if (publicKey) + memcpy (m_StandardIdentity.publicKey, publicKey, 256); + else + RAND_bytes (m_StandardIdentity.publicKey, 256); + /*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; @@ -93,7 +110,9 @@ 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);*/ + RAND_bytes (m_StandardIdentity.signingKey, 96); memcpy (m_StandardIdentity.signingKey + padding, signingKey, i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH); break; } @@ -695,7 +714,7 @@ namespace data 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) { @@ -705,9 +724,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; diff --git a/libi2pd/Identity.h b/libi2pd/Identity.h index 10f1d5ed..d5a2da21 100644 --- a/libi2pd/Identity.h +++ b/libi2pd/Identity.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2021, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -171,7 +171,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); diff --git a/libi2pd/LeaseSet.cpp b/libi2pd/LeaseSet.cpp index c844ab60..12b064b5 100644 --- a/libi2pd/LeaseSet.cpp +++ b/libi2pd/LeaseSet.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2021, The PurpleI2P Project +* Copyright (c) 2013-2022, 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); } @@ -59,9 +52,9 @@ namespace data if (readIdentity || !m_Identity) m_Identity = std::make_shared(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; } @@ -74,7 +67,7 @@ namespace data size += m_Identity->GetSigningPublicKeyLen (); // unused signing key if (size + 1 > m_BufferLen) { - LogPrint (eLogError, "LeaseSet: ", size, " exceeds buffer size ", m_BufferLen); + LogPrint (eLogError, "LeaseSet: ", int(size), " exceeds buffer size ", int(m_BufferLen)); m_IsValid = false; return; } @@ -89,7 +82,7 @@ namespace data } if (size + num*LEASE_SIZE > m_BufferLen) { - LogPrint (eLogError, "LeaseSet: ", size, " exceeds buffer size ", m_BufferLen); + LogPrint (eLogError, "LeaseSet: ", int(size), " exceeds buffer size ", int(m_BufferLen)); m_IsValid = false; return; } @@ -125,7 +118,7 @@ namespace data auto signedSize = leases - m_Buffer; if (signedSize + m_Identity->GetSignatureLen () > m_BufferLen) { - LogPrint (eLogError, "LeaseSet: Signature exceeds buffer size ", m_BufferLen); + LogPrint (eLogError, "LeaseSet: Signature exceeds buffer size ", int(m_BufferLen)); m_IsValid = false; } else if (!m_Identity->Verify (m_Buffer, signedSize, leases)) @@ -172,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; } @@ -264,8 +257,18 @@ namespace data 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); } @@ -274,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): @@ -320,7 +323,7 @@ namespace data 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 @@ -364,6 +367,10 @@ namespace data SetIsValid (verified); } 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); } @@ -388,17 +395,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 @@ -413,7 +420,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 ()) @@ -432,7 +439,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) @@ -442,18 +450,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++) { diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp index 8a2d6c7c..3619e0c6 100644 --- a/libi2pd/NTCP2.cpp +++ b/libi2pd/NTCP2.cpp @@ -1558,7 +1558,7 @@ 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) { @@ -1672,21 +1672,21 @@ namespace transport 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; auto& ep = conn->GetRemoteEndpoint (); if(ep.address ().is_v4 ()) { - (*buff)[3] = 0x01; + (*buff)[3] = SOCKS5_ATYP_IPV4; auto addrbytes = ep.address ().to_v4().to_bytes(); sz += 4; memcpy(buff->data () + 4, addrbytes.data(), 4); } else if (ep.address ().is_v6 ()) { - (*buff)[3] = 0x04; + (*buff)[3] = SOCKS5_ATYP_IPV6; auto addrbytes = ep.address ().to_v6().to_bytes(); sz += 16; memcpy(buff->data () + 4, addrbytes.data(), 16); @@ -1708,22 +1708,24 @@ namespace transport } }); - boost::asio::async_read(conn->GetSocket(), boost::asio::buffer(readbuff->data (), 10), + 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, sz, 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(transferred == sz) + else if (!(*readbuff)[1]) // succeeded { - if((*readbuff)[1] == 0x00) - { - timer->cancel(); - conn->ClientLogin(); - return; - } + 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 + LogPrint(eLogError, "NTCP2: Proxy reply error ", (int)(*readbuff)[1]); timer->cancel(); conn->Terminate(); }); diff --git a/libi2pd/NetDb.cpp b/libi2pd/NetDb.cpp index 8566380a..193bfdc3 100644 --- a/libi2pd/NetDb.cpp +++ b/libi2pd/NetDb.cpp @@ -240,11 +240,10 @@ namespace data 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) @@ -272,7 +271,10 @@ namespace data if (r->IsNewer (buf, len)) { bool wasFloodfill = r->IsFloodfill (); - r->Update (buf, len); + { + std::unique_lock l(m_RouterInfosMutex); + r->Update (buf, len); + } LogPrint (eLogInfo, "NetDb: RouterInfo updated: ", ident.ToBase64()); if (wasFloodfill != r->IsFloodfill ()) // if floodfill status updated { @@ -436,12 +438,15 @@ namespace data // try reseeding from floodfill first if specified std::string riPath; - if(i2p::config::GetOption("reseed.floodfill", riPath)) { + if(i2p::config::GetOption("reseed.floodfill", riPath)) + { 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"); return; @@ -623,7 +628,8 @@ namespace data (it.second->IsFloodfill () && totalFloodfills - deletedFloodfillsCount < NETDB_MIN_FLOODFILLS))) it.second->SetUnreachable (false); // find & mark expired routers - if (!it.second->IsReachable () && it.second->IsSSU (false)) + if (!it.second->IsReachable () && (it.second->GetCompatibleTransports (true) & (RouterInfo::eSSUV4 | RouterInfo::eSSU2V4))) + // non-reachable router, but reachable by ipv4 SSU or SSU2 means introducers { if (ts > it.second->GetTimestamp () + NETDB_INTRODUCEE_EXPIRATION_TIMEOUT*1000LL) // RouterInfo expires after 1 hour if uses introducer @@ -743,6 +749,11 @@ 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 ()) { @@ -753,6 +764,11 @@ namespace data size_t offset = DATABASE_STORE_HEADER_SIZE; if (replyToken) { + if (len < offset + 36) // 32 + 4 + { + LogPrint (eLogError, "NetDb: Database store msg with reply token is too short ", len, ". Dropped"); + return; + } auto deliveryStatus = CreateDeliveryStatusMsg (replyToken); uint32_t tunnelID = bufbe32toh (buf + offset); offset += 4; @@ -940,9 +956,9 @@ 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; } @@ -951,10 +967,11 @@ namespace data { 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++) @@ -1012,7 +1029,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) @@ -1211,7 +1228,7 @@ namespace data router->IsSSU2PeerTesting (v4) && !excluded.count (router->GetIdentHash ()); }); } - + std::shared_ptr NetDb::GetRandomSSUV6Router () const { return GetRandomRouter ( @@ -1231,6 +1248,16 @@ namespace data }); } + std::shared_ptr NetDb::GetRandomSSU2Introducer (bool v4, const std::set& excluded) const + { + return GetRandomRouter ( + [v4, &excluded](std::shared_ptr router)->bool + { + return !router->IsHidden () && router->IsSSU2Introducer (v4) && + !excluded.count (router->GetIdentHash ()); + }); + } + std::shared_ptr NetDb::GetHighBandwidthRandomRouter (std::shared_ptr compatibleWith, bool reverse) const { return GetRandomRouter ( @@ -1431,12 +1458,13 @@ namespace data else ++it; } + m_LeasesPool.CleanUpMt (); } void NetDb::PopulateRouterInfoBuffer (std::shared_ptr r) { if (!r || r->GetBuffer ()) return; r->LoadBuffer (m_Storage.Path (r->GetIdentHashBase64 ())); - } + } } } diff --git a/libi2pd/NetDb.hpp b/libi2pd/NetDb.hpp index 1a07903c..26e0a41b 100644 --- a/libi2pd/NetDb.hpp +++ b/libi2pd/NetDb.hpp @@ -69,7 +69,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); @@ -93,6 +93,7 @@ namespace data std::shared_ptr GetRandomSSU2PeerTestRouter (bool v4, const std::set& excluded) const; std::shared_ptr GetRandomSSUV6Router () const; // TODO: change to v6 peer test later std::shared_ptr GetRandomIntroducer (bool v4, const std::set& excluded) const; + std::shared_ptr GetRandomSSU2Introducer (bool v4, const std::set& excluded) const; std::shared_ptr GetClosestFloodfill (const IdentHash& destination, const std::set& excluded, bool closeThanUsOnly = false) const; std::vector GetClosestFloodfills (const IdentHash& destination, size_t num, std::set& excluded, bool closeThanUsOnly = false) const; @@ -124,7 +125,8 @@ namespace data void ClearRouterInfos () { m_RouterInfos.clear (); }; std::shared_ptr NewRouterInfoBuffer () { return m_RouterInfoBuffersPool.AcquireSharedMt (); }; - void PopulateRouterInfoBuffer (std::shared_ptr r); + void PopulateRouterInfoBuffer (std::shared_ptr r); + std::shared_ptr NewLease (const Lease& lease) { return m_LeasesPool.AcquireSharedMt (lease); }; uint32_t GetPublishReplyToken () const { return m_PublishReplyToken; }; @@ -181,6 +183,7 @@ namespace data uint32_t m_PublishReplyToken = 0; i2p::util::MemoryPoolMt m_RouterInfoBuffersPool; + i2p::util::MemoryPoolMt m_LeasesPool; }; extern NetDb netdb; diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index 1c8914e5..6dd3a30d 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -29,7 +29,7 @@ namespace i2p RouterContext::RouterContext (): m_LastUpdateTime (0), m_AcceptsTunnels (true), m_IsFloodfill (false), m_ShareRatio (100), m_Status (eRouterStatusUnknown), m_StatusV6 (eRouterStatusUnknown), - m_Error (eRouterErrorNone), m_NetID (I2PD_NET_ID) + m_Error (eRouterErrorNone), m_ErrorV6 (eRouterErrorNone), m_NetID (I2PD_NET_ID) { } @@ -60,11 +60,7 @@ namespace i2p i2p::data::LocalRouterInfo routerInfo; routerInfo.SetRouterIdentity (GetIdentity ()); uint16_t port; i2p::config::GetOption("port", port); - if (!port) - { - port = rand () % (30777 - 9111) + 9111; // I2P network ports range - if (port == 9150) port = 9151; // Tor browser - } + if (!port) port = SelectRandomPort (); bool ipv4; i2p::config::GetOption("ipv4", ipv4); bool ipv6; i2p::config::GetOption("ipv6", ipv6); bool ssu; i2p::config::GetOption("ssu", ssu); @@ -121,7 +117,11 @@ namespace i2p if (ssu2) { if (ssu2Published) - routerInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, boost::asio::ip::address_v4::from_string (host), port); + { + uint16_t ssu2Port; i2p::config::GetOption ("ssu2.port", ssu2Port); + if (!ssu2Port) ssu2Port = ssu ? (port + 1) : port; + routerInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, boost::asio::ip::address_v4::from_string (host), ssu2Port); + } else { addressCaps |= i2p::data::RouterInfo::AddressCaps::eV4; @@ -166,7 +166,11 @@ namespace i2p if (ssu2) { if (ssu2Published) - routerInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, boost::asio::ip::address_v6::from_string (host), port); + { + uint16_t ssu2Port; i2p::config::GetOption ("ssu2.port", ssu2Port); + if (!ssu2Port) ssu2Port = ssu ? (port + 1) : port; + routerInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, boost::asio::ip::address_v6::from_string (host), ssu2Port); + } else { if (!ipv4) // no other ssu2 addresses yet @@ -192,6 +196,13 @@ namespace i2p m_RouterInfo.Update (routerInfo.GetBuffer (), routerInfo.GetBufferLen ()); } + uint16_t RouterContext::SelectRandomPort () const + { + uint16_t port = rand () % (30777 - 9111) + 9111; // I2P network ports range + if (port == 9150) port = 9151; // Tor browser + return port; + } + void RouterContext::UpdateRouterInfo () { m_RouterInfo.CreateBuffer (m_Keys); @@ -225,6 +236,13 @@ namespace i2p fk.write ((char *)m_SSU2Keys.get (), sizeof (SSU2PrivateKeys)); } + bool RouterContext::IsSSU2Only () const + { + auto transports = m_RouterInfo.GetCompatibleTransports (false); + return (transports & (i2p::data::RouterInfo::eSSU2V4 | i2p::data::RouterInfo::eSSU2V6)) && + !(transports & (i2p::data::RouterInfo::eSSUV4 | i2p::data::RouterInfo::eSSUV6)); + } + void RouterContext::SetStatus (RouterStatus status) { if (status != m_Status) @@ -245,11 +263,18 @@ namespace i2p } } + void RouterContext::SetStatusSSU2 (RouterStatus status) + { + if (IsSSU2Only ()) + SetStatus (status); + } + void RouterContext::SetStatusV6 (RouterStatus status) { if (status != m_StatusV6) { m_StatusV6 = status; + m_ErrorV6 = eRouterErrorNone; switch (m_StatusV6) { case eRouterStatusOK: @@ -264,12 +289,18 @@ namespace i2p } } + void RouterContext::SetStatusV6SSU2 (RouterStatus status) + { + if (IsSSU2Only ()) + SetStatusV6 (status); + } + void RouterContext::UpdatePort (int port) { bool updated = false; for (auto& address : m_RouterInfo.GetAddresses ()) { - if (!address->IsNTCP2 () && !address->IsSSU2 () && address->port != port) + if (address->port != port && (address->transportStyle == i2p::data::RouterInfo::eTransportSSU || IsSSU2Only ())) { address->port = port; updated = true; @@ -297,12 +328,7 @@ namespace i2p } if (isAddr) { - if (!port && !address->port) - { - // select random port only if address's port is not set - port = rand () % (30777 - 9111) + 9111; // I2P network ports range - if (port == 9150) port = 9151; // Tor browser - } + if (!port && !address->port) port = SelectRandomPort (); if (port) address->port = port; address->published = publish; memcpy (address->i, m_NTCP2Keys->iv, 16); @@ -318,18 +344,23 @@ namespace i2p { auto& addresses = m_RouterInfo.GetAddresses (); bool found = false, updated = false; - for (auto it = addresses.begin (); it != addresses.end (); ++it) + for (auto it = addresses.begin (); it != addresses.end ();) { if ((*it)->IsNTCP2 ()) { found = true; - if (!enable) + if (enable) { - addresses.erase (it); - updated= true; + (*it)->s = m_NTCP2Keys->staticPublicKey; + memcpy ((*it)->i, m_NTCP2Keys->iv, 16); + it++; } - break; + else + it = addresses.erase (it); + updated = true; } + else + it++; } if (enable && !found) { @@ -342,14 +373,26 @@ namespace i2p void RouterContext::PublishSSU2Address (int port, bool publish, bool v4, bool v6) { - if (!m_SSU2Keys || (publish && !port)) return; + if (!m_SSU2Keys) return; + int newPort = 0; + if (!port) + { + for (const auto& address : m_RouterInfo.GetAddresses ()) + if (address->port) + { + newPort = address->port; + break; + } + if (!newPort) newPort = SelectRandomPort (); + } bool updated = false; for (auto& address : m_RouterInfo.GetAddresses ()) { - if (address->IsSSU2 () && (address->port != port || address->published != publish) && + if (address->IsSSU2 () && (!address->port || address->port != port || address->published != publish) && ((v4 && address->IsV4 ()) || (v6 && address->IsV6 ()))) { - address->port = port; + if (port) address->port = port; + else if (!address->port) address->port = newPort; address->published = publish; if (publish) address->caps |= (i2p::data::RouterInfo::eSSUIntroducer | i2p::data::RouterInfo::eSSUTesting); @@ -366,27 +409,41 @@ namespace i2p { auto& addresses = m_RouterInfo.GetAddresses (); bool found = false, updated = false; - for (auto it = addresses.begin (); it != addresses.end (); ++it) + for (auto it = addresses.begin (); it != addresses.end ();) { if ((*it)->IsSSU2 ()) { found = true; - if (!enable) + if (enable) { - addresses.erase (it); - updated= true; + (*it)->s = m_SSU2Keys->staticPublicKey; + (*it)->i = m_SSU2Keys->intro; + it++; } - break; + else + it = addresses.erase (it); + updated = true; } + else + it++; } if (enable && !found) { - uint8_t addressCaps = 0; bool ipv4; i2p::config::GetOption("ipv4", ipv4); bool ipv6; i2p::config::GetOption("ipv6", ipv6); - if (ipv4) addressCaps |= i2p::data::RouterInfo::AddressCaps::eV4; - if (ipv6) addressCaps |= i2p::data::RouterInfo::AddressCaps::eV6; - m_RouterInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, addressCaps); + bool published; i2p::config::GetOption("ntcp2.published", published); + if (published) + { + if (ipv4) m_RouterInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, i2p::data::RouterInfo::AddressCaps::eV4); + if (ipv6) m_RouterInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, i2p::data::RouterInfo::AddressCaps::eV6); + } + else + { + uint8_t addressCaps = 0; + if (ipv4) addressCaps |= i2p::data::RouterInfo::AddressCaps::eV4; + if (ipv6) addressCaps |= i2p::data::RouterInfo::AddressCaps::eV6; + m_RouterInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, addressCaps); + } updated = true; } if (updated) @@ -401,23 +458,30 @@ namespace i2p if (address->host != host && address->IsCompatible (host) && !i2p::util::net::IsYggdrasilAddress (address->host)) { + // update host address->host = host; - if (host.is_v6 () && address->transportStyle == i2p::data::RouterInfo::eTransportSSU) - { - // update MTU - auto mtu = i2p::util::net::GetMTU (host); - if (mtu) - { - LogPrint (eLogDebug, "Router: Our v6 MTU=", mtu); - if (mtu > 1472) { // TODO: magic constant - mtu = 1472; - LogPrint(eLogWarning, "Router: MTU dropped to upper limit of 1472 bytes"); - } - if (address->ssu) address->ssu->mtu = mtu; - } - } updated = true; } + if (host.is_v6 () && address->IsV6 () && address->ssu && + (!address->ssu->mtu || updated) && m_StatusV6 != eRouterStatusProxy) + { + // update MTU + auto mtu = i2p::util::net::GetMTU (host); + if (mtu) + { + LogPrint (eLogDebug, "Router: Our v6 MTU=", mtu); + int maxMTU = i2p::util::net::GetMaxMTU (host.to_v6 ()); + if (mtu > maxMTU) + { + mtu = maxMTU; + LogPrint(eLogWarning, "Router: MTU dropped to upper limit of ", maxMTU, " bytes"); + } + if (mtu && !address->IsSSU2 ()) // SSU1 + mtu = (mtu >> 4) << 4; // round to multiple of 16 + address->ssu->mtu = mtu; + updated = true; + } + } } auto ts = i2p::util::GetSecondsSinceEpoch (); if (updated || ts > m_LastUpdateTime + ROUTER_INFO_UPDATE_INTERVAL) @@ -438,6 +502,37 @@ namespace i2p UpdateRouterInfo (); } + bool RouterContext::AddSSU2Introducer (const i2p::data::RouterInfo::Introducer& introducer, bool v4) + { + if (!IsSSU2Only ()) return false; + bool ret = m_RouterInfo.AddSSU2Introducer (introducer, v4); + if (ret) + UpdateRouterInfo (); + return ret; + } + + void RouterContext::RemoveSSU2Introducer (const i2p::data::IdentHash& h, bool v4) + { + if (!IsSSU2Only ()) return; + if (m_RouterInfo.RemoveSSU2Introducer (h, v4)) + UpdateRouterInfo (); + } + + void RouterContext::ClearSSU2Introducers (bool v4) + { + bool updated = false; + auto& addresses = m_RouterInfo.GetAddresses (); + for (auto& addr : addresses) + if (addr->IsSSU2 () && ((v4 && addr->IsV4 ()) || (!v4 && addr->IsV6 ())) && + addr->ssu && !addr->ssu->introducers.empty ()) + { + addr->ssu->introducers.clear (); + updated = true; + } + if (updated) + UpdateRouterInfo (); + } + void RouterContext::SetFloodfill (bool floodfill) { m_IsFloodfill = floodfill; @@ -538,6 +633,7 @@ namespace i2p void RouterContext::RemoveNTCPAddress (bool v4only) { + bool updated = false; auto& addresses = m_RouterInfo.GetAddresses (); for (auto it = addresses.begin (); it != addresses.end ();) { @@ -545,11 +641,38 @@ namespace i2p (!v4only || (*it)->host.is_v4 ())) { it = addresses.erase (it); + updated = true; if (v4only) break; // otherwise might be more than one address } else ++it; } + if (updated) + m_RouterInfo.UpdateSupportedTransports (); + } + + void RouterContext::RemoveSSUAddress () + { + bool updated = false; + auto& addresses = m_RouterInfo.GetAddresses (); + for (auto it = addresses.begin (); it != addresses.end ();) + { + if ((*it)->transportStyle == i2p::data::RouterInfo::eTransportSSU) + { + it = addresses.erase (it); + updated = true; + } + else + ++it; + } + if (updated) + m_RouterInfo.UpdateSupportedTransports (); + } + + void RouterContext::SetUnreachableSSU2 (bool v4, bool v6) + { + if (IsSSU2Only ()) + SetUnreachable (v4, v6); } void RouterContext::SetUnreachable (bool v4, bool v6) @@ -568,7 +691,8 @@ namespace i2p // delete previous introducers auto& addresses = m_RouterInfo.GetAddresses (); for (auto& addr : addresses) - if (addr->ssu && !addr->IsSSU2 () && ((v4 && addr->IsV4 ()) || (v6 && addr->IsV6 ()))) + if (addr->ssu && (!addr->IsSSU2 () || IsSSU2Only ()) && + ((v4 && addr->IsV4 ()) || (v6 && addr->IsV6 ()))) { addr->published = false; addr->caps &= ~i2p::data::RouterInfo::eSSUIntroducer; // can't be introducer @@ -598,14 +722,19 @@ namespace i2p } uint16_t port = 0; // delete previous introducers + bool isSSU2Published = IsSSU2Only (); // TODO + if (isSSU2Published) + i2p::config::GetOption ("ssu2.published", isSSU2Published); auto& addresses = m_RouterInfo.GetAddresses (); for (auto& addr : addresses) - if (addr->ssu && !addr->IsSSU2 () && ((v4 && addr->IsV4 ()) || (v6 && addr->IsV6 ()))) + if (addr->ssu && (!addr->IsSSU2 () || isSSU2Published) && + ((v4 && addr->IsV4 ()) || (v6 && addr->IsV6 ()))) { addr->published = true; addr->caps |= i2p::data::RouterInfo::eSSUIntroducer; addr->ssu->introducers.clear (); - port = addr->port; + if (addr->port && (!addr->IsSSU2 () || IsSSU2Only ())) + port = addr->port; } // publish NTCP2 bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); @@ -652,16 +781,17 @@ namespace i2p } 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, nullptr); - } + i2p::config::GetOption("port", port); + if (!port) port = SelectRandomPort (); + } + // SSU + bool ssu; i2p::config::GetOption("ssu", ssu); + if (!foundSSU && ssu) + { + std::string host = "::1"; // TODO: read host + m_RouterInfo.AddSSUAddress (host.c_str (), port, nullptr); } // NTCP2 if (!foundNTCP2) @@ -695,6 +825,7 @@ namespace i2p if (ssu2Published) { uint16_t ssu2Port; i2p::config::GetOption ("ssu2.port", ssu2Port); + if (!ssu2Port) ssu2Port = ssu ? (port + 1) : port; m_RouterInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, boost::asio::ip::address::from_string ("::1"), ssu2Port); } else @@ -740,14 +871,16 @@ namespace i2p } if (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) - m_RouterInfo.AddSSUAddress (host.c_str (), port, nullptr); + i2p::config::GetOption("port", port); + if (!port) port = SelectRandomPort (); } + // SSU + bool ssu; i2p::config::GetOption("ssu", ssu); + if (!foundSSU && ssu) + m_RouterInfo.AddSSUAddress (host.c_str (), port, nullptr); + // NTCP2 if (!foundNTCP2) { @@ -775,10 +908,11 @@ namespace i2p if (ssu2Published) { uint16_t ssu2Port; i2p::config::GetOption ("ssu2.port", ssu2Port); + if (!ssu2Port) ssu2Port = ssu ? (port + 1) : port; m_RouterInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, boost::asio::ip::address::from_string ("127.0.0.1"), ssu2Port); } else - m_RouterInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, i2p::data::RouterInfo::eV6); + m_RouterInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, i2p::data::RouterInfo::eV4); } } m_RouterInfo.EnableV4 (); @@ -815,6 +949,43 @@ namespace i2p UpdateRouterInfo (); } + void RouterContext::SetMTU (int mtu, bool v4) + { + if (mtu < 1280 || mtu > 1500) return; + auto& addresses = m_RouterInfo.GetAddresses (); + for (auto& addr: addresses) + { + if (addr->ssu && ((v4 && addr->IsV4 ()) || (!v4 && addr->IsV6 ()))) + { + if (!addr->IsSSU2 ()) // SSU1 + { + // round to multiple of 16 + if (v4) + { + if (mtu > 1484) mtu = 1484; + else + { + mtu -= 12; + mtu = (mtu >> 4) << 4; + mtu += 12; + } + } + else + { + if (mtu > 1488) mtu = 1488; + else + mtu = (mtu >> 4) << 4; + } + } + if (mtu) + { + 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) { bool isYgg = i2p::util::net::IsYggdrasilAddress (host); diff --git a/libi2pd/RouterContext.h b/libi2pd/RouterContext.h index eb5db38f..cf394162 100644 --- a/libi2pd/RouterContext.h +++ b/libi2pd/RouterContext.h @@ -37,10 +37,9 @@ namespace garlic eRouterStatusOK = 0, eRouterStatusTesting = 1, eRouterStatusFirewalled = 2, - eRouterStatusError = 3, - eRouterStatusUnknown = 4, - eRouterStatusProxy = 5, - eRouterStatusMesh = 6 + eRouterStatusUnknown = 3, + eRouterStatusProxy = 4, + eRouterStatusMesh = 5 }; enum RouterError @@ -103,10 +102,14 @@ namespace garlic uint64_t GetTransitBandwidthLimit () const { return (m_BandwidthLimit*m_ShareRatio)/100LL; }; RouterStatus GetStatus () const { return m_Status; }; void SetStatus (RouterStatus status); + void SetStatusSSU2 (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; }; RouterStatus GetStatusV6 () const { return m_StatusV6; }; void SetStatusV6 (RouterStatus status); + void SetStatusV6SSU2 (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); @@ -119,10 +122,15 @@ namespace garlic void PublishSSU2Address (int port, bool publish, bool v4, bool v6); void UpdateSSU2Address (bool enable); void RemoveNTCPAddress (bool v4only = true); // delete NTCP address for older routers. TODO: remove later + void RemoveSSUAddress (); // delete SSU address for older routers bool AddIntroducer (const i2p::data::RouterInfo::Introducer& introducer); void RemoveIntroducer (const boost::asio::ip::udp::endpoint& e); + 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 (bool v4, bool v6); + void SetUnreachableSSU2 (bool v4, bool v6); void SetReachable (bool v4, bool v6); bool IsFloodfill () const { return m_IsFloodfill; }; void SetFloodfill (bool floodfill); @@ -139,6 +147,7 @@ namespace garlic 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); i2p::crypto::NoiseSymmetricState& GetCurrentNoiseState () { return m_CurrentNoiseState; }; void UpdateNTCP2V6Address (const boost::asio::ip::address& host); // called from Daemon. TODO: remove @@ -173,8 +182,10 @@ namespace garlic void UpdateRouterInfo (); void NewNTCP2Keys (); void NewSSU2Keys (); + bool IsSSU2Only () const; // SSU2 and no SSU bool Load (); void SaveKeys (); + uint16_t SelectRandomPort () const; bool DecryptECIESTunnelBuildRecord (const uint8_t * encrypted, uint8_t * data, size_t clearTextSize); @@ -190,7 +201,7 @@ namespace garlic uint64_t m_BandwidthLimit; // allowed bandwidth int m_ShareRatio; RouterStatus m_Status, m_StatusV6; - RouterError m_Error; + RouterError m_Error, m_ErrorV6; int m_NetID; std::mutex m_GarlicMutex; std::unique_ptr m_NTCP2Keys; diff --git a/libi2pd/RouterInfo.cpp b/libi2pd/RouterInfo.cpp index 1575cbbc..d9c966d7 100644 --- a/libi2pd/RouterInfo.cpp +++ b/libi2pd/RouterInfo.cpp @@ -257,20 +257,38 @@ namespace data if (!ecode && !address->host.is_unspecified ()) isHost = true; } 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"); + LogPrint (eLogWarning, "RouterInfo: Unexpected field 'mtu' for NTCP2"); } else if (!strcmp (key, "key")) { if (address->ssu) isIntroKey = (Base64ToByteStream (value, strlen (value), address->i, 32) == 32); else - LogPrint (eLogWarning, "RouterInfo: Unexpected field 'key' for NTCP"); + LogPrint (eLogWarning, "RouterInfo: Unexpected field 'key' for NTCP2"); } else if (!strcmp (key, "caps")) address->caps = ExtractAddressCaps (value); @@ -327,13 +345,40 @@ namespace data introducer.iHost = boost::asio::ip::address::from_string (value, ecode); } else if (!strcmp (key, "iport")) - introducer.iPort = boost::lexical_cast(value); + { + try + { + introducer.iPort = boost::lexical_cast(value); + } + catch (std::exception& ex) + { + LogPrint (eLogWarning, "RouterInfo: 'iport' exception ", ex.what ()); + } + } else if (!strcmp (key, "itag")) - introducer.iTag = boost::lexical_cast(value); + { + try + { + introducer.iTag = boost::lexical_cast(value); + } + catch (std::exception& ex) + { + LogPrint (eLogWarning, "RouterInfo: 'itag' exception ", ex.what ()); + } + } else if (!strcmp (key, "ikey") || !strcmp (key, "ih")) Base64ToByteStream (value, strlen (value), introducer.iKey, 32); else if (!strcmp (key, "iexp")) - introducer.iExp = boost::lexical_cast(value); + { + try + { + introducer.iExp = boost::lexical_cast(value); + } + catch (std::exception& ex) + { + LogPrint (eLogWarning, "RouterInfo: 'iexp' exception ", ex.what ()); + } + } } if (!s) return; } @@ -386,10 +431,10 @@ namespace data ((it.iHost.is_v4 () && address->IsV4 ()) || (it.iHost.is_v6 () && address->IsV6 ()))) numValid++; else - { + { it.iPort = 0; if (isV2) numValid++; - } + } } if (numValid) m_ReachableTransports |= supportedTransports; @@ -422,16 +467,16 @@ namespace data int numValid = 0; for (auto& it: address->ssu->introducers) { - if (it.iTag && ts <= it.iExp) + if (it.iTag && ts <= it.iExp) numValid++; - else + else it.iTag = 0; } if (numValid) m_ReachableTransports |= supportedTransports; else address->ssu->introducers.resize (0); - } + } } else { @@ -445,15 +490,15 @@ namespace data ssu2addr->ssu.reset (new SSUExt ()); ssu2addr->ssu->mtu = address->ssu->mtu; uint32_t ts = i2p::util::GetSecondsSinceEpoch (); if (!address->ssu->introducers.empty ()) - { + { for (const auto& introducer: address->ssu->introducers) if (!introducer.iPort && introducer.iHost.is_unspecified () && ts < introducer.iExp) // SSU2 ssu2addr->ssu->introducers.push_back (introducer); if (!ssu2addr->ssu->introducers.empty ()) m_ReachableTransports |= supportedTransports; - } + } addresses->push_back(ssu2addr); - } + } } if (supportedTransports) { @@ -708,6 +753,7 @@ namespace data { auto addr = std::make_shared
(); addr->transportStyle = eTransportSSU2; + addr->port = 0; addr->caps = caps; addr->date = 0; addr->ssu.reset (new SSUExt ()); @@ -727,7 +773,7 @@ namespace data addr->host = host; addr->port = port; addr->published = true; - addr->caps = 0; + addr->caps = i2p::data::RouterInfo::eSSUTesting | i2p::data::RouterInfo::eSSUIntroducer; // BC; addr->date = 0; addr->ssu.reset (new SSUExt ()); addr->ssu->mtu = 0; @@ -936,7 +982,7 @@ namespace data std::shared_ptr RouterInfo::GetSSU2Address (bool v4) const { if (v4) - { + { if (m_SupportedTransports & eSSU2V4) return GetSSU2V4Address (); } @@ -944,10 +990,10 @@ namespace data { if (m_SupportedTransports & eSSU2V6) return GetSSU2V6Address (); - } + } return nullptr; - } - + } + template std::shared_ptr RouterInfo::GetAddress (Filter filter) const { @@ -979,7 +1025,8 @@ namespace data return GetAddress ( [key, isV6](std::shared_ptr address)->bool { - return address->IsSSU2 () && !memcmp (address->s, key, 32) && address->IsV6 () == isV6; + return address->IsSSU2 () && !memcmp (address->s, key, 32) && + ((isV6 && address->IsV6 ()) || (!isV6 && address->IsV4 ())); }); } @@ -1052,8 +1099,8 @@ namespace data return (address->IsSSU2 ()) && address->IsPeerTesting () && ((v4 && address->IsV4 ()) || (!v4 && address->IsV6 ())) && address->IsReachableSSU (); }); - } - + } + bool RouterInfo::IsIntroducer (bool v4) const { if (!(m_SupportedTransports & (v4 ? eSSUV4 : eSSUV6))) return false; @@ -1065,6 +1112,17 @@ namespace data }); } + bool RouterInfo::IsSSU2Introducer (bool v4) const + { + if (!(m_SupportedTransports & (v4 ? eSSU2V4 : eSSU2V6))) return false; + return (bool)GetAddress ( + [v4](std::shared_ptr address)->bool + { + return (address->IsSSU2 ()) && address->IsIntroducer () && + ((v4 && address->IsV4 ()) || (!v4 && address->IsV6 ())) && !address->host.is_unspecified (); + }); + } + void RouterInfo::SetUnreachableAddressesTransportCaps (uint8_t transports) { for (auto& addr: *m_Addresses) @@ -1086,7 +1144,7 @@ namespace data { uint8_t transports = 0; switch (addr->transportStyle) - { + { case eTransportNTCP: if (addr->IsV4 ()) transports |= eNTCP2V4; if (addr->IsV6 ()) @@ -1105,9 +1163,9 @@ namespace data if (addr->IsV6 ()) transports |= eSSU2V6; if (addr->IsReachableSSU ()) m_ReachableTransports |= transports; - break; - default: ; - } + break; + default: ; + } m_SupportedTransports |= transports; } } @@ -1296,7 +1354,7 @@ namespace data else WriteString ("", s); - if (isPublished) + if (isPublished && !address.host.is_unspecified ()) { WriteString ("host", properties); properties << '='; @@ -1399,7 +1457,7 @@ namespace data properties << ';'; } } - if (isPublished || (address.ssu && !address.IsSSU2 ())) + if ((isPublished || (address.ssu && !address.IsSSU2 ())) && address.port) { WriteString ("port", properties); properties << '='; @@ -1467,5 +1525,40 @@ namespace data { return std::make_shared (); } + + bool LocalRouterInfo::AddSSU2Introducer (const Introducer& introducer, bool v4) + { + for (auto& addr : GetAddresses ()) + { + if (addr->IsSSU2 () && ((v4 && addr->IsV4 ()) || (!v4 && addr->IsV6 ()))) + { + for (auto& intro: addr->ssu->introducers) + if (intro.iTag == introducer.iTag) return false; // already presented + addr->ssu->introducers.push_back (introducer); + SetReachableTransports (GetReachableTransports () | ((addr->IsV4 () ? eSSU2V4 : eSSU2V6))); + return true; + } + } + return false; + } + + bool LocalRouterInfo::RemoveSSU2Introducer (const IdentHash& h, bool v4) + { + for (auto& addr: GetAddresses ()) + { + if (addr->IsSSU2 () && ((v4 && addr->IsV4 ()) || (!v4 && addr->IsV6 ()))) + { + for (auto it = addr->ssu->introducers.begin (); it != addr->ssu->introducers.end (); ++it) + if (h == it->iKey) + { + addr->ssu->introducers.erase (it); + if (addr->ssu->introducers.empty ()) + SetReachableTransports (GetReachableTransports () & ~(addr->IsV4 () ? eSSU2V4 : eSSU2V6)); + return true; + } + } + } + return false; + } } } diff --git a/libi2pd/RouterInfo.h b/libi2pd/RouterInfo.h index 1aa90ce3..8f76707c 100644 --- a/libi2pd/RouterInfo.h +++ b/libi2pd/RouterInfo.h @@ -235,6 +235,7 @@ namespace data bool IsPeerTesting (bool v4) const; bool IsSSU2PeerTesting (bool v4) const; bool IsIntroducer (bool v4) const; + bool IsSSU2Introducer (bool v4) const; uint8_t GetCaps () const { return m_Caps; }; void SetCaps (uint8_t caps) { m_Caps = caps; }; @@ -274,6 +275,8 @@ namespace data void SetBufferLen (size_t len) { m_BufferLen = len; }; void RefreshTimestamp (); const Addresses& GetAddresses () const { return *m_Addresses; }; + CompatibleTransports GetReachableTransports () const { return m_ReachableTransports; }; + void SetReachableTransports (CompatibleTransports transports) { m_ReachableTransports = transports; }; private: @@ -316,6 +319,9 @@ namespace data 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; diff --git a/libi2pd/SSU2.cpp b/libi2pd/SSU2.cpp index beca55cd..65f6f4f1 100644 --- a/libi2pd/SSU2.cpp +++ b/libi2pd/SSU2.cpp @@ -6,6 +6,7 @@ * See full license text in LICENSE file at top of project tree */ +#include #include "Log.h" #include "RouterContext.h" #include "Transports.h" @@ -16,12 +17,14 @@ 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_ResendTimer (GetService ()) + m_TerminationTimer (GetService ()), m_ResendTimer (GetService ()), + m_IntroducersUpdateTimer (GetService ()), m_IntroducersUpdateTimerV6 (GetService ()), + m_IsPublished (true), m_IsSyncClockFromPeers (true), m_IsThroughProxy (false) { } @@ -30,6 +33,8 @@ namespace transport if (!IsRunning ()) { StartIOService (); + i2p::config::GetOption ("ssu2.published", m_IsPublished); + i2p::config::GetOption("nettime.frompeers", m_IsSyncClockFromPeers); bool found = false; auto& addresses = i2p::context.GetRouterInfo ().GetAddresses (); for (const auto& address: addresses) @@ -37,6 +42,25 @@ namespace transport 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) { @@ -44,8 +68,9 @@ namespace transport if (ssu2Port) port = ssu2Port; else { + bool ssu; i2p::config::GetOption("ssu", ssu); uint16_t p; i2p::config::GetOption ("port", p); - if (p) port = p; + if (p) port = ssu ? (p + 1) : p; } } if (port) @@ -59,6 +84,7 @@ namespace transport { Receive (m_SocketV4); }); + ScheduleIntroducersUpdateTimer (); // wait for 30 seconds and decide if we need introducers } if (address->IsV6 ()) { @@ -69,6 +95,7 @@ namespace transport { Receive (m_SocketV6); }); + ScheduleIntroducersUpdateTimerV6 (); // wait for 30 seconds and decide if we need introducers } } else @@ -76,54 +103,108 @@ namespace transport } } if (found) + { + if (m_IsThroughProxy) + ConnectToProxy (); m_ReceiveService.Start (); + } ScheduleTermination (); + ScheduleResend (false); } } void SSU2Server::Stop () { - for (auto& it: m_Sessions) + if (IsRunning ()) + { + m_TerminationTimer.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 (); - m_Sessions.clear (); - m_SessionsByRouterHash.clear (); - m_PendingOutgoingSessions.clear (); + } if (context.SupportsV4 () || context.SupportsV6 ()) m_ReceiveService.Stop (); - m_SocketV4.close (); m_SocketV6.close (); - if (IsRunning ()) - m_TerminationTimer.cancel (); + + 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 ()) + { + if (m_SocketV4.is_open ()) return true; - } + } else if (addr.is_v6 ()) - { - if (m_SocketV6.is_open ()) + { + 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; @@ -148,7 +229,7 @@ namespace transport void SSU2Server::Receive (boost::asio::ip::udp::socket& socket) { Packet * packet = m_PacketsPool.AcquireMt (); - socket.async_receive_from (boost::asio::buffer (packet->buf, SSU2_MTU), packet->from, + 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))); } @@ -169,7 +250,7 @@ namespace transport while (moreBytes && packets.size () < 32) { packet = m_PacketsPool.AcquireMt (); - packet->len = socket.receive_from (boost::asio::buffer (packet->buf, SSU2_MTU), packet->from, 0, ec); + 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); @@ -196,10 +277,20 @@ namespace transport if (ecode != boost::asio::error::operation_aborted) { LogPrint (eLogError, "SSU2: Receive error: code ", ecode.value(), ": ", ecode.message ()); - auto ep = socket.local_endpoint (); - socket.close (); - OpenSocket (ep); - Receive (socket); + if (m_IsThroughProxy) + { + m_UDPAssociateSocket.reset (nullptr); + m_ProxyRelayEndpoint.reset (nullptr); + m_SocketV4.close (); + ConnectToProxy (); + } + else + { + auto ep = socket.local_endpoint (); + socket.close (); + OpenSocket (ep); + Receive (socket); + } } } } @@ -208,18 +299,27 @@ namespace transport { if (packet) { - ProcessNextPacket (packet->buf, packet->len, packet->from); + 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->FlushData (); + if (m_LastSession && m_LastSession->GetState () != eSSU2SessionStateTerminated) + m_LastSession->FlushData (); } } void SSU2Server::HandleReceivedPackets (std::vector packets) { - for (auto& packet: packets) - ProcessNextPacket (packet->buf, packet->len, packet->from); + 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->FlushData (); + if (m_LastSession && m_LastSession->GetState () != eSSU2SessionStateTerminated) + m_LastSession->FlushData (); } void SSU2Server::AddSession (std::shared_ptr session) @@ -239,10 +339,12 @@ namespace transport 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) @@ -254,9 +356,9 @@ namespace transport if (!ret.second) { // session already exists - LogPrint (eLogWarning, "SSU2: Session to ", ident->GetIdentHash ().ToBase64 (), " aready exists"); + LogPrint (eLogWarning, "SSU2: Session to ", ident->GetIdentHash ().ToBase64 (), " already exists"); // terminate existing - GetService ().post (std::bind (&SSU2Session::Terminate, ret.first->second)); + GetService ().post (std::bind (&SSU2Session::RequestTermination, ret.first->second, eSSU2TerminationReasonReplacedByNewSession)); // update session ret.first->second = session; } @@ -276,10 +378,10 @@ namespace transport if (it != m_SessionsByRouterHash.end ()) return it->second; return nullptr; - } + } std::shared_ptr SSU2Server::FindPendingOutgoingSession (const boost::asio::ip::udp::endpoint& ep) const - { + { auto it = m_PendingOutgoingSessions.find (ep); if (it != m_PendingOutgoingSessions.end ()) return it->second; @@ -289,8 +391,8 @@ namespace transport void SSU2Server::RemovePendingOutgoingSession (const boost::asio::ip::udp::endpoint& ep) { m_PendingOutgoingSessions.erase (ep); - } - + } + std::shared_ptr SSU2Server::GetRandomSession ( i2p::data::RouterInfo::CompatibleTransports remoteTransports, const i2p::data::IdentHash& excluded) const { @@ -302,7 +404,7 @@ namespace transport std::advance (it, ind); while (it != m_Sessions.end ()) { - if ((it->second->GetRemoteTransports () & remoteTransports) && + if ((it->second->GetRemoteTransports () & remoteTransports) && it->second->GetRemoteIdentity ()->GetIdentHash () != excluded) return it->second; it++; @@ -311,14 +413,14 @@ namespace transport it = m_Sessions.begin (); while (it != m_Sessions.end () && ind) { - if ((it->second->GetRemoteTransports () & remoteTransports) && + 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); @@ -341,7 +443,7 @@ namespace transport } return nullptr; } - + void SSU2Server::ProcessNextPacket (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint) { if (len < 24) return; @@ -362,32 +464,41 @@ namespace transport switch (m_LastSession->GetState ()) { case eSSU2SessionStateEstablished: - case eSSU2SessionStateSessionConfirmedSent: - m_LastSession->ProcessData (buf, len); + case eSSU2SessionStateSessionConfirmedSent: + m_LastSession->ProcessData (buf, len, senderEndpoint); break; case eSSU2SessionStateSessionCreatedSent: - m_LastSession->ProcessSessionConfirmed (buf, len); + if (!m_LastSession->ProcessSessionConfirmed (buf, len)) + { + m_LastSession->Done (); + m_LastSession = nullptr; + } break; case eSSU2SessionStateIntroduced: - if (m_LastSession->GetRemoteEndpoint ().address ().is_unspecified ()) + if (m_LastSession->GetRemoteEndpoint ().address ().is_unspecified ()) m_LastSession->SetRemoteEndpoint (senderEndpoint); - if (m_LastSession->GetRemoteEndpoint () == senderEndpoint) + if (m_LastSession->GetRemoteEndpoint ().address () == senderEndpoint.address ()) // port might be different m_LastSession->ProcessHolePunch (buf, len); else { - LogPrint (eLogWarning, "SSU2: HolePunch endpoint ", senderEndpoint, - " doesn't match RelayResponse ", m_LastSession->GetRemoteEndpoint ()); - m_LastSession->Terminate (); - m_LastSession = nullptr; - } + 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 () != eSSU2SessionStateTerminated) + m_LastSession->RequestTermination (eSSU2TerminationReasonIdleTimeout); // send termination again + break; case eSSU2SessionStateTerminated: m_LastSession = nullptr; - break; + break; default: LogPrint (eLogWarning, "SSU2: Invalid session state ", (int)m_LastSession->GetState ()); } @@ -400,7 +511,7 @@ namespace transport { if (it1->second->GetState () == eSSU2SessionStateSessionRequestSent && it1->second->ProcessSessionCreated (buf, len)) - m_PendingOutgoingSessions.erase (it1); // we are done with that endpoint + m_PendingOutgoingSessions.erase (it1); // we are done with that endpoint else it1->second->ProcessRetry (buf, len); } @@ -413,10 +524,15 @@ namespace transport } } } - + 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), @@ -436,6 +552,11 @@ namespace transport 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), @@ -459,37 +580,50 @@ namespace transport { if (router && address) { - // check is no peding session + // 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, 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 + else if (isValidEndpoint) // we can't connect without endpoint GetService ().post ([session]() { session->Connect (); }); else return false; @@ -519,17 +653,26 @@ namespace transport auto ts = i2p::util::GetSecondsSinceEpoch (); std::shared_ptr r; uint32_t relayTag = 0; - for (auto& it: address->ssu->introducers) + if (!address->ssu->introducers.empty ()) { - if (it.iTag && ts < it.iExp) - { - r = i2p::data::netdb.FindRouter (it.iKey); - if (r && r->IsReachableFrom (i2p::context.GetRouterInfo ())) + std::vector indicies; + for (int i = 0; i < (int)address->ssu->introducers.size (); i++) indicies.push_back(i); + if (indicies.size () > 1) + std::shuffle (indicies.begin(), indicies.end(), std::mt19937(std::random_device()())); + + for (auto i: indicies) + { + const auto& introducer = address->ssu->introducers[indicies[i]]; + if (introducer.iTag && ts < introducer.iExp) { - relayTag = it.iTag; - if (relayTag) break; + r = i2p::data::netdb.FindRouter (introducer.iKey); + if (r && r->IsReachableFrom (i2p::context.GetRouterInfo ())) + { + relayTag = introducer.iTag; + if (relayTag) break; + } } - } + } } if (r) { @@ -539,12 +682,13 @@ namespace transport auto addr = address->IsV6 () ? r->GetSSU2V6Address () : r->GetSSU2V4Address (); if (addr) { - bool isValidEndpoint = !addr->host.is_unspecified () && addr->port; + 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 (); @@ -558,10 +702,10 @@ namespace transport onEstablished (); s->Introduce (session, relayTag); }); - else + else s->SetOnEstablished ([session, s, relayTag]() {s->Introduce (session, relayTag); }); - } - } + } + } } } } @@ -585,14 +729,15 @@ namespace transport auto s = it->second; if (it->second->IsEstablished ()) GetService ().post ([s]() { s->SendPeerTest (); }); - else - s->SetOnEstablished ([s]() { s->SendPeerTest (); }); - return true; - } - CreateSession (router, addr, true); + 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)); @@ -616,22 +761,36 @@ namespace transport it++; } - for (auto it = m_Sessions.begin (); it != m_Sessions.end ();) + for (auto it: m_Sessions) { - if (it->second->GetState () == eSSU2SessionStateTerminated || - it->second->IsTerminationTimeoutExpired (ts)) + 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->TerminateByTimeout (); - if (it->second == m_LastSession) - m_LastSession = nullptr; - it = m_Sessions.erase (it); + if (it.second->IsEstablished ()) + it.second->RequestTermination (eSSU2TerminationReasonIdleTimeout); + else + it.second->Done (); } else - { - it->second->CleanUp (ts); + 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++; + } + + 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 (); ) @@ -650,13 +809,16 @@ namespace transport it++; } + m_PacketsPool.CleanUpMt (); + m_SentPacketsPool.CleanUp (); ScheduleTermination (); } } - void SSU2Server::ScheduleResend () + void SSU2Server::ScheduleResend (bool more) { - m_ResendTimer.expires_from_now (boost::posix_time::seconds(SSU2_RESEND_INTERVAL)); + 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)); } @@ -665,12 +827,16 @@ namespace transport { if (ecode != boost::asio::error::operation_aborted) { - auto ts = i2p::util::GetSecondsSinceEpoch (); + size_t resentPacketsNum = 0; + auto ts = i2p::util::GetMillisecondsSinceEpoch (); for (auto it: m_Sessions) - it.second->Resend (ts); + { + resentPacketsNum += it.second->Resend (ts); + if (resentPacketsNum > SSU2_MAX_RESEND_PACKETS) break; + } for (auto it: m_PendingOutgoingSessions) it.second->Resend (ts); - ScheduleResend (); + ScheduleResend (resentPacketsNum > SSU2_MAX_RESEND_PACKETS); } } @@ -687,7 +853,7 @@ namespace transport if (i2p::util::GetSecondsSinceEpoch () + SSU2_TOKEN_EXPIRATION_THRESHOLD > it->second.second) return 0; // token expired return it->second.first; - } + } return 0; } @@ -707,9 +873,493 @@ namespace transport m_IncomingTokens.erase (ep); // drop previous uint64_t token; RAND_bytes ((uint8_t *)&token, 8); - auto ret = std::make_pair (token, i2p::util::GetSecondsSinceEpoch () + SSU2_NEXT_TOKEN_EXPIRATION_TIMEOUT); + auto ret = std::make_pair (token, 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; + 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 ()) + { + 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 + 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"); + for (auto& it : introducers) + { + auto it1 = m_SessionsByRouterHash.find (it); + if (it1 != m_SessionsByRouterHash.end ()) + { + auto session = it1->second; + if (session->IsEstablished ()) + { + session->SetCreationTime (session->GetCreationTime () + SSU2_TO_INTRODUCER_SESSION_DURATION); + if (std::find (newList.begin (), newList.end (), it) == newList.end ()) + { + newList.push_back (it); + sessions.push_back (session); + } + } + } + } + } + + for (const auto& it : sessions) + { + i2p::data::RouterInfo::Introducer introducer; + introducer.iTag = it->GetRelayTag (); + introducer.iKey = it->GetRemoteIdentity ()->GetIdentHash (); + introducer.iExp = it->GetCreationTime () + SSU2_TO_INTRODUCER_SESSION_EXPIRATION; + 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; + } + } + } + } + + void SSU2Server::ScheduleIntroducersUpdateTimer () + { + if (m_IsPublished) + { + m_IntroducersUpdateTimer.expires_from_now (boost::posix_time::seconds(SSU2_KEEP_ALIVE_INTERVAL)); + 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/2)); + m_IntroducersUpdateTimer.async_wait (std::bind (&SSU2Server::HandleIntroducersUpdateTimer, + this, std::placeholders::_1, true)); + } + } + + void SSU2Server::ScheduleIntroducersUpdateTimerV6 () + { + if (m_IsPublished) + { + m_IntroducersUpdateTimerV6.expires_from_now (boost::posix_time::seconds(SSU2_KEEP_ALIVE_INTERVAL)); + m_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/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.GetStatus () == eRouterStatusTesting) + { + // 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.SetUnreachableSSU2 (true, false); // v4 + + UpdateIntroducers (true); + ScheduleIntroducersUpdateTimer (); + } + else + { + if (i2p::context.GetStatusV6 () == eRouterStatusTesting) + { + // we still don't know if we need introducers + ScheduleIntroducersUpdateTimerV6 (); + return; + } + if (i2p::context.GetStatusV6 () != eRouterStatusFirewalled) + { + // we don't need introducers + 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.SetUnreachableSSU2 (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 index 429cfdb7..c5c2c140 100644 --- a/libi2pd/SSU2.h +++ b/libi2pd/SSU2.h @@ -17,15 +17,24 @@ namespace i2p { namespace transport { - const int SSU2_TERMINATION_CHECK_TIMEOUT = 30; // 30 seconds + const int SSU2_TERMINATION_CHECK_TIMEOUT = 30; // 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 = 30; // in seconds + const int SSU2_PROXY_CONNECT_RETRY_TIMEOUT = 30; // in seconds + class SSU2Server: private i2p::util::RunnableServiceWithWork { struct Packet { - uint8_t buf[SSU2_MTU]; + uint8_t buf[SSU2_MAX_PACKET_SIZE]; size_t len; boost::asio::ip::udp::endpoint from; }; @@ -49,8 +58,12 @@ namespace transport 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); @@ -60,7 +73,7 @@ namespace transport 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); @@ -73,12 +86,16 @@ namespace transport 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) const; uint64_t GetIncomingToken (const boost::asio::ip::udp::endpoint& ep); std::pair NewIncomingToken (const boost::asio::ip::udp::endpoint& ep); - + + void RescheduleIntroducersUpdateTimer (); + void RescheduleIntroducersUpdateTimerV6 (); + + i2p::util::MemoryPool& GetSentPacketsPool () { return m_SentPacketsPool; }; private: @@ -93,25 +110,55 @@ namespace transport void ScheduleTermination (); void HandleTerminationTimer (const boost::system::error_code& ecode); - void ScheduleResend (); + 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::map > m_SessionsByRouterHash; + std::unordered_map > m_SessionsByRouterHash; std::map > m_PendingOutgoingSessions; 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; - boost::asio::deadline_timer m_TerminationTimer, m_ResendTimer; + i2p::util::MemoryPool m_SentPacketsPool; + boost::asio::deadline_timer m_TerminationTimer, 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 diff --git a/libi2pd/SSU2Session.cpp b/libi2pd/SSU2Session.cpp index 55c07b90..493a8a76 100644 --- a/libi2pd/SSU2Session.cpp +++ b/libi2pd/SSU2Session.cpp @@ -9,7 +9,6 @@ #include #include #include "Log.h" -#include "RouterContext.h" #include "Transports.h" #include "Gzip.h" #include "NetDb.hpp" @@ -19,14 +18,31 @@ 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 (); + *newMsg = *msg; + msg = newMsg; + } + if (msg->Concat (fragment, fragmentSize) < fragmentSize) + LogPrint (eLogError, "SSU2: I2NP buffer overflow ", msg->maxLen); + nextFragmentNum++; + } + + 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_IsDataReceived (false), - m_WindowSize (SSU2_MAX_WINDOW_SIZE), m_RelayTag (0), - m_ConnectTimer (server.GetService ()) + m_SendPacketNum (0), m_ReceivePacketNum (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) @@ -52,17 +68,17 @@ namespace transport void SSU2Session::Connect () { if (m_State == eSSU2SessionStateUnknown || m_State == eSSU2SessionStateTokenReceived) - { + { ScheduleConnectTimer (); auto token = m_Server.FindOutgoingToken (m_RemoteEndpoint); if (token) SendSessionRequest (token); else - { - m_State = eSSU2SessionStateUnknown; + { + m_State = eSSU2SessionStateUnknown; SendTokenRequest (); - } - } + } + } } void SSU2Session::ScheduleConnectTimer () @@ -82,7 +98,7 @@ namespace transport Terminate (); } } - + bool SSU2Session::Introduce (std::shared_ptr session, uint32_t relayTag) { // we are Alice @@ -95,7 +111,7 @@ namespace transport RAND_bytes ((uint8_t *)&nonce, 4); auto ts = i2p::util::GetSecondsSinceEpoch (); // payload - uint8_t payload[SSU2_MAX_PAYLOAD_SIZE]; + uint8_t payload[SSU2_MAX_PACKET_SIZE]; size_t payloadSize = 0; payload[0] = eSSU2BlkRelayRequest; payload[3] = 0; // flag @@ -103,7 +119,7 @@ namespace transport htobe32buf (payload + 8, relayTag); htobe32buf (payload + 12, ts); payload[16] = 2; // ver - size_t asz = CreateEndpoint (payload + 18, SSU2_MAX_PAYLOAD_SIZE - 18, boost::asio::ip::udp::endpoint (localAddress->host, localAddress->port)); + 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; @@ -115,7 +131,7 @@ namespace transport s.Sign (i2p::context.GetPrivateKeys (), payload + payloadSize); payloadSize += i2p::context.GetIdentity ()->GetSignatureLen (); htobe16buf (payload + 1, payloadSize - 3); // size - payloadSize += CreatePaddingBlock (payload + payloadSize, SSU2_MAX_PAYLOAD_SIZE - payloadSize); + 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); @@ -131,48 +147,90 @@ namespace transport 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::GetSecondsSinceEpoch (); + 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)); + 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 - uint8_t payload[SSU2_MAX_PAYLOAD_SIZE]; - size_t payloadSize = CreatePeerTestBlock (payload, SSU2_MAX_PAYLOAD_SIZE, nonce); - payloadSize += CreatePaddingBlock (payload + payloadSize, SSU2_MAX_PAYLOAD_SIZE - payloadSize); - SendData (payload, payloadSize); - } - + 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, 5); + SendData (payload, payloadSize); + } + } + void SSU2Session::Terminate () { if (m_State != eSSU2SessionStateTerminated) { m_State = eSSU2SessionStateTerminated; m_ConnectTimer.cancel (); - transports.PeerDisconnected (shared_from_this ()); m_OnEstablished = nullptr; - m_Server.RemoveSession (m_SourceConnID); if (m_RelayTag) m_Server.RemoveRelay (m_RelayTag); m_SentHandshakePacket.reset (nullptr); + m_SessionConfirmedFragment.reset (nullptr); + m_PathChallenge.reset (nullptr); m_SendQueue.clear (); + m_SentPackets.clear (); + m_IncompleteMessages.clear (); + m_RelaySessions.clear (); + m_PeerTests.clear (); + m_Server.RemoveSession (m_SourceConnID); + transports.PeerDisconnected (shared_from_this ()); LogPrint (eLogDebug, "SSU2: Session terminated"); } } - void SSU2Session::TerminateByTimeout () + void SSU2Session::RequestTermination (SSU2TerminationReason reason) { - SendTermination (); - m_Server.GetService ().post (std::bind (&SSU2Session::Terminate, shared_from_this ())); + if (m_State == eSSU2SessionStateEstablished || m_State == eSSU2SessionStateClosing) + { + m_TerminationReason = reason; + SendTermination (); + } + m_State = eSSU2SessionStateClosing; } void SSU2Session::Established () @@ -180,16 +238,16 @@ namespace transport m_State = eSSU2SessionStateEstablished; m_EphemeralKeys = nullptr; m_NoiseState.reset (nullptr); - m_SessionConfirmedFragment1.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) - { + if (m_OnEstablished) + { m_OnEstablished (); m_OnEstablished = nullptr; - } + } } void SSU2Session::Done () @@ -200,26 +258,26 @@ namespace transport 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_PAYLOAD_SIZE]; - size_t payloadSize = s->CreateRouterInfoBlock (payload, SSU2_MAX_PAYLOAD_SIZE - 32, i2p::context.GetSharedRouterInfo ()); + uint8_t payload[SSU2_MAX_PACKET_SIZE]; + size_t payloadSize = s->CreateRouterInfoBlock (payload, s->m_MaxPayloadSize - 32, i2p::context.GetSharedRouterInfo ()); if (payloadSize) { - if (payloadSize < SSU2_MAX_PAYLOAD_SIZE) - payloadSize += s->CreatePaddingBlock (payload + payloadSize, SSU2_MAX_PAYLOAD_SIZE - 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)); @@ -227,109 +285,189 @@ namespace transport void SSU2Session::PostI2NPMessages (std::vector > msgs) { + if (m_State == eSSU2SessionStateTerminated) return; for (auto it: msgs) m_SendQueue.push_back (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); + } + } } bool SSU2Session::SendQueue () { if (!m_SendQueue.empty () && m_SentPackets.size () <= m_WindowSize) { - auto nextResend = i2p::util::GetSecondsSinceEpoch () + SSU2_RESEND_INTERVAL; - auto packet = std::make_shared(); - packet->payloadSize += CreateAckBlock (packet->payload + packet->payloadSize, SSU2_MAX_PAYLOAD_SIZE - packet->payloadSize); + 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 (); - size_t len = msg->GetNTCP2Length (); - if (len + 3 < SSU2_MAX_PAYLOAD_SIZE - packet->payloadSize) + if (!msg) { m_SendQueue.pop_front (); - packet->payloadSize += CreateI2NPBlock (packet->payload + packet->payloadSize, SSU2_MAX_PAYLOAD_SIZE - packet->payloadSize, std::move (msg)); + continue; } - else if (len > SSU2_MAX_PAYLOAD_SIZE) // message too long + size_t len = msg->GetNTCP2Length () + 3; + if (len > m_MaxPayloadSize) // message too long { m_SendQueue.pop_front (); - SendFragmentedMessage (msg); + 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 - if (packet->payloadSize + 16 < SSU2_MAX_PAYLOAD_SIZE) - packet->payloadSize += CreatePaddingBlock (packet->payload + packet->payloadSize, SSU2_MAX_PAYLOAD_SIZE - packet->payloadSize); uint32_t packetNum = SendData (packet->payload, packet->payloadSize); - packet->nextResendTime = nextResend; + packet->sendTime = ts; m_SentPackets.emplace (packetNum, packet); - packet = std::make_shared(); - packet->payloadSize += CreateAckBlock (packet->payload + packet->payloadSize, SSU2_MAX_PAYLOAD_SIZE - packet->payloadSize); + packet = newPacket; // just ack block } }; - if (packet->payloadSize) + if (packet->payloadSize > ackBlockSize) { - if (packet->payloadSize + 16 < SSU2_MAX_PAYLOAD_SIZE) - packet->payloadSize += CreatePaddingBlock (packet->payload + packet->payloadSize, SSU2_MAX_PAYLOAD_SIZE - packet->payloadSize); - uint32_t packetNum = SendData (packet->payload, packet->payloadSize); - packet->nextResendTime = nextResend; + // 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 true; + return ackBlockSent; } return false; } - void SSU2Session::SendFragmentedMessage (std::shared_ptr msg) + 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 nextResend = i2p::util::GetSecondsSinceEpoch () + SSU2_RESEND_INTERVAL; - auto packet = std::make_shared(); - packet->payloadSize = CreateAckBlock (packet->payload, SSU2_MAX_PAYLOAD_SIZE); - auto size = CreateFirstFragmentBlock (packet->payload + packet->payloadSize, SSU2_MAX_PAYLOAD_SIZE - 16 - packet->payloadSize, msg); - if (!size) return; + 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; - packet->payloadSize += CreatePaddingBlock (packet->payload + packet->payloadSize, SSU2_MAX_PAYLOAD_SIZE - 16 - packet->payloadSize); uint32_t firstPacketNum = SendData (packet->payload, packet->payloadSize); - packet->nextResendTime = nextResend; + packet->sendTime = ts; m_SentPackets.emplace (firstPacketNum, packet); uint8_t fragmentNum = 0; while (msg->offset < msg->len) { - packet = std::make_shared(); - packet->payloadSize = CreateFollowOnFragmentBlock (packet->payload, SSU2_MAX_PAYLOAD_SIZE - 16, msg, fragmentNum, msgID); - packet->payloadSize += CreatePaddingBlock (packet->payload + packet->payloadSize, SSU2_MAX_PAYLOAD_SIZE - 16 - packet->payloadSize); - uint32_t followonPacketNum = SendData (packet->payload, packet->payloadSize); - packet->nextResendTime = nextResend; + 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; } - void SSU2Session::Resend (uint64_t ts) + size_t SSU2Session::Resend (uint64_t ts) { // resend handshake packet - if (m_SentHandshakePacket && ts >= m_SentHandshakePacket->nextResendTime) + if (m_SentHandshakePacket && ts >= m_SentHandshakePacket->sendTime + SSU2_HANDSHAKE_RESEND_INTERVAL) { LogPrint (eLogDebug, "SSU2: Resending ", (int)m_State); - m_Server.Send (m_SentHandshakePacket->header.buf, 16, m_SentHandshakePacket->headerX, 48, - m_SentHandshakePacket->payload, m_SentHandshakePacket->payloadSize, m_RemoteEndpoint); - m_SentHandshakePacket->numResends++; - m_SentHandshakePacket->nextResendTime = ts + SSU2_HANDSHAKE_RESEND_INTERVAL; - return; - } + ResendHandshakePacket (); + m_SentHandshakePacket->sendTime = ts; + return 0; + } // resend data packets - if (m_SentPackets.empty ()) return; - std::map > resentPackets; + if (m_SentPackets.empty ()) return 0; + std::map > resentPackets; for (auto it = m_SentPackets.begin (); it != m_SentPackets.end (); ) - if (ts >= it->second->nextResendTime) + if (ts >= it->second->sendTime + it->second->numResends*m_RTO) { if (it->second->numResends > SSU2_MAX_NUM_RESENDS) - it = m_SentPackets.erase (it); + { + LogPrint (eLogInfo, "SSU2: Packet was not Acked after ", it->second->numResends, " attempts. Terminate session"); + m_SentPackets.clear (); + m_SendQueue.clear (); + RequestTermination (eSSU2TerminationReasonTimeout); + return resentPackets.size (); + } else { uint32_t packetNum = SendData (it->second->payload, it->second->payloadSize); it->second->numResends++; - it->second->nextResendTime = ts + it->second->numResends*SSU2_RESEND_INTERVAL; - m_LastActivityTimestamp = ts; + it->second->sendTime = ts; resentPackets.emplace (packetNum, it->second); it = m_SentPackets.erase (it); } @@ -343,8 +481,24 @@ namespace transport #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); } - SendQueue (); } bool SSU2Session::ProcessFirstIncomingMessage (uint64_t connID, uint8_t * buf, size_t len) @@ -365,16 +519,19 @@ namespace transport break; case eSSU2PeerTest: { - // TODO: remove later - const uint8_t nonce[12] = {0}; - uint64_t headerX[2]; - i2p::crypto::ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, (uint8_t *)headerX); + // TODO: remove later + 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); + LogPrint (eLogWarning, "SSU2: Unexpected message type ", (int)header.h.type, " from ", m_RemoteEndpoint, " of ", len, " bytes"); return false; } } @@ -386,11 +543,11 @@ namespace transport // we are Alice m_EphemeralKeys = i2p::transport::transports.GetNextX25519KeysPair (); m_SentHandshakePacket.reset (new HandshakePacket); - auto ts = i2p::util::GetSecondsSinceEpoch (); - m_SentHandshakePacket->nextResendTime = ts + SSU2_HANDSHAKE_RESEND_INTERVAL; - + auto ts = i2p::util::GetMillisecondsSinceEpoch (); + m_SentHandshakePacket->sendTime = ts; + Header& header = m_SentHandshakePacket->header; - uint8_t * headerX = m_SentHandshakePacket->headerX, + uint8_t * headerX = m_SentHandshakePacket->headerX, * payload = m_SentHandshakePacket->payload; // fill packet header.h.connID = m_DestConnID; // dest id @@ -405,8 +562,15 @@ namespace transport // payload payload[0] = eSSU2BlkDateTime; htobe16buf (payload + 1, 4); - htobe32buf (payload + 3, ts); + htobe32buf (payload + 3, ts/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) @@ -425,15 +589,15 @@ namespace transport m_SentHandshakePacket->payloadSize = payloadSize; // send if (m_State == eSSU2SessionStateTokenReceived || m_Server.AddPendingOutgoingSession (shared_from_this ())) - { + { m_State = eSSU2SessionStateSessionRequestSent; m_Server.Send (header.buf, 16, headerX, 48, payload, payloadSize, m_RemoteEndpoint); - } + } else { - LogPrint (eLogWarning, "SSU2: SessionRequest request to ", m_RemoteEndpoint, " already pending"); + LogPrint (eLogWarning, "SSU2: SessionRequest request to ", m_RemoteEndpoint, " already pending"); Terminate (); - } + } } void SSU2Session::ProcessSessionRequest (Header& header, uint8_t * buf, size_t len) @@ -468,10 +632,16 @@ namespace transport } 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 ()); - m_Server.AddSession (shared_from_this ()); - SendSessionCreated (headerX + 16); + if (m_TerminationReason == eSSU2TerminationReasonNormalClose) + { + m_Server.AddSession (shared_from_this ()); + SendSessionCreated (headerX + 16); + } + else + SendRetry (); } void SSU2Session::SendSessionCreated (const uint8_t * X) @@ -479,14 +649,14 @@ namespace transport // we are Bob m_EphemeralKeys = i2p::transport::transports.GetNextX25519KeysPair (); m_SentHandshakePacket.reset (new HandshakePacket); - auto ts = i2p::util::GetSecondsSinceEpoch (); - m_SentHandshakePacket->nextResendTime = ts + SSU2_HANDSHAKE_RESEND_INTERVAL; - + 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, + uint8_t * headerX = m_SentHandshakePacket->headerX, * payload = m_SentHandshakePacket->payload; header.h.connID = m_DestConnID; // dest id header.h.packetNum = 0; @@ -498,11 +668,12 @@ namespace transport 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); + htobe32buf (payload + 3, ts/1000); size_t payloadSize = 7; - payloadSize += CreateAddressBlock (payload + payloadSize, 80 - payloadSize, m_RemoteEndpoint); + payloadSize += CreateAddressBlock (payload + payloadSize, maxPayloadSize - payloadSize, m_RemoteEndpoint); if (m_RelayTag) { payload[payloadSize] = eSSU2BlkRelayTag; @@ -512,14 +683,14 @@ namespace transport } 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, 80 - payloadSize); + } + 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); @@ -572,9 +743,11 @@ namespace transport } 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); @@ -585,9 +758,8 @@ namespace transport { // we are Alice m_SentHandshakePacket.reset (new HandshakePacket); - auto ts = i2p::util::GetSecondsSinceEpoch (); - m_SentHandshakePacket->nextResendTime = ts + SSU2_HANDSHAKE_RESEND_INTERVAL; - + 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 @@ -598,10 +770,17 @@ namespace transport memset (header.h.flags, 0, 3); header.h.flags[0] = 1; // frag, total fragments always 1 // payload - const size_t maxPayloadSize = SSU2_MAX_PAYLOAD_SIZE - 48; // part 2 + 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 ()); - // TODO: check is RouterInfo doesn't fit and split by two fragments + 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 @@ -621,14 +800,42 @@ namespace transport 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; - m_SentHandshakePacket->payloadSize = payloadSize; // 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) @@ -642,32 +849,65 @@ namespace transport header.ll[1] ^= CreateHeaderMask (kh2, buf + (len - 12)); if (header.h.type != eSSU2SessionConfirmed) { - LogPrint (eLogWarning, "SSU2: Unexpected message type ", (int)header.h.type); - return false; + LogPrint (eLogInfo, "SSU2: Unexpected message type ", (int)header.h.type, " instead ", (int)eSSU2SessionConfirmed); + // TODO: queue up + return true; } // check if fragmented - if ((header.h.flags[0] & 0x0F) > 1) + uint8_t numFragments = header.h.flags[0] & 0x0F; + if (numFragments > 1) { // fragmented + if (numFragments > 2) + { + LogPrint (eLogError, "SSU2: Too many fragments ", numFragments, " in SessionConfirmed"); + return false; + } if (!(header.h.flags[0] & 0xF0)) { // first fragment - m_SessionConfirmedFragment1.reset (new HandshakePacket); - m_SessionConfirmedFragment1->header = header; - memcpy (m_SessionConfirmedFragment1->payload, buf + 16, len - 16); - m_SessionConfirmedFragment1->payloadSize = len - 16; - return true; // wait for second 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_SessionConfirmedFragment1) return false; // out of sequence - uint8_t fullMsg[2*SSU2_MTU]; - header = m_SessionConfirmedFragment1->header; - memcpy (fullMsg + 16, m_SessionConfirmedFragment1->payload, m_SessionConfirmedFragment1->payloadSize); - memcpy (fullMsg + 16 + m_SessionConfirmedFragment1->payloadSize, buf + 16, len - 16); - buf = fullMsg; - len += m_SessionConfirmedFragment1->payloadSize; + 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; } } // KDF for Session Confirmed part 1 @@ -680,13 +920,15 @@ namespace transport 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 + // 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; @@ -695,9 +937,11 @@ namespace transport 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) @@ -718,19 +962,25 @@ namespace transport LogPrint (eLogError, "SSU2: SessionConfirmed malformed RouterInfo block"); return false; } - SetRemoteIdentity (ri->GetRouterIdentity ()); - m_Server.AddSessionByRouterHash (shared_from_this ()); // we know remote router now m_Address = ri->GetSSU2AddressWithStaticKey (S, m_RemoteEndpoint.address ().is_v6 ()); if (!m_Address) { - LogPrint (eLogError, "SSU2: No SSU2 address with static key found in SessionConfirmed"); + LogPrint (eLogError, "SSU2: No SSU2 address with static key found in SessionConfirmed 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); - i2p::data::netdb.PostI2NPMsg (CreateDatabaseStoreMsg (ri)); // TODO: should insert ri // handle other blocks HandlePayload (decryptedPayload.data () + riSize + 3, decryptedPayload.size () - riSize - 3); - KDFDataPhase (m_KeyDataReceive, m_KeyDataSend); Established (); SendQuickAck (); @@ -779,13 +1029,13 @@ namespace transport memset (nonce, 0, 12); i2p::crypto::ChaCha20 (h + 16, 16, m_Address->i, nonce, h + 16); // send - if (m_Server.AddPendingOutgoingSession (shared_from_this ())) + 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"); + LogPrint (eLogWarning, "SSU2: TokenRequest request to ", m_RemoteEndpoint, " already pending"); Terminate (); - } + } } void SSU2Session::ProcessTokenRequest (Header& header, uint8_t * buf, size_t len) @@ -795,7 +1045,7 @@ namespace transport { LogPrint (eLogWarning, "SSU2: Incorrect TokenRequest len ", len); return; - } + } uint8_t nonce[12] = {0}; uint8_t h[32]; memcpy (h, header.buf, 16); @@ -811,6 +1061,7 @@ namespace transport return; } // payload + m_State = eSSU2SessionStateTokenRequestReceived; HandlePayload (payload, len - 48); SendRetry (); } @@ -819,7 +1070,7 @@ namespace transport { // we are Bob Header header; - uint8_t h[32], payload[64]; + uint8_t h[32], payload[72]; // fill packet header.h.connID = m_DestConnID; // dest id RAND_bytes (header.buf + 8, 4); // random packet num @@ -829,15 +1080,19 @@ namespace transport header.h.flags[2] = 0; // flag memcpy (h, header.buf, 16); memcpy (h + 16, &m_SourceConnID, 8); // source id - uint64_t token = m_Server.GetIncomingToken (m_RemoteEndpoint); + 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::GetSecondsSinceEpoch ()); size_t payloadSize = 7; - payloadSize += CreateAddressBlock (payload + payloadSize, 64 - payloadSize, m_RemoteEndpoint); - payloadSize += CreatePaddingBlock (payload + payloadSize, 64 - payloadSize); + 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); @@ -860,13 +1115,15 @@ namespace transport 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); + LogPrint (eLogWarning, "SSU2: Unexpected message type ", (int)header.h.type, " instead ", (int)eSSU2Retry); return false; } uint8_t nonce[12] = {0}; uint64_t headerX[2]; // sourceConnID, token i2p::crypto::ChaCha20 (buf + 16, 16, m_Address->i, nonce, (uint8_t *)headerX); - m_Server.UpdateOutgoingToken (m_RemoteEndpoint, headerX[1], i2p::util::GetSecondsSinceEpoch () + SSU2_TOKEN_EXPIRATION_TIMEOUT); + 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); @@ -876,22 +1133,29 @@ namespace transport if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len - 48, h, 32, m_Address->i, nonce, payload, len - 48, false)) { - LogPrint (eLogWarning, "SSU2: Retry AEAD verification failed "); + LogPrint (eLogWarning, "SSU2: Retry AEAD verification failed"); return false; } - HandlePayload (payload, len - 48); - 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 (headerX[1]); + SendSessionRequest (token); return true; } - void SSU2Session::SendHolePunch (uint32_t nonce, const boost::asio::ip::udp::endpoint& ep, const uint8_t * introKey) + 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_PAYLOAD_SIZE]; + 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 @@ -908,10 +1172,10 @@ namespace transport htobe16buf (payload + 1, 4); htobe32buf (payload + 3, i2p::util::GetSecondsSinceEpoch ()); size_t payloadSize = 7; - payloadSize += CreateAddressBlock (payload + payloadSize, SSU2_MAX_PAYLOAD_SIZE - payloadSize, ep); - payloadSize += CreateRelayResponseBlock (payload + payloadSize, SSU2_MAX_PAYLOAD_SIZE - payloadSize, - eSSU2RelayResponseCodeAccept ,nonce, true); - payloadSize += CreatePaddingBlock (payload + payloadSize, SSU2_MAX_PAYLOAD_SIZE - payloadSize); + 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); @@ -935,7 +1199,7 @@ namespace transport 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); + LogPrint (eLogWarning, "SSU2: Unexpected message type ", (int)header.h.type, " instead ", (int)eSSU2HolePunch); return false; } uint8_t nonce[12] = {0}; @@ -956,18 +1220,7 @@ namespace transport } HandlePayload (payload, len - 48); // connect to Charlie - 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 (); - } + ConnectAfterIntroduction (); return true; } @@ -975,7 +1228,7 @@ namespace transport 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_PAYLOAD_SIZE]; + 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 @@ -991,10 +1244,10 @@ namespace transport htobe32buf (payload + 3, i2p::util::GetSecondsSinceEpoch ()); size_t payloadSize = 7; if (msg == 6 || msg == 7) - payloadSize += CreateAddressBlock (payload + payloadSize, SSU2_MAX_PAYLOAD_SIZE - payloadSize, m_RemoteEndpoint); - payloadSize += CreatePeerTestBlock (payload + payloadSize, SSU2_MAX_PAYLOAD_SIZE - payloadSize, + 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, SSU2_MAX_PAYLOAD_SIZE - payloadSize); + payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize); // encrypt uint8_t n[12]; CreateNonce (be32toh (header.h.packetNum), n); @@ -1006,8 +1259,8 @@ namespace transport 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 @@ -1017,7 +1270,7 @@ namespace transport 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); + LogPrint (eLogWarning, "SSU2: Unexpected message type ", (int)header.h.type, " instead ", (int)eSSU2PeerTest); return false; } uint8_t nonce[12] = {0}; @@ -1040,7 +1293,7 @@ namespace transport return true; } - uint32_t SSU2Session::SendData (const uint8_t * buf, size_t len) + uint32_t SSU2Session::SendData (const uint8_t * buf, size_t len, uint8_t flags) { if (len < 8) { @@ -1052,10 +1305,11 @@ namespace transport 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_MTU]; - i2p::crypto::AEADChaCha20Poly1305 (buf, len, header.buf, 16, m_KeyDataSend, nonce, payload, SSU2_MTU, true); + 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); @@ -1065,7 +1319,7 @@ namespace transport return m_SendPacketNum - 1; } - void SSU2Session::ProcessData (uint8_t * buf, size_t len) + void SSU2Session::ProcessData (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& from) { Header header; header.ll[0] = m_SourceConnID; @@ -1073,10 +1327,20 @@ namespace transport 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); + 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; } - uint8_t payload[SSU2_MTU]; + if (from != m_RemoteEndpoint && !i2p::util::net::IsInReservedRange (from.address ())) + { + LogPrint (eLogInfo, "SSU2: Remote endpoint update ", m_RemoteEndpoint, "->", from); + m_RemoteEndpoint = from; + SendPathChallenge (); + } + uint8_t payload[SSU2_MAX_PACKET_SIZE]; size_t payloadSize = len - 32; uint32_t packetNum = be32toh (header.h.packetNum); uint8_t nonce[12]; @@ -1089,7 +1353,7 @@ namespace transport } m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch (); m_NumReceivedBytes += len; - if (UpdateReceivePacketNum (packetNum)) + if (!packetNum || UpdateReceivePacketNum (packetNum)) HandlePayload (payload, payloadSize); } @@ -1112,23 +1376,24 @@ namespace transport { case eSSU2BlkDateTime: LogPrint (eLogDebug, "SSU2: Datetime"); + HandleDateTime (buf + offset, size); break; case eSSU2BlkOptions: LogPrint (eLogDebug, "SSU2: Options"); break; case eSSU2BlkRouterInfo: { - // not from SessionConfirmed + // 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.PostI2NPMsg (CreateI2NPMessage (eI2NPDummyMsg, ri->GetBuffer (), ri->GetBufferLen ())); // TODO: should insert ri + i2p::data::netdb.AddRouterInfo (ri->GetBuffer (), ri->GetBufferLen ()); // TODO: add ri break; } case eSSU2BlkI2NPMessage: { LogPrint (eLogDebug, "SSU2: I2NP message"); - auto nextMsg = NewI2NPShortMessage (); + 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 @@ -1147,8 +1412,11 @@ namespace transport m_IsDataReceived = true; break; case eSSU2BlkTermination: - LogPrint (eLogDebug, "SSU2: Termination"); - Terminate (); + LogPrint (eLogDebug, "SSU2: Termination reason=", (int)buf[11]); + if (IsEstablished () && buf[11] != eSSU2TerminationReasonTerminationReceived) + RequestTermination (eSSU2TerminationReasonTerminationReceived); + else + Done (); break; case eSSU2BlkRelayRequest: LogPrint (eLogDebug, "SSU2: RelayRequest"); @@ -1165,6 +1433,8 @@ namespace transport 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; @@ -1173,12 +1443,9 @@ namespace transport HandleAck (buf + offset, size); break; case eSSU2BlkAddress: - { - boost::asio::ip::udp::endpoint ep; - if (ExtractEndpoint (buf + offset, size, ep)) - LogPrint (eLogInfo, "SSU2: Our external address is ", ep); - break; - } + LogPrint (eLogDebug, "SSU2: Address"); + HandleAddress (buf + offset, size); + break; case eSSU2BlkIntroKey: break; case eSSU2BlkRelayTagRequest: @@ -1202,9 +1469,21 @@ namespace transport break; } case eSSU2BlkPathChallenge: + LogPrint (eLogDebug, "SSU2: Path challenge"); + SendPathResponse (buf + offset, size); break; case eSSU2BlkPathResponse: - break; + { + 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: @@ -1217,19 +1496,53 @@ namespace transport } } + 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: + if (std::abs (offset) > SSU2_CLOCK_SKEW) + m_TerminationReason = eSSU2TerminationReasonClockSkew; + break; + case eSSU2SessionStateSessionCreatedReceived: + case eSSU2SessionStateTokenReceived: + if ((m_RemoteEndpoint.address ().is_v4 () && i2p::context.GetStatus () == eRouterStatusTesting) || + (m_RemoteEndpoint.address ().is_v6 () && i2p::context.GetStatusV6 () == eRouterStatusTesting)) + { + 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); // acnt + HandleAckRange (firstPacketNum, ackThrough, i2p::util::GetMillisecondsSinceEpoch ()); // acnt // ranges len -= 5; const uint8_t * ranges = buf + 5; @@ -1241,25 +1554,88 @@ namespace transport if (*ranges > lastPacketNum + 1) break; firstPacketNum = lastPacketNum - *ranges + 1; ranges++; // acks len -= 2; - HandleAckRange (firstPacketNum, lastPacketNum); + HandleAckRange (firstPacketNum, lastPacketNum, 0); } } - void SSU2Session::HandleAckRange (uint32_t firstPacketNum, uint32_t lastPacketNum) + 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; - while (it1 != m_SentPackets.end () && it1->first <= lastPacketNum) it1++; + 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 = (m_RTT*m_SendPacketNum + rtt)/(m_SendPacketNum + 1); + 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)) + { + if (isV4) + { + if (i2p::context.GetStatus () == eRouterStatusTesting) + i2p::context.SetError (eRouterErrorSymmetricNAT); + } + else + { + if (i2p::context.GetStatusV6 () == eRouterStatusTesting) + i2p::context.SetErrorV6 (eRouterErrorSymmetricNAT); + } + } + else + { + if (isV4) + { + if (i2p::context.GetError () == eRouterErrorSymmetricNAT) + i2p::context.SetError (eRouterErrorNone); + } + else + { + if (i2p::context.GetErrorV6 () == eRouterErrorSymmetricNAT) + i2p::context.SetErrorV6 (eRouterErrorNone); + } + } + } + } } void SSU2Session::HandleFirstFragment (const uint8_t * buf, size_t len) { uint32_t msgID; memcpy (&msgID, buf + 1, 4); - auto msg = NewI2NPMessage (); + auto msg = NewI2NPShortMessage (); // same format as I2NP message block msg->len = msg->offset + len + 7; memcpy (msg->GetNTCP2Header (), buf, len); @@ -1297,10 +1673,11 @@ namespace transport auto it = m_IncompleteMessages.find (msgID); if (it != m_IncompleteMessages.end ()) { - if (it->second->nextFragmentNum == fragmentNum && it->second->msg) + if (it->second->nextFragmentNum == fragmentNum && fragmentNum < SSU2_MAX_NUM_FRAGMENTS && + it->second->msg) { // in sequence - it->second->msg->Concat (buf + 5, len - 5); + it->second->AttachNextFragment (buf + 5, len - 5); if (isLast) { it->second->msg->FromNTCP2 (); @@ -1309,7 +1686,6 @@ namespace transport } else { - it->second->nextFragmentNum++; if (ConcatOutOfSequenceFragments (it->second)) { m_Handler.PutNextMessage (std::move (it->second->msg)); @@ -1329,6 +1705,11 @@ namespace transport it = m_IncompleteMessages.emplace (msgID, msg).first; } // insert out of sequence fragment + if (fragmentNum >= SSU2_MAX_NUM_FRAGMENTS) + { + LogPrint (eLogWarning, "SSU2: Fragment number ", fragmentNum, " exceeds ", SSU2_MAX_NUM_FRAGMENTS); + return; + } auto fragment = std::make_shared (); memcpy (fragment->buf, buf + 5, len -5); fragment->len = len - 5; @@ -1344,10 +1725,9 @@ namespace transport for (auto it = m->outOfSequenceFragments.begin (); it != m->outOfSequenceFragments.end ();) if (it->first == m->nextFragmentNum) { - m->msg->Concat (it->second->buf, it->second->len); + m->AttachNextFragment (it->second->buf, it->second->len); isLast = it->second->isLast; it = m->outOfSequenceFragments.erase (it); - m->nextFragmentNum++; } else break; @@ -1363,29 +1743,29 @@ namespace transport { LogPrint (eLogWarning, "SSU2: RelayRequest session with relay tag ", relayTag, " not found"); // send relay response back to Alice - uint8_t payload[SSU2_MAX_PAYLOAD_SIZE]; - size_t payloadSize = CreateRelayResponseBlock (payload, SSU2_MAX_PAYLOAD_SIZE, - eSSU2RelayResponseCodeBobRelayTagNotFound, bufbe32toh (buf + 1), false); - payloadSize += CreatePaddingBlock (payload + payloadSize, SSU2_MAX_PAYLOAD_SIZE - payloadSize); + 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; + 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) + if (r) i2p::data::netdb.PopulateRouterInfoBuffer (r); else LogPrint (eLogWarning, "SSU2: RelayRequest Alice's router info not found"); - uint8_t payload[SSU2_MAX_PAYLOAD_SIZE]; - size_t payloadSize = r ? CreateRouterInfoBlock (payload, SSU2_MAX_PAYLOAD_SIZE - len - 32, r) : 0; + 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, SSU2_MAX_PAYLOAD_SIZE - payloadSize, buf + 1, len -1); - if (payloadSize < SSU2_MAX_PAYLOAD_SIZE) - payloadSize += CreatePaddingBlock (payload + payloadSize, SSU2_MAX_PAYLOAD_SIZE - payloadSize); + 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); } @@ -1393,9 +1773,11 @@ namespace transport { // 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 @@ -1413,15 +1795,27 @@ namespace transport if (addr) { if (m_Server.IsSupported (ep.address ())) - SendHolePunch (bufbe32toh (buf + 33), ep, addr->i); + { + 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: RelayInfo unknown address"); - code = eSSU2RelayResponseCodeCharlieAliceIsUnknown; - } + LogPrint (eLogWarning, "SSU2: RelayIntro unknown address"); + code = eSSU2RelayResponseCodeCharlieAliceIsUnknown; + } + } + else + { + LogPrint (eLogWarning, "SSU2: RelayIntro can't extract endpoint"); + code = eSSU2RelayResponseCodeCharlieAliceIsUnknown; } } else @@ -1429,25 +1823,25 @@ namespace transport LogPrint (eLogWarning, "SSU2: RelayIntro signature verification failed"); code = eSSU2RelayResponseCodeCharlieSignatureFailure; } - } + } else { LogPrint (eLogError, "SSU2: RelayIntro unknown router to introduce"); code = eSSU2RelayResponseCodeCharlieAliceIsUnknown; - } + } // send relay response to Bob - uint8_t payload[SSU2_MAX_PAYLOAD_SIZE]; - size_t payloadSize = CreateRelayResponseBlock (payload, SSU2_MAX_PAYLOAD_SIZE, - code, bufbe32toh (buf + 33), true); - payloadSize += CreatePaddingBlock (payload + payloadSize, SSU2_MAX_PAYLOAD_SIZE - payloadSize); + 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) - { + if (m_State == eSSU2SessionStateIntroduced) + { // HolePunch from Charlie // TODO: verify address and signature // verify nonce @@ -1456,26 +1850,26 @@ namespace transport if (len >= 8) { // new token - uint64_t 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); + } + 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_PAYLOAD_SIZE]; + 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, SSU2_MAX_PAYLOAD_SIZE - payloadSize); - it->second.first->SendData (payload, payloadSize); - } + payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize); + it->second.first->SendData (payload, payloadSize); + } else { // we are Alice, message from Bob @@ -1490,20 +1884,33 @@ namespace transport 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 - ExtractEndpoint (buf + 12, csz, it->second.first->m_RemoteEndpoint); + 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"); - m_Server.GetService ().post (std::bind (&SSU2Session::Terminate, it->second.first)); - } + it->second.first->Done (); + } } else - { + { LogPrint (eLogInfo, "SSU2: RelayResponse status code=", (int)buf[1]); - m_Server.GetService ().post (std::bind (&SSU2Session::Terminate, it->second.first)); - } + it->second.first->Done (); + } } m_RelaySessions.erase (it); } @@ -1516,46 +1923,51 @@ namespace transport 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; - uint32_t nonce = bufbe32toh (buf + offset + 1); + 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 ())); - uint8_t payload[SSU2_MAX_PAYLOAD_SIZE]; + auto packet = m_Server.GetSentPacketsPool ().AcquireShared (); // Alice's RouterInfo auto r = i2p::data::netdb.FindRouter (GetRemoteIdentity ()->GetIdentHash ()); if (r) i2p::data::netdb.PopulateRouterInfoBuffer (r); - size_t payloadSize = r ? CreateRouterInfoBlock (payload, SSU2_MAX_PAYLOAD_SIZE - len - 32, r) : 0; - if (!payloadSize && r) + packet->payloadSize = r ? CreateRouterInfoBlock (packet->payload, m_MaxPayloadSize - len - 32, r) : 0; + if (!packet->payloadSize && r) session->SendFragmentedMessage (CreateDatabaseStoreMsg (r)); - if (payloadSize + len + 48 > SSU2_MAX_PAYLOAD_SIZE) + if (packet->payloadSize + len + 48 > m_MaxPayloadSize) { // doesn't fit one message, send RouterInfo in separate message - session->SendData (payload, payloadSize); - payloadSize = 0; - } + 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 - payloadSize += CreatePeerTestBlock (payload + payloadSize, SSU2_MAX_PAYLOAD_SIZE - payloadSize, 2, + packet->payloadSize += CreatePeerTestBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize - packet->payloadSize, 2, eSSU2PeerTestCodeAccept, GetRemoteIdentity ()->GetIdentHash (), buf + offset, len - offset); - payloadSize += CreatePaddingBlock (payload + payloadSize, SSU2_MAX_PAYLOAD_SIZE - payloadSize); - session->SendData (payload, payloadSize); + 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_PAYLOAD_SIZE], zeroHash[32] = {0}; - size_t payloadSize = CreatePeerTestBlock (payload, SSU2_MAX_PAYLOAD_SIZE, 4, + 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, SSU2_MAX_PAYLOAD_SIZE - payloadSize); + payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize); SendData (payload, payloadSize); - } + } break; } case 2: // Charlie from Bob @@ -1568,7 +1980,7 @@ namespace transport 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.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; @@ -1577,75 +1989,75 @@ namespace transport { 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 + 9, len - offset - 9, ep)) + 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->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 + code = eSSU2PeerTestCodeCharlieSignatureFailure; + } else // maformed message code = eSSU2PeerTestCodeCharlieReasonUnspecified; } else code = eSSU2PeerTestCodeCharlieAliceIsUnknown; // send msg 3 back to Bob - uint8_t payload[SSU2_MAX_PAYLOAD_SIZE]; - size_t payloadSize = CreatePeerTestBlock (payload, SSU2_MAX_PAYLOAD_SIZE, 3, + 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, SSU2_MAX_PAYLOAD_SIZE - payloadSize); + 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_PAYLOAD_SIZE]; + uint8_t payload[SSU2_MAX_PACKET_SIZE]; // Charlie's RouterInfo auto r = i2p::data::netdb.FindRouter (GetRemoteIdentity ()->GetIdentHash ()); if (r) i2p::data::netdb.PopulateRouterInfoBuffer (r); - size_t payloadSize = r ? CreateRouterInfoBlock (payload, SSU2_MAX_PAYLOAD_SIZE - len - 32, r) : 0; + 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 > SSU2_MAX_PAYLOAD_SIZE) + 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, SSU2_MAX_PAYLOAD_SIZE, 4, + payloadSize += CreatePeerTestBlock (payload + payloadSize, m_MaxPayloadSize, 4, (SSU2PeerTestCode)buf[1], GetRemoteIdentity ()->GetIdentHash (), buf + offset, len - offset); - if (payloadSize < SSU2_MAX_PAYLOAD_SIZE) - payloadSize += CreatePaddingBlock (payload + payloadSize, SSU2_MAX_PAYLOAD_SIZE - payloadSize); + if (payloadSize < m_MaxPayloadSize) + payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize); it->second.first->SendData (payload, payloadSize); m_PeerTests.erase (it); } @@ -1654,68 +2066,96 @@ namespace transport break; } case 4: // Alice from Bob - { + { auto it = m_PeerTests.find (nonce); if (it != m_PeerTests.end ()) { if (buf[1] == eSSU2PeerTestCodeAccept) - { + { + if (GetRouterStatus () == eRouterStatusUnknown) + SetRouterStatus (eRouterStatusTesting); 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 + 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 + // 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 (GetRouterStatus () == eRouterStatusTesting) + { + 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->Terminate (); - } + it->second.first->Done (); + } } else - { + { LogPrint (eLogWarning, "SSU2: Peer test 4 signature verification failed"); - it->second.first->Terminate (); - } - } + 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]); - it->second.first->Terminate (); - } + 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 (GetRouterStatus () == eRouterStatusTesting) + 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; @@ -1728,6 +2168,8 @@ namespace transport break; case 7: // Alice from Charlie 2 m_Server.RemoveSession (htobe64 (((uint64_t)nonce << 32) | nonce)); + if (m_Address->IsV6 ()) + i2p::context.SetStatusV6 (eRouterStatusOK); // set status OK for ipv6 even if from SSU2 break; default: LogPrint (eLogWarning, "SSU2: PeerTest unexpected msg num ", buf[0]); @@ -1787,8 +2229,49 @@ namespace transport 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.SetStatusSSU2 (status); + else if (m_Address->IsV6 ()) + i2p::context.SetStatusV6SSU2 (status); + } + } + size_t SSU2Session::CreateAddressBlock (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& ep) { if (len < 9) return 0; @@ -1812,6 +2295,7 @@ namespace transport 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 @@ -1824,10 +2308,12 @@ namespace transport 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 - uint8_t acnt = 0; + uint16_t acnt = 0; int numRanges = 0; if (ackThrough) { @@ -1843,26 +2329,63 @@ namespace transport } // ranges uint32_t lastNum = ackThrough - acnt; - while (it != m_OutOfSequencePackets.rend () && numRanges < SSU2_MAX_NUM_ACK_RANGES) + if (acnt > 255) { - if (lastNum - (*it) < 255) + auto d = std::div (acnt - 255, 255); + acnt = 255; + if (d.quot > maxNumRanges) { - buf[8 + numRanges*2] = lastNum - (*it) - 1; // NACKs - lastNum = *it; it++; - uint8_t numAcks = 1; - while (it != m_OutOfSequencePackets.rend () && numAcks < 255 && lastNum > 0 && *it == lastNum - 1) - { - numAcks++; lastNum--; - it++; - } - buf[8 + numRanges*2 + 1] = numAcks; // Acks - numRanges++; - if (numAcks == 255) break; + d.quot = maxNumRanges; + d.rem = 0; + } + // Acks only ragnes for acnt + for (int i = 0; i < d.quot; i++) + { + buf[8 + numRanges*2] = 0; buf[8 + numRanges*2 + 1] = 255; // NACKs 0, Acks 255 + numRanges++; + } + if (d.rem > 0) + { + buf[8 + numRanges*2] = 0; buf[8 + numRanges*2 + 1] = d.rem; + numRanges++; } - else - break; } - if (numRanges < SSU2_MAX_NUM_ACK_RANGES && it == m_OutOfSequencePackets.rend ()) + while (it != m_OutOfSequencePackets.rend () && numRanges < maxNumRanges) + { + if (lastNum - (*it) > 255) + { + // NACKs only ranges + if (lastNum > (*it) + 255*(maxNumRanges - numRanges)) break; // too many NACKs + while (lastNum - (*it) > 255) + { + buf[8 + numRanges*2] = 255; buf[8 + numRanges*2 + 1] = 0; // NACKs 255, Acks 0 + lastNum -= 255; + numRanges++; + } + } + // NACKs and Acks ranges + buf[8 + numRanges*2] = lastNum - (*it) - 1; // NACKs + lastNum = *it; it++; + int numAcks = 1; + while (it != m_OutOfSequencePackets.rend () && lastNum > 0 && *it == lastNum - 1) + { + numAcks++; lastNum--; + it++; + } + while (numAcks > 255) + { + // Acks only ranges + buf[8 + numRanges*2 + 1] = 255; // Acks 255 + numAcks -= 255; + numRanges++; + buf[8 + numRanges*2] = 0; // NACKs 0 + if (numRanges >= maxNumRanges) break; + } + if (numAcks > 255) numAcks = 255; + buf[8 + numRanges*2 + 1] = (uint8_t)numAcks; // Acks + numRanges++; + } + if (numRanges < maxNumRanges && it == m_OutOfSequencePackets.rend ()) { // add range between out-of-seqence and received int nacks = *m_OutOfSequencePackets.begin () - m_ReceivePacketNum - 1; @@ -1872,21 +2395,21 @@ namespace transport buf[8 + numRanges*2] = nacks; buf[8 + numRanges*2 + 1] = std::min ((int)m_ReceivePacketNum + 1, 255); numRanges++; - } - } + } + } } } - buf[7] = acnt; // acnt + 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 < minSize) return 0; - uint8_t paddingSize = rand () & 0x0F; // 0 - 15 - if (paddingSize > len) paddingSize = len; - else if (paddingSize < minSize) paddingSize = 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; if (paddingSize) { buf[0] = eSSU2BlkPadding; @@ -1958,8 +2481,8 @@ namespace transport return payloadSize + 3; } - size_t SSU2Session::CreateRelayResponseBlock (uint8_t * buf, size_t len, - SSU2RelayResponseCode code, uint32_t nonce, bool endpoint) + 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 @@ -1968,23 +2491,52 @@ namespace transport htobe32buf (buf + 9, i2p::util::GetSecondsSinceEpoch ()); // timestamp buf[13] = 2; // ver size_t csz = 0; - if (endpoint) - { - csz = CreateEndpoint (buf + 15, len - 15, boost::asio::ip::udp::endpoint (m_Address->host, m_Address->port)); - if (!csz) return 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 - s.Insert (endpoint ? GetRemoteIdentity ()->GetIdentHash () : i2p::context.GetIdentity ()->GetIdentHash (), 32); // bhash + 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 + i2p::context.GetIdentity ()->GetSignatureLen (); + 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) { @@ -1998,22 +2550,23 @@ namespace transport 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 ()) + 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]; @@ -2026,12 +2579,21 @@ namespace transport 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.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, + 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; @@ -2082,23 +2644,53 @@ namespace transport void SSU2Session::SendQuickAck () { - uint8_t payload[SSU2_MTU]; - size_t payloadSize = CreateAckBlock (payload, SSU2_MTU); - payloadSize += CreatePaddingBlock (payload + payloadSize, SSU2_MTU - payloadSize); + uint8_t payload[SSU2_MAX_PACKET_SIZE]; + size_t payloadSize = CreateAckBlock (payload, m_MaxPayloadSize); + payloadSize += CreatePaddingBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize); SendData (payload, payloadSize); } void SSU2Session::SendTermination () { uint8_t payload[32]; - size_t payloadSize = 12; - payload[0] = eSSU2BlkTermination; - htobe16buf (payload + 1, 9); - memset (payload + 3, 0, 9); + 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 < 8 || 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); + SendData (payload, len + 3); + } + + 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); + SendData (payload, len); + } + void SSU2Session::CleanUp (uint64_t ts) { for (auto it = m_IncompleteMessages.begin (); it != m_IncompleteMessages.end ();) @@ -2111,11 +2703,30 @@ namespace transport else ++it; } - if (m_OutOfSequencePackets.size () > 255) + if (!m_OutOfSequencePackets.empty ()) { - m_ReceivePacketNum = *m_OutOfSequencePackets.rbegin (); - m_OutOfSequencePackets.clear (); + if (m_OutOfSequencePackets.size () > 2*SSU2_MAX_NUM_ACK_RANGES || + *m_OutOfSequencePackets.rbegin () > m_ReceivePacketNum + 255*8) + { + 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); + } + else + LogPrint (eLogError, "SSU2: Out of sequence packet ", packet, " is less than last received ", m_ReceivePacketNum); + } + 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) @@ -2136,6 +2747,8 @@ namespace transport else ++it; } + if (m_PathChallenge) + RequestTermination (eSSU2TerminationReasonNormalClose); } void SSU2Session::FlushData () diff --git a/libi2pd/SSU2Session.h b/libi2pd/SSU2Session.h index f1dba8a7..a4306e68 100644 --- a/libi2pd/SSU2Session.h +++ b/libi2pd/SSU2Session.h @@ -17,6 +17,7 @@ #include #include "Crypto.h" #include "RouterInfo.h" +#include "RouterContext.h" #include "TransportSession.h" namespace i2p @@ -25,19 +26,30 @@ 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_MTU = 1440; // TODO: should be 1456 for ipv4 - const size_t SSU2_MAX_PAYLOAD_SIZE = SSU2_MTU - 32; - const int SSU2_HANDSHAKE_RESEND_INTERVAL = 1; // in seconds - const int SSU2_RESEND_INTERVAL = 3; // in 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 millseconds + 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 size_t SSU2_MAX_WINDOW_SIZE = 128; // in packets + 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_ACK_RANGES = 32; // to send + const uint8_t SSU2_MAX_NUM_FRAGMENTS = 64; + + // flags + const uint8_t SSU2_FLAG_IMMEDIATE_ACK_REQUESTED = 0x01; enum SSU2MessageType { @@ -82,14 +94,18 @@ namespace transport eSSU2SessionStateUnknown, eSSU2SessionStateTokenReceived, eSSU2SessionStateSessionRequestSent, + eSSU2SessionStateSessionRequestReceived, eSSU2SessionStateSessionCreatedSent, + eSSU2SessionStateSessionCreatedReceived, eSSU2SessionStateSessionConfirmedSent, eSSU2SessionStateEstablished, + eSSU2SessionStateClosing, eSSU2SessionStateTerminated, eSSU2SessionStateFailed, eSSU2SessionStateIntroduced, eSSU2SessionStatePeerTest, - eSSU2SessionStatePeerTestReceived // 5 before 4 + eSSU2SessionStatePeerTestReceived, // 5 before 4 + eSSU2SessionStateTokenRequestReceived }; enum SSU2PeerTestCode @@ -107,7 +123,7 @@ namespace transport eSSU2PeerTestCodeCharlieAliceIsBanned = 69, eSSU2PeerTestCodeCharlieAliceIsUnknown = 70, eSSU2PeerTestCodeUnspecified = 128 - }; + }; enum SSU2RelayResponseCode { @@ -116,13 +132,40 @@ namespace transport 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_MTU]; + uint8_t buf[SSU2_MAX_PACKET_SIZE]; size_t len; bool isLast; }; @@ -131,6 +174,16 @@ namespace transport int nextFragmentNum; uint32_t lastFragmentInsertTime; // in seconds std::map > outOfSequenceFragments; + + void AttachNextFragment (const uint8_t * fragment, size_t fragmentSize); + }; + + struct SSU2SentPacket + { + uint8_t payload[SSU2_MAX_PACKET_SIZE]; + size_t payloadSize = 0; + uint64_t sendTime; // in milliseconds + int numResends = 0; }; // RouterInfo flags @@ -153,18 +206,14 @@ namespace transport } h; }; - struct SentPacket - { - uint8_t payload[SSU2_MAX_PAYLOAD_SIZE]; - size_t payloadSize = 0; - uint32_t nextResendTime; // in seconds - int numResends = 0; - }; - - struct HandshakePacket: public SentPacket + 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; @@ -186,15 +235,15 @@ namespace transport bool Introduce (std::shared_ptr session, uint32_t relayTag); void WaitForIntroduction (); void SendPeerTest (); // Alice, Data message - void Terminate (); - void TerminateByTimeout (); + 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; }; - void Resend (uint64_t ts); + size_t Resend (uint64_t ts); // return number or resent packets bool IsEstablished () const { return m_State == eSSU2SessionStateEstablished; }; uint64_t GetConnID () const { return m_SourceConnID; }; SSU2SessionState GetState () const { return m_State; }; @@ -206,16 +255,19 @@ namespace transport 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); + 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 (); - void SendFragmentedMessage (std::shared_ptr msg); + 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); @@ -226,18 +278,25 @@ namespace transport void KDFDataPhase (uint8_t * keydata_ab, uint8_t * keydata_ba); void SendTokenRequest (); void SendRetry (); - uint32_t SendData (const uint8_t * buf, size_t len); // returns packet num + 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); - void SendPeerTest (uint8_t msg, const uint8_t * signedData, size_t signedDataLen, const uint8_t * introKey); // PeerTest message - + 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); + 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; 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 @@ -257,17 +316,18 @@ namespace transport 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, bool endpoint); // add endpoint for Chralie and no endpoint for Bob + 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_SessionConfirmedFragment1; // for Bob if applicable - std::unique_ptr m_SentHandshakePacket; // SessionRequest or SessionCreated + 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 @@ -276,17 +336,20 @@ namespace transport uint8_t m_KeyDataSend[64], m_KeyDataReceive[64]; uint32_t m_SendPacketNum, m_ReceivePacketNum; std::set m_OutOfSequencePackets; // packet nums > receive packet num - std::map > m_SentPackets; // packetNum -> packet + std::map > m_SentPackets; // packetNum -> packet std::map > m_IncompleteMessages; // I2NP std::map, uint64_t > > m_RelaySessions; // nonce->(Alice, timestamp) for Bob or nonce->(Charlie, timestamp) for Alice std::map, uint64_t > > m_PeerTests; // same as for relay sessions std::list > m_SendQueue; i2p::I2NPMessagesHandler m_Handler; bool m_IsDataReceived; - size_t m_WindowSize; + 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; }; inline uint64_t CreateHeaderMask (const uint8_t * kh, const uint8_t * nonce) diff --git a/libi2pd/SSUData.h b/libi2pd/SSUData.h index 1746dcd8..eba0fc28 100644 --- a/libi2pd/SSUData.h +++ b/libi2pd/SSUData.h @@ -19,17 +19,14 @@ #include "I2NPProtocol.h" #include "Identity.h" #include "RouterInfo.h" +#include "TransportSession.h" namespace i2p { namespace transport { - const size_t SSU_MTU_V4 = 1484; const size_t SSU_MTU_V6 = 1488; - const size_t IPV4_HEADER_SIZE = 20; - const size_t IPV6_HEADER_SIZE = 40; - const size_t UDP_HEADER_SIZE = 8; const size_t SSU_V4_MAX_PACKET_SIZE = SSU_MTU_V4 - IPV4_HEADER_SIZE - UDP_HEADER_SIZE; // 1456 const size_t SSU_V6_MAX_PACKET_SIZE = SSU_MTU_V6 - IPV6_HEADER_SIZE - UDP_HEADER_SIZE; // 1440 const int RESEND_INTERVAL = 3; // in seconds diff --git a/libi2pd/SSUSession.cpp b/libi2pd/SSUSession.cpp index 817133e8..73f9cdb1 100644 --- a/libi2pd/SSUSession.cpp +++ b/libi2pd/SSUSession.cpp @@ -41,7 +41,6 @@ namespace transport i2p::context.GetRouterInfo ().GetSSUAddress (true); if (address) m_IntroKey = address->i; } - m_CreationTime = i2p::util::GetSecondsSinceEpoch (); } SSUSession::~SSUSession () @@ -720,8 +719,8 @@ namespace transport if (i2p::context.GetStatus () == eRouterStatusTesting) i2p::context.SetError (eRouterErrorSymmetricNAT); } - else if (i2p::context.GetStatus () == eRouterStatusError && i2p::context.GetError () == eRouterErrorSymmetricNAT) - i2p::context.SetStatus (eRouterStatusTesting); + else if (i2p::context.GetError () == eRouterErrorSymmetricNAT) + i2p::context.SetError (eRouterErrorNone); } uint32_t nonce = bufbe32toh (buf); buf += 4; // nonce diff --git a/libi2pd/SSUSession.h b/libi2pd/SSUSession.h index 535de328..e28b4991 100644 --- a/libi2pd/SSUSession.h +++ b/libi2pd/SSUSession.h @@ -103,8 +103,6 @@ namespace transport void SendKeepAlive (); uint32_t GetRelayTag () const { return m_RelayTag; }; const i2p::data::RouterInfo::IntroKey& GetIntroKey () const { return m_IntroKey; }; - uint32_t GetCreationTime () const { return m_CreationTime; }; - void SetCreationTime (uint32_t ts) { m_CreationTime = ts; }; // for introducers void FlushData (); void CleanUp (uint64_t ts); @@ -167,7 +165,6 @@ namespace transport 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 diff --git a/libi2pd/Streaming.cpp b/libi2pd/Streaming.cpp index af0a359f..9ba236d7 100644 --- a/libi2pd/Streaming.cpp +++ b/libi2pd/Streaming.cpp @@ -474,6 +474,29 @@ 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; + std::condition_variable newDataReceived; + std::mutex newDataReceivedMutex; + std::unique_lock l(newDataReceivedMutex); + AsyncReceive (boost::asio::buffer (buf, len), + [&ret, &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 (); + }, + timeout); + if (newDataReceived.wait_for (l, std::chrono::seconds (timeout)) == std::cv_status::timeout) + ret = 0; + return ret; + } + size_t Stream::Send (const uint8_t * buf, size_t len) { AsyncSend (buf, len, nullptr); @@ -729,7 +752,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); }; } @@ -855,7 +878,7 @@ namespace stream 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, @@ -1085,8 +1108,6 @@ namespace stream m_Owner (owner), m_LocalPort (localPort), m_Gzip (gzip), m_PendingIncomingTimer (m_Owner->GetService ()) { - if (m_Gzip) - m_Deflator.reset (new i2p::data::GzipDeflator); } StreamingDestination::~StreamingDestination () @@ -1341,6 +1362,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) @@ -1365,7 +1406,7 @@ 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) { size_t size; auto msg = m_I2NPMsgsPool.AcquireShared (); @@ -1373,8 +1414,8 @@ namespace stream buf += 4; // reserve for lengthlength msg->len += 4; - if (m_Gzip && m_Deflator) - size = 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); diff --git a/libi2pd/Streaming.h b/libi2pd/Streaming.h index 088c2e11..105977e8 100644 --- a/libi2pd/Streaming.h +++ b/libi2pd/Streaming.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2021, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -185,7 +185,8 @@ namespace stream template void AsyncReceive (const Buffer& buffer, ReceiveHandler handler, int timeout = 0); size_t ReadSome (uint8_t * buf, size_t len) { return ConcatenatePackets (buf, len); }; - + size_t Receive (uint8_t * buf, size_t len, int timeout); + void AsyncClose() { m_Service.post(std::bind(&Stream::Close, shared_from_this())); }; /** only call close from destination thread, use Stream::AsyncClose for other threads */ @@ -278,13 +279,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); } @@ -315,7 +317,7 @@ namespace stream public: i2p::data::GzipInflator m_Inflator; - std::unique_ptr m_Deflator; + i2p::data::GzipDeflator m_Deflator; // for HTTP only const decltype(m_Streams)& GetStreams () const { return m_Streams; }; @@ -336,11 +338,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); }); } }); diff --git a/libi2pd/TransportSession.h b/libi2pd/TransportSession.h index a51207af..3ad146cb 100644 --- a/libi2pd/TransportSession.h +++ b/libi2pd/TransportSession.h @@ -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: @@ -38,7 +42,7 @@ namespace transport { m_Stream.str(""); } - + void Insert (const uint8_t * buf, size_t len) { m_Stream.write ((char *)buf, len); @@ -75,6 +79,7 @@ namespace transport { if (router) m_RemoteIdentity = router->GetRouterIdentity (); + m_CreationTime = m_LastActivityTimestamp; } virtual ~TransportSession () {}; @@ -102,6 +107,9 @@ namespace transport bool IsTerminationTimeoutExpired (uint64_t ts) const { return ts >= m_LastActivityTimestamp + GetTerminationTimeout (); }; + 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; @@ -114,7 +122,17 @@ namespace transport bool m_IsOutgoing; int m_TerminationTimeout; uint64_t m_LastActivityTimestamp; + uint32_t m_CreationTime; // seconds since epoch }; + + // 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 31abad6e..0d5764cf 100644 --- a/libi2pd/Transports.cpp +++ b/libi2pd/Transports.cpp @@ -167,6 +167,8 @@ namespace transport m_PeerTestTimer = new boost::asio::deadline_timer (*m_Service); } + bool ipv4; i2p::config::GetOption("ipv4", ipv4); + bool ipv6; i2p::config::GetOption("ipv6", ipv6); i2p::config::GetOption("nat", m_IsNAT); m_X25519KeysPairSupplier.Start (); m_IsRunning = true; @@ -190,6 +192,8 @@ namespace transport m_NTCP2Server->UseProxy(proxytype, proxyurl.host, proxyurl.port, proxyurl.user, proxyurl.pass); i2p::context.SetStatus (eRouterStatusProxy); + if (ipv6) + i2p::context.SetStatusV6 (eRouterStatusProxy); } else LogPrint(eLogError, "Transports: Unsupported NTCP2 proxy URL ", ntcp2proxy); @@ -218,10 +222,29 @@ namespace transport } } // create SSU2 server - if (enableSSU2) m_SSU2Server = new SSU2Server (); + 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(eLogError, "Transports: Can't set SSU2 proxy ", ssu2proxy); + } + else + LogPrint(eLogError, "Transports: Invalid SSU2 proxy URL ", ssu2proxy); + } + } // bind to interfaces - bool ipv4; i2p::config::GetOption("ipv4", ipv4); if (ipv4) { std::string address; i2p::config::GetOption("address4", address); @@ -236,9 +259,19 @@ namespace transport 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); + } + } } - bool ipv6; i2p::config::GetOption("ipv6", ipv6); if (ipv6) { std::string address; i2p::config::GetOption("address6", address); @@ -253,6 +286,17 @@ namespace transport 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); @@ -285,8 +329,8 @@ namespace transport delete m_SSUServer; m_SSUServer = nullptr; } - if (m_SSUServer) DetectExternalIP (); } + if (m_SSUServer || 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)); @@ -417,8 +461,7 @@ namespace transport { auto ts = i2p::util::GetSecondsSinceEpoch (); std::unique_lock l(m_PeersMutex); - it = m_Peers.insert (std::pair(ident, { 0, r, {}, - ts, ts + PEER_ROUTER_INFO_UPDATE_INTERVAL, {} })).first; + it = m_Peers.insert (std::pair(ident, {r, ts})).first; } connected = ConnectToPeer (ident, it->second); } @@ -453,127 +496,81 @@ namespace transport peer.router = netdb.FindRouter (ident); // try to get new one from netdb if (peer.router) // we have RI already { - if (peer.numAttempts < 2) // NTCP2, 0 - ipv6, 1- ipv4 - { - if (m_NTCP2Server) // we support NTCP2 - { - std::shared_ptr address; - if (!peer.numAttempts) // NTCP2 ipv6 - { - if (context.GetRouterInfo ().IsNTCP2V6 () && peer.router->IsReachableBy (RouterInfo::eNTCP2V6)) - { - address = peer.router->GetPublishedNTCP2V6Address (); - if (address && m_CheckReserved && i2p::util::net::IsInReservedRange(address->host)) - address = nullptr; - } - peer.numAttempts++; - } - if (!address && peer.numAttempts == 1) // NTCP2 ipv4 - { - if (context.GetRouterInfo ().IsNTCP2 (true) && peer.router->IsReachableBy (RouterInfo::eNTCP2V4)) - { - address = peer.router->GetPublishedNTCP2V4Address (); - if (address && m_CheckReserved && i2p::util::net::IsInReservedRange(address->host)) - address = nullptr; - } - peer.numAttempts++; - } - if (address) - { - auto s = std::make_shared (*m_NTCP2Server, peer.router, address); - if( m_NTCP2Server->UsingProxy()) - m_NTCP2Server->ConnectWithProxy(s); - else - m_NTCP2Server->Connect (s); - return true; - } - } - else - peer.numAttempts = 2; // switch to SSU - } - if (peer.numAttempts == 2 || peer.numAttempts == 3) // SSU - { - if (m_SSUServer) - { - std::shared_ptr address; - if (peer.numAttempts == 2) // SSU ipv6 - { - if (context.GetRouterInfo ().IsSSUV6 () && peer.router->IsReachableBy (RouterInfo::eSSUV6)) - { - address = peer.router->GetSSUV6Address (); - if (address && m_CheckReserved && i2p::util::net::IsInReservedRange(address->host)) - address = nullptr; - } - peer.numAttempts++; - } - if (!address && peer.numAttempts == 3) // SSU ipv4 - { - if (context.GetRouterInfo ().IsSSU (true) && peer.router->IsReachableBy (RouterInfo::eSSUV4)) - { - address = peer.router->GetSSUAddress (true); - if (address && m_CheckReserved && i2p::util::net::IsInReservedRange(address->host)) - address = nullptr; - } - peer.numAttempts++; - } - if (address && address->IsReachableSSU ()) - { - if (m_SSUServer->CreateSession (peer.router, address)) - return true; - } - } - else - peer.numAttempts += 2; // switch to Mesh - } - if (peer.numAttempts == 4) // Mesh + if (peer.priority.empty ()) + SetPriority (peer); + while (peer.numAttempts < (int)peer.priority.size ()) { + auto tr = peer.priority[peer.numAttempts]; peer.numAttempts++; - if (m_NTCP2Server && context.GetRouterInfo ().IsMesh () && peer.router->IsMesh ()) + switch (tr) { - auto address = peer.router->GetYggdrasilAddress (); - if (address) + case i2p::data::RouterInfo::eNTCP2V4: + case i2p::data::RouterInfo::eNTCP2V6: { - auto s = std::make_shared (*m_NTCP2Server, peer.router, address); - m_NTCP2Server->Connect (s); - return true; - } - } - } - if (peer.numAttempts == 5 || peer.numAttempts == 6) // SSU2 - { - if (m_SSU2Server) - { - std::shared_ptr address; - if (peer.numAttempts == 5) // SSU2 ipv6 - { - if (context.GetRouterInfo ().IsSSU2V6 () && peer.router->IsReachableBy (RouterInfo::eSSU2V6)) + 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) { - address = peer.router->GetSSU2V6Address (); - if (address && m_CheckReserved && i2p::util::net::IsInReservedRange(address->host)) - address = nullptr; - } - peer.numAttempts++; - } - if (!address && peer.numAttempts == 6) // SSU2 ipv4 - { - if (context.GetRouterInfo ().IsSSU2V4 () && peer.router->IsReachableBy (RouterInfo::eSSU2V4)) - { - address = peer.router->GetSSU2V4Address (); - if (address && m_CheckReserved && i2p::util::net::IsInReservedRange(address->host)) - address = nullptr; - } - peer.numAttempts++; - } - if (address && address->IsReachableSSU ()) - { - if (m_SSU2Server->CreateSession (peer.router, address)) + auto s = std::make_shared (*m_NTCP2Server, peer.router, address); + if( m_NTCP2Server->UsingProxy()) + m_NTCP2Server->ConnectWithProxy(s); + else + m_NTCP2Server->Connect (s); return true; + } + break; } + case i2p::data::RouterInfo::eSSU2V4: + case i2p::data::RouterInfo::eSSU2V6: + { + if (!m_SSU2Server) continue; + std::shared_ptr address = (tr == i2p::data::RouterInfo::eSSU2V6) ? + peer.router->GetSSU2V6Address () : peer.router->GetSSU2V4Address (); + if (address && 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::eSSUV4: + case i2p::data::RouterInfo::eSSUV6: + { + if (!m_SSUServer) continue; + std::shared_ptr address = (tr == i2p::data::RouterInfo::eSSUV6) ? + peer.router->GetSSUV6Address () : peer.router->GetSSUAddress (true); + if (address && m_CheckReserved && i2p::util::net::IsInReservedRange(address->host)) + address = nullptr; + if (address && address->IsReachableSSU ()) + { + if (m_SSUServer->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); } - else - peer.numAttempts += 2; } - LogPrint (eLogInfo, "Transports: No compatble NTCP2 or SSU addresses available"); + + LogPrint (eLogInfo, "Transports: No compatible addresses available"); i2p::data::netdb.SetUnreachable (ident, true); // we are here because all connection attempts failed peer.Done (); std::unique_lock l(m_PeersMutex); @@ -589,6 +586,41 @@ 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, + i2p::data::RouterInfo::eSSUV6, + i2p::data::RouterInfo::eSSUV4 + }, + ssu2Priority = + { + i2p::data::RouterInfo::eSSU2V6, + i2p::data::RouterInfo::eSSU2V4, + i2p::data::RouterInfo::eNTCP2V6, + i2p::data::RouterInfo::eNTCP2V4, + i2p::data::RouterInfo::eNTCP2V6Mesh, + i2p::data::RouterInfo::eSSUV6, + i2p::data::RouterInfo::eSSUV4 + }; + if (!peer.router) return; + auto compatibleTransports = context.GetRouterInfo ().GetCompatibleTransports (false) & + peer.router->GetCompatibleTransports (true); + peer.numAttempts = 0; + peer.priority.clear (); + bool ssu2 = rand () & 1; + 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)); @@ -622,88 +654,111 @@ namespace transport i2p::context.SetStatus (eRouterStatusOK); return; } - if (m_SSUServer) + if (m_SSUServer || 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 (bool ipv4, bool ipv6) { - if (RoutesRestricted() || !m_SSUServer) return; + if (RoutesRestricted() || (!m_SSUServer && !m_SSU2Server)) return; if (ipv4 && i2p::context.SupportsV4 ()) { LogPrint (eLogInfo, "Transports: Started peer test IPv4"); std::set excluded; excluded.insert (i2p::context.GetIdentHash ()); // don't pick own router - bool statusChanged = false; - for (int i = 0; i < 5; i++) + if (m_SSUServer) { - auto router = i2p::data::netdb.GetRandomPeerTestRouter (true, excluded); // v4 - if (router) + bool statusChanged = false; + for (int i = 0; i < 5; i++) { - auto addr = router->GetSSUAddress (true); // ipv4 - if (addr && !i2p::util::net::IsInReservedRange(addr->host)) + auto router = i2p::data::netdb.GetRandomPeerTestRouter (true, excluded); // v4 + if (router) { - if (!statusChanged) + auto addr = router->GetSSUAddress (true); // ipv4 + if (addr && !i2p::util::net::IsInReservedRange(addr->host)) { - statusChanged = true; - i2p::context.SetStatus (eRouterStatusTesting); // first time only + if (!statusChanged) + { + statusChanged = true; + i2p::context.SetStatus (eRouterStatusTesting); // first time only + } + m_SSUServer->CreateSession (router, addr, true); // peer test v4 } - m_SSUServer->CreateSession (router, addr, true); // peer test v4 + excluded.insert (router->GetIdentHash ()); } - excluded.insert (router->GetIdentHash ()); } + if (!statusChanged) + LogPrint (eLogWarning, "Transports: Can't find routers for peer test IPv4"); } - if (!statusChanged) - LogPrint (eLogWarning, "Transports: Can't find routers for peer test IPv4"); - // SSU2 - if (m_SSU2Server) + if (m_SSU2Server && !m_SSU2Server->UsesProxy ()) { excluded.clear (); excluded.insert (i2p::context.GetIdentHash ()); - auto router = i2p::data::netdb.GetRandomSSU2PeerTestRouter (true, excluded); // v4 - if (router) - m_SSU2Server->StartPeerTest (router, true); - } + int numTests = m_SSUServer ? 3 : 5; + for (int i = 0; i < numTests; i++) + { + auto router = i2p::data::netdb.GetRandomSSU2PeerTestRouter (true, excluded); // v4 + if (router) + { + if (i2p::context.GetStatus () != eRouterStatusTesting) + i2p::context.SetStatusSSU2 (eRouterStatusTesting); + m_SSU2Server->StartPeerTest (router, true); + excluded.insert (router->GetIdentHash ()); + } + } + } } 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 - bool statusChanged = false; - for (int i = 0; i < 5; i++) + if (m_SSUServer) { - auto router = i2p::data::netdb.GetRandomPeerTestRouter (false, excluded); // v6 - if (router) + bool statusChanged = false; + for (int i = 0; i < 5; i++) { - auto addr = router->GetSSUV6Address (); - if (addr && !i2p::util::net::IsInReservedRange(addr->host)) + auto router = i2p::data::netdb.GetRandomPeerTestRouter (false, excluded); // v6 + if (router) { - if (!statusChanged) + auto addr = router->GetSSUV6Address (); + if (addr && !i2p::util::net::IsInReservedRange(addr->host)) { - statusChanged = true; - i2p::context.SetStatusV6 (eRouterStatusTesting); // first time only + if (!statusChanged) + { + statusChanged = true; + i2p::context.SetStatusV6 (eRouterStatusTesting); // first time only + } + m_SSUServer->CreateSession (router, addr, true); // peer test v6 } - m_SSUServer->CreateSession (router, addr, true); // peer test v6 + excluded.insert (router->GetIdentHash ()); } - excluded.insert (router->GetIdentHash ()); } + if (!statusChanged) + LogPrint (eLogWarning, "Transports: Can't find routers for peer test IPv6"); } - if (!statusChanged) - LogPrint (eLogWarning, "Transports: Can't find routers for peer test IPv6"); // SSU2 - if (m_SSU2Server) + if (m_SSU2Server && !m_SSU2Server->UsesProxy ()) { excluded.clear (); excluded.insert (i2p::context.GetIdentHash ()); - auto router = i2p::data::netdb.GetRandomSSU2PeerTestRouter (false, excluded); // v6 - if (router) - m_SSU2Server->StartPeerTest (router, false); - } + int numTests = m_SSUServer ? 3 : 5; + for (int i = 0; i < numTests; i++) + { + auto router = i2p::data::netdb.GetRandomSSU2PeerTestRouter (false, excluded); // v6 + if (router) + { + if (i2p::context.GetStatusV6 () != eRouterStatusTesting) + i2p::context.SetStatusV6SSU2 (eRouterStatusTesting); + m_SSU2Server->StartPeerTest (router, false); + excluded.insert (router->GetIdentHash ()); + } + } + } } } @@ -756,8 +811,7 @@ namespace transport session->SendI2NPMessages ({ CreateDatabaseStoreMsg () }); // send DatabaseStore auto ts = i2p::util::GetSecondsSinceEpoch (); std::unique_lock l(m_PeersMutex); - m_Peers.insert (std::make_pair (ident, Peer{ 0, nullptr, { session }, - ts, ts + PEER_ROUTER_INFO_UPDATE_INTERVAL, {} })); + m_Peers.insert (std::make_pair (ident, Peer{ nullptr, ts })); } }); } @@ -824,11 +878,11 @@ namespace transport auto session = it->second.sessions.front (); if (session) session->SendLocalRouterInfo (true); - it->second.nextRouterInfoUpdateTime = ts + PEER_ROUTER_INFO_UPDATE_INTERVAL + + 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 bool ipv4Testing = i2p::context.GetStatus () == eRouterStatusTesting; @@ -954,5 +1008,122 @@ namespace transport 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); + + i2p::context.RemoveNTCPAddress (!ipv6); // TODO: remove later + bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); + if (ntcp2) + { + bool published; i2p::config::GetOption("ntcp2.published", published); + if (published) + { + std::string ntcp2proxy; i2p::config::GetOption("ntcp2.proxy", ntcp2proxy); + if (!ntcp2proxy.empty ()) published = false; + } + if (published) + { + uint16_t ntcp2port; i2p::config::GetOption("ntcp2.port", ntcp2port); + if (!ntcp2port) ntcp2port = port; // use standard port + i2p::context.PublishNTCP2Address (ntcp2port, true, ipv4, ipv6, false); // publish + if (ipv6) + { + std::string ipv6Addr; i2p::config::GetOption("ntcp2.addressv6", ipv6Addr); + auto addr = boost::asio::ip::address_v6::from_string (ipv6Addr); + if (!addr.is_unspecified () && addr != boost::asio::ip::address_v6::any ()) + i2p::context.UpdateNTCP2V6Address (addr); // set ipv6 address if configured + } + } + else + i2p::context.PublishNTCP2Address (port, false, ipv4, ipv6, false); // unpublish + } + if (ygg) + { + i2p::context.PublishNTCP2Address (port, true, false, false, true); + i2p::context.UpdateNTCP2V6Address (yggaddr); + if (!ipv4 && !ipv6) + i2p::context.SetStatus (eRouterStatusMesh); + } + bool ssu; i2p::config::GetOption("ssu", ssu); + if (!ssu) i2p::context.RemoveSSUAddress (); // TODO: remove later + 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 = ssu ? (port + 1) : 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 8fdf6055..4ee08af2 100644 --- a/libi2pd/Transports.h +++ b/libi2pd/Transports.h @@ -62,8 +62,8 @@ namespace transport }; 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 int PEER_ROUTER_INFO_UPDATE_INTERVAL = 31*60; // in seconds + const int PEER_ROUTER_INFO_UPDATE_INTERVAL_VARIANCE = 7*60; // in seconds struct Peer { int numAttempts; @@ -71,6 +71,13 @@ namespace transport std::list > sessions; uint64_t creationTime, nextRouterInfoUpdateTime; std::vector > delayedMessages; + std::vector priority; + + Peer (std::shared_ptr r, uint64_t ts): + numAttempts (0), router (r), creationTime (ts), + nextRouterInfoUpdateTime (ts + PEER_ROUTER_INFO_UPDATE_INTERVAL) + { + } void Done () { @@ -93,6 +100,7 @@ namespace transport 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; }; @@ -146,6 +154,7 @@ 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); @@ -194,6 +203,9 @@ namespace transport }; extern Transports transports; + + void InitAddressFromIface (); + void InitTransports (); } } diff --git a/libi2pd/api.cpp b/libi2pd/api.cpp index 905fab75..a298e4b4 100644 --- a/libi2pd/api.cpp +++ b/libi2pd/api.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 * @@ -60,6 +60,7 @@ namespace api else i2p::log::Logger().SendTo (i2p::fs::DataDirPath (i2p::fs::GetAppName () + ".log")); i2p::log::Logger().Start (); + i2p::transport::InitTransports (); LogPrint(eLogInfo, "API: Starting NetDB"); i2p::data::netdb.Start(); LogPrint(eLogInfo, "API: Starting Transports"); diff --git a/libi2pd/util.cpp b/libi2pd/util.cpp index 07681dbd..b8234b39 100644 --- a/libi2pd/util.cpp +++ b/libi2pd/util.cpp @@ -12,6 +12,7 @@ #include "util.h" #include "Log.h" +#include "I2PEndian.h" #if not defined (__FreeBSD__) #include @@ -21,6 +22,9 @@ #include #endif +#if defined(__APPLE__) +# include +#endif #ifdef _WIN32 #include @@ -35,7 +39,7 @@ #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) { @@ -61,6 +65,29 @@ int inet_pton_xp (int af, const char *src, void *dst) } return 0; } + +const char *inet_ntop_xp(int af, const void *src, char *dst, socklen_t size) +{ + struct sockaddr_storage ss; + unsigned long s = size; + + ZeroMemory(&ss, sizeof(ss)); + ss.ss_family = af; + + switch(af) { + case AF_INET: + ((struct sockaddr_in *)&ss)->sin_addr = *(struct in_addr *)src; + break; + case AF_INET6: + ((struct sockaddr_in6 *)&ss)->sin6_addr = *(struct in6_addr *)src; + break; + default: + return NULL; + } + /* cannot direclty 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 @@ -119,8 +146,15 @@ namespace util } void SetThreadName (const char *name) { -#if defined(__APPLE__) && !defined(__powerpc__) +#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__) @@ -133,27 +167,12 @@ namespace util namespace net { #ifdef _WIN32 - bool IsWindowsXPorLater () - { - static bool isRequested = false; - static bool isXP = false; - if (!isRequested) - { - // request - OSVERSIONINFO osvi; - - ZeroMemory(&osvi, sizeof(OSVERSIONINFO)); - osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); - GetVersionEx(&osvi); - - isXP = osvi.dwMajorVersion <= 5; - isRequested = true; - } - return isXP; - } - int GetMTUWindowsIpv4 (sockaddr_in inputAddress, int fallback) { + typedef const char *(* IPN)(int af, const void *src, char *dst, socklen_t size); + IPN inetntop = (IPN)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; @@ -172,7 +191,7 @@ namespace net 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; } @@ -184,7 +203,7 @@ namespace net pUnicast = pCurrAddresses->FirstUnicastAddress; if(pUnicast == nullptr) - LogPrint(eLogError, "NetIface: GetMTU(): Not a unicast IPv4 address, this is not supported"); + LogPrint(eLogError, "NetIface: GetMTU: Not a unicast IPv4 address, this is not supported"); for(int i = 0; pUnicast != nullptr; ++i) { @@ -192,8 +211,13 @@ namespace net sockaddr_in* localInterfaceAddress = (sockaddr_in*) lpAddr; if(localInterfaceAddress->sin_addr.S_un.S_addr == inputAddress.sin_addr.S_un.S_addr) { - auto result = pAddresses->Mtu; + 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; @@ -201,19 +225,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) { + typedef const char *(* IPN)(int af, const void *src, char *dst, socklen_t size); + IPN inetntop = (IPN)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); @@ -224,23 +252,23 @@ namespace net AF_INET6, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen ); - if(dwRetVal != NO_ERROR) + if (dwRetVal != NO_ERROR) { - LogPrint(eLogError, "NetIface: GetMTU(): Enclosed GetAdaptersAddresses() call has failed"); + LogPrint(eLogError, "NetIface: GetMTU: Enclosed GetAdaptersAddresses() call has failed"); FREE(pAddresses); return fallback; } bool found_address = false; pCurrAddresses = pAddresses; - while(pCurrAddresses) + while (pCurrAddresses) { PIP_ADAPTER_UNICAST_ADDRESS firstUnicastAddress = pCurrAddresses->FirstUnicastAddress; pUnicast = pCurrAddresses->FirstUnicastAddress; - if(pUnicast == nullptr) - LogPrint(eLogError, "NetIface: GetMTU(): Not a unicast IPv6 address, this is not supported"); + if (pUnicast == nullptr) + LogPrint(eLogError, "NetIface: GetMTU: Not a unicast IPv6 address, this is not supported"); - for(int i = 0; pUnicast != nullptr; ++i) + for (int i = 0; pUnicast != nullptr; ++i) { LPSOCKADDR lpAddr = pUnicast->Address.lpSockaddr; sockaddr_in6 *localInterfaceAddress = (sockaddr_in6*) lpAddr; @@ -255,9 +283,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; @@ -266,7 +298,7 @@ namespace net pCurrAddresses = pCurrAddresses->Next; } - LogPrint(eLogError, "NetIface: GetMTU(): No usable unicast IPv6 addresses found"); + LogPrint(eLogError, "NetIface: GetMTU: No usable unicast IPv6 addresses found"); FREE(pAddresses); return fallback; } @@ -298,7 +330,7 @@ namespace net } else { - LogPrint(eLogError, "NetIface: GetMTU(): Address family is not supported"); + LogPrint(eLogError, "NetIface: GetMTU: Address family is not supported"); return fallback; } } @@ -423,6 +455,27 @@ namespace net #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; diff --git a/libi2pd/util.h b/libi2pd/util.h index 2c74d4dd..9420060b 100644 --- a/libi2pd/util.h +++ b/libi2pd/util.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2021, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -218,6 +218,7 @@ namespace util namespace net { int GetMTU (const boost::asio::ip::address& localAddress); + int GetMaxMTU (const boost::asio::ip::address_v6& localAddress); // check tunnel broker for ipv6 address const boost::asio::ip::address GetInterfaceAddress (const std::string & ifname, bool ipv6=false); boost::asio::ip::address_v6 GetYggdrasilAddress (); bool IsLocalAddress (const boost::asio::ip::address& addr); diff --git a/libi2pd/version.h b/libi2pd/version.h index be27358f..94d14403 100644 --- a/libi2pd/version.h +++ b/libi2pd/version.h @@ -16,8 +16,8 @@ #define MAKE_VERSION_NUMBER(a,b,c) ((a*100+b)*100+c) #define I2PD_VERSION_MAJOR 2 -#define I2PD_VERSION_MINOR 42 -#define I2PD_VERSION_MICRO 1 +#define I2PD_VERSION_MINOR 44 +#define I2PD_VERSION_MICRO 0 #define I2PD_VERSION_PATCH 0 #ifdef GITVER #define I2PD_VERSION GITVER @@ -31,7 +31,7 @@ #define I2P_VERSION_MAJOR 0 #define I2P_VERSION_MINOR 9 -#define I2P_VERSION_MICRO 54 +#define I2P_VERSION_MICRO 56 #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 e67e3f06..07f91d2e 100644 --- a/libi2pd_client/AddressBook.cpp +++ b/libi2pd_client/AddressBook.cpp @@ -313,14 +313,14 @@ namespace client { 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 () @@ -397,6 +397,19 @@ 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"); @@ -567,6 +580,7 @@ namespace client void AddressBook::LoadLocal () { + if (!m_Storage) return; std::map> localAddresses; m_Storage->LoadLocal (localAddresses); for (const auto& it: localAddresses) @@ -819,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); } - /* 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"); @@ -863,44 +859,39 @@ namespace client req.AddHeader("If-None-Match", m_Etag); if (!m_LastModified.empty()) req.AddHeader("If-Modified-Since", m_LastModified); - /* convert url to relative */ + // convert url to relative url.schema = ""; url.host = ""; req.uri = url.to_string(); req.version = "HTTP/1.1"; - auto stream = i2p::client::context.GetSharedLocalDestination ()->CreateStream (leaseSet, dest_port); std::string request = req.to_string(); stream->Send ((const uint8_t *) request.data(), request.length()); - /* read response */ + // read response std::string response; uint8_t recv_buf[4096]; bool end = false; int numAttempts = 0; while (!end) { - 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) + { + response.append ((char *)recv_buf, received); + if (!stream->IsOpen ()) end = true; + } + else if (!stream->IsOpen ()) + end = true; + else { LogPrint (eLogError, "Addressbook: Subscriptions request timeout expired"); numAttempts++; if (numAttempts > 5) end = true; - } + } } // process remaining buffer while (size_t len = stream->ReadSome (recv_buf, sizeof(recv_buf))) response.append ((char *)recv_buf, len); - /* parse response */ + // parse response i2p::http::HTTPRes res; int res_head_len = res.parse(response); if (res_head_len < 0) @@ -913,7 +904,7 @@ namespace client LogPrint(eLogError, "Addressbook: Incomplete http response from ", dest_host, ", interrupted by timeout"); return false; } - /* assert: res_head_len > 0 */ + // assert: res_head_len > 0 response.erase(0, res_head_len); if (res.code == 304) { @@ -936,7 +927,7 @@ namespace client LogPrint(eLogError, "Addressbook: Response size mismatch, expected: ", len, ", got: ", response.length(), "bytes"); return false; } - /* assert: res.code == 200 */ + // assert: res.code == 200 auto it = res.headers.find("ETag"); if (it != res.headers.end()) m_Etag = it->second; it = res.headers.find("Last-Modified"); diff --git a/libi2pd_client/AddressBook.h b/libi2pd_client/AddressBook.h index 192c4ebb..9b2c7e7e 100644 --- a/libi2pd_client/AddressBook.h +++ b/libi2pd_client/AddressBook.h @@ -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 diff --git a/libi2pd_client/BOB.cpp b/libi2pd_client/BOB.cpp index a8520e9b..5f738380 100644 --- a/libi2pd_client/BOB.cpp +++ b/libi2pd_client/BOB.cpp @@ -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 (); } @@ -547,7 +547,7 @@ namespace client } - m_Keys = i2p::data::PrivateKeys::CreateRandomKeys (signatureType, cryptoType); + m_Keys = i2p::data::PrivateKeys::CreateRandomKeys (signatureType, cryptoType, true); SendReplyOK (m_Keys.GetPublic ()->ToBase64 ().c_str ()); } diff --git a/libi2pd_client/ClientContext.cpp b/libi2pd_client/ClientContext.cpp index 3f670f06..3881f2e1 100644 --- a/libi2pd_client/ClientContext.cpp +++ b/libi2pd_client/ClientContext.cpp @@ -261,7 +261,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; } @@ -288,7 +288,7 @@ 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); + 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]; @@ -328,7 +328,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; @@ -339,7 +339,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; @@ -726,6 +726,7 @@ namespace client 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; @@ -799,11 +800,13 @@ namespace client if (!address.empty ()) serverTunnel->SetLocalAddress (address); - if(!isUniqueLocal) + if (!isUniqueLocal) { LogPrint(eLogInfo, "Clients: Disabling loopback address mapping"); serverTunnel->SetUniqueLocal(isUniqueLocal); } + if (ssl) + serverTunnel->SetSSL (true); if (accessList.length () > 0) { std::set idents; @@ -865,7 +868,7 @@ namespace client 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::config::GetOption("addressbook.enabled", httpAddresshelper); // addresshelper is not supported without address book i2p::data::SigningKeyType sigType; i2p::config::GetOption("httpproxy.signaturetype", sigType); LogPrint(eLogInfo, "Clients: Starting HTTP Proxy at ", httpProxyAddr, ":", httpProxyPort); if (httpProxyKeys.length () > 0) diff --git a/libi2pd_client/ClientContext.h b/libi2pd_client/ClientContext.h index d512a55b..4e969a6b 100644 --- a/libi2pd_client/ClientContext.h +++ b/libi2pd_client/ClientContext.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2021, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -18,6 +18,7 @@ #include "HTTPProxy.h" #include "SOCKS.h" #include "I2PTunnel.h" +#include "UDPTunnel.h" #include "SAM.h" #include "BOB.h" #include "I2CP.h" @@ -61,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 { diff --git a/libi2pd_client/HTTPProxy.cpp b/libi2pd_client/HTTPProxy.cpp index 5cb08bfb..eb6d29b5 100644 --- a/libi2pd_client/HTTPProxy.cpp +++ b/libi2pd_client/HTTPProxy.cpp @@ -36,12 +36,14 @@ namespace proxy { "reg.i2p", "stats.i2p", "identiguy.i2p", + "notbob.i2p" }; static const std::map jumpservices = { { "reg.i2p", "http://shx5vqsw7usdaunyzr2qmes2fq37oumybpudrd4jjj4e4vk4uusa.b32.i2p/jump/" }, { "identiguy.i2p", "http://3mzmrus2oron5fxptw7hw2puho3bnqmw2hqy7nw64dsrrjwdilva.b32.i2p/cgi-bin/query?hostname=" }, { "stats.i2p", "http://7tbay5p4kzeekxvyvbf6v7eauazemsnnl2aoyqhg5jzpr5eke7tq.b32.i2p/cgi-bin/jump.cgi?a=" }, + { "notbob.i2p", "http://nytzrhrjjfsutowojvxi7hphesskpqqr65wpistz6wa7cpajhp7a.b32.i2p/cgi-bin/jump.cgi?q=" } }; static const char *pageHead = @@ -80,8 +82,9 @@ namespace proxy { /* error helpers */ 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 HostNotFound(std::string& host); + void SendProxyError(std::string& content); + void SendRedirect(std::string& address); void ForwardToUpstreamProxy(); void HandleUpstreamHTTPProxyConnect(const boost::system::error_code & ec); @@ -174,7 +177,7 @@ namespace proxy { SendProxyError(content); } - void HTTPReqHandler::HostNotFound(std::string & host) { + void HTTPReqHandler::HostNotFound(std::string& host) { std::stringstream ss; ss << "

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

\r\n" << "

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

\r\n" @@ -191,7 +194,7 @@ namespace proxy { SendProxyError(content); } - void HTTPReqHandler::SendProxyError(std::string & content) + void HTTPReqHandler::SendProxyError(std::string& content) { i2p::http::HTTPRes res; res.code = 500; @@ -207,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; @@ -237,6 +251,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"); @@ -297,7 +312,14 @@ namespace proxy { 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) { i2p::client::context.GetAddressBook ().InsertAddress (m_RequestURL.host, jump); LogPrint (eLogInfo, "HTTPProxy: Added address from addresshelper for ", m_RequestURL.host); @@ -313,7 +335,8 @@ namespace proxy { std::string full_url = m_RequestURL.to_string(); std::stringstream ss; ss << tr("Host") << " " << m_RequestURL.host << " " << tr("already in router's addressbook") << ". "; - ss << tr("Click here to update record:") << " " << tr("Continue") << "."; GenericProxyInfo(tr("Addresshelper found"), ss.str()); return true; /* request processed */ @@ -422,8 +445,8 @@ namespace proxy { void HTTPReqHandler::ForwardToUpstreamProxy() { LogPrint(eLogDebug, "HTTPProxy: Forwarded to upstream"); - // build http request + /* build http request */ m_ClientRequestURL = m_RequestURL; LogPrint(eLogDebug, "HTTPProxy: ", m_ClientRequestURL.host); m_ClientRequestURL.schema = ""; @@ -431,17 +454,17 @@ namespace proxy { std::string origURI = m_ClientRequest.uri; // TODO: what do we need to change uri for? m_ClientRequest.uri = m_ClientRequestURL.to_string(); - // update User-Agent to ESR version of Firefox, same as Tor Browser below version 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()) { @@ -449,9 +472,9 @@ namespace proxy { auto auth = i2p::http::CreateBasicAuthorizationString (m_ProxyURL.user, m_ProxyURL.pass); if (!auth.empty ()) { - // remove existing authorization if any + /* remove existing authorization if any */ m_ClientRequest.RemoveHeader("Proxy-"); - // add own http proxy authorization + /* add own http proxy authorization */ m_ClientRequest.AddHeader("Proxy-Authorization", auth); } m_send_buf = m_ClientRequest.to_string(); @@ -470,7 +493,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) { @@ -479,7 +502,7 @@ namespace proxy { } else { - // unknown type, complain + /* unknown type, complain */ GenericProxyError(tr("unknown outproxy url"), m_ProxyURL.to_string()); } } diff --git a/libi2pd_client/I2PTunnel.cpp b/libi2pd_client/I2PTunnel.cpp index 4d749927..5558cd8d 100644 --- a/libi2pd_client/I2PTunnel.cpp +++ b/libi2pd_client/I2PTunnel.cpp @@ -21,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()) { @@ -46,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 () @@ -69,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; @@ -80,24 +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); - 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 ()); - + 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) @@ -131,6 +136,7 @@ namespace client void I2PTunnelConnection::Terminate () { if (Kill()) return; + if (m_SSL) m_SSL = nullptr; if (m_Stream) { m_Stream->Close (); @@ -145,12 +151,17 @@ 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) { @@ -239,8 +250,12 @@ 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) @@ -253,22 +268,45 @@ namespace client else { LogPrint (eLogDebug, "I2PTunnel: Connected"); - if (m_IsQuiet) - StreamReceive (); + 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) @@ -321,15 +359,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) @@ -353,7 +400,7 @@ namespace client // strip up some headers static const std::vector excluded // list of excluded headers { - "Keep-Alive:", "X-I2P" + "Keep-Alive:", "X-I2P" }; bool matched = false; for (const auto& it: excluded) @@ -375,8 +422,8 @@ namespace client else m_OutHeader << "Connection: close\r\n"; connection = true; - } - else // forward as is + } + else // forward as is m_OutHeader << line << "\n"; } } @@ -404,6 +451,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 (); + } } } @@ -460,9 +514,9 @@ namespace client } 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) { } @@ -473,7 +527,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 (); @@ -660,6 +715,12 @@ namespace client void I2PServerTunnel::Stop () { + if (m_PortDestination) + m_PortDestination->ResetAcceptor (); + auto localDestination = GetLocalDestination (); + if (localDestination) + localDestination->StopAcceptingStreams (); + ClearHandlers (); } @@ -733,6 +794,17 @@ namespace client 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) @@ -773,7 +845,7 @@ namespace client 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); } @@ -787,8 +859,7 @@ 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, @@ -801,371 +872,7 @@ 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_Gzip (gzip) - { - } - - I2PUDPServerTunnel::~I2PUDPServerTunnel () - { - Stop (); - } - - void I2PUDPServerTunnel::Start () - { - m_LocalDest->Start (); - - auto dgram = m_LocalDest->CreateDatagramDestination (m_Gzip); - dgram->SetReceiver (std::bind (&I2PUDPServerTunnel::HandleRecvFromI2P, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5)); - dgram->SetRawReceiver (std::bind (&I2PUDPServerTunnel::HandleRecvFromI2PRaw, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); - } - - void I2PUDPServerTunnel::Stop () - { - auto dgram = m_LocalDest->GetDatagramDestination (); - if (dgram) dgram->ResetReceiver (); - } - - std::vector > I2PUDPServerTunnel::GetSessions () - { - std::vector > sessions; - std::lock_guard lock (m_SessionsMutex); - - for (UDPSessionPtr s: m_Sessions) - { - if (!s->m_Destination) continue; - auto info = s->m_Destination->GetInfoForRemote (s->Identity); - if (!info) continue; - - auto sinfo = std::make_shared (); - sinfo->Name = m_Name; - sinfo->LocalIdent = std::make_shared (m_LocalDest->GetIdentHash ().data ()); - sinfo->RemoteIdent = std::make_shared (s->Identity.data ()); - sinfo->CurrentIBGW = info->IBGW; - sinfo->CurrentOBEP = info->OBEP; - sessions.push_back (sinfo); - } - return sessions; - } - - I2PUDPClientTunnel::I2PUDPClientTunnel (const std::string & name, const std::string &remoteDest, - boost::asio::ip::udp::endpoint localEndpoint, - std::shared_ptr localDestination, - uint16_t remotePort, bool gzip) : - m_Name (name), m_RemoteDest (remoteDest), m_LocalDest (localDestination), m_LocalEndpoint (localEndpoint), - m_RemoteIdent (nullptr), m_ResolveThread (nullptr), m_LocalSocket (nullptr), RemotePort (remotePort), - m_LastPort (0), m_cancel_resolve (false), m_Gzip (gzip) - { - } - - I2PUDPClientTunnel::~I2PUDPClientTunnel () - { - Stop (); - } - - void I2PUDPClientTunnel::Start () - { - // Reset flag in case of tunnel reload - if (m_cancel_resolve) m_cancel_resolve = false; - - m_LocalSocket.reset (new boost::asio::ip::udp::socket (m_LocalDest->GetService (), m_LocalEndpoint)); - m_LocalSocket->set_option (boost::asio::socket_base::receive_buffer_size (I2P_UDP_MAX_MTU)); - m_LocalSocket->set_option (boost::asio::socket_base::reuse_address (true)); - - auto dgram = m_LocalDest->CreateDatagramDestination (m_Gzip); - dgram->SetReceiver (std::bind (&I2PUDPClientTunnel::HandleRecvFromI2P, this, - std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3, std::placeholders::_4, - std::placeholders::_5)); - dgram->SetRawReceiver (std::bind (&I2PUDPClientTunnel::HandleRecvFromI2PRaw, this, - std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); - - m_LocalDest->Start (); - if (m_ResolveThread == nullptr) - m_ResolveThread = new std::thread (std::bind (&I2PUDPClientTunnel::TryResolving, this)); - RecvFromLocal (); - } - - void I2PUDPClientTunnel::Stop () - { - auto dgram = m_LocalDest->GetDatagramDestination (); - if (dgram) dgram->ResetReceiver (); - m_cancel_resolve = true; - - m_Sessions.clear(); - - if(m_LocalSocket && m_LocalSocket->is_open ()) - m_LocalSocket->close (); - - if(m_ResolveThread) - { - m_ResolveThread->join (); - delete m_ResolveThread; - m_ResolveThread = nullptr; - } - if (m_RemoteIdent) - { - delete m_RemoteIdent; - m_RemoteIdent = nullptr; - } - } - - void I2PUDPClientTunnel::RecvFromLocal () - { - m_LocalSocket->async_receive_from (boost::asio::buffer (m_RecvBuff, I2P_UDP_MAX_MTU), - m_RecvEndpoint, std::bind (&I2PUDPClientTunnel::HandleRecvFromLocal, this, std::placeholders::_1, std::placeholders::_2)); - } - - void I2PUDPClientTunnel::HandleRecvFromLocal (const boost::system::error_code & ec, std::size_t transferred) - { - if (m_cancel_resolve) { - LogPrint (eLogDebug, "UDP Client: Ignoring incomming data: stopping"); - return; - } - if (ec) { - LogPrint (eLogError, "UDP Client: Reading from socket error: ", ec.message (), ". Restarting listener..."); - RecvFromLocal (); // Restart listener and continue work - return; - } - if (!m_RemoteIdent) { - LogPrint (eLogWarning, "UDP Client: Remote endpoint not resolved yet"); - RecvFromLocal (); - return; // drop, remote not resolved - } - auto remotePort = m_RecvEndpoint.port (); - if (!m_LastPort || m_LastPort != remotePort) - { - auto itr = m_Sessions.find (remotePort); - if (itr != m_Sessions.end ()) - m_LastSession = itr->second; - else - { - m_LastSession = std::make_shared (boost::asio::ip::udp::endpoint (m_RecvEndpoint), 0); - m_Sessions.emplace (remotePort, m_LastSession); - } - m_LastPort = remotePort; - } - // send off to remote i2p destination - auto ts = i2p::util::GetMillisecondsSinceEpoch (); - LogPrint (eLogDebug, "UDP Client: Send ", transferred, " to ", m_RemoteIdent->ToBase32 (), ":", RemotePort); - auto session = m_LocalDest->GetDatagramDestination ()->GetSession (*m_RemoteIdent); - if (ts > m_LastSession->second + I2P_UDP_REPLIABLE_DATAGRAM_INTERVAL) - m_LocalDest->GetDatagramDestination ()->SendDatagram (session, m_RecvBuff, transferred, remotePort, RemotePort); - else - m_LocalDest->GetDatagramDestination ()->SendRawDatagram (session, m_RecvBuff, transferred, remotePort, RemotePort); - size_t numPackets = 0; - while (numPackets < i2p::datagram::DATAGRAM_SEND_QUEUE_MAX_SIZE) - { - boost::system::error_code ec; - size_t moreBytes = m_LocalSocket->available (ec); - if (ec || !moreBytes) break; - transferred = m_LocalSocket->receive_from (boost::asio::buffer (m_RecvBuff, I2P_UDP_MAX_MTU), m_RecvEndpoint, 0, ec); - remotePort = m_RecvEndpoint.port (); - // TODO: check remotePort - m_LocalDest->GetDatagramDestination ()->SendRawDatagram (session, m_RecvBuff, transferred, remotePort, RemotePort); - numPackets++; - } - if (numPackets) - LogPrint (eLogDebug, "UDP Client: Sent ", numPackets, " more packets to ", m_RemoteIdent->ToBase32 ()); - m_LocalDest->GetDatagramDestination ()->FlushSendQueue (session); - - // mark convo as active - if (m_LastSession) - m_LastSession->second = ts; - RecvFromLocal (); - } - - std::vector > I2PUDPClientTunnel::GetSessions () - { - // TODO: implement - std::vector > infos; - return infos; - } - - void I2PUDPClientTunnel::TryResolving () - { - i2p::util::SetThreadName ("UDP Resolver"); - LogPrint (eLogInfo, "UDP Tunnel: Trying to resolve ", m_RemoteDest); - - std::shared_ptr addr; - while (!(addr = context.GetAddressBook().GetAddress(m_RemoteDest)) && !m_cancel_resolve) - { - LogPrint (eLogWarning, "UDP Tunnel: Failed to lookup ", m_RemoteDest); - std::this_thread::sleep_for (std::chrono::seconds (1)); - } - if (m_cancel_resolve) - { - LogPrint(eLogError, "UDP Tunnel: Lookup of ", m_RemoteDest, " was cancelled"); - return; - } - if (!addr || !addr->IsIdentHash ()) - { - LogPrint (eLogError, "UDP Tunnel: ", m_RemoteDest, " not found"); - return; - } - m_RemoteIdent = new i2p::data::IdentHash; - *m_RemoteIdent = addr->identHash; - LogPrint(eLogInfo, "UDP Tunnel: Resolved ", m_RemoteDest, " to ", m_RemoteIdent->ToBase32 ()); - } - - void I2PUDPClientTunnel::HandleRecvFromI2P (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) - { - if (m_RemoteIdent && from.GetIdentHash() == *m_RemoteIdent) - HandleRecvFromI2PRaw (fromPort, toPort, buf, len); - else - LogPrint(eLogWarning, "UDP Client: Unwarranted traffic from ", from.GetIdentHash().ToBase32 ()); - } - - void I2PUDPClientTunnel::HandleRecvFromI2PRaw (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) - { - auto itr = m_Sessions.find (toPort); - // found convo ? - if (itr != m_Sessions.end ()) - { - // found convo - if (len > 0) - { - LogPrint (eLogDebug, "UDP Client: Got ", len, "B from ", m_RemoteIdent ? m_RemoteIdent->ToBase32 () : ""); - m_LocalSocket->send_to (boost::asio::buffer (buf, len), itr->second->first); - // mark convo as active - itr->second->second = i2p::util::GetMillisecondsSinceEpoch (); - } - } - else - LogPrint (eLogWarning, "UDP Client: Not tracking udp session using port ", (int) toPort); + return std::make_shared (this, stream, GetEndpoint (), m_WebircPass, GetSSLCtx ()); } } } diff --git a/libi2pd_client/I2PTunnel.h b/libi2pd_client/I2PTunnel.h index dfa23fb2..4c7b2002 100644 --- a/libi2pd_client/I2PTunnel.h +++ b/libi2pd_client/I2PTunnel.h @@ -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" @@ -34,6 +34,7 @@ namespace client 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 { @@ -43,8 +44,9 @@ namespace client std::shared_ptr leaseSet, int port = 0); // to I2P I2PTunnelConnection (I2PService * owner, std::shared_ptr socket, std::shared_ptr stream); // to I2P using simplified API - I2PTunnelConnection (I2PService * owner, std::shared_ptr stream, 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); @@ -55,21 +57,27 @@ namespace client void Terminate (); void Receive (); - void HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred); + void StreamReceive (); 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); + std::shared_ptr GetSocket () const { return m_Socket; }; + std::shared_ptr > GetSSL () const { return m_SSL; }; - std::shared_ptr GetSocket () const { return m_Socket; }; + 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 @@ -99,8 +107,8 @@ 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: @@ -120,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: @@ -171,162 +179,6 @@ namespace client std::unique_ptr m_KeepAliveTimer; }; - - /** 2 minute timeout for udp sessions */ - const uint64_t I2P_UDP_SESSION_TIMEOUT = 1000 * 60 * 2; - const uint64_t I2P_UDP_REPLIABLE_DATAGRAM_INTERVAL = 100; // in milliseconds - - /** max size for i2p udp */ - const size_t I2P_UDP_MAX_MTU = 64*1024; - - struct UDPSession - { - i2p::datagram::DatagramDestination * m_Destination; - boost::asio::ip::udp::socket IPSocket; - i2p::data::IdentHash Identity; - boost::asio::ip::udp::endpoint FromEndpoint; - boost::asio::ip::udp::endpoint SendEndpoint; - uint64_t LastActivity; - - uint16_t LocalPort; - uint16_t RemotePort; - - uint8_t m_Buffer[I2P_UDP_MAX_MTU]; - - UDPSession(boost::asio::ip::udp::endpoint localEndpoint, - const std::shared_ptr & localDestination, - boost::asio::ip::udp::endpoint remote, const i2p::data::IdentHash * ident, - uint16_t ourPort, uint16_t theirPort); - void HandleReceived(const boost::system::error_code & ecode, std::size_t len); - void Receive(); - }; - - - /** read only info about a datagram session */ - struct DatagramSessionInfo - { - /** the name of this forward */ - std::string Name; - /** ident hash of local destination */ - std::shared_ptr LocalIdent; - /** ident hash of remote destination */ - std::shared_ptr RemoteIdent; - /** ident hash of IBGW in use currently in this session or nullptr if none is set */ - std::shared_ptr CurrentIBGW; - /** ident hash of OBEP in use for this session or nullptr if none is set */ - std::shared_ptr CurrentOBEP; - /** i2p router's udp endpoint */ - boost::asio::ip::udp::endpoint LocalEndpoint; - /** client's udp endpoint */ - boost::asio::ip::udp::endpoint RemoteEndpoint; - /** how long has this converstation been idle in ms */ - uint64_t idle; - }; - - typedef std::shared_ptr UDPSessionPtr; - - /** server side udp tunnel, many i2p inbound to 1 ip outbound */ - class I2PUDPServerTunnel - { - public: - - I2PUDPServerTunnel (const std::string & name, - std::shared_ptr localDestination, - boost::asio::ip::address localAddress, - boost::asio::ip::udp::endpoint forwardTo, uint16_t port, bool gzip); - ~I2PUDPServerTunnel (); - - /** expire stale udp conversations */ - void ExpireStale (const uint64_t delta=I2P_UDP_SESSION_TIMEOUT); - void Start (); - void Stop (); - const char * GetName () const { return m_Name.c_str(); } - std::vector > GetSessions (); - std::shared_ptr GetLocalDestination () const { return m_LocalDest; } - - void SetUniqueLocal (bool isUniqueLocal = true) { m_IsUniqueLocal = isUniqueLocal; } - - private: - - void HandleRecvFromI2P (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); - void HandleRecvFromI2PRaw (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); - UDPSessionPtr ObtainUDPSession (const i2p::data::IdentityEx& from, uint16_t localPort, uint16_t remotePort); - - private: - - bool m_IsUniqueLocal; - const std::string m_Name; - boost::asio::ip::address m_LocalAddress; - boost::asio::ip::udp::endpoint m_RemoteEndpoint; - std::mutex m_SessionsMutex; - std::vector m_Sessions; - std::shared_ptr m_LocalDest; - UDPSessionPtr m_LastSession; - bool m_Gzip; - - public: - - bool isUpdated; // transient, used during reload only - }; - - class I2PUDPClientTunnel - { - public: - - I2PUDPClientTunnel (const std::string & name, const std::string &remoteDest, - boost::asio::ip::udp::endpoint localEndpoint, std::shared_ptr localDestination, - uint16_t remotePort, bool gzip); - ~I2PUDPClientTunnel (); - - void Start (); - void Stop (); - const char * GetName () const { return m_Name.c_str(); } - std::vector > GetSessions (); - - bool IsLocalDestination (const i2p::data::IdentHash & destination) const { return destination == m_LocalDest->GetIdentHash(); } - - std::shared_ptr GetLocalDestination () const { return m_LocalDest; } - inline void SetLocalDestination (std::shared_ptr dest) - { - if (m_LocalDest) m_LocalDest->Release (); - if (dest) dest->Acquire (); - m_LocalDest = dest; - } - - void ExpireStale (const uint64_t delta=I2P_UDP_SESSION_TIMEOUT); - - private: - - typedef std::pair UDPConvo; - void RecvFromLocal (); - void HandleRecvFromLocal (const boost::system::error_code & e, std::size_t transferred); - void HandleRecvFromI2P (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); - void HandleRecvFromI2PRaw (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); - void TryResolving (); - - private: - - const std::string m_Name; - std::mutex m_SessionsMutex; - std::unordered_map > m_Sessions; // maps i2p port -> local udp convo - const std::string m_RemoteDest; - std::shared_ptr m_LocalDest; - const boost::asio::ip::udp::endpoint m_LocalEndpoint; - i2p::data::IdentHash * m_RemoteIdent; - std::thread * m_ResolveThread; - std::unique_ptr m_LocalSocket; - boost::asio::ip::udp::endpoint m_RecvEndpoint; - uint8_t m_RecvBuff[I2P_UDP_MAX_MTU]; - uint16_t RemotePort, m_LastPort; - bool m_cancel_resolve; - bool m_Gzip; - std::shared_ptr m_LastSession; - - public: - - bool isUpdated; // transient, used during reload only - }; - class I2PServerTunnel: public I2PService { public: @@ -342,6 +194,9 @@ 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; } @@ -370,6 +225,7 @@ namespace client std::set m_AccessList; bool m_IsAccessList; std::unique_ptr m_LocalAddress; + std::shared_ptr m_SSLCtx; }; class I2PServerTunnelHTTP: public I2PServerTunnel @@ -405,6 +261,8 @@ namespace client std::string m_WebircPass; }; + + boost::asio::ip::address GetLoopbackAddressFor(const i2p::data::IdentHash & addr); } } diff --git a/libi2pd_client/SAM.cpp b/libi2pd_client/SAM.cpp index fc56b2c9..8b991802 100644 --- a/libi2pd_client/SAM.cpp +++ b/libi2pd_client/SAM.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2021, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -709,7 +709,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 ()); @@ -1478,14 +1478,21 @@ namespace client auto session = FindSession (sessionID); if (session) { - i2p::data::IdentityEx dest; - dest.FromBase64 (destination); - if (session->Type == eSAMSessionTypeDatagram) - session->GetLocalDestination ()->GetDatagramDestination ()-> - SendDatagramTo ((uint8_t *)eol, payloadLen, dest.GetIdentHash ()); - else // raw - session->GetLocalDestination ()->GetDatagramDestination ()-> - SendRawDatagramTo ((uint8_t *)eol, payloadLen, dest.GetIdentHash ()); + 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"); diff --git a/libi2pd_client/UDPTunnel.cpp b/libi2pd_client/UDPTunnel.cpp new file mode 100644 index 00000000..4f1b31f2 --- /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 incomming data: stopping"); + return; + } + if (ec) { + LogPrint (eLogError, "UDP Client: Reading from socket error: ", ec.message (), ". Restarting listener..."); + RecvFromLocal (); // Restart listener and continue work + return; + } + if (!m_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..749ec4d3 --- /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
" + << tr("Streams") + << "
StreamID" // Stream closing button column + << "DestinationSentReceivedOutInBufRTTWindowStatus