diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d8828f61..22ba60bf 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -38,3 +38,51 @@ 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" -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" -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 new file mode 100644 index 00000000..1c01d373 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,63 @@ +name: Build containers + +on: [push] + +jobs: + docker: + runs-on: ubuntu-latest + permissions: + packages: write + contents: read + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v1 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Login to DockerHub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Login to GitHub Container registry + uses: docker/login-action@v1 + 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 + with: + context: ./contrib/docker + file: ./contrib/docker/Dockerfile + platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v7 + push: true + tags: | + purplei2p/i2pd:latest + ghcr.io/purplei2p/i2pd:latest + + - name: Set env + if: ${{ startsWith(github.ref, 'refs/tags/') }} + run: echo "RELEASE_VERSION=${GITHUB_REF:10}" >> $GITHUB_ENV + + - name: Build and push release container + if: ${{ startsWith(github.ref, 'refs/tags/') }} + uses: docker/build-push-action@v2 + with: + context: ./contrib/docker + file: ./contrib/docker/Dockerfile + platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v7 + push: true + tags: | + purplei2p/i2pd:latest + purplei2p/i2pd:release-${{ env.RELEASE_VERSION }} + ghcr.io/purplei2p/i2pd:latest + ghcr.io/purplei2p/i2pd:release-${{ env.RELEASE_VERSION }} diff --git a/.gitignore b/.gitignore index 2506fd93..75bd6abb 100644 --- a/.gitignore +++ b/.gitignore @@ -7,8 +7,13 @@ netDb /i2pd /libi2pd.a /libi2pdclient.a +/libi2pdlang.a /libi2pd.so /libi2pdclient.so +/libi2pdlang.so +/libi2pd.dll +/libi2pdclient.dll +/libi2pdlang.dll *.exe @@ -255,6 +260,7 @@ docs/generated build/Makefile # debian stuff +debian/i2pd.1.gz .pc/ # qt diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index af47f458..00000000 --- a/.travis.yml +++ /dev/null @@ -1,54 +0,0 @@ -language: cpp -cache: - apt: true -os: -- linux -#- osx -dist: xenial -sudo: required -compiler: -- g++ -- clang++ -env: - global: - - MAKEFLAGS="-j 2" - matrix: - - BUILD_TYPE=make UPNP=ON MAKE_UPNP=yes - - BUILD_TYPE=make UPNP=OFF MAKE_UPNP=no - - BUILD_TYPE=cmake UPNP=ON MAKE_UPNP=yes - - BUILD_TYPE=cmake UPNP=OFF MAKE_UPNP=no -matrix: - exclude: - - os: osx - env: BUILD_TYPE=cmake UPNP=ON MAKE_UPNP=yes - - os: osx - env: BUILD_TYPE=cmake UPNP=OFF MAKE_UPNP=no - - os: linux - compiler: clang++ - env: BUILD_TYPE=make UPNP=ON MAKE_UPNP=yes - - os: linux - compiler: clang++ - env: BUILD_TYPE=make UPNP=OFF MAKE_UPNP=no -addons: - apt: - packages: - - build-essential - - cmake - - g++ - - clang - - libboost-chrono-dev - - libboost-date-time-dev - - libboost-filesystem-dev - - libboost-program-options-dev - - libboost-system-dev - - libboost-thread-dev - - libminiupnpc-dev - - libssl-dev -before_install: -- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update ; fi -- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install libressl miniupnpc ; fi -- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew outdated boost || brew upgrade boost ; fi -script: -- if [[ "$TRAVIS_OS_NAME" == "linux" && "$BUILD_TYPE" == "cmake" ]]; then cd build && cmake -DCMAKE_BUILD_TYPE=Release -DWITH_UPNP=${UPNP} && make ; fi -- if [[ "$TRAVIS_OS_NAME" == "linux" && "$BUILD_TYPE" == "make" ]]; then make USE_UPNP=${MAKE_UPNP} ; fi -- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then make HOMEBREW=1 USE_UPNP=${MAKE_UPNP} ; fi diff --git a/ChangeLog b/ChangeLog index 9a79440e..2128f5d2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,7 +1,49 @@ # for this file format description, # see https://github.com/olivierlacan/keep-a-changelog -## [2.38.0] - 2021-03-17 +## [2.39.0] - 2021-08-23 +### Added +- Short tunnel build messages +- Localization. To: Russian, Ukrainian, Turkmen, Uzbek and Afrikaans +- Custom CSS styles for webconsole +- Avoid slow tunnels with more than 250 ms per hop +- Process DELAY_REQUESTED streaming option +- "certsdir" options for certificates location +- Keep own RouterInfo in NetBb +- Pick ECIES routers only for tunnels on non-x64 +- NTP sync through ipv6 +- Allow ipv6 addresses for UDP server tunnels +### Changed +- Rekey of all routers to ECIES +- Better distribution for random tunnel's peer selection +- Yggdrasil reseed for v0.4, added two more +- Encryption type 0,4 by default for server tunnels +- Handle i2cp.dontPublishLeaseSet param for all destinations +- reg.i2p for subscriptions +- LeaseSet type 3 by default +- Don't allocate payload buffer for every single ECIESx25519 message +- Prefer public ipv6 instead rfc4941 +- Optimal padding for one-time ECIESx25519 message +- Don't send datetime block for one-time ECIESx25519 message with one-time key +- Router with expired introducer is still valid +- Don't disable floodfill if still reachable by ipv6 +- Set minimal version for floodfill to 0.9.38 +- Eliminate extra lookups for sequential fragments on tunnel endpoint +- Consistent path for explicit peers +- Always create new tunnel from exploratory pool +- Don't try to connect to a router not reachable from us +- Mark additional ipv6 addresses/nets as reserved (#1679) +### Fixed +- Zero-hop tunnels +- Crash upon SAM session termination +- Build with boost < 1.55.0 +- Address type for NTCP2 acceptors +- Check of ipv4/ipv6 address +- Request router to send to if not in NetDb +- Count outbound traffic for zero-hop tunnels +- URLdecode domain for registration string generator in webconsole + +## [2.38.0] - 2021-05-17 ### Added - Publish ipv6 introducers - Bind ipv6 or yggdrasil NTCP2 acceptor to specified address diff --git a/Makefile b/Makefile index 40e72918..f1258874 100644 --- a/Makefile +++ b/Makefile @@ -1,23 +1,45 @@ +.DEFAULT_GOAL := all + SYS := $(shell $(CXX) -dumpmachine) -SHLIB := libi2pd.so + +ifneq (, $(findstring darwin, $(SYS))) + SHARED_SUFFIX = dylib +else ifneq (, $(findstring mingw, $(SYS))$(findstring cygwin, $(SYS))) + SHARED_SUFFIX = dll +else + SHARED_SUFFIX = so +endif + +SHLIB := libi2pd.$(SHARED_SUFFIX) ARLIB := libi2pd.a -SHLIB_CLIENT := libi2pdclient.so +SHLIB_LANG := libi2pdlang.$(SHARED_SUFFIX) +ARLIB_LANG := libi2pdlang.a +SHLIB_CLIENT := libi2pdclient.$(SHARED_SUFFIX) ARLIB_CLIENT := libi2pdclient.a +SHLIB_WRAP := libi2pdwrapper.$(SHARED_SUFFIX) +ARLIB_WRAP := libi2pdwrapper.a I2PD := i2pd LIB_SRC_DIR := libi2pd LIB_CLIENT_SRC_DIR := libi2pd_client +WRAP_SRC_DIR := libi2pd_wrapper LANG_SRC_DIR := i18n DAEMON_SRC_DIR := daemon # import source files lists include filelist.mk -USE_AESNI := yes -USE_STATIC := no -USE_MESHNET := no -USE_UPNP := no -DEBUG := yes +USE_AESNI := $(or $(USE_AESNI),yes) +USE_STATIC := $(or $(USE_STATIC),no) +USE_MESHNET := $(or $(USE_MESHNET),no) +USE_UPNP := $(or $(USE_UPNP),no) +DEBUG := $(or $(DEBUG),yes) + +# for debugging purposes only, when commit hash needed in trunk builds in i2pd version string +USE_GIT_VERSION := $(or $(USE_GIT_VERSION),no) + +# for MacOS only, waiting for "1", not "yes" +HOMEBREW := $(or $(HOMEBREW),0) ifeq ($(DEBUG),yes) CXX_DEBUG = -g @@ -50,27 +72,36 @@ ifeq ($(USE_MESHNET),yes) NEEDED_CXXFLAGS += -DMESHNET endif +ifeq ($(USE_GIT_VERSION),yes) + GIT_VERSION := $(shell git describe --tags) + NEEDED_CXXFLAGS += -DGITVER=\"$(GIT_VERSION)\" +endif + NEEDED_CXXFLAGS += -MMD -MP -I$(LIB_SRC_DIR) -I$(LIB_CLIENT_SRC_DIR) -I$(LANG_SRC_DIR) LIB_OBJS += $(patsubst %.cpp,obj/%.o,$(LIB_SRC)) LIB_CLIENT_OBJS += $(patsubst %.cpp,obj/%.o,$(LIB_CLIENT_SRC)) LANG_OBJS += $(patsubst %.cpp,obj/%.o,$(LANG_SRC)) DAEMON_OBJS += $(patsubst %.cpp,obj/%.o,$(DAEMON_SRC)) -DEPS += $(LIB_OBJS:.o=.d) $(LIB_CLIENT_OBJS:.o=.d) $(LANG_OBJS:.o=.d) $(DAEMON_OBJS:.o=.d) +WRAP_LIB_OBJS += $(patsubst %.cpp,obj/%.o,$(WRAP_LIB_SRC)) +DEPS += $(LIB_OBJS:.o=.d) $(LIB_CLIENT_OBJS:.o=.d) $(LANG_OBJS:.o=.d) $(DAEMON_OBJS:.o=.d) $(WRAP_LIB_OBJS:.o=.d) -all: mk_obj_dir $(ARLIB) $(ARLIB_CLIENT) $(I2PD) +## Build all code (libi2pd, libi2pdclient, libi2pdlang), link it to .a and build binary +all: $(ARLIB) $(ARLIB_CLIENT) $(ARLIB_LANG) $(I2PD) mk_obj_dir: - @mkdir -p obj - @mkdir -p obj/Win32 @mkdir -p obj/$(LIB_SRC_DIR) @mkdir -p obj/$(LIB_CLIENT_SRC_DIR) @mkdir -p obj/$(LANG_SRC_DIR) @mkdir -p obj/$(DAEMON_SRC_DIR) + @mkdir -p obj/$(WRAP_SRC_DIR) + @mkdir -p obj/Win32 -api: mk_obj_dir $(SHLIB) $(ARLIB) -client: mk_obj_dir $(SHLIB_CLIENT) $(ARLIB_CLIENT) -api_client: mk_obj_dir $(SHLIB) $(ARLIB) $(SHLIB_CLIENT) $(ARLIB_CLIENT) +api: $(SHLIB) $(ARLIB) +client: $(SHLIB_CLIENT) $(ARLIB_CLIENT) +lang: $(SHLIB_LANG) $(ARLIB_LANG) +api_client: api client lang +wrapper: api_client $(SHLIB_WRAP) $(ARLIB_WRAP) ## NOTE: The NEEDED_CXXFLAGS are here so that CXXFLAGS can be specified at build time ## **without** overwriting the CXXFLAGS which we need in order to build. @@ -79,23 +110,33 @@ api_client: mk_obj_dir $(SHLIB) $(ARLIB) $(SHLIB_CLIENT) $(ARLIB_CLIENT) ## -std=c++11. If you want to remove this variable please do so in a way that allows setting ## custom FLAGS to work at build-time. -obj/%.o: %.cpp +obj/%.o: %.cpp | mk_obj_dir $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -c -o $@ $< # '-' is 'ignore if missing' on first run -include $(DEPS) -$(I2PD): $(LANG_OBJS) $(DAEMON_OBJS) $(ARLIB) $(ARLIB_CLIENT) +$(I2PD): $(DAEMON_OBJS) $(ARLIB) $(ARLIB_CLIENT) $(ARLIB_LANG) $(CXX) -o $@ $(LDFLAGS) $^ $(LDLIBS) -$(SHLIB): $(LIB_OBJS) +$(SHLIB): $(LIB_OBJS) $(SHLIB_LANG) +ifneq ($(USE_STATIC),yes) + $(CXX) $(LDFLAGS) -shared -o $@ $^ $(LDLIBS) $(SHLIB_LANG) +endif + +$(SHLIB_CLIENT): $(LIB_CLIENT_OBJS) $(SHLIB) $(SHLIB_LANG) +ifneq ($(USE_STATIC),yes) + $(CXX) $(LDFLAGS) -shared -o $@ $^ $(LDLIBS) $(SHLIB) $(SHLIB_LANG) +endif + +$(SHLIB_WRAP): $(WRAP_LIB_OBJS) ifneq ($(USE_STATIC),yes) $(CXX) $(LDFLAGS) -shared -o $@ $^ $(LDLIBS) endif -$(SHLIB_CLIENT): $(LIB_CLIENT_OBJS) +$(SHLIB_LANG): $(LANG_OBJS) ifneq ($(USE_STATIC),yes) - $(CXX) $(LDFLAGS) -shared -o $@ $^ $(LDLIBS) $(SHLIB) + $(CXX) $(LDFLAGS) -shared -o $@ $^ $(LDLIBS) endif $(ARLIB): $(LIB_OBJS) @@ -104,12 +145,18 @@ $(ARLIB): $(LIB_OBJS) $(ARLIB_CLIENT): $(LIB_CLIENT_OBJS) $(AR) -r $@ $^ +$(ARLIB_WRAP): $(WRAP_LIB_OBJS) + $(AR) -r $@ $^ + +$(ARLIB_LANG): $(LANG_OBJS) + $(AR) -r $@ $^ + clean: $(RM) -r obj $(RM) -r docs/generated - $(RM) $(I2PD) $(SHLIB) $(ARLIB) $(SHLIB_CLIENT) $(ARLIB_CLIENT) + $(RM) $(I2PD) $(SHLIB) $(ARLIB) $(SHLIB_CLIENT) $(ARLIB_CLIENT) $(SHLIB_LANG) $(ARLIB_LANG) $(SHLIB_WRAP) $(ARLIB_WRAP) -strip: $(I2PD) $(SHLIB_CLIENT) $(SHLIB) +strip: $(I2PD) $(SHLIB) $(SHLIB_CLIENT) $(SHLIB_LANG) strip $^ LATEST_TAG=$(shell git describe --tags --abbrev=0 openssl) @@ -133,6 +180,7 @@ doxygen: .PHONY: api .PHONY: api_client .PHONY: client +.PHONY: lang .PHONY: mk_obj_dir .PHONY: install .PHONY: strip diff --git a/Makefile.homebrew b/Makefile.homebrew index c1992296..8d6dd7a3 100644 --- a/Makefile.homebrew +++ b/Makefile.homebrew @@ -44,7 +44,7 @@ install: all install -m 644 contrib/i2pd.conf contrib/subscriptions.txt contrib/tunnels.conf ${PREFIX}/etc/i2pd @cp -R contrib/certificates ${PREFIX}/share/i2pd/ install -m 644 ChangeLog LICENSE README.md contrib/i2pd.conf contrib/subscriptions.txt contrib/tunnels.conf ${PREFIX}/share/doc/i2pd - @gzip debian/i2pd.1 && install debian/i2pd.1.gz ${PREFIX}/share/man/man1 + @gzip -kf debian/i2pd.1 && install debian/i2pd.1.gz ${PREFIX}/share/man/man1 @ln -sf ${PREFIX}/share/i2pd/certificates ${PREFIX}/var/lib/i2pd/ @ln -sf ${PREFIX}/etc/i2pd/i2pd.conf ${PREFIX}/var/lib/i2pd/i2pd.conf @ln -sf ${PREFIX}/etc/i2pd/subscriptions.txt ${PREFIX}/var/lib/i2pd/subscriptions.txt diff --git a/Makefile.mingw b/Makefile.mingw index ce1966a1..e31d895e 100644 --- a/Makefile.mingw +++ b/Makefile.mingw @@ -3,9 +3,9 @@ USE_WIN32_APP := yes WINDRES = windres -CXXFLAGS := $(CXX_DEBUG) -D_MT -DWIN32_LEAN_AND_MEAN -fPIC -msse +CXXFLAGS := $(CXX_DEBUG) -DWIN32_LEAN_AND_MEAN -fPIC -msse INCFLAGS = -I$(DAEMON_SRC_DIR) -IWin32 -LDFLAGS := ${LD_DEBUG} -Wl,-Bstatic -static-libgcc +LDFLAGS := ${LD_DEBUG} -static # detect proper flag for c++11 support by compilers CXXVER := $(shell $(CXX) -dumpversion) @@ -61,5 +61,5 @@ ifeq ($(USE_ASLR),yes) LDFLAGS += -Wl,--nxcompat -Wl,--high-entropy-va -Wl,--dynamicbase,--export-all-symbols endif -obj/%.o : %.rc +obj/%.o : %.rc | mk_obj_dir $(WINDRES) -i $< -o $@ diff --git a/README.md b/README.md index 1aa96fb0..6f2d23ec 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ [![License](https://img.shields.io/github/license/PurpleI2P/i2pd.svg)](https://github.com/PurpleI2P/i2pd/blob/openssl/LICENSE) [![Packaging status](https://repology.org/badge/tiny-repos/i2pd.svg)](https://repology.org/project/i2pd/versions) [![Docker Pulls](https://img.shields.io/docker/pulls/purplei2p/i2pd)](https://hub.docker.com/r/purplei2p/i2pd) +[![Crowdin](https://badges.crowdin.net/i2pd/localized.svg)](https://crowdin.com/project/i2pd) *note: i2pd for Android can be found in [i2pd-android](https://github.com/PurpleI2P/i2pd-android) repository and with Qt GUI in [i2pd-qt](https://github.com/PurpleI2P/i2pd-qt) repository* @@ -68,15 +69,15 @@ Build instructions: **Supported systems:** -* GNU/Linux - [![Build Status](https://travis-ci.org/PurpleI2P/i2pd.svg?branch=openssl)](https://travis-ci.org/PurpleI2P/i2pd) +* GNU/Linux - [![Build on Ubuntu](https://github.com/PurpleI2P/i2pd/actions/workflows/build.yml/badge.svg)](https://github.com/PurpleI2P/i2pd/actions/workflows/build.yml) * CentOS / Fedora / Mageia - [![Build Status](https://copr.fedorainfracloud.org/coprs/supervillain/i2pd/package/i2pd-git/status_image/last_build.png)](https://copr.fedorainfracloud.org/coprs/supervillain/i2pd/package/i2pd-git/) * Alpine, ArchLinux, openSUSE, Gentoo, Debian, Ubuntu, etc. -* Windows - [![Build status](https://ci.appveyor.com/api/projects/status/1908qe4p48ff1x23?svg=true)](https://ci.appveyor.com/project/PurpleI2P/i2pd) -* Mac OS X - [![Build Status](https://travis-ci.org/PurpleI2P/i2pd.svg?branch=openssl)](https://travis-ci.org/PurpleI2P/i2pd) -* Docker image - [![Build Status](https://img.shields.io/docker/cloud/build/purplei2p/i2pd)](https://hub.docker.com/r/purplei2p/i2pd/builds/) -* Snap -* FreeBSD -* Android +* Windows - [![Build on Windows](https://github.com/PurpleI2P/i2pd/actions/workflows/build-windows.yml/badge.svg)](https://github.com/PurpleI2P/i2pd/actions/workflows/build-windows.yml) +* Mac OS X - [![Build on OSX](https://github.com/PurpleI2P/i2pd/actions/workflows/build-osx.yml/badge.svg)](https://github.com/PurpleI2P/i2pd/actions/workflows/build-osx.yml) +* Docker image - [![Build Status](https://img.shields.io/docker/cloud/build/purplei2p/i2pd)](https://hub.docker.com/r/purplei2p/i2pd/builds/) [![Build containers](https://github.com/PurpleI2P/i2pd/actions/workflows/docker.yml/badge.svg)](https://github.com/PurpleI2P/i2pd/actions/workflows/docker.yml) +* Snap - [![i2pd](https://snapcraft.io/i2pd/badge.svg)](https://snapcraft.io/i2pd) [![i2pd](https://snapcraft.io/i2pd/trending.svg?name=0)](https://snapcraft.io/i2pd) +* FreeBSD - [![Build on FreeBSD](https://github.com/PurpleI2P/i2pd/actions/workflows/build-freebsd.yml/badge.svg)](https://github.com/PurpleI2P/i2pd/actions/workflows/build-freebsd.yml) +* Android - [![Android CI](https://github.com/PurpleI2P/i2pd-android/actions/workflows/android.yml/badge.svg)](https://github.com/PurpleI2P/i2pd-android/actions/workflows/android.yml) * iOS Using i2pd @@ -85,6 +86,16 @@ Using i2pd See [documentation](https://i2pd.readthedocs.io/en/latest/user-guide/run/) and [example config file](https://github.com/PurpleI2P/i2pd/blob/openssl/contrib/i2pd.conf). +Localization +------------ + +You can help us with translation i2pd to your language using Crowdin platform! +Translation project can be found [here](https://crowdin.com/project/i2pd). + +New languages can be requested on project's [discussion page](https://crowdin.com/project/i2pd/discussions). + +Current status: [![Crowdin](https://badges.crowdin.net/i2pd/localized.svg)](https://crowdin.com/project/i2pd) + Donations --------- diff --git a/Win32/Win32App.cpp b/Win32/Win32App.cpp index 7104ec7f..377e1076 100644 --- a/Win32/Win32App.cpp +++ b/Win32/Win32App.cpp @@ -31,6 +31,7 @@ #define ID_RELOAD 2006 #define ID_ACCEPT_TRANSIT 2007 #define ID_DECLINE_TRANSIT 2008 +#define ID_DATADIR 2009 #define ID_TRAY_ICON 2050 #define WM_TRAYICON (WM_USER + 1) @@ -49,7 +50,8 @@ namespace win32 { HMENU hPopup = CreatePopupMenu(); InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_CONSOLE, "Open &console"); - InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_APP, "Show app"); + InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_DATADIR, "Open &datadir"); + InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_APP, "&Show app"); InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_ABOUT, "&About..."); InsertMenu (hPopup, -1, MF_BYPOSITION | MF_SEPARATOR, 0, NULL); if(!i2p::context.AcceptsTunnels()) @@ -303,6 +305,12 @@ namespace win32 SetTimer(hWnd, FRAME_UPDATE_TIMER, 3000, NULL); return 0; } + case ID_DATADIR: + { + std::string datadir(i2p::fs::GetUTF8DataDir()); + ShellExecute(NULL, "explore", datadir.c_str(), NULL, NULL, SW_SHOWNORMAL); + return 0; + } } break; } diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 89d85494..00000000 --- a/appveyor.yml +++ /dev/null @@ -1,57 +0,0 @@ -version: 2.38.0.{build} -pull_requests: - do_not_increment_build_number: true -branches: - only: - - openssl -skip_tags: true -os: Visual Studio 2015 -shallow_clone: true -clone_depth: 1 - -# avoid building 32-bit if 64-bit failed already -matrix: - fast_finish: true - -environment: - APPVEYOR_SAVE_CACHE_ON_ERROR: true - MSYS2_PATH_TYPE: inherit - CHERE_INVOKING: enabled_from_arguments - matrix: - - MSYSTEM: MINGW64 - - MSYSTEM: MINGW32 - -cache: - - c:\msys64\var\cache\pacman\pkg\ - -install: -# install new signing keyring -- c:\msys64\usr\bin\bash -lc "curl -O https://mirror.selfnet.de/msys2/msys/x86_64/msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz" -- c:\msys64\usr\bin\bash -lc "curl -O https://mirror.selfnet.de/msys2/msys/x86_64/msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz.sig" -- c:\msys64\usr\bin\bash -lc "pacman-key --verify msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz.sig" -- c:\msys64\usr\bin\bash -lc "pacman --noconfirm -U msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz" -# remove packages which can break build -- c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Rns gcc-fortran gcc mingw-w64-{i686,x86_64}-gcc-ada mingw-w64-{i686,x86_64}-gcc-objc" -# update runtime -- c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Syuu" -# Kill bash before next try -- taskkill /T /F /IM bash.exe /IM gpg.exe /IM gpg-agent.exe | exit /B 0 -# update packages and install required -- c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Syuu $MINGW_PACKAGE_PREFIX-boost $MINGW_PACKAGE_PREFIX-miniupnpc" - -build_script: -- c:\msys64\usr\bin\bash -lc "make USE_UPNP=yes DEBUG=no -j3" -# prepare archive for uploading -- set "FILELIST=i2pd.exe README.txt contrib/i2pd.conf contrib/tunnels.conf contrib/certificates contrib/tunnels.d" -- echo This is development build, use it carefully! For running in portable mode, move all files from contrib directory here. > README.txt -- 7z a -tzip -mx9 -mmt i2pd-%APPVEYOR_BUILD_VERSION%-%APPVEYOR_REPO_COMMIT:~0,7%-mingw-win%MSYSTEM:~-2%.zip %FILELIST% - -after_build: -- c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Sc" - -test: off - -deploy: off - -artifacts: -- path: i2pd-*.zip diff --git a/build/.gitignore b/build/.gitignore index b595141b..872332c5 100644 --- a/build/.gitignore +++ b/build/.gitignore @@ -3,6 +3,7 @@ /i2pd /libi2pd.a /libi2pdclient.a +/libi2pdlang.a /cmake_install.cmake /CMakeCache.txt /CPackConfig.cmake @@ -11,4 +12,4 @@ /arch.c # windows build script i2pd*.zip -build*.log \ No newline at end of file +build*.log diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index d1b51f23..2cd598f4 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -27,6 +27,9 @@ option(WITH_THREADSANITIZER "Build with thread sanitizer unix only" OFF) set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules") set(CMAKE_SOURCE_DIR "..") +#Handle paths nicely +include(GNUInstallDirs) + # architecture include(TargetArch) target_architecture(ARCHITECTURE) @@ -48,8 +51,8 @@ set_target_properties(libi2pd PROPERTIES PREFIX "") if(WITH_LIBRARY) install(TARGETS libi2pd EXPORT libi2pd - ARCHIVE DESTINATION lib - LIBRARY DESTINATION lib + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT Libraries) # TODO Make libi2pd available to 3rd party projects via CMake as imported target # FIXME This pulls stdafx @@ -63,12 +66,22 @@ set_target_properties(libi2pdclient PROPERTIES PREFIX "") if(WITH_LIBRARY) install(TARGETS libi2pdclient EXPORT libi2pdclient - ARCHIVE DESTINATION lib - LIBRARY DESTINATION lib + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT Libraries) endif() FILE(GLOB LANG_SRC ${LANG_SRC_DIR}/*.cpp) +add_library(libi2pdlang ${LANG_SRC}) +set_target_properties(libi2pdlang PROPERTIES PREFIX "") + +if(WITH_LIBRARY) + install(TARGETS libi2pdlang + EXPORT libi2pdlang + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + COMPONENT Libraries) +endif() set(DAEMON_SRC "${DAEMON_SRC_DIR}/Daemon.cpp" @@ -198,10 +211,11 @@ if(WITH_PCH) ) target_compile_options(libi2pd PRIVATE -include libi2pd/stdafx.h) target_compile_options(libi2pdclient PRIVATE -include libi2pd/stdafx.h) + target_compile_options(libi2pdlang PRIVATE -include libi2pd/stdafx.h) target_link_libraries(libi2pd stdafx) endif() -target_link_libraries(libi2pdclient libi2pd) +target_link_libraries(libi2pdclient libi2pd libi2pdlang) find_package(Boost COMPONENTS system filesystem program_options date_time REQUIRED) if(NOT DEFINED Boost_INCLUDE_DIRS) @@ -261,11 +275,8 @@ message(STATUS " ADDRSANITIZER : ${WITH_ADDRSANITIZER}") message(STATUS " THREADSANITIZER : ${WITH_THREADSANITIZER}") message(STATUS "---------------------------------------") -#Handle paths nicely -include(GNUInstallDirs) - if(WITH_BINARY) - add_executable("${PROJECT_NAME}" ${LANG_SRC} ${DAEMON_SRC}) + add_executable("${PROJECT_NAME}" ${DAEMON_SRC}) if(WITH_STATIC) set_target_properties("${PROJECT_NAME}" PROPERTIES LINK_FLAGS "-static") @@ -295,7 +306,7 @@ if(WITH_BINARY) endif() target_link_libraries(libi2pd ${Boost_LIBRARIES} ${ZLIB_LIBRARY}) - target_link_libraries("${PROJECT_NAME}" libi2pd libi2pdclient ${DL_LIB} ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${UPNP_LIB} ${ZLIB_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} ${MINGW_EXTRA} ${DL_LIB} ${CMAKE_REQUIRED_LIBRARIES}) + target_link_libraries("${PROJECT_NAME}" libi2pd libi2pdclient libi2pdlang ${DL_LIB} ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${UPNP_LIB} ${ZLIB_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} ${MINGW_EXTRA} ${DL_LIB} ${CMAKE_REQUIRED_LIBRARIES}) install(TARGETS "${PROJECT_NAME}" RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT Runtime) set(APPS "\${CMAKE_INSTALL_PREFIX}/bin/${PROJECT_NAME}${CMAKE_EXECUTABLE_SUFFIX}") diff --git a/build/build_mingw.cmd b/build/build_mingw.cmd index e8f1378b..a4e6d8de 100644 --- a/build/build_mingw.cmd +++ b/build/build_mingw.cmd @@ -34,13 +34,19 @@ del /S build_*.log >> nul 2>&1 echo Receiving latest commit and cleaning up... %xSH% "git checkout contrib/* && git pull && make clean" > build\build.log 2>&1 -echo. REM set to variable current commit hash -FOR /F "usebackq" %%a IN (`%xSH% 'git describe --tags'`) DO ( +FOR /F "usebackq" %%a IN (`%xSH% "git describe --tags"`) DO ( set tag=%%a ) +REM set to variable latest released tag +FOR /F "usebackq" %%b IN (`%xSH% "git describe --abbrev=0"`) DO ( + set reltag=%%b +) + +echo Preparing configuration files and README for packaging... + %xSH% "echo To use configs and certificates, move all files and certificates folder from contrib directory here. > README.txt" >> nul REM converting configuration files to DOS format (usable in default notepad) @@ -64,8 +70,9 @@ call :BUILDING_XP echo. REM compile installer -C:\PROGRA~2\INNOSE~1\ISCC.exe /dI2Pd_TextVer="%tag%" /dI2Pd_Ver="%tag%.0" build\win_installer.iss >> build\build.log 2>&1 +C:\PROGRA~2\INNOSE~1\ISCC.exe /dI2Pd_TextVer="%tag%" /dI2Pd_Ver="%reltag%.0" build\win_installer.iss >> build\build.log 2>&1 +%xSH% "git checkout contrib/*" >> build\build.log 2>&1 del README.txt i2pd_x32.exe i2pd_x64.exe i2pd_xp.exe >> nul echo Build complete... @@ -74,13 +81,13 @@ exit /b 0 :BUILDING %xSH% "make clean" >> nul -echo Building i2pd %tag% for win%bitness% +echo Building i2pd %tag% for win%bitness%... %xSH% "make DEBUG=no USE_UPNP=yes -j%threads% && cp i2pd.exe i2pd_x%bitness%.exe && zip -r9 build/i2pd_%tag%_win%bitness%_mingw.zip %FILELIST% && make clean" > build\build_win%bitness%_%tag%.log 2>&1 goto EOF :BUILDING_XP %xSH% "make clean" >> nul -echo Building i2pd %tag% for winxp +echo Building i2pd %tag% for winxp... %xSH% "make DEBUG=no USE_UPNP=yes USE_WINXP_FLAGS=yes -j%threads% && cp i2pd.exe i2pd_xp.exe && zip -r9 build/i2pd_%tag%_winxp_mingw.zip %FILELIST% && make clean" > build\build_winxp_%tag%.log 2>&1 :EOF \ No newline at end of file diff --git a/build/docker/README.md b/build/docker/README.md deleted file mode 100644 index 877a9ce5..00000000 --- a/build/docker/README.md +++ /dev/null @@ -1,34 +0,0 @@ -Howto build & run -================== - -**Build** - -Assuming you're in the root directory of the anoncoin source code. - -$ `cd build/docker` -$ `docker -t meeh/i2pd:latest .` - -**Run** - -To run either the local build, or if not found - fetched prebuild from hub.docker.io, run the following command. - -$ `docker run --name anonnode -v /path/to/i2pd/datadir/on/host:/var/lib/i2pd -p 7070:7070 -p 4444:4444 -p 4447:4447 -p 7656:7656 -p 2827:2827 -p 7654:7654 -p 7650:7650 -d meeh/i2pd` - -All the ports ( -p HOSTPORT:DOCKERPORT ) is optional. However the command above enable all features (Webconsole, HTTP Proxy, BOB, SAM, i2cp, etc) - -The volume ( -v HOSTDIR:DOCKERDIR ) is also optional, but if you don't use it, your config, routerid and private keys will die along with the container. - -**Options** - -Options are set via docker environment variables. This can be set at run with -e parameters. - -* **ENABLE_IPV6** - Enable IPv6 support. Any value can be used - it triggers as long as it's not empty. -* **LOGLEVEL** - Set the loglevel. -* **ENABLE_AUTH** - Enable auth for the webconsole. Username and password needs to be set manually in i2pd.conf cause security reasons. - -**Logging** - -Logging happens to STDOUT as the best practise with docker containers, since infrastructure systems like kubernetes with ELK integration can automatically forward the log to say, kibana or greylog without manual setup. :) - - - diff --git a/build/docker/old-ubuntu-based/Dockerfile b/build/docker/old-ubuntu-based/Dockerfile deleted file mode 100644 index 3faddf2e..00000000 --- a/build/docker/old-ubuntu-based/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -FROM ubuntu - -RUN apt-get update && apt-get install -y libboost-dev libboost-filesystem-dev \ - libboost-program-options-dev libboost-date-time-dev \ - libssl-dev git build-essential - -RUN git clone https://github.com/PurpleI2P/i2pd.git -WORKDIR /i2pd -RUN make - -CMD ./i2pd diff --git a/build/fig.yml b/build/fig.yml deleted file mode 100644 index 372ef350..00000000 --- a/build/fig.yml +++ /dev/null @@ -1,2 +0,0 @@ -i2pd: - build: . diff --git a/contrib/certificates/reseed/orignal_at_mail.i2p.crt b/contrib/certificates/reseed/orignal_at_mail.i2p.crt new file mode 100644 index 00000000..799b601b --- /dev/null +++ b/contrib/certificates/reseed/orignal_at_mail.i2p.crt @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFfzCCA2egAwIBAgIEbNbRPjANBgkqhkiG9w0BAQ0FADBwMQswCQYDVQQGEwJY +WDELMAkGA1UECAwCWFgxCzAJBgNVBAcMAlhYMR4wHAYDVQQKDBVJMlAgQW5vbnlt +b3VzIE5ldHdvcmsxDDAKBgNVBAsMA0kyUDEZMBcGA1UEAwwQb3JpZ25hbEBtYWls +LmkycDAeFw0yMTA3MDYyMjExMDFaFw0zMTA3MDQyMjExMDFaMHAxCzAJBgNVBAYT +AlhYMQswCQYDVQQIDAJYWDELMAkGA1UEBwwCWFgxHjAcBgNVBAoMFUkyUCBBbm9u +eW1vdXMgTmV0d29yazEMMAoGA1UECwwDSTJQMRkwFwYDVQQDDBBvcmlnbmFsQG1h +aWwuaTJwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvNJz2KGuAkHP +tGFobfLvpybtxB50fkcEsTc9opmiy7wBKK9rSI01VS616IhABkWKZVfK2A9NqpGv +v/CyhTKoaeSNeXY7+zORUWgWK/zA9fA4GRZFqlW8j4tbompDwcLYNqRBCsn1C0OY +YA5JhXPBixMcnXl8N8x4sXhQ4l9R3+QrydhUHRvgDc8dOxRyIX7zuQAyf8tmA2Xo +xZLdvDcCJdLBIbFwxhIceIhgcOwaOx7oRkZDZdYcLJd3zjyPbu8JtOM2ZkwH7r+0 +ro5PktuDp2LAS6SII5yYNcwcrvPZGPqhLdifIw1BrdTIb/rIkQZ5iXOOdyPmT7e8 +IwAJcPFlfvrS4Vbi9oDqyx3aDUBoubgmFnO1TirL56ck83R/ubcKtdnyzAn5dp+f +ZNYW6/foSBpDDOCViylbFAR5H0HJEbBns7PZx6mGEEI4tUAJdNYl7Ly7Df60a9Rz +cD/gz08U9UwFXYKoT6roEjToADGAzb5MI4cVlAb2AmQaMNXNe04HcDL1bU50mkNU +amqPv8nxf72fBQCEmZz2G57T6QiYTtcCwiWS1QdWsuaOtCo9zO0MKcjzSdUxuxEc +dXhjQdNegsgg/Xk7bJ8lKOsACqMpFftdPmuyeZU2t+3RPuBpV/0j2qUfg/y6kb0z +CxAOYmlcL4kqw4VT+5V/EeZLIG0h9I0CAwEAAaMhMB8wHQYDVR0OBBYEFD/wJObg +CCDuhMJCVWTSTj+B3rsUMA0GCSqGSIb3DQEBDQUAA4ICAQC0PjsTSPWlGbLNeeI8 +F0B5xAwXYJzZ7/LRxh8u42HDUqVIDjqkuls1l3v9D7htty2Gr3Ws2dcvcOr2KcOy +mEWg+jdP/N3vt9IkZeVS4YQoPgq6orn7lVkk00bcKb24f7ZnoQnnVV0/m42Y5P4j +LLh+8MBxsez9azXyZbDVEkgsMUAkdVO6KNz6scqz7wb8egV2GAMAp7cwChC6lanK +gv9ZyJhG/HdTv6VyuMZhJy6rX4geM97tm1iHu1VLsQcIzBKAdEvWJv8ofMeiyINe +hqAP9NYaeowKi975NOrmf+XZwxd0niApIohV684RCVUfL8H7HSPbdXhBJ/WslyDP +cTGhA2BLqEXZBn/nLQknlnl0SZTQxG2n4fEgD1E5YS/aoBrig/uXtWm2Zdf8U3mM ++bNXhbi9s7LneN2ye8LlNJBSRklNn/bNo8OmzLII1RQwf1+vaHT96lASbTVepMZ/ +Y9VcC8fAmho/zfQEKueLEB03K+gr2dGD+1crmMtUBjWJ9vPjtooZArtkDbh+kVYA +cx4N4NXULRwxVWZe5wTQOqcZ3qSS1ClMwaziwychGaj8xRAirHMZnlPOZO1UK4+5 +8F4RMJktyZjNgSLP76XPS4rJK5fobuPqFeA4OpDFn/5+/XeQFF6i6wntx1tzztzH +zc+BrVZOdcYPqu9iLXyRQ9JwwA== +-----END CERTIFICATE----- diff --git a/contrib/certificates/router/orignal_at_mail.i2p.crt b/contrib/certificates/router/orignal_at_mail.i2p.crt deleted file mode 100644 index c1229f3b..00000000 --- a/contrib/certificates/router/orignal_at_mail.i2p.crt +++ /dev/null @@ -1,31 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIFVDCCAzwCCQC2r1XWYtqtAzANBgkqhkiG9w0BAQsFADBsMQswCQYDVQQGEwJY -WDELMAkGA1UECAwCWFgxCzAJBgNVBAcMAlhYMRMwEQYDVQQKDApQdXJwbGUgSTJQ -MQ0wCwYDVQQLDARJMlBEMR8wHQYJKoZIhvcNAQkBFhBvcmlnbmFsQG1haWwuaTJw -MB4XDTE1MDIyMjEzNTgxOFoXDTI1MDIxOTEzNTgxOFowbDELMAkGA1UEBhMCWFgx -CzAJBgNVBAgMAlhYMQswCQYDVQQHDAJYWDETMBEGA1UECgwKUHVycGxlIEkyUDEN -MAsGA1UECwwESTJQRDEfMB0GCSqGSIb3DQEJARYQb3JpZ25hbEBtYWlsLmkycDCC -AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALp3D/gdvFjrMm+IE8tHZCWE -hQ6Pp0CCgCGDBC3WQFLqR98bqVPl4UwRG/MKY/LY7Woai06JNmGcpfw0LMoNnHxT -bvKtDRe/8kQdhdLHhgIkWKSbMvTAl7uUdV6FzsPgDR0x7scoFVWEhkF0wfmzGF2V -yr/WCBQejFPu69z03m5tRQ8Xjp2txWV45RawUmFu50bgbZvLCSLfTkIvxmfJzgPN -pJ3sPa/g7TBZl2uEiAu4uaEKvTuuzStOWCGgFaHYFVlTfFXTvmhFMqHfaidtzrlu -H35WGrmIWTDl6uGPC5QkSppvkj73rDj5aEyPzWMz5DN3YeECoVSchN+OJJCM6m7+ -rLFYXghVEp2h+T9O1GBRfcHlQ2E3CrWWvxhmK8dfteJmd501dyNX2paeuIg/aPFO -54/8m2r11uyF29hgY8VWLdXtqvwhKuK36PCzofEwDp9QQX8GRsEV4pZTrn4bDhGo -kb9BF7TZTqtL3uyiRmIyBXrNNiYlA1Xm4fyKRtxl0mrPaUXdgdnCt3KxOAJ8WM2B -7L/kk9U8C/nexHbMxIZfTap49XcUg5dxSO9kOBosIOcCUms8sAzBPDV2tWAByhYF -jI/Tutbd3F0+fvcmTcIFOlGbOxKgO2SfwXjv/44g/3LMK6IAMFB9UOc8KhnnJP0f -uAHvMXn1ahRs4pM1VizLAgMBAAEwDQYJKoZIhvcNAQELBQADggIBAIOxdaXT+wfu -nv/+1hy5T4TlRMNNsuj79ROcy6Mp+JwMG50HjTc0qTlXh8C7nHybDJn4v7DA+Nyn -RxT0J5I+Gqn+Na9TaC9mLeX/lwe8/KomyhBWxjrsyWj1V6v/cLO924S2rtcfzMDm -l3SFh9YHM1KF/R9N1XYBwtMzr3bupWDnE1yycYp1F4sMLr5SMzMQ0svQpQEM2/y5 -kly8+eUzryhm+ag9x1686uEG5gxhQ1eHQoZEaClHUOsV+28+d5If7cqcYx9Hf5Tt -CiVjJQzdxBF+6GeiJtKxnLtevqlkbyIJt6Cm9/7YIy/ovRGF2AKSYN6oCwmZQ6i1 -8nRnFq5zE7O94m+GXconWZxy0wVqA6472HThMi7S+Tk/eLYen2ilGY+KCb9a0FH5 -5MOuWSoJZ8/HfW2VeQmL8EjhWm5F2ybg28wgXK4BOGR3jQi03Fsc+AFidnWxSKo0 -aiJoPgOsfyu8/fnCcAi07kSmjzUKIWskApgcpGQLNXHFK9mtg7+VA8esRnfLlKtP -tJf+nNAPY1sqHfGBzh7WWGWal5RGHF5nEm3ta3oiFF5sMKCJ6C87zVwFkEcRytGC -xOGmiG1O1RPrO5NG7rZUaQ4y1OKl2Y1H+nGONzZ3mvoAOvxEq6JtUnU2kZscpPlk -fpeOSDoGBYJGbIpzDreBDhxaZrwGq36k ------END CERTIFICATE----- diff --git a/contrib/docker/Dockerfile b/contrib/docker/Dockerfile index dc9f5501..71af141e 100644 --- a/contrib/docker/Dockerfile +++ b/contrib/docker/Dockerfile @@ -25,7 +25,8 @@ RUN mkdir -p "$I2PD_HOME" "$DATA_DIR" \ # 1. install deps, clone and build. # 2. strip binaries. # 3. Purge all dependencies and other unrelated packages, including build directory. -RUN apk --no-cache --virtual build-dependendencies add make gcc g++ libtool zlib-dev boost-dev build-base openssl-dev openssl miniupnpc-dev git \ +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 \ diff --git a/contrib/i18n/English.po b/contrib/i18n/English.po new file mode 100644 index 00000000..25378f82 --- /dev/null +++ b/contrib/i18n/English.po @@ -0,0 +1,724 @@ +# i2pd +# Copyright (C) 2021 PurpleI2P team +# This file is distributed under the same license as the i2pd package. +# R4SAS , 2021. +# +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" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 3.0\n" +"X-Poedit-SourceCharset: UTF-8\n" +"X-Poedit-Basepath: .\n" +"X-Poedit-KeywordsList: ;tr\n" +"X-Poedit-SearchPath-0: daemon/HTTPServer.cpp\n" +"X-Poedit-SearchPath-1: libi2pd_client/HTTPProxy.cpp\n" + +#: daemon/HTTPServer.cpp:177 +msgid "day" +msgid_plural "days" +msgstr[0] "" +msgstr[1] "" + +#: daemon/HTTPServer.cpp:181 +msgid "hour" +msgid_plural "hours" +msgstr[0] "" +msgstr[1] "" + +#: daemon/HTTPServer.cpp:185 +msgid "minute" +msgid_plural "minutes" +msgstr[0] "" +msgstr[1] "" + +#: daemon/HTTPServer.cpp:188 +msgid "second" +msgid_plural "seconds" +msgstr[0] "" +msgstr[1] "" + +#. tr: Kibibit +#: daemon/HTTPServer.cpp:196 daemon/HTTPServer.cpp:224 +msgid "KiB" +msgstr "" + +#. tr: Mebibit +#: daemon/HTTPServer.cpp:198 +msgid "MiB" +msgstr "" + +#. tr: Gibibit +#: daemon/HTTPServer.cpp:200 +msgid "GiB" +msgstr "" + +#: daemon/HTTPServer.cpp:217 +msgid "building" +msgstr "" + +#: daemon/HTTPServer.cpp:218 +msgid "failed" +msgstr "" + +#: daemon/HTTPServer.cpp:219 +msgid "expiring" +msgstr "" + +#: daemon/HTTPServer.cpp:220 +msgid "established" +msgstr "" + +#: daemon/HTTPServer.cpp:221 +msgid "unknown" +msgstr "" + +#: daemon/HTTPServer.cpp:223 +msgid "exploratory" +msgstr "" + +#: daemon/HTTPServer.cpp:259 +msgid "i2pd webconsole" +msgstr "" + +#: daemon/HTTPServer.cpp:262 +msgid "Main page" +msgstr "" + +#: daemon/HTTPServer.cpp:263 daemon/HTTPServer.cpp:725 +msgid "Router commands" +msgstr "" + +#: daemon/HTTPServer.cpp:264 daemon/HTTPServer.cpp:448 +#: daemon/HTTPServer.cpp:460 +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 +msgid "LeaseSets" +msgstr "" + +#: daemon/HTTPServer.cpp:268 daemon/HTTPServer.cpp:694 +msgid "Tunnels" +msgstr "" + +#: daemon/HTTPServer.cpp:269 daemon/HTTPServer.cpp:425 +#: daemon/HTTPServer.cpp:787 daemon/HTTPServer.cpp:803 +msgid "Transit Tunnels" +msgstr "" + +#: daemon/HTTPServer.cpp:270 daemon/HTTPServer.cpp:852 +msgid "Transports" +msgstr "" + +#: daemon/HTTPServer.cpp:271 +msgid "I2P tunnels" +msgstr "" + +#: daemon/HTTPServer.cpp:273 daemon/HTTPServer.cpp:914 +#: daemon/HTTPServer.cpp:924 +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 +msgid "ERROR" +msgstr "" + +#: daemon/HTTPServer.cpp:296 +msgid "OK" +msgstr "" + +#: daemon/HTTPServer.cpp:297 +msgid "Testing" +msgstr "" + +#: daemon/HTTPServer.cpp:298 +msgid "Firewalled" +msgstr "" + +#: daemon/HTTPServer.cpp:299 daemon/HTTPServer.cpp:320 +#: daemon/HTTPServer.cpp:406 +msgid "Unknown" +msgstr "" + +#: daemon/HTTPServer.cpp:300 daemon/HTTPServer.cpp:435 +#: daemon/HTTPServer.cpp:436 daemon/HTTPServer.cpp:982 +#: daemon/HTTPServer.cpp:991 +msgid "Proxy" +msgstr "" + +#: daemon/HTTPServer.cpp:301 +msgid "Mesh" +msgstr "" + +#: daemon/HTTPServer.cpp:304 +msgid "Error" +msgstr "" + +#: daemon/HTTPServer.cpp:308 +msgid "Clock skew" +msgstr "" + +#: daemon/HTTPServer.cpp:311 +msgid "Offline" +msgstr "" + +#: daemon/HTTPServer.cpp:314 +msgid "Symmetric NAT" +msgstr "" + +#: daemon/HTTPServer.cpp:326 +msgid "Uptime" +msgstr "" + +#: daemon/HTTPServer.cpp:329 +msgid "Network status" +msgstr "" + +#: daemon/HTTPServer.cpp:334 +msgid "Network status v6" +msgstr "" + +#: daemon/HTTPServer.cpp:340 daemon/HTTPServer.cpp:347 +msgid "Stopping in" +msgstr "" + +#: daemon/HTTPServer.cpp:354 +msgid "Family" +msgstr "" + +#: daemon/HTTPServer.cpp:355 +msgid "Tunnel creation success rate" +msgstr "" + +#: daemon/HTTPServer.cpp:356 +msgid "Received" +msgstr "" + +#. tr: Kibibit/s +#: daemon/HTTPServer.cpp:358 daemon/HTTPServer.cpp:361 +#: daemon/HTTPServer.cpp:364 +msgid "KiB/s" +msgstr "" + +#: daemon/HTTPServer.cpp:359 +msgid "Sent" +msgstr "" + +#: daemon/HTTPServer.cpp:362 +msgid "Transit" +msgstr "" + +#: daemon/HTTPServer.cpp:365 +msgid "Data path" +msgstr "" + +#: daemon/HTTPServer.cpp:368 +msgid "Hidden content. Press on text to see." +msgstr "" + +#: daemon/HTTPServer.cpp:371 +msgid "Router Ident" +msgstr "" + +#: daemon/HTTPServer.cpp:373 +msgid "Router Family" +msgstr "" + +#: daemon/HTTPServer.cpp:374 +msgid "Router Caps" +msgstr "" + +#: daemon/HTTPServer.cpp:375 +msgid "Version" +msgstr "" + +#: daemon/HTTPServer.cpp:376 +msgid "Our external address" +msgstr "" + +#: daemon/HTTPServer.cpp:384 +msgid "supported" +msgstr "" + +#: daemon/HTTPServer.cpp:416 +msgid "Routers" +msgstr "" + +#: daemon/HTTPServer.cpp:417 +msgid "Floodfills" +msgstr "" + +#: daemon/HTTPServer.cpp:424 daemon/HTTPServer.cpp:968 +msgid "Client Tunnels" +msgstr "" + +#: daemon/HTTPServer.cpp:434 +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 +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 +msgid "Disabled" +msgstr "" + +#: daemon/HTTPServer.cpp:483 +msgid "Encrypted B33 address" +msgstr "" + +#: daemon/HTTPServer.cpp:492 +msgid "Address registration line" +msgstr "" + +#: daemon/HTTPServer.cpp:497 +msgid "Domain" +msgstr "" + +#: daemon/HTTPServer.cpp:498 +msgid "Generate" +msgstr "" + +#: daemon/HTTPServer.cpp:499 +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 +msgid "Address" +msgstr "" + +#: daemon/HTTPServer.cpp:505 +msgid "Type" +msgstr "" + +#: daemon/HTTPServer.cpp:505 +msgid "EncType" +msgstr "" + +#: daemon/HTTPServer.cpp:515 daemon/HTTPServer.cpp:699 +msgid "Inbound tunnels" +msgstr "" + +#. tr: Milliseconds +#: daemon/HTTPServer.cpp:520 daemon/HTTPServer.cpp:530 +#: daemon/HTTPServer.cpp:704 daemon/HTTPServer.cpp:714 +msgid "ms" +msgstr "" + +#: daemon/HTTPServer.cpp:525 daemon/HTTPServer.cpp:709 +msgid "Outbound tunnels" +msgstr "" + +#: daemon/HTTPServer.cpp:537 +msgid "Tags" +msgstr "" + +#: daemon/HTTPServer.cpp:537 +msgid "Incoming" +msgstr "" + +#: daemon/HTTPServer.cpp:544 daemon/HTTPServer.cpp:547 +msgid "Outgoing" +msgstr "" + +#: daemon/HTTPServer.cpp:545 daemon/HTTPServer.cpp:561 +msgid "Destination" +msgstr "" + +#: daemon/HTTPServer.cpp:545 +msgid "Amount" +msgstr "" + +#: daemon/HTTPServer.cpp:552 +msgid "Incoming Tags" +msgstr "" + +#: daemon/HTTPServer.cpp:560 daemon/HTTPServer.cpp:563 +msgid "Tags sessions" +msgstr "" + +#: daemon/HTTPServer.cpp:561 +msgid "Status" +msgstr "" + +#: daemon/HTTPServer.cpp:570 daemon/HTTPServer.cpp:626 +msgid "Local Destination" +msgstr "" + +#: daemon/HTTPServer.cpp:580 daemon/HTTPServer.cpp:947 +msgid "Streams" +msgstr "" + +#: daemon/HTTPServer.cpp:602 +msgid "Close stream" +msgstr "" + +#: daemon/HTTPServer.cpp:631 +msgid "I2CP session not found" +msgstr "" + +#: daemon/HTTPServer.cpp:634 +msgid "I2CP is not enabled" +msgstr "" + +#: daemon/HTTPServer.cpp:660 +msgid "Invalid" +msgstr "" + +#: daemon/HTTPServer.cpp:663 +msgid "Store type" +msgstr "" + +#: daemon/HTTPServer.cpp:664 +msgid "Expires" +msgstr "" + +#: daemon/HTTPServer.cpp:669 +msgid "Non Expired Leases" +msgstr "" + +#: daemon/HTTPServer.cpp:672 +msgid "Gateway" +msgstr "" + +#: daemon/HTTPServer.cpp:673 +msgid "TunnelID" +msgstr "" + +#: daemon/HTTPServer.cpp:674 +msgid "EndDate" +msgstr "" + +#: daemon/HTTPServer.cpp:684 +msgid "not floodfill" +msgstr "" + +#: daemon/HTTPServer.cpp:695 +msgid "Queue size" +msgstr "" + +#: daemon/HTTPServer.cpp:726 +msgid "Run peer test" +msgstr "" + +#: daemon/HTTPServer.cpp:731 +msgid "Decline transit tunnels" +msgstr "" + +#: daemon/HTTPServer.cpp:733 +msgid "Accept transit tunnels" +msgstr "" + +#: daemon/HTTPServer.cpp:737 daemon/HTTPServer.cpp:742 +msgid "Cancel graceful shutdown" +msgstr "" + +#: daemon/HTTPServer.cpp:739 daemon/HTTPServer.cpp:744 +msgid "Start graceful shutdown" +msgstr "" + +#: daemon/HTTPServer.cpp:747 +msgid "Force shutdown" +msgstr "" + +#: daemon/HTTPServer.cpp:748 +msgid "Reload external CSS styles" +msgstr "" + +#: daemon/HTTPServer.cpp:751 +msgid "" +"Note: any action done here are not persistent and not changes your " +"config files." +msgstr "" + +#: daemon/HTTPServer.cpp:753 +msgid "Logging level" +msgstr "" + +#: daemon/HTTPServer.cpp:761 +msgid "Transit tunnels limit" +msgstr "" + +#: daemon/HTTPServer.cpp:766 daemon/HTTPServer.cpp:778 +msgid "Change" +msgstr "" + +#: daemon/HTTPServer.cpp:770 +msgid "Change language" +msgstr "" + +#: daemon/HTTPServer.cpp:803 +msgid "no transit tunnels currently built" +msgstr "" + +#: daemon/HTTPServer.cpp:908 daemon/HTTPServer.cpp:931 +msgid "SAM disabled" +msgstr "" + +#: daemon/HTTPServer.cpp:924 +msgid "no sessions currently running" +msgstr "" + +#: daemon/HTTPServer.cpp:937 +msgid "SAM session not found" +msgstr "" + +#: daemon/HTTPServer.cpp:942 +msgid "SAM Session" +msgstr "" + +#: daemon/HTTPServer.cpp:999 +msgid "Server Tunnels" +msgstr "" + +#: daemon/HTTPServer.cpp:1015 +msgid "Client Forwards" +msgstr "" + +#: daemon/HTTPServer.cpp:1029 +msgid "Server Forwards" +msgstr "" + +#: daemon/HTTPServer.cpp:1227 +msgid "Unknown page" +msgstr "" + +#: daemon/HTTPServer.cpp:1246 +msgid "Invalid token" +msgstr "" + +#: daemon/HTTPServer.cpp:1304 daemon/HTTPServer.cpp:1361 +#: daemon/HTTPServer.cpp:1401 +msgid "SUCCESS" +msgstr "" + +#: daemon/HTTPServer.cpp:1304 +msgid "Stream closed" +msgstr "" + +#: daemon/HTTPServer.cpp:1306 +msgid "Stream not found or already was closed" +msgstr "" + +#: daemon/HTTPServer.cpp:1309 +msgid "Destination not found" +msgstr "" + +#: daemon/HTTPServer.cpp:1312 +msgid "StreamID can't be null" +msgstr "" + +#: daemon/HTTPServer.cpp:1314 daemon/HTTPServer.cpp:1379 +msgid "Return to destination page" +msgstr "" + +#: daemon/HTTPServer.cpp:1315 daemon/HTTPServer.cpp:1328 +#: daemon/HTTPServer.cpp:1403 +msgid "You will be redirected in 5 seconds" +msgstr "" + +#: daemon/HTTPServer.cpp:1326 +msgid "Transit tunnels count must not exceed 65535" +msgstr "" + +#: daemon/HTTPServer.cpp:1327 daemon/HTTPServer.cpp:1402 +msgid "Back to commands list" +msgstr "" + +#: daemon/HTTPServer.cpp:1363 +msgid "Register at reg.i2p" +msgstr "" + +#: daemon/HTTPServer.cpp:1364 +msgid "Description" +msgstr "" + +#: daemon/HTTPServer.cpp:1364 +msgid "A bit information about service on domain" +msgstr "" + +#: daemon/HTTPServer.cpp:1365 +msgid "Submit" +msgstr "" + +#: daemon/HTTPServer.cpp:1371 +msgid "Domain can't end with .b32.i2p" +msgstr "" + +#: daemon/HTTPServer.cpp:1374 +msgid "Domain must end with .i2p" +msgstr "" + +#: daemon/HTTPServer.cpp:1377 +msgid "Such destination is not found" +msgstr "" + +#: daemon/HTTPServer.cpp:1397 +msgid "Unknown command" +msgstr "" + +#: daemon/HTTPServer.cpp:1401 +msgid "Command accepted" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:157 +msgid "Proxy error" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:165 +msgid "Proxy info" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:173 +msgid "Proxy error: Host not found" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:174 +msgid "Remote host not found in router's addressbook" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:175 +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 +msgid "Invalid request" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:273 +msgid "Proxy unable to parse your request" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:288 +msgid "addresshelper is not supported" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:297 libi2pd_client/HTTPProxy.cpp:306 +#: libi2pd_client/HTTPProxy.cpp:385 +msgid "Host" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:297 +msgid "added to router's addressbook from helper" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:298 +msgid "Click here to proceed:" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:298 libi2pd_client/HTTPProxy.cpp:308 +msgid "Continue" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:299 libi2pd_client/HTTPProxy.cpp:309 +msgid "Addresshelper found" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:306 +msgid "already in router's addressbook" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:307 +msgid "Click here to update record:" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:322 +msgid "invalid request uri" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:365 +msgid "Can't detect destination host from request" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:382 libi2pd_client/HTTPProxy.cpp:386 +msgid "Outproxy failure" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:382 +msgid "bad outproxy settings" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:385 +msgid "not inside I2P network, but outproxy is not enabled" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:474 +msgid "unknown outproxy url" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:480 +msgid "cannot resolve upstream proxy" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:488 +msgid "hostname too long" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:515 +msgid "cannot connect to upstream socks proxy" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:521 +msgid "Cannot negotiate with socks proxy" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:563 +msgid "CONNECT error" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:563 +msgid "Failed to Connect" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:574 libi2pd_client/HTTPProxy.cpp:600 +msgid "socks proxy error" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:582 +msgid "failed to send request to upstream" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:603 +msgid "No Reply From socks proxy" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:610 +msgid "cannot connect" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:610 +msgid "http out proxy not implemented" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:611 +msgid "cannot connect to upstream http proxy" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:644 +msgid "Host is down" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:644 +msgid "" +"Can't create connection to requested host, it may be down. Please try again " +"later." +msgstr "" diff --git a/contrib/i18n/README.md b/contrib/i18n/README.md new file mode 100644 index 00000000..04779473 --- /dev/null +++ b/contrib/i18n/README.md @@ -0,0 +1,29 @@ +`xgettext` command for extracting translation +--- + +``` +xgettext --omit-header -ctr: -ktr -ktr:1,2 daemon/HTTPServer.cpp libi2pd_client/HTTPProxy.cpp +``` + +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\ \"(.*)\"\nmsgstr\ \"(.*)\"\n +out: {"$1", "$2"},\n +``` + +``` +in: ^#[:.](.*)$\n +out: +``` + +``` +in: \n\n +out: \n +``` diff --git a/contrib/i2pd.conf b/contrib/i2pd.conf index f3cea2b5..dbf74002 100644 --- a/contrib/i2pd.conf +++ b/contrib/i2pd.conf @@ -15,6 +15,10 @@ ## Default: ~/.i2pd/tunnels.d or /var/lib/i2pd/tunnels.d # tunnelsdir = /var/lib/i2pd/tunnels.d +## Path to certificates used for verifying .su3, families +## Default: ~/.i2pd/certificates or /var/lib/i2pd/certificates +# certsdir = /var/lib/i2pd/certificates + ## Where to write pidfile (default: i2pd.pid, not used in Windows) # pidfile = /run/i2pd.pid @@ -104,7 +108,7 @@ port = 7070 # user = i2pd # pass = changeme ## Select webconsole language -## Currently supported english (default), russian, turkmen and ukrainian languages +## Currently supported english (default), afrikaans, russian, turkmen, ukrainian and uzbek languages # lang = english [httpproxy] diff --git a/debian/i2pd.openrc b/contrib/openrc/i2pd.openrc similarity index 100% rename from debian/i2pd.openrc rename to contrib/openrc/i2pd.openrc diff --git a/contrib/rpm/i2pd-git.spec b/contrib/rpm/i2pd-git.spec index 1061fc8f..b1354173 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.38.0 +Version: 2.39.0 Release: git%{git_hash}%{?dist} Summary: I2P router written in C++ Conflicts: i2pd @@ -56,9 +56,14 @@ cd build %endif %endif + +%if 0%{?fedora} >= 35 +pushd redhat-linux-build +%else %if 0%{?fedora} >= 33 pushd %{_target_platform} %endif +%endif %if 0%{?mageia} > 7 pushd build @@ -77,9 +82,13 @@ popd %install pushd build +%if 0%{?fedora} >= 35 +pushd redhat-linux-build +%else %if 0%{?fedora} >= 33 pushd %{_target_platform} %endif +%endif %if 0%{?mageia} pushd build @@ -137,6 +146,13 @@ getent passwd i2pd >/dev/null || \ %changelog +* Mon Aug 24 2021 r4sas - 2.39.0-2 +- changed if statements to cover fedora 35 + +* Mon Aug 23 2021 orignal - 2.39.0 +- update to 2.39.0 +- fixed build on fedora 36 + * Mon May 17 2021 orignal - 2.38.0 - update to 2.38.0 diff --git a/contrib/rpm/i2pd.spec b/contrib/rpm/i2pd.spec index a2994f28..ef8e32f2 100644 --- a/contrib/rpm/i2pd.spec +++ b/contrib/rpm/i2pd.spec @@ -1,6 +1,6 @@ Name: i2pd -Version: 2.38.0 -Release: 1%{?dist} +Version: 2.39.0 +Release: 2%{?dist} Summary: I2P router written in C++ Conflicts: i2pd-git @@ -54,9 +54,13 @@ cd build %endif %endif +%if 0%{?fedora} >= 35 +pushd redhat-linux-build +%else %if 0%{?fedora} >= 33 pushd %{_target_platform} %endif +%endif %if 0%{?mageia} > 7 pushd build @@ -75,9 +79,13 @@ popd %install pushd build +%if 0%{?fedora} >= 35 +pushd redhat-linux-build +%else %if 0%{?fedora} >= 33 pushd %{_target_platform} %endif +%endif %if 0%{?mageia} pushd build @@ -135,6 +143,13 @@ getent passwd i2pd >/dev/null || \ %changelog +* Mon Aug 24 2021 r4sas - 2.39.0-2 +- changed if statements to cover fedora 35 + +* Mon Aug 23 2021 orignal - 2.39.0 +- update to 2.39.0 +- fixed build on fedora 36 + * Mon May 17 2021 orignal - 2.38.0 - update to 2.38.0 diff --git a/contrib/tunnels.conf b/contrib/tunnels.conf index 3358ffc4..55723c43 100644 --- a/contrib/tunnels.conf +++ b/contrib/tunnels.conf @@ -1,16 +1,16 @@ -[IRC-IRC2P] +[IRC-ILITA] type = client address = 127.0.0.1 port = 6668 -destination = irc.postman.i2p +destination = irc.ilita.i2p destinationport = 6667 keys = irc-keys.dat -#[IRC-ILITA] +#[IRC-IRC2P] #type = client #address = 127.0.0.1 #port = 6669 -#destination = irc.ilita.i2p +#destination = irc.postman.i2p #destinationport = 6667 #keys = irc-keys.dat diff --git a/debian/i2pd.upstart b/contrib/upstart/i2pd.upstart similarity index 100% rename from debian/i2pd.upstart rename to contrib/upstart/i2pd.upstart diff --git a/contrib/webconsole/style.css b/contrib/webconsole/style.css new file mode 100644 index 00000000..861e747a --- /dev/null +++ b/contrib/webconsole/style.css @@ -0,0 +1,296 @@ +/* + * Copyright (c) 2013-2021, The PurpleI2P Project + * + * This file is part of Purple i2pd project and licensed under BSD3 + * + * See full license text in LICENSE file at top of project tree + * + ****************************************************************** + * + * This is style sheet for webconsole, with @media selectors for adaptive + * view on desktop and mobile devices, respecting preferred user's color + * scheme used in system/browser. + * + * Minified copy of that style sheet is bundled inside i2pd sources. +*/ + +:root { + --main-bg-color: #FAFAFA; + --main-text-color: #103456; + --main-link-color: #894C84; +} + +@media (prefers-color-scheme: dark) { + :root { + --main-bg-color: #181818; + --main-text-color: #156A3D; + --main-link-color: #894C84; + } +} + +body { + font: 100%/1.5em sans-serif; + margin: 0; + padding: 1.5em; + background: var(--main-bg-color); + color: var(--main-text-color); +} + +a, .slide label { + text-decoration: none; + color: var(--main-link-color); +} + +a:hover, .slide label:hover, button[type=submit]:hover { + color: #FAFAFA; + background: var(--main-link-color); +} + +a.button { + appearance: button; + text-decoration: none; + padding: 0 5px; + border: 1px solid var(--main-link-color); +} + +.header { + font-size: 2.5em; + text-align: center; + margin: 1em 0; + color: var(--main-link-color); +} + +.wrapper { + margin: 0 auto; + padding: 1em; + max-width: 60em; +} + +.menu { + display: block; + float: left; + overflow: hidden; + padding: 4px; + max-width: 12em; + white-space: nowrap; + text-overflow: ellipsis; +} + +.listitem { + display: block; + font-family: monospace; + font-size: 1.2em; + white-space: nowrap; +} + +.tableitem { + font-family: monospace; + font-size: 1.2em; + white-space: nowrap; +} + +.content { + float: left; + font-size: 1em; + margin-left: 4em; + padding: 4px; + max-width: 50em; + overflow: auto; +} + +.tunnel.established { + color: #56B734; +} + +.tunnel.expiring { + color: #D3AE3F; +} + +.tunnel.failed { + color: #D33F3F; +} + +.tunnel.building { + color: #434343; +} + +caption { + font-size: 1.5em; + text-align: center; + color: var(--main-link-color); +} + +table { + display: table; + border-collapse: collapse; + text-align: center; +} + +table.extaddr { + text-align: left; +} + +table.services { + width: 100%; +} + +textarea { + background-color: var(--main-bg-color); + color: var(--main-text-color); + word-break: break-all; +} + +.streamdest { + width: 120px; + max-width: 240px; + overflow: hidden; + text-overflow: ellipsis; +} + +.slide div.slidecontent, .slide [type="checkbox"] { + display: none; +} + +.slide [type="checkbox"]:checked ~ div.slidecontent { + display: block; + margin-top: 0; + padding: 0; +} + +.disabled { + color: #D33F3F; +} + +.enabled { + color: #56B734; +} + +button[type=submit] { + background-color: transparent; + color: var(--main-link-color); + text-decoration: none; + padding: 5px; + border: 1px solid var(--main-link-color); + font-size: 14px; +} + +input, select, select option { + background-color: var(--main-bg-color); + color: var(--main-link-color); + padding: 5px; + border: 1px solid var(--main-link-color); + font-size: 14px; +} + +input:focus, select:focus, select option:focus { + outline: none; +} + +input[type=number]::-webkit-inner-spin-button { + -webkit-appearance: none; +} + +@media screen and (max-width: 1150px) { /* adaptive style */ + .wrapper { + max-width: 56em; + } + + .menu { + max-width: 10em; + } + + .content { + margin-left: 2em; + max-width: 44em; + } +} + +@media screen and (max-width: 980px) { + body { + font: 100%/1.2em sans-serif; + padding: 1.2em 0 0 0; + } + + .menu { + width: 100%; + max-width: unset; + display: block; + float: none; + position: unset; + font-size: 16px; + text-align: center; + } + + .menu a, .commands a { + display: inline-block; + padding: 4px; + } + + .content { + float: none; + margin-left: unset; + margin-top: 16px; + max-width: 100%; + width: 100%; + text-align: center; + } + + a, .slide label { + display: block; + } + + .header { + margin: unset; + font-size: 1.5em; + } + + small { + display: block + } + + a.button { + appearance: button; + text-decoration: none; + margin-top: 10px; + padding: 6px; + border: 2px solid var(--main-link-color); + border-radius: 5px; + width: -webkit-fill-available; + } + + input, select { + width: 35%; + text-align: center; + padding: 5px; + border: 2px solid var(--main-link-color); + border-radius: 5px; + font-size: 18px; + } + + table.extaddr { + margin: auto; + text-align: unset; + } + + textarea { + width: -webkit-fill-available; + height: auto; + padding: 5px; + border: 2px solid var(--main-link-color); + border-radius: 5px; + font-size: 12px; + } + + button[type=submit] { + padding: 5px 15px; + background: transparent; + border: 2px solid var(--main-link-color); + cursor: pointer; + -webkit-border-radius: 5px; + border-radius: 5px; + position: relative; + height: 36px; + display: -webkit-inline-box; + margin-top: 10px; + } +} \ No newline at end of file diff --git a/daemon/Daemon.cpp b/daemon/Daemon.cpp index f7f2ef2f..8cee7852 100644 --- a/daemon/Daemon.cpp +++ b/daemon/Daemon.cpp @@ -94,6 +94,11 @@ namespace util i2p::config::GetOption("daemon", isDaemon); + std::string certsdir; i2p::config::GetOption("certsdir", certsdir); + i2p::fs::SetCertsDir(certsdir); + + certsdir = i2p::fs::GetCertsDir(); + std::string logs = ""; i2p::config::GetOption("log", logs); std::string logfile = ""; i2p::config::GetOption("logfile", logfile); std::string loglevel = ""; i2p::config::GetOption("loglevel", loglevel); @@ -129,9 +134,10 @@ namespace util // use stdout -- default } - LogPrint(eLogNone, "i2pd v", VERSION, " starting"); + LogPrint(eLogNone, "i2pd v", VERSION, " (", I2P_VERSION, ") starting"); LogPrint(eLogDebug, "FS: main config file: ", config); LogPrint(eLogDebug, "FS: data directory: ", datadir); + LogPrint(eLogDebug, "FS: certificates directory: ", certsdir); bool precomputation; i2p::config::GetOption("precomputation.elgamal", precomputation); bool aesni; i2p::config::GetOption("cpuext.aesni", aesni); diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index e184dd06..1e002b1b 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -36,73 +36,31 @@ #include "Win32App.h" #endif -// For image and info +// For image, style and info #include "version.h" +#include "HTTPServerResources.h" namespace i2p { namespace http { - const std::string itoopieFavicon = - "data:image/png;base64," - "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACx" - "jwv8YQUAAAAJcEhZcwAALiIAAC4iAari3ZIAAAAHdElNRQfgCQsUNSZrkhi1AAAAGXRFWHRTb2Z0" - "d2FyZQBwYWludC5uZXQgNC4wLjEyQwRr7AAAAoJJREFUOE9jwAUqi4Q1oEwwcDTV1+5sETaBclGB" - "vb09C5QJB6kWpvFQJoOCeLC5kmjEHCgXE2SlyETLi3h6QrkM4VL+ssWSCZUgtopITLKqaOotRTEn" - "cbAkLqAkGtOqLBLVAWLXyWSVFkkmRiqLxuaqiWb/VBYJMAYrwgckJY25VEUzniqKhjU2y+RtCRSP" - "6lUXy/1jIBV5tlYxZUaFVMq2NInwIi9hO8fSfOEAqDZUoCwal6MulvOvyS7gi69K4j9zxZT/m0ps" - "/28ptvvvquXXryIa7QYMMdTwqi0WNtVi0GIDseXl7TnUxFKfnGlxAGp0+D8j2eH/8Ub7/9e7nf7X" - "+Af/B7rwt6pI0h0l0WhQADOC9DBkhSirpImHNVZKp24ukkyoshGLnN8d5fA/y13t/44Kq/8hlnL/" - "z7fZ/58f6vcxSNpbVUVFhV1RLNBVTsQzVYZPSwhsCAhkiIfpNMrkbO6TLf071Sfk/5ZSi/+7q6z/" - "P5ns+v9mj/P/CpuI/20y+aeNGYxZoVoYGmsF3aFMBAAZlCwftnF9ke3//bU2//fXWP8/UGv731Am" - "+V+DdNblSqnUYqhSTKAiYSOqJBrVqiaa+S3UNPr/gmyH/xuKXf63hnn/B8bIP0UxHfEyyeSNQKVM" - "EB1AEB2twhcTLp+gIBJUoyKasEpVJHmqskh8qryovUG/ffCHHRU2q/Tk/YuB6eGPsbExa7ZkpLu1" - "oLEcVDtuUCgV1w60rQzElpRUE1EVSX0BYidHiInXF4nagNhYQW60EF+ApH1ktni0A1SIITSUgVlZ" - "JHYnlIsfzJjIp9xZKswL5YKBHL+coKJoRDaUSzoozxHVrygQU4JykQADAwAT5b1NHtwZugAAAABJ" - "RU5ErkJggg=="; + static void LoadExtCSS () + { + std::stringstream s; + std::string styleFile = i2p::fs::DataDirPath ("webconsole/style.css"); + if (i2p::fs::Exists(styleFile)) { + std::ifstream f(styleFile, std::ifstream::binary); + s << f.rdbuf(); + externalCSS = s.str(); + } else if (externalCSS.length() != 0) { // clean up external style if file was removed + externalCSS = ""; + } + } static void GetStyles (std::stringstream& s) { - s << "\r\n"; + if (externalCSS.length() != 0) + s << "\r\n"; + else + s << internalCSS; } const char HTTP_PAGE_TUNNELS[] = "tunnels"; @@ -127,29 +85,38 @@ namespace http { const char HTTP_COMMAND_KILLSTREAM[] = "closestream"; const char HTTP_COMMAND_LIMITTRANSIT[] = "limittransit"; const char HTTP_COMMAND_GET_REG_STRING[] = "get_reg_string"; + const char HTTP_COMMAND_SETLANGUAGE[] = "setlanguage"; + const char HTTP_COMMAND_RELOAD_CSS[] = "reload_css"; const char HTTP_PARAM_SAM_SESSION_ID[] = "id"; const char HTTP_PARAM_ADDRESS[] = "address"; - static std::string ConvertTime (uint64_t time); - std::map HTTPConnection::m_Tokens; + static std::string ConvertTime (uint64_t time) + { + lldiv_t divTime = lldiv(time, 1000); + time_t t = divTime.quot; + struct tm *tm = localtime(&t); + char date[128]; + snprintf(date, sizeof(date), "%02d/%02d/%d %02d:%02d:%02d.%03lld", tm->tm_mday, tm->tm_mon + 1, tm->tm_year + 1900, tm->tm_hour, tm->tm_min, tm->tm_sec, divTime.rem); + return date; + } static void ShowUptime (std::stringstream& s, int seconds) { int num; if ((num = seconds / 86400) > 0) { - s << num << " " << tr("days", num) << ", "; + s << num << " " << tr("day", "days", num) << ", "; seconds -= num * 86400; } if ((num = seconds / 3600) > 0) { - s << num << " " << tr("hours", num) << ", "; + s << num << " " << tr("hour", "hours", num) << ", "; seconds -= num * 3600; } if ((num = seconds / 60) > 0) { - s << num << " " << tr("minutes", num) << ", "; + s << num << " " << tr("minute", "minutes", num) << ", "; seconds -= num * 60; } - s << seconds << " " << tr("seconds", seconds); + s << seconds << " " << tr("second", "seconds", seconds); } static void ShowTraffic (std::stringstream& s, uint64_t bytes) @@ -157,11 +124,11 @@ namespace http { s << std::fixed << std::setprecision(2); auto numKBytes = (double) bytes / 1024; if (numKBytes < 1024) - s << numKBytes << " " << tr("KiB"); + s << numKBytes << " " << tr(/* tr: Kibibit */ "KiB"); else if (numKBytes < 1024 * 1024) - s << numKBytes / 1024 << " " << tr("MiB"); + s << numKBytes / 1024 << " " << tr(/* tr: Mebibit */ "MiB"); else - s << numKBytes / 1024 / 1024 << " " << tr("GiB"); + s << numKBytes / 1024 / 1024 << " " << tr(/* tr: Gibibit */ "GiB"); } static void ShowTunnelDetails (std::stringstream& s, enum i2p::tunnel::TunnelState eState, bool explr, int bytes) @@ -185,7 +152,7 @@ namespace http { else stateText = tr("unknown"); s << " " << stateText << ((explr) ? " (" + tr("exploratory") + ")" : "") << ", "; - s << " " << (int) (bytes / 1024) << " " << tr("KiB") << "\r\n"; + s << " " << (int) (bytes / 1024) << " " << tr(/* tr: Kibibit */ "KiB") << "\r\n"; } static void SetLogLevel (const std::string& level) @@ -204,9 +171,9 @@ namespace http { std::string webroot; i2p::config::GetOption("http.webroot", webroot); // Page language - std::string lang, langCode; i2p::config::GetOption("http.lang", lang); - if (lang == "russian") langCode = "ru"; - else langCode = "en"; + std::string currLang = i2p::client::context.GetLanguage ()->GetLanguage(); // get current used language + auto it = i2p::i18n::languages.find(currLang); + std::string langCode = it->second.ShortCode; s << "\r\n" @@ -223,18 +190,18 @@ namespace http { "
" << tr("i2pd webconsole") << "
\r\n" "
\r\n" "\r\n" "
"; @@ -319,13 +286,13 @@ namespace http { s << "" << tr("Tunnel creation success rate") << ": " << i2p::tunnel::tunnels.GetTunnelCreationSuccessRate () << "%
\r\n"; s << "" << tr("Received") << ": "; ShowTraffic (s, i2p::transport::transports.GetTotalReceivedBytes ()); - s << " (" << (double) i2p::transport::transports.GetInBandwidth () / 1024 << " " << tr("KiB/s") << ")
\r\n"; + s << " (" << (double) i2p::transport::transports.GetInBandwidth () / 1024 << " " << tr(/* tr: Kibibit/s */ "KiB/s") << ")
\r\n"; s << "" << tr("Sent") << ": "; ShowTraffic (s, i2p::transport::transports.GetTotalSentBytes ()); - s << " (" << (double) i2p::transport::transports.GetOutBandwidth () / 1024 << " " << tr("KiB/s") << ")
\r\n"; + s << " (" << (double) i2p::transport::transports.GetOutBandwidth () / 1024 << " " << tr(/* tr: Kibibit/s */ "KiB/s") << ")
\r\n"; s << "" << tr("Transit") << ": "; ShowTraffic (s, i2p::transport::transports.GetTotalTransitTransmittedBytes ()); - s << " (" << (double) i2p::transport::transports.GetTransitBandwidth () / 1024 << " " << tr("KiB/s") << ")
\r\n"; + s << " (" << (double) i2p::transport::transports.GetTransitBandwidth () / 1024 << " " << tr(/* tr: Kibibit/s */ "KiB/s") << ")
\r\n"; s << "" << tr("Data path") << ": " << i2p::fs::GetUTF8DataDir() << "
\r\n"; s << "
"; if((outputFormat == OutputFormatEnum::forWebConsole) || !includeHiddenContent) { @@ -389,14 +356,19 @@ namespace http { s << "" << tr("Transit Tunnels") << ": " << std::to_string(transitTunnelCount) << "
\r\n
\r\n"; if(outputFormat==OutputFormatEnum::forWebConsole) { - bool i2pcontrol; i2p::config::GetOption("i2pcontrol.enabled", i2pcontrol); + bool httpproxy = i2p::client::context.GetHttpProxy () ? true : false; + bool socksproxy = i2p::client::context.GetSocksProxy () ? true : false; + bool bob = i2p::client::context.GetBOBCommandChannel () ? true : false; + bool sam = i2p::client::context.GetSAMBridge () ? true : false; + bool i2cp = i2p::client::context.GetI2CPServer () ? true : false; + bool i2pcontrol; i2p::config::GetOption("i2pcontrol.enabled", i2pcontrol); s << "\r\n"; - s << "\r\n"; - s << "\r\n"; - s << "\r\n"; - s << "\r\n"; - s << "\r\n"; - s << "\r\n"; + s << "\r\n"; + s << "\r\n"; + s << "\r\n"; + s << "\r\n"; + s << "\r\n"; + s << "\r\n"; s << "
" << tr("Services") << "
" << "HTTP " << tr("Proxy") << "
" << "SOCKS " << tr("Proxy") << "
" << "BOB" << "
" << "SAM" << "
" << "I2CP" << "
" << "I2PControl" << "
" << "HTTP " << tr("Proxy") << "" << (httpproxy ? tr("Enabled") : tr("Disabled")) << "
" << "SOCKS " << tr("Proxy") << "" << (socksproxy ? tr("Enabled") : tr("Disabled")) << "
" << "BOB" << "" << (bob ? tr("Enabled") : tr("Disabled")) << "
" << "SAM" << "" << (sam ? tr("Enabled") : tr("Disabled")) << "
" << "I2CP" << "" << (i2cp ? tr("Enabled") : tr("Disabled")) << "
" << "I2PControl" << "" << (i2pcontrol ? tr("Enabled") : tr("Disabled")) << "
\r\n"; } } @@ -476,7 +448,7 @@ namespace http { s << "
"; it->Print(s); if(it->LatencyIsKnown()) - s << " ( " << it->GetMeanLatency() << tr("ms") << " )"; + s << " ( " << it->GetMeanLatency() << tr(/* tr: Milliseconds */ "ms") << " )"; ShowTunnelDetails(s, it->GetState (), false, it->GetNumReceivedBytes ()); s << "
\r\n"; } @@ -486,7 +458,7 @@ namespace http { s << "
"; it->Print(s); if(it->LatencyIsKnown()) - s << " ( " << it->GetMeanLatency() << tr("ms") << " )"; + s << " ( " << it->GetMeanLatency() << tr("ms") << " )"; ShowTunnelDetails(s, it->GetState (), false, it->GetNumSentBytes ()); s << "
\r\n"; } @@ -534,7 +506,8 @@ namespace http { if (dest) { ShowLeaseSetDestination (s, dest, token); - // show streams + + // Print table with streams information s << "\r\n\r\n\r\n"; s << ""; s << "
" << tr("Streams") << "
StreamID"; // Stream closing button column @@ -679,26 +652,31 @@ namespace http { static void ShowCommands (std::stringstream& s, uint32_t token) { std::string webroot; i2p::config::GetOption("http.webroot", webroot); - /* commands */ + s << "" << tr("Router commands") << "
\r\n
\r\n
\r\n"; - s << " " << tr("Run peer test") << "\r\n"; - //s << " Reload config
\r\n"; + s << " " << tr("Run peer test") << "
\r\n"; + + // s << " Reload config
\r\n"; + if (i2p::context.AcceptsTunnels ()) - s << " " << tr("Decline transit tunnels") << "\r\n"; + s << " " << tr("Decline transit tunnels") << "
\r\n"; else - s << " " << tr("Accept transit tunnels") << "\r\n"; + s << " " << tr("Accept transit tunnels") << "
\r\n"; + #if ((!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) || defined(ANDROID_BINARY)) if (Daemon.gracefulShutdownInterval) - s << " " << tr("Cancel graceful shutdown") << "\r\n"; + s << " " << tr("Cancel graceful shutdown") << "
\r\n"; else s << " " << tr("Start graceful shutdown") << "
\r\n"; #elif defined(WIN32_APP) if (i2p::util::DaemonWin32::Instance().isGraceful) - s << " " << tr("Cancel graceful shutdown") << "\r\n"; + s << " " << tr("Cancel graceful shutdown") << "
\r\n"; else - s << " " << tr("Start graceful shutdown") << "\r\n"; + s << " " << tr("Start graceful shutdown") << "
\r\n"; #endif - s << " " << tr("Force shutdown") << "\r\n"; + + s << " " << tr("Force shutdown") << "

\r\n"; + s << " " << tr("Reload external CSS styles") << "\r\n"; s << "
"; s << "
\r\n" << tr("Note: any action done here are not persistent and not changes your config files.") << "\r\n
\r\n"; @@ -718,13 +696,26 @@ namespace http { s << " \r\n"; 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"; + s << " \r\n"; + s << "
\r\n
\r\n"; + } void ShowTransitTunnels (std::stringstream& s) { if(i2p::tunnel::tunnels.CountTransitTunnels()) { - s << "" << tr("Transit tunnels") << ":
\r\n
\r\n"; + s << "" << tr("Transit Tunnels") << ":
\r\n
\r\n"; for (const auto& it: i2p::tunnel::tunnels.GetTransitTunnels ()) { s << "
\r\n"; @@ -740,7 +731,7 @@ namespace http { } else { - s << "" << tr("Transit tunnels") << ": " << tr("no transit tunnels currently built") << ".
\r\n"; + s << "" << tr("Transit Tunnels") << ": " << tr("no transit tunnels currently built") << ".
\r\n"; } } @@ -979,16 +970,6 @@ namespace http { } } - std::string ConvertTime (uint64_t time) - { - lldiv_t divTime = lldiv(time, 1000); - time_t t = divTime.quot; - struct tm *tm = localtime(&t); - char date[128]; - snprintf(date, sizeof(date), "%02d/%02d/%d %02d:%02d:%02d.%03lld", tm->tm_mday, tm->tm_mon + 1, tm->tm_year + 1900, tm->tm_hour, tm->tm_min, tm->tm_sec, divTime.rem); - return date; - } - HTTPConnection::HTTPConnection (std::string hostname, std::shared_ptr socket): m_Socket (socket), m_BufferLen (0), expected_host(hostname) { @@ -1115,6 +1096,8 @@ namespace http { SendReply (res, content); } + std::map HTTPConnection::m_Tokens; + uint32_t HTTPConnection::CreateToken () { uint32_t token; @@ -1260,7 +1243,7 @@ namespace http { s << "" << tr("ERROR") << ": " << tr("StreamID can't be null") << "
\r\n
\r\n"; s << "" << tr("Return to destination page") << "
\r\n"; - s << "

" << tr("You will be redirected back in 5 seconds") << ""; + s << "

" << tr("You will be redirected in 5 seconds") << ""; redirect = "5; url=" + webroot + "?page=local_destination&b32=" + b32; res.add_header("Refresh", redirect.c_str()); return; @@ -1273,7 +1256,7 @@ namespace http { else { s << "" << tr("ERROR") << ": " << tr("Transit tunnels count must not exceed 65535") << "\r\n
\r\n
\r\n"; s << "" << tr("Back to commands list") << "\r\n
\r\n"; - s << "

" << tr("You will be redirected back in 5 seconds") << ""; + s << "

" << tr("You will be redirected in 5 seconds") << ""; res.add_header("Refresh", redirect.c_str()); return; } @@ -1281,7 +1264,7 @@ namespace http { else if (cmd == HTTP_COMMAND_GET_REG_STRING) { std::string b32 = params["b32"]; - std::string name = params["name"]; + std::string name = i2p::http::UrlDecode(params["name"]); i2p::data::IdentHash ident; ident.FromBase32 (b32); @@ -1327,6 +1310,18 @@ namespace http { s << "" << tr("Return to destination page") << "\r\n"; return; } + else if (cmd == HTTP_COMMAND_SETLANGUAGE) + { + std::string lang = params["lang"]; + std::string currLang = i2p::client::context.GetLanguage ()->GetLanguage(); + + if (currLang.compare(lang) != 0) + i2p::i18n::SetLanguage(lang); + } + else if (cmd == HTTP_COMMAND_RELOAD_CSS) + { + LoadExtCSS(); + } else { res.code = 400; @@ -1389,6 +1384,8 @@ namespace http { m_Thread.reset (new std::thread (std::bind (&HTTPServer::Run, this))); m_Acceptor.listen (); Accept (); + + LoadExtCSS(); } void HTTPServer::Stop () diff --git a/daemon/HTTPServerResources.h b/daemon/HTTPServerResources.h new file mode 100644 index 00000000..89c742d3 --- /dev/null +++ b/daemon/HTTPServerResources.h @@ -0,0 +1,98 @@ +/* +* Copyright (c) 2013-2021, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#ifndef HTTP_SERVER_RESOURCES_H__ +#define HTTP_SERVER_RESOURCES_H__ + +namespace i2p +{ +namespace http +{ + const std::string itoopieFavicon = + "data:image/png;base64," + "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACx" + "jwv8YQUAAAAJcEhZcwAALiIAAC4iAari3ZIAAAAHdElNRQfgCQsUNSZrkhi1AAAAGXRFWHRTb2Z0" + "d2FyZQBwYWludC5uZXQgNC4wLjEyQwRr7AAAAoJJREFUOE9jwAUqi4Q1oEwwcDTV1+5sETaBclGB" + "vb09C5QJB6kWpvFQJoOCeLC5kmjEHCgXE2SlyETLi3h6QrkM4VL+ssWSCZUgtopITLKqaOotRTEn" + "cbAkLqAkGtOqLBLVAWLXyWSVFkkmRiqLxuaqiWb/VBYJMAYrwgckJY25VEUzniqKhjU2y+RtCRSP" + "6lUXy/1jIBV5tlYxZUaFVMq2NInwIi9hO8fSfOEAqDZUoCwal6MulvOvyS7gi69K4j9zxZT/m0ps" + "/28ptvvvquXXryIa7QYMMdTwqi0WNtVi0GIDseXl7TnUxFKfnGlxAGp0+D8j2eH/8Ub7/9e7nf7X" + "+Af/B7rwt6pI0h0l0WhQADOC9DBkhSirpImHNVZKp24ukkyoshGLnN8d5fA/y13t/44Kq/8hlnL/" + "z7fZ/58f6vcxSNpbVUVFhV1RLNBVTsQzVYZPSwhsCAhkiIfpNMrkbO6TLf071Sfk/5ZSi/+7q6z/" + "P5ns+v9mj/P/CpuI/20y+aeNGYxZoVoYGmsF3aFMBAAZlCwftnF9ke3//bU2//fXWP8/UGv731Am" + "+V+DdNblSqnUYqhSTKAiYSOqJBrVqiaa+S3UNPr/gmyH/xuKXf63hnn/B8bIP0UxHfEyyeSNQKVM" + "EB1AEB2twhcTLp+gIBJUoyKasEpVJHmqskh8qryovUG/ffCHHRU2q/Tk/YuB6eGPsbExa7ZkpLu1" + "oLEcVDtuUCgV1w60rQzElpRUE1EVSX0BYidHiInXF4nagNhYQW60EF+ApH1ktni0A1SIITSUgVlZ" + "JHYnlIsfzJjIp9xZKswL5YKBHL+coKJoRDaUSzoozxHVrygQU4JykQADAwAT5b1NHtwZugAAAABJ" + "RU5ErkJggg=="; + + // bundled style sheet + const std::string internalCSS = + "\r\n"; + + // for external style sheet + std::string externalCSS; + +} // http +} // i2p + +#endif /* HTTP_SERVER_RESOURCES_H__ */ diff --git a/daemon/I2PControl.cpp b/daemon/I2PControl.cpp index 12602c99..73dde618 100644 --- a/daemon/I2PControl.cpp +++ b/daemon/I2PControl.cpp @@ -406,7 +406,7 @@ namespace client void I2PControlService::UptimeHandler (std::ostringstream& results) { - InsertParam (results, "i2p.router.uptime", (int)i2p::context.GetUptime ()*1000); + InsertParam (results, "i2p.router.uptime", std::to_string (i2p::context.GetUptime ()*1000LL)); } void I2PControlService::VersionHandler (std::ostringstream& results) diff --git a/debian/changelog b/debian/changelog index 777b6216..c675f263 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +i2pd (2.39.0-1) unstable; urgency=medium + + * updated to version 2.39.0/0.9.51 + + -- orignal Mon, 23 Aug 2021 16:00:00 +0000 + i2pd (2.38.0-1) unstable; urgency=medium * updated to version 2.38.0/0.9.50 diff --git a/debian/compat b/debian/compat index 9a037142..ec635144 100644 --- a/debian/compat +++ b/debian/compat @@ -1 +1 @@ -10 \ No newline at end of file +9 diff --git a/debian/control b/debian/control index 843a153c..318463bc 100644 --- a/debian/control +++ b/debian/control @@ -3,30 +3,16 @@ Section: net Priority: optional Maintainer: r4sas Build-Depends: debhelper (>= 9), dpkg-dev (>= 1.17.2~), gcc (>= 4.7) | clang (>= 3.3), libboost-system-dev (>= 1.46), libboost-date-time-dev (>= 1.46), libboost-filesystem-dev (>= 1.46), libboost-program-options-dev (>= 1.46), libminiupnpc-dev, libssl-dev, zlib1g-dev -Standards-Version: 3.9.6 +Standards-Version: 3.9.8 Homepage: http://i2pd.website/ Vcs-Git: git://github.com/PurpleI2P/i2pd.git Vcs-Browser: https://github.com/PurpleI2P/i2pd Package: i2pd Architecture: any -Pre-Depends: adduser +Pre-Depends: ${misc:Pre-Depends}, adduser Depends: ${shlibs:Depends}, ${misc:Depends}, lsb-base, Description: Full-featured C++ implementation of I2P client. I2P (Invisible Internet Protocol) is a universal anonymous network layer. All communications over I2P are anonymous and end-to-end encrypted, participants don't reveal their real IP addresses. - . - This package contains the full-featured C++ implementation of I2P router. - -Package: i2pd-dbg -Architecture: any -Priority: extra -Section: debug -Depends: i2pd (= ${binary:Version}), ${misc:Depends} -Description: i2pd debugging symbols - I2P (Invisible Internet Protocol) is a universal anonymous network layer. All - communications over I2P are anonymous and end-to-end encrypted, participants - don't reveal their real IP addresses. - . - This package contains symbols required for debugging. diff --git a/debian/copyright b/debian/copyright index 9f18f53a..73df9da0 100644 --- a/debian/copyright +++ b/debian/copyright @@ -6,13 +6,6 @@ Files: * Copyright: 2013-2020 PurpleI2P License: BSD-3-clause -Files: qt/i2pd_qt/android/src/org/kde/necessitas/ministro/IMinistro.aidl - qt/i2pd_qt/android/src/org/kde/necessitas/ministro/IMinistroCallback.aidl - qt/i2pd_qt/android/src/org/qtproject/qt5/android/bindings/QtActivity.java - qt/i2pd_qt/android/src/org/qtproject/qt5/android/bindings/QtApplication.java -Copyright: 2011-2013 BogDan Vatra -License: BSD-2-Clause - Files: debian/* Copyright: 2013-2015 Kill Your TV 2014-2016 hagen @@ -49,28 +42,6 @@ License: BSD-3-clause TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -License: BSD-2-Clause - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - 1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - . - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE HOLDERS OR - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - License: GPL-2+ This package is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/debian/docs b/debian/docs index dfa6f572..b43bf86b 100644 --- a/debian/docs +++ b/debian/docs @@ -1,5 +1 @@ README.md -contrib/i2pd.conf -contrib/subscriptions.txt -contrib/tunnels.conf -contrib/tunnels.d diff --git a/debian/i2pd.dirs b/debian/i2pd.dirs deleted file mode 100644 index 3b643352..00000000 --- a/debian/i2pd.dirs +++ /dev/null @@ -1,2 +0,0 @@ -etc/i2pd -var/lib/i2pd diff --git a/debian/i2pd.install b/debian/i2pd.install index d20b2c17..6eb6c8c2 100644 --- a/debian/i2pd.install +++ b/debian/i2pd.install @@ -1,5 +1,5 @@ i2pd usr/sbin/ -contrib/i2pd.conf etc/i2pd/ +contrib/i2pd.conf etc/i2pd/ contrib/tunnels.conf etc/i2pd/ contrib/subscriptions.txt etc/i2pd/ contrib/certificates/ usr/share/i2pd/ diff --git a/debian/patches/02-upnp.patch b/debian/patches/02-upnp.patch index 7b9bb317..99698737 100644 --- a/debian/patches/02-upnp.patch +++ b/debian/patches/02-upnp.patch @@ -2,16 +2,16 @@ Description: Enable UPnP usage in package Author: r4sas Reviewed-By: r4sas -Last-Update: 2021-01-16 +Last-Update: 2021-10-22 --- i2pd.orig/Makefile +++ i2pd/Makefile -@@ -15,7 +15,7 @@ include filelist.mk - USE_AESNI := yes - USE_STATIC := no - USE_MESHNET := no --USE_UPNP := no -+USE_UPNP := yes - DEBUG := yes +@@ -32,7 +32,7 @@ include filelist.mk + USE_AESNI := $(or $(USE_AESNI),yes) + USE_STATIC := $(or $(USE_STATIC),no) + USE_MESHNET := $(or $(USE_MESHNET),no) +-USE_UPNP := $(or $(USE_UPNP),no) ++USE_UPNP := $(or $(USE_UPNP),yes) + DEBUG := $(or $(DEBUG),yes) - ifeq ($(DEBUG),yes) + # for debugging purposes only, when commit hash needed in trunk builds in i2pd version string diff --git a/debian/postinst b/debian/postinst index 9c9e74ae..720753fd 100755 --- a/debian/postinst +++ b/debian/postinst @@ -12,7 +12,6 @@ case "$1" in # Create user and group as a system user. if getent passwd $I2PDUSER > /dev/null 2>&1; then groupadd -f $I2PDUSER || true - usermod -s "/bin/false" -e 1 $I2PDUSER > /dev/null || true else adduser --system --quiet --group --home $I2PDHOME $I2PDUSER fi @@ -23,7 +22,7 @@ case "$1" in chmod 640 $LOGFILE chown -f ${I2PDUSER}:adm $LOGFILE mkdir -p -m0750 $I2PDHOME - chown -f -R -P ${I2PDUSER}:${I2PDUSER} ${I2PDHOME} + chown -f -P ${I2PDUSER}:${I2PDUSER} ${I2PDHOME} ;; abort-upgrade|abort-remove|abort-deconfigure) echo "Aborting upgrade" diff --git a/debian/rules b/debian/rules index 77ecd7b7..24a44f55 100755 --- a/debian/rules +++ b/debian/rules @@ -1,22 +1,16 @@ #!/usr/bin/make -f -# -*- makefile -*- - -# Uncomment this to turn on verbose mode. #export DH_VERBOSE=1 -DEB_BUILD_MAINT_OPTIONS=hardening=+bindnow -#DPKG_EXPORT_BUILDFLAGS = 1 -#include /usr/share/dpkg/buildflags.mk -#CXXFLAGS+=$(CPPFLAGS) -#PREFIX=/usr + +export DEB_BUILD_MAINT_OPTIONS = hardening=+all + + +include /usr/share/dpkg/architecture.mk + +export DEB_CXXFLAGS_MAINT_APPEND = -Wall -pedantic -O3 + +export DEB_LDFLAGS_MAINT_APPEND = + %: dh $@ --parallel -# dh_apparmor --profile-name=usr.sbin.i2pd -pi2pd - -override_dh_strip: - dh_strip --dbg-package=i2pd-dbg - -## uncomment this if you have "missing info" problem when building package -#override_dh_shlibdeps: -# dh_shlibdeps --dpkg-shlibdeps-params=--ignore-missing-info diff --git a/debian/watch b/debian/watch index 55cda021..64367c81 100644 --- a/debian/watch +++ b/debian/watch @@ -1,3 +1,3 @@ -version=3 -opts=filenamemangle=s/.+\/v?(\d\S*)\.tar\.gz/i2pd-$1\.tar\.gz/ \ - https://github.com/PurpleI2P/i2pd/tags .*/v?(\d\S*)\.tar\.gz +version=4 opts="filenamemangle=s%(?:.*?)?v?(\d[\d.]*)\.tar\.gz%i2pd-$1.tar.gz%" \ + https://github.com/PurpleI2P/i2pd/tags \ + (?:.*?/)?(\d[\d.]*)\.tar\.gz debian uupdate diff --git a/filelist.mk b/filelist.mk index e2a5d40e..d8f503e6 100644 --- a/filelist.mk +++ b/filelist.mk @@ -21,4 +21,6 @@ LIB_CLIENT_SRC = $(wildcard $(LIB_CLIENT_SRC_DIR)/*.cpp) LANG_SRC = $(wildcard $(LANG_SRC_DIR)/*.cpp) +WRAP_LIB_SRC = $(wildcard $(WRAP_SRC_DIR)/*.cpp) + DAEMON_SRC = $(wildcard $(DAEMON_SRC_DIR)/*.cpp) diff --git a/i18n/Afrikaans.cpp b/i18n/Afrikaans.cpp new file mode 100644 index 00000000..5860facf --- /dev/null +++ b/i18n/Afrikaans.cpp @@ -0,0 +1,81 @@ +/* +* Copyright (c) 2021, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#include +#include +#include +#include +#include "I18N.h" + +// Afrikaans localization file + +namespace i2p +{ +namespace i18n +{ +namespace afrikaans // language namespace +{ + // language name in lowercase + static std::string language = "afrikaans"; + + // See for language plural forms here: + // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html + static int plural (int n) { + return n != 1 ? 1 : 0; + } + + static std::map strings + { + {"failed", "Het misluk"}, + {"unknown", "onbekend"}, + {"Tunnels", "Tonnels"}, + {"I2P tunnels", "I2P tonnels"}, + {"SAM sessions", "SAM sessies"}, + {"OK", "LEKKER"}, + {"Testing", "Besig om te toets"}, + {"Firewalled", "Vuurmuur'd"}, + {"Unknown", "Onbekend"}, + {"Error", "Fout"}, + {"Offline", "Aflyn"}, + {"Uptime", "Optyd"}, + {"Network status", "Netwerk status"}, + {"Network status v6", "Netwerk status v6"}, + {"Family", "Familie"}, + {"Received", "Ontvang"}, + {"Sent", "Gestuur"}, + {"Hidden content. Press on text to see.", "Hidden content. Druk om te sien."}, + {"Router Ident", "Router Ident"}, + {"Router Family", "Router Familie"}, + {"Enabled", "Geaktiveer"}, + {"Disabled", "Gedeaktiveer"}, + {"Change", "Verander"}, + {"Change language", "Verander taal"}, + {"Description", "Beskrywing"}, + {"Submit", "Stuur"}, + {"Proxy error", "Proxy-fout"}, + {"Host", "Gasheer"}, + {"", ""}, + }; + + static std::map> plurals + { + {"days", {"dag", "dae"}}, + {"hours", {"uur", "ure"}}, + {"minutes", {"minuut", "minute"}}, + {"seconds", {"seconde", "sekondes"}}, + {"", {"", ""}}, + }; + + std::shared_ptr GetLocale() + { + return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); + } + +} // language +} // i18n +} // i2p diff --git a/i18n/English.cpp b/i18n/English.cpp index 8b13279a..2670e984 100644 --- a/i18n/English.cpp +++ b/i18n/English.cpp @@ -13,13 +13,17 @@ #include "I18N.h" // English localization file +// This is an example translation file without strings in it. namespace i2p { namespace i18n { -namespace english // language +namespace english // language namespace { + // language name in lowercase + static std::string language = "english"; + // See for language plural forms here: // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html static int plural (int n) { @@ -33,16 +37,12 @@ namespace english // language static std::map> plurals { - {"days", {"day", "days"}}, - {"hours", {"hour", "hours"}}, - {"minutes", {"minute", "minutes"}}, - {"seconds", {"second", "seconds"}}, {"", {"", ""}}, }; std::shared_ptr GetLocale() { - return std::make_shared(strings, plurals, [] (int n)->int { return plural(n); }); + return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); } } // language diff --git a/i18n/I18N.h b/i18n/I18N.h index 7d0baf1a..5024fb56 100644 --- a/i18n/I18N.h +++ b/i18n/I18N.h @@ -9,7 +9,7 @@ #ifndef __I18N_H__ #define __I18N_H__ -#include "RouterContext.h" +#include "ClientContext.h" namespace i2p { @@ -17,24 +17,21 @@ namespace i18n { inline void SetLanguage(const std::string &lang) { - if (!lang.compare("russian")) - i2p::context.SetLanguage (i2p::i18n::russian::GetLocale()); - else if (!lang.compare("turkmen")) - i2p::context.SetLanguage (i2p::i18n::turkmen::GetLocale()); - else if (!lang.compare("ukrainian")) - i2p::context.SetLanguage (i2p::i18n::ukrainian::GetLocale()); - else // fallback - i2p::context.SetLanguage (i2p::i18n::english::GetLocale()); + const auto it = i2p::i18n::languages.find(lang); + if (it == i2p::i18n::languages.end()) // fallback + i2p::client::context.SetLanguage (i2p::i18n::english::GetLocale()); + else + i2p::client::context.SetLanguage (it->second.LocaleFunc()); } inline std::string translate (const std::string& arg) { - return i2p::context.GetLanguage ()->GetString (arg); + return i2p::client::context.GetLanguage ()->GetString (arg); } - inline std::string translate (const std::string& arg, const int& n) + inline std::string translate (const std::string& arg, const std::string& arg2, const int& n) { - return i2p::context.GetLanguage ()->GetPlural (arg, n); + return i2p::client::context.GetLanguage ()->GetPlural (arg, arg2, n); } } // i18n } // i2p diff --git a/i18n/I18N_langs.h b/i18n/I18N_langs.h index 435141bf..559b17be 100644 --- a/i18n/I18N_langs.h +++ b/i18n/I18N_langs.h @@ -17,10 +17,17 @@ namespace i18n { public: Locale ( + const std::string& language, const std::map& strings, const std::map>& plurals, std::function formula - ): m_Strings (strings), m_Plurals (plurals), m_Formula (formula) { }; + ): m_Language (language), m_Strings (strings), m_Plurals (plurals), m_Formula (formula) { }; + + // Get activated language name for webconsole + std::string GetLanguage() const + { + return m_Language; + } std::string GetString (const std::string& arg) const { @@ -35,12 +42,12 @@ namespace i18n } } - std::string GetPlural (const std::string& arg, const int& n) const + std::string GetPlural (const std::string& arg, const std::string& arg2, const int& n) const { - const auto it = m_Plurals.find(arg); - if (it == m_Plurals.end()) + const auto it = m_Plurals.find(arg2); + if (it == m_Plurals.end()) // not found, fallback to english { - return arg; + return n == 1 ? arg : arg2; } else { @@ -50,16 +57,39 @@ namespace i18n } private: + const std::string m_Language; const std::map m_Strings; const std::map> m_Plurals; std::function m_Formula; }; + struct langData + { + std::string LocaleName; // localized name + std::string ShortCode; // short language code, like "en" + std::function (void)> LocaleFunc; + }; + // Add localization here with language name as namespace - namespace english { std::shared_ptr GetLocale (); } - namespace russian { std::shared_ptr GetLocale (); } - namespace turkmen { std::shared_ptr GetLocale (); } + namespace afrikaans { std::shared_ptr GetLocale (); } + namespace english { std::shared_ptr GetLocale (); } + namespace russian { std::shared_ptr GetLocale (); } + namespace turkmen { std::shared_ptr GetLocale (); } namespace ukrainian { std::shared_ptr GetLocale (); } + namespace uzbek { std::shared_ptr GetLocale (); } + + /** + * That map contains international language name lower-case and name in it's language + */ + static std::map languages + { + { "afrikaans", {"Afrikaans", "af", i2p::i18n::afrikaans::GetLocale} }, + { "english", {"English", "en", i2p::i18n::english::GetLocale} }, + { "russian", {"русский язык", "ru", i2p::i18n::russian::GetLocale} }, + { "turkmen", {"türkmen dili", "tk", i2p::i18n::turkmen::GetLocale} }, + { "ukrainian", {"украї́нська мо́ва", "uk", i2p::i18n::ukrainian::GetLocale} }, + { "uzbek", {"Oʻzbek", "uz", i2p::i18n::uzbek::GetLocale} }, + }; } // i18n } // i2p diff --git a/i18n/Russian.cpp b/i18n/Russian.cpp index 7df82d54..f6a19c7c 100644 --- a/i18n/Russian.cpp +++ b/i18n/Russian.cpp @@ -18,8 +18,11 @@ namespace i2p { namespace i18n { -namespace russian // language +namespace russian // language namespace { + // language name in lowercase + static std::string language = "russian"; + // See for language plural forms here: // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html static int plural (int n) { @@ -28,23 +31,148 @@ namespace russian // language static std::map strings { - // HTTP Proxy + {"KiB", "КиБ"}, + {"MiB", "МиБ"}, + {"GiB", "ГиБ"}, + {"building", "строится"}, + {"failed", "неудачный"}, + {"expiring", "истекает"}, + {"established", "работает"}, + {"unknown", "неизвестно"}, + {"exploratory", "исследовательский"}, + {"i2pd webconsole", "Веб-консоль i2pd"}, + {"Main page", "Главная"}, + {"Router commands", "Команды роутера"}, + {"Local Destinations", "Локальные назначения"}, + {"LeaseSets", "Лизсеты"}, + {"Tunnels", "Туннели"}, + {"Transit Tunnels", "Транзитные туннели"}, + {"Transports", "Транспорты"}, + {"I2P tunnels", "I2P туннели"}, + {"SAM sessions", "SAM сессии"}, + {"ERROR", "ОШИБКА"}, + {"OK", "OK"}, + {"Testing", "Тестирование"}, + {"Firewalled", "Заблокировано извне"}, + {"Unknown", "Неизвестно"}, + {"Proxy", "Прокси"}, + {"Mesh", "MESH-сеть"}, + {"Error", "Ошибка"}, + {"Clock skew", "Не точное время"}, + {"Offline", "Оффлайн"}, + {"Symmetric NAT", "Симметричный NAT"}, + {"Uptime", "В сети"}, + {"Network status", "Сетевой статус"}, + {"Network status v6", "Сетевой статус v6"}, + {"Stopping in", "Остановка через"}, + {"Family", "Семейство"}, + {"Tunnel creation success rate", "Успешно построенных туннелей"}, + {"Received", "Получено"}, + {"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", "Не истекшие Lease-ы"}, + {"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", "Вы можете попробовать найти адрес на джамп сервисах ниже"}, + {"Proxy error: Host not found", "Ошибка прокси: Узел не найден"}, + {"Remote host not found in router's addressbook", "Запрошенный узел не найден в адресной книге роутера"}, + {"You may try to find this host on jump services below", "Вы можете попробовать найти узел через джамп сервисы ниже"}, {"Invalid request", "Некорректный запрос"}, {"Proxy unable to parse your request", "Прокси не может разобрать ваш запрос"}, {"addresshelper is not supported", "addresshelper не поддерживается"}, - {"Host", "Адрес"}, + {"Host", "Узел"}, {"added to router's addressbook from helper", "добавлен в адресную книгу роутера через хелпер"}, - {"already in router's addressbook", "уже в адресной книге роутера"}, - {"Click", "Нажмите"}, - {"here", "здесь"}, - {"to proceed", "чтобы продолжить"}, - {"to update record", "чтобы обновить запись"}, + {"Click here to proceed:", "Нажмите здесь, чтобы продолжить:"}, + {"Continue", "Продолжить"}, {"Addresshelper found", "Найден addresshelper"}, + {"already in router's addressbook", "уже в адресной книге роутера"}, + {"Click here to update record:", "Нажмите здесь, чтобы обновить запись:"}, {"invalid request uri", "некорректный URI запроса"}, {"Can't detect destination host from request", "Не удалось определить адрес назначения из запроса"}, {"Outproxy failure", "Ошибка внешнего прокси"}, @@ -63,169 +191,13 @@ namespace russian // language {"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.", - "Не удалось установить соединение к запрошенному адресу, возможно он не в сети. Попробуйте повторить запрос позже."}, - - // Webconsole // - // cssStyles - {"Disabled", "Выключено"}, - {"Enabled", "Включено"}, - // ShowTraffic - {"KiB", "КиБ"}, - {"MiB", "МиБ"}, - {"GiB", "ГиБ"}, - // ShowTunnelDetails - {"building", "строится"}, - {"failed", "неудачный"}, - {"expiring", "истекает"}, - {"established", "работает"}, - {"exploratory", "исследовательский"}, - {"unknown", "неизвестно"}, - {"i2pd webconsole", "Веб-консоль i2pd"}, - // ShowPageHead - {"Main page", "Главная"}, - {"Router commands", "Команды роутера"}, - {"Local destinations", "Локальные назнач."}, - {"LeaseSets", "Лизсеты"}, - {"Tunnels", "Туннели"}, - {"Transit tunnels", "Транзит. туннели"}, - {"Transports", "Транспорты"}, - {"I2P tunnels", "I2P туннели"}, - {"SAM sessions", "SAM сессии"}, - // Network Status - {"OK", "OK"}, - {"Testing", "Тестирование"}, - {"Firewalled", "Заблокировано извне"}, - {"Unknown", "Неизвестно"}, - {"Proxy", "Прокси"}, - {"Mesh", "MESH-сеть"}, - {"Error", "Ошибка"}, - {"Clock skew", "Не точное время"}, - {"Offline", "Оффлайн"}, - {"Symmetric NAT", "Симметричный NAT"}, - // Status - {"Uptime", "В сети"}, - {"Network status", "Сетевой статус"}, - {"Network status v6", "Сетевой статус v6"}, - {"Stopping in", "Остановка через"}, - {"Family", "Семейство"}, - {"Tunnel creation success rate", "Успешно построенных туннелей"}, - {"Received", "Получено"}, - {"Sent", "Отправлено"}, - {"Transit", "Транзит"}, - {"KiB/s", "КиБ/с"}, - {"Data path", "Путь к данным"}, - {"Hidden content. Press on text to see.", "Скрытый контент. Нажмите на текст чтобы отобразить."}, - {"Router Ident", "Идентификатор роутера"}, - {"Router Family", "Семейство роутера"}, - {"Router Caps", "Флаги роутера"}, - {"Version", "Версия"}, - {"Our external address", "Наш внешний адрес"}, - {"supported", "поддерживается"}, - {"Routers", "Роутеры"}, - {"Floodfills", "Флудфилы"}, - {"LeaseSets", "Лизсеты"}, - {"Client Tunnels", "Клиентские туннели"}, - {"Transit Tunnels", "Транзитные туннели"}, - {"Services", "Сервисы"}, - // ShowLocalDestinations - {"Local Destinations", "Локальные назначения"}, - // ShowLeaseSetDestination - {"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.", - "Примечание: полученная строка может быть использована только для регистрации доменов второго уровня. Для регистрации поддоменов используйте i2pd-tools."}, - {"Address", "Адрес"}, - {"Type", "Тип"}, - {"EncType", "ТипШифр"}, - {"Inbound tunnels", "Входящие туннели"}, - {"Outbound tunnels", "Исходящие туннели"}, - {"ms", "мс"}, // milliseconds - {"Tags", "Теги"}, - {"Incoming", "Входящие"}, - {"Outgoing", "Исходящие"}, - {"Destination", "Назначение"}, - {"Amount", "Количество"}, - {"Incoming Tags", "Входящие Теги"}, - {"Tags sessions", "Сессии Тегов"}, - {"Status", "Статус"}, - // ShowLocalDestination - {"Local Destination", "Локальное назначение"}, - {"Streams", "Стримы"}, - {"Close stream", "Закрыть стрим"}, - // ShowI2CPLocalDestination - {"I2CP session not found", "I2CP сессия не найдена"}, - {"I2CP is not enabled", "I2CP не включен"}, - // ShowLeasesSets - {"Invalid", "Некорректный"}, - {"Store type", "Тип хранилища"}, - {"Expires", "Истекает"}, - {"Non Expired Leases", "Не истекшие Lease-ы"}, - {"Gateway", "Шлюз"}, - {"TunnelID", "ID туннеля"}, - {"EndDate", "Заканчивается"}, - {"not floodfill", "не флудфил"}, - // ShowTunnels - {"Queue size", "Размер очереди"}, - // ShowCommands - {"Run peer test", "Запустить тестирование"}, - {"Decline transit tunnels", "Отклонять транзитные туннели"}, - {"Accept transit tunnels", "Принимать транзитные туннели"}, - {"Cancel graceful shutdown", "Отменить плавную остановку"}, - {"Start graceful shutdown", "Запустить плавную остановку"}, - {"Force shutdown", "Принудительная остановка"}, - {"Note: any action done here are not persistent and not changes your config files.", - "Примечание: любое действие произведенное здесь не является постоянным и не изменяет ваши конфигурационные файлы."}, - {"Logging level", "Уровень логирования"}, - {"Transit tunnels limit", "Лимит транзитных туннелей"}, - {"Change", "Изменить"}, - // ShowTransitTunnels - {"no transit tunnels currently built", "нет построенных транзитных туннелей"}, - // ShowSAMSessions/ShowSAMSession - {"SAM disabled", "SAM выключен"}, - {"SAM session not found", "SAM сессия не найдена"}, - {"no sessions currently running", "нет запущенных сессий"}, - {"SAM Session", "SAM сессия"}, - // ShowI2PTunnels - {"Server Tunnels", "Серверные туннели"}, - {"Client Forwards", "Клиентские перенаправления"}, - {"Server Forwards", "Серверные перенаправления"}, - // HandlePage - {"Unknown page", "Неизвестная страница"}, - // HandleCommand, ShowError - {"Invalid token", "Неверный токен"}, - {"SUCCESS", "УСПЕШНО"}, - {"ERROR", "ОШИБКА"}, - {"Unknown command", "Неизвестная команда"}, - {"Command accepted", "Команда принята"}, - {"Back to commands list", "Вернуться к списку команд"}, - {"You will be redirected in 5 seconds", "Вы будете переадресованы через 5 секунд"}, - // HTTP_COMMAND_KILLSTREAM - {"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 back in 5 seconds", "Вы будете переадресованы назад через 5 секунд"}, - // HTTP_COMMAND_LIMITTRANSIT - {"Transit tunnels count must not exceed 65535", "Число транзитных туннелей не должно превышать 65535"}, - // HTTP_COMMAND_GET_REG_STRING - {"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", "Такая точка назначения не найдена"}, + {"Host is down", "Узел недоступен"}, + {"Can't create connection to requested host, it may be down. Please try again later.", "Не удалось установить соединение к запрошенному узлу, возможно он не в сети. Попробуйте повторить запрос позже."}, {"", ""}, }; static std::map> plurals { - // ShowUptime {"days", {"день", "дня", "дней"}}, {"hours", {"час", "часа", "часов"}}, {"minutes", {"минуту", "минуты", "минут"}}, @@ -235,7 +207,7 @@ namespace russian // language std::shared_ptr GetLocale() { - return std::make_shared(strings, plurals, [] (int n)->int { return plural(n); }); + return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); } } // language diff --git a/i18n/Turkmen.cpp b/i18n/Turkmen.cpp index 93bd92fe..1d4f5ee1 100644 --- a/i18n/Turkmen.cpp +++ b/i18n/Turkmen.cpp @@ -18,8 +18,11 @@ namespace i2p { namespace i18n { -namespace turkmen // language +namespace turkmen // language namespace { + // language name in lowercase + static std::string language = "turkmen"; + // See for language plural forms here: // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html static int plural (int n) { @@ -28,7 +31,133 @@ namespace turkmen // language static std::map strings { - // HTTP Proxy + {"KiB", "KiB"}, + {"MiB", "MiB"}, + {"GiB", "GiB"}, + {"building", "bina"}, + {"failed", "şowsuz"}, + {"expiring", "möhleti gutarýar"}, + {"established", "işleýär"}, + {"unknown", "näbelli"}, + {"exploratory", "gözleg"}, + {"i2pd webconsole", "Web konsoly i2pd"}, + {"Main page", "Esasy sahypa"}, + {"Router commands", "Marşrutizator buýruklary"}, + {"Local Destinations", "Ýerli ýerler"}, + {"LeaseSets", "Lizset"}, + {"Tunnels", "Tuneller"}, + {"Transit Tunnels", "Tranzit Tunelleri"}, + {"Transports", "Daşamak"}, + {"I2P tunnels", "I2P tuneller"}, + {"SAM sessions", "SAM Sessiýasy"}, + {"ERROR", "Ýalňyşlyk"}, + {"OK", "OK"}, + {"Testing", "Synag etmek"}, + {"Firewalled", "Daşynda petiklendi"}, + {"Unknown", "Näbelli"}, + {"Proxy", "Proksi"}, + {"Mesh", "MESH-tor"}, + {"Error", "Ýalňyşlyk"}, + {"Clock skew", "Takyk wagt däl"}, + {"Offline", "Awtonom"}, + {"Symmetric NAT", "Simmetriklik NAT"}, + {"Uptime", "Onlaýn onlaýn sözlügi"}, + {"Network status", "Tor ýagdaýy"}, + {"Network status v6", "Tor ýagdaýy v6"}, + {"Stopping in", "Soň duruň"}, + {"Family", "Maşgala"}, + {"Tunnel creation success rate", "Gurlan teneller üstünlikli gurlan teneller"}, + {"Received", "Alnan"}, + {"KiB/s", "KiB/s"}, + {"Sent", "Ýerleşdirildi"}, + {"Transit", "Tranzit"}, + {"Data path", "Maglumat ýoly"}, + {"Hidden content. Press on text to see.", "Gizlin mazmun. Görkezmek üçin tekste basyň."}, + {"Router Ident", "Marşrutly kesgitleýji"}, + {"Router Family", "Marşrutler maşgalasy"}, + {"Router Caps", "Baýdaklar marşruteri"}, + {"Version", "Wersiýasy"}, + {"Our external address", "Daşarky salgymyz"}, + {"supported", "goldanýar"}, + {"Routers", "Marşrutizatorlar"}, + {"Floodfills", "Fludfillar"}, + {"Client Tunnels", "Müşderi tunelleri"}, + {"Services", "Hyzmatlar"}, + {"Enabled", "Goşuldy"}, + {"Disabled", "Öçürildi"}, + {"Encrypted B33 address", "Şifrlenen B33 salgylar"}, + {"Address registration line", "Hasaba alyş salgysy"}, + {"Domain", "Domen"}, + {"Generate", "Öndürmek"}, + {"Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", "Bellik: Alnan setir diňe ikinji derejeli domenleri bellige almak üçin ulanylyp bilner (example.i2p). Subýutmalary hasaba almak üçin i2pd ulanyň-tools."}, + {"Address", "Salgysy"}, + {"Type", "Görnüş"}, + {"EncType", "Şifrlemek görnüşi"}, + {"Inbound tunnels", "Gelýän tuneller"}, + {"ms", "ms"}, + {"Outbound tunnels", "Çykýan tuneller"}, + {"Tags", "Bellikler"}, + {"Incoming", "Gelýän"}, + {"Outgoing", "Çykýan"}, + {"Destination", "Maksat"}, + {"Amount", "Sany"}, + {"Incoming Tags", "Gelýän bellikler"}, + {"Tags sessions", "Sapaklar bellikler"}, + {"Status", "Ýagdaýy"}, + {"Local Destination", "Ýerli maksat"}, + {"Streams", "Strimlary"}, + {"Close stream", "Yap strim"}, + {"I2CP session not found", "I2CP Sessiýa tapylmady"}, + {"I2CP is not enabled", "I2CP goşulmaýar"}, + {"Invalid", "Nädogry"}, + {"Store type", "Ammar görnüşi"}, + {"Expires", "Möhleti gutarýar"}, + {"Non Expired Leases", "Möhleti gutarmady Lizsetlary"}, + {"Gateway", "Derweze"}, + {"TunnelID", "Tuneliň ID"}, + {"EndDate", "Gutarýar"}, + {"not floodfill", "fludfil däl"}, + {"Queue size", "Nobatyň ululygy"}, + {"Run peer test", "Synag başlaň"}, + {"Decline transit tunnels", "Tranzit tunellerini ret ediň"}, + {"Accept transit tunnels", "Tranzit tunellerini alyň"}, + {"Cancel graceful shutdown", "Tekiz durmagy ýatyryň"}, + {"Start graceful shutdown", "Tekiz durmak"}, + {"Force shutdown", "Mejbury duralga"}, + {"Reload external CSS styles", "Daşarky CSS stillerini täzeden ýükläň"}, + {"Note: any action done here are not persistent and not changes your config files.", "Bellik: Bu ýerde öndürilen islendik çäre hemişelik däl we konfigurasiýa faýllaryňyzy üýtgetmeýär."}, + {"Logging level", "Giriş derejesi"}, + {"Transit tunnels limit", "Tranzit tunelleriniň çägi"}, + {"Change", "Üýtgetmek"}, + {"Change language", "Dil üýtgetmek"}, + {"no transit tunnels currently built", "gurlan tranzit tunelleri ýok"}, + {"SAM disabled", "SAM öçürilen"}, + {"no sessions currently running", "başlamagyň sessiýalary ýok"}, + {"SAM session not found", "SAM Sessiýa tapylmady"}, + {"SAM Session", "SAM Sessiýa"}, + {"Server Tunnels", "Serwer tunelleri"}, + {"Client Forwards", "Müşderi gönükdirýär"}, + {"Server Forwards", "Serweriň täzeden düzlüleri"}, + {"Unknown page", "Näbelli sahypa"}, + {"Invalid token", "Nädogry token"}, + {"SUCCESS", "Üstünlikli"}, + {"Stream closed", "Strim ýapyk"}, + {"Stream not found or already was closed", "Strim tapylmady ýa-da eýýäm ýapyldy"}, + {"Destination not found", "Niýetlenen ýeri tapylmady"}, + {"StreamID can't be null", "StreamID boş bolup bilmez"}, + {"Return to destination page", "Barmaly nokadynyň nokadyna gaýdyp geliň"}, + {"You will be redirected in 5 seconds", "5 sekuntdan soň täzeden ugrukdyrylarsyňyz"}, + {"Transit tunnels count must not exceed 65535", "Tranzit tagtalaryň sany 65535-den geçmeli däldir"}, + {"Back to commands list", "Topar sanawyna dolan"}, + {"Register at reg.i2p", "Reg.i2P-de hasaba duruň"}, + {"Description", "Beýany"}, + {"A bit information about service on domain", "Domendäki hyzmat barada käbir maglumatlar"}, + {"Submit", "Iber"}, + {"Domain can't end with .b32.i2p", "Domain .b32.i2p bilen gutaryp bilmez"}, + {"Domain must end with .i2p", "Domeni .i2p bilen gutarmaly"}, + {"Such destination is not found", "Bu barmaly ýer tapylmady"}, + {"Unknown command", "Näbelli topar"}, + {"Command accepted", "Topar kabul edilýär"}, {"Proxy error", "Proksi ýalňyşlygy"}, {"Proxy info", "Proksi maglumat"}, {"Proxy error: Host not found", "Proksi ýalňyşlygy: Host tapylmady"}, @@ -39,12 +168,11 @@ namespace turkmen // language {"addresshelper is not supported", "Salgylandyryjy goldanok"}, {"Host", "Adres"}, {"added to router's addressbook from helper", "marşruteriň adresini kömekçiden goşdy"}, - {"already in router's addressbook", "marşruteriň adres kitaby"}, - {"Click", "Basyň"}, - {"here", "bu ýerde"}, - {"to proceed", "dowam etmek"}, - {"to update record", "recordazgyny täzelemek üçin"}, + {"Click here to proceed:", "Dowam etmek bu ýerde basyň:"}, + {"Continue", "Dowam et"}, {"Addresshelper found", "Forgelper tapyldy"}, + {"already in router's addressbook", "marşruteriň adres kitaby"}, + {"Click here to update record:", "Recordazgyny täzelemek üçin bu ýerde basyň:"}, {"invalid request uri", "nädogry haýyş URI"}, {"Can't detect destination host from request", "Haýyşdan barmaly ýerini tapyp bilemok"}, {"Outproxy failure", "Daşarky proksi ýalňyşlyk"}, @@ -61,165 +189,10 @@ namespace turkmen // language {"failed to send request to upstream", "öý eýesi proksi üçin haýyş iberip bilmedi"}, {"No Reply From socks proxy", "Jorap proksi serwerinden hiç hili jogap ýok"}, {"cannot connect", "birikdirip bilmedi"}, - {"http out proxy not implemented", "daşarky http proksi serwerini goldamak amala aşyrylmaýar"}, + {"http out proxy not implemented", "daşarky HTTP proksi serwerini goldamak amala aşyrylmaýar"}, {"cannot connect to upstream http proxy", "ýokary akym HTTP proksi serwerine birigip bilmedi"}, {"Host is down", "Salgy elýeterli däl"}, - {"Can't create connection to requested host, it may be down. Please try again later.", - "Talap edilýän salgyda birikmäni gurup bilmedim, onlaýn bolup bilmez. Soňra haýyşy soň gaýtalamaga synanyşyň."}, - - // Webconsole // - // cssStyles - {"Disabled", "Öçürildi"}, - {"Enabled", "Goşuldy"}, - // ShowTraffic - {"KiB", "KiB"}, - {"MiB", "MiB"}, - {"GiB", "GiB"}, - // ShowTunnelDetails - {"building", "bina"}, - {"failed", "şowsuz"}, - {"expiring", "möhleti gutarýar"}, - {"established", "işleýär"}, - {"exploratory", "gözleg"}, - {"unknown", "näbelli"}, - {"i2pd webconsole", "Web konsoly i2pd"}, - // ShowPageHead - {"Main page", "Esasy sahypa"}, - {"Router commands", "Marşrutizator buýruklary"}, - {"Local destinations", "Ýerli ýerler"}, - {"LeaseSets", "Lizset"}, - {"Tunnels", "Tuneller"}, - {"Transit tunnels", "Tranzit Tunels"}, - {"Transports", "Daşamak"}, - {"I2P tunnels", "I2P tuneller"}, - {"SAM sessions", "SAM Sessiýasy"}, - // Network Status - {"OK", "OK"}, - {"Testing", "Synag etmek"}, - {"Firewalled", "Daşynda petiklendi"}, - {"Unknown", "Näbelli"}, - {"Proxy", "Proksi"}, - {"Mesh", "MESH-tor"}, - {"Error", "Ýalňyşlyk"}, - {"Clock skew", "Takyk wagt däl"}, - {"Offline", "Awtonom"}, - {"Symmetric NAT", "Simmetriklik NAT"}, - // Status - {"Uptime", "Onlaýn onlaýn sözlügi"}, - {"Network status", "Tor ýagdaýy"}, - {"Network status v6", "Tor ýagdaýy v6"}, - {"Stopping in", "soň duruň"}, - {"Family", "Maşgala"}, - {"Tunnel creation success rate", "Gurlan teneller üstünlikli gurlan teneller"}, - {"Received", "Alnan"}, - {"Sent", "Ýerleşdirildi"}, - {"Transit", "Tranzit"}, - {"KiB/s", "KiB/s"}, - {"Data path", "Maglumat ýoly"}, - {"Hidden content. Press on text to see.", "Gizlin mazmun. Görkezmek üçin tekste basyň."}, - {"Router Ident", "Marşrutly kesgitleýji"}, - {"Router Family", "Marşrutler maşgalasy"}, - {"Router Caps", "Baýdaklar marşruteri"}, - {"Version", "Wersiýasy"}, - {"Our external address", "Daşarky salgymyz"}, - {"supported", "Goldanýar"}, - {"Routers", "Marşrutizatorlar"}, - {"Floodfills", "Fludfillar"}, - {"LeaseSets", "Lizsetllar"}, - {"Client Tunnels", "Müşderi tunelleri"}, - {"Transit Tunnels", "Tranzit Tunelleri"}, - {"Services", "Hyzmatlar"}, - // ShowLocalDestinations - {"Local Destinations", "Ýerli ýerler"}, - // ShowLeaseSetDestination - {"Encrypted B33 address", "Şifrlenen B33 salgylar"}, - {"Address registration line", "Hasaba alyş salgysy"}, - {"Domain", "Domen"}, - {"Generate", "Öndürmek"}, - {"Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", - "Bellik: Alnan setir diňe ikinji derejeli domenleri bellige almak üçin ulanylyp bilner. Subýutmalary hasaba almak üçin i2pd ulanyň-tools."}, - {"Address", "Salgysy"}, - {"Type", "Görnüş"}, - {"EncType", "Şifrlemek görnüşi"}, - {"Inbound tunnels", "Gelýän tuneller"}, - {"Outbound tunnels", "Çykýan tuneller"}, - {"ms", "ms"}, // milliseconds - {"Tags", "Bellikler"}, - {"Incoming", "Gelýän"}, - {"Outgoing", "Çykýan"}, - {"Destination", "Maksat"}, - {"Amount", "Sany"}, - {"Incoming Tags", "Gelýän bellikler"}, - {"Tags sessions", "Sapaklar Tag."}, - {"Status", "Ýagdaýy"}, - // ShowLocalDestination - {"Local Destination", "Ýerli maksat"}, - {"Streams", "Strimlary"}, - {"Close stream", "Yap strim"}, - // ShowI2CPLocalDestination - {"I2CP session not found", "I2CP Sessiýa tapylmady"}, - {"I2CP is not enabled", "I2CP goşulmaýar"}, - // ShowLeasesSets - {"Invalid", "Nädogry"}, - {"Store type", "Ammar görnüşi"}, - {"Expires", "Möhleti gutarýar"}, - {"Non Expired Leases", "Möhleti gutarmady Lizsetlary"}, - {"Gateway", "Derweze"}, - {"TunnelID", "Tuneliň ID"}, - {"EndDate", "Gutarýar"}, - {"not floodfill", "fludfil däl"}, - // ShowTunnels - {"Queue size", "Nobatyň ululygy"}, - // ShowCommands - {"Run peer test", "Synag başlaň"}, - {"Decline transit tunnels", "Tranzit tunellerini ret ediň"}, - {"Accept transit tunnels", "Tranzit tunellerini alyň"}, - {"Cancel graceful shutdown", "Tekiz durmagy ýatyryň"}, - {"Start graceful shutdown", "Tekiz durmak"}, - {"Force shutdown", "Mejbury duralga"}, - {"Note: any action done here are not persistent and not changes your config files.", - "Bellik: Bu ýerde öndürilen islendik çäre hemişelik däl we konfigurasiýa faýllaryňyzy üýtgetmeýär.."}, - {"Logging level", "Giriş derejesi"}, - {"Transit tunnels limit", "Tranzit tunelleriniň çägi"}, - {"Change", "Üýtgetmek"}, - // ShowTransitTunnels - {"no transit tunnels currently built", "gurlan tranzit tunelleri ýok"}, - // ShowSAMSessions/ShowSAMSession - {"SAM disabled", "SAM öçürilen"}, - {"SAM session not found", "SAM Sessiýa tapylmady"}, - {"no sessions currently running", "başlamagyň sessiýalary ýok"}, - {"SAM Session", "SAM Sessiýa"}, - // ShowI2PTunnels - {"Server Tunnels", "Serwer tunelleri"}, - {"Client Forwards", "Müşderi gönükdirýär"}, - {"Server Forwards", "Serweriň täzeden düzlüleri"}, - // HandlePage - {"Unknown page", "Näbelli sahypa"}, - // HandleCommand, ShowError - {"Invalid token", "Nädogry token"}, - {"SUCCESS", "Üstünlikli"}, - {"ERROR", "Ýalňyşlyk"}, - {"Unknown command", "Näbelli topar"}, - {"Command accepted", "Topar kabul edilýär"}, - {"Back to commands list", "Topar sanawyna dolan"}, - {"You will be redirected in 5 seconds", "5 sekuntdan soň täzeden ugrukdyrylarsyňyz"}, - // HTTP_COMMAND_KILLSTREAM - {"Stream closed", "Strim ýapyk"}, - {"Stream not found or already was closed", "Strim tapylmady ýa-da eýýäm ýapyldy"}, - {"Destination not found", "Niýetlenen ýeri tapylmady"}, - {"StreamID can't be null", "StreamID boş bolup bilmez"}, - {"Return to destination page", "Barmaly nokadynyň nokadyna gaýdyp geliň"}, - {"You will be redirected back in 5 seconds", "5 sekuntda yzyna iberiler"}, - // HTTP_COMMAND_LIMITTRANSIT - {"Transit tunnels count must not exceed 65535", "Tranzit tagtalaryň sany 65535-den geçmeli däldir"}, - // HTTP_COMMAND_GET_REG_STRING - {"Register at reg.i2p", "Reg.i2P-de hasaba duruň"}, - {"Description", "Beýany"}, - {"A bit information about service on domain", "Domendäki hyzmat barada käbir maglumatlar"}, - {"Submit", "Iber"}, - {"Domain can't end with .b32.i2p", "Domain .b32.i2p bilen gutaryp bilmez."}, - {"Domain must end with .i2p", "Domeni .i2p bilen gutarmaly"}, - {"Such destination is not found", "Bu barmaly ýer tapylmady"}, + {"Can't create connection to requested host, it may be down. Please try again later.", "Talap edilýän salgyda birikmäni gurup bilmedim, onlaýn bolup bilmez. Soňra haýyşy soň gaýtalamaga synanyşyň."}, {"", ""}, }; @@ -235,7 +208,7 @@ namespace turkmen // language std::shared_ptr GetLocale() { - return std::make_shared(strings, plurals, [] (int n)->int { return plural(n); }); + return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); } } // language diff --git a/i18n/Ukrainian.cpp b/i18n/Ukrainian.cpp index 05e7c786..abbe8f81 100644 --- a/i18n/Ukrainian.cpp +++ b/i18n/Ukrainian.cpp @@ -18,8 +18,11 @@ namespace i2p { namespace i18n { -namespace ukrainian // language +namespace ukrainian // language namespace { + // language name in lowercase + static std::string language = "ukrainian"; + // See for language plural forms here: // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html static int plural (int n) { @@ -28,23 +31,148 @@ namespace ukrainian // language static std::map strings { - // HTTP Proxy + {"KiB", "КіБ"}, + {"MiB", "МіБ"}, + {"GiB", "ГіБ"}, + {"building", "будується"}, + {"failed", "невдалий"}, + {"expiring", "завершується"}, + {"established", "працює"}, + {"unknown", "невідомо"}, + {"exploratory", "дослідницький"}, + {"i2pd webconsole", "Веб-консоль i2pd"}, + {"Main page", "Головна"}, + {"Router commands", "Команди маршрутизатора"}, + {"Local Destinations", "Локальні Призначення"}, + {"LeaseSets", "Лізсети"}, + {"Tunnels", "Тунелі"}, + {"Transit Tunnels", "Транзитні Тунелі"}, + {"Transports", "Транспорти"}, + {"I2P tunnels", "I2P тунелі"}, + {"SAM sessions", "SAM сесії"}, + {"ERROR", "ПОМИЛКА"}, + {"OK", "OK"}, + {"Testing", "Тестування"}, + {"Firewalled", "Заблоковано ззовні"}, + {"Unknown", "Невідомо"}, + {"Proxy", "Проксі"}, + {"Mesh", "MESH-мережа"}, + {"Error", "Помилка"}, + {"Clock skew", "Неточний час"}, + {"Offline", "Офлайн"}, + {"Symmetric NAT", "Симетричний NAT"}, + {"Uptime", "У мережі"}, + {"Network status", "Мережевий статус"}, + {"Network status v6", "Мережевий статус v6"}, + {"Stopping in", "Зупинка через"}, + {"Family", "Сімейство"}, + {"Tunnel creation success rate", "Успішно побудованих тунелів"}, + {"Received", "Отримано"}, + {"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", "Не завершені Lease-и"}, + {"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", "Ідентифікатор потоку не може бути порожнім"}, + {"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", "Віддалена адреса не знайдена в адресній книзі роутера"}, + {"Remote host not found in router's addressbook", "Віддалена адреса не знайдена в адресній книзі маршрутизатора"}, {"You may try to find this host on jump services below", "Ви можете спробувати знайти дану адресу на джамп сервісах нижче"}, {"Invalid request", "Некоректний запит"}, {"Proxy unable to parse your request", "Проксі не може розібрати ваш запит"}, {"addresshelper is not supported", "addresshelper не підтримується"}, {"Host", "Адреса"}, - {"added to router's addressbook from helper", "доданий в адресну книгу роутера через хелпер"}, - {"already in router's addressbook", "вже в адресній книзі роутера"}, - {"Click", "Натисніть"}, - {"here", "тут"}, - {"to proceed", "щоб продовжити"}, - {"to update record", "щоб оновити запис"}, + {"added to router's addressbook from helper", "доданий в адресну книгу маршрутизатора через хелпер"}, + {"Click here to proceed:", "Натисніть тут щоб продовжити:"}, + {"Continue", "Продовжити"}, {"Addresshelper found", "Знайдено addresshelper"}, + {"already in router's addressbook", "вже в адресній книзі маршрутизатора"}, + {"Click here to update record:", "Натисніть тут щоб оновити запис:"}, {"invalid request uri", "некоректний URI запиту"}, {"Can't detect destination host from request", "Не вдалось визначити адресу призначення з запиту"}, {"Outproxy failure", "Помилка зовнішнього проксі"}, @@ -64,170 +192,14 @@ namespace ukrainian // language {"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.", - "Не вдалося встановити з'єднання до запитаного вузла, можливо він не в мережі. Спробуйте повторити запит пізніше."}, - - // Webconsole // - // cssStyles - {"Disabled", "Вимкнуто"}, - {"Enabled", "Увімкнуто"}, - // ShowTraffic - {"KiB", "КіБ"}, - {"MiB", "МіБ"}, - {"GiB", "ГіБ"}, - // ShowTunnelDetails - {"building", "будується"}, - {"failed", "невдалий"}, - {"expiring", "завершується"}, - {"established", "працює"}, - {"exploratory", "дослідницький"}, - {"unknown", "невідомо"}, - {"i2pd webconsole", "Веб-консоль i2pd"}, - // ShowPageHead - {"Main page", "Головна"}, - {"Router commands", "Команди роутера"}, - {"Local destinations", "Локальні признач."}, - {"LeaseSets", "Лізсети"}, - {"Tunnels", "Тунелі"}, - {"Transit tunnels", "Транзит. тунелі"}, - {"Transports", "Транспорти"}, - {"I2P tunnels", "I2P тунелі"}, - {"SAM sessions", "SAM сесії"}, - // Network Status - {"OK", "OK"}, - {"Testing", "Тестування"}, - {"Firewalled", "Файрвол"}, - {"Unknown", "Невідомо"}, - {"Proxy", "Проксі"}, - {"Mesh", "MESH-мережа"}, - {"Error", "Помилка"}, - {"Clock skew", "Неточний час"}, - {"Offline", "Офлайн"}, - {"Symmetric NAT", "Симетричний NAT"}, - // Status - {"Uptime", "В мережі"}, - {"Network status", "Мережевий статус"}, - {"Network status v6", "Мережевий статус v6"}, - {"Stopping in", "Зупинка через"}, - {"Family", "Сімейство"}, - {"Tunnel creation success rate", "Успішно побудованих тунелів"}, - {"Received", "Отримано"}, - {"Sent", "Відправлено"}, - {"Transit", "Транзит"}, - {"KiB/s", "КіБ/с"}, - {"Data path", "Шлях до даних"}, - {"Hidden content. Press on text to see.", "Прихований вміст. Натисніть на текст щоб відобразити."}, - {"Router Ident", "Ідентифікатор Роутера"}, - {"Router Family", "Сімейство Роутера"}, - {"Router Caps", "Прапорці Роутера"}, - {"Version", "Версія"}, - {"Our external address", "Наша зовнішня адреса"}, - {"supported", "підтримується"}, - {"Routers", "Роутери"}, - {"Floodfills", "Флудфіли"}, - {"LeaseSets", "Лізсети"}, - {"Client Tunnels", "Клієнтські Тунелі"}, - {"Transit Tunnels", "Транзитні Тунелі"}, - {"Services", "Сервіси"}, - // ShowLocalDestinations - {"Local Destinations", "Локальні Призначення"}, - // ShowLeaseSetDestination - {"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.", - "Примітка: отриманий рядок може бути використаний тільки для реєстрації доменів другого рівня. Для реєстрації піддоменів використовуйте i2pd-tools."}, - {"Address", "Адреса"}, - {"Type", "Тип"}, - {"EncType", "ТипШифр"}, - {"Inbound tunnels", "Вхідні тунелі"}, - {"Outbound tunnels", "Вихідні тунелі"}, - {"ms", "мс"}, // milliseconds - {"Tags", "Теги"}, - {"Incoming", "Вхідні"}, - {"Outgoing", "Вихідні"}, - {"Destination", "Призначення"}, - {"Amount", "Кількість"}, - {"Incoming Tags", "Вхідні Теги"}, - {"Tags sessions", "Сесії тегів"}, - {"Status", "Статус"}, - // ShowLocalDestination - {"Local Destination", "Локальне Призначення"}, - {"Streams", "Потоки"}, - {"Close stream", "Закрити потік"}, - // ShowI2CPLocalDestination - {"I2CP session not found", "I2CP сесія не знайдена"}, - {"I2CP is not enabled", "I2CP не увікнуто"}, - // ShowLeasesSets - {"Invalid", "Некоректний"}, - {"Store type", "Тип сховища"}, - {"Expires", "Завершується"}, - {"Non Expired Leases", "Не завершені Lease-и"}, - {"Gateway", "Шлюз"}, - {"TunnelID", "ID тунеля"}, - {"EndDate", "Закінчується"}, - {"not floodfill", "не флудфіл"}, - // ShowTunnels - {"Queue size", "Розмір черги"}, - // ShowCommands - {"Run peer test", "Запустити тестування"}, - {"Decline transit tunnels", "Відхиляти транзитні тунелі"}, - {"Accept transit tunnels", "Ухвалювати транзитні тунелі"}, - {"Cancel graceful shutdown", "Скасувати плавну зупинку"}, - {"Start graceful shutdown", "Запустити плавну зупинку"}, - {"Force shutdown", "Примусова зупинка"}, - {"Note: any action done here are not persistent and not changes your config files.", - "Примітка: будь-яка зроблена тут дія не є постійною та не змінює ваші конфігураційні файли."}, - {"Logging level", "Рівень логування"}, - {"Transit tunnels limit", "Обмеження транзитних тунелів"}, - {"Change", "Змінити"}, - // ShowTransitTunnels - {"no transit tunnels currently built", "немає побудованих транзитних тунелів"}, - // ShowSAMSessions/ShowSAMSession - {"SAM disabled", "SAM вимкнуто"}, - {"SAM session not found", "SAM сесія не знайдена"}, - {"no sessions currently running", "немає запущених сесій"}, - {"SAM Session", "SAM сесія"}, - // ShowI2PTunnels - {"Server Tunnels", "Серверні Тунелі"}, - {"Client Forwards", "Клієнтські Переспрямування"}, - {"Server Forwards", "Серверні Переспрямування"}, - // HandlePage - {"Unknown page", "Невідома сторінка"}, - // HandleCommand, ShowError - {"Invalid token", "Невірний токен"}, - {"SUCCESS", "ВДАЛО"}, - {"ERROR", "ПОМИЛКА"}, - {"Unknown command", "Невідома команда"}, - {"Command accepted", "Команда прийнята"}, - {"Back to commands list", "Повернутися до списку команд"}, - {"You will be redirected in 5 seconds", "Ви будете переадресовані через 5 секунд"}, - // HTTP_COMMAND_KILLSTREAM - {"Stream closed", "Потік зачинений"}, - {"Stream not found or already was closed", "Потік не знайдений або вже зачинений"}, - {"Destination not found", "Точка призначення не знайдена"}, - {"StreamID can't be null", "Ідентифікатор потоку не може бути порожнім"}, - {"Return to destination page", "Повернутися на сторінку точки призначення"}, - {"You will be redirected back in 5 seconds", "Ви будете переадресовані назад через 5 секунд"}, - // HTTP_COMMAND_LIMITTRANSIT - {"Transit tunnels count must not exceed 65535", "Кількість транзитних тунелів не повинна перевищувати 65535"}, - // HTTP_COMMAND_GET_REG_STRING - {"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", "Така точка призначення не знайдена"}, + {"Can't create connection to requested host, it may be down. Please try again later.", "Не вдалося встановити з'єднання до запитаного вузла, можливо він не в мережі. Спробуйте повторити запит пізніше."}, {"", ""}, }; static std::map> plurals { - // ShowUptime - {"days", {"день", "дня", "днів"}}, - {"hours", {"годину", "години", "годин"}}, + {"days", {"день", "дня", "днів"}}, + {"hours", {"годину", "години", "годин"}}, {"minutes", {"хвилину", "хвилини", "хвилин"}}, {"seconds", {"секунду", "секунди", "секунд"}}, {"", {"", "", ""}}, @@ -235,7 +207,7 @@ namespace ukrainian // language std::shared_ptr GetLocale() { - return std::make_shared(strings, plurals, [] (int n)->int { return plural(n); }); + return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); } } // language diff --git a/i18n/Uzbek.cpp b/i18n/Uzbek.cpp new file mode 100644 index 00000000..72734b58 --- /dev/null +++ b/i18n/Uzbek.cpp @@ -0,0 +1,206 @@ +/* +* Copyright (c) 2021, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#include +#include +#include +#include +#include "I18N.h" + +// Ukrainian localization file + +namespace i2p +{ +namespace i18n +{ +namespace uzbek // language namespace +{ + // language name in lowercase + static std::string language = "uzbek"; + + // See for language plural forms here: + // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html + static int plural (int n) { + return n > 1 ? 1 : 0; + } + + static std::map strings + { + {"KiB", "KiB"}, + {"MiB", "MiB"}, + {"GiB", "GiB"}, + {"building", "qurilish"}, + {"failed", "muvaffaqiyatsiz"}, + {"expiring", "muddati tugaydi"}, + {"established", "aloqa o'rnatildi"}, + {"unknown", "noma'lum"}, + {"exploratory", "tadqiqiy"}, + {"i2pd webconsole", "i2pd veb -konsoli"}, + {"Main page", "Asosiy sahifa"}, + {"Router commands", "Router buyruqlari"}, + {"LeaseSets", "LeaseSets"}, + {"Tunnels", "Tunnellar"}, + {"Transit Tunnels", "Tranzit Tunellar"}, + {"Transports", "Transportlar"}, + {"I2P tunnels", "I2P tunnellar"}, + {"SAM sessions", "SAM sessiyalari"}, + {"ERROR", "XATO"}, + {"OK", "OK"}, + {"Testing", "Testlash"}, + {"Firewalled", "Xavfsizlik devori bilan himoyalangan"}, + {"Unknown", "Notanish"}, + {"Proxy", "Proksi"}, + {"Mesh", "Mesh To'r"}, + {"Error", "Xato"}, + {"Clock skew", "Aniq vaqt emas"}, + {"Offline", "Oflayn"}, + {"Symmetric NAT", "Simmetrik NAT"}, + {"Uptime", "Ish vaqti"}, + {"Network status", "Tarmoq holati"}, + {"Network status v6", "Tarmoq holati v6"}, + {"Stopping in", "Ichida to'xtatish"}, + {"Family", "Oila"}, + {"Tunnel creation success rate", "Tunnel yaratish muvaffaqiyat darajasi"}, + {"Received", "Qabul qilindi"}, + {"KiB/s", "KiB/s"}, + {"Sent", "Yuborilgan"}, + {"Transit", "Tranzit"}, + {"Data path", "Ma'lumotlar yo'li"}, + {"Hidden content. Press on text to see.", "Yashirin tarkib. Ko'rish uchun matn ustida bosing."}, + {"Router Ident", "Router identifikatori"}, + {"Router Family", "Router Oila"}, + {"Router Caps", "Router bayroqlari"}, + {"Version", "Versiya"}, + {"Our external address", "Bizning tashqi manzilimiz"}, + {"supported", "qo'llab -quvvatlanadi"}, + {"Routers", "Routerlar"}, + {"Floodfills", "Floodfills"}, + {"Client Tunnels", "Mijoz tunellari"}, + {"Services", "Xizmatlar"}, + {"Enabled", "Yoqilgan"}, + {"Disabled", "O'chirilgan"}, + {"Encrypted B33 address", "Shifrlangan B33 manzil"}, + {"Address registration line", "Manzilni ro'yxatga olish liniyasi"}, + {"Domain", "Domen"}, + {"Generate", "Varatish"}, + {"Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", "Eslatma: natija satridan faqat 2LD domenlarini ro'yxatdan o'tkazish uchun foydalanish mumkin (example.i2p). Subdomenlarni ro'yxatdan o'tkazish uchun i2pd-tools dan foydalaning."}, + {"Address", "Manzil"}, + {"Type", "Turi"}, + {"EncType", "ShifrlashTuri"}, + {"Inbound tunnels", "Kirish tunnellari"}, + {"ms", "ms"}, + {"Outbound tunnels", "Chiquvchi tunnellar"}, + {"Tags", "Teglar"}, + {"Incoming", "Kiruvchi"}, + {"Outgoing", "Chiquvchi"}, + {"Destination", "Manzilgoh"}, + {"Amount", "Yig'indi"}, + {"Incoming Tags", "Kiruvchi teglar"}, + {"Tags sessions", "Teglar sessiyalari"}, + {"Status", "Holat"}, + {"Streams", "Strim"}, + {"Close stream", "Strimni o'chirish"}, + {"I2CP session not found", "I2CP sessiyasi topilmadi"}, + {"I2CP is not enabled", "I2CP yoqilmagan"}, + {"Invalid", "Noto'g'ri"}, + {"Store type", "Saqlash turi"}, + {"Expires", "Muddati tugaydi"}, + {"Non Expired Leases", "Muddati O'tmagan Leases"}, + {"Gateway", "Kirish yo'li"}, + {"TunnelID", "TunnelID"}, + {"EndDate", "Tugash Sanasi"}, + {"not floodfill", "floodfill emas"}, + {"Queue size", "Navbat hajmi"}, + {"Run peer test", "Sinovni boshlang"}, + {"Decline transit tunnels", "Tranzit tunnellarni rad etish"}, + {"Accept transit tunnels", "Tranzit tunnellarni qabul qilish"}, + {"Cancel graceful shutdown", "Yumshoq to'xtashni bekor qiling"}, + {"Start graceful shutdown", "Yumshoq to'xtashni boshlang"}, + {"Force shutdown", "Bizning tashqi manzilimiz"}, + {"Reload external CSS styles", "Tashqi CSS uslublarini qayta yuklang"}, + {"Note: any action done here are not persistent and not changes your config files.", "Eslatma: bu erda qilingan har qanday harakat doimiy emas va konfiguratsiya fayllarini o'zgartirmaydi."}, + {"Transit tunnels limit", "Tranzit tunellar chegarasi"}, + {"Change", "O'zgartirish"}, + {"Change language", "Tilni o'zgartirish"}, + {"no transit tunnels currently built", "qurilgan tranzit tunnellari yo'q"}, + {"SAM disabled", "SAM o'chirilgan"}, + {"no sessions currently running", "hech qanday ishlaydigan sessiyalar yo'q"}, + {"SAM session not found", "SAM sessiyasi topilmadi"}, + {"SAM Session", "SAM sessiyasi"}, + {"Server Tunnels", "Server Tunellari"}, + {"Client Forwards", "Mijozlarni Yo'naltirish"}, + {"Server Forwards", "Serverni Yo'naltirish"}, + {"Unknown page", "Noma'lum sahifa"}, + {"Invalid token", "Noto‘g‘ri belgi"}, + {"SUCCESS", "Muvaffaqiyat"}, + {"Stream closed", "Strim yopiq"}, + {"Stream not found or already was closed", "Strim topilmadi yoki allaqachon yopilgan"}, + {"Destination not found", "Yo'nalish topilmadi"}, + {"StreamID can't be null", "StreamID bo'sh bo'lishi mumkin emas"}, + {"Return to destination page", "Belgilangan sahifaga qaytish"}, + {"You will be redirected in 5 seconds", "Siz 5 soniyada qayta yo'naltirilasiz"}, + {"Transit tunnels count must not exceed 65535", "Tranzit tunnellar soni 65535 dan oshmasligi kerak"}, + {"Back to commands list", "Buyruqlar ro'yxatiga qaytish"}, + {"Register at reg.i2p", "Reg.i2p-da ro'yxatdan o'ting"}, + {"Description", "Tavsif"}, + {"A bit information about service on domain", "Domen xizmatlari haqida bir oz ma'lumot"}, + {"Submit", "Yuborish"}, + {"Domain can't end with .b32.i2p", "Domen .b32.i2p bilan tugashi mumkin emas"}, + {"Domain must end with .i2p", "Domen .i2p bilan tugashi kerak"}, + {"Such destination is not found", "Bunday yo'nalish topilmadi"}, + {"Unknown command", "Noma'lum buyruq"}, + {"Command accepted", "Buyruq qabul qilindi"}, + {"Proxy error", "Proksi xatosi"}, + {"Proxy info", "Proksi ma'lumotlari"}, + {"Proxy error: Host not found", "Proksi xatosi: Xost topilmadi"}, + {"Remote host not found in router's addressbook", "Masofaviy xost yo'riqnoma manzillar kitobida topilmadi"}, + {"Invalid request", "Noto‘g‘ri so‘rov"}, + {"Proxy unable to parse your request", "Proksi sizning so'rovingizni tahlil qila olmaydi"}, + {"addresshelper is not supported", "addresshelper qo'llab -quvvatlanmaydi"}, + {"Host", "Xost"}, + {"Addresshelper found", "Addresshelper topildi"}, + {"invalid request uri", "noto'g'ri URI so'rovi"}, + {"Can't detect destination host from request", "So‘rov orqali manzil xostini aniqlab bo'lmayapti"}, + {"Outproxy failure", "Tashqi proksi muvaffaqiyatsizligi"}, + {"bad outproxy settings", "noto'g'ri tashqi proksi -server sozlamalari"}, + {"not inside I2P network, but outproxy is not enabled", "I2P tarmog'ida emas, lekin tashqi proksi yoqilmagan"}, + {"unknown outproxy url", "noma'lum outproxy url"}, + {"cannot resolve upstream proxy", "yuqoridagi proksi -serverni aniqlab olib bolmaydi"}, + {"hostname too long", "xost nomi juda uzun"}, + {"cannot connect to upstream socks proxy", "yuqori soks proksi -serveriga ulanib bo'lmaydi"}, + {"Cannot negotiate with socks proxy", "Soks proksi bilan muzokara olib bo'lmaydi"}, + {"CONNECT error", "CONNECT xatosi"}, + {"Failed to Connect", "Ulanmadi"}, + {"socks proxy error", "soks proksi xatosi"}, + {"failed to send request to upstream", "yuqori http proksi-serveriga ulanib bo'lmadi"}, + {"No Reply From socks proxy", "Soks-proksidan javob yo'q"}, + {"cannot connect", "ulab bo'lmaydi"}, + {"http out proxy not implemented", "tashqi HTTP proksi -serverni qo'llab -quvvatlash amalga oshirilmagan"}, + {"cannot connect to upstream http proxy", "yuqori http proksi-serveriga ulanib bo'lmadi"}, + {"Host is down", "Xost ishlamayapti"}, + {"Can't create connection to requested host, it may be down. Please try again later.", "Talab qilingan xost bilan aloqa o'rnatilmadi, u ishlamay qolishi mumkin. Iltimos keyinroq qayta urinib ko'ring."}, + {"", ""}, + }; + + static std::map> plurals + { + {"days", {"kun", "kunlar"}}, + {"hours", {"soat", "soat"}}, + {"minutes", {"daqiqa", "daqiqalar"}}, + {"seconds", {"soniya", "soniyalar"}}, + {"", {"", ""}}, + }; + + 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 f5316860..a8516e00 100644 --- a/libi2pd/Config.cpp +++ b/libi2pd/Config.cpp @@ -37,6 +37,7 @@ namespace config { ("conf", value()->default_value(""), "Path to main i2pd config file (default: try ~/.i2pd/i2pd.conf or /var/lib/i2pd/i2pd.conf)") ("tunconf", value()->default_value(""), "Path to config with tunnels list and options (default: try ~/.i2pd/tunnels.conf or /var/lib/i2pd/tunnels.conf)") ("tunnelsdir", value()->default_value(""), "Path to extra tunnels' configs folder (default: ~/.i2pd/tunnels.d or /var/lib/i2pd/tunnels.d") + ("certsdir", value()->default_value(""), "Path to certificates used for verifying .su3, families (default: ~/.i2pd/certificates or /var/lib/i2pd/certificates") ("pidfile", value()->default_value(""), "Path to pidfile (default: ~/i2pd/i2pd.pid or /var/lib/i2pd/i2pd.pid)") ("log", value()->default_value(""), "Logs destination: stdout, file, syslog (stdout if not set)") ("logfile", value()->default_value(""), "Path to logfile (stdout if not set, autodetect if daemon)") @@ -212,7 +213,9 @@ namespace config { "https://i2p.novg.net/" ), "Reseed URLs, separated by comma") ("reseed.yggurls", value()->default_value( - "http://[324:9de3:fea4:f6ac::ace]:7070/" + "http://[324:71e:281a:9ed3::ace]:7070/," + "http://[301:65b9:c7cd:9a36::1]:18801/," + "http://[320:8936:ec1a:31f1::216]/" ), "Reseed URLs through the Yggdrasil, separated by comma") ; @@ -221,7 +224,7 @@ namespace config { ("addressbook.defaulturl", value()->default_value( "http://shx5vqsw7usdaunyzr2qmes2fq37oumybpudrd4jjj4e4vk4uusa.b32.i2p/hosts.txt" ), "AddressBook subscription URL for initial setup") - ("addressbook.subscriptions", value()->default_value(""), "AddressBook subscriptions URLs, separated by comma") + ("addressbook.subscriptions", value()->default_value("http://reg.i2p/hosts.txt"), "AddressBook subscriptions URLs, separated by comma") ("addressbook.hostsfile", value()->default_value(""), "File to dump addresses in hosts.txt format"); options_description trust("Trust options"); @@ -284,7 +287,7 @@ namespace config { options_description meshnets("Meshnet transports options"); meshnets.add_options() - ("meshnets.yggdrasil", bool_switch()->default_value(false), "Support transports through the Yggdrasil (deafult: false)") + ("meshnets.yggdrasil", bool_switch()->default_value(false), "Support transports through the Yggdrasil (default: false)") ("meshnets.yggaddress", value()->default_value(""), "Yggdrasil address to publish") ; diff --git a/libi2pd/Crypto.cpp b/libi2pd/Crypto.cpp index 14ef83ae..d38d489a 100644 --- a/libi2pd/Crypto.cpp +++ b/libi2pd/Crypto.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -398,8 +398,9 @@ namespace crypto } // ElGamal - void ElGamalEncrypt (const uint8_t * key, const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding) + void ElGamalEncrypt (const uint8_t * key, const uint8_t * data, uint8_t * encrypted) { + BN_CTX * ctx = BN_CTX_new (); BN_CTX_start (ctx); // everything, but a, because a might come from table BIGNUM * k = BN_CTX_get (ctx); @@ -435,37 +436,32 @@ namespace crypto BN_bin2bn (m, 255, b); BN_mod_mul (b, b1, b, elgp, ctx); // copy a and b - if (zeroPadding) - { - encrypted[0] = 0; - bn2buf (a, encrypted + 1, 256); - encrypted[257] = 0; - bn2buf (b, encrypted + 258, 256); - } - else - { - bn2buf (a, encrypted, 256); - bn2buf (b, encrypted + 256, 256); - } + encrypted[0] = 0; + bn2buf (a, encrypted + 1, 256); + encrypted[257] = 0; + bn2buf (b, encrypted + 258, 256); + BN_free (a); BN_CTX_end (ctx); + BN_CTX_free (ctx); } - bool ElGamalDecrypt (const uint8_t * key, const uint8_t * encrypted, - uint8_t * data, BN_CTX * ctx, bool zeroPadding) + bool ElGamalDecrypt (const uint8_t * key, const uint8_t * encrypted, uint8_t * data) { + BN_CTX * ctx = BN_CTX_new (); BN_CTX_start (ctx); BIGNUM * x = BN_CTX_get (ctx), * a = BN_CTX_get (ctx), * b = BN_CTX_get (ctx); BN_bin2bn (key, 256, x); BN_sub (x, elgp, x); BN_sub_word (x, 1); // x = elgp - x- 1 - BN_bin2bn (zeroPadding ? encrypted + 1 : encrypted, 256, a); - BN_bin2bn (zeroPadding ? encrypted + 258 : encrypted + 256, 256, b); + BN_bin2bn (encrypted + 1, 256, a); + BN_bin2bn (encrypted + 258, 256, b); // m = b*(a^x mod p) mod p BN_mod_exp (x, a, x, elgp, ctx); BN_mod_mul (b, b, x, elgp, ctx); uint8_t m[255]; bn2buf (b, m, 255); BN_CTX_end (ctx); + BN_CTX_free (ctx); uint8_t hash[32]; SHA256 (m + 33, 222, hash); if (memcmp (m + 1, hash, 32)) @@ -499,8 +495,9 @@ namespace crypto } // ECIES - void ECIESEncrypt (const EC_GROUP * curve, const EC_POINT * key, const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding) + void ECIESEncrypt (const EC_GROUP * curve, const EC_POINT * key, const uint8_t * data, uint8_t * encrypted) { + BN_CTX * ctx = BN_CTX_new (); BN_CTX_start (ctx); BIGNUM * q = BN_CTX_get (ctx); EC_GROUP_get_order(curve, q, ctx); @@ -512,19 +509,10 @@ namespace crypto EC_POINT_mul (curve, p, k, nullptr, nullptr, ctx); BIGNUM * x = BN_CTX_get (ctx), * y = BN_CTX_get (ctx); EC_POINT_get_affine_coordinates_GFp (curve, p, x, y, nullptr); - if (zeroPadding) - { - encrypted[0] = 0; - bn2buf (x, encrypted + 1, len); - bn2buf (y, encrypted + 1 + len, len); - RAND_bytes (encrypted + 1 + 2*len, 256 - 2*len); - } - else - { - bn2buf (x, encrypted, len); - bn2buf (y, encrypted + len, len); - RAND_bytes (encrypted + 2*len, 256 - 2*len); - } + encrypted[0] = 0; + bn2buf (x, encrypted + 1, len); + bn2buf (y, encrypted + 1 + len, len); + RAND_bytes (encrypted + 1 + 2*len, 256 - 2*len); // encryption key and iv EC_POINT_mul (curve, p, nullptr, key, k, ctx); EC_POINT_get_affine_coordinates_GFp (curve, p, x, y, nullptr); @@ -541,36 +529,25 @@ namespace crypto CBCEncryption encryption; encryption.SetKey (shared); encryption.SetIV (iv); - if (zeroPadding) - { - encrypted[257] = 0; - encryption.Encrypt (m, 256, encrypted + 258); - } - else - encryption.Encrypt (m, 256, encrypted + 256); + encrypted[257] = 0; + encryption.Encrypt (m, 256, encrypted + 258); EC_POINT_free (p); BN_CTX_end (ctx); + BN_CTX_free (ctx); } - bool ECIESDecrypt (const EC_GROUP * curve, const BIGNUM * key, const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding) + bool ECIESDecrypt (const EC_GROUP * curve, const BIGNUM * key, const uint8_t * encrypted, uint8_t * data) { bool ret = true; + BN_CTX * ctx = BN_CTX_new (); BN_CTX_start (ctx); BIGNUM * q = BN_CTX_get (ctx); EC_GROUP_get_order(curve, q, ctx); int len = BN_num_bytes (q); // point for shared secret BIGNUM * x = BN_CTX_get (ctx), * y = BN_CTX_get (ctx); - if (zeroPadding) - { - BN_bin2bn (encrypted + 1, len, x); - BN_bin2bn (encrypted + 1 + len, len, y); - } - else - { - BN_bin2bn (encrypted, len, x); - BN_bin2bn (encrypted + len, len, y); - } + BN_bin2bn (encrypted + 1, len, x); + BN_bin2bn (encrypted + 1 + len, len, y); auto p = EC_POINT_new (curve); if (EC_POINT_set_affine_coordinates_GFp (curve, p, x, y, nullptr)) { @@ -587,10 +564,7 @@ namespace crypto CBCDecryption decryption; decryption.SetKey (shared); decryption.SetIV (iv); - if (zeroPadding) - decryption.Decrypt (encrypted + 258, 256, m); - else - decryption.Decrypt (encrypted + 256, 256, m); + decryption.Decrypt (encrypted + 258, 256, m); // verify and copy uint8_t hash[32]; SHA256 (m + 33, 222, hash); @@ -610,6 +584,7 @@ namespace crypto EC_POINT_free (p); BN_CTX_end (ctx); + BN_CTX_free (ctx); return ret; } @@ -1302,7 +1277,7 @@ namespace crypto EVP_PKEY_CTX_set1_hkdf_key (pctx, tempKey, len); } if (info.length () > 0) - EVP_PKEY_CTX_add1_hkdf_info (pctx, info.c_str (), info.length ()); + EVP_PKEY_CTX_add1_hkdf_info (pctx, (const uint8_t *)info.c_str (), info.length ()); EVP_PKEY_derive (pctx, out, &outLen); EVP_PKEY_CTX_free (pctx); #else diff --git a/libi2pd/Crypto.h b/libi2pd/Crypto.h index a6c64c70..75942905 100644 --- a/libi2pd/Crypto.h +++ b/libi2pd/Crypto.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -39,7 +39,9 @@ # define OPENSSL_HKDF 1 # define OPENSSL_EDDSA 1 # define OPENSSL_X25519 1 -# define OPENSSL_SIPHASH 1 +# if (OPENSSL_VERSION_NUMBER < 0x030000000) // 3.0.0, regression in SipHash +# define OPENSSL_SIPHASH 1 +# endif # endif # if !defined OPENSSL_NO_CHACHA && !defined OPENSSL_NO_POLY1305 // some builds might not include them # define OPENSSL_AEAD_CHACHA20_POLY1305 1 @@ -108,13 +110,13 @@ namespace crypto }; // ElGamal - void ElGamalEncrypt (const uint8_t * key, const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding = false); - bool ElGamalDecrypt (const uint8_t * key, const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding = false); + void ElGamalEncrypt (const uint8_t * key, const uint8_t * data, uint8_t * encrypted); // 222 bytes data, 514 bytes encrypted + bool ElGamalDecrypt (const uint8_t * key, const uint8_t * encrypted, uint8_t * data); // 514 bytes encrypted, 222 data void GenerateElGamalKeyPair (uint8_t * priv, uint8_t * pub); // ECIES - void ECIESEncrypt (const EC_GROUP * curve, const EC_POINT * key, const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding = false); // 222 bytes data, 514 bytes encrypted with zeropadding, 512 without - bool ECIESDecrypt (const EC_GROUP * curve, const BIGNUM * key, const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding = false); + void ECIESEncrypt (const EC_GROUP * curve, const EC_POINT * key, const uint8_t * data, uint8_t * encrypted); // 222 bytes data, 514 bytes encrypted + bool ECIESDecrypt (const EC_GROUP * curve, const BIGNUM * key, const uint8_t * encrypted, uint8_t * data); // 514 bytes encrypted, 222 data void GenerateECIESKeyPair (const EC_GROUP * curve, BIGNUM *& priv, EC_POINT *& pub); // HMAC diff --git a/libi2pd/CryptoKey.cpp b/libi2pd/CryptoKey.cpp index ad93d386..ad986129 100644 --- a/libi2pd/CryptoKey.cpp +++ b/libi2pd/CryptoKey.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -20,10 +20,9 @@ namespace crypto memcpy (m_PublicKey, pub, 256); } - void ElGamalEncryptor::Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding) + void ElGamalEncryptor::Encrypt (const uint8_t * data, uint8_t * encrypted) { - if (!ctx) return; - ElGamalEncrypt (m_PublicKey, data, encrypted, ctx, zeroPadding); + ElGamalEncrypt (m_PublicKey, data, encrypted); } ElGamalDecryptor::ElGamalDecryptor (const uint8_t * priv) @@ -31,10 +30,9 @@ namespace crypto memcpy (m_PrivateKey, priv, 256); } - bool ElGamalDecryptor::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding) + bool ElGamalDecryptor::Decrypt (const uint8_t * encrypted, uint8_t * data) { - if (!ctx) return false; - return ElGamalDecrypt (m_PrivateKey, encrypted, data, ctx, zeroPadding); + return ElGamalDecrypt (m_PrivateKey, encrypted, data); } ECIESP256Encryptor::ECIESP256Encryptor (const uint8_t * pub) @@ -54,10 +52,10 @@ namespace crypto if (m_PublicKey) EC_POINT_free (m_PublicKey); } - void ECIESP256Encryptor::Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding) + void ECIESP256Encryptor::Encrypt (const uint8_t * data, uint8_t * encrypted) { if (m_Curve && m_PublicKey) - ECIESEncrypt (m_Curve, m_PublicKey, data, encrypted, ctx, zeroPadding); + ECIESEncrypt (m_Curve, m_PublicKey, data, encrypted); } ECIESP256Decryptor::ECIESP256Decryptor (const uint8_t * priv) @@ -72,10 +70,10 @@ namespace crypto if (m_PrivateKey) BN_free (m_PrivateKey); } - bool ECIESP256Decryptor::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding) + bool ECIESP256Decryptor::Decrypt (const uint8_t * encrypted, uint8_t * data) { if (m_Curve && m_PrivateKey) - return ECIESDecrypt (m_Curve, m_PrivateKey, encrypted, data, ctx, zeroPadding); + return ECIESDecrypt (m_Curve, m_PrivateKey, encrypted, data); return false; } @@ -114,10 +112,10 @@ namespace crypto if (m_PublicKey) EC_POINT_free (m_PublicKey); } - void ECIESGOSTR3410Encryptor::Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding) + void ECIESGOSTR3410Encryptor::Encrypt (const uint8_t * data, uint8_t * encrypted) { if (m_PublicKey) - ECIESEncrypt (GetGOSTR3410Curve (eGOSTR3410CryptoProA)->GetGroup (), m_PublicKey, data, encrypted, ctx, zeroPadding); + ECIESEncrypt (GetGOSTR3410Curve (eGOSTR3410CryptoProA)->GetGroup (), m_PublicKey, data, encrypted); } ECIESGOSTR3410Decryptor::ECIESGOSTR3410Decryptor (const uint8_t * priv) @@ -130,10 +128,10 @@ namespace crypto if (m_PrivateKey) BN_free (m_PrivateKey); } - bool ECIESGOSTR3410Decryptor::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding) + bool ECIESGOSTR3410Decryptor::Decrypt (const uint8_t * encrypted, uint8_t * data) { if (m_PrivateKey) - return ECIESDecrypt (GetGOSTR3410Curve (eGOSTR3410CryptoProA)->GetGroup (), m_PrivateKey, encrypted, data, ctx, zeroPadding); + return ECIESDecrypt (GetGOSTR3410Curve (eGOSTR3410CryptoProA)->GetGroup (), m_PrivateKey, encrypted, data); return false; } @@ -161,7 +159,7 @@ namespace crypto memcpy (m_PublicKey, pub, 32); } - void ECIESX25519AEADRatchetEncryptor::Encrypt (const uint8_t *, uint8_t * pub, BN_CTX *, bool) + void ECIESX25519AEADRatchetEncryptor::Encrypt (const uint8_t *, uint8_t * pub) { memcpy (pub, m_PublicKey, 32); } @@ -171,7 +169,7 @@ namespace crypto m_StaticKeys.SetPrivateKey (priv, calculatePublic); } - bool ECIESX25519AEADRatchetDecryptor::Decrypt (const uint8_t * epub, uint8_t * sharedSecret, BN_CTX * ctx, bool zeroPadding) + bool ECIESX25519AEADRatchetDecryptor::Decrypt (const uint8_t * epub, uint8_t * sharedSecret) { return m_StaticKeys.Agree (epub, sharedSecret); } diff --git a/libi2pd/CryptoKey.h b/libi2pd/CryptoKey.h index fb69558f..0ac0bdd5 100644 --- a/libi2pd/CryptoKey.h +++ b/libi2pd/CryptoKey.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -21,7 +21,7 @@ namespace crypto public: virtual ~CryptoKeyEncryptor () {}; - virtual void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding) = 0; // 222 bytes data, 512/514 bytes encrypted + virtual void Encrypt (const uint8_t * data, uint8_t * encrypted) = 0; }; class CryptoKeyDecryptor @@ -29,7 +29,7 @@ namespace crypto public: virtual ~CryptoKeyDecryptor () {}; - virtual bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding) = 0; // 512/514 bytes encrypted, 222 bytes data + virtual bool Decrypt (const uint8_t * encrypted, uint8_t * data) = 0; virtual size_t GetPublicKeyLen () const = 0; // we need it to set key in LS2 }; @@ -39,7 +39,7 @@ namespace crypto public: ElGamalEncryptor (const uint8_t * pub); - void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding); + void Encrypt (const uint8_t * data, uint8_t * encrypted) override; // 222 bytes data, 514 bytes encrypted private: @@ -51,8 +51,8 @@ namespace crypto public: ElGamalDecryptor (const uint8_t * priv); - bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding); - size_t GetPublicKeyLen () const { return 256; }; + bool Decrypt (const uint8_t * encrypted, uint8_t * data) override; // 514 bytes encrypted, 222 bytes data + size_t GetPublicKeyLen () const override { return 256; }; private: @@ -67,7 +67,7 @@ namespace crypto ECIESP256Encryptor (const uint8_t * pub); ~ECIESP256Encryptor (); - void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding); + void Encrypt (const uint8_t * data, uint8_t * encrypted) override; private: @@ -82,8 +82,8 @@ namespace crypto ECIESP256Decryptor (const uint8_t * priv); ~ECIESP256Decryptor (); - bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding); - size_t GetPublicKeyLen () const { return 64; }; + bool Decrypt (const uint8_t * encrypted, uint8_t * data) override; + size_t GetPublicKeyLen () const override { return 64; }; private: @@ -101,7 +101,7 @@ namespace crypto ECIESGOSTR3410Encryptor (const uint8_t * pub); ~ECIESGOSTR3410Encryptor (); - void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding); + void Encrypt (const uint8_t * data, uint8_t * encrypted) override; private: @@ -115,8 +115,8 @@ namespace crypto ECIESGOSTR3410Decryptor (const uint8_t * priv); ~ECIESGOSTR3410Decryptor (); - bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding); - size_t GetPublicKeyLen () const { return 64; }; + bool Decrypt (const uint8_t * encrypted, uint8_t * data) override; + size_t GetPublicKeyLen () const override { return 64; }; private: @@ -133,7 +133,7 @@ namespace crypto ECIESX25519AEADRatchetEncryptor (const uint8_t * pub); ~ECIESX25519AEADRatchetEncryptor () {}; - void Encrypt (const uint8_t *, uint8_t * pub, BN_CTX *, bool); + void Encrypt (const uint8_t *, uint8_t * pub) override; // copies m_PublicKey to pub private: @@ -147,9 +147,9 @@ namespace crypto ECIESX25519AEADRatchetDecryptor (const uint8_t * priv, bool calculatePublic = false); ~ECIESX25519AEADRatchetDecryptor () {}; - bool Decrypt (const uint8_t * epub, uint8_t * sharedSecret, BN_CTX * ctx, bool zeroPadding); + bool Decrypt (const uint8_t * epub, uint8_t * sharedSecret) override; // agree with static and return in sharedSecret (32 bytes) - size_t GetPublicKeyLen () const { return 32; }; + size_t GetPublicKeyLen () const override { return 32; }; const uint8_t * GetPubicKey () const { return m_StaticKeys.GetPublicKey (); }; private: diff --git a/libi2pd/Datagram.cpp b/libi2pd/Datagram.cpp index d000a9e0..4ee115cf 100644 --- a/libi2pd/Datagram.cpp +++ b/libi2pd/Datagram.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -21,6 +21,9 @@ namespace datagram DatagramDestination::DatagramDestination (std::shared_ptr owner, bool gzip): m_Owner (owner), m_Receiver (nullptr), m_RawReceiver (nullptr), m_Gzip (gzip) { + if (m_Gzip) + m_Deflator.reset (new i2p::data::GzipDeflator); + auto identityLen = m_Owner->GetIdentity ()->GetFullLen (); m_From.resize (identityLen); m_Owner->GetIdentity ()->ToBuffer (m_From.data (), identityLen); @@ -152,11 +155,16 @@ namespace datagram const std::vector >& payloads, uint16_t fromPort, uint16_t toPort, bool isRaw, bool checksum) { + size_t size; auto msg = m_I2NPMsgsPool.AcquireShared (); uint8_t * buf = msg->GetPayload (); buf += 4; // reserve for length - size_t size = m_Gzip ? m_Deflator.Deflate (payloads, buf, msg->maxLen - msg->len) : - i2p::data::GzipNoCompression (payloads, buf, msg->maxLen - msg->len); + + if (m_Gzip && m_Deflator) + size = m_Deflator->Deflate (payloads, buf, msg->maxLen - msg->len); + else + size = i2p::data::GzipNoCompression (payloads, buf, msg->maxLen - msg->len); + if (size) { htobe32buf (msg->GetPayload (), size); // length @@ -363,8 +371,6 @@ namespace datagram { // no current path, make one path = std::make_shared(); - path->outboundTunnel = m_LocalDestination->GetTunnelPool()->GetNextOutboundTunnel(); - if (!path->outboundTunnel) return nullptr; if (m_RemoteLeaseSet) { @@ -378,6 +384,11 @@ namespace datagram } else return nullptr; + + auto leaseRouter = i2p::data::netdb.FindRouter (path->remoteLease->tunnelGateway); + path->outboundTunnel = m_LocalDestination->GetTunnelPool()->GetNextOutboundTunnel(nullptr, + leaseRouter ? leaseRouter->GetCompatibleTransports (false) : (i2p::data::RouterInfo::CompatibleTransports)i2p::data::RouterInfo::eAllTransports); + if (!path->outboundTunnel) return nullptr; } else { diff --git a/libi2pd/Datagram.h b/libi2pd/Datagram.h index 5dd6c8b6..564eb10b 100644 --- a/libi2pd/Datagram.h +++ b/libi2pd/Datagram.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -164,7 +164,7 @@ namespace datagram std::map m_ReceiversByPorts; i2p::data::GzipInflator m_Inflator; - i2p::data::GzipDeflator m_Deflator; + std::unique_ptr m_Deflator; std::vector m_From, m_Signature; i2p::util::MemoryPool > m_I2NPMsgsPool; }; diff --git a/libi2pd/Destination.cpp b/libi2pd/Destination.cpp index 7bcfe111..9341baa4 100644 --- a/libi2pd/Destination.cpp +++ b/libi2pd/Destination.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -345,10 +345,11 @@ namespace client void LeaseSetDestination::HandleI2NPMessage (const uint8_t * buf, size_t len) { I2NPMessageType typeID = (I2NPMessageType)(buf[I2NP_HEADER_TYPEID_OFFSET]); - LeaseSetDestination::HandleCloveI2NPMessage (typeID, buf + I2NP_HEADER_SIZE, GetI2NPMessageLength(buf, len) - I2NP_HEADER_SIZE); + uint32_t msgID = bufbe32toh (buf + I2NP_HEADER_MSGID_OFFSET); + LeaseSetDestination::HandleCloveI2NPMessage (typeID, buf + I2NP_HEADER_SIZE, GetI2NPMessageLength(buf, len) - I2NP_HEADER_SIZE, msgID); } - bool LeaseSetDestination::HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len) + bool LeaseSetDestination::HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID) { switch (typeID) { @@ -365,6 +366,9 @@ namespace client case eI2NPDatabaseSearchReply: HandleDatabaseSearchReplyMessage (payload, len); break; + case eI2NPShortTunnelBuildReply: // might come as garlic encrypted + i2p::HandleI2NPMessage (CreateI2NPMessage (typeID, payload, len, msgID)); + break; default: LogPrint (eLogWarning, "Destination: Unexpected I2NP message type ", typeID); return false; @@ -551,18 +555,6 @@ namespace client shared_from_this (), std::placeholders::_1)); return; } - auto outbound = m_Pool->GetNextOutboundTunnel (); - if (!outbound) - { - LogPrint (eLogError, "Destination: Can't publish LeaseSet. No outbound tunnels"); - return; - } - auto inbound = m_Pool->GetNextInboundTunnel (); - if (!inbound) - { - LogPrint (eLogError, "Destination: Can't publish LeaseSet. No inbound tunnels"); - return; - } auto floodfill = i2p::data::netdb.GetClosestFloodfill (leaseSet->GetIdentHash (), m_ExcludedFloodfills); if (!floodfill) { @@ -570,6 +562,18 @@ namespace client m_ExcludedFloodfills.clear (); return; } + auto outbound = m_Pool->GetNextOutboundTunnel (nullptr, floodfill->GetCompatibleTransports (false)); + if (!outbound) + { + LogPrint (eLogError, "Destination: Can't publish LeaseSet. No outbound tunnels"); + return; + } + auto inbound = m_Pool->GetNextInboundTunnel (nullptr, floodfill->GetCompatibleTransports (true)); + if (!inbound) + { + LogPrint (eLogError, "Destination: Can't publish LeaseSet. No inbound tunnels"); + return; + } m_ExcludedFloodfills.insert (floodfill->GetIdentHash ()); LogPrint (eLogDebug, "Destination: Publish LeaseSet of ", GetIdentHash ().ToBase32 ()); RAND_bytes ((uint8_t *)&m_PublishReplyToken, 4); @@ -747,10 +751,10 @@ namespace client std::shared_ptr nextFloodfill, std::shared_ptr request) { if (!request->replyTunnel || !request->replyTunnel->IsEstablished ()) - request->replyTunnel = m_Pool->GetNextInboundTunnel (); + request->replyTunnel = m_Pool->GetNextInboundTunnel (nullptr, nextFloodfill->GetCompatibleTransports (true)); if (!request->replyTunnel) LogPrint (eLogError, "Destination: Can't send LeaseSet request, no inbound tunnels found"); if (!request->outboundTunnel || !request->outboundTunnel->IsEstablished ()) - request->outboundTunnel = m_Pool->GetNextOutboundTunnel (); + request->outboundTunnel = m_Pool->GetNextOutboundTunnel (nullptr, nextFloodfill->GetCompatibleTransports (false)); if (!request->outboundTunnel) LogPrint (eLogError, "Destination: Can't send LeaseSet request, no outbound tunnels found"); if (request->replyTunnel && request->outboundTunnel) @@ -1092,6 +1096,35 @@ namespace client return nullptr; } + void ClientDestination::SendPing (const i2p::data::IdentHash& to) + { + if (m_StreamingDestination) + { + auto leaseSet = FindLeaseSet (to); + if (leaseSet) + m_StreamingDestination->SendPing (leaseSet); + else + { + auto s = m_StreamingDestination; + RequestDestination (to, + [s](std::shared_ptr ls) + { + if (ls) s->SendPing (ls); + }); + } + } + } + + void ClientDestination::SendPing (std::shared_ptr to) + { + auto s = m_StreamingDestination; + RequestDestinationWithEncryptedLeaseSet (to, + [s](std::shared_ptr ls) + { + if (ls) s->SendPing (ls); + }); + } + std::shared_ptr ClientDestination::GetStreamingDestination (int port) const { if (port) @@ -1241,13 +1274,13 @@ namespace client if (m_DatagramDestination) m_DatagramDestination->CleanUp (); } - bool ClientDestination::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, i2p::data::CryptoKeyType preferredCrypto) const + bool ClientDestination::Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const { if (preferredCrypto == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) if (m_ECIESx25519EncryptionKey && m_ECIESx25519EncryptionKey->decryptor) - return m_ECIESx25519EncryptionKey->decryptor->Decrypt (encrypted, data, ctx, true); + return m_ECIESx25519EncryptionKey->decryptor->Decrypt (encrypted, data); if (m_StandardEncryptionKey && m_StandardEncryptionKey->decryptor) - return m_StandardEncryptionKey->decryptor->Decrypt (encrypted, data, ctx, true); + return m_StandardEncryptionKey->decryptor->Decrypt (encrypted, data); else LogPrint (eLogError, "Destinations: decryptor is not set"); return false; diff --git a/libi2pd/Destination.h b/libi2pd/Destination.h index 6695796d..54df8706 100644 --- a/libi2pd/Destination.h +++ b/libi2pd/Destination.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -139,12 +139,13 @@ namespace client void SetLeaseSetUpdated (); bool IsPublic () const { return m_IsPublic; }; + void SetPublic (bool pub) { m_IsPublic = pub; }; protected: // implements GarlicDestination void HandleI2NPMessage (const uint8_t * buf, size_t len); - bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len); + bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID); void SetLeaseSet (std::shared_ptr newLeaseSet); int GetLeaseSetType () const { return m_LeaseSetType; }; @@ -242,6 +243,8 @@ namespace client void CreateStream (StreamRequestComplete streamRequestComplete, const i2p::data::IdentHash& dest, int port = 0); void CreateStream (StreamRequestComplete streamRequestComplete, std::shared_ptr dest, int port = 0); std::shared_ptr CreateStream (std::shared_ptr remote, int port = 0); + void SendPing (const i2p::data::IdentHash& to); + void SendPing (std::shared_ptr to); void AcceptStreams (const i2p::stream::StreamingDestination::Acceptor& acceptor); void StopAcceptingStreams (); bool IsAcceptingStreams () const; @@ -254,7 +257,7 @@ namespace client i2p::datagram::DatagramDestination * CreateDatagramDestination (bool gzip = true); // implements LocalDestination - bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, i2p::data::CryptoKeyType preferredCrypto) const; + bool Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const; std::shared_ptr GetIdentity () const { return m_Keys.GetPublic (); }; bool SupportsEncryptionType (i2p::data::CryptoKeyType keyType) const; const uint8_t * GetEncryptionPublicKey (i2p::data::CryptoKeyType keyType) const; diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index 45affde7..88d59c33 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -31,16 +31,16 @@ namespace garlic uint8_t keydata[64]; i2p::crypto::HKDF (rootKey, k, 32, "KDFDHRatchetStep", keydata); // keydata = HKDF(rootKey, k, "KDFDHRatchetStep", 64) memcpy (m_NextRootKey, keydata, 32); // nextRootKey = keydata[0:31] - i2p::crypto::HKDF (keydata + 32, nullptr, 0, "TagAndKeyGenKeys", m_KeyData.buf); + i2p::crypto::HKDF (keydata + 32, nullptr, 0, "TagAndKeyGenKeys", m_SessionTagKeyData); // [sessTag_ck, symmKey_ck] = HKDF(keydata[32:63], ZEROLEN, "TagAndKeyGenKeys", 64) - memcpy (m_SymmKeyCK, m_KeyData.buf + 32, 32); + memcpy (m_SymmKeyCK, (const uint8_t *)m_SessionTagKeyData + 32, 32); m_NextSymmKeyIndex = 0; } void RatchetTagSet::NextSessionTagRatchet () { - i2p::crypto::HKDF (m_KeyData.GetSessTagCK (), nullptr, 0, "STInitialization", m_KeyData.buf); // [sessTag_ck, sesstag_constant] = HKDF(sessTag_ck, ZEROLEN, "STInitialization", 64) - memcpy (m_SessTagConstant, m_KeyData.GetSessTagConstant (), 32); + i2p::crypto::HKDF (m_SessionTagKeyData, nullptr, 0, "STInitialization", m_SessionTagKeyData); // [sessTag_ck, sesstag_constant] = HKDF(sessTag_ck, ZEROLEN, "STInitialization", 64) + memcpy (m_SessTagConstant, (const uint8_t *)m_SessionTagKeyData + 32, 32); // SESSTAG_CONSTANT = keydata[32:63] m_NextIndex = 0; } @@ -52,8 +52,8 @@ namespace garlic LogPrint (eLogError, "Garlic: Tagset ", GetTagSetID (), " is empty"); return 0; } - i2p::crypto::HKDF (m_KeyData.GetSessTagCK (), m_SessTagConstant, 32, "SessionTagKeyGen", m_KeyData.buf); // [sessTag_ck, tag] = HKDF(sessTag_chainkey, SESSTAG_CONSTANT, "SessionTagKeyGen", 64) - return m_KeyData.GetTag (); + i2p::crypto::HKDF (m_SessionTagKeyData, m_SessTagConstant, 32, "SessionTagKeyGen", m_SessionTagKeyData); // [sessTag_ck, tag] = HKDF(sessTag_chainkey, SESSTAG_CONSTANT, "SessionTagKeyGen", 64) + return m_SessionTagKeyData.GetLL ()[4]; // tag = keydata[32:39] } void RatchetTagSet::GetSymmKey (int index, uint8_t * key) @@ -239,7 +239,7 @@ namespace garlic MixHash (m_Aepk, 32); // h = SHA256(h || aepk) uint8_t sharedSecret[32]; - if (!GetOwner ()->Decrypt (m_Aepk, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) // x25519(bsk, aepk) + if (!GetOwner ()->Decrypt (m_Aepk, sharedSecret, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) // x25519(bsk, aepk) { LogPrint (eLogWarning, "Garlic: Incorrect Alice ephemeral key"); return false; @@ -263,7 +263,7 @@ namespace garlic { // static key, fs is apk memcpy (m_RemoteStaticKey, fs, 32); - if (!GetOwner ()->Decrypt (fs, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) // x25519(bsk, apk) + if (!GetOwner ()->Decrypt (fs, sharedSecret, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) // x25519(bsk, apk) { LogPrint (eLogWarning, "Garlic: Incorrect Alice static key"); return false; @@ -492,7 +492,7 @@ namespace garlic // KDF2 if (isStatic) { - GetOwner ()->Decrypt (m_RemoteStaticKey, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); // x25519 (ask, bpk) + GetOwner ()->Decrypt (m_RemoteStaticKey, sharedSecret, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); // x25519 (ask, bpk) MixKey (sharedSecret); } else @@ -518,36 +518,7 @@ namespace garlic } return true; } - - bool ECIESX25519AEADRatchetSession::NewOutgoingMessageForRouter (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen) - { - // we are Alice, router's bpk is m_RemoteStaticKey - i2p::crypto::InitNoiseNState (GetNoiseState (), m_RemoteStaticKey); - size_t offset = 0; - m_EphemeralKeys = i2p::transport::transports.GetNextX25519KeysPair (); - memcpy (out + offset, m_EphemeralKeys->GetPublicKey (), 32); - MixHash (out + offset, 32); // h = SHA256(h || aepk) - offset += 32; - uint8_t sharedSecret[32]; - if (!m_EphemeralKeys->Agree (m_RemoteStaticKey, sharedSecret)) // x25519(aesk, bpk) - { - LogPrint (eLogWarning, "Garlic: Incorrect Bob static key"); - return false; - } - MixKey (sharedSecret); - uint8_t nonce[12]; - CreateNonce (0, nonce); - // encrypt payload - if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, m_H, 32, m_CK + 32, nonce, out + offset, len + 16, true)) // encrypt - { - LogPrint (eLogWarning, "Garlic: Payload for router AEAD encryption failed"); - return false; - } - - m_State = eSessionStateNewSessionSent; - return true; - } - + bool ECIESX25519AEADRatchetSession::NewSessionReplyMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen) { // we are Bob @@ -563,7 +534,7 @@ namespace garlic LogPrint (eLogError, "Garlic: Can't encode elligator"); return false; } - memcpy (m_NSREncodedKey, out + offset, 56); // for possible next NSR + memcpy (m_NSREncodedKey, out + offset, 32); // for possible next NSR memcpy (m_NSRH, m_H, 32); offset += 32; // KDF for Reply Key Section @@ -668,7 +639,7 @@ namespace garlic return false; } MixKey (sharedSecret); - GetOwner ()->Decrypt (bepk, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); // x25519 (ask, bepk) + GetOwner ()->Decrypt (bepk, sharedSecret, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); // x25519 (ask, bepk) MixKey (sharedSecret); uint8_t nonce[12]; @@ -824,8 +795,9 @@ namespace garlic std::shared_ptr ECIESX25519AEADRatchetSession::WrapSingleMessage (std::shared_ptr msg) { - auto payload = CreatePayload (msg, m_State != eSessionStateEstablished); - size_t len = payload.size (); + uint8_t * payload = GetOwner ()->GetPayloadBuffer (); + if (!payload) return nullptr; + size_t len = CreatePayload (msg, m_State != eSessionStateEstablished, payload); if (!len) return nullptr; auto m = NewI2NPMessage (len + 100); // 96 + 4 m->Align (12); // in order to get buf aligned to 16 (12 + 4) @@ -834,35 +806,30 @@ namespace garlic switch (m_State) { case eSessionStateEstablished: - if (!NewExistingSessionMessage (payload.data (), payload.size (), buf, m->maxLen)) + if (!NewExistingSessionMessage (payload, len, buf, m->maxLen)) return nullptr; len += 24; break; case eSessionStateNew: - if (!NewOutgoingSessionMessage (payload.data (), payload.size (), buf, m->maxLen)) + if (!NewOutgoingSessionMessage (payload, len, buf, m->maxLen)) return nullptr; len += 96; break; case eSessionStateNewSessionReceived: - if (!NewSessionReplyMessage (payload.data (), payload.size (), buf, m->maxLen)) + if (!NewSessionReplyMessage (payload, len, buf, m->maxLen)) return nullptr; len += 72; break; case eSessionStateNewSessionReplySent: - if (!NextNewSessionReplyMessage (payload.data (), payload.size (), buf, m->maxLen)) + if (!NextNewSessionReplyMessage (payload, len, buf, m->maxLen)) return nullptr; len += 72; break; case eSessionStateOneTime: - if (!NewOutgoingSessionMessage (payload.data (), payload.size (), buf, m->maxLen, false)) + if (!NewOutgoingSessionMessage (payload, len, buf, m->maxLen, false)) return nullptr; len += 96; break; - case eSessionStateForRouter: - if (!NewOutgoingMessageForRouter (payload.data (), payload.size (), buf, m->maxLen)) - return nullptr; - len += 48; - break; default: return nullptr; } @@ -873,13 +840,13 @@ namespace garlic return m; } - std::shared_ptr ECIESX25519AEADRatchetSession::WrapOneTimeMessage (std::shared_ptr msg, bool isForRouter) + std::shared_ptr ECIESX25519AEADRatchetSession::WrapOneTimeMessage (std::shared_ptr msg) { - m_State = isForRouter ? eSessionStateForRouter : eSessionStateOneTime; + m_State = eSessionStateOneTime; return WrapSingleMessage (msg); } - std::vector ECIESX25519AEADRatchetSession::CreatePayload (std::shared_ptr msg, bool first) + size_t ECIESX25519AEADRatchetSession::CreatePayload (std::shared_ptr msg, bool first, uint8_t * payload) { uint64_t ts = i2p::util::GetMillisecondsSinceEpoch (); size_t payloadLen = 0; @@ -941,89 +908,93 @@ namespace garlic payloadLen += paddingSize + 3; } } - std::vector v(payloadLen); if (payloadLen) - { + { + if (payloadLen > I2NP_MAX_MESSAGE_SIZE) + { + LogPrint (eLogError, "Garlic: payload length ", payloadLen, " is too long"); + return 0; + } m_LastSentTimestamp = ts; size_t offset = 0; // DateTime if (first) { - v[offset] = eECIESx25519BlkDateTime; offset++; - htobe16buf (v.data () + offset, 4); offset += 2; - htobe32buf (v.data () + offset, ts/1000); offset += 4; // in seconds + payload[offset] = eECIESx25519BlkDateTime; offset++; + htobe16buf (payload + offset, 4); offset += 2; + htobe32buf (payload + offset, ts/1000); offset += 4; // in seconds } // LeaseSet if (leaseSet) { - offset += CreateLeaseSetClove (leaseSet, ts, v.data () + offset, payloadLen - offset); + offset += CreateLeaseSetClove (leaseSet, ts, payload + offset, payloadLen - offset); if (!first) { // ack request - v[offset] = eECIESx25519BlkAckRequest; offset++; - htobe16buf (v.data () + offset, 1); offset += 2; - v[offset] = 0; offset++; // flags + payload[offset] = eECIESx25519BlkAckRequest; offset++; + htobe16buf (payload + offset, 1); offset += 2; + payload[offset] = 0; offset++; // flags } } // msg if (msg) - offset += CreateGarlicClove (msg, v.data () + offset, payloadLen - offset); + offset += CreateGarlicClove (msg, payload + offset, payloadLen - offset); // ack if (m_AckRequests.size () > 0) { - v[offset] = eECIESx25519BlkAck; offset++; - htobe16buf (v.data () + offset, m_AckRequests.size () * 4); offset += 2; + payload[offset] = eECIESx25519BlkAck; offset++; + htobe16buf (payload + offset, m_AckRequests.size () * 4); offset += 2; for (auto& it: m_AckRequests) { - htobe16buf (v.data () + offset, it.first); offset += 2; - htobe16buf (v.data () + offset, it.second); offset += 2; + htobe16buf (payload + offset, it.first); offset += 2; + htobe16buf (payload + offset, it.second); offset += 2; } m_AckRequests.clear (); } // next keys if (m_SendReverseKey) { - v[offset] = eECIESx25519BlkNextKey; offset++; - htobe16buf (v.data () + offset, m_NextReceiveRatchet->newKey ? 35 : 3); offset += 2; - v[offset] = ECIESX25519_NEXT_KEY_REVERSE_KEY_FLAG; + payload[offset] = eECIESx25519BlkNextKey; offset++; + htobe16buf (payload + offset, m_NextReceiveRatchet->newKey ? 35 : 3); offset += 2; + payload[offset] = ECIESX25519_NEXT_KEY_REVERSE_KEY_FLAG; int keyID = m_NextReceiveRatchet->keyID - 1; if (m_NextReceiveRatchet->newKey) { - v[offset] |= ECIESX25519_NEXT_KEY_KEY_PRESENT_FLAG; + payload[offset] |= ECIESX25519_NEXT_KEY_KEY_PRESENT_FLAG; keyID++; } offset++; // flag - htobe16buf (v.data () + offset, keyID); offset += 2; // keyid + htobe16buf (payload + offset, keyID); offset += 2; // keyid if (m_NextReceiveRatchet->newKey) { - memcpy (v.data () + offset, m_NextReceiveRatchet->key->GetPublicKey (), 32); + memcpy (payload + offset, m_NextReceiveRatchet->key->GetPublicKey (), 32); offset += 32; // public key } m_SendReverseKey = false; } if (m_SendForwardKey) { - v[offset] = eECIESx25519BlkNextKey; offset++; - htobe16buf (v.data () + offset, m_NextSendRatchet->newKey ? 35 : 3); offset += 2; - v[offset] = m_NextSendRatchet->newKey ? ECIESX25519_NEXT_KEY_KEY_PRESENT_FLAG : ECIESX25519_NEXT_KEY_REQUEST_REVERSE_KEY_FLAG; - if (!m_NextSendRatchet->keyID) v[offset] |= ECIESX25519_NEXT_KEY_REQUEST_REVERSE_KEY_FLAG; // for first key only + payload[offset] = eECIESx25519BlkNextKey; offset++; + htobe16buf (payload + offset, m_NextSendRatchet->newKey ? 35 : 3); offset += 2; + payload[offset] = m_NextSendRatchet->newKey ? ECIESX25519_NEXT_KEY_KEY_PRESENT_FLAG : ECIESX25519_NEXT_KEY_REQUEST_REVERSE_KEY_FLAG; + if (!m_NextSendRatchet->keyID) payload[offset] |= ECIESX25519_NEXT_KEY_REQUEST_REVERSE_KEY_FLAG; // for first key only offset++; // flag - htobe16buf (v.data () + offset, m_NextSendRatchet->keyID); offset += 2; // keyid + htobe16buf (payload + offset, m_NextSendRatchet->keyID); offset += 2; // keyid if (m_NextSendRatchet->newKey) { - memcpy (v.data () + offset, m_NextSendRatchet->key->GetPublicKey (), 32); + memcpy (payload + offset, m_NextSendRatchet->key->GetPublicKey (), 32); offset += 32; // public key } } // padding if (paddingSize) { - v[offset] = eECIESx25519BlkPadding; offset++; - htobe16buf (v.data () + offset, paddingSize); offset += 2; - memset (v.data () + offset, 0, paddingSize); offset += paddingSize; + payload[offset] = eECIESx25519BlkPadding; offset++; + htobe16buf (payload + offset, paddingSize); offset += 2; + memset (payload + offset, 0, paddingSize); offset += paddingSize; } } - return v; + return payloadLen; } size_t ECIESX25519AEADRatchetSession::CreateGarlicClove (std::shared_ptr msg, uint8_t * buf, size_t len) @@ -1109,21 +1080,22 @@ namespace garlic bool RouterIncomingRatchetSession::HandleNextMessage (const uint8_t * buf, size_t len) { if (!GetOwner ()) return false; - i2p::crypto::NoiseSymmetricState state (GetNoiseState ()); + m_CurrentNoiseState = GetNoiseState (); // we are Bob - state.MixHash (buf, 32); + m_CurrentNoiseState.MixHash (buf, 32); uint8_t sharedSecret[32]; - if (!GetOwner ()->Decrypt (buf, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) // x25519(bsk, aepk) + if (!GetOwner ()->Decrypt (buf, sharedSecret, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) // x25519(bsk, aepk) { LogPrint (eLogWarning, "Garlic: Incorrect N ephemeral public key"); return false; } - state.MixKey (sharedSecret); + m_CurrentNoiseState.MixKey (sharedSecret); buf += 32; len -= 32; uint8_t nonce[12]; CreateNonce (0, nonce); std::vector payload (len - 16); - if (!i2p::crypto::AEADChaCha20Poly1305 (buf, len - 16, state.m_H, 32, state.m_CK + 32, nonce, payload.data (), len - 16, false)) // decrypt + if (!i2p::crypto::AEADChaCha20Poly1305 (buf, len - 16, m_CurrentNoiseState.m_H, 32, + m_CurrentNoiseState.m_CK + 32, nonce, payload.data (), len - 16, false)) // decrypt { LogPrint (eLogWarning, "Garlic: Payload for router AEAD verification failed"); return false; @@ -1131,40 +1103,108 @@ namespace garlic HandlePayload (payload.data (), len - 16, nullptr, 0); return true; } + + static size_t CreateGarlicPayload (std::shared_ptr msg, uint8_t * payload, + bool datetime, size_t optimalSize) + { + size_t len = 0; + if (datetime) + { + // DateTime + payload[0] = eECIESx25519BlkDateTime; + htobe16buf (payload + 1, 4); + htobe32buf (payload + 3, i2p::util::GetSecondsSinceEpoch ()); + len = 7; + } + // I2NP + payload += len; + uint16_t cloveSize = msg->GetPayloadLength () + 10; + payload[0] = eECIESx25519BlkGalicClove; // clove type + htobe16buf (payload + 1, cloveSize); // size + payload += 3; + payload[0] = 0; // flag and delivery instructions + payload[1] = msg->GetTypeID (); // I2NP msg type + htobe32buf (payload + 2, msg->GetMsgID ()); // msgID + htobe32buf (payload + 6, msg->GetExpiration () / 1000); // expiration in seconds + memcpy (payload + 10, msg->GetPayload (), msg->GetPayloadLength ()); + len += cloveSize + 3; + payload += cloveSize; + // padding + int delta = (int)optimalSize - (int)len; + if (delta < 0 || delta > 3) // don't create padding if we are close to optimal size + { + uint8_t paddingSize = rand () & 0x0F; // 0 - 15 + if (delta > 3) + { + delta -= 3; + if (paddingSize > delta) paddingSize %= delta; + } + payload[0] = eECIESx25519BlkPadding; + htobe16buf (payload + 1, paddingSize); + if (paddingSize) memset (payload + 3, 0, paddingSize); + len += paddingSize + 3; + } + return len; + } - std::shared_ptr WrapECIESX25519AEADRatchetMessage (std::shared_ptr msg, const uint8_t * key, uint64_t tag) + std::shared_ptr WrapECIESX25519Message (std::shared_ptr msg, const uint8_t * key, uint64_t tag) { auto m = NewI2NPMessage (); m->Align (12); // in order to get buf aligned to 16 (12 + 4) uint8_t * buf = m->GetPayload () + 4; // 4 bytes for length - uint8_t nonce[12]; - memset (nonce, 0, 12); // n = 0 size_t offset = 0; memcpy (buf + offset, &tag, 8); offset += 8; auto payload = buf + offset; - uint16_t cloveSize = msg->GetPayloadLength () + 9 + 1; - size_t len = cloveSize + 3; - payload[0] = eECIESx25519BlkGalicClove; // clove type - htobe16buf (payload + 1, cloveSize); // size - payload += 3; - *payload = 0; payload++; // flag and delivery instructions - *payload = msg->GetTypeID (); // I2NP msg type - htobe32buf (payload + 1, msg->GetMsgID ()); // msgID - htobe32buf (payload + 5, msg->GetExpiration () / 1000); // expiration in seconds - memcpy (payload + 9, msg->GetPayload (), msg->GetPayloadLength ()); - - if (!i2p::crypto::AEADChaCha20Poly1305 (buf + offset, len, buf, 8, key, nonce, buf + offset, len + 16, true)) // encrypt + size_t len = CreateGarlicPayload (msg, payload, false, 956); // 1003 - 8 tag - 16 Poly1305 hash - 16 I2NP header - 4 garlic length - 3 local tunnel delivery + uint8_t nonce[12]; + memset (nonce, 0, 12); // n = 0 + if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, buf, 8, key, nonce, payload, len + 16, true)) // encrypt { LogPrint (eLogWarning, "Garlic: Payload section AEAD encryption failed"); return nullptr; } offset += len + 16; - htobe32buf (m->GetPayload (), offset); m->len += offset + 4; m->FillI2NPMessageHeader (eI2NPGarlic); return m; } + std::shared_ptr WrapECIESX25519MessageForRouter (std::shared_ptr msg, const uint8_t * routerPublicKey) + { + // Noise_N, we are Alice, routerPublicKey is Bob's + i2p::crypto::NoiseSymmetricState noiseState; + i2p::crypto::InitNoiseNState (noiseState, routerPublicKey); + auto m = NewI2NPMessage (); + m->Align (12); // in order to get buf aligned to 16 (12 + 4) + uint8_t * buf = m->GetPayload () + 4; // 4 bytes for length + size_t offset = 0; + auto ephemeralKeys = i2p::transport::transports.GetNextX25519KeysPair (); + memcpy (buf + offset, ephemeralKeys->GetPublicKey (), 32); + noiseState.MixHash (buf + offset, 32); // h = SHA256(h || aepk) + offset += 32; + uint8_t sharedSecret[32]; + if (!ephemeralKeys->Agree (routerPublicKey, sharedSecret)) // x25519(aesk, bpk) + { + LogPrint (eLogWarning, "Garlic: Incorrect Bob static key"); + return nullptr; + } + noiseState.MixKey (sharedSecret); + auto payload = buf + offset; + size_t len = CreateGarlicPayload (msg, payload, true, 900); // 1003 - 32 eph key - 16 Poly1305 hash - 16 I2NP header - 4 garlic length - 35 router tunnel delivery + uint8_t nonce[12]; + memset (nonce, 0, 12); + // encrypt payload + if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, noiseState.m_H, 32, noiseState.m_CK + 32, nonce, payload, len + 16, true)) // encrypt + { + LogPrint (eLogWarning, "Garlic: Payload for router AEAD encryption failed"); + return nullptr; + } + offset += len + 16; + htobe32buf (m->GetPayload (), offset); + m->len += offset + 4; + m->FillI2NPMessageHeader (eI2NPGarlic); + return m; + } } } diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index 43e88ca6..5fd2c929 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -1,5 +1,6 @@ + /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -59,16 +60,7 @@ namespace garlic private: - union - { - uint64_t ll[8]; - uint8_t buf[64]; - - const uint8_t * GetSessTagCK () const { return buf; }; // sessTag_chainKey = keydata[0:31] - const uint8_t * GetSessTagConstant () const { return buf + 32; }; // SESSTAG_CONSTANT = keydata[32:63] - uint64_t GetTag () const { return ll[4]; }; // tag = keydata[32:39] - - } m_KeyData; + i2p::data::Tag<64> m_SessionTagKeyData; uint8_t m_SessTagConstant[32], m_SymmKeyCK[32], m_CurrentSymmKeyCK[64], m_NextRootKey[32]; int m_NextIndex, m_NextSymmKeyIndex; std::unordered_map > m_ItermediateSymmKeys; @@ -147,8 +139,7 @@ namespace garlic eSessionStateNewSessionSent, eSessionStateNewSessionReplySent, eSessionStateEstablished, - eSessionStateOneTime, - eSessionStateForRouter + eSessionStateOneTime }; struct DHRatchet @@ -166,7 +157,7 @@ namespace garlic bool HandleNextMessage (uint8_t * buf, size_t len, std::shared_ptr receiveTagset, int index = 0); std::shared_ptr WrapSingleMessage (std::shared_ptr msg); - std::shared_ptr WrapOneTimeMessage (std::shared_ptr msg, bool isForRouter = false); + std::shared_ptr WrapOneTimeMessage (std::shared_ptr msg); const uint8_t * GetRemoteStaticKey () const { return m_RemoteStaticKey; } void SetRemoteStaticKey (const uint8_t * key) { memcpy (m_RemoteStaticKey, key, 32); } @@ -207,9 +198,8 @@ namespace garlic bool NewSessionReplyMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); bool NextNewSessionReplyMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); bool NewExistingSessionMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); - bool NewOutgoingMessageForRouter (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); - - std::vector CreatePayload (std::shared_ptr msg, bool first); + + size_t CreatePayload (std::shared_ptr msg, bool first, uint8_t * payload); size_t CreateGarlicClove (std::shared_ptr msg, uint8_t * buf, size_t len); size_t CreateLeaseSetClove (std::shared_ptr ls, uint64_t ts, uint8_t * buf, size_t len); @@ -249,10 +239,17 @@ namespace garlic RouterIncomingRatchetSession (const i2p::crypto::NoiseSymmetricState& initState); bool HandleNextMessage (const uint8_t * buf, size_t len); + i2p::crypto::NoiseSymmetricState& GetCurrentNoiseState () { return m_CurrentNoiseState; }; + + private: + + i2p::crypto::NoiseSymmetricState m_CurrentNoiseState; }; - std::shared_ptr WrapECIESX25519AEADRatchetMessage (std::shared_ptr msg, const uint8_t * key, uint64_t tag); + std::shared_ptr WrapECIESX25519Message (std::shared_ptr msg, const uint8_t * key, uint64_t tag); + std::shared_ptr WrapECIESX25519MessageForRouter (std::shared_ptr msg, const uint8_t * routerPublicKey); } } #endif + diff --git a/libi2pd/FS.cpp b/libi2pd/FS.cpp index 6ac302b0..f6653e55 100644 --- a/libi2pd/FS.cpp +++ b/libi2pd/FS.cpp @@ -24,6 +24,7 @@ namespace i2p { namespace fs { std::string appName = "i2pd"; std::string dataDir = ""; + std::string certsDir = ""; #ifdef _WIN32 std::string dirSep = "\\"; #else @@ -42,6 +43,10 @@ namespace fs { return dataDir; } + const std::string & GetCertsDir () { + return certsDir; + } + const std::string GetUTF8DataDir () { #ifdef _WIN32 boost::filesystem::wpath path (dataDir); @@ -126,6 +131,21 @@ namespace fs { #endif } + void SetCertsDir(const std::string & cmdline_certsdir) { + if (cmdline_certsdir != "") + { + if (cmdline_certsdir[cmdline_certsdir.length()-1] == '/') + certsDir = cmdline_certsdir.substr(0, cmdline_certsdir.size()-1); // strip trailing slash + else + certsDir = cmdline_certsdir; + } + else + { + certsDir = i2p::fs::DataDirPath("certificates"); + } + return; + } + bool Init() { if (!boost::filesystem::exists(dataDir)) boost::filesystem::create_directory(dataDir); diff --git a/libi2pd/FS.h b/libi2pd/FS.h index f07ee35c..d51aa955 100644 --- a/libi2pd/FS.h +++ b/libi2pd/FS.h @@ -75,6 +75,9 @@ namespace fs { /** @brief Returns datadir path */ const std::string & GetDataDir(); + /** @brief Returns certsdir path */ + const std::string & GetCertsDir(); + /** @brief Returns datadir path in UTF-8 encoding */ const std::string GetUTF8DataDir(); @@ -90,7 +93,20 @@ namespace fs { * Mac: /Library/Application Support/i2pd/ or ~/Library/Application Support/i2pd/ * Unix: /var/lib/i2pd/ (system=1) >> ~/.i2pd/ or /tmp/i2pd/ */ - void DetectDataDir(const std::string & cmdline_datadir, bool isService = false); + void DetectDataDir(const std::string & cmdline_datadir, bool isService = false); + + /** + * @brief Set certsdir either from cmdline option or using autodetection + * @param cmdline_param Value of cmdline parameter --certsdir= + * + * Examples of autodetected paths: + * + * Windows < Vista: C:\Documents and Settings\Username\Application Data\i2pd\certificates + * Windows >= Vista: C:\Users\Username\AppData\Roaming\i2pd\certificates + * Mac: /Library/Application Support/i2pd/ or ~/Library/Application Support/i2pd/certificates + * Unix: /var/lib/i2pd/certificates (system=1) >> ~/.i2pd/ or /tmp/i2pd/certificates + */ + void SetCertsDir(const std::string & cmdline_certsdir); /** * @brief Create subdirectories inside datadir diff --git a/libi2pd/Family.cpp b/libi2pd/Family.cpp index fbb7b9ee..a6f0e2ee 100644 --- a/libi2pd/Family.cpp +++ b/libi2pd/Family.cpp @@ -13,6 +13,7 @@ #include "FS.h" #include "Log.h" #include "Family.h" +#include "Config.h" namespace i2p { @@ -98,7 +99,8 @@ namespace data void Families::LoadCertificates () { - std::string certDir = i2p::fs::DataDirPath("certificates", "family"); + std::string certDir = i2p::fs::GetCertsDir() + i2p::fs::dirSep + "family"; + std::vector files; int numCertificates = 0; diff --git a/libi2pd/Garlic.cpp b/libi2pd/Garlic.cpp index 2bf89556..ea30a249 100644 --- a/libi2pd/Garlic.cpp +++ b/libi2pd/Garlic.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -164,9 +164,7 @@ namespace garlic RAND_bytes (elGamal.preIV, 32); // Pre-IV uint8_t iv[32]; // IV is first 16 bytes SHA256(elGamal.preIV, 32, iv); - BN_CTX * ctx = BN_CTX_new (); - m_Destination->Encrypt ((uint8_t *)&elGamal, buf, ctx); - BN_CTX_free (ctx); + m_Destination->Encrypt ((uint8_t *)&elGamal, buf); m_Encryption.SetIV (iv); buf += 514; len += 514; @@ -433,14 +431,14 @@ namespace garlic } GarlicDestination::GarlicDestination (): m_NumTags (32), // 32 tags by default - m_NumRatchetInboundTags (0) // 0 means standard + m_PayloadBuffer (nullptr), m_NumRatchetInboundTags (0) // 0 means standard { - m_Ctx = BN_CTX_new (); } GarlicDestination::~GarlicDestination () { - BN_CTX_free (m_Ctx); + if (m_PayloadBuffer) + delete[] m_PayloadBuffer; } void GarlicDestination::CleanUp () @@ -471,8 +469,13 @@ namespace garlic { uint64_t t; memcpy (&t, tag, 8); + AddECIESx25519Key (key, t); + } + + void GarlicDestination::AddECIESx25519Key (const uint8_t * key, uint64_t tag) + { auto tagset = std::make_shared(this, key); - m_ECIESx25519Tags.emplace (t, ECIESX25519AEADRatchetIndexTagset{0, tagset}); + m_ECIESx25519Tags.emplace (tag, ECIESX25519AEADRatchetIndexTagset{0, tagset}); } bool GarlicDestination::SubmitSessionKey (const uint8_t * key, const uint8_t * tag) @@ -494,22 +497,9 @@ namespace garlic buf += 4; // length bool found = false; - uint64_t tag; if (SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) - { // try ECIESx25519 tag - memcpy (&tag, buf, 8); - auto it1 = m_ECIESx25519Tags.find (tag); - if (it1 != m_ECIESx25519Tags.end ()) - { - found = true; - if (it1->second.tagset->HandleNextMessage (buf, length, it1->second.index)) - m_LastTagset = it1->second.tagset; - else - LogPrint (eLogError, "Garlic: can't handle ECIES-X25519-AEAD-Ratchet message"); - m_ECIESx25519Tags.erase (it1); - } - } + found = HandleECIESx25519TagMessage (buf, length); if (!found) { auto it = !mod ? m_Tags.find (SessionTag(buf)) : m_Tags.end (); // AES block is multiple of 16 @@ -537,7 +527,7 @@ namespace garlic // try ElGamal/AES first if leading block is 514 ElGamalBlock elGamal; if (mod == 2 && length >= 514 && SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ELGAMAL) && - Decrypt (buf, (uint8_t *)&elGamal, m_Ctx, i2p::data::CRYPTO_KEY_TYPE_ELGAMAL)) + Decrypt (buf, (uint8_t *)&elGamal, i2p::data::CRYPTO_KEY_TYPE_ELGAMAL)) { auto decryption = std::make_shared(elGamal.sessionKey); uint8_t iv[32]; // IV is first 16 bytes @@ -555,6 +545,7 @@ namespace garlic // try to gererate more tags for last tagset if (m_LastTagset && (m_LastTagset->GetNextIndex () - m_LastTagset->GetTrimBehind () < 3*ECIESX25519_MAX_NUM_GENERATED_TAGS)) { + uint64_t missingTag; memcpy (&missingTag, buf, 8); auto maxTags = std::max (m_NumRatchetInboundTags, ECIESX25519_MAX_NUM_GENERATED_TAGS); LogPrint (eLogWarning, "Garlic: trying to generate more ECIES-X25519-AEAD-Ratchet tags"); for (int i = 0; i < maxTags; i++) @@ -565,10 +556,10 @@ namespace garlic LogPrint (eLogError, "Garlic: can't create new ECIES-X25519-AEAD-Ratchet tag for last tagset"); break; } - if (nextTag == tag) + if (nextTag == missingTag) { LogPrint (eLogDebug, "Garlic: Missing ECIES-X25519-AEAD-Ratchet tag was generated"); - if (m_LastTagset->HandleNextMessage (buf, length, m_ECIESx25519Tags[tag].index)) + if (m_LastTagset->HandleNextMessage (buf, length, m_ECIESx25519Tags[nextTag].index)) found = true; break; } @@ -585,6 +576,23 @@ namespace garlic } } + bool GarlicDestination::HandleECIESx25519TagMessage (uint8_t * buf, size_t len) + { + uint64_t tag; + memcpy (&tag, buf, 8); + auto it = m_ECIESx25519Tags.find (tag); + if (it != m_ECIESx25519Tags.end ()) + { + if (it->second.tagset->HandleNextMessage (buf, len, it->second.index)) + m_LastTagset = it->second.tagset; + else + LogPrint (eLogError, "Garlic: can't handle ECIES-X25519-AEAD-Ratchet message"); + m_ECIESx25519Tags.erase (it); + return true; + } + return false; + } + void GarlicDestination::HandleAESBlock (uint8_t * buf, size_t len, std::shared_ptr decryption, std::shared_ptr from) { @@ -749,11 +757,7 @@ namespace garlic std::shared_ptr msg) { if (router->GetEncryptionType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) - { - auto session = std::make_shared(this, false); - session->SetRemoteStaticKey (router->GetIdentity ()->GetEncryptionPublicKey ()); - return session->WrapOneTimeMessage (msg, true); - } + return WrapECIESX25519MessageForRouter (msg, router->GetIdentity ()->GetEncryptionPublicKey ()); else { auto session = GetRoutingSession (router, false); @@ -769,7 +773,7 @@ namespace garlic { ECIESX25519AEADRatchetSessionPtr session; uint8_t staticKey[32]; - destination->Encrypt (nullptr, staticKey, nullptr); // we are supposed to get static key + destination->Encrypt (nullptr, staticKey); // we are supposed to get static key auto it = m_ECIESx25519Sessions.find (staticKey); if (it != m_ECIESx25519Sessions.end ()) { @@ -1036,10 +1040,11 @@ namespace garlic { LogPrint (eLogDebug, "Garlic: type local"); I2NPMessageType typeID = (I2NPMessageType)(buf[0]); buf++; // typeid - buf += (4 + 4); // msgID + expiration + int32_t msgID = bufbe32toh (buf); buf += 4; // msgID + buf += 4; // expiration ptrdiff_t offset = buf - buf1; if (offset <= (int)len) - HandleCloveI2NPMessage (typeID, buf, len - offset); + HandleCloveI2NPMessage (typeID, buf, len - offset, msgID); else LogPrint (eLogError, "Garlic: clove is too long"); break; @@ -1058,13 +1063,14 @@ namespace garlic } uint32_t gwTunnel = bufbe32toh (buf); buf += 4; I2NPMessageType typeID = (I2NPMessageType)(buf[0]); buf++; // typeid - buf += (4 + 4); // msgID + expiration + uint32_t msgID = bufbe32toh (buf); buf += 4; // msgID + buf += 4; // expiration offset += 13; if (GetTunnelPool ()) { auto tunnel = GetTunnelPool ()->GetNextOutboundTunnel (); if (tunnel) - tunnel->SendTunnelDataMsg (gwHash, gwTunnel, CreateI2NPMessage (typeID, buf, len - offset)); + tunnel->SendTunnelDataMsg (gwHash, gwTunnel, CreateI2NPMessage (typeID, buf, len - offset, msgID)); else LogPrint (eLogWarning, "Garlic: No outbound tunnels available for garlic clove"); } @@ -1115,5 +1121,12 @@ namespace garlic m_ECIESx25519Sessions.erase (it); } } + + uint8_t * GarlicDestination::GetPayloadBuffer () + { + if (!m_PayloadBuffer) + m_PayloadBuffer = new uint8_t[I2NP_MAX_MESSAGE_SIZE]; + return m_PayloadBuffer; + } } } diff --git a/libi2pd/Garlic.h b/libi2pd/Garlic.h index 4288e74b..0d7b8461 100644 --- a/libi2pd/Garlic.h +++ b/libi2pd/Garlic.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -243,14 +243,15 @@ namespace garlic std::shared_ptr msg); void AddSessionKey (const uint8_t * key, const uint8_t * tag); // one tag - void AddECIESx25519Key (const uint8_t * key, const uint8_t * tag); // one tag + void AddECIESx25519Key (const uint8_t * key, uint64_t tag); // one tag virtual bool SubmitSessionKey (const uint8_t * key, const uint8_t * tag); // from different thread void DeliveryStatusSent (GarlicRoutingSessionPtr session, uint32_t msgID); uint64_t AddECIESx25519SessionNextTag (ReceiveRatchetTagSetPtr tagset); void AddECIESx25519Session (const uint8_t * staticKey, ECIESX25519AEADRatchetSessionPtr session); void RemoveECIESx25519Session (const uint8_t * staticKey); void HandleECIESx25519GarlicClove (const uint8_t * buf, size_t len); - + uint8_t * GetPayloadBuffer (); + virtual void ProcessGarlicMessage (std::shared_ptr msg); virtual void ProcessDeliveryStatusMessage (std::shared_ptr msg); virtual void SetLeaseSetUpdated (); @@ -260,8 +261,10 @@ namespace garlic protected: + void AddECIESx25519Key (const uint8_t * key, const uint8_t * tag); // one tag + bool HandleECIESx25519TagMessage (uint8_t * buf, size_t len); // return true if found virtual void HandleI2NPMessage (const uint8_t * buf, size_t len) = 0; // called from clove only - virtual bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len) = 0; + virtual bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID) = 0; void HandleGarlicMessage (std::shared_ptr msg); void HandleDeliveryStatusMessage (uint32_t msgID); @@ -276,12 +279,12 @@ namespace garlic private: - BN_CTX * m_Ctx; // incoming // outgoing sessions int m_NumTags; std::mutex m_SessionsMutex; std::unordered_map m_Sessions; std::unordered_map, ECIESX25519AEADRatchetSessionPtr> m_ECIESx25519Sessions; // static key -> session + uint8_t * m_PayloadBuffer; // for ECIESX25519AEADRatchet // incoming int m_NumRatchetInboundTags; std::unordered_map, std::hash > > m_Tags; diff --git a/libi2pd/I2NPProtocol.cpp b/libi2pd/I2NPProtocol.cpp index e47c9add..bdd65d30 100644 --- a/libi2pd/I2NPProtocol.cpp +++ b/libi2pd/I2NPProtocol.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -18,6 +18,7 @@ #include "Tunnel.h" #include "Transports.h" #include "Garlic.h" +#include "ECIESX25519AEADRatchetSession.h" #include "I2NPProtocol.h" #include "version.h" @@ -35,11 +36,9 @@ namespace i2p return std::make_shared >(); } - std::shared_ptr NewI2NPTunnelMessage () + std::shared_ptr NewI2NPTunnelMessage (bool endpoint) { - auto msg = new I2NPMessageBuffer(); // reserved for alignment and NTCP 16 + 6 + 12 - msg->Align (12); - return std::shared_ptr(msg); + return i2p::tunnel::tunnels.NewI2NPTunnelMessage (endpoint); } std::shared_ptr NewI2NPMessage (size_t len) @@ -210,12 +209,12 @@ namespace i2p { memcpy (buf + 33, replyTag, 8); // 8 bytes tag buf += 41; - } - else - { + } + else + { memcpy (buf + 33, replyTag, 32); // 32 bytes tag buf += 65; - } + } m->len += (buf - m->GetPayload ()); m->FillI2NPMessageHeader (eI2NPDatabaseLookup); @@ -250,6 +249,12 @@ namespace i2p if (!router) // we send own RouterInfo router = context.GetSharedRouterInfo (); + if (!router->GetBuffer ()) + { + LogPrint (eLogError, "I2NP: Invalid RouterInfo buffer for DatabaseStore"); + return nullptr; + } + auto m = NewI2NPShortMessage (); uint8_t * payload = m->GetPayload (); @@ -267,12 +272,12 @@ namespace i2p buf += 32; // reply tunnel gateway } else - { + { memset (buf, 0, 4); // zero tunnelID means direct reply buf += 4; memcpy (buf, context.GetIdentHash (), 32); buf += 32; - } + } } uint8_t * sizePtr = buf; @@ -285,7 +290,7 @@ namespace i2p { i2p::data::GzipDeflator deflator; size = deflator.Deflate (router->GetBuffer (), router->GetBufferLen (), buf, m->maxLen -m->len); - } + } if (size) { htobe16buf (sizePtr, size); // size @@ -363,7 +368,7 @@ namespace i2p return g_MaxNumTransitTunnels; } - bool HandleBuildRequestRecords (int num, uint8_t * records, uint8_t * clearText) + static bool HandleBuildRequestRecords (int num, uint8_t * records, uint8_t * clearText) { for (int i = 0; i < num; i++) { @@ -373,78 +378,50 @@ namespace i2p LogPrint (eLogDebug, "I2NP: Build request record ", i, " is ours"); if (!i2p::context.DecryptTunnelBuildRecord (record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, clearText)) return false; uint8_t retCode = 0; - bool isECIES = i2p::context.IsECIES (); // replace record to reply if (i2p::context.AcceptsTunnels () && i2p::tunnel::tunnels.GetTransitTunnels ().size () <= g_MaxNumTransitTunnels && !i2p::transport::transports.IsBandwidthExceeded () && !i2p::transport::transports.IsTransitBandwidthExceeded ()) { - auto transitTunnel = isECIES ? - i2p::tunnel::CreateTransitTunnel ( + auto transitTunnel = i2p::tunnel::CreateTransitTunnel ( bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET), clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), clearText + ECIES_BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET, clearText + ECIES_BUILD_REQUEST_RECORD_IV_KEY_OFFSET, - clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & 0x80, - clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & 0x40) : - i2p::tunnel::CreateTransitTunnel ( - bufbe32toh (clearText + BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET), - clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, - bufbe32toh (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), - clearText + BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET, - clearText + BUILD_REQUEST_RECORD_IV_KEY_OFFSET, - clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] & 0x80, - clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] & 0x40); + clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_GATEWAY_FLAG, + clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG); i2p::tunnel::tunnels.AddTransitTunnel (transitTunnel); } else retCode = 30; // always reject with bandwidth reason (30) - if (isECIES) - { - memset (record + ECIES_BUILD_RESPONSE_RECORD_OPTIONS_OFFSET, 0, 2); // no options - record[ECIES_BUILD_RESPONSE_RECORD_RET_OFFSET] = retCode; - } - else - { - record[BUILD_RESPONSE_RECORD_RET_OFFSET] = retCode; - SHA256 (record + BUILD_RESPONSE_RECORD_PADDING_OFFSET, BUILD_RESPONSE_RECORD_PADDING_SIZE + 1, // + 1 byte of ret - record + BUILD_RESPONSE_RECORD_HASH_OFFSET); - } + memset (record + ECIES_BUILD_RESPONSE_RECORD_OPTIONS_OFFSET, 0, 2); // no options + record[ECIES_BUILD_RESPONSE_RECORD_RET_OFFSET] = retCode; // encrypt reply i2p::crypto::CBCEncryption encryption; for (int j = 0; j < num; j++) { uint8_t * reply = records + j*TUNNEL_BUILD_RECORD_SIZE; - if (isECIES) - { - if (j == i) + if (j == i) + { + uint8_t nonce[12]; + memset (nonce, 0, 12); + auto& noiseState = i2p::context.GetCurrentNoiseState (); + if (!i2p::crypto::AEADChaCha20Poly1305 (reply, TUNNEL_BUILD_RECORD_SIZE - 16, + noiseState.m_H, 32, noiseState.m_CK, nonce, reply, TUNNEL_BUILD_RECORD_SIZE, true)) // encrypt { - uint8_t nonce[12]; - memset (nonce, 0, 12); - auto noiseState = std::move (i2p::context.GetCurrentNoiseState ()); - if (!noiseState || !i2p::crypto::AEADChaCha20Poly1305 (reply, TUNNEL_BUILD_RECORD_SIZE - 16, - noiseState->m_H, 32, noiseState->m_CK, nonce, reply, TUNNEL_BUILD_RECORD_SIZE, true)) // encrypt - { - LogPrint (eLogWarning, "I2NP: Reply AEAD encryption failed"); - return false; - } + LogPrint (eLogWarning, "I2NP: Reply AEAD encryption failed"); + return false; } - else - { - encryption.SetKey (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET); - encryption.SetIV (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_IV_OFFSET); - encryption.Encrypt(reply, TUNNEL_BUILD_RECORD_SIZE, reply); - } } else { - encryption.SetKey (clearText + BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET); - encryption.SetIV (clearText + BUILD_REQUEST_RECORD_REPLY_IV_OFFSET); + encryption.SetKey (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET); + encryption.SetIV (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_IV_OFFSET); encryption.Encrypt(reply, TUNNEL_BUILD_RECORD_SIZE, reply); - } + } } return true; } @@ -452,7 +429,7 @@ namespace i2p return false; } - void HandleVariableTunnelBuildMsg (uint32_t replyMsgID, uint8_t * buf, size_t len) + static void HandleVariableTunnelBuildMsg (uint32_t replyMsgID, uint8_t * buf, size_t len) { int num = buf[0]; LogPrint (eLogDebug, "I2NP: VariableTunnelBuild ", num, " records"); @@ -481,84 +458,38 @@ namespace i2p } else { - if (i2p::context.IsECIES ()) + uint8_t clearText[ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; + if (HandleBuildRequestRecords (num, buf + 1, clearText)) { - uint8_t clearText[ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; - if (HandleBuildRequestRecords (num, buf + 1, clearText)) + if (clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG) // we are endpoint of outboud tunnel { - if (clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & 0x40) // we are endpoint of outboud tunnel - { - // so we send it to reply tunnel - transports.SendMessage (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, - CreateTunnelGatewayMsg (bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), - eI2NPVariableTunnelBuildReply, buf, len, - bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); - } - else - transports.SendMessage (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, - CreateI2NPMessage (eI2NPVariableTunnelBuild, buf, len, - bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); + // so we send it to reply tunnel + transports.SendMessage (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, + CreateTunnelGatewayMsg (bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), + eI2NPVariableTunnelBuildReply, buf, len, + bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); } + else + transports.SendMessage (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, + CreateI2NPMessage (eI2NPVariableTunnelBuild, buf, len, + bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); } - else - { - uint8_t clearText[BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; - if (HandleBuildRequestRecords (num, buf + 1, clearText)) - { - if (clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] & 0x40) // we are endpoint of outboud tunnel - { - // so we send it to reply tunnel - transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, - CreateTunnelGatewayMsg (bufbe32toh (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), - eI2NPVariableTunnelBuildReply, buf, len, - bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); - } - else - transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, - CreateI2NPMessage (eI2NPVariableTunnelBuild, buf, len, - bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); - } - } } } - void HandleTunnelBuildMsg (uint8_t * buf, size_t len) + static void HandleTunnelBuildMsg (uint8_t * buf, size_t len) { - if (i2p::context.IsECIES ()) - { - LogPrint (eLogWarning, "I2NP: TunnelBuild is too old for ECIES router"); - return; - } - if (len < NUM_TUNNEL_BUILD_RECORDS*TUNNEL_BUILD_RECORD_SIZE) - { - LogPrint (eLogError, "I2NP: TunnelBuild message is too short ", len); - return; - } - uint8_t clearText[BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; - if (HandleBuildRequestRecords (NUM_TUNNEL_BUILD_RECORDS, buf, clearText)) - { - if (clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] & 0x40) // we are endpoint of outbound tunnel - { - // so we send it to reply tunnel - transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, - CreateTunnelGatewayMsg (bufbe32toh (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), - eI2NPTunnelBuildReply, buf, len, - bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); - } - else - transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, - CreateI2NPMessage (eI2NPTunnelBuild, buf, len, - bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); - } + LogPrint (eLogWarning, "I2NP: TunnelBuild is too old for ECIES router"); } - void HandleVariableTunnelBuildReplyMsg (uint32_t replyMsgID, uint8_t * buf, size_t len) + static void HandleTunnelBuildReplyMsg (uint32_t replyMsgID, uint8_t * buf, size_t len, bool isShort) { int num = buf[0]; - LogPrint (eLogDebug, "I2NP: VariableTunnelBuildReplyMsg of ", num, " records replyMsgID=", replyMsgID); - if (len < num*BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE + 1) + LogPrint (eLogDebug, "I2NP: TunnelBuildReplyMsg of ", num, " records replyMsgID=", replyMsgID); + size_t recordSize = isShort ? SHORT_TUNNEL_BUILD_RECORD_SIZE : TUNNEL_BUILD_RECORD_SIZE; + if (len < num*recordSize + 1) { - LogPrint (eLogError, "I2NP: VaribleTunnelBuildReply message of ", num, " records is too short ", len); + LogPrint (eLogError, "I2NP: TunnelBuildReply message of ", num, " records is too short ", len); return; } @@ -582,13 +513,8 @@ namespace i2p LogPrint (eLogWarning, "I2NP: Pending tunnel for message ", replyMsgID, " not found"); } - void HandleShortTunnelBuildMsg (uint32_t replyMsgID, uint8_t * buf, size_t len) + static void HandleShortTunnelBuildMsg (uint32_t replyMsgID, uint8_t * buf, size_t len) { - if (!i2p::context.IsECIES ()) - { - LogPrint (eLogWarning, "I2NP: ShortTunnelBuild can be handled by ECIES router only"); - return; - } int num = buf[0]; LogPrint (eLogDebug, "I2NP: ShortTunnelBuild ", num, " records"); if (len < num*SHORT_TUNNEL_BUILD_RECORD_SIZE + 1) @@ -596,7 +522,24 @@ namespace i2p LogPrint (eLogError, "I2NP: ShortTunnelBuild message of ", num, " records is too short ", len); return; } - // TODO: check replyMsgID + auto tunnel = i2p::tunnel::tunnels.GetPendingInboundTunnel (replyMsgID); + if (tunnel) + { + // endpoint of inbound tunnel + LogPrint (eLogDebug, "I2NP: ShortTunnelBuild reply for tunnel ", tunnel->GetTunnelID ()); + if (tunnel->HandleTunnelBuildResponse (buf, len)) + { + LogPrint (eLogInfo, "I2NP: Inbound tunnel ", tunnel->GetTunnelID (), " has been created"); + tunnel->SetState (i2p::tunnel::eTunnelStateEstablished); + i2p::tunnel::tunnels.AddInboundTunnel (tunnel); + } + else + { + LogPrint (eLogInfo, "I2NP: Inbound tunnel ", tunnel->GetTunnelID (), " has been declined"); + tunnel->SetState (i2p::tunnel::eTunnelStateBuildFailed); + } + return; + } const uint8_t * record = buf + 1; for (int i = 0; i < num; i++) { @@ -605,46 +548,113 @@ namespace i2p LogPrint (eLogDebug, "I2NP: Short request record ", i, " is ours"); uint8_t clearText[SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE]; if (!i2p::context.DecryptTunnelShortRequestRecord (record + SHORT_REQUEST_RECORD_ENCRYPTED_OFFSET, clearText)) - { + { LogPrint (eLogWarning, "I2NP: Can't decrypt short request record ", i); return; } - // TODO: fill reply - // encrypt reply - auto noiseState = std::move (i2p::context.GetCurrentNoiseState ()); - if (!noiseState) - { - LogPrint (eLogWarning, "I2NP: Invalid Noise state for short reply encryption"); + if (clearText[SHORT_REQUEST_RECORD_LAYER_ENCRYPTION_TYPE]) // not AES + { + LogPrint (eLogWarning, "I2NP: Unknown layer encryption type ", clearText[SHORT_REQUEST_RECORD_LAYER_ENCRYPTION_TYPE], " in short request record"); return; - } + } + auto& noiseState = i2p::context.GetCurrentNoiseState (); + uint8_t replyKey[32], layerKey[32], ivKey[32]; + i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "SMTunnelReplyKey", noiseState.m_CK); + memcpy (replyKey, noiseState.m_CK + 32, 32); + i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "SMTunnelLayerKey", noiseState.m_CK); + memcpy (layerKey, noiseState.m_CK + 32, 32); + bool isEndpoint = clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG; + if (isEndpoint) + { + i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "TunnelLayerIVKey", noiseState.m_CK); + memcpy (ivKey, noiseState.m_CK + 32, 32); + } + else + memcpy (ivKey, noiseState.m_CK , 32); + + // check if we accept this tunnel + uint8_t retCode = 0; + if (!i2p::context.AcceptsTunnels () || + i2p::tunnel::tunnels.GetTransitTunnels ().size () > g_MaxNumTransitTunnels || + i2p::transport::transports.IsBandwidthExceeded () || + i2p::transport::transports.IsTransitBandwidthExceeded ()) + retCode = 30; + if (!retCode) + { + // create new transit tunnel + auto transitTunnel = i2p::tunnel::CreateTransitTunnel ( + bufbe32toh (clearText + SHORT_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET), + clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, + bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), + layerKey, ivKey, + clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_GATEWAY_FLAG, + clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG); + i2p::tunnel::tunnels.AddTransitTunnel (transitTunnel); + } + + // encrypt reply uint8_t nonce[12]; memset (nonce, 0, 12); uint8_t * reply = buf + 1; for (int j = 0; j < num; j++) - { + { + nonce[4] = j; // nonce is record # if (j == i) { + memset (reply + SHORT_RESPONSE_RECORD_OPTIONS_OFFSET, 0, 2); // no options + reply[SHORT_RESPONSE_RECORD_RET_OFFSET] = retCode; if (!i2p::crypto::AEADChaCha20Poly1305 (reply, SHORT_TUNNEL_BUILD_RECORD_SIZE - 16, - noiseState->m_H, 32, noiseState->m_CK, nonce, reply, SHORT_TUNNEL_BUILD_RECORD_SIZE, true)) // encrypt + noiseState.m_H, 32, replyKey, nonce, reply, SHORT_TUNNEL_BUILD_RECORD_SIZE, true)) // encrypt { LogPrint (eLogWarning, "I2NP: Short reply AEAD encryption failed"); return; - } + } } else - i2p::crypto::ChaCha20 (reply, SHORT_TUNNEL_BUILD_RECORD_SIZE, noiseState->m_CK, nonce, reply); - reply += SHORT_TUNNEL_BUILD_RECORD_SIZE; + i2p::crypto::ChaCha20 (reply, SHORT_TUNNEL_BUILD_RECORD_SIZE, replyKey, nonce, reply); + reply += SHORT_TUNNEL_BUILD_RECORD_SIZE; } - // TODO: send + // send reply + if (isEndpoint) + { + auto replyMsg = NewI2NPShortMessage (); + replyMsg->Concat (buf, len); + replyMsg->FillI2NPMessageHeader (eI2NPShortTunnelBuildReply, bufbe32toh (clearText + SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET)); + if (memcmp ((const uint8_t *)i2p::context.GetIdentHash (), + clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, 32)) // reply IBGW is not local? + { + i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "RGarlicKeyAndTag", noiseState.m_CK); + uint64_t tag; + memcpy (&tag, noiseState.m_CK, 8); + // we send it to reply tunnel + transports.SendMessage (clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, + CreateTunnelGatewayMsg (bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), + i2p::garlic::WrapECIESX25519Message (replyMsg, noiseState.m_CK + 32, tag))); + } + else + { + // IBGW is local + uint32_t tunnelID = bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET); + auto tunnel = i2p::tunnel::tunnels.GetTunnel (tunnelID); + if (tunnel) + tunnel->SendTunnelDataMsg (replyMsg); + else + LogPrint (eLogWarning, "I2NP: Tunnel ", tunnelID, " not found for short tunnel build reply"); + } + } + else + transports.SendMessage (clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, + CreateI2NPMessage (eI2NPShortTunnelBuild, buf, len, + bufbe32toh (clearText + SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); return; - } + } record += SHORT_TUNNEL_BUILD_RECORD_SIZE; - } - } - + } + } + std::shared_ptr CreateTunnelDataMsg (const uint8_t * buf) { - auto msg = NewI2NPTunnelMessage (); + auto msg = NewI2NPTunnelMessage (false); msg->Concat (buf, i2p::tunnel::TUNNEL_DATA_MSG_SIZE); msg->FillI2NPMessageHeader (eI2NPTunnelData); return msg; @@ -652,7 +662,7 @@ namespace i2p std::shared_ptr CreateTunnelDataMsg (uint32_t tunnelID, const uint8_t * payload) { - auto msg = NewI2NPTunnelMessage (); + auto msg = NewI2NPTunnelMessage (false); htobe32buf (msg->GetPayload (), tunnelID); msg->len += 4; // tunnelID msg->Concat (payload, i2p::tunnel::TUNNEL_DATA_MSG_SIZE - 4); @@ -660,9 +670,9 @@ namespace i2p return msg; } - std::shared_ptr CreateEmptyTunnelDataMsg () + std::shared_ptr CreateEmptyTunnelDataMsg (bool endpoint) { - auto msg = NewI2NPTunnelMessage (); + auto msg = NewI2NPTunnelMessage (endpoint); msg->len += i2p::tunnel::TUNNEL_DATA_MSG_SIZE; return msg; } @@ -756,12 +766,15 @@ namespace i2p case eI2NPVariableTunnelBuild: HandleVariableTunnelBuildMsg (msgID, buf, size); break; - case eI2NPVariableTunnelBuildReply: - HandleVariableTunnelBuildReplyMsg (msgID, buf, size); - break; case eI2NPShortTunnelBuild: HandleShortTunnelBuildMsg (msgID, buf, size); - break; + break; + case eI2NPVariableTunnelBuildReply: + HandleTunnelBuildReplyMsg (msgID, buf, size, false); + break; + case eI2NPShortTunnelBuildReply: + HandleTunnelBuildReplyMsg (msgID, buf, size, true); + break; case eI2NPTunnelBuild: HandleTunnelBuildMsg (buf, size); break; @@ -818,7 +831,8 @@ namespace i2p case eI2NPVariableTunnelBuildReply: case eI2NPTunnelBuild: case eI2NPTunnelBuildReply: - case eI2NPShortTunnelBuild: + case eI2NPShortTunnelBuild: + case eI2NPShortTunnelBuildReply: // forward to tunnel thread i2p::tunnel::tunnels.PostTunnelData (msg); break; @@ -833,7 +847,7 @@ namespace i2p Flush (); } - void I2NPMessagesHandler::PutNextMessage (std::shared_ptr msg) + void I2NPMessagesHandler::PutNextMessage (std::shared_ptr&& msg) { if (msg) { diff --git a/libi2pd/I2NPProtocol.h b/libi2pd/I2NPProtocol.h index 5db93978..c8bf1697 100644 --- a/libi2pd/I2NPProtocol.h +++ b/libi2pd/I2NPProtocol.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -55,33 +55,12 @@ namespace i2p // TunnelBuild const size_t TUNNEL_BUILD_RECORD_SIZE = 528; - const size_t SHORT_TUNNEL_BUILD_RECORD_SIZE = 236; + const size_t SHORT_TUNNEL_BUILD_RECORD_SIZE = 218; - //BuildRequestRecordClearText - const size_t BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET = 0; - const size_t BUILD_REQUEST_RECORD_OUR_IDENT_OFFSET = BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET + 4; - const size_t BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET = BUILD_REQUEST_RECORD_OUR_IDENT_OFFSET + 32; - const size_t BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET = BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET + 4; - const size_t BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET = BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET + 32; - const size_t BUILD_REQUEST_RECORD_IV_KEY_OFFSET = BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET + 32; - const size_t BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET = BUILD_REQUEST_RECORD_IV_KEY_OFFSET + 32; - const size_t BUILD_REQUEST_RECORD_REPLY_IV_OFFSET = BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET + 32; - const size_t BUILD_REQUEST_RECORD_FLAG_OFFSET = BUILD_REQUEST_RECORD_REPLY_IV_OFFSET + 16; - const size_t BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET = BUILD_REQUEST_RECORD_FLAG_OFFSET + 1; - const size_t BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET = BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET + 4; - const size_t BUILD_REQUEST_RECORD_PADDING_OFFSET = BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET + 4; - const size_t BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE = 222; - // BuildRequestRecordEncrypted const size_t BUILD_REQUEST_RECORD_TO_PEER_OFFSET = 0; const size_t BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET = BUILD_REQUEST_RECORD_TO_PEER_OFFSET + 16; - // BuildResponseRecord - const size_t BUILD_RESPONSE_RECORD_HASH_OFFSET = 0; - const size_t BUILD_RESPONSE_RECORD_PADDING_OFFSET = 32; - const size_t BUILD_RESPONSE_RECORD_PADDING_SIZE = 495; - const size_t BUILD_RESPONSE_RECORD_RET_OFFSET = BUILD_RESPONSE_RECORD_PADDING_OFFSET + BUILD_RESPONSE_RECORD_PADDING_SIZE; - // ECIES BuildRequestRecordClearText const size_t ECIES_BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET = 0; const size_t ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET = ECIES_BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET + 4; @@ -104,7 +83,21 @@ namespace i2p // ShortRequestRecordClearText const size_t SHORT_REQUEST_RECORD_ENCRYPTED_OFFSET = 16; - const size_t SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE = 172; + const size_t SHORT_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET = 0; + const size_t SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET = SHORT_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET + 4; + const size_t SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET = SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET + 4; + const size_t SHORT_REQUEST_RECORD_FLAG_OFFSET = SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET + 32; + const size_t SHORT_REQUEST_RECORD_MORE_FLAGS_OFFSET = SHORT_REQUEST_RECORD_FLAG_OFFSET + 1; + const size_t SHORT_REQUEST_RECORD_LAYER_ENCRYPTION_TYPE = SHORT_REQUEST_RECORD_MORE_FLAGS_OFFSET + 2; + const size_t SHORT_REQUEST_RECORD_REQUEST_TIME_OFFSET = SHORT_REQUEST_RECORD_LAYER_ENCRYPTION_TYPE + 1; + const size_t SHORT_REQUEST_RECORD_REQUEST_EXPIRATION_OFFSET = SHORT_REQUEST_RECORD_REQUEST_TIME_OFFSET + 4; + const size_t SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET = SHORT_REQUEST_RECORD_REQUEST_EXPIRATION_OFFSET + 4; + const size_t SHORT_REQUEST_RECORD_PADDING_OFFSET = SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET + 4; + const size_t SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE = 154; + + // ShortResponseRecord + const size_t SHORT_RESPONSE_RECORD_OPTIONS_OFFSET = 0; + const size_t SHORT_RESPONSE_RECORD_RET_OFFSET = 201; enum I2NPMessageType { @@ -121,9 +114,12 @@ namespace i2p eI2NPTunnelBuildReply = 22, eI2NPVariableTunnelBuild = 23, eI2NPVariableTunnelBuildReply = 24, - eI2NPShortTunnelBuild = 25 + eI2NPShortTunnelBuild = 25, + eI2NPShortTunnelBuildReply = 26 }; + const uint8_t TUNNEL_BUILD_RECORD_GATEWAY_FLAG = 0x80; + const uint8_t TUNNEL_BUILD_RECORD_ENDPOINT_FLAG = 0x40; const int NUM_TUNNEL_BUILD_RECORDS = 8; // DatabaseLookup flags @@ -266,7 +262,7 @@ namespace tunnel std::shared_ptr NewI2NPMessage (); std::shared_ptr NewI2NPShortMessage (); - std::shared_ptr NewI2NPTunnelMessage (); + std::shared_ptr NewI2NPTunnelMessage (bool endpoint); std::shared_ptr NewI2NPMessage (size_t len); std::shared_ptr CreateI2NPMessage (I2NPMessageType msgType, const uint8_t * buf, size_t len, uint32_t replyMsgID = 0); @@ -287,15 +283,9 @@ namespace tunnel std::shared_ptr CreateDatabaseStoreMsg (std::shared_ptr leaseSet, uint32_t replyToken = 0, std::shared_ptr replyTunnel = nullptr); bool IsRouterInfoMsg (std::shared_ptr msg); - bool HandleBuildRequestRecords (int num, uint8_t * records, uint8_t * clearText); - void HandleVariableTunnelBuildMsg (uint32_t replyMsgID, uint8_t * buf, size_t len); - void HandleVariableTunnelBuildReplyMsg (uint32_t replyMsgID, uint8_t * buf, size_t len); - void HandleShortTunnelBuildMsg (uint32_t replyMsgID, uint8_t * buf, size_t len); - void HandleTunnelBuildMsg (uint8_t * buf, size_t len); - std::shared_ptr CreateTunnelDataMsg (const uint8_t * buf); std::shared_ptr CreateTunnelDataMsg (uint32_t tunnelID, const uint8_t * payload); - std::shared_ptr CreateEmptyTunnelDataMsg (); + std::shared_ptr CreateEmptyTunnelDataMsg (bool endpoint); std::shared_ptr CreateTunnelGatewayMsg (uint32_t tunnelID, const uint8_t * buf, size_t len); std::shared_ptr CreateTunnelGatewayMsg (uint32_t tunnelID, I2NPMessageType msgType, @@ -311,7 +301,7 @@ namespace tunnel public: ~I2NPMessagesHandler (); - void PutNextMessage (std::shared_ptr msg); + void PutNextMessage (std::shared_ptr&& msg); void Flush (); private: diff --git a/libi2pd/Identity.cpp b/libi2pd/Identity.cpp index 9dfaa1fc..fe876997 100644 --- a/libi2pd/Identity.cpp +++ b/libi2pd/Identity.cpp @@ -19,7 +19,8 @@ namespace data Identity& Identity::operator=(const Keys& keys) { // copy public and signing keys together - memcpy (publicKey, keys.publicKey, sizeof (publicKey) + sizeof (signingKey)); + memcpy (publicKey, keys.publicKey, sizeof (publicKey)); + memcpy (signingKey, keys.signingKey, sizeof (signingKey)); memset (certificate, 0, sizeof (certificate)); return *this; } @@ -42,7 +43,7 @@ namespace data } IdentityEx::IdentityEx (): - m_ExtendedLen (0), m_ExtendedBuffer (nullptr) + m_ExtendedLen (0) { } @@ -119,11 +120,15 @@ namespace data m_StandardIdentity.certificate[0] = CERTIFICATE_TYPE_KEY; htobe16buf (m_StandardIdentity.certificate + 1, m_ExtendedLen); // fill extended buffer - m_ExtendedBuffer = new uint8_t[m_ExtendedLen]; htobe16buf (m_ExtendedBuffer, type); htobe16buf (m_ExtendedBuffer + 2, cryptoType); if (excessLen && excessBuf) { + if (excessLen > MAX_EXTENDED_BUFFER_SIZE - 4) + { + LogPrint (eLogError, "Identity: Unexpected excessive signing key len ", excessLen); + excessLen = MAX_EXTENDED_BUFFER_SIZE - 4; + } memcpy (m_ExtendedBuffer + 4, excessBuf, excessLen); delete[] excessBuf; } @@ -136,7 +141,6 @@ namespace data memset (m_StandardIdentity.certificate, 0, sizeof (m_StandardIdentity.certificate)); m_IdentHash = m_StandardIdentity.Hash (); m_ExtendedLen = 0; - m_ExtendedBuffer = nullptr; } CreateVerifier (); } @@ -154,26 +158,25 @@ namespace data } IdentityEx::IdentityEx (const uint8_t * buf, size_t len): - m_ExtendedLen (0), m_ExtendedBuffer (nullptr) + m_ExtendedLen (0) { FromBuffer (buf, len); } IdentityEx::IdentityEx (const IdentityEx& other): - m_ExtendedLen (0), m_ExtendedBuffer (nullptr) + m_ExtendedLen (0) { *this = other; } IdentityEx::IdentityEx (const Identity& standard): - m_ExtendedLen (0), m_ExtendedBuffer (nullptr) + m_ExtendedLen (0) { *this = standard; } IdentityEx::~IdentityEx () { - delete[] m_ExtendedBuffer; delete m_Verifier; } @@ -182,15 +185,12 @@ namespace data memcpy (&m_StandardIdentity, &other.m_StandardIdentity, DEFAULT_IDENTITY_SIZE); m_IdentHash = other.m_IdentHash; - delete[] m_ExtendedBuffer; m_ExtendedLen = other.m_ExtendedLen; if (m_ExtendedLen > 0) { - m_ExtendedBuffer = new uint8_t[m_ExtendedLen]; + if (m_ExtendedLen > MAX_EXTENDED_BUFFER_SIZE) m_ExtendedLen = MAX_EXTENDED_BUFFER_SIZE; memcpy (m_ExtendedBuffer, other.m_ExtendedBuffer, m_ExtendedLen); } - else - m_ExtendedBuffer = nullptr; delete m_Verifier; m_Verifier = nullptr; @@ -203,8 +203,6 @@ namespace data m_StandardIdentity = standard; m_IdentHash = m_StandardIdentity.Hash (); - delete[] m_ExtendedBuffer; - m_ExtendedBuffer = nullptr; m_ExtendedLen = 0; delete m_Verifier; @@ -222,15 +220,12 @@ namespace data } memcpy (&m_StandardIdentity, buf, DEFAULT_IDENTITY_SIZE); - if(m_ExtendedBuffer) delete[] m_ExtendedBuffer; - m_ExtendedBuffer = nullptr; - m_ExtendedLen = bufbe16toh (m_StandardIdentity.certificate + 1); if (m_ExtendedLen) { if (m_ExtendedLen + DEFAULT_IDENTITY_SIZE <= len) { - m_ExtendedBuffer = new uint8_t[m_ExtendedLen]; + if (m_ExtendedLen > MAX_EXTENDED_BUFFER_SIZE) m_ExtendedLen = MAX_EXTENDED_BUFFER_SIZE; memcpy (m_ExtendedBuffer, buf + DEFAULT_IDENTITY_SIZE, m_ExtendedLen); } else @@ -241,10 +236,7 @@ namespace data } } else - { m_ExtendedLen = 0; - m_ExtendedBuffer = nullptr; - } SHA256(buf, GetFullLen (), m_IdentHash); delete m_Verifier; @@ -258,7 +250,7 @@ namespace data const size_t fullLen = GetFullLen(); if (fullLen > len) return 0; // buffer is too small and may overflow somewhere else memcpy (buf, &m_StandardIdentity, DEFAULT_IDENTITY_SIZE); - if (m_ExtendedLen > 0 && m_ExtendedBuffer) + if (m_ExtendedLen > 0) memcpy (buf + DEFAULT_IDENTITY_SIZE, m_ExtendedBuffer, m_ExtendedLen); return fullLen; } diff --git a/libi2pd/Identity.h b/libi2pd/Identity.h index e9cf63ed..f96d5f41 100644 --- a/libi2pd/Identity.h +++ b/libi2pd/Identity.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -84,8 +84,9 @@ namespace data typedef uint16_t SigningKeyType; typedef uint16_t CryptoKeyType; + const size_t MAX_EXTENDED_BUFFER_SIZE = 8; // cryptoKeyType + signingKeyType + 4 extra bytes of P521 class IdentityEx - { + { public: IdentityEx (); @@ -137,7 +138,7 @@ namespace data mutable i2p::crypto::Verifier * m_Verifier = nullptr; mutable std::mutex m_VerifierMutex; size_t m_ExtendedLen; - uint8_t * m_ExtendedBuffer; + uint8_t m_ExtendedBuffer[MAX_EXTENDED_BUFFER_SIZE]; }; class PrivateKeys // for eepsites @@ -222,7 +223,7 @@ namespace data virtual ~RoutingDestination () {}; virtual std::shared_ptr GetIdentity () const = 0; - virtual void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx) const = 0; // encrypt data for + virtual void Encrypt (const uint8_t * data, uint8_t * encrypted) const = 0; // encrypt data for virtual bool IsDestination () const = 0; // for garlic const IdentHash& GetIdentHash () const { return GetIdentity ()->GetIdentHash (); }; @@ -234,7 +235,7 @@ namespace data public: virtual ~LocalDestination() {}; - virtual bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, CryptoKeyType preferredCrypto = CRYPTO_KEY_TYPE_ELGAMAL) const = 0; + virtual bool Decrypt (const uint8_t * encrypted, uint8_t * data, CryptoKeyType preferredCrypto = CRYPTO_KEY_TYPE_ELGAMAL) const = 0; virtual std::shared_ptr GetIdentity () const = 0; const IdentHash& GetIdentHash () const { return GetIdentity ()->GetIdentHash (); }; diff --git a/libi2pd/LeaseSet.cpp b/libi2pd/LeaseSet.cpp index a0b14385..f6734b10 100644 --- a/libi2pd/LeaseSet.cpp +++ b/libi2pd/LeaseSet.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -254,12 +254,12 @@ namespace data return ts > m_ExpirationTime; } - void LeaseSet::Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx) const + void LeaseSet::Encrypt (const uint8_t * data, uint8_t * encrypted) const { if (!m_EncryptionKey) return; auto encryptor = m_Identity->CreateEncryptor (m_EncryptionKey); if (encryptor) - encryptor->Encrypt (data, encrypted, ctx, true); + encryptor->Encrypt (data, encrypted); } void LeaseSet::SetBuffer (const uint8_t * buf, size_t len) @@ -658,11 +658,11 @@ namespace data return offset - 1; } - void LeaseSet2::Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx) const + void LeaseSet2::Encrypt (const uint8_t * data, uint8_t * encrypted) const { auto encryptor = m_Encryptor; // TODO: atomic if (encryptor) - encryptor->Encrypt (data, encrypted, ctx, true); + encryptor->Encrypt (data, encrypted); } uint64_t LeaseSet2::ExtractExpirationTimestamp (const uint8_t * buf, size_t len) const @@ -912,6 +912,11 @@ namespace data uint8_t blindedPriv[64], blindedPub[128]; // 64 and 128 max size_t publicKeyLen = blindedKey.BlindPrivateKey (keys.GetSigningPrivateKey (), date, blindedPriv, blindedPub); std::unique_ptr blindedSigner (i2p::data::PrivateKeys::CreateSigner (blindedKey.GetBlindedSigType (), blindedPriv)); + if (!blindedSigner) + { + LogPrint (eLogError, "LeaseSet2: Can't create blinded signer for signature type ", blindedKey.GetSigType ()); + return; + } auto offset = 1; htobe16buf (m_Buffer + offset, blindedKey.GetBlindedSigType ()); offset += 2; // Blinded Public Key Sig Type memcpy (m_Buffer + offset, blindedPub, publicKeyLen); offset += publicKeyLen; // Blinded Public Key diff --git a/libi2pd/LeaseSet.h b/libi2pd/LeaseSet.h index cd6535df..8d501cb1 100644 --- a/libi2pd/LeaseSet.h +++ b/libi2pd/LeaseSet.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -93,7 +93,7 @@ namespace data // implements RoutingDestination std::shared_ptr GetIdentity () const { return m_Identity; }; - void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx) const; + void Encrypt (const uint8_t * data, uint8_t * encrypted) const; bool IsDestination () const { return true; }; protected: @@ -156,7 +156,7 @@ namespace data bool IsNewer (const uint8_t * buf, size_t len) const; // implements RoutingDestination - void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx) const; + void Encrypt (const uint8_t * data, uint8_t * encrypted) const; CryptoKeyType GetEncryptionType () const { return m_EncryptionType; }; private: diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp index 72e822cd..e592a7f2 100644 --- a/libi2pd/NTCP2.cpp +++ b/libi2pd/NTCP2.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -32,14 +32,12 @@ namespace i2p namespace transport { NTCP2Establisher::NTCP2Establisher (): - m_SessionRequestBuffer (nullptr), m_SessionCreatedBuffer (nullptr), m_SessionConfirmedBuffer (nullptr) + m_SessionConfirmedBuffer (nullptr) { } NTCP2Establisher::~NTCP2Establisher () { - delete[] m_SessionRequestBuffer; - delete[] m_SessionCreatedBuffer; delete[] m_SessionConfirmedBuffer; } @@ -112,9 +110,8 @@ namespace transport void NTCP2Establisher::CreateSessionRequestMessage () { // create buffer and fill padding - auto paddingLength = rand () % (287 - 64); // message length doesn't exceed 287 bytes + auto paddingLength = rand () % (NTCP2_SESSION_REQUEST_MAX_SIZE - 64); // message length doesn't exceed 287 bytes m_SessionRequestBufferLen = paddingLength + 64; - m_SessionRequestBuffer = new uint8_t[m_SessionRequestBufferLen]; RAND_bytes (m_SessionRequestBuffer + 64, paddingLength); // encrypt X i2p::crypto::CBCEncryption encryption; @@ -152,9 +149,8 @@ namespace transport void NTCP2Establisher::CreateSessionCreatedMessage () { - auto paddingLen = rand () % (287 - 64); + auto paddingLen = rand () % (NTCP2_SESSION_CREATED_MAX_SIZE - 64); m_SessionCreatedBufferLen = paddingLen + 64; - m_SessionCreatedBuffer = new uint8_t[m_SessionCreatedBufferLen]; RAND_bytes (m_SessionCreatedBuffer + 64, paddingLen); // encrypt Y i2p::crypto::CBCEncryption encryption; @@ -327,12 +323,14 @@ namespace transport m_Server (server), m_Socket (m_Server.GetService ()), m_IsEstablished (false), m_IsTerminated (false), m_Establisher (new NTCP2Establisher), - m_SendSipKey (nullptr), m_ReceiveSipKey (nullptr), #if OPENSSL_SIPHASH m_SendMDCtx(nullptr), m_ReceiveMDCtx (nullptr), +#else + m_SendSipKey (nullptr), m_ReceiveSipKey (nullptr), #endif m_NextReceivedLen (0), m_NextReceivedBuffer (nullptr), m_NextSendBuffer (nullptr), - m_ReceiveSequenceNumber (0), m_SendSequenceNumber (0), m_IsSending (false) + m_NextReceivedBufferSize (0), m_ReceiveSequenceNumber (0), m_SendSequenceNumber (0), + m_IsSending (false), m_IsReceiving (false), m_NextPaddingSize (16) { if (in_RemoteRouter) // Alice { @@ -355,8 +353,6 @@ namespace transport delete[] m_NextReceivedBuffer; delete[] m_NextSendBuffer; #if OPENSSL_SIPHASH - if (m_SendSipKey) EVP_PKEY_free (m_SendSipKey); - if (m_ReceiveSipKey) EVP_PKEY_free (m_ReceiveSipKey); if (m_SendMDCtx) EVP_MD_CTX_destroy (m_SendMDCtx); if (m_ReceiveMDCtx) EVP_MD_CTX_destroy (m_ReceiveMDCtx); #endif @@ -404,7 +400,30 @@ namespace transport htole64buf (nonce + 4, seqn); } + void NTCP2Session::CreateNextReceivedBuffer (size_t size) + { + if (m_NextReceivedBuffer) + { + if (size <= m_NextReceivedBufferSize) + return; // buffer is good, do nothing + else + delete[] m_NextReceivedBuffer; + } + m_NextReceivedBuffer = new uint8_t[size]; + m_NextReceivedBufferSize = size; + } + void NTCP2Session::DeleteNextReceiveBuffer (uint64_t ts) + { + if (m_NextReceivedBuffer && !m_IsReceiving && + ts > m_LastActivityTimestamp + NTCP2_RECEIVE_BUFFER_DELETION_TIMEOUT) + { + delete[] m_NextReceivedBuffer; + m_NextReceivedBuffer = nullptr; + m_NextReceivedBufferSize = 0; + } + } + void NTCP2Session::KeyDerivationFunctionDataPhase () { uint8_t k[64]; @@ -439,7 +458,6 @@ namespace transport } else { - m_Establisher->m_SessionCreatedBuffer = new uint8_t[287]; // TODO: determine actual max size // we receive first 64 bytes (32 Y, and 32 ChaCha/Poly frame) first boost::asio::async_read (m_Socket, boost::asio::buffer(m_Establisher->m_SessionCreatedBuffer, 64), boost::asio::transfer_all (), std::bind(&NTCP2Session::HandleSessionCreatedReceived, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); @@ -462,7 +480,7 @@ namespace transport { if (paddingLen > 0) { - if (paddingLen <= 287 - 64) // session request is 287 bytes max + if (paddingLen <= NTCP2_SESSION_REQUEST_MAX_SIZE - 64) // session request is 287 bytes max { boost::asio::async_read (m_Socket, boost::asio::buffer(m_Establisher->m_SessionRequestBuffer + 64, paddingLen), boost::asio::transfer_all (), std::bind(&NTCP2Session::HandleSessionRequestPaddingReceived, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); @@ -515,7 +533,7 @@ namespace transport { if (paddingLen > 0) { - if (paddingLen <= 287 - 64) // session created is 287 bytes max + if (paddingLen <= NTCP2_SESSION_CREATED_MAX_SIZE - 64) // session created is 287 bytes max { boost::asio::async_read (m_Socket, boost::asio::buffer(m_Establisher->m_SessionCreatedBuffer + 64, paddingLen), boost::asio::transfer_all (), std::bind(&NTCP2Session::HandleSessionCreatedPaddingReceived, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); @@ -692,17 +710,19 @@ namespace transport void NTCP2Session::SetSipKeys (const uint8_t * sendSipKey, const uint8_t * receiveSipKey) { #if OPENSSL_SIPHASH - m_SendSipKey = EVP_PKEY_new_raw_private_key (EVP_PKEY_SIPHASH, nullptr, sendSipKey, 16); + EVP_PKEY * sipKey = EVP_PKEY_new_raw_private_key (EVP_PKEY_SIPHASH, nullptr, sendSipKey, 16); m_SendMDCtx = EVP_MD_CTX_create (); EVP_PKEY_CTX *ctx = nullptr; - EVP_DigestSignInit (m_SendMDCtx, &ctx, nullptr, nullptr, m_SendSipKey); + EVP_DigestSignInit (m_SendMDCtx, &ctx, nullptr, nullptr, sipKey); EVP_PKEY_CTX_ctrl (ctx, -1, EVP_PKEY_OP_SIGNCTX, EVP_PKEY_CTRL_SET_DIGEST_SIZE, 8, nullptr); - - m_ReceiveSipKey = EVP_PKEY_new_raw_private_key (EVP_PKEY_SIPHASH, nullptr, receiveSipKey, 16); + EVP_PKEY_free (sipKey); + + sipKey = EVP_PKEY_new_raw_private_key (EVP_PKEY_SIPHASH, nullptr, receiveSipKey, 16); m_ReceiveMDCtx = EVP_MD_CTX_create (); ctx = nullptr; - EVP_DigestSignInit (m_ReceiveMDCtx, &ctx, NULL, NULL, m_ReceiveSipKey); + EVP_DigestSignInit (m_ReceiveMDCtx, &ctx, NULL, NULL, sipKey); EVP_PKEY_CTX_ctrl (ctx, -1, EVP_PKEY_OP_SIGNCTX, EVP_PKEY_CTRL_SET_DIGEST_SIZE, 8, nullptr); + EVP_PKEY_free (sipKey); #else m_SendSipKey = sendSipKey; m_ReceiveSipKey = receiveSipKey; @@ -718,7 +738,6 @@ namespace transport void NTCP2Session::ServerLogin () { m_Establisher->CreateEphemeralKey (); - m_Establisher->m_SessionRequestBuffer = new uint8_t[287]; // 287 bytes max for now boost::asio::async_read (m_Socket, boost::asio::buffer(m_Establisher->m_SessionRequestBuffer, 64), boost::asio::transfer_all (), std::bind(&NTCP2Session::HandleSessionRequestReceived, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); @@ -758,8 +777,7 @@ namespace transport LogPrint (eLogDebug, "NTCP2: received length ", m_NextReceivedLen); if (m_NextReceivedLen >= 16) { - if (m_NextReceivedBuffer) delete[] m_NextReceivedBuffer; - m_NextReceivedBuffer = new uint8_t[m_NextReceivedLen]; + CreateNextReceivedBuffer (m_NextReceivedLen); boost::system::error_code ec; size_t moreBytes = m_Socket.available(ec); if (!ec && moreBytes >= m_NextReceivedLen) @@ -786,6 +804,7 @@ namespace transport const int one = 1; setsockopt(m_Socket.native_handle(), IPPROTO_TCP, TCP_QUICKACK, &one, sizeof(one)); #endif + m_IsReceiving = true; boost::asio::async_read (m_Socket, boost::asio::buffer(m_NextReceivedBuffer, m_NextReceivedLen), boost::asio::transfer_all (), std::bind(&NTCP2Session::HandleReceived, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } @@ -809,7 +828,7 @@ namespace transport { LogPrint (eLogDebug, "NTCP2: received message decrypted"); ProcessNextFrame (m_NextReceivedBuffer, m_NextReceivedLen-16); - delete[] m_NextReceivedBuffer; m_NextReceivedBuffer = nullptr; // we don't need received buffer anymore + m_IsReceiving = false; ReceiveLength (); } else @@ -857,12 +876,16 @@ namespace transport LogPrint (eLogError, "NTCP2: I2NP block is too long ", size); break; } - auto nextMsg = NewI2NPMessage (size); - nextMsg->Align (12); // for possible tunnel msg + auto nextMsg = (frame[offset] == eI2NPTunnelData) ? NewI2NPTunnelMessage (true) : NewI2NPMessage (size); nextMsg->len = nextMsg->offset + size + 7; // 7 more bytes for full I2NP header - memcpy (nextMsg->GetNTCP2Header (), frame + offset, size); - nextMsg->FromNTCP2 (); - m_Handler.PutNextMessage (nextMsg); + if (nextMsg->len <= nextMsg->maxLen) + { + memcpy (nextMsg->GetNTCP2Header (), frame + offset, size); + nextMsg->FromNTCP2 (); + m_Handler.PutNextMessage (std::move (nextMsg)); + } + else + LogPrint (eLogError, "NTCP2: I2NP block is too long for I2NP message"); break; } case eNTCP2BlkTermination: @@ -887,11 +910,11 @@ namespace transport void NTCP2Session::SetNextSentFrameLength (size_t frameLen, uint8_t * lengthBuf) { - #if OPENSSL_SIPHASH +#if OPENSSL_SIPHASH EVP_DigestSignInit (m_SendMDCtx, nullptr, nullptr, nullptr, nullptr); EVP_DigestSignUpdate (m_SendMDCtx, m_SendIV.buf, 8); size_t l = 8; - EVP_DigestSignFinal (m_SendMDCtx, m_SendIV.buf, &l); + EVP_DigestSignFinal (m_SendMDCtx, m_SendIV.buf, &l); #else i2p::crypto::Siphash<8> (m_SendIV.buf, m_SendIV.buf, 8, m_SendSipKey); #endif @@ -1058,7 +1081,15 @@ namespace transport size_t paddingSize = (msgLen*NTCP2_MAX_PADDING_RATIO)/100; if (msgLen + paddingSize + 3 > NTCP2_UNENCRYPTED_FRAME_MAX_SIZE) paddingSize = NTCP2_UNENCRYPTED_FRAME_MAX_SIZE - msgLen -3; if (paddingSize > len) paddingSize = len; - if (paddingSize) paddingSize = rand () % paddingSize; + if (paddingSize) + { + if (m_NextPaddingSize >= 16) + { + RAND_bytes ((uint8_t *)m_PaddingSizes, sizeof (m_PaddingSizes)); + m_NextPaddingSize = 0; + } + paddingSize = m_PaddingSizes[m_NextPaddingSize++] % paddingSize; + } buf[0] = eNTCP2BlkPadding; // blk htobe16buf (buf + 1, paddingSize); // size memset (buf + 3, 0, paddingSize); @@ -1084,7 +1115,13 @@ namespace transport void NTCP2Session::SendTermination (NTCP2TerminationReason reason) { - if (!m_SendKey || !m_SendSipKey) return; + if (!m_SendKey || +#if OPENSSL_SIPHASH + !m_SendMDCtx +#else + !m_SendSipKey +#endif + ) return; m_NextSendBuffer = new uint8_t[49]; // 49 = 12 bytes message + 16 bytes MAC + 2 bytes size + up to 19 padding block // termination block m_NextSendBuffer[2] = eNTCP2BlkTermination; @@ -1170,7 +1207,7 @@ namespace transport if (!address) continue; if (address->IsPublishedNTCP2 () && address->port) { - if (address->host.is_v4()) + if (address->IsV4()) { try { @@ -1189,7 +1226,7 @@ namespace transport auto conn = std::make_shared(*this); m_NTCP2Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCP2Server::HandleAccept, this, conn, std::placeholders::_1)); } - else if (address->host.is_v6() && (context.SupportsV6 () || context.SupportsMesh ())) + else if (address->IsV6() && (context.SupportsV6 () || context.SupportsMesh ())) { m_NTCP2V6Acceptor.reset (new boost::asio::ip::tcp::acceptor (GetService ())); try @@ -1201,7 +1238,11 @@ namespace transport if (!m_Address6 && !m_YggdrasilAddress) // only if not binded to address { // Set preference to use public IPv6 address -- tested on linux, not works on windows, and not tested on others +#if (BOOST_VERSION >= 105500) typedef boost::asio::detail::socket_option::integer ipv6PreferAddr; +#else + typedef boost::asio::detail::socket_option::integer ipv6PreferAddr; +#endif m_NTCP2V6Acceptor->set_option (ipv6PreferAddr(IPV6_PREFER_SRC_PUBLIC | IPV6_PREFER_SRC_HOME | IPV6_PREFER_SRC_NONCGA)); } #endif @@ -1435,6 +1476,8 @@ namespace transport LogPrint (eLogDebug, "NTCP2: No activity for ", session->GetTerminationTimeout (), " seconds"); session->TerminateByTimeout (); // it doesn't change m_NTCP2Session right a way } + else + it.second->DeleteNextReceiveBuffer (ts); // pending for (auto it = m_PendingIncomingSessions.begin (); it != m_PendingIncomingSessions.end ();) { diff --git a/libi2pd/NTCP2.h b/libi2pd/NTCP2.h index 01ee3e34..3516ce6a 100644 --- a/libi2pd/NTCP2.h +++ b/libi2pd/NTCP2.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -28,18 +28,21 @@ namespace transport { const size_t NTCP2_UNENCRYPTED_FRAME_MAX_SIZE = 65519; + const size_t NTCP2_SESSION_REQUEST_MAX_SIZE = 287; + const size_t NTCP2_SESSION_CREATED_MAX_SIZE = 287; const int NTCP2_MAX_PADDING_RATIO = 6; // in % const int NTCP2_CONNECT_TIMEOUT = 5; // 5 seconds const int NTCP2_ESTABLISH_TIMEOUT = 10; // 10 seconds const int NTCP2_TERMINATION_TIMEOUT = 120; // 2 minutes const int NTCP2_TERMINATION_CHECK_TIMEOUT = 30; // 30 seconds + const int NTCP2_RECEIVE_BUFFER_DELETION_TIMEOUT = 3; // 3 seconds const int NTCP2_ROUTERINFO_RESEND_INTERVAL = 25*60; // 25 minuntes in seconds const int NTCP2_ROUTERINFO_RESEND_INTERVAL_THRESHOLD = 25*60; // 25 minuntes const int NTCP2_CLOCK_SKEW = 60; // in seconds const int NTCP2_MAX_OUTGOING_QUEUE_SIZE = 500; // how many messages we can queue up - + enum NTCP2BlockType { eNTCP2BlkDateTime = 0, @@ -115,7 +118,8 @@ namespace transport i2p::data::IdentHash m_RemoteIdentHash; uint16_t m3p2Len; - uint8_t * m_SessionRequestBuffer, * m_SessionCreatedBuffer, * m_SessionConfirmedBuffer; + uint8_t m_SessionRequestBuffer[NTCP2_SESSION_REQUEST_MAX_SIZE], + m_SessionCreatedBuffer[NTCP2_SESSION_CREATED_MAX_SIZE], * m_SessionConfirmedBuffer; size_t m_SessionRequestBufferLen, m_SessionCreatedBufferLen; }; @@ -132,7 +136,8 @@ namespace transport void TerminateByTimeout (); void Done (); void Close () { m_Socket.close (); }; // for accept - + void DeleteNextReceiveBuffer (uint64_t ts); + boost::asio::ip::tcp::socket& GetSocket () { return m_Socket; }; const boost::asio::ip::tcp::endpoint& GetRemoteEndpoint () { return m_RemoteEndpoint; }; void SetRemoteEndpoint (const boost::asio::ip::tcp::endpoint& ep) { m_RemoteEndpoint = ep; }; @@ -151,6 +156,7 @@ namespace transport void Established (); void CreateNonce (uint64_t seqn, uint8_t * nonce); + void CreateNextReceivedBuffer (size_t size); void KeyDerivationFunctionDataPhase (); void SetSipKeys (const uint8_t * sendSipKey, const uint8_t * receiveSipKey); @@ -199,13 +205,13 @@ namespace transport uint8_t m_Kab[32], m_Kba[32], m_Sipkeysab[32], m_Sipkeysba[32]; const uint8_t * m_SendKey, * m_ReceiveKey; #if OPENSSL_SIPHASH - EVP_PKEY * m_SendSipKey, * m_ReceiveSipKey; EVP_MD_CTX * m_SendMDCtx, * m_ReceiveMDCtx; #else const uint8_t * m_SendSipKey, * m_ReceiveSipKey; #endif uint16_t m_NextReceivedLen; uint8_t * m_NextReceivedBuffer, * m_NextSendBuffer; + size_t m_NextReceivedBufferSize; union { uint8_t buf[8]; @@ -215,9 +221,12 @@ namespace transport i2p::I2NPMessagesHandler m_Handler; - bool m_IsSending; + bool m_IsSending, m_IsReceiving; std::list > m_SendQueue; uint64_t m_NextRouterInfoResendTime; // seconds since epoch + + uint16_t m_PaddingSizes[16]; + int m_NextPaddingSize; }; class NTCP2Server: private i2p::util::RunnableServiceWithWork diff --git a/libi2pd/NetDb.cpp b/libi2pd/NetDb.cpp index a1d97cba..7123741a 100644 --- a/libi2pd/NetDb.cpp +++ b/libi2pd/NetDb.cpp @@ -55,10 +55,24 @@ namespace data Load (); uint16_t threshold; i2p::config::GetOption("reseed.threshold", threshold); - if (m_RouterInfos.size () < threshold) // reseed if # of router less than threshold + if (m_RouterInfos.size () < threshold || m_Floodfills.size () < NETDB_MIN_FLOODFILLS) // reseed if # of router less than threshold or too few floodfiils + { Reseed (); + } else if (!GetRandomRouter (i2p::context.GetSharedRouterInfo (), false)) Reseed (); // we don't have a router we can connect to. Trying to reseed + + auto it = m_RouterInfos.find (i2p::context.GetIdentHash ()); + if (it != m_RouterInfos.end ()) + { + // remove own router + m_Floodfills.remove (it->second); + m_RouterInfos.erase (it); + } + // insert own router + m_RouterInfos.emplace (i2p::context.GetIdentHash (), i2p::context.GetSharedRouterInfo ()); + if (i2p::context.IsFloodfill ()) + m_Floodfills.push_back (i2p::context.GetSharedRouterInfo ()); i2p::config::GetOption("persist.profiles", m_PersistProfiles); @@ -162,10 +176,18 @@ namespace data bool publish = false; if (m_PublishReplyToken) { + // next publishing attempt if (ts - lastPublish >= NETDB_PUBLISH_CONFIRMATION_TIMEOUT) publish = true; } else if (i2p::context.GetLastUpdateTime () > lastPublish || - ts - lastPublish >= NETDB_PUBLISH_INTERVAL) publish = true; + ts - lastPublish >= NETDB_PUBLISH_INTERVAL) + { + // new publish + m_PublishExcluded.clear (); + if (i2p::context.IsFloodfill ()) + m_PublishExcluded.insert (i2p::context.GetIdentHash ()); // do publish to ourselves + publish = true; + } if (publish) // update timestamp and publish { i2p::context.UpdateTimestamp (ts); @@ -453,14 +475,15 @@ namespace data bool NetDb::LoadRouterInfo (const std::string & path) { auto r = std::make_shared(path); - if (r->GetRouterIdentity () && !r->IsUnreachable () && - (r->IsReachable () || !r->IsSSU (false) || m_LastLoad < r->GetTimestamp () + NETDB_INTRODUCEE_EXPIRATION_TIMEOUT*1000LL)) // 1 hour - { + if (r->GetRouterIdentity () && !r->IsUnreachable () && r->HasValidAddresses ()) + { r->DeleteBuffer (); r->ClearProperties (); // properties are not used for regular routers - m_RouterInfos[r->GetIdentHash ()] = r; - if (r->IsFloodfill () && r->IsReachable ()) // floodfill must be reachable - m_Floodfills.push_back (r); + if (m_RouterInfos.emplace (r->GetIdentHash (), r).second) + { + if (r->IsFloodfill () && r->IsEligibleFloodfill ()) + m_Floodfills.push_back (r); + } } else { @@ -556,8 +579,9 @@ namespace data void NetDb::SaveUpdated () { - int updatedCount = 0, deletedCount = 0; + int updatedCount = 0, deletedCount = 0, deletedFloodfillsCount = 0; auto total = m_RouterInfos.size (); + auto totalFloodfills = m_Floodfills.size (); uint64_t expirationTimeout = NETDB_MAX_EXPIRATION_TIMEOUT*1000LL; uint64_t ts = i2p::util::GetMillisecondsSinceEpoch(); auto uptime = i2p::context.GetUptime (); @@ -567,8 +591,10 @@ namespace data expirationTimeout = i2p::context.IsFloodfill () ? NETDB_FLOODFILL_EXPIRATION_TIMEOUT*1000LL : NETDB_MIN_EXPIRATION_TIMEOUT*1000LL + (NETDB_MAX_EXPIRATION_TIMEOUT - NETDB_MIN_EXPIRATION_TIMEOUT)*1000LL*NETDB_MIN_ROUTERS/total; + auto own = i2p::context.GetSharedRouterInfo (); for (auto& it: m_RouterInfos) { + if (it.second == own) continue; // skip own std::string ident = it.second->GetIdentHashBase64(); std::string path = m_Storage.Path(ident); if (it.second->IsUpdated ()) @@ -580,8 +606,9 @@ namespace data updatedCount++; continue; } - // make router reachable back if too few routers - if (it.second->IsUnreachable () && total - deletedCount < NETDB_MIN_ROUTERS) + // make router reachable back if too few routers or floodfills + if (it.second->IsUnreachable () && (total - deletedCount < NETDB_MIN_ROUTERS || + (it.second->IsFloodfill () && totalFloodfills - deletedFloodfillsCount < NETDB_MIN_FLOODFILLS))) it.second->SetUnreachable (false); // find & mark expired routers if (!it.second->IsReachable () && it.second->IsSSU (false)) @@ -595,6 +622,7 @@ namespace data if (it.second->IsUnreachable ()) { + if (it.second->IsFloodfill ()) deletedFloodfillsCount++; // delete RI file m_Storage.Remove(ident); deletedCount++; @@ -653,8 +681,8 @@ namespace data else { auto pool = i2p::tunnel::tunnels.GetExploratoryPool (); - auto outbound = pool ? pool->GetNextOutboundTunnel () : nullptr; - auto inbound = pool ? pool->GetNextInboundTunnel () : nullptr; + auto outbound = pool ? pool->GetNextOutboundTunnel (nullptr, floodfill->GetCompatibleTransports (false)) : nullptr; + auto inbound = pool ? pool->GetNextInboundTunnel (nullptr, floodfill->GetCompatibleTransports (true)) : nullptr; if (outbound && inbound) outbound->SendTunnelDataMsg (floodfill->GetIdentHash (), 0, dest->CreateRequestMessage (floodfill, inbound)); else @@ -935,7 +963,8 @@ namespace data if (router) { LogPrint (eLogDebug, "NetDb: requested RouterInfo ", key, " found"); - router->LoadBuffer (); + if (!router->GetBuffer ()) + router->LoadBuffer (m_Storage.Path (router->GetIdentHashBase64 ())); if (router->GetBuffer ()) replyMsg = CreateDatabaseStoreMsg (router); } @@ -988,7 +1017,7 @@ namespace data { uint64_t tag; memcpy (&tag, excluded + 33, 8); - replyMsg = i2p::garlic::WrapECIESX25519AEADRatchetMessage (replyMsg, sessionKey, tag); + replyMsg = i2p::garlic::WrapECIESX25519Message (replyMsg, sessionKey, tag); } else { @@ -1100,8 +1129,8 @@ namespace data { // otherwise through exploratory auto exploratoryPool = i2p::tunnel::tunnels.GetExploratoryPool (); - auto outbound = exploratoryPool ? exploratoryPool->GetNextOutboundTunnel () : nullptr; - auto inbound = exploratoryPool ? exploratoryPool->GetNextInboundTunnel () : nullptr; + auto outbound = exploratoryPool ? exploratoryPool->GetNextOutboundTunnel (nullptr, floodfill->GetCompatibleTransports (false)) : nullptr; + auto inbound = exploratoryPool ? exploratoryPool->GetNextInboundTunnel (nullptr, floodfill->GetCompatibleTransports (true)) : nullptr; if (inbound && outbound) outbound->SendTunnelDataMsg (floodfill->GetIdentHash (), 0, CreateDatabaseStoreMsg (i2p::context.GetSharedRouterInfo (), replyToken, inbound)); @@ -1145,7 +1174,8 @@ namespace data { return !router->IsHidden () && router != compatibleWith && (reverse ? compatibleWith->IsReachableFrom (*router) : - router->IsReachableFrom (*compatibleWith)); + router->IsReachableFrom (*compatibleWith)) && + router->IsECIES (); }); } @@ -1154,8 +1184,8 @@ namespace data return GetRandomRouter ( [v4, &excluded](std::shared_ptr router)->bool { - return !router->IsHidden () && router->IsPeerTesting (v4) && - !excluded.count (router->GetIdentHash ()); + return !router->IsHidden () && router->IsECIES () && + router->IsPeerTesting (v4) && !excluded.count (router->GetIdentHash ()); }); } @@ -1164,7 +1194,7 @@ namespace data return GetRandomRouter ( [](std::shared_ptr router)->bool { - return !router->IsHidden () && router->IsSSUV6 (); + return !router->IsHidden () && router->IsECIES () && router->IsSSUV6 (); }); } @@ -1173,8 +1203,8 @@ namespace data return GetRandomRouter ( [v4, &excluded](std::shared_ptr router)->bool { - return router->IsIntroducer (v4) && !excluded.count (router->GetIdentHash ()) && - !router->IsHidden () && !router->IsFloodfill (); // floodfills don't send relay tag + return !router->IsHidden () && router->IsECIES () && !router->IsFloodfill () && // floodfills don't send relay tag + router->IsIntroducer (v4) && !excluded.count (router->GetIdentHash ()); }); } @@ -1186,8 +1216,9 @@ namespace data return !router->IsHidden () && router != compatibleWith && (reverse ? compatibleWith->IsReachableFrom (*router) : router->IsReachableFrom (*compatibleWith)) && - (router->GetCaps () & RouterInfo::eHighBandwidth) && - router->GetVersion () >= NETDB_MIN_HIGHBANDWIDTH_VERSION; + (router->GetCaps () & RouterInfo::eHighBandwidth) && + router->GetVersion () >= NETDB_MIN_HIGHBANDWIDTH_VERSION && + router->IsECIES (); }); } @@ -1196,23 +1227,55 @@ namespace data { if (m_RouterInfos.empty()) return 0; - uint32_t ind = rand () % m_RouterInfos.size (); - for (int j = 0; j < 2; j++) + uint16_t inds[3]; + RAND_bytes ((uint8_t *)inds, sizeof (inds)); + std::unique_lock l(m_RouterInfosMutex); + inds[0] %= m_RouterInfos.size (); + auto it = m_RouterInfos.begin (); + std::advance (it, inds[0]); + // try random router + if (it != m_RouterInfos.end () && !it->second->IsUnreachable () && filter (it->second)) + return it->second; + // try some routers around + auto it1 = m_RouterInfos.begin (); + if (inds[0]) { - uint32_t i = 0; - std::unique_lock l(m_RouterInfosMutex); - for (const auto& it: m_RouterInfos) - { - if (i >= ind) - { - if (!it.second->IsUnreachable () && filter (it.second)) - return it.second; - } - else - i++; - } - // we couldn't find anything, try second pass - ind = 0; + // before + inds[1] %= inds[0]; + std::advance (it1, (inds[1] + inds[0])/2); + } + else + it1 = it; + auto it2 = it; + if (inds[0] < m_RouterInfos.size () - 1) + { + // after + inds[2] %= (m_RouterInfos.size () - 1 - inds[0]); inds[2] /= 2; + std::advance (it2, inds[2]); + } + // it1 - from, it2 - to + it = it1; + while (it != it2 && it != m_RouterInfos.end ()) + { + if (!it->second->IsUnreachable () && filter (it->second)) + return it->second; + it++; + } + // still not found, try from the begining + it = m_RouterInfos.begin (); + while (it != it1 && it != m_RouterInfos.end ()) + { + if (!it->second->IsUnreachable () && filter (it->second)) + return it->second; + it++; + } + // still not found, try to the begining + it = it2; + while (it != m_RouterInfos.end ()) + { + if (!it->second->IsUnreachable () && filter (it->second)) + return it->second; + it++; } return nullptr; // seems we have too few routers } diff --git a/libi2pd/NetDb.hpp b/libi2pd/NetDb.hpp index 364cae4b..097f92e7 100644 --- a/libi2pd/NetDb.hpp +++ b/libi2pd/NetDb.hpp @@ -36,6 +36,7 @@ namespace i2p namespace data { const int NETDB_MIN_ROUTERS = 90; + const int NETDB_MIN_FLOODFILLS = 5; const int NETDB_FLOODFILL_EXPIRATION_TIMEOUT = 60 * 60; // 1 hour, in seconds const int NETDB_INTRODUCEE_EXPIRATION_TIMEOUT = 65 * 60; const int NETDB_MIN_EXPIRATION_TIMEOUT = 90 * 60; // 1.5 hours @@ -44,7 +45,8 @@ namespace data const int NETDB_PUBLISH_CONFIRMATION_TIMEOUT = 5; // in seconds const int NETDB_MAX_PUBLISH_EXCLUDED_FLOODFILLS = 15; const int NETDB_MIN_HIGHBANDWIDTH_VERSION = MAKE_VERSION_NUMBER(0, 9, 36); // 0.9.36 - const int NETDB_MIN_FLOODFILL_VERSION = MAKE_VERSION_NUMBER(0, 9, 28); // 0.9.28 + const int NETDB_MIN_FLOODFILL_VERSION = MAKE_VERSION_NUMBER(0, 9, 38); // 0.9.38 + const int NETDB_MIN_SHORT_TUNNEL_BUILD_VERSION = MAKE_VERSION_NUMBER(0, 9, 51); // 0.9.51 /** function for visiting a leaseset stored in a floodfill */ typedef std::function)> LeaseSetVisitor; diff --git a/libi2pd/Reseed.cpp b/libi2pd/Reseed.cpp index 41111ecc..2e8eccf7 100644 --- a/libi2pd/Reseed.cpp +++ b/libi2pd/Reseed.cpp @@ -478,7 +478,7 @@ namespace data if (terminator) terminator[0] = 0; } // extract RSA key (we need n only, e = 65537) - RSA * key = EVP_PKEY_get0_RSA (X509_get_pubkey (cert)); + const RSA * key = EVP_PKEY_get0_RSA (X509_get_pubkey (cert)); const BIGNUM * n, * e, * d; RSA_get0_key(key, &n, &e, &d); PublicKey value; @@ -497,7 +497,8 @@ namespace data void Reseeder::LoadCertificates () { - std::string certDir = i2p::fs::DataDirPath("certificates", "reseed"); + std::string certDir = i2p::fs::GetCertsDir() + i2p::fs::dirSep + "reseed"; + std::vector files; int numCertificates = 0; diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index d1f503bb..3710092b 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -43,13 +43,8 @@ namespace i2p m_Decryptor = m_Keys.CreateDecryptor (nullptr); m_TunnelDecryptor = m_Keys.CreateDecryptor (nullptr); UpdateRouterInfo (); - if (IsECIES ()) - { - auto initState = new i2p::crypto::NoiseSymmetricState (); - i2p::crypto::InitNoiseNState (*initState, GetIdentity ()->GetEncryptionPublicKey ()); - m_InitialNoiseState.reset (initState); - m_ECIESSession = std::make_shared(*initState); - } + i2p::crypto::InitNoiseNState (m_InitialNoiseState, GetIdentity ()->GetEncryptionPublicKey ()); + m_ECIESSession = std::make_shared(m_InitialNoiseState); } void RouterContext::CreateNewRouter () @@ -472,7 +467,8 @@ namespace i2p uint8_t caps = m_RouterInfo.GetCaps (); caps &= ~i2p::data::RouterInfo::eReachable; caps |= i2p::data::RouterInfo::eUnreachable; - caps &= ~i2p::data::RouterInfo::eFloodfill; // can't be floodfill + if (v6 || !SupportsV6 ()) + caps &= ~i2p::data::RouterInfo::eFloodfill; // can't be floodfill m_RouterInfo.SetCaps (caps); } uint16_t port = 0; @@ -486,11 +482,12 @@ namespace i2p addr->ssu->introducers.clear (); port = addr->port; } - // unpiblish NTCP2 addreeses + // unpublish NTCP2 addreeses bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); if (ntcp2) PublishNTCP2Address (port, false, v4, v6, false); // update + m_RouterInfo.UpdateSupportedTransports (); UpdateRouterInfo (); } @@ -530,6 +527,7 @@ namespace i2p } } // update + m_RouterInfo.UpdateSupportedTransports (); UpdateRouterInfo (); } @@ -568,16 +566,21 @@ namespace i2p { bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); bool ntcp2Published; i2p::config::GetOption("ntcp2.published", ntcp2Published); - if (ntcp2 && ntcp2Published) + if (ntcp2) { - std::string ntcp2Host; - if (!i2p::config::IsDefault ("ntcp2.addressv6")) - i2p::config::GetOption ("ntcp2.addressv6", ntcp2Host); + if (ntcp2Published) + { + std::string ntcp2Host; + if (!i2p::config::IsDefault ("ntcp2.addressv6")) + i2p::config::GetOption ("ntcp2.addressv6", ntcp2Host); + else + ntcp2Host = "::1"; + uint16_t ntcp2Port; i2p::config::GetOption ("ntcp2.port", ntcp2Port); + if (!ntcp2Port) ntcp2Port = port; + m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, boost::asio::ip::address::from_string (ntcp2Host), ntcp2Port); + } else - ntcp2Host = "::1"; - uint16_t ntcp2Port; i2p::config::GetOption ("ntcp2.port", ntcp2Port); - if (!ntcp2Port) ntcp2Port = port; - m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, boost::asio::ip::address::from_string (ntcp2Host), ntcp2Port); + m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, boost::asio::ip::address(), 0, i2p::data::RouterInfo::eV6); } } m_RouterInfo.EnableV6 (); @@ -632,7 +635,7 @@ namespace i2p m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, boost::asio::ip::address::from_string (host), ntcp2Port); } else - m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv); + m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, boost::asio::ip::address(), 0, i2p::data::RouterInfo::eV4); } } m_RouterInfo.EnableV4 (); @@ -679,7 +682,7 @@ namespace i2p if (addr->IsPublishedNTCP2 ()) { bool isYgg1 = i2p::util::net::IsYggdrasilAddress (addr->host); - if (addr->host.is_v6 () && ((isYgg && isYgg1) || (!isYgg && !isYgg1))) + if (addr->IsV6 () && ((isYgg && isYgg1) || (!isYgg && !isYgg1))) { if (addr->host != host) { @@ -815,9 +818,9 @@ namespace i2p i2p::HandleI2NPMessage (CreateI2NPMessage (buf, GetI2NPMessageLength (buf, len))); } - bool RouterContext::HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len) + bool RouterContext::HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID) { - auto msg = CreateI2NPMessage (typeID, payload, len); + auto msg = CreateI2NPMessage (typeID, payload, len, msgID); if (!msg) return false; i2p::HandleI2NPMessage (msg); return true; @@ -827,23 +830,22 @@ namespace i2p void RouterContext::ProcessGarlicMessage (std::shared_ptr msg) { std::unique_lock l(m_GarlicMutex); - if (IsECIES ()) + uint8_t * buf = msg->GetPayload (); + uint32_t len = bufbe32toh (buf); + if (len > msg->GetLength ()) { - uint8_t * buf = msg->GetPayload (); - uint32_t len = bufbe32toh (buf); - if (len > msg->GetLength ()) - { - LogPrint (eLogWarning, "Router: garlic message length ", len, " exceeds I2NP message length ", msg->GetLength ()); - return; - } - buf += 4; + LogPrint (eLogWarning, "Router: garlic message length ", len, " exceeds I2NP message length ", msg->GetLength ()); + return; + } + buf += 4; + if (!HandleECIESx25519TagMessage (buf, len)) // try tag first + { + // then Noise_N one-time decryption if (m_ECIESSession) m_ECIESSession->HandleNextMessage (buf, len); else LogPrint (eLogError, "Router: Session is not set for ECIES router"); - } - else - i2p::garlic::GarlicDestination::ProcessGarlicMessage (msg); + } } void RouterContext::ProcessDeliveryStatusMessage (std::shared_ptr msg) @@ -868,60 +870,44 @@ namespace i2p return std::chrono::duration_cast (std::chrono::steady_clock::now() - m_StartupTime).count (); } - bool RouterContext::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, i2p::data::CryptoKeyType preferredCrypto) const + bool RouterContext::Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const { - return m_Decryptor ? m_Decryptor->Decrypt (encrypted, data, ctx, true) : false; + return m_Decryptor ? m_Decryptor->Decrypt (encrypted, data) : false; } bool RouterContext::DecryptTunnelBuildRecord (const uint8_t * encrypted, uint8_t * data) { - if (IsECIES ()) - return DecryptECIESTunnelBuildRecord (encrypted, data, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE); - else - { - if (!m_TunnelDecryptor) return false; - BN_CTX * ctx = BN_CTX_new (); - bool success = m_TunnelDecryptor->Decrypt (encrypted, data, ctx, false); - BN_CTX_free (ctx); - return success; - } + return DecryptECIESTunnelBuildRecord (encrypted, data, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE); } bool RouterContext::DecryptECIESTunnelBuildRecord (const uint8_t * encrypted, uint8_t * data, size_t clearTextSize) { - if (!m_InitialNoiseState || !m_TunnelDecryptor) return false; // m_InitialNoiseState is h = SHA256(h || hepk) - m_CurrentNoiseState.reset (new i2p::crypto::NoiseSymmetricState (*m_InitialNoiseState)); - m_CurrentNoiseState->MixHash (encrypted, 32); // h = SHA256(h || sepk) + m_CurrentNoiseState = m_InitialNoiseState; + m_CurrentNoiseState.MixHash (encrypted, 32); // h = SHA256(h || sepk) uint8_t sharedSecret[32]; - if (!m_TunnelDecryptor->Decrypt (encrypted, sharedSecret, nullptr, false)) + if (!m_TunnelDecryptor->Decrypt (encrypted, sharedSecret)) { LogPrint (eLogWarning, "Router: Incorrect ephemeral public key"); return false; } - m_CurrentNoiseState->MixKey (sharedSecret); + m_CurrentNoiseState.MixKey (sharedSecret); encrypted += 32; uint8_t nonce[12]; memset (nonce, 0, 12); - if (!i2p::crypto::AEADChaCha20Poly1305 (encrypted, clearTextSize, m_CurrentNoiseState->m_H, 32, - m_CurrentNoiseState->m_CK + 32, nonce, data, clearTextSize, false)) // decrypt + if (!i2p::crypto::AEADChaCha20Poly1305 (encrypted, clearTextSize, m_CurrentNoiseState.m_H, 32, + m_CurrentNoiseState.m_CK + 32, nonce, data, clearTextSize, false)) // decrypt { LogPrint (eLogWarning, "Router: Tunnel record AEAD decryption failed"); return false; } - m_CurrentNoiseState->MixHash (encrypted, clearTextSize + 16); // h = SHA256(h || ciphertext) + m_CurrentNoiseState.MixHash (encrypted, clearTextSize + 16); // h = SHA256(h || ciphertext) return true; } bool RouterContext::DecryptTunnelShortRequestRecord (const uint8_t * encrypted, uint8_t * data) { - if (IsECIES ()) - return DecryptECIESTunnelBuildRecord (encrypted, data, SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE); - else - { - LogPrint (eLogWarning, "Router: Can't decrypt short request record on non-ECIES router"); - return false; - } + return DecryptECIESTunnelBuildRecord (encrypted, data, SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE); } i2p::crypto::X25519Keys& RouterContext::GetStaticKeys () diff --git a/libi2pd/RouterContext.h b/libi2pd/RouterContext.h index 10498b3b..647c1a7f 100644 --- a/libi2pd/RouterContext.h +++ b/libi2pd/RouterContext.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -18,7 +18,6 @@ #include "Identity.h" #include "RouterInfo.h" #include "Garlic.h" -#include "I18N_langs.h" namespace i2p { @@ -68,11 +67,11 @@ namespace garlic void Init (); const i2p::data::PrivateKeys& GetPrivateKeys () const { return m_Keys; }; - i2p::data::RouterInfo& GetRouterInfo () { return m_RouterInfo; }; - std::shared_ptr GetSharedRouterInfo () const + i2p::data::RouterInfo& GetRouterInfo () { return m_RouterInfo; }; + std::shared_ptr GetSharedRouterInfo () { - return std::shared_ptr (&m_RouterInfo, - [](const i2p::data::RouterInfo *) {}); + return std::shared_ptr (&m_RouterInfo, + [](i2p::data::RouterInfo *) {}); } std::shared_ptr GetSharedDestination () { @@ -124,8 +123,7 @@ namespace garlic void SetSupportsV6 (bool supportsV6); void SetSupportsV4 (bool supportsV4); void SetSupportsMesh (bool supportsmesh, const boost::asio::ip::address_v6& host); - bool IsECIES () const { return GetIdentity ()->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD; }; - std::unique_ptr& GetCurrentNoiseState () { return m_CurrentNoiseState; }; + i2p::crypto::NoiseSymmetricState& GetCurrentNoiseState () { return m_CurrentNoiseState; }; void UpdateNTCP2V6Address (const boost::asio::ip::address& host); // called from Daemon. TODO: remove void UpdateStats (); @@ -134,7 +132,7 @@ namespace garlic // implements LocalDestination std::shared_ptr GetIdentity () const { return m_Keys.GetPublic (); }; - bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, i2p::data::CryptoKeyType preferredCrypto) const; + bool Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const; void Sign (const uint8_t * buf, int len, uint8_t * signature) const { m_Keys.Sign (buf, len, signature); }; void SetLeaseSetUpdated () {}; @@ -146,15 +144,11 @@ namespace garlic void ProcessGarlicMessage (std::shared_ptr msg); void ProcessDeliveryStatusMessage (std::shared_ptr msg); - // i18n - std::shared_ptr GetLanguage () { return m_Language; }; - void SetLanguage (const std::shared_ptr language) { m_Language = language; }; - protected: // implements GarlicDestination void HandleI2NPMessage (const uint8_t * buf, size_t len); - bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len); + bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID); private: @@ -185,10 +179,7 @@ namespace garlic std::unique_ptr m_NTCP2Keys; std::unique_ptr m_StaticKeys; // for ECIESx25519 - std::unique_ptr m_InitialNoiseState, m_CurrentNoiseState; - - // i18n - std::shared_ptr m_Language; + i2p::crypto::NoiseSymmetricState m_InitialNoiseState, m_CurrentNoiseState; }; extern RouterContext context; diff --git a/libi2pd/RouterInfo.cpp b/libi2pd/RouterInfo.cpp index 3035bd31..cb5a3db7 100644 --- a/libi2pd/RouterInfo.cpp +++ b/libi2pd/RouterInfo.cpp @@ -35,12 +35,12 @@ namespace data } RouterInfo::RouterInfo (const std::string& fullPath): - m_FullPath (fullPath), m_IsUpdated (false), m_IsUnreachable (false), - m_SupportedTransports (0), m_ReachableTransports (0), m_Caps (0), m_Version (0) + m_IsUpdated (false), m_IsUnreachable (false), m_SupportedTransports (0), + m_ReachableTransports (0), m_Caps (0), m_Version (0) { m_Addresses = boost::make_shared(); // create empty list m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE]; - ReadFromFile (); + ReadFromFile (fullPath); } RouterInfo::RouterInfo (const uint8_t * buf, int len): @@ -113,16 +113,16 @@ namespace data m_Timestamp = i2p::util::GetMillisecondsSinceEpoch (); } - bool RouterInfo::LoadFile () + bool RouterInfo::LoadFile (const std::string& fullPath) { - std::ifstream s(m_FullPath, std::ifstream::binary); + std::ifstream s(fullPath, std::ifstream::binary); if (s.is_open ()) { s.seekg (0,std::ios::end); m_BufferLen = s.tellg (); if (m_BufferLen < 40 || m_BufferLen > MAX_RI_BUFFER_SIZE) { - LogPrint(eLogError, "RouterInfo: File", m_FullPath, " is malformed"); + LogPrint(eLogError, "RouterInfo: File", fullPath, " is malformed"); return false; } s.seekg(0, std::ios::beg); @@ -131,15 +131,15 @@ namespace data } else { - LogPrint (eLogError, "RouterInfo: Can't open file ", m_FullPath); + LogPrint (eLogError, "RouterInfo: Can't open file ", fullPath); return false; } return true; } - void RouterInfo::ReadFromFile () + void RouterInfo::ReadFromFile (const std::string& fullPath) { - if (LoadFile ()) + if (LoadFile (fullPath)) ReadFromBuffer (false); else m_IsUnreachable = true; @@ -187,13 +187,15 @@ namespace data void RouterInfo::ReadFromStream (std::istream& s) { + if (!s) return; m_Caps = 0; s.read ((char *)&m_Timestamp, sizeof (m_Timestamp)); m_Timestamp = be64toh (m_Timestamp); // read addresses auto addresses = boost::make_shared(); uint8_t numAddresses; - s.read ((char *)&numAddresses, sizeof (numAddresses)); if (!s) return; + s.read ((char *)&numAddresses, sizeof (numAddresses)); + addresses->reserve (numAddresses); for (int i = 0; i < numAddresses; i++) { uint8_t supportedTransports = 0; @@ -281,7 +283,11 @@ namespace data if (s) continue; else return; } if (index >= address->ssu->introducers.size ()) + { + if (address->ssu->introducers.empty ()) // first time + address->ssu->introducers.reserve (3); address->ssu->introducers.resize (index + 1); + } Introducer& introducer = address->ssu->introducers.at (index); if (!strcmp (key, "ihost")) { @@ -343,7 +349,8 @@ namespace data int numValid = 0; for (auto& it: address->ssu->introducers) { - if ((!it.iExp || ts <= it.iExp) && it.iPort > 0 && + if (!it.iExp) it.iExp = m_Timestamp/1000 + NETDB_INTRODUCEE_EXPIRATION_TIMEOUT; + if (ts <= it.iExp && it.iPort > 0 && ((it.iHost.is_v4 () && address->IsV4 ()) || (it.iHost.is_v6 () && address->IsV6 ()))) numValid++; else @@ -554,7 +561,7 @@ namespace data if (address.IsNTCP2 ()) { WriteString ("NTCP2", s); - if (address.IsPublishedNTCP2 () && !address.host.is_unspecified ()) + if (address.IsPublishedNTCP2 () && !address.host.is_unspecified () && address.port) isPublished = true; else { @@ -747,11 +754,11 @@ namespace data return bufbe64toh (buf + size) > m_Timestamp; } - const uint8_t * RouterInfo::LoadBuffer () + const uint8_t * RouterInfo::LoadBuffer (const std::string& fullPath) { if (!m_Buffer) { - if (LoadFile ()) + if (LoadFile (fullPath)) LogPrint (eLogDebug, "RouterInfo: Buffer for ", GetIdentHashAbbreviation (GetIdentHash ()), " loaded from file"); } return m_Buffer; @@ -782,8 +789,8 @@ namespace data bool RouterInfo::SaveToFile (const std::string& fullPath) { - m_FullPath = fullPath; - if (!m_Buffer) { + if (!m_Buffer) + { LogPrint (eLogError, "RouterInfo: Can't save, m_Buffer == NULL"); return false; } @@ -840,21 +847,33 @@ namespace data for (const auto& it: *m_Addresses) // don't insert same address twice if (*it == *addr) return; m_SupportedTransports |= addr->host.is_v6 () ? eSSUV6 : eSSUV4; + m_ReachableTransports |= addr->host.is_v6 () ? eSSUV6 : eSSUV4; m_Addresses->push_back(std::move(addr)); } - void RouterInfo::AddNTCP2Address (const uint8_t * staticKey, const uint8_t * iv, const boost::asio::ip::address& host, int port) + void RouterInfo::AddNTCP2Address (const uint8_t * staticKey, const uint8_t * iv, + const boost::asio::ip::address& host, int port, uint8_t caps) { auto addr = std::make_shared

(); addr->host = host; addr->port = port; addr->transportStyle = eTransportNTCP; - addr->caps = 0; + addr->caps = caps; addr->date = 0; addr->ntcp2.reset (new NTCP2Ext ()); if (port) addr->published = true; memcpy (addr->ntcp2->staticKey, staticKey, 32); memcpy (addr->ntcp2->iv, iv, 16); + if (addr->IsV4 ()) + { + m_SupportedTransports |= eNTCP2V4; + if (addr->published) m_ReachableTransports |= eNTCP2V4; + } + if (addr->IsV6 ()) + { + m_SupportedTransports |= eNTCP2V6; + if (addr->published) m_ReachableTransports |= eNTCP2V6; + } m_Addresses->push_back(std::move(addr)); } @@ -971,10 +990,10 @@ namespace data { if (!IsV6 ()) { - m_SupportedTransports |= eSSUV6 | eNTCP2V6; uint8_t addressCaps = AddressCaps::eV6; if (IsV4 ()) addressCaps |= AddressCaps::eV4; SetUnreachableAddressesTransportCaps (addressCaps); + UpdateSupportedTransports (); } } @@ -982,10 +1001,10 @@ namespace data { if (!IsV4 ()) { - m_SupportedTransports |= eSSUV4 | eNTCP2V4; uint8_t addressCaps = AddressCaps::eV4; if (IsV6 ()) addressCaps |= AddressCaps::eV6; SetUnreachableAddressesTransportCaps (addressCaps); + UpdateSupportedTransports (); } } @@ -994,16 +1013,23 @@ namespace data { if (IsV6 ()) { - m_SupportedTransports &= ~(eSSUV6 | eNTCP2V6); for (auto it = m_Addresses->begin (); it != m_Addresses->end ();) { auto addr = *it; - addr->caps &= ~AddressCaps::eV6; - if (addr->host.is_v6 ()) - it = m_Addresses->erase (it); + if (addr->IsV6 ()) + { + if (addr->IsV4 ()) + { + addr->caps &= ~AddressCaps::eV6; + ++it; + } + else + it = m_Addresses->erase (it); + } else ++it; } + UpdateSupportedTransports (); } } @@ -1011,23 +1037,33 @@ namespace data { if (IsV4 ()) { - m_SupportedTransports &= ~(eSSUV4 | eNTCP2V4); for (auto it = m_Addresses->begin (); it != m_Addresses->end ();) { auto addr = *it; - addr->caps &= ~AddressCaps::eV4; - if (addr->host.is_v4 ()) - it = m_Addresses->erase (it); + if (addr->IsV4 ()) + { + if (addr->IsV6 ()) + { + addr->caps &= ~AddressCaps::eV4; + ++it; + } + else + it = m_Addresses->erase (it); + } else ++it; } + UpdateSupportedTransports (); } } void RouterInfo::EnableMesh () { if (!IsMesh ()) + { m_SupportedTransports |= eNTCP2V6Mesh; + m_ReachableTransports |= eNTCP2V6Mesh; + } } void RouterInfo::DisableMesh () @@ -1035,6 +1071,7 @@ namespace data if (IsMesh ()) { m_SupportedTransports &= ~eNTCP2V6Mesh; + m_ReachableTransports &= ~eNTCP2V6Mesh; for (auto it = m_Addresses->begin (); it != m_Addresses->end ();) { auto addr = *it; @@ -1124,18 +1161,17 @@ namespace data return m_Profile; } - void RouterInfo::Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx) const + void RouterInfo::Encrypt (const uint8_t * data, uint8_t * encrypted) const { auto encryptor = m_RouterIdentity->CreateEncryptor (nullptr); if (encryptor) - encryptor->Encrypt (data, encrypted, ctx, true); + encryptor->Encrypt (data, encrypted); } bool RouterInfo::IsEligibleFloodfill () const { - // floodfill must be reachable somehow, >= 0.9.28 and not DSA - return (IsReachable () || (m_SupportedTransports & eSSUV4)) && - m_Version >= NETDB_MIN_FLOODFILL_VERSION && + // floodfill must be reachable by ipv4, >= 0.9.38 and not DSA + return IsReachableBy (eNTCP2V4 | eSSUV4) && m_Version >= NETDB_MIN_FLOODFILL_VERSION && GetIdentity ()->GetSigningKeyType () != SIGNING_KEY_TYPE_DSA_SHA1; } @@ -1161,34 +1197,12 @@ namespace data }); } - bool RouterInfo::IsReachableFrom (const RouterInfo& other) const - { - auto commonTransports = m_SupportedTransports & other.m_SupportedTransports; - if (!commonTransports) return false; - if (commonTransports & eNTCP2V6Mesh) return true; - return (bool)GetAddress ( - [commonTransports](std::shared_ptr address)->bool - { - if (address->IsPublishedNTCP2 ()) - { - if ((commonTransports & eNTCP2V4) && address->IsV4 ()) return true; - if ((commonTransports & eNTCP2V6) && address->IsV6 ()) return true; - } - else if (address->IsReachableSSU ()) - { - if ((commonTransports & eSSUV4) && address->IsV4 ()) return true; - if ((commonTransports & eSSUV6) && address->IsV6 ()) return true; - } - return false; - }); - } - void RouterInfo::SetUnreachableAddressesTransportCaps (uint8_t transports) { for (auto& addr: *m_Addresses) { // TODO: implement SSU - if (addr->transportStyle == eTransportNTCP && (!addr->IsPublishedNTCP2 () || addr->port)) + if (addr->transportStyle == eTransportNTCP && !addr->IsPublishedNTCP2 ()) { addr->caps &= ~(eV4 | eV6); addr->caps |= transports; diff --git a/libi2pd/RouterInfo.h b/libi2pd/RouterInfo.h index bea83fda..de1fe235 100644 --- a/libi2pd/RouterInfo.h +++ b/libi2pd/RouterInfo.h @@ -13,7 +13,6 @@ #include #include #include -#include #include #include #include @@ -65,9 +64,11 @@ namespace data eNTCP2V6 = 0x02, eSSUV4 = 0x04, eSSUV6 = 0x08, - eNTCP2V6Mesh = 0x10 + eNTCP2V6Mesh = 0x10, + eAllTransports = 0xFF }; - + typedef uint8_t CompatibleTransports; + enum Caps { eFloodfill = 0x01, @@ -156,7 +157,7 @@ namespace data bool IsV4 () const { return (caps & AddressCaps::eV4) || (host.is_v4 () && !host.is_unspecified ()); }; bool IsV6 () const { return (caps & AddressCaps::eV6) || (host.is_v6 () && !host.is_unspecified ()); }; }; - typedef std::list > Addresses; + typedef std::vector > Addresses; RouterInfo (); RouterInfo (const std::string& fullPath); @@ -179,7 +180,8 @@ namespace data std::shared_ptr GetYggdrasilAddress () const; void AddSSUAddress (const char * host, int port, const uint8_t * key, int mtu = 0); - void AddNTCP2Address (const uint8_t * staticKey, const uint8_t * iv, const boost::asio::ip::address& host = boost::asio::ip::address(), int port = 0); + void AddNTCP2Address (const uint8_t * staticKey, const uint8_t * iv, + const boost::asio::ip::address& host = boost::asio::ip::address(), int port = 0, uint8_t caps = 0); bool AddIntroducer (const Introducer& introducer); bool RemoveIntroducer (const boost::asio::ip::udp::endpoint& e); void SetProperty (const std::string& key, const std::string& value); // called from RouterContext only @@ -190,6 +192,7 @@ namespace data void UpdateSupportedTransports (); bool IsFloodfill () const { return m_Caps & Caps::eFloodfill; }; bool IsReachable () const { return m_Caps & Caps::eReachable; }; + bool IsECIES () const { return m_RouterIdentity->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD; }; bool IsSSU (bool v4only = true) const; bool IsSSUV6 () const; bool IsNTCP2 (bool v4only = true) const; @@ -204,8 +207,10 @@ namespace data void EnableMesh (); void DisableMesh (); bool IsCompatible (const RouterInfo& other) const { return m_SupportedTransports & other.m_SupportedTransports; }; - bool IsReachableFrom (const RouterInfo& other) const; - bool HasValidAddresses () const { return m_SupportedTransports; }; + bool IsReachableFrom (const RouterInfo& other) const { return m_ReachableTransports & other.m_SupportedTransports; }; + bool IsReachableBy (CompatibleTransports transports) const { return m_ReachableTransports & transports; }; + CompatibleTransports GetCompatibleTransports (bool incoming) const { return incoming ? m_ReachableTransports : m_SupportedTransports; }; + bool HasValidAddresses () const { return m_SupportedTransports; }; bool IsHidden () const { return m_Caps & eHidden; }; bool IsHighBandwidth () const { return m_Caps & RouterInfo::eHighBandwidth; }; bool IsExtraBandwidth () const { return m_Caps & RouterInfo::eExtraBandwidth; }; @@ -221,7 +226,7 @@ namespace data bool IsUnreachable () const { return m_IsUnreachable; }; const uint8_t * GetBuffer () const { return m_Buffer; }; - const uint8_t * LoadBuffer (); // load if necessary + const uint8_t * LoadBuffer (const std::string& fullPath); // load if necessary int GetBufferLen () const { return m_BufferLen; }; void CreateBuffer (const PrivateKeys& privateKeys); @@ -241,14 +246,14 @@ namespace data // implements RoutingDestination std::shared_ptr GetIdentity () const { return m_RouterIdentity; }; - void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx) const; + void Encrypt (const uint8_t * data, uint8_t * encrypted) const; bool IsDestination () const { return false; }; private: - bool LoadFile (); - void ReadFromFile (); + bool LoadFile (const std::string& fullPath); + void ReadFromFile (const std::string& fullPath); void ReadFromStream (std::istream& s); void ReadFromBuffer (bool verifySignature); void WriteToStream (std::ostream& s) const; @@ -262,7 +267,7 @@ namespace data private: - std::string m_FullPath, m_Family; + std::string m_Family; std::shared_ptr m_RouterIdentity; uint8_t * m_Buffer; size_t m_BufferLen; @@ -270,7 +275,8 @@ namespace data boost::shared_ptr m_Addresses; // TODO: use std::shared_ptr and std::atomic_store for gcc >= 4.9 std::map m_Properties; bool m_IsUpdated, m_IsUnreachable; - uint8_t m_SupportedTransports, m_ReachableTransports, m_Caps; + CompatibleTransports m_SupportedTransports, m_ReachableTransports; + uint8_t m_Caps; int m_Version; mutable std::shared_ptr m_Profile; }; diff --git a/libi2pd/SSU.cpp b/libi2pd/SSU.cpp index e4901f7c..be263370 100644 --- a/libi2pd/SSU.cpp +++ b/libi2pd/SSU.cpp @@ -70,7 +70,11 @@ namespace transport if (m_EndpointV6.address() == boost::asio::ip::address().from_string("::")) // only if not binded to address { // Set preference to use public IPv6 address -- tested on linux, not works on windows, and not tested on others +#if (BOOST_VERSION >= 105500) typedef boost::asio::detail::socket_option::integer ipv6PreferAddr; +#else + typedef boost::asio::detail::socket_option::integer ipv6PreferAddr; +#endif m_SocketV6.set_option (ipv6PreferAddr(IPV6_PREFER_SRC_PUBLIC | IPV6_PREFER_SRC_HOME | IPV6_PREFER_SRC_NONCGA)); } #endif @@ -214,7 +218,7 @@ namespace transport void SSUServer::AddRelay (uint32_t tag, std::shared_ptr relay) { - m_Relays[tag] = relay; + m_Relays.emplace (tag, relay); } void SSUServer::RemoveRelay (uint32_t tag) @@ -251,14 +255,14 @@ namespace transport void SSUServer::Receive () { - SSUPacket * packet = new SSUPacket (); + SSUPacket * packet = m_PacketsPool.AcquireMt (); m_Socket.async_receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V4), packet->from, std::bind (&SSUServer::HandleReceivedFrom, this, std::placeholders::_1, std::placeholders::_2, packet)); } void SSUServer::ReceiveV6 () { - SSUPacket * packet = new SSUPacket (); + SSUPacket * packet = m_PacketsPool.AcquireMt (); m_SocketV6.async_receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V6), packet->from, std::bind (&SSUServer::HandleReceivedFromV6, this, std::placeholders::_1, std::placeholders::_2, packet)); } @@ -289,7 +293,7 @@ namespace transport { while (moreBytes && packets.size () < 25) { - packet = new SSUPacket (); + packet = m_PacketsPool.AcquireMt (); packet->len = m_Socket.receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V4), packet->from, 0, ec); if (!ec) { @@ -300,7 +304,7 @@ namespace transport else { LogPrint (eLogError, "SSU: receive_from error: code ", ec.value(), ": ", ec.message ()); - delete packet; + m_PacketsPool.ReleaseMt (packet); break; } } @@ -311,7 +315,7 @@ namespace transport } else { - delete packet; + m_PacketsPool.ReleaseMt (packet); if (ecode != boost::asio::error::operation_aborted) { LogPrint (eLogError, "SSU: receive error: code ", ecode.value(), ": ", ecode.message ()); @@ -348,7 +352,7 @@ namespace transport { while (moreBytes && packets.size () < 25) { - packet = new SSUPacket (); + packet = m_PacketsPool.AcquireMt (); packet->len = m_SocketV6.receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V6), packet->from, 0, ec); if (!ec) { @@ -359,7 +363,7 @@ namespace transport else { LogPrint (eLogError, "SSU: v6 receive_from error: code ", ec.value(), ": ", ec.message ()); - delete packet; + m_PacketsPool.ReleaseMt (packet);; break; } } @@ -370,7 +374,7 @@ namespace transport } else { - delete packet; + m_PacketsPool.ReleaseMt (packet); if (ecode != boost::asio::error::operation_aborted) { LogPrint (eLogError, "SSU: v6 receive error: code ", ecode.value(), ": ", ecode.message ()); @@ -417,8 +421,8 @@ namespace transport if (session) session->FlushData (); session = nullptr; } - delete packet; } + m_PacketsPool.ReleaseMt (packets); if (session) session->FlushData (); } @@ -915,13 +919,19 @@ namespace transport } if (numDeleted > 0) LogPrint (eLogDebug, "SSU: ", numDeleted, " peer tests have been expired"); + // some cleaups. TODO: use separate timer + m_FragmentsPool.CleanUp (); + m_IncompleteMessagesPool.CleanUp (); + m_SentMessagesPool.CleanUp (); + SchedulePeerTestsCleanupTimer (); } } void SSUServer::ScheduleTermination () { - m_TerminationTimer.expires_from_now (boost::posix_time::seconds(SSU_TERMINATION_CHECK_TIMEOUT)); + uint64_t timeout = SSU_TERMINATION_CHECK_TIMEOUT + (rand () % SSU_TERMINATION_CHECK_TIMEOUT)/5; + m_TerminationTimer.expires_from_now (boost::posix_time::seconds(timeout)); m_TerminationTimer.async_wait (std::bind (&SSUServer::HandleTerminationTimer, this, std::placeholders::_1)); } @@ -943,13 +953,16 @@ namespace transport session->Failed (); }); } + else + it.second->CleanUp (ts); ScheduleTermination (); } } void SSUServer::ScheduleTerminationV6 () { - m_TerminationTimerV6.expires_from_now (boost::posix_time::seconds(SSU_TERMINATION_CHECK_TIMEOUT)); + uint64_t timeout = SSU_TERMINATION_CHECK_TIMEOUT + (rand () % SSU_TERMINATION_CHECK_TIMEOUT)/5; + m_TerminationTimerV6.expires_from_now (boost::posix_time::seconds(timeout)); m_TerminationTimerV6.async_wait (std::bind (&SSUServer::HandleTerminationTimerV6, this, std::placeholders::_1)); } @@ -971,6 +984,8 @@ namespace transport session->Failed (); }); } + else + it.second->CleanUp (ts); ScheduleTerminationV6 (); } } diff --git a/libi2pd/SSU.h b/libi2pd/SSU.h index aad3a384..b97cacb4 100644 --- a/libi2pd/SSU.h +++ b/libi2pd/SSU.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -18,6 +18,7 @@ #include #include #include "Crypto.h" +#include "util.h" #include "I2PEndian.h" #include "Identity.h" #include "RouterInfo.h" @@ -63,6 +64,10 @@ namespace transport void DeleteAllSessions (); boost::asio::io_service& GetService () { return m_Service; }; + i2p::util::MemoryPool& GetFragmentsPool () { return m_FragmentsPool; }; + i2p::util::MemoryPool& GetIncompleteMessagesPool () { return m_IncompleteMessagesPool; }; + i2p::util::MemoryPool& GetSentMessagesPool () { return m_SentMessagesPool; }; + uint16_t GetPort () const { return m_Endpoint.port (); }; void SetLocalAddress (const boost::asio::ip::address& localAddress); @@ -136,6 +141,11 @@ namespace transport std::map > m_Relays; // we are introducer std::map m_PeerTests; // nonce -> creation time in milliseconds + i2p::util::MemoryPool m_FragmentsPool; + i2p::util::MemoryPool m_IncompleteMessagesPool; + i2p::util::MemoryPool m_SentMessagesPool; + i2p::util::MemoryPoolMt m_PacketsPool; + public: // for HTTP only const decltype(m_Sessions)& GetSessions () const { return m_Sessions; }; diff --git a/libi2pd/SSUData.cpp b/libi2pd/SSUData.cpp index 5458cc97..2f93218e 100644 --- a/libi2pd/SSUData.cpp +++ b/libi2pd/SSUData.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -33,7 +33,6 @@ namespace transport SSUData::SSUData (SSUSession& session): m_Session (session), m_ResendTimer (session.GetService ()), - m_IncompleteMessagesCleanupTimer (session.GetService ()), m_MaxPacketSize (session.IsV6 () ? SSU_V6_MAX_PACKET_SIZE : SSU_V4_MAX_PACKET_SIZE), m_PacketSize (m_MaxPacketSize), m_LastMessageReceivedTime (0) { @@ -45,13 +44,11 @@ namespace transport void SSUData::Start () { - ScheduleIncompleteMessagesCleanup (); } void SSUData::Stop () { m_ResendTimer.cancel (); - m_IncompleteMessagesCleanupTimer.cancel (); m_IncompleteMessages.clear (); m_SentMessages.clear (); m_ReceivedMessages.clear (); @@ -140,7 +137,7 @@ namespace transport if (bitfield & mask) { if (fragment < numSentFragments) - it->second->fragments[fragment].reset (nullptr); + it->second->fragments[fragment] = nullptr; } fragment++; mask <<= 1; @@ -179,15 +176,16 @@ namespace transport if (it == m_IncompleteMessages.end ()) { // create new message - auto msg = NewI2NPShortMessage (); + auto msg = (!fragmentNum && fragmentSize > 0 && buf[I2NP_SHORT_HEADER_TYPEID_OFFSET] == eI2NPTunnelData) ? + NewI2NPTunnelMessage (true) : NewI2NPShortMessage (); msg->len -= I2NP_SHORT_HEADER_SIZE; it = m_IncompleteMessages.insert (std::make_pair (msgID, - std::unique_ptr(new IncompleteMessage (msg)))).first; + m_Session.GetServer ().GetIncompleteMessagesPool ().AcquireShared (msg))).first; } - std::unique_ptr& incompleteMessage = it->second; + auto& incompleteMessage = it->second; // mark fragment as received if (fragmentNum < 64) - incompleteMessage->receivedFragmentsBits |= (0x01 << fragmentNum); + incompleteMessage->receivedFragmentsBits |= (uint64_t(0x01) << fragmentNum); else LogPrint (eLogWarning, "SSU: Fragment number ", fragmentNum, " exceeds 64"); @@ -224,8 +222,8 @@ namespace transport { // missing fragment LogPrint (eLogWarning, "SSU: Missing fragments from ", (int)incompleteMessage->nextFragmentNum, " to ", fragmentNum - 1, " of message ", msgID); - auto savedFragment = new Fragment (fragmentNum, buf, fragmentSize, isLast); - if (incompleteMessage->savedFragments.insert (std::unique_ptr(savedFragment)).second) + auto savedFragment = m_Session.GetServer ().GetFragmentsPool ().AcquireShared (fragmentNum, buf, fragmentSize, isLast); + if (incompleteMessage->savedFragments.insert (savedFragment).second) incompleteMessage->lastFragmentInsertTime = i2p::util::GetSecondsSinceEpoch (); else LogPrint (eLogWarning, "SSU: Fragment ", (int)fragmentNum, " of message ", msgID, " already saved"); @@ -246,11 +244,11 @@ namespace transport { if (!m_ReceivedMessages.count (msgID)) { - m_ReceivedMessages.insert (msgID); m_LastMessageReceivedTime = i2p::util::GetSecondsSinceEpoch (); + m_ReceivedMessages.emplace (msgID, m_LastMessageReceivedTime); if (!msg->IsExpired ()) { - m_Handler.PutNextMessage (msg); + m_Handler.PutNextMessage (std::move (msg)); } else LogPrint (eLogDebug, "SSU: message expired"); @@ -313,8 +311,8 @@ namespace transport if (m_SentMessages.empty ()) // schedule resend at first message only ScheduleResend (); - auto ret = m_SentMessages.insert (std::make_pair (msgID, std::unique_ptr(new SentMessage))); - std::unique_ptr& sentMessage = ret.first->second; + auto ret = m_SentMessages.emplace (msgID, m_Session.GetServer ().GetSentMessagesPool ().AcquireShared ()); + auto& sentMessage = ret.first->second; if (ret.second) { sentMessage->nextResendTime = i2p::util::GetSecondsSinceEpoch () + RESEND_INTERVAL; @@ -328,7 +326,7 @@ namespace transport uint32_t fragmentNum = 0; while (len > 0 && fragmentNum <= 127) { - Fragment * fragment = new Fragment; + auto fragment = m_Session.GetServer ().GetFragmentsPool ().AcquireShared (); fragment->fragmentNum = fragmentNum; uint8_t * payload = fragment->buf + sizeof (SSUHeader); *payload = DATA_FLAG_WANT_REPLY; // for compatibility @@ -358,7 +356,7 @@ namespace transport size += padding; } fragment->len = size; - fragments.push_back (std::unique_ptr (fragment)); + fragments.push_back (fragment); // encrypt message with session key uint8_t buf[SSU_V4_MAX_PACKET_SIZE + 18]; @@ -487,37 +485,33 @@ namespace transport } } - void SSUData::ScheduleIncompleteMessagesCleanup () + void SSUData::CleanUp (uint64_t ts) { - m_IncompleteMessagesCleanupTimer.cancel (); - m_IncompleteMessagesCleanupTimer.expires_from_now (boost::posix_time::seconds(INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT)); - auto s = m_Session.shared_from_this(); - m_IncompleteMessagesCleanupTimer.async_wait ([s](const boost::system::error_code& ecode) - { s->m_Data.HandleIncompleteMessagesCleanupTimer (ecode); }); - } - - void SSUData::HandleIncompleteMessagesCleanupTimer (const boost::system::error_code& ecode) - { - if (ecode != boost::asio::error::operation_aborted) + for (auto it = m_IncompleteMessages.begin (); it != m_IncompleteMessages.end ();) { - uint32_t ts = i2p::util::GetSecondsSinceEpoch (); - for (auto it = m_IncompleteMessages.begin (); it != m_IncompleteMessages.end ();) + if (ts > it->second->lastFragmentInsertTime + INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT) { - if (ts > it->second->lastFragmentInsertTime + INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT) - { - LogPrint (eLogWarning, "SSU: message ", it->first, " was not completed in ", INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT, " seconds, deleted"); - it = m_IncompleteMessages.erase (it); - } + LogPrint (eLogWarning, "SSU: message ", it->first, " was not completed in ", INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT, " seconds, deleted"); + it = m_IncompleteMessages.erase (it); + } + else + ++it; + } + + if (m_ReceivedMessages.size () > MAX_NUM_RECEIVED_MESSAGES || ts > m_LastMessageReceivedTime + DECAY_INTERVAL) + // decay + m_ReceivedMessages.clear (); + else + { + // delete old received messages + for (auto it = m_ReceivedMessages.begin (); it != m_ReceivedMessages.end ();) + { + if (ts > it->second + RECEIVED_MESSAGES_CLEANUP_TIMEOUT) + it = m_ReceivedMessages.erase (it); else ++it; - } - // decay - if (m_ReceivedMessages.size () > MAX_NUM_RECEIVED_MESSAGES || - i2p::util::GetSecondsSinceEpoch () > m_LastMessageReceivedTime + DECAY_INTERVAL) - m_ReceivedMessages.clear (); - - ScheduleIncompleteMessagesCleanup (); - } - } + } + } + } } } diff --git a/libi2pd/SSUData.h b/libi2pd/SSUData.h index 902c009a..8fe33a33 100644 --- a/libi2pd/SSUData.h +++ b/libi2pd/SSUData.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,9 +11,9 @@ #include #include -#include #include -#include +#include +#include #include #include #include "I2NPProtocol.h" @@ -40,6 +40,7 @@ namespace transport const int MAX_NUM_RESENDS = 5; const int DECAY_INTERVAL = 20; // in seconds const int INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT = 30; // in seconds + const int RECEIVED_MESSAGES_CLEANUP_TIMEOUT = 40; // in seconds const unsigned int MAX_NUM_RECEIVED_MESSAGES = 1000; // how many msgID we store for duplicates check const int MAX_OUTGOING_WINDOW_SIZE = 200; // how many unacked message we can store // data flags @@ -64,7 +65,7 @@ namespace transport struct FragmentCmp { - bool operator() (const std::unique_ptr& f1, const std::unique_ptr& f2) const + bool operator() (const std::shared_ptr& f1, const std::shared_ptr& f2) const { return f1->fragmentNum < f2->fragmentNum; }; @@ -76,7 +77,7 @@ namespace transport int nextFragmentNum; uint32_t lastFragmentInsertTime; // in seconds uint64_t receivedFragmentsBits; - std::set, FragmentCmp> savedFragments; + std::set, FragmentCmp> savedFragments; IncompleteMessage (std::shared_ptr m): msg (m), nextFragmentNum (0), lastFragmentInsertTime (0), receivedFragmentsBits (0) {}; @@ -85,7 +86,7 @@ namespace transport struct SentMessage { - std::vector > fragments; + std::vector > fragments; uint32_t nextResendTime; // in seconds int numResends; }; @@ -100,7 +101,8 @@ namespace transport void Start (); void Stop (); - + void CleanUp (uint64_t ts); + void ProcessMessage (uint8_t * buf, size_t len); void FlushReceivedMessage (); void Send (std::shared_ptr msg); @@ -119,17 +121,13 @@ namespace transport void ScheduleResend (); void HandleResendTimer (const boost::system::error_code& ecode); - void ScheduleIncompleteMessagesCleanup (); - void HandleIncompleteMessagesCleanupTimer (const boost::system::error_code& ecode); - - private: SSUSession& m_Session; - std::unordered_map > m_IncompleteMessages; - std::unordered_map > m_SentMessages; - std::unordered_set m_ReceivedMessages; - boost::asio::deadline_timer m_ResendTimer, m_IncompleteMessagesCleanupTimer; + std::map > m_IncompleteMessages; + std::map > m_SentMessages; + std::unordered_map m_ReceivedMessages; // msgID -> timestamp in seconds + boost::asio::deadline_timer m_ResendTimer; int m_MaxPacketSize, m_PacketSize; i2p::I2NPMessagesHandler m_Handler; uint32_t m_LastMessageReceivedTime; // in second diff --git a/libi2pd/SSUSession.cpp b/libi2pd/SSUSession.cpp index 339ac8df..b28f324b 100644 --- a/libi2pd/SSUSession.cpp +++ b/libi2pd/SSUSession.cpp @@ -303,7 +303,7 @@ namespace transport } else { - LogPrint (eLogError, "SSU: Wrong external address ", ourIP.to_string ()); + LogPrint (eLogError, "SSU: External address ", ourIP.to_string (), " is in reserved range"); Failed (); } } @@ -383,7 +383,7 @@ namespace transport { // tell out peer to now assign relay tag flag = SSU_HEADER_EXTENDED_OPTIONS_INCLUDED; - *payload = 2; payload++; // 1 byte length + *payload = 2; payload++; // 1 byte length uint16_t flags = 0; // clear EXTENDED_OPTIONS_FLAG_REQUEST_RELAY_TAG htobe16buf (payload, flags); payload += 2; @@ -609,7 +609,7 @@ namespace transport { *payload = 16; payload++; // size - memcpy (payload, to.address ().to_v6 ().to_bytes ().data (), 16); // Alice's IP V6 + memcpy (payload, to.address ().to_v6 ().to_bytes ().data (), 16); // Charlie's IP V6 payload += 16; // address } htobe16buf (payload, to.port ()); // Charlie's port @@ -703,7 +703,7 @@ namespace transport if (!i2p::util::net::IsInReservedRange (ourIP)) i2p::context.UpdateAddress (ourIP); else - LogPrint (eLogWarning, "SSU: Wrong external address ", ourIP.to_string ()); + LogPrint (eLogError, "SSU: External address ", ourIP.to_string (), " is in reserved range"); if (ourIP.is_v4 ()) { if (ourPort != m_Server.GetPort ()) @@ -730,7 +730,7 @@ namespace transport (remoteIP.is_v6 () && i2p::context.GetStatusV6 () == eRouterStatusFirewalled)) m_Server.Send (buf, 0, remoteEndpoint); // send HolePunch // we assume that HolePunch has been sent by this time and our SessionRequest will go through - m_Server.CreateDirectSession (it->second, remoteEndpoint, false); + m_Server.CreateDirectSession (it->second.first, remoteEndpoint, false); } // delete request m_RelayRequests.erase (it); @@ -905,7 +905,8 @@ namespace transport } uint32_t nonce; RAND_bytes ((uint8_t *)&nonce, 4); - m_RelayRequests[nonce] = to; + auto ts = i2p::util::GetSecondsSinceEpoch (); + m_RelayRequests.emplace (nonce, std::make_pair (to, ts)); SendRelayRequest (introducer, nonce); } @@ -1004,6 +1005,18 @@ namespace transport } } + void SSUSession::CleanUp (uint64_t ts) + { + m_Data.CleanUp (ts); + for (auto it = m_RelayRequests.begin (); it != m_RelayRequests.end ();) + { + if (ts > it->second.second + SSU_CONNECT_TIMEOUT) + it = m_RelayRequests.erase (it); + else + ++it; + } + } + void SSUSession::ProcessPeerTest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint) { uint32_t nonce = bufbe32toh (buf); // 4 bytes @@ -1073,7 +1086,10 @@ namespace transport LogPrint (eLogDebug, "SSU: peer test from Charlie. We are Bob"); auto session = m_Server.GetPeerTestSession (nonce); // session with Alice from PeerTest if (session && session->m_State == eSessionStateEstablished) - session->Send (PAYLOAD_TYPE_PEER_TEST, buf, len); // back to Alice + { + const auto& ep = session->GetRemoteEndpoint (); // Alice's endpoint as known to Bob + session->SendPeerTest (nonce, ep.address (), ep.port (), introKey, false, true); // send back to Alice + } m_Server.RemovePeerTest (nonce); // nonce has been used break; } @@ -1093,9 +1109,12 @@ namespace transport if (port) { LogPrint (eLogDebug, "SSU: peer test from Bob. We are Charlie"); - m_Server.NewPeerTest (nonce, ePeerTestParticipantCharlie); Send (PAYLOAD_TYPE_PEER_TEST, buf, len); // back to Bob - SendPeerTest (nonce, addr, port, introKey); // to Alice with her address received from Bob + if (!addr.is_unspecified () && !i2p::util::net::IsInReservedRange(addr)) + { + m_Server.NewPeerTest (nonce, ePeerTestParticipantCharlie); + SendPeerTest (nonce, addr, port, introKey); // to Alice with her address received from Bob + } } else { @@ -1282,7 +1301,7 @@ namespace transport ip = boost::asio::ip::address_v6 (bytes); } else - LogPrint (eLogWarning, "SSU: Address size ", size, " is not supported"); + LogPrint (eLogWarning, "SSU: Address size ", int(size), " is not supported"); buf += size; port = bufbe16toh (buf); return s; diff --git a/libi2pd/SSUSession.h b/libi2pd/SSUSession.h index 43d0d595..bd4b4b93 100644 --- a/libi2pd/SSUSession.h +++ b/libi2pd/SSUSession.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -89,7 +89,8 @@ namespace transport void Done (); void Failed (); const boost::asio::ip::udp::endpoint& GetRemoteEndpoint () { return m_RemoteEndpoint; }; - + SSUServer& GetServer () { return m_Server; }; + bool IsV6 () const { return m_RemoteEndpoint.address ().is_v6 (); }; void SendI2NPMessages (const std::vector >& msgs); void SendPeerTest (); // Alice @@ -105,6 +106,7 @@ namespace transport void SetCreationTime (uint32_t ts) { m_CreationTime = ts; }; // for introducers void FlushData (); + void CleanUp (uint64_t ts); private: @@ -168,7 +170,7 @@ namespace transport SSUData m_Data; bool m_IsDataReceived; std::unique_ptr m_SignedData; // we need it for SessionConfirmed only - std::map > m_RelayRequests; // nonce->Charlie + std::map, uint64_t > > m_RelayRequests; // nonce->(Charlie, timestamp) std::shared_ptr m_DHKeysPair; // X - for client and Y - for server }; } diff --git a/libi2pd/Signature.cpp b/libi2pd/Signature.cpp index 88ee4060..28ae5fa6 100644 --- a/libi2pd/Signature.cpp +++ b/libi2pd/Signature.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -15,8 +15,7 @@ namespace i2p namespace crypto { #if OPENSSL_EDDSA - EDDSA25519Verifier::EDDSA25519Verifier (): - m_Pkey (nullptr) + EDDSA25519Verifier::EDDSA25519Verifier () { m_MDCtx = EVP_MD_CTX_create (); } @@ -24,13 +23,13 @@ namespace crypto EDDSA25519Verifier::~EDDSA25519Verifier () { EVP_MD_CTX_destroy (m_MDCtx); - if (m_Pkey) EVP_PKEY_free (m_Pkey); } void EDDSA25519Verifier::SetPublicKey (const uint8_t * signingKey) { - m_Pkey = EVP_PKEY_new_raw_public_key (EVP_PKEY_ED25519, NULL, signingKey, 32); - EVP_DigestVerifyInit (m_MDCtx, NULL, NULL, NULL, m_Pkey); + EVP_PKEY * pkey = EVP_PKEY_new_raw_public_key (EVP_PKEY_ED25519, NULL, signingKey, 32); + EVP_DigestVerifyInit (m_MDCtx, NULL, NULL, NULL, pkey); + EVP_PKEY_free (pkey); } bool EDDSA25519Verifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const @@ -100,33 +99,29 @@ namespace crypto #if OPENSSL_EDDSA EDDSA25519Signer::EDDSA25519Signer (const uint8_t * signingPrivateKey, const uint8_t * signingPublicKey): - m_Fallback (nullptr) + m_MDCtx (nullptr), m_Fallback (nullptr) { - m_Pkey = EVP_PKEY_new_raw_private_key (EVP_PKEY_ED25519, NULL, signingPrivateKey, 32); + EVP_PKEY * pkey = EVP_PKEY_new_raw_private_key (EVP_PKEY_ED25519, NULL, signingPrivateKey, 32); uint8_t publicKey[EDDSA25519_PUBLIC_KEY_LENGTH]; size_t len = EDDSA25519_PUBLIC_KEY_LENGTH; - EVP_PKEY_get_raw_public_key (m_Pkey, publicKey, &len); + EVP_PKEY_get_raw_public_key (pkey, publicKey, &len); if (signingPublicKey && memcmp (publicKey, signingPublicKey, EDDSA25519_PUBLIC_KEY_LENGTH)) { LogPrint (eLogWarning, "EdDSA public key mismatch. Fallback"); - EVP_PKEY_free (m_Pkey); m_Fallback = new EDDSA25519SignerCompat (signingPrivateKey, signingPublicKey); } else { m_MDCtx = EVP_MD_CTX_create (); - EVP_DigestSignInit (m_MDCtx, NULL, NULL, NULL, m_Pkey); + EVP_DigestSignInit (m_MDCtx, NULL, NULL, NULL, pkey); } + EVP_PKEY_free (pkey); } EDDSA25519Signer::~EDDSA25519Signer () { if (m_Fallback) delete m_Fallback; - else - { - EVP_MD_CTX_destroy (m_MDCtx); - EVP_PKEY_free (m_Pkey); - } + EVP_MD_CTX_destroy (m_MDCtx); } void EDDSA25519Signer::Sign (const uint8_t * buf, int len, uint8_t * signature) const diff --git a/libi2pd/Signature.h b/libi2pd/Signature.h index 18084603..caf9b274 100644 --- a/libi2pd/Signature.h +++ b/libi2pd/Signature.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -304,7 +304,6 @@ namespace crypto private: #if OPENSSL_EDDSA - EVP_PKEY * m_Pkey; EVP_MD_CTX * m_MDCtx; #else EDDSAPoint m_PublicKey; @@ -341,7 +340,7 @@ namespace crypto void Sign (const uint8_t * buf, int len, uint8_t * signature) const; private: - EVP_PKEY * m_Pkey; + EVP_MD_CTX * m_MDCtx; EDDSA25519SignerCompat * m_Fallback; }; diff --git a/libi2pd/Streaming.cpp b/libi2pd/Streaming.cpp index 612b1058..8be6811b 100644 --- a/libi2pd/Streaming.cpp +++ b/libi2pd/Streaming.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -104,6 +104,7 @@ namespace stream void Stream::Terminate (bool deleteFromDestination) // shoudl be called from StreamingDestination::Stop only { + m_Status = eStreamStatusTerminated; m_AckSendTimer.cancel (); m_ReceiveTimer.cancel (); m_ResendTimer.cancel (); @@ -276,7 +277,20 @@ namespace stream const uint8_t * optionData = packet->GetOptionData (); size_t optionSize = packet->GetOptionSize (); if (flags & PACKET_FLAG_DELAY_REQUESTED) + { + if (!m_IsAckSendScheduled) + { + uint16_t delayRequested = bufbe16toh (optionData); + if (delayRequested > 0 && delayRequested < m_RTT) + { + m_IsAckSendScheduled = true; + m_AckSendTimer.expires_from_now (boost::posix_time::milliseconds(delayRequested)); + m_AckSendTimer.async_wait (std::bind (&Stream::HandleAckSendTimer, + shared_from_this (), std::placeholders::_1)); + } + } optionData += 2; + } if (flags & PACKET_FLAG_FROM_INCLUDED) { @@ -476,7 +490,7 @@ namespace stream handler(boost::system::error_code ()); m_Service.post (std::bind (&Stream::SendBuffer, shared_from_this ())); } - + void Stream::SendBuffer () { int numMsgs = m_WindowSize - m_SentPackets.size (); @@ -651,6 +665,42 @@ namespace stream LogPrint (eLogDebug, "Streaming: Quick Ack sent. ", (int)numNacks, " NACKs"); } + void Stream::SendPing () + { + Packet p; + uint8_t * packet = p.GetBuffer (); + size_t size = 0; + htobe32buf (packet, m_RecvStreamID); + size += 4; // sendStreamID + memset (packet + size, 0, 14); + size += 14; // all zeroes + uint16_t flags = PACKET_FLAG_ECHO | PACKET_FLAG_SIGNATURE_INCLUDED | PACKET_FLAG_FROM_INCLUDED; + bool isOfflineSignature = m_LocalDestination.GetOwner ()->GetPrivateKeys ().IsOfflineSignature (); + if (isOfflineSignature) flags |= PACKET_FLAG_OFFLINE_SIGNATURE; + htobe16buf (packet + size, flags); + size += 2; // flags + size_t identityLen = m_LocalDestination.GetOwner ()->GetIdentity ()->GetFullLen (); + size_t signatureLen = m_LocalDestination.GetOwner ()->GetPrivateKeys ().GetSignatureLen (); + uint8_t * optionsSize = packet + size; // set options size later + size += 2; // options size + m_LocalDestination.GetOwner ()->GetIdentity ()->ToBuffer (packet + size, identityLen); + size += identityLen; // from + if (isOfflineSignature) + { + const auto& offlineSignature = m_LocalDestination.GetOwner ()->GetPrivateKeys ().GetOfflineSignature (); + memcpy (packet + size, offlineSignature.data (), offlineSignature.size ()); + size += offlineSignature.size (); // offline signature + } + uint8_t * signature = packet + size; // set it later + memset (signature, 0, signatureLen); // zeroes for now + size += signatureLen; // signature + htobe16buf (optionsSize, packet + size - 2 - optionsSize); // actual options size + m_LocalDestination.GetOwner ()->Sign (packet, size, signature); + p.len = size; + SendPackets (std::vector { &p }); + LogPrint (eLogDebug, "Streaming: Ping of ", p.len, " bytes sent"); + } + void Stream::Close () { LogPrint(eLogDebug, "Streaming: closing stream with sSID=", m_SendStreamID, ", rSID=", m_RecvStreamID, ", status=", m_Status); @@ -778,13 +828,6 @@ namespace stream m_RTO = m_RTT*1.5; // TODO: implement it better } } - if (!m_CurrentOutboundTunnel || !m_CurrentOutboundTunnel->IsEstablished ()) - m_CurrentOutboundTunnel = m_LocalDestination.GetOwner ()->GetTunnelPool ()->GetNewOutboundTunnel (m_CurrentOutboundTunnel); - if (!m_CurrentOutboundTunnel) - { - LogPrint (eLogError, "Streaming: No outbound tunnels in the pool, sSID=", m_SendStreamID); - return; - } auto ts = i2p::util::GetMillisecondsSinceEpoch (); if (!m_CurrentRemoteLease || !m_CurrentRemoteLease->endDate || // excluded from LeaseSet @@ -792,8 +835,23 @@ namespace stream UpdateCurrentRemoteLease (true); if (m_CurrentRemoteLease && ts < m_CurrentRemoteLease->endDate + i2p::data::LEASE_ENDDATE_THRESHOLD) { + if (!m_CurrentOutboundTunnel) + { + auto leaseRouter = i2p::data::netdb.FindRouter (m_CurrentRemoteLease->tunnelGateway); + m_CurrentOutboundTunnel = m_LocalDestination.GetOwner ()->GetTunnelPool ()->GetNextOutboundTunnel (nullptr, + leaseRouter ? leaseRouter->GetCompatibleTransports (false) : (i2p::data::RouterInfo::CompatibleTransports)i2p::data::RouterInfo::eAllTransports); + } + else if (!m_CurrentOutboundTunnel->IsEstablished ()) + m_CurrentOutboundTunnel = m_LocalDestination.GetOwner ()->GetTunnelPool ()->GetNewOutboundTunnel (m_CurrentOutboundTunnel); + if (!m_CurrentOutboundTunnel) + { + LogPrint (eLogError, "Streaming: No outbound tunnels in the pool, sSID=", m_SendStreamID); + m_CurrentRemoteLease = nullptr; + return; + } + std::vector msgs; - for (auto it: packets) + for (const auto& it: packets) { auto msg = m_RoutingSession->WrapSingleMessage (m_LocalDestination.CreateDataMessage ( it->GetBuffer (), it->GetLength (), m_Port, !m_RoutingSession->IsRatchets ())); @@ -844,12 +902,15 @@ namespace stream void Stream::ScheduleResend () { - m_ResendTimer.cancel (); - // check for invalid value - if (m_RTO <= 0) m_RTO = INITIAL_RTO; - m_ResendTimer.expires_from_now (boost::posix_time::milliseconds(m_RTO)); - m_ResendTimer.async_wait (std::bind (&Stream::HandleResendTimer, - shared_from_this (), std::placeholders::_1)); + if (m_Status != eStreamStatusTerminated) + { + m_ResendTimer.cancel (); + // check for invalid value + if (m_RTO <= 0) m_RTO = INITIAL_RTO; + m_ResendTimer.expires_from_now (boost::posix_time::milliseconds(m_RTO)); + m_ResendTimer.async_wait (std::bind (&Stream::HandleResendTimer, + shared_from_this (), std::placeholders::_1)); + } } void Stream::HandleResendTimer (const boost::system::error_code& ecode) @@ -1023,6 +1084,8 @@ 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 () @@ -1050,6 +1113,7 @@ namespace stream it.second->Terminate (false); // we delete here m_Streams.clear (); m_IncomingStreams.clear (); + m_LastStream = nullptr; } } @@ -1058,9 +1122,16 @@ namespace stream uint32_t sendStreamID = packet->GetSendStreamID (); if (sendStreamID) { - auto it = m_Streams.find (sendStreamID); - if (it != m_Streams.end ()) - it->second->HandleNextPacket (packet); + if (!m_LastStream || sendStreamID != m_LastStream->GetRecvStreamID ()) + { + auto it = m_Streams.find (sendStreamID); + if (it != m_Streams.end ()) + m_LastStream = it->second; + else + m_LastStream = nullptr; + } + if (m_LastStream) + m_LastStream->HandleNextPacket (packet); else if (packet->IsEcho () && m_Owner->IsStreamingAnswerPings ()) { // ping @@ -1076,6 +1147,13 @@ namespace stream } else { + if (packet->IsEcho ()) + { + // pong + LogPrint (eLogInfo, "Streaming: Pong received rSID=", packet->GetReceiveStreamID ()); + DeletePacket (packet); + return; + } if (packet->IsSYN () && !packet->GetSeqn ()) // new incoming stream { uint32_t receiveStreamID = packet->GetReceiveStreamID (); @@ -1166,16 +1244,22 @@ namespace stream { auto s = std::make_shared (m_Owner->GetService (), *this, remote, port); std::unique_lock l(m_StreamsMutex); - m_Streams[s->GetRecvStreamID ()] = s; + m_Streams.emplace (s->GetRecvStreamID (), s); return s; } + void StreamingDestination::SendPing (std::shared_ptr remote) + { + auto s = std::make_shared (m_Owner->GetService (), *this, remote, 0); + s->SendPing (); + } + std::shared_ptr StreamingDestination::CreateNewIncomingStream (uint32_t receiveStreamID) { auto s = std::make_shared (m_Owner->GetService (), *this); std::unique_lock l(m_StreamsMutex); - m_Streams[s->GetRecvStreamID ()] = s; - m_IncomingStreams[receiveStreamID] = s; + m_Streams.emplace (s->GetRecvStreamID (), s); + m_IncomingStreams.emplace (receiveStreamID, s); return s; } @@ -1186,6 +1270,7 @@ namespace stream std::unique_lock l(m_StreamsMutex); m_Streams.erase (stream->GetRecvStreamID ()); m_IncomingStreams.erase (stream->GetSendStreamID ()); + if (m_LastStream == stream) m_LastStream = nullptr; } } @@ -1270,13 +1355,17 @@ namespace stream std::shared_ptr StreamingDestination::CreateDataMessage ( const uint8_t * payload, size_t len, uint16_t toPort, bool checksum) { + size_t size; auto msg = m_I2NPMsgsPool.AcquireShared (); uint8_t * buf = msg->GetPayload (); buf += 4; // reserve for lengthlength msg->len += 4; - size_t size = (!m_Gzip || len <= i2p::stream::COMPRESSION_THRESHOLD_SIZE)? - i2p::data::GzipNoCompression (payload, len, buf, msg->maxLen - msg->len): - m_Deflator.Deflate (payload, len, buf, msg->maxLen - msg->len); + + if (m_Gzip && m_Deflator) + size = m_Deflator->Deflate (payload, len, buf, msg->maxLen - msg->len); + else + size = i2p::data::GzipNoCompression (payload, len, buf, msg->maxLen - msg->len); + if (size) { htobe32buf (msg->GetPayload (), size); // length diff --git a/libi2pd/Streaming.h b/libi2pd/Streaming.h index fe035136..c13dcab1 100644 --- a/libi2pd/Streaming.h +++ b/libi2pd/Streaming.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,7 +11,7 @@ #include #include -#include +#include #include #include #include @@ -152,7 +152,8 @@ namespace stream eStreamStatusOpen, eStreamStatusReset, eStreamStatusClosing, - eStreamStatusClosed + eStreamStatusClosed, + eStreamStatusTerminated }; class StreamingDestination; @@ -178,7 +179,8 @@ namespace stream void HandlePing (Packet * packet); size_t Send (const uint8_t * buf, size_t len); void AsyncSend (const uint8_t * buf, size_t len, SendHandler handler); - + void SendPing (); + template void AsyncReceive (const Buffer& buffer, ReceiveHandler handler, int timeout = 0); size_t ReadSome (uint8_t * buf, size_t len) { return ConcatenatePackets (buf, len); }; @@ -260,13 +262,14 @@ namespace stream typedef std::function)> Acceptor; - StreamingDestination (std::shared_ptr owner, uint16_t localPort = 0, bool gzip = true); + StreamingDestination (std::shared_ptr owner, uint16_t localPort = 0, bool gzip = false); ~StreamingDestination (); void Start (); void Stop (); std::shared_ptr CreateNewOutgoingStream (std::shared_ptr remote, int port = 0); + void SendPing (std::shared_ptr remote); void DeleteStream (std::shared_ptr stream); bool DeleteStream (uint32_t recvStreamID); void SetAcceptor (const Acceptor& acceptor); @@ -297,12 +300,13 @@ namespace stream uint16_t m_LocalPort; bool m_Gzip; // gzip compression of data messages std::mutex m_StreamsMutex; - std::map > m_Streams; // sendStreamID->stream - std::map > m_IncomingStreams; // receiveStreamID->stream + std::unordered_map > m_Streams; // sendStreamID->stream + std::unordered_map > m_IncomingStreams; // receiveStreamID->stream + std::shared_ptr m_LastStream; Acceptor m_Acceptor; std::list > m_PendingIncomingStreams; boost::asio::deadline_timer m_PendingIncomingTimer; - std::map > m_SavedPackets; // receiveStreamID->packets, arrived before SYN + std::unordered_map > m_SavedPackets; // receiveStreamID->packets, arrived before SYN i2p::util::MemoryPool m_PacketsPool; i2p::util::MemoryPool > m_I2NPMsgsPool; @@ -310,7 +314,7 @@ namespace stream public: i2p::data::GzipInflator m_Inflator; - i2p::data::GzipDeflator m_Deflator; + std::unique_ptr m_Deflator; // for HTTP only const decltype(m_Streams)& GetStreams () const { return m_Streams; }; diff --git a/libi2pd/Timestamp.cpp b/libi2pd/Timestamp.cpp index 3cd336ed..ec0c25f2 100644 --- a/libi2pd/Timestamp.cpp +++ b/libi2pd/Timestamp.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -16,6 +16,7 @@ #include #include "Config.h" #include "Log.h" +#include "RouterContext.h" #include "I2PEndian.h" #include "Timestamp.h" #include "util.h" @@ -60,14 +61,43 @@ namespace util { LogPrint (eLogInfo, "Timestamp: NTP request to ", address); boost::asio::io_service service; - boost::asio::ip::udp::resolver::query query (boost::asio::ip::udp::v4 (), address, "ntp"); boost::system::error_code ec; - auto it = boost::asio::ip::udp::resolver (service).resolve (query, ec); - if (!ec && it != boost::asio::ip::udp::resolver::iterator()) + auto it = boost::asio::ip::udp::resolver (service).resolve ( + boost::asio::ip::udp::resolver::query (address, "ntp"), ec); + if (!ec) { - auto ep = (*it).endpoint (); // take first one + bool found = false; + boost::asio::ip::udp::resolver::iterator end; + boost::asio::ip::udp::endpoint ep; + while (it != end) + { + ep = *it; + if (!ep.address ().is_unspecified ()) + { + if (ep.address ().is_v4 ()) + { + if (i2p::context.SupportsV4 ()) found = true; + } + else if (ep.address ().is_v6 ()) + { + if (i2p::util::net::IsYggdrasilAddress (ep.address ())) + { + if (i2p::context.SupportsMesh ()) found = true; + } + else if (i2p::context.SupportsV6 ()) found = true; + } + } + if (found) break; + it++; + } + if (!found) + { + LogPrint (eLogError, "Timestamp: can't find compatible address for ", address); + return; + } + boost::asio::ip::udp::socket socket (service); - socket.open (boost::asio::ip::udp::v4 (), ec); + socket.open (ep.protocol (), ec); if (!ec) { uint8_t buf[48];// 48 bytes NTP request/response @@ -103,7 +133,7 @@ namespace util LogPrint (eLogError, "Timestamp: Couldn't open UDP socket"); } else - LogPrint (eLogError, "Timestamp: Couldn't resove address ", address); + LogPrint (eLogError, "Timestamp: Couldn't resolve address ", address); } NTPTimeSync::NTPTimeSync (): m_IsRunning (false), m_Timer (m_Service) diff --git a/libi2pd/TransitTunnel.cpp b/libi2pd/TransitTunnel.cpp index 73ca977c..c4f3fa19 100644 --- a/libi2pd/TransitTunnel.cpp +++ b/libi2pd/TransitTunnel.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -37,15 +37,14 @@ namespace tunnel { } - void TransitTunnelParticipant::HandleTunnelDataMsg (std::shared_ptr tunnelMsg) + void TransitTunnelParticipant::HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg) { - auto newMsg = CreateEmptyTunnelDataMsg (); - EncryptTunnelMsg (tunnelMsg, newMsg); + EncryptTunnelMsg (tunnelMsg, tunnelMsg); m_NumTransmittedBytes += tunnelMsg->GetLength (); - htobe32buf (newMsg->GetPayload (), GetNextTunnelID ()); - newMsg->FillI2NPMessageHeader (eI2NPTunnelData); - m_TunnelDataMsgs.push_back (newMsg); + htobe32buf (tunnelMsg->GetPayload (), GetNextTunnelID ()); + tunnelMsg->FillI2NPMessageHeader (eI2NPTunnelData); + m_TunnelDataMsgs.push_back (tunnelMsg); } void TransitTunnelParticipant::FlushTunnelDataMsgs () @@ -65,7 +64,7 @@ namespace tunnel LogPrint (eLogError, "TransitTunnel: We are not a gateway for ", GetTunnelID ()); } - void TransitTunnel::HandleTunnelDataMsg (std::shared_ptr tunnelMsg) + void TransitTunnel::HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg) { LogPrint (eLogError, "TransitTunnel: Incoming tunnel message is not supported ", GetTunnelID ()); } @@ -85,9 +84,9 @@ namespace tunnel m_Gateway.SendBuffer (); } - void TransitTunnelEndpoint::HandleTunnelDataMsg (std::shared_ptr tunnelMsg) + void TransitTunnelEndpoint::HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg) { - auto newMsg = CreateEmptyTunnelDataMsg (); + auto newMsg = CreateEmptyTunnelDataMsg (true); EncryptTunnelMsg (tunnelMsg, newMsg); LogPrint (eLogDebug, "TransitTunnel: handle msg for endpoint ", GetTunnelID ()); diff --git a/libi2pd/TransitTunnel.h b/libi2pd/TransitTunnel.h index e71ec750..bce90958 100644 --- a/libi2pd/TransitTunnel.h +++ b/libi2pd/TransitTunnel.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -35,7 +35,7 @@ namespace tunnel // implements TunnelBase void SendTunnelDataMsg (std::shared_ptr msg); - void HandleTunnelDataMsg (std::shared_ptr tunnelMsg); + void HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg); void EncryptTunnelMsg (std::shared_ptr in, std::shared_ptr out); private: @@ -54,7 +54,7 @@ namespace tunnel ~TransitTunnelParticipant (); size_t GetNumTransmittedBytes () const { return m_NumTransmittedBytes; }; - void HandleTunnelDataMsg (std::shared_ptr tunnelMsg); + void HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg); void FlushTunnelDataMsgs (); private: @@ -95,7 +95,7 @@ namespace tunnel void Cleanup () { m_Endpoint.Cleanup (); } - void HandleTunnelDataMsg (std::shared_ptr tunnelMsg); + void HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg); size_t GetNumTransmittedBytes () const { return m_Endpoint.GetNumReceivedBytes (); } private: diff --git a/libi2pd/Transports.cpp b/libi2pd/Transports.cpp index 1e40f88c..6fd5fad9 100644 --- a/libi2pd/Transports.cpp +++ b/libi2pd/Transports.cpp @@ -389,7 +389,7 @@ namespace transport { // we send it to ourself for (auto& it: msgs) - m_LoopbackHandler.PutNextMessage (it); + m_LoopbackHandler.PutNextMessage (std::move (it)); m_LoopbackHandler.Flush (); return; } @@ -401,7 +401,7 @@ namespace transport try { auto r = netdb.FindRouter (ident); - if (!r || r->IsUnreachable () || !r->IsCompatible (i2p::context.GetRouterInfo ())) return; + if (r && (r->IsUnreachable () || !r->IsReachableFrom (i2p::context.GetRouterInfo ()))) return; // router found but non-reachable { std::unique_lock l(m_PeersMutex); it = m_Peers.insert (std::pair(ident, { 0, r, {}, @@ -447,7 +447,7 @@ namespace transport std::shared_ptr address; if (!peer.numAttempts) // NTCP2 ipv6 { - if (context.GetRouterInfo ().IsNTCP2V6 () && peer.router->IsNTCP2V6 ()) + if (context.GetRouterInfo ().IsNTCP2V6 () && peer.router->IsReachableBy (RouterInfo::eNTCP2V6)) { address = peer.router->GetPublishedNTCP2V6Address (); if (address && m_CheckReserved && i2p::util::net::IsInReservedRange(address->host)) @@ -457,7 +457,7 @@ namespace transport } if (!address && peer.numAttempts == 1) // NTCP2 ipv4 { - if (context.GetRouterInfo ().IsNTCP2 (true) && peer.router->IsNTCP2 (true) && !peer.router->IsUnreachable ()) + if (context.GetRouterInfo ().IsNTCP2 (true) && peer.router->IsReachableBy (RouterInfo::eNTCP2V4)) { address = peer.router->GetPublishedNTCP2V4Address (); if (address && m_CheckReserved && i2p::util::net::IsInReservedRange(address->host)) @@ -485,7 +485,7 @@ namespace transport std::shared_ptr address; if (peer.numAttempts == 2) // SSU ipv6 { - if (context.GetRouterInfo ().IsSSUV6 () && peer.router->IsSSUV6 ()) + if (context.GetRouterInfo ().IsSSUV6 () && peer.router->IsReachableBy (RouterInfo::eSSUV6)) { address = peer.router->GetSSUV6Address (); if (address && m_CheckReserved && i2p::util::net::IsInReservedRange(address->host)) @@ -495,7 +495,7 @@ namespace transport } if (!address && peer.numAttempts == 3) // SSU ipv4 { - if (context.GetRouterInfo ().IsSSU (true) && peer.router->IsSSU (true)) + 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)) diff --git a/libi2pd/Tunnel.cpp b/libi2pd/Tunnel.cpp index e016e9e4..24d2c668 100644 --- a/libi2pd/Tunnel.cpp +++ b/libi2pd/Tunnel.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -23,6 +23,7 @@ #include "Tunnel.h" #include "TunnelPool.h" #include "util.h" +#include "ECIESX25519AEADRatchetSession.h" namespace i2p { @@ -30,8 +31,9 @@ namespace tunnel { Tunnel::Tunnel (std::shared_ptr config): TunnelBase (config->GetTunnelID (), config->GetNextTunnelID (), config->GetNextIdentHash ()), - m_Config (config), m_Pool (nullptr), m_State (eTunnelStatePending), m_IsRecreated (false), - m_Latency (0) + m_Config (config), m_IsShortBuildMessage (false), m_Pool (nullptr), + m_State (eTunnelStatePending), m_FarEndTransports (i2p::data::RouterInfo::eAllTransports), + m_IsRecreated (false), m_Latency (0) { } @@ -42,10 +44,11 @@ namespace tunnel void Tunnel::Build (uint32_t replyMsgID, std::shared_ptr outboundTunnel) { auto numHops = m_Config->GetNumHops (); - int numRecords = numHops <= STANDARD_NUM_RECORDS ? STANDARD_NUM_RECORDS : MAX_NUM_RECORDS; + const int numRecords = numHops <= STANDARD_NUM_RECORDS ? STANDARD_NUM_RECORDS : MAX_NUM_RECORDS; auto msg = numRecords <= STANDARD_NUM_RECORDS ? NewI2NPShortMessage () : NewI2NPMessage (); *msg->GetPayload () = numRecords; - msg->len += numRecords*TUNNEL_BUILD_RECORD_SIZE + 1; + const size_t recordSize = m_Config->IsShort () ? SHORT_TUNNEL_BUILD_RECORD_SIZE : TUNNEL_BUILD_RECORD_SIZE; + msg->len += numRecords*recordSize + 1; // shuffle records std::vector recordIndicies; for (int i = 0; i < numRecords; i++) recordIndicies.push_back(i); @@ -55,7 +58,6 @@ namespace tunnel uint8_t * records = msg->GetPayload () + 1; TunnelHopConfig * hop = m_Config->GetFirstHop (); int i = 0; - BN_CTX * ctx = BN_CTX_new (); while (hop) { uint32_t msgID; @@ -63,80 +65,89 @@ namespace tunnel RAND_bytes ((uint8_t *)&msgID, 4); else msgID = replyMsgID; - int idx = recordIndicies[i]; - hop->CreateBuildRequestRecord (records + idx*TUNNEL_BUILD_RECORD_SIZE, msgID, ctx); - hop->recordIndex = idx; - i++; + hop->recordIndex = recordIndicies[i]; i++; + hop->CreateBuildRequestRecord (records, msgID); hop = hop->next; } - BN_CTX_free (ctx); // fill up fake records with random data for (int i = numHops; i < numRecords; i++) { int idx = recordIndicies[i]; - RAND_bytes (records + idx*TUNNEL_BUILD_RECORD_SIZE, TUNNEL_BUILD_RECORD_SIZE); + RAND_bytes (records + idx*recordSize, recordSize); } // decrypt real records - i2p::crypto::CBCDecryption decryption; hop = m_Config->GetLastHop ()->prev; while (hop) { - decryption.SetKey (hop->replyKey); // decrypt records after current hop TunnelHopConfig * hop1 = hop->next; while (hop1) { - decryption.SetIV (hop->replyIV); - uint8_t * record = records + hop1->recordIndex*TUNNEL_BUILD_RECORD_SIZE; - decryption.Decrypt(record, TUNNEL_BUILD_RECORD_SIZE, record); + hop->DecryptRecord (records, hop1->recordIndex); hop1 = hop1->next; } hop = hop->prev; } - msg->FillI2NPMessageHeader (eI2NPVariableTunnelBuild); + msg->FillI2NPMessageHeader (m_Config->IsShort () ? eI2NPShortTunnelBuild : eI2NPVariableTunnelBuild); // send message if (outboundTunnel) + { + if (m_Config->IsShort ()) + { + auto ident = m_Config->GetFirstHop () ? m_Config->GetFirstHop ()->ident : nullptr; + if (ident && ident->GetIdentHash () != outboundTunnel->GetNextIdentHash ()) // don't encrypt if IBGW = OBEP + { + auto msg1 = i2p::garlic::WrapECIESX25519MessageForRouter (msg, ident->GetEncryptionPublicKey ()); + if (msg1) msg = msg1; + } + } outboundTunnel->SendTunnelDataMsg (GetNextIdentHash (), 0, msg); + } else + { + if (m_Config->IsShort () && m_Config->GetLastHop () && + m_Config->GetLastHop ()->ident->GetIdentHash () != m_Config->GetLastHop ()->nextIdent) + { + // add garlic key/tag for reply + uint8_t key[32]; + uint64_t tag = m_Config->GetLastHop ()->GetGarlicKey (key); + if (m_Pool && m_Pool->GetLocalDestination ()) + m_Pool->GetLocalDestination ()->AddECIESx25519Key (key, tag); + else + i2p::context.AddECIESx25519Key (key, tag); + } i2p::transport::transports.SendMessage (GetNextIdentHash (), msg); + } } bool Tunnel::HandleTunnelBuildResponse (uint8_t * msg, size_t len) { LogPrint (eLogDebug, "Tunnel: TunnelBuildResponse ", (int)msg[0], " records."); - i2p::crypto::CBCDecryption decryption; TunnelHopConfig * hop = m_Config->GetLastHop (); while (hop) { - decryption.SetKey (hop->replyKey); - // decrypt records before and current hop - TunnelHopConfig * hop1 = hop; + // decrypt current hop + if (hop->recordIndex >= 0 && hop->recordIndex < msg[0]) + { + if (!hop->DecryptBuildResponseRecord (msg + 1)) + return false; + } + else + { + LogPrint (eLogWarning, "Tunnel: hop index ", hop->recordIndex, " is out of range"); + return false; + } + + // decrypt records before current hop + TunnelHopConfig * hop1 = hop->prev; while (hop1) { auto idx = hop1->recordIndex; if (idx >= 0 && idx < msg[0]) - { - uint8_t * record = msg + 1 + idx*TUNNEL_BUILD_RECORD_SIZE; - if (hop1 == hop && hop1->IsECIES ()) - { - uint8_t nonce[12]; - memset (nonce, 0, 12); - if (!i2p::crypto::AEADChaCha20Poly1305 (record, TUNNEL_BUILD_RECORD_SIZE - 16, - hop->m_H, 32, hop->m_CK, nonce, record, TUNNEL_BUILD_RECORD_SIZE - 16, false)) // decrypt - { - LogPrint (eLogWarning, "Tunnel: Response AEAD decryption failed"); - return false; - } - } - else - { - decryption.SetIV (hop->replyIV); - decryption.Decrypt(record, TUNNEL_BUILD_RECORD_SIZE, record); - } - } + hop->DecryptRecord (msg + 1, idx); else LogPrint (eLogWarning, "Tunnel: hop index ", idx, " is out of range"); hop1 = hop1->prev; @@ -145,11 +156,11 @@ namespace tunnel } bool established = true; + size_t numHops = 0; hop = m_Config->GetFirstHop (); while (hop) { - const uint8_t * record = msg + 1 + hop->recordIndex*TUNNEL_BUILD_RECORD_SIZE; - uint8_t ret = record[hop->IsECIES () ? ECIES_BUILD_RESPONSE_RECORD_RET_OFFSET : BUILD_RESPONSE_RECORD_RET_OFFSET]; + uint8_t ret = hop->GetRetCode (msg + 1); LogPrint (eLogDebug, "Tunnel: Build response ret code=", (int)ret); auto profile = i2p::data::netdb.FindRouterProfile (hop->ident->GetIdentHash ()); if (profile) @@ -158,19 +169,23 @@ namespace tunnel // if any of participants declined the tunnel is not established established = false; hop = hop->next; + numHops++; } if (established) { // create tunnel decryptions from layer and iv keys in reverse order + m_Hops.resize (numHops); hop = m_Config->GetLastHop (); + int i = 0; while (hop) { - auto tunnelHop = new TunnelHop; - tunnelHop->ident = hop->ident; - tunnelHop->decryption.SetKeys (hop->layerKey, hop->ivKey); - m_Hops.push_back (std::unique_ptr(tunnelHop)); + m_Hops[i].ident = hop->ident; + m_Hops[i].decryption.SetKeys (hop->layerKey, hop->ivKey); hop = hop->prev; + i++; } + m_IsShortBuildMessage = m_Config->IsShort (); + m_FarEndTransports = m_Config->GetFarEndTransports (); m_Config = nullptr; } if (established) m_State = eTunnelStateEstablished; @@ -189,7 +204,7 @@ namespace tunnel uint8_t * outPayload = out->GetPayload () + 4; for (auto& it: m_Hops) { - it->decryption.Decrypt (inPayload, outPayload); + it.decryption.Decrypt (inPayload, outPayload); inPayload = outPayload; } } @@ -210,8 +225,8 @@ namespace tunnel { // hops are in inverted order std::vector > ret; - for (auto& it: m_Hops) - ret.push_back (it->ident); + for (const auto& it: m_Hops) + ret.push_back (it.ident); return ret; } @@ -227,17 +242,16 @@ namespace tunnel for (auto it = m_Hops.rbegin (); it != m_Hops.rend (); it++) { s << " ⇒ "; - s << i2p::data::GetIdentHashAbbreviation ((*it)->ident->GetIdentHash ()); + s << i2p::data::GetIdentHashAbbreviation ((*it).ident->GetIdentHash ()); } } - void InboundTunnel::HandleTunnelDataMsg (std::shared_ptr msg) + void InboundTunnel::HandleTunnelDataMsg (std::shared_ptr&& msg) { if (IsFailed ()) SetState (eTunnelStateEstablished); // incoming messages means a tunnel is alive - auto newMsg = CreateEmptyTunnelDataMsg (); - EncryptTunnelMsg (msg, newMsg); - newMsg->from = shared_from_this (); - m_Endpoint.HandleDecryptedTunnelDataMsg (newMsg); + EncryptTunnelMsg (msg, msg); + msg->from = shared_from_this (); + m_Endpoint.HandleDecryptedTunnelDataMsg (msg); } void InboundTunnel::Print (std::stringstream& s) const @@ -296,7 +310,7 @@ namespace tunnel m_Gateway.SendBuffer (); } - void OutboundTunnel::HandleTunnelDataMsg (std::shared_ptr tunnelMsg) + void OutboundTunnel::HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg) { LogPrint (eLogError, "Tunnel: incoming message for outbound tunnel ", GetTunnelID ()); } @@ -319,10 +333,11 @@ namespace tunnel for (auto& msg : msgs) { if (!msg.data) continue; + m_NumSentBytes += msg.data->GetLength (); switch (msg.deliveryType) { case eDeliveryTypeLocal: - i2p::HandleI2NPMessage (msg.data); + HandleI2NPMessage (msg.data); break; case eDeliveryTypeTunnel: i2p::transport::transports.SendMessage (msg.hash, i2p::CreateTunnelGatewayMsg (msg.tunnelID, msg.data)); @@ -476,7 +491,7 @@ namespace tunnel i2p::util::SetThreadName("Tunnels"); std::this_thread::sleep_for (std::chrono::seconds(1)); // wait for other parts are ready - uint64_t lastTs = 0, lastPoolsTs = 0; + uint64_t lastTs = 0, lastPoolsTs = 0, lastMemoryPoolTs = 0; while (m_IsRunning) { try @@ -506,7 +521,7 @@ namespace tunnel if (tunnel) { if (typeID == eI2NPTunnelData) - tunnel->HandleTunnelDataMsg (msg); + tunnel->HandleTunnelDataMsg (std::move (msg)); else // tunnel gateway assumed HandleTunnelGatewayMsg (tunnel, msg); } @@ -517,8 +532,10 @@ namespace tunnel } case eI2NPVariableTunnelBuild: case eI2NPVariableTunnelBuildReply: + case eI2NPShortTunnelBuild: + case eI2NPShortTunnelBuildReply: case eI2NPTunnelBuild: - case eI2NPTunnelBuildReply: + case eI2NPTunnelBuildReply: HandleI2NPMessage (msg->GetBuffer (), msg->GetLength ()); break; default: @@ -550,6 +567,12 @@ namespace tunnel ManageTunnelPools (ts); lastPoolsTs = ts; } + if (ts - lastMemoryPoolTs >= 120) // manage memory pool every 2 minutes + { + m_I2NPTunnelEndpointMessagesMemoryPool.CleanUpMt (); + m_I2NPTunnelMessagesMemoryPool.CleanUpMt (); + lastMemoryPoolTs = ts; + } } } catch (std::exception& ex) @@ -680,7 +703,7 @@ namespace tunnel // let it die if the tunnel pool has been reconfigured and this is old if (pool && tunnel->GetNumHops() == pool->GetNumOutboundHops()) { - tunnel->SetIsRecreated (); + tunnel->SetRecreated (true); pool->RecreateOutboundTunnel (tunnel); } } @@ -703,7 +726,7 @@ namespace tunnel LogPrint (eLogDebug, "Tunnel: creating one hop outbound tunnel"); CreateTunnel ( std::make_shared (std::vector > { router->GetRouterIdentity () }, - inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash ()) + inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash (), false), nullptr ); } } @@ -734,7 +757,7 @@ namespace tunnel // let it die if the tunnel pool was reconfigured and has different number of hops if (pool && tunnel->GetNumHops() == pool->GetNumInboundHops()) { - tunnel->SetIsRecreated (); + tunnel->SetRecreated (true); pool->RecreateInboundTunnel (tunnel); } } @@ -752,8 +775,8 @@ namespace tunnel if (m_InboundTunnels.empty ()) { LogPrint (eLogDebug, "Tunnel: Creating zero hops inbound tunnel"); - CreateZeroHopsInboundTunnel (); - CreateZeroHopsOutboundTunnel (); + CreateZeroHopsInboundTunnel (nullptr); + CreateZeroHopsOutboundTunnel (nullptr); if (!m_ExploratoryPool) { int ibLen; i2p::config::GetOption("exploratory.inbound.length", ibLen); @@ -779,7 +802,7 @@ namespace tunnel } LogPrint (eLogDebug, "Tunnel: creating one hop inbound tunnel"); CreateTunnel ( - std::make_shared (std::vector > { router->GetRouterIdentity () }) + std::make_shared (std::vector > { router->GetRouterIdentity () }, false), nullptr ); } } @@ -825,9 +848,11 @@ namespace tunnel } template - std::shared_ptr Tunnels::CreateTunnel (std::shared_ptr config, std::shared_ptr outboundTunnel) + std::shared_ptr Tunnels::CreateTunnel (std::shared_ptr config, + std::shared_ptr pool, std::shared_ptr outboundTunnel) { auto newTunnel = std::make_shared (config); + newTunnel->SetTunnelPool (pool); uint32_t replyMsgID; RAND_bytes ((uint8_t *)&replyMsgID, 4); AddPendingTunnel (replyMsgID, newTunnel); @@ -835,20 +860,21 @@ namespace tunnel return newTunnel; } - std::shared_ptr Tunnels::CreateInboundTunnel (std::shared_ptr config, std::shared_ptr outboundTunnel) + std::shared_ptr Tunnels::CreateInboundTunnel (std::shared_ptr config, + std::shared_ptr pool, std::shared_ptr outboundTunnel) { if (config) - return CreateTunnel(config, outboundTunnel); + return CreateTunnel(config, pool, outboundTunnel); else - return CreateZeroHopsInboundTunnel (); + return CreateZeroHopsInboundTunnel (pool); } - std::shared_ptr Tunnels::CreateOutboundTunnel (std::shared_ptr config) + std::shared_ptr Tunnels::CreateOutboundTunnel (std::shared_ptr config, std::shared_ptr pool) { if (config) - return CreateTunnel(config); + return CreateTunnel(config, pool); else - return CreateZeroHopsOutboundTunnel (); + return CreateZeroHopsOutboundTunnel (pool); } void Tunnels::AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr tunnel) @@ -882,7 +908,7 @@ namespace tunnel { // build symmetric outbound tunnel CreateTunnel (std::make_shared(newTunnel->GetInvertedPeers (), - newTunnel->GetNextTunnelID (), newTunnel->GetNextIdentHash ()), + newTunnel->GetNextTunnelID (), newTunnel->GetNextIdentHash (), false), nullptr, GetNextOutboundTunnel ()); } else @@ -898,24 +924,44 @@ namespace tunnel } - std::shared_ptr Tunnels::CreateZeroHopsInboundTunnel () + std::shared_ptr Tunnels::CreateZeroHopsInboundTunnel (std::shared_ptr pool) { auto inboundTunnel = std::make_shared (); + inboundTunnel->SetTunnelPool (pool); inboundTunnel->SetState (eTunnelStateEstablished); m_InboundTunnels.push_back (inboundTunnel); m_Tunnels[inboundTunnel->GetTunnelID ()] = inboundTunnel; return inboundTunnel; } - std::shared_ptr Tunnels::CreateZeroHopsOutboundTunnel () + std::shared_ptr Tunnels::CreateZeroHopsOutboundTunnel (std::shared_ptr pool) { auto outboundTunnel = std::make_shared (); + outboundTunnel->SetTunnelPool (pool); outboundTunnel->SetState (eTunnelStateEstablished); m_OutboundTunnels.push_back (outboundTunnel); // we don't insert into m_Tunnels return outboundTunnel; } + std::shared_ptr Tunnels::NewI2NPTunnelMessage (bool endpoint) + { + if (endpoint) + { + // should fit two tunnel message + tunnel gateway header, enough for one garlic encrypted streaming packet + auto msg = m_I2NPTunnelEndpointMessagesMemoryPool.AcquireSharedMt (); + msg->Align (6); + msg->offset += TUNNEL_GATEWAY_HEADER_SIZE; // reserve room for TunnelGateway header + return msg; + } + else + { + auto msg = m_I2NPTunnelMessagesMemoryPool.AcquireSharedMt (); + msg->Align (12); + return msg; + } + } + int Tunnels::GetTransitTunnelsExpirationTimeout () { int timeout = 0; diff --git a/libi2pd/Tunnel.h b/libi2pd/Tunnel.h index f30eab14..cfd54d26 100644 --- a/libi2pd/Tunnel.h +++ b/libi2pd/Tunnel.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -18,6 +18,7 @@ #include #include #include +#include "util.h" #include "Queue.h" #include "Crypto.h" #include "TunnelConfig.h" @@ -40,6 +41,9 @@ namespace tunnel const int MAX_NUM_RECORDS = 8; const int HIGH_LATENCY_PER_HOP = 250; // in milliseconds + const size_t I2NP_TUNNEL_MESSAGE_SIZE = TUNNEL_DATA_MSG_SIZE + I2NP_HEADER_SIZE + 34; // reserved for alignment and NTCP 16 + 6 + 12 + const size_t I2NP_TUNNEL_ENPOINT_MESSAGE_SIZE = 2*TUNNEL_DATA_MSG_SIZE + I2NP_HEADER_SIZE + TUNNEL_GATEWAY_HEADER_SIZE + 28; // reserved for alignment and NTCP 16 + 6 + 6 + enum TunnelState { eTunnelStatePending, @@ -71,12 +75,14 @@ namespace tunnel std::shared_ptr GetTunnelConfig () const { return m_Config; } std::vector > GetPeers () const; std::vector > GetInvertedPeers () const; + bool IsShortBuildMessage () const { return m_IsShortBuildMessage; }; + i2p::data::RouterInfo::CompatibleTransports GetFarEndTransports () const { return m_FarEndTransports; }; TunnelState GetState () const { return m_State; }; void SetState (TunnelState state); bool IsEstablished () const { return m_State == eTunnelStateEstablished; }; bool IsFailed () const { return m_State == eTunnelStateFailed; }; bool IsRecreated () const { return m_IsRecreated; }; - void SetIsRecreated () { m_IsRecreated = true; }; + void SetRecreated (bool recreated) { m_IsRecreated = recreated; }; int GetNumHops () const { return m_Hops.size (); }; virtual bool IsInbound() const = 0; @@ -108,10 +114,12 @@ namespace tunnel private: std::shared_ptr m_Config; - std::vector > m_Hops; + std::vector m_Hops; + bool m_IsShortBuildMessage; std::shared_ptr m_Pool; // pool, tunnel belongs to, or null TunnelState m_State; - bool m_IsRecreated; + i2p::data::RouterInfo::CompatibleTransports m_FarEndTransports; + bool m_IsRecreated; // if tunnel is replaced by new, or new tunnel requested to replace uint64_t m_Latency; // in milliseconds }; @@ -129,7 +137,7 @@ namespace tunnel void Print (std::stringstream& s) const; // implements TunnelBase - void HandleTunnelDataMsg (std::shared_ptr tunnelMsg); + void HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg); bool IsInbound() const { return false; } @@ -145,7 +153,7 @@ namespace tunnel public: InboundTunnel (std::shared_ptr config): Tunnel (config), m_Endpoint (true) {}; - void HandleTunnelDataMsg (std::shared_ptr msg); + void HandleTunnelDataMsg (std::shared_ptr&& msg); virtual size_t GetNumReceivedBytes () const { return m_Endpoint.GetNumReceivedBytes (); }; void Print (std::stringstream& s) const; bool IsInbound() const { return true; } @@ -205,8 +213,8 @@ namespace tunnel void AddTransitTunnel (std::shared_ptr tunnel); void AddOutboundTunnel (std::shared_ptr newTunnel); void AddInboundTunnel (std::shared_ptr newTunnel); - std::shared_ptr CreateInboundTunnel (std::shared_ptr config, std::shared_ptr outboundTunnel); - std::shared_ptr CreateOutboundTunnel (std::shared_ptr config); + std::shared_ptr CreateInboundTunnel (std::shared_ptr config, std::shared_ptr pool, std::shared_ptr outboundTunnel); + std::shared_ptr CreateOutboundTunnel (std::shared_ptr config, std::shared_ptr pool); void PostTunnelData (std::shared_ptr msg); void PostTunnelData (const std::vector >& msgs); void AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr tunnel); @@ -216,10 +224,13 @@ namespace tunnel void DeleteTunnelPool (std::shared_ptr pool); void StopTunnelPool (std::shared_ptr pool); + std::shared_ptr NewI2NPTunnelMessage (bool endpoint); + private: template - std::shared_ptr CreateTunnel (std::shared_ptr config, std::shared_ptr outboundTunnel = nullptr); + std::shared_ptr CreateTunnel (std::shared_ptr config, + std::shared_ptr pool, std::shared_ptr outboundTunnel = nullptr); template std::shared_ptr GetPendingTunnel (uint32_t replyMsgID, const std::map >& pendingTunnels); @@ -236,8 +247,8 @@ namespace tunnel void ManagePendingTunnels (PendingTunnels& pendingTunnels); void ManageTunnelPools (uint64_t ts); - std::shared_ptr CreateZeroHopsInboundTunnel (); - std::shared_ptr CreateZeroHopsOutboundTunnel (); + std::shared_ptr CreateZeroHopsInboundTunnel (std::shared_ptr pool); + std::shared_ptr CreateZeroHopsOutboundTunnel (std::shared_ptr pool); private: @@ -253,6 +264,8 @@ namespace tunnel std::list> m_Pools; std::shared_ptr m_ExploratoryPool; i2p::util::Queue > m_Queue; + i2p::util::MemoryPoolMt > m_I2NPTunnelEndpointMessagesMemoryPool; + i2p::util::MemoryPoolMt > m_I2NPTunnelMessagesMemoryPool; // some stats int m_NumSuccesiveTunnelCreations, m_NumFailedTunnelCreations; diff --git a/libi2pd/TunnelBase.h b/libi2pd/TunnelBase.h index f98066d3..8d0edff1 100644 --- a/libi2pd/TunnelBase.h +++ b/libi2pd/TunnelBase.h @@ -47,7 +47,7 @@ namespace tunnel virtual ~TunnelBase () {}; virtual void Cleanup () {}; - virtual void HandleTunnelDataMsg (std::shared_ptr tunnelMsg) = 0; + virtual void HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg) = 0; virtual void SendTunnelDataMsg (std::shared_ptr msg) = 0; virtual void FlushTunnelDataMsgs () {}; virtual void EncryptTunnelMsg (std::shared_ptr in, std::shared_ptr out) = 0; diff --git a/libi2pd/TunnelConfig.cpp b/libi2pd/TunnelConfig.cpp index d43483c0..4592c663 100644 --- a/libi2pd/TunnelConfig.cpp +++ b/libi2pd/TunnelConfig.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -23,10 +23,6 @@ namespace tunnel { TunnelHopConfig::TunnelHopConfig (std::shared_ptr r) { - RAND_bytes (layerKey, 32); - RAND_bytes (ivKey, 32); - RAND_bytes (replyKey, 32); - RAND_bytes (replyIV, 16); RAND_bytes ((uint8_t *)&tunnelID, 4); if (!tunnelID) tunnelID = 1; // tunnelID can't be zero isGateway = true; @@ -77,75 +73,178 @@ namespace tunnel isGateway = false; } } - - void TunnelHopConfig::CreateBuildRequestRecord (uint8_t * record, uint32_t replyMsgID, BN_CTX * ctx) - { - uint8_t flag = 0; - if (isGateway) flag |= 0x80; - if (isEndpoint) flag |= 0x40; - auto encryptor = ident->CreateEncryptor (nullptr); - if (IsECIES ()) - { - uint8_t clearText[ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; - htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET, tunnelID); - htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET, nextTunnelID); - memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, nextIdent, 32); - memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET, layerKey, 32); - memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_IV_KEY_OFFSET, ivKey, 32); - memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET, replyKey, 32); - memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_IV_OFFSET, replyIV, 16); - clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] = flag; - memset (clearText + ECIES_BUILD_REQUEST_RECORD_MORE_FLAGS_OFFSET, 0, 3); // set to 0 for compatibility - htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET, i2p::util::GetMinutesSinceEpoch ()); - htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_REQUEST_EXPIRATION_OFFSET, 600); // +10 minutes - htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET, replyMsgID); - memset (clearText + ECIES_BUILD_REQUEST_RECORD_PADDING_OFFSET, 0, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE - ECIES_BUILD_REQUEST_RECORD_PADDING_OFFSET); - if (encryptor) - EncryptECIES (encryptor, clearText, record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, ctx); - } - else - { - uint8_t clearText[BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; - htobe32buf (clearText + BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET, tunnelID); - memcpy (clearText + BUILD_REQUEST_RECORD_OUR_IDENT_OFFSET, ident->GetIdentHash (), 32); - htobe32buf (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET, nextTunnelID); - memcpy (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, nextIdent, 32); - memcpy (clearText + BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET, layerKey, 32); - memcpy (clearText + BUILD_REQUEST_RECORD_IV_KEY_OFFSET, ivKey, 32); - memcpy (clearText + BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET, replyKey, 32); - memcpy (clearText + BUILD_REQUEST_RECORD_REPLY_IV_OFFSET, replyIV, 16); - clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] = flag; - htobe32buf (clearText + BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET, i2p::util::GetHoursSinceEpoch ()); - htobe32buf (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET, replyMsgID); - RAND_bytes (clearText + BUILD_REQUEST_RECORD_PADDING_OFFSET, BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE - BUILD_REQUEST_RECORD_PADDING_OFFSET); - if (encryptor) - encryptor->Encrypt (clearText, record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, ctx, false); - } - memcpy (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)ident->GetIdentHash (), 16); - } - void TunnelHopConfig::EncryptECIES (std::shared_ptr& encryptor, - const uint8_t * plainText, uint8_t * encrypted, BN_CTX * ctx) + void TunnelHopConfig::DecryptRecord (uint8_t * records, int index) const { - uint8_t hepk[32]; - encryptor->Encrypt (nullptr, hepk, nullptr, false); - i2p::crypto::InitNoiseNState (*this, hepk); + uint8_t * record = records + index*TUNNEL_BUILD_RECORD_SIZE; + i2p::crypto::CBCDecryption decryption; + decryption.SetKey (replyKey); + decryption.SetIV (replyIV); + decryption.Decrypt(record, TUNNEL_BUILD_RECORD_SIZE, record); + } + + void ECIESTunnelHopConfig::EncryptECIES (const uint8_t * plainText, size_t len, uint8_t * encrypted) + { + if (!ident) return; + i2p::crypto::InitNoiseNState (*this, ident->GetEncryptionPublicKey ()); auto ephemeralKeys = i2p::transport::transports.GetNextX25519KeysPair (); memcpy (encrypted, ephemeralKeys->GetPublicKey (), 32); MixHash (encrypted, 32); // h = SHA256(h || sepk) encrypted += 32; uint8_t sharedSecret[32]; - ephemeralKeys->Agree (hepk, sharedSecret); // x25519(sesk, hepk) + ephemeralKeys->Agree (ident->GetEncryptionPublicKey (), sharedSecret); // x25519(sesk, hepk) MixKey (sharedSecret); uint8_t nonce[12]; memset (nonce, 0, 12); - if (!i2p::crypto::AEADChaCha20Poly1305 (plainText, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE, m_H, 32, - m_CK + 32, nonce, encrypted, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE + 16, true)) // encrypt + if (!i2p::crypto::AEADChaCha20Poly1305 (plainText, len, m_H, 32, m_CK + 32, nonce, encrypted, len + 16, true)) // encrypt { LogPrint (eLogWarning, "Tunnel: Plaintext AEAD encryption failed"); return; } - MixHash (encrypted, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE + 16); // h = SHA256(h || ciphertext) + MixHash (encrypted, len + 16); // h = SHA256(h || ciphertext) } + + bool ECIESTunnelHopConfig::DecryptECIES (const uint8_t * key, const uint8_t * nonce, const uint8_t * encrypted, size_t len, uint8_t * clearText) const + { + return i2p::crypto::AEADChaCha20Poly1305 (encrypted, len - 16, m_H, 32, key, nonce, clearText, len - 16, false); // decrypt + } + + void LongECIESTunnelHopConfig::CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID) + { + // generate keys + RAND_bytes (layerKey, 32); + RAND_bytes (ivKey, 32); + RAND_bytes (replyKey, 32); + RAND_bytes (replyIV, 16); + // fill clear text + uint8_t flag = 0; + if (isGateway) flag |= TUNNEL_BUILD_RECORD_GATEWAY_FLAG; + if (isEndpoint) flag |= TUNNEL_BUILD_RECORD_ENDPOINT_FLAG; + uint8_t clearText[ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; + htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET, tunnelID); + htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET, nextTunnelID); + memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, nextIdent, 32); + memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET, layerKey, 32); + memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_IV_KEY_OFFSET, ivKey, 32); + memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET, replyKey, 32); + memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_IV_OFFSET, replyIV, 16); + clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] = flag; + memset (clearText + ECIES_BUILD_REQUEST_RECORD_MORE_FLAGS_OFFSET, 0, 3); // set to 0 for compatibility + htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET, i2p::util::GetMinutesSinceEpoch ()); + htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_REQUEST_EXPIRATION_OFFSET, 600); // +10 minutes + htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET, replyMsgID); + memset (clearText + ECIES_BUILD_REQUEST_RECORD_PADDING_OFFSET, 0, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE - ECIES_BUILD_REQUEST_RECORD_PADDING_OFFSET); + // encrypt + uint8_t * record = records + recordIndex*TUNNEL_BUILD_RECORD_SIZE; + EncryptECIES (clearText, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE, record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET); + memcpy (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)ident->GetIdentHash (), 16); + } + + bool LongECIESTunnelHopConfig::DecryptBuildResponseRecord (uint8_t * records) const + { + uint8_t * record = records + recordIndex*TUNNEL_BUILD_RECORD_SIZE; + uint8_t nonce[12]; + memset (nonce, 0, 12); + if (!DecryptECIES (m_CK, nonce, record, TUNNEL_BUILD_RECORD_SIZE, record)) + { + LogPrint (eLogWarning, "Tunnel: Response AEAD decryption failed"); + return false; + } + return true; + } + + void ShortECIESTunnelHopConfig::CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID) + { + // fill clear text + uint8_t flag = 0; + if (isGateway) flag |= TUNNEL_BUILD_RECORD_GATEWAY_FLAG; + if (isEndpoint) flag |= TUNNEL_BUILD_RECORD_ENDPOINT_FLAG; + uint8_t clearText[SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE ]; + htobe32buf (clearText + SHORT_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET, tunnelID); + htobe32buf (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET, nextTunnelID); + memcpy (clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, nextIdent, 32); + clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] = flag; + memset (clearText + SHORT_REQUEST_RECORD_MORE_FLAGS_OFFSET, 0, 2); + clearText[SHORT_REQUEST_RECORD_LAYER_ENCRYPTION_TYPE] = 0; // AES + htobe32buf (clearText + SHORT_REQUEST_RECORD_REQUEST_TIME_OFFSET, i2p::util::GetMinutesSinceEpoch ()); + htobe32buf (clearText + SHORT_REQUEST_RECORD_REQUEST_EXPIRATION_OFFSET , 600); // +10 minutes + htobe32buf (clearText + SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET, replyMsgID); + memset (clearText + SHORT_REQUEST_RECORD_PADDING_OFFSET, 0, SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE - SHORT_REQUEST_RECORD_PADDING_OFFSET); + // encrypt + uint8_t * record = records + recordIndex*SHORT_TUNNEL_BUILD_RECORD_SIZE; + EncryptECIES (clearText, SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE, record + SHORT_REQUEST_RECORD_ENCRYPTED_OFFSET); + // derive keys + i2p::crypto::HKDF (m_CK, nullptr, 0, "SMTunnelReplyKey", m_CK); + memcpy (replyKey, m_CK + 32, 32); + i2p::crypto::HKDF (m_CK, nullptr, 0, "SMTunnelLayerKey", m_CK); + memcpy (layerKey, m_CK + 32, 32); + if (isEndpoint) + { + i2p::crypto::HKDF (m_CK, nullptr, 0, "TunnelLayerIVKey", m_CK); + memcpy (ivKey, m_CK + 32, 32); + i2p::crypto::HKDF (m_CK, nullptr, 0, "RGarlicKeyAndTag", m_CK); // OTBRM garlic key m_CK + 32, tag first 8 bytes of m_CK + } + else + memcpy (ivKey, m_CK, 32); // last HKDF + memcpy (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)ident->GetIdentHash (), 16); + } + + bool ShortECIESTunnelHopConfig::DecryptBuildResponseRecord (uint8_t * records) const + { + uint8_t * record = records + recordIndex*SHORT_TUNNEL_BUILD_RECORD_SIZE; + uint8_t nonce[12]; + memset (nonce, 0, 12); + nonce[4] = recordIndex; // nonce is record index + if (!DecryptECIES (replyKey, nonce, record, SHORT_TUNNEL_BUILD_RECORD_SIZE, record)) + { + LogPrint (eLogWarning, "Tunnel: Response AEAD decryption failed"); + return false; + } + return true; + } + + void ShortECIESTunnelHopConfig::DecryptRecord (uint8_t * records, int index) const + { + uint8_t * record = records + index*SHORT_TUNNEL_BUILD_RECORD_SIZE; + uint8_t nonce[12]; + memset (nonce, 0, 12); + nonce[4] = index; // nonce is index + i2p::crypto::ChaCha20 (record, SHORT_TUNNEL_BUILD_RECORD_SIZE, replyKey, nonce, record); + } + + uint64_t ShortECIESTunnelHopConfig::GetGarlicKey (uint8_t * key) const + { + uint64_t tag; + memcpy (&tag, m_CK, 8); + memcpy (key, m_CK + 32, 32); + return tag; + } + + void TunnelConfig::CreatePeers (const std::vector >& peers) + { + TunnelHopConfig * prev = nullptr; + for (const auto& it: peers) + { + TunnelHopConfig * hop = nullptr; + if (m_IsShort) + hop = new ShortECIESTunnelHopConfig (it); + else + { + if (it->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) + hop = new LongECIESTunnelHopConfig (it); + else + LogPrint (eLogError, "Tunnel: ElGamal router is not supported"); + } + if (hop) + { + if (prev) + prev->SetNext (hop); + else + m_FirstHop = hop; + prev = hop; + } + } + m_LastHop = prev; + } } } \ No newline at end of file diff --git a/libi2pd/TunnelConfig.h b/libi2pd/TunnelConfig.h index 45693970..7934bc6b 100644 --- a/libi2pd/TunnelConfig.h +++ b/libi2pd/TunnelConfig.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -18,7 +18,7 @@ namespace i2p { namespace tunnel { - struct TunnelHopConfig: public i2p::crypto::NoiseSymmetricState + struct TunnelHopConfig { std::shared_ptr ident; i2p::data::IdentHash nextIdent; @@ -31,39 +31,75 @@ namespace tunnel TunnelHopConfig * next, * prev; int recordIndex; // record # in tunnel build message - + TunnelHopConfig (std::shared_ptr r); - + virtual ~TunnelHopConfig () {}; + void SetNextIdent (const i2p::data::IdentHash& ident); void SetReplyHop (uint32_t replyTunnelID, const i2p::data::IdentHash& replyIdent); void SetNext (TunnelHopConfig * n); - void SetPrev (TunnelHopConfig * p); + void SetPrev (TunnelHopConfig * p); - bool IsECIES () const { return ident->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD; }; - void CreateBuildRequestRecord (uint8_t * record, uint32_t replyMsgID, BN_CTX * ctx); - void EncryptECIES (std::shared_ptr& encryptor, - const uint8_t * clearText, uint8_t * encrypted, BN_CTX * ctx); + virtual uint8_t GetRetCode (const uint8_t * records) const = 0; + virtual void CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID) = 0; + virtual bool DecryptBuildResponseRecord (uint8_t * records) const = 0; + virtual void DecryptRecord (uint8_t * records, int index) const; // AES + virtual uint64_t GetGarlicKey (uint8_t * key) const { return 0; }; // return tag }; - + + struct ECIESTunnelHopConfig: public TunnelHopConfig, public i2p::crypto::NoiseSymmetricState + { + ECIESTunnelHopConfig (std::shared_ptr r): + TunnelHopConfig (r) {}; + void EncryptECIES (const uint8_t * clearText, size_t len, uint8_t * encrypted); + bool DecryptECIES (const uint8_t * key, const uint8_t * nonce, const uint8_t * encrypted, size_t len, uint8_t * clearText) const; + }; + + struct LongECIESTunnelHopConfig: public ECIESTunnelHopConfig + { + LongECIESTunnelHopConfig (std::shared_ptr r): + ECIESTunnelHopConfig (r) {}; + uint8_t GetRetCode (const uint8_t * records) const override + { return (records + recordIndex*TUNNEL_BUILD_RECORD_SIZE)[ECIES_BUILD_RESPONSE_RECORD_RET_OFFSET]; }; + void CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID) override; + bool DecryptBuildResponseRecord (uint8_t * records) const override; + }; + + struct ShortECIESTunnelHopConfig: public ECIESTunnelHopConfig + { + ShortECIESTunnelHopConfig (std::shared_ptr r): + ECIESTunnelHopConfig (r) {}; + uint8_t GetRetCode (const uint8_t * records) const override + { return (records + recordIndex*SHORT_TUNNEL_BUILD_RECORD_SIZE)[SHORT_RESPONSE_RECORD_RET_OFFSET]; }; + void CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID) override; + bool DecryptBuildResponseRecord (uint8_t * records) const override; + void DecryptRecord (uint8_t * records, int index) const override; // Chacha20 + uint64_t GetGarlicKey (uint8_t * key) const override; + }; + class TunnelConfig { public: - TunnelConfig (std::vector > peers) // inbound + TunnelConfig (const std::vector >& peers, + bool isShort, i2p::data::RouterInfo::CompatibleTransports farEndTransports = i2p::data::RouterInfo::eAllTransports): // inbound + m_IsShort (isShort), m_FarEndTransports (farEndTransports) { CreatePeers (peers); m_LastHop->SetNextIdent (i2p::context.GetIdentHash ()); } - TunnelConfig (std::vector > peers, - uint32_t replyTunnelID, const i2p::data::IdentHash& replyIdent) // outbound + TunnelConfig (const std::vector >& peers, + uint32_t replyTunnelID, const i2p::data::IdentHash& replyIdent, bool isShort, + i2p::data::RouterInfo::CompatibleTransports farEndTransports = i2p::data::RouterInfo::eAllTransports): // outbound + m_IsShort (isShort), m_FarEndTransports (farEndTransports) { CreatePeers (peers); m_FirstHop->isGateway = false; m_LastHop->SetReplyHop (replyTunnelID, replyIdent); } - ~TunnelConfig () + virtual ~TunnelConfig () { TunnelHopConfig * hop = m_FirstHop; @@ -75,6 +111,13 @@ namespace tunnel } } + bool IsShort () const { return m_IsShort; } + + i2p::data::RouterInfo::CompatibleTransports GetFarEndTransports () const + { + return m_FarEndTransports; + } + TunnelHopConfig * GetFirstHop () const { return m_FirstHop; @@ -141,31 +184,20 @@ namespace tunnel protected: // this constructor can't be called from outside - TunnelConfig (): m_FirstHop (nullptr), m_LastHop (nullptr) + TunnelConfig (): m_FirstHop (nullptr), m_LastHop (nullptr), m_IsShort (false), + m_FarEndTransports (i2p::data::RouterInfo::eAllTransports) { } private: - template - void CreatePeers (const Peers& peers) - { - TunnelHopConfig * prev = nullptr; - for (const auto& it: peers) - { - auto hop = new TunnelHopConfig (it); - if (prev) - prev->SetNext (hop); - else - m_FirstHop = hop; - prev = hop; - } - m_LastHop = prev; - } + void CreatePeers (const std::vector >& peers); private: TunnelHopConfig * m_FirstHop, * m_LastHop; + bool m_IsShort; + i2p::data::RouterInfo::CompatibleTransports m_FarEndTransports; }; class ZeroHopsTunnelConfig: public TunnelConfig diff --git a/libi2pd/TunnelEndpoint.cpp b/libi2pd/TunnelEndpoint.cpp index eb70bdca..4885c090 100644 --- a/libi2pd/TunnelEndpoint.cpp +++ b/libi2pd/TunnelEndpoint.cpp @@ -52,24 +52,25 @@ namespace tunnel bool isFollowOnFragment = flag & 0x80, isLastFragment = true; uint32_t msgID = 0; int fragmentNum = 0; - TunnelMessageBlockEx m; if (!isFollowOnFragment) { // first fragment - - m.deliveryType = (TunnelDeliveryType)((flag >> 5) & 0x03); - switch (m.deliveryType) + if (m_CurrentMsgID) + AddIncompleteCurrentMessage (); // we have got a new message while previous is not complete + + m_CurrentMessage.deliveryType = (TunnelDeliveryType)((flag >> 5) & 0x03); + switch (m_CurrentMessage.deliveryType) { case eDeliveryTypeLocal: // 0 break; case eDeliveryTypeTunnel: // 1 - m.tunnelID = bufbe32toh (fragment); + m_CurrentMessage.tunnelID = bufbe32toh (fragment); fragment += 4; // tunnelID - m.hash = i2p::data::IdentHash (fragment); + m_CurrentMessage.hash = i2p::data::IdentHash (fragment); fragment += 32; // hash break; case eDeliveryTypeRouter: // 2 - m.hash = i2p::data::IdentHash (fragment); + m_CurrentMessage.hash = i2p::data::IdentHash (fragment); fragment += 32; // to hash break; default: ; @@ -81,6 +82,7 @@ namespace tunnel // Message ID msgID = bufbe32toh (fragment); fragment += 4; + m_CurrentMsgID = msgID; isLastFragment = false; } } @@ -96,50 +98,60 @@ namespace tunnel uint16_t size = bufbe16toh (fragment); fragment += 2; - msg->offset = fragment - msg->buf; - msg->len = msg->offset + size; - if (msg->len > msg->maxLen) + // handle fragment + if (isFollowOnFragment) { - LogPrint (eLogError, "TunnelMessage: fragment is too long ", (int)size); - return; - } - if (fragment + size < decrypted + TUNNEL_DATA_ENCRYPTED_SIZE) - { - // this is not last message. we have to copy it - m.data = NewI2NPTunnelMessage (); - m.data->offset += TUNNEL_GATEWAY_HEADER_SIZE; // reserve room for TunnelGateway header - m.data->len += TUNNEL_GATEWAY_HEADER_SIZE; - *(m.data) = *msg; - } - else - m.data = msg; - - if (!isFollowOnFragment && isLastFragment) - HandleNextMessage (m); - else - { - if (msgID) // msgID is presented, assume message is fragmented + // existing message + if (m_CurrentMsgID && m_CurrentMsgID == msgID && m_CurrentMessage.nextFragmentNum == fragmentNum) + HandleCurrenMessageFollowOnFragment (fragment, size, isLastFragment); // previous + else { - if (!isFollowOnFragment) // create new incomlete message - { - m.nextFragmentNum = 1; - m.receiveTime = i2p::util::GetMillisecondsSinceEpoch (); - auto ret = m_IncompleteMessages.insert (std::pair(msgID, m)); - if (ret.second) - HandleOutOfSequenceFragments (msgID, ret.first->second); - else - LogPrint (eLogError, "TunnelMessage: Incomplete message ", msgID, " already exists"); - } - else - { - m.nextFragmentNum = fragmentNum; - HandleFollowOnFragment (msgID, isLastFragment, m); - } + HandleFollowOnFragment (msgID, isLastFragment, fragmentNum, fragment, size); // another + m_CurrentMsgID = 0; m_CurrentMessage.data = nullptr; + } + } + else + { + // new message + msg->offset = fragment - msg->buf; + msg->len = msg->offset + size; + // check message size + if (msg->len > msg->maxLen) + { + LogPrint (eLogError, "TunnelMessage: fragment is too long ", (int)size); + m_CurrentMsgID = 0; m_CurrentMessage.data = nullptr; + return; + } + // create new or assign I2NP message + if (fragment + size < decrypted + TUNNEL_DATA_ENCRYPTED_SIZE) + { + // this is not last message. we have to copy it + m_CurrentMessage.data = NewI2NPTunnelMessage (true); + *(m_CurrentMessage.data) = *msg; } else + m_CurrentMessage.data = msg; + + if (isLastFragment) + { + // single message + HandleNextMessage (m_CurrentMessage); + m_CurrentMsgID = 0; m_CurrentMessage.data = nullptr; + } + else if (msgID) + { + // first fragment of a new message + m_CurrentMessage.nextFragmentNum = 1; + m_CurrentMessage.receiveTime = i2p::util::GetMillisecondsSinceEpoch (); + HandleOutOfSequenceFragments (msgID, m_CurrentMessage); + } + else + { LogPrint (eLogError, "TunnelMessage: Message is fragmented, but msgID is not presented"); - } - + m_CurrentMsgID = 0; m_CurrentMessage.data = nullptr; + } + } + fragment += size; } } @@ -147,27 +159,17 @@ namespace tunnel LogPrint (eLogError, "TunnelMessage: zero not found"); } - void TunnelEndpoint::HandleFollowOnFragment (uint32_t msgID, bool isLastFragment, const TunnelMessageBlockEx& m) + void TunnelEndpoint::HandleFollowOnFragment (uint32_t msgID, bool isLastFragment, + uint8_t fragmentNum, const uint8_t * fragment, size_t size) { - auto fragment = m.data->GetBuffer (); - auto size = m.data->GetLength (); auto it = m_IncompleteMessages.find (msgID); if (it != m_IncompleteMessages.end()) { auto& msg = it->second; - if (m.nextFragmentNum == msg.nextFragmentNum) + if (fragmentNum == msg.nextFragmentNum) { - if (msg.data->len + size < I2NP_MAX_MESSAGE_SIZE) // check if message is not too long + if (ConcatFollowOnFragment (msg, fragment, size)) { - if (msg.data->len + size > msg.data->maxLen) - { - // LogPrint (eLogWarning, "TunnelMessage: I2NP message size ", msg.data->maxLen, " is not enough"); - auto newMsg = NewI2NPMessage (); - *newMsg = *(msg.data); - msg.data = newMsg; - } - if (msg.data->Concat (fragment, size) < size) // concatenate fragment - LogPrint (eLogError, "TunnelMessage: I2NP buffer overflow ", msg.data->maxLen); if (isLastFragment) { // message complete @@ -182,26 +184,86 @@ namespace tunnel } else { - LogPrint (eLogError, "TunnelMessage: Fragment ", m.nextFragmentNum, " of message ", msgID, "exceeds max I2NP message size, message dropped"); + LogPrint (eLogError, "TunnelMessage: Fragment ", fragmentNum, " of message ", msgID, "exceeds max I2NP message size, message dropped"); m_IncompleteMessages.erase (it); } } else { - LogPrint (eLogWarning, "TunnelMessage: Unexpected fragment ", (int)m.nextFragmentNum, " instead ", (int)msg.nextFragmentNum, " of message ", msgID, ", saved"); - AddOutOfSequenceFragment (msgID, m.nextFragmentNum, isLastFragment, m.data); + LogPrint (eLogWarning, "TunnelMessage: Unexpected fragment ", (int)fragmentNum, " instead ", (int)msg.nextFragmentNum, " of message ", msgID, ", saved"); + AddOutOfSequenceFragment (msgID, fragmentNum, isLastFragment, fragment, size); } } else { - LogPrint (eLogWarning, "TunnelMessage: First fragment of message ", msgID, " not found, saved"); - AddOutOfSequenceFragment (msgID, m.nextFragmentNum, isLastFragment, m.data); + LogPrint (eLogDebug, "TunnelMessage: First fragment of message ", msgID, " not found, saved"); + AddOutOfSequenceFragment (msgID, fragmentNum, isLastFragment, fragment, size); } } - void TunnelEndpoint::AddOutOfSequenceFragment (uint32_t msgID, uint8_t fragmentNum, bool isLastFragment, std::shared_ptr data) + bool TunnelEndpoint::ConcatFollowOnFragment (TunnelMessageBlockEx& msg, const uint8_t * fragment, size_t size) const { - if (!m_OutOfSequenceFragments.insert ({{msgID, fragmentNum}, {isLastFragment, data, i2p::util::GetMillisecondsSinceEpoch () }}).second) + if (msg.data->len + size < I2NP_MAX_MESSAGE_SIZE) // check if message is not too long + { + if (msg.data->len + size > msg.data->maxLen) + { + // LogPrint (eLogWarning, "TunnelMessage: I2NP message size ", msg.data->maxLen, " is not enough"); + auto newMsg = NewI2NPMessage (); + *newMsg = *(msg.data); + msg.data = newMsg; + } + if (msg.data->Concat (fragment, size) < size) // concatenate fragment + { + LogPrint (eLogError, "TunnelMessage: I2NP buffer overflow ", msg.data->maxLen); + return false; + } + } + else + return false; + return true; + } + + void TunnelEndpoint::HandleCurrenMessageFollowOnFragment (const uint8_t * fragment, size_t size, bool isLastFragment) + { + if (ConcatFollowOnFragment (m_CurrentMessage, fragment, size)) + { + if (isLastFragment) + { + // message complete + HandleNextMessage (m_CurrentMessage); + m_CurrentMsgID = 0; m_CurrentMessage.data = nullptr; + } + else + { + m_CurrentMessage.nextFragmentNum++; + HandleOutOfSequenceFragments (m_CurrentMsgID, m_CurrentMessage); + } + } + else + { + LogPrint (eLogError, "TunnelMessage: Fragment ", m_CurrentMessage.nextFragmentNum, " of message ", m_CurrentMsgID, " exceeds max I2NP message size, message dropped"); + m_CurrentMsgID = 0; m_CurrentMessage.data = nullptr; + } + } + + void TunnelEndpoint::AddIncompleteCurrentMessage () + { + if (m_CurrentMsgID) + { + auto ret = m_IncompleteMessages.emplace (m_CurrentMsgID, m_CurrentMessage); + if (!ret.second) + LogPrint (eLogError, "TunnelMessage: Incomplete message ", m_CurrentMsgID, " already exists"); + m_CurrentMessage.data = nullptr; + m_CurrentMsgID = 0; + } + } + + void TunnelEndpoint::AddOutOfSequenceFragment (uint32_t msgID, uint8_t fragmentNum, + bool isLastFragment, const uint8_t * fragment, size_t size) + { + std::unique_ptr f(new Fragment (isLastFragment, i2p::util::GetMillisecondsSinceEpoch (), size)); + memcpy (f->data.data (), fragment, size); + if (!m_OutOfSequenceFragments.emplace ((uint64_t)msgID << 32 | fragmentNum, std::move (f)).second) LogPrint (eLogInfo, "TunnelMessage: duplicate out-of-sequence fragment ", fragmentNum, " of message ", msgID); } @@ -212,7 +274,14 @@ namespace tunnel if (!msg.nextFragmentNum) // message complete { HandleNextMessage (msg); - m_IncompleteMessages.erase (msgID); + if (&msg == &m_CurrentMessage) + { + m_CurrentMsgID = 0; + m_CurrentMessage.data = nullptr; + } + else + m_IncompleteMessages.erase (msgID); + LogPrint (eLogDebug, "TunnelMessage: All fragments of message ", msgID, " found"); break; } } @@ -220,11 +289,11 @@ namespace tunnel bool TunnelEndpoint::ConcatNextOutOfSequenceFragment (uint32_t msgID, TunnelMessageBlockEx& msg) { - auto it = m_OutOfSequenceFragments.find ({msgID, msg.nextFragmentNum}); + auto it = m_OutOfSequenceFragments.find ((uint64_t)msgID << 32 | msg.nextFragmentNum); if (it != m_OutOfSequenceFragments.end ()) { LogPrint (eLogDebug, "TunnelMessage: Out-of-sequence fragment ", (int)msg.nextFragmentNum, " of message ", msgID, " found"); - size_t size = it->second.data->GetLength (); + size_t size = it->second->data.size (); if (msg.data->len + size > msg.data->maxLen) { LogPrint (eLogWarning, "TunnelMessage: Tunnel endpoint I2NP message size ", msg.data->maxLen, " is not enough"); @@ -232,9 +301,9 @@ namespace tunnel *newMsg = *(msg.data); msg.data = newMsg; } - if (msg.data->Concat (it->second.data->GetBuffer (), size) < size) // concatenate out-of-sync fragment + if (msg.data->Concat (it->second->data.data (), size) < size) // concatenate out-of-sync fragment LogPrint (eLogError, "TunnelMessage: Tunnel endpoint I2NP buffer overflow ", msg.data->maxLen); - if (it->second.isLastFragment) + if (it->second->isLastFragment) // message complete msg.nextFragmentNum = 0; else @@ -287,7 +356,7 @@ namespace tunnel // out-of-sequence fragments for (auto it = m_OutOfSequenceFragments.begin (); it != m_OutOfSequenceFragments.end ();) { - if (ts > it->second.receiveTime + i2p::I2NP_MESSAGE_EXPIRATION_TIMEOUT) + if (ts > it->second->receiveTime + i2p::I2NP_MESSAGE_EXPIRATION_TIMEOUT) it = m_OutOfSequenceFragments.erase (it); else ++it; diff --git a/libi2pd/TunnelEndpoint.h b/libi2pd/TunnelEndpoint.h index 43b836f1..e5a6bbc8 100644 --- a/libi2pd/TunnelEndpoint.h +++ b/libi2pd/TunnelEndpoint.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -10,8 +10,9 @@ #define TUNNEL_ENDPOINT_H__ #include -#include +#include #include +#include #include "I2NPProtocol.h" #include "TunnelBase.h" @@ -29,14 +30,15 @@ namespace tunnel struct Fragment { + Fragment (bool last, uint64_t t, size_t size): isLastFragment (last), receiveTime (t), data (size) {}; bool isLastFragment; - std::shared_ptr data; uint64_t receiveTime; // milliseconds since epoch + std::vector data; }; public: - TunnelEndpoint (bool isInbound): m_IsInbound (isInbound), m_NumReceivedBytes (0) {}; + TunnelEndpoint (bool isInbound): m_IsInbound (isInbound), m_NumReceivedBytes (0), m_CurrentMsgID (0) {}; ~TunnelEndpoint (); size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; }; void Cleanup (); @@ -45,19 +47,24 @@ namespace tunnel private: - void HandleFollowOnFragment (uint32_t msgID, bool isLastFragment, const TunnelMessageBlockEx& m); + void HandleFollowOnFragment (uint32_t msgID, bool isLastFragment, uint8_t fragmentNum, const uint8_t * fragment, size_t size); + bool ConcatFollowOnFragment (TunnelMessageBlockEx& msg, const uint8_t * fragment, size_t size) const; // true if success + void HandleCurrenMessageFollowOnFragment (const uint8_t * frgament, size_t size, bool isLastFragment); void HandleNextMessage (const TunnelMessageBlock& msg); - void AddOutOfSequenceFragment (uint32_t msgID, uint8_t fragmentNum, bool isLastFragment, std::shared_ptr data); + void AddOutOfSequenceFragment (uint32_t msgID, uint8_t fragmentNum, bool isLastFragment, const uint8_t * fragment, size_t size); bool ConcatNextOutOfSequenceFragment (uint32_t msgID, TunnelMessageBlockEx& msg); // true if something added void HandleOutOfSequenceFragments (uint32_t msgID, TunnelMessageBlockEx& msg); - + void AddIncompleteCurrentMessage (); + private: - std::map m_IncompleteMessages; - std::map, Fragment> m_OutOfSequenceFragments; // (msgID, fragment#)->fragment + std::unordered_map m_IncompleteMessages; + std::unordered_map > m_OutOfSequenceFragments; // ((msgID << 8) + fragment#)->fragment bool m_IsInbound; size_t m_NumReceivedBytes; + TunnelMessageBlockEx m_CurrentMessage; + uint32_t m_CurrentMsgID; }; } } diff --git a/libi2pd/TunnelGateway.cpp b/libi2pd/TunnelGateway.cpp index 317926ae..c9ad0357 100644 --- a/libi2pd/TunnelGateway.cpp +++ b/libi2pd/TunnelGateway.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -158,8 +158,7 @@ namespace tunnel void TunnelGatewayBuffer::CreateCurrentTunnelDataMessage () { m_CurrentTunnelDataMsg = nullptr; - m_CurrentTunnelDataMsg = NewI2NPShortMessage (); - m_CurrentTunnelDataMsg->Align (12); + m_CurrentTunnelDataMsg = NewI2NPTunnelMessage (true); // tunnel endpoint is at least of two tunnel messages size // we reserve space for padding m_CurrentTunnelDataMsg->offset += TUNNEL_DATA_MSG_SIZE + I2NP_HEADER_SIZE; m_CurrentTunnelDataMsg->len = m_CurrentTunnelDataMsg->offset; @@ -215,7 +214,7 @@ namespace tunnel const auto& tunnelDataMsgs = m_Buffer.GetTunnelDataMsgs (); for (auto& tunnelMsg : tunnelDataMsgs) { - auto newMsg = CreateEmptyTunnelDataMsg (); + auto newMsg = CreateEmptyTunnelDataMsg (false); m_Tunnel->EncryptTunnelMsg (tunnelMsg, newMsg); htobe32buf (newMsg->GetPayload (), m_Tunnel->GetNextTunnelID ()); newMsg->FillI2NPMessageHeader (eI2NPTunnelData); diff --git a/libi2pd/TunnelPool.cpp b/libi2pd/TunnelPool.cpp index f5af249a..7bce0519 100644 --- a/libi2pd/TunnelPool.cpp +++ b/libi2pd/TunnelPool.cpp @@ -24,6 +24,22 @@ namespace i2p { namespace tunnel { + void Path::Add (std::shared_ptr r) + { + if (r) + { + peers.push_back (r->GetRouterIdentity ()); + if (r->GetVersion () < i2p::data::NETDB_MIN_SHORT_TUNNEL_BUILD_VERSION || + r->GetRouterIdentity ()->GetCryptoKeyType () != i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) + isShort = false; + } + } + + void Path::Reverse () + { + std::reverse (peers.begin (), peers.end ()); + } + TunnelPool::TunnelPool (int numInboundHops, int numOutboundHops, int numInboundTunnels, int numOutboundTunnels): m_NumInboundHops (numInboundHops), m_NumOutboundHops (numOutboundHops), m_NumInboundTunnels (numInboundTunnels), m_NumOutboundTunnels (numOutboundTunnels), @@ -97,6 +113,17 @@ namespace tunnel if (!m_IsActive) return; { std::unique_lock l(m_InboundTunnelsMutex); + if (createdTunnel->IsRecreated ()) + { + // find and mark old tunnel as expired + createdTunnel->SetRecreated (false); + for (auto& it: m_InboundTunnels) + if (it->IsRecreated () && it->GetNextIdentHash () == createdTunnel->GetNextIdentHash ()) + { + it->SetState (eTunnelStateExpiring); + break; + } + } m_InboundTunnels.insert (createdTunnel); } if (m_LocalDestination) @@ -142,33 +169,44 @@ namespace tunnel { std::vector > v; int i = 0; + std::shared_ptr slowTunnel; std::unique_lock l(m_InboundTunnelsMutex); for (const auto& it : m_InboundTunnels) { if (i >= num) break; if (it->IsEstablished ()) { - v.push_back (it); - i++; + if (it->IsSlow () && !slowTunnel) + slowTunnel = it; + else + { + v.push_back (it); + i++; + } } } + if (slowTunnel && (int)v.size () < (num/2+1)) + v.push_back (slowTunnel); return v; } - std::shared_ptr TunnelPool::GetNextOutboundTunnel (std::shared_ptr excluded) const + std::shared_ptr TunnelPool::GetNextOutboundTunnel (std::shared_ptr excluded, + i2p::data::RouterInfo::CompatibleTransports compatible) const { std::unique_lock l(m_OutboundTunnelsMutex); - return GetNextTunnel (m_OutboundTunnels, excluded); + return GetNextTunnel (m_OutboundTunnels, excluded, compatible); } - std::shared_ptr TunnelPool::GetNextInboundTunnel (std::shared_ptr excluded) const + std::shared_ptr TunnelPool::GetNextInboundTunnel (std::shared_ptr excluded, + i2p::data::RouterInfo::CompatibleTransports compatible) const { std::unique_lock l(m_InboundTunnelsMutex); - return GetNextTunnel (m_InboundTunnels, excluded); + return GetNextTunnel (m_InboundTunnels, excluded, compatible); } template - typename TTunnels::value_type TunnelPool::GetNextTunnel (TTunnels& tunnels, typename TTunnels::value_type excluded) const + typename TTunnels::value_type TunnelPool::GetNextTunnel (TTunnels& tunnels, + typename TTunnels::value_type excluded, i2p::data::RouterInfo::CompatibleTransports compatible) const { if (tunnels.empty ()) return nullptr; uint32_t ind = rand () % (tunnels.size ()/2 + 1), i = 0; @@ -176,7 +214,7 @@ namespace tunnel typename TTunnels::value_type tunnel = nullptr; for (const auto& it: tunnels) { - if (it->IsEstablished () && it != excluded) + if (it->IsEstablished () && it != excluded && (compatible & it->GetFarEndTransports ())) { if (it->IsSlow () || (HasLatencyRequirement() && it->LatencyIsKnown() && !it->LatencyFitsRange(m_MinLatency, m_MaxLatency))) @@ -412,16 +450,16 @@ namespace tunnel return hop; } - bool StandardSelectPeers(Path & peers, int numHops, bool inbound, SelectHopFunc nextHop) + bool StandardSelectPeers(Path & path, int numHops, bool inbound, SelectHopFunc nextHop) { int start = 0; - auto prevHop = i2p::context.GetSharedRouterInfo (); + std::shared_ptr prevHop = i2p::context.GetSharedRouterInfo (); if(i2p::transport::transports.RoutesRestricted()) { /** if routes are restricted prepend trusted first hop */ auto hop = i2p::transport::transports.GetRestrictedPeer(); if(!hop) return false; - peers.push_back(hop->GetRouterIdentity()); + path.Add (hop); prevHop = hop; start++; } @@ -429,11 +467,11 @@ namespace tunnel (inbound && i2p::transport::transports.GetNumPeers () > 25)) { auto r = i2p::transport::transports.GetRandomPeer (); - if (r && !r->GetProfile ()->IsBad () && + if (r && r->IsECIES () && !r->GetProfile ()->IsBad () && (numHops > 1 || (r->IsV4 () && (!inbound || r->IsReachable ())))) // first inbound must be reachable { prevHop = r; - peers.push_back (r->GetRouterIdentity ()); + path.Add (r); start++; } } @@ -445,6 +483,7 @@ namespace tunnel { LogPrint (eLogInfo, "Tunnels: Can't select first hop for a tunnel. Trying already connected"); hop = i2p::transport::transports.GetRandomPeer (); + if (hop && !hop->IsECIES ()) hop = nullptr; } if (!hop) { @@ -458,12 +497,13 @@ namespace tunnel if (hop1) hop = hop1; } prevHop = hop; - peers.push_back (hop->GetRouterIdentity ()); + path.Add (hop); } + path.farEndTransports = prevHop->GetCompatibleTransports (inbound); // last hop return true; } - bool TunnelPool::SelectPeers (std::vector >& peers, bool isInbound) + bool TunnelPool::SelectPeers (Path& path, bool isInbound) { int numHops = isInbound ? m_NumInboundHops : m_NumOutboundHops; // peers is empty @@ -472,27 +512,36 @@ namespace tunnel { std::lock_guard lock(m_CustomPeerSelectorMutex); if (m_CustomPeerSelector) - return m_CustomPeerSelector->SelectPeers(peers, numHops, isInbound); + return m_CustomPeerSelector->SelectPeers(path, numHops, isInbound); } // explicit peers in use - if (m_ExplicitPeers) return SelectExplicitPeers (peers, isInbound); - return StandardSelectPeers(peers, numHops, isInbound, std::bind(&TunnelPool::SelectNextHop, this, std::placeholders::_1, std::placeholders::_2)); + if (m_ExplicitPeers) return SelectExplicitPeers (path, isInbound); + return StandardSelectPeers(path, numHops, isInbound, std::bind(&TunnelPool::SelectNextHop, this, std::placeholders::_1, std::placeholders::_2)); } - bool TunnelPool::SelectExplicitPeers (std::vector >& peers, bool isInbound) + bool TunnelPool::SelectExplicitPeers (Path& path, bool isInbound) { - int size = m_ExplicitPeers->size (); - std::vector peerIndicies; - for (int i = 0; i < size; i++) peerIndicies.push_back(i); - std::shuffle (peerIndicies.begin(), peerIndicies.end(), std::mt19937(std::random_device()())); - int numHops = isInbound ? m_NumInboundHops : m_NumOutboundHops; + if (numHops > (int)m_ExplicitPeers->size ()) numHops = m_ExplicitPeers->size (); + if (!numHops) return false; for (int i = 0; i < numHops; i++) { - auto& ident = (*m_ExplicitPeers)[peerIndicies[i]]; + auto& ident = (*m_ExplicitPeers)[i]; auto r = i2p::data::netdb.FindRouter (ident); if (r) - peers.push_back (r->GetRouterIdentity ()); + { + if (r->IsECIES ()) + { + path.Add (r); + if (i == numHops - 1) + path.farEndTransports = r->GetCompatibleTransports (isInbound); + } + else + { + LogPrint (eLogError, "Tunnels: ElGamal router ", ident.ToBase64 (), " is not supported"); + return false; + } + } else { LogPrint (eLogInfo, "Tunnels: Can't find router for ", ident.ToBase64 ()); @@ -505,21 +554,20 @@ namespace tunnel void TunnelPool::CreateInboundTunnel () { - auto outboundTunnel = GetNextOutboundTunnel (); - if (!outboundTunnel) - outboundTunnel = tunnels.GetNextOutboundTunnel (); LogPrint (eLogDebug, "Tunnels: Creating destination inbound tunnel..."); - std::vector > peers; - if (SelectPeers (peers, true)) + Path path; + if (SelectPeers (path, true)) { + auto outboundTunnel = GetNextOutboundTunnel (nullptr, path.farEndTransports); + if (!outboundTunnel) + outboundTunnel = tunnels.GetNextOutboundTunnel (); std::shared_ptr config; if (m_NumInboundHops > 0) { - std::reverse (peers.begin (), peers.end ()); - config = std::make_shared (peers); + path.Reverse (); + config = std::make_shared (path.peers, path.isShort, path.farEndTransports); } - auto tunnel = tunnels.CreateInboundTunnel (config, outboundTunnel); - tunnel->SetTunnelPool (shared_from_this ()); + auto tunnel = tunnels.CreateInboundTunnel (config, shared_from_this (), outboundTunnel); if (tunnel->IsEstablished ()) // zero hops TunnelCreated (tunnel); } @@ -534,48 +582,60 @@ namespace tunnel CreateInboundTunnel (); return; } - auto outboundTunnel = GetNextOutboundTunnel (); + auto outboundTunnel = GetNextOutboundTunnel (nullptr, tunnel->GetFarEndTransports ()); if (!outboundTunnel) outboundTunnel = tunnels.GetNextOutboundTunnel (); LogPrint (eLogDebug, "Tunnels: Re-creating destination inbound tunnel..."); std::shared_ptr config; if (m_NumInboundHops > 0 && tunnel->GetPeers().size()) + config = std::make_shared(tunnel->GetPeers (), tunnel->IsShortBuildMessage (), tunnel->GetFarEndTransports ()); + if (!m_NumInboundHops || config) { - config = std::make_shared(tunnel->GetPeers ()); - } - if (m_NumInboundHops == 0 || config) - { - auto newTunnel = tunnels.CreateInboundTunnel (config, outboundTunnel); - newTunnel->SetTunnelPool (shared_from_this()); + auto newTunnel = tunnels.CreateInboundTunnel (config, shared_from_this(), outboundTunnel); if (newTunnel->IsEstablished ()) // zero hops TunnelCreated (newTunnel); + else + newTunnel->SetRecreated (true); } } void TunnelPool::CreateOutboundTunnel () { - auto inboundTunnel = GetNextInboundTunnel (); - if (!inboundTunnel) - inboundTunnel = tunnels.GetNextInboundTunnel (); - if (inboundTunnel) + LogPrint (eLogDebug, "Tunnels: Creating destination outbound tunnel..."); + Path path; + if (SelectPeers (path, false)) { - LogPrint (eLogDebug, "Tunnels: Creating destination outbound tunnel..."); - std::vector > peers; - if (SelectPeers (peers, false)) + auto inboundTunnel = GetNextInboundTunnel (nullptr, path.farEndTransports); + if (!inboundTunnel) + inboundTunnel = tunnels.GetNextInboundTunnel (); + if (!inboundTunnel) { - std::shared_ptr config; - if (m_NumOutboundHops > 0) - config = std::make_shared(peers, inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash ()); - auto tunnel = tunnels.CreateOutboundTunnel (config); + LogPrint (eLogError, "Tunnels: Can't create outbound tunnel, no inbound tunnels found"); + return; + } + + if (m_LocalDestination && !m_LocalDestination->SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) + path.isShort = false; // because can't handle ECIES encrypted reply + + std::shared_ptr config; + if (m_NumOutboundHops > 0) + config = std::make_shared(path.peers, inboundTunnel->GetNextTunnelID (), + inboundTunnel->GetNextIdentHash (), path.isShort, path.farEndTransports); + + std::shared_ptr tunnel; + if (path.isShort) + { + // TODO: implement it better + tunnel = tunnels.CreateOutboundTunnel (config, inboundTunnel->GetTunnelPool ()); tunnel->SetTunnelPool (shared_from_this ()); - if (tunnel->IsEstablished ()) // zero hops - TunnelCreated (tunnel); - } - else - LogPrint (eLogError, "Tunnels: Can't create outbound tunnel, no peers available"); + } + else + tunnel = tunnels.CreateOutboundTunnel (config, shared_from_this ()); + if (tunnel && tunnel->IsEstablished ()) // zero hops + TunnelCreated (tunnel); } else - LogPrint (eLogError, "Tunnels: Can't create outbound tunnel, no inbound tunnels found"); + LogPrint (eLogError, "Tunnels: Can't create outbound tunnel, no peers available"); } void TunnelPool::RecreateOutboundTunnel (std::shared_ptr tunnel) @@ -585,7 +645,7 @@ namespace tunnel CreateOutboundTunnel (); return; } - auto inboundTunnel = GetNextInboundTunnel (); + auto inboundTunnel = GetNextInboundTunnel (nullptr, tunnel->GetFarEndTransports ()); if (!inboundTunnel) inboundTunnel = tunnels.GetNextInboundTunnel (); if (inboundTunnel) @@ -594,12 +654,12 @@ namespace tunnel std::shared_ptr config; if (m_NumOutboundHops > 0 && tunnel->GetPeers().size()) { - config = std::make_shared(tunnel->GetPeers (), inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash ()); + config = std::make_shared(tunnel->GetPeers (), inboundTunnel->GetNextTunnelID (), + inboundTunnel->GetNextIdentHash (), inboundTunnel->IsShortBuildMessage (), tunnel->GetFarEndTransports ()); } if (!m_NumOutboundHops || config) { - auto newTunnel = tunnels.CreateOutboundTunnel (config); - newTunnel->SetTunnelPool (shared_from_this ()); + auto newTunnel = tunnels.CreateOutboundTunnel (config, shared_from_this ()); if (newTunnel->IsEstablished ()) // zero hops TunnelCreated (newTunnel); } @@ -612,9 +672,9 @@ namespace tunnel { LogPrint (eLogDebug, "Tunnels: Creating paired inbound tunnel..."); auto tunnel = tunnels.CreateInboundTunnel ( - m_NumOutboundHops > 0 ? std::make_shared(outboundTunnel->GetInvertedPeers ()) : nullptr, - outboundTunnel); - tunnel->SetTunnelPool (shared_from_this ()); + m_NumOutboundHops > 0 ? std::make_shared(outboundTunnel->GetInvertedPeers (), + outboundTunnel->IsShortBuildMessage ()) : nullptr, + shared_from_this (), outboundTunnel); if (tunnel->IsEstablished ()) // zero hops TunnelCreated (tunnel); } diff --git a/libi2pd/TunnelPool.h b/libi2pd/TunnelPool.h index 164ca7a1..41cc400f 100644 --- a/libi2pd/TunnelPool.h +++ b/libi2pd/TunnelPool.h @@ -36,7 +36,15 @@ namespace tunnel class OutboundTunnel; typedef std::shared_ptr Peer; - typedef std::vector Path; + struct Path + { + std::vector peers; + bool isShort = true; + i2p::data::RouterInfo::CompatibleTransports farEndTransports = i2p::data::RouterInfo::eAllTransports; + + void Add (std::shared_ptr r); + void Reverse (); + }; /** interface for custom tunnel peer selection algorithm */ struct ITunnelPeerSelector @@ -47,9 +55,8 @@ namespace tunnel typedef std::function(std::shared_ptr, bool)> SelectHopFunc; - // standard peer selection algorithm - bool StandardSelectPeers(Path & path, int hops, bool inbound, SelectHopFunc nextHop); - + bool StandardSelectPeers(Path & path, int numHops, bool inbound, SelectHopFunc nextHop); + class TunnelPool: public std::enable_shared_from_this // per local destination { public: @@ -69,8 +76,10 @@ namespace tunnel void RecreateInboundTunnel (std::shared_ptr tunnel); void RecreateOutboundTunnel (std::shared_ptr tunnel); std::vector > GetInboundTunnels (int num) const; - std::shared_ptr GetNextOutboundTunnel (std::shared_ptr excluded = nullptr) const; - std::shared_ptr GetNextInboundTunnel (std::shared_ptr excluded = nullptr) const; + std::shared_ptr GetNextOutboundTunnel (std::shared_ptr excluded = nullptr, + i2p::data::RouterInfo::CompatibleTransports compatible = i2p::data::RouterInfo::eAllTransports) const; + std::shared_ptr GetNextInboundTunnel (std::shared_ptr excluded = nullptr, + i2p::data::RouterInfo::CompatibleTransports compatible = i2p::data::RouterInfo::eAllTransports) const; std::shared_ptr GetNewOutboundTunnel (std::shared_ptr old) const; void TestTunnels (); void ManageTunnels (uint64_t ts); @@ -113,9 +122,10 @@ namespace tunnel void CreateOutboundTunnel (); void CreatePairedInboundTunnel (std::shared_ptr outboundTunnel); template - typename TTunnels::value_type GetNextTunnel (TTunnels& tunnels, typename TTunnels::value_type excluded) const; - bool SelectPeers (std::vector >& hops, bool isInbound); - bool SelectExplicitPeers (std::vector >& hops, bool isInbound); + typename TTunnels::value_type GetNextTunnel (TTunnels& tunnels, + typename TTunnels::value_type excluded, i2p::data::RouterInfo::CompatibleTransports compatible) const; + bool SelectPeers (Path& path, bool isInbound); + bool SelectExplicitPeers (Path& path, bool isInbound); private: diff --git a/libi2pd/util.cpp b/libi2pd/util.cpp index 69fc366a..2b5c79f2 100644 --- a/libi2pd/util.cpp +++ b/libi2pd/util.cpp @@ -129,7 +129,7 @@ namespace util pthread_set_name_np(pthread_self(), name); #elif defined(__NetBSD__) pthread_setname_np(pthread_self(), "%s", (void *)name); -#else +#elif !defined(__gnu_hurd__) pthread_setname_np(pthread_self(), name); #endif } @@ -344,7 +344,7 @@ namespace net if(fd > 0) { ifreq ifr; - strncpy(ifr.ifr_name, ifa->ifa_name, IFNAMSIZ); // set interface for query + strncpy(ifr.ifr_name, ifa->ifa_name, IFNAMSIZ-1); // set interface for query if(ioctl(fd, SIOCGIFMTU, &ifr) >= 0) mtu = ifr.ifr_mtu; // MTU else @@ -555,7 +555,10 @@ namespace net static const std::vector< std::pair > reservedIPv6Ranges { address_pair_v6("2001:db8::", "2001:db8:ffff:ffff:ffff:ffff:ffff:ffff"), address_pair_v6("fc00::", "fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"), - address_pair_v6("fe80::", "febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff") + address_pair_v6("fe80::", "febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff"), + address_pair_v6("ff00::", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"), + address_pair_v6("::", "::"), + address_pair_v6("::1", "::1") }; boost::asio::ip::address_v6::bytes_type ipv6_address = host.to_v6 ().to_bytes (); diff --git a/libi2pd/util.h b/libi2pd/util.h index 000cb74e..d681829c 100644 --- a/libi2pd/util.h +++ b/libi2pd/util.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -51,14 +51,15 @@ namespace util MemoryPool (): m_Head (nullptr) {} ~MemoryPool () { - while (m_Head) - { - auto tmp = m_Head; - m_Head = static_cast(*(void * *)m_Head); // next - ::operator delete ((void *)tmp); - } + CleanUp (); } + void CleanUp () + { + CleanUp (m_Head); + m_Head = nullptr; + } + template T * Acquire (TArgs&&... args) { @@ -93,6 +94,18 @@ namespace util std::bind (&MemoryPool::Release, this, std::placeholders::_1)); } + protected: + + void CleanUp (T * head) + { + while (head) + { + auto tmp = head; + head = static_cast(*(void * *)head); // next + ::operator delete ((void *)tmp); + } + } + protected: T * m_Head; @@ -126,6 +139,24 @@ namespace util this->Release (it); } + template + std::shared_ptr AcquireSharedMt (TArgs&&... args) + { + return std::shared_ptr(AcquireMt (std::forward(args)...), + std::bind::*)(T *)> (&MemoryPoolMt::ReleaseMt, this, std::placeholders::_1)); + } + + void CleanUpMt () + { + T * head; + { + std::lock_guard l(m_Mutex); + head = this->m_Head; + this->m_Head = nullptr; + } + if (head) this->CleanUp (head); + } + private: std::mutex m_Mutex; diff --git a/libi2pd/version.h b/libi2pd/version.h index 028d1692..5aba36bd 100644 --- a/libi2pd/version.h +++ b/libi2pd/version.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -16,10 +16,15 @@ #define MAKE_VERSION_NUMBER(a,b,c) ((a*100+b)*100+c) #define I2PD_VERSION_MAJOR 2 -#define I2PD_VERSION_MINOR 38 +#define I2PD_VERSION_MINOR 39 #define I2PD_VERSION_MICRO 0 #define I2PD_VERSION_PATCH 0 -#define I2PD_VERSION MAKE_VERSION(I2PD_VERSION_MAJOR, I2PD_VERSION_MINOR, I2PD_VERSION_MICRO) +#ifdef GITVER + #define I2PD_VERSION GITVER +#else + #define I2PD_VERSION MAKE_VERSION(I2PD_VERSION_MAJOR, I2PD_VERSION_MINOR, I2PD_VERSION_MICRO) +#endif + #define VERSION I2PD_VERSION #ifdef MESHNET @@ -30,7 +35,7 @@ #define I2P_VERSION_MAJOR 0 #define I2P_VERSION_MINOR 9 -#define I2P_VERSION_MICRO 50 +#define I2P_VERSION_MICRO 51 #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 ba2d8276..3d5c83c0 100644 --- a/libi2pd_client/AddressBook.cpp +++ b/libi2pd_client/AddressBook.cpp @@ -470,6 +470,20 @@ namespace client if (pos != std::string::npos) addr = addr.substr(0, pos); // remove comments + pos = name.find(".b32.i2p"); + if (pos != std::string::npos) + { + LogPrint (eLogError, "Addressbook: skipped adding of b32 address: ", name); + continue; + } + + pos = name.find(".i2p"); + if (pos == std::string::npos) + { + LogPrint (eLogError, "Addressbook: malformed domain: ", name); + continue; + } + auto ident = std::make_shared (); if (!ident->FromBase64(addr)) { LogPrint (eLogError, "Addressbook: malformed address ", addr, " for ", name); diff --git a/libi2pd_client/ClientContext.cpp b/libi2pd_client/ClientContext.cpp index 94311297..d6871fbe 100644 --- a/libi2pd_client/ClientContext.cpp +++ b/libi2pd_client/ClientContext.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -185,7 +185,7 @@ namespace client LogPrint(eLogInfo, "Clients: stopping AddressBook"); m_AddressBook.Stop (); - { + { std::lock_guard lock(m_ForwardsMutex); m_ServerForwards.clear(); m_ClientForwards.clear(); @@ -200,6 +200,8 @@ namespace client for (auto& it: m_Destinations) it.second->Stop (); m_Destinations.clear (); + + m_SharedLocalDestination->Release (); m_SharedLocalDestination = nullptr; } @@ -209,14 +211,6 @@ namespace client /*std::string config; i2p::config::GetOption("conf", config); i2p::config::ParseConfig(config);*/ - // handle tunnels - // reset isUpdated for each tunnel - VisitTunnels ([](I2PService * s)->bool { s->isUpdated = false; return true; }); - // reload tunnels - ReadTunnels(); - // delete not updated tunnels (not in config anymore) - VisitTunnels ([](I2PService * s)->bool { return s->isUpdated; }); - // change shared local destination m_SharedLocalDestination->Release (); CreateNewSharedLocalDestination (); @@ -225,6 +219,7 @@ namespace client if (m_HttpProxy) { m_HttpProxy->Stop (); + delete m_HttpProxy; m_HttpProxy = nullptr; } ReadHttpProxy (); @@ -233,10 +228,19 @@ namespace client if (m_SocksProxy) { m_SocksProxy->Stop (); + delete m_SocksProxy; m_SocksProxy = nullptr; } ReadSocksProxy (); + // handle tunnels + // reset isUpdated for each tunnel + VisitTunnels (false); + // reload tunnels + ReadTunnels(); + // delete not updated tunnels (not in config anymore) + VisitTunnels (true); + // delete unused destinations std::unique_lock l(m_DestinationsMutex); for (auto it = m_Destinations.begin (); it != m_Destinations.end ();) @@ -457,7 +461,7 @@ namespace client options[I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY] = GetI2CPOption(section, I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY, DEFAULT_INITIAL_ACK_DELAY); options[I2CP_PARAM_STREAMING_ANSWER_PINGS] = GetI2CPOption(section, I2CP_PARAM_STREAMING_ANSWER_PINGS, isServer ? DEFAULT_ANSWER_PINGS : false); options[I2CP_PARAM_LEASESET_TYPE] = GetI2CPOption(section, I2CP_PARAM_LEASESET_TYPE, DEFAULT_LEASESET_TYPE); - std::string encType = GetI2CPStringOption(section, I2CP_PARAM_LEASESET_ENCRYPTION_TYPE, isServer ? "" : "0,4"); + std::string encType = GetI2CPStringOption(section, I2CP_PARAM_LEASESET_ENCRYPTION_TYPE, "0,4"); if (encType.length () > 0) options[I2CP_PARAM_LEASESET_ENCRYPTION_TYPE] = encType; std::string privKey = GetI2CPStringOption(section, I2CP_PARAM_LEASESET_PRIV_KEY, ""); if (privKey.length () > 0) options[I2CP_PARAM_LEASESET_PRIV_KEY] = privKey; @@ -504,20 +508,15 @@ namespace client int numClientTunnels = 0, numServerTunnels = 0; std::string tunConf; i2p::config::GetOption("tunconf", tunConf); if (tunConf.empty ()) - { - // TODO: cleanup this in 2.8.0 - tunConf = i2p::fs::DataDirPath ("tunnels.cfg"); - if (i2p::fs::Exists(tunConf)) - LogPrint(eLogWarning, "Clients: please rename tunnels.cfg -> tunnels.conf here: ", tunConf); - else - tunConf = i2p::fs::DataDirPath ("tunnels.conf"); - } + tunConf = i2p::fs::DataDirPath ("tunnels.conf"); + LogPrint(eLogDebug, "Clients: tunnels config file: ", tunConf); ReadTunnels (tunConf, numClientTunnels, numServerTunnels); std::string tunDir; i2p::config::GetOption("tunnelsdir", tunDir); if (tunDir.empty ()) tunDir = i2p::fs::DataDirPath ("tunnels.d"); + if (i2p::fs::Exists (tunDir)) { std::vector files; @@ -582,7 +581,7 @@ namespace client if (it != destinations.end ()) localDestination = it->second; else - { + { i2p::data::PrivateKeys k; if(LoadPrivateKeys (k, keys, sigType, cryptoType)) { @@ -597,7 +596,7 @@ namespace client destinations[keys] = localDestination; } } - } + } } if (type == I2P_TUNNELS_SECTION_TYPE_UDPCLIENT) { @@ -609,10 +608,18 @@ namespace client bool gzip = section.second.get (I2P_CLIENT_TUNNEL_GZIP, true); auto clientTunnel = std::make_shared(name, dest, end, localDestination, destinationPort, gzip); - if(m_ClientForwards.insert(std::make_pair(end, clientTunnel)).second) + + auto ins = m_ClientForwards.insert(std::make_pair(end, clientTunnel)); + if (ins.second) + { clientTunnel->Start(); + numClientTunnels++; + } else + { + ins.first->second->isUpdated = true; LogPrint(eLogError, "Clients: I2P Client forward for endpoint ", end, " already exists"); + } } else { boost::asio::ip::tcp::endpoint clientEndpoint; @@ -645,6 +652,13 @@ namespace client auto tun = std::make_shared (name, dest, address, port, localDestination, destinationPort); clientTunnel = tun; clientEndpoint = tun->GetLocalEndpoint (); + + uint32_t keepAlive = section.second.get(I2P_CLIENT_TUNNEL_KEEP_ALIVE_INTERVAL, 0); + if (keepAlive) + { + tun->SetKeepAliveInterval (keepAlive); + LogPrint(eLogInfo, "Clients: I2P Client tunnel keep alive interval set to ", keepAlive); + } } uint32_t timeout = section.second.get(I2P_CLIENT_TUNNEL_CONNECT_TIMEOUT, 0); @@ -666,13 +680,16 @@ namespace client if (ins.first->second->GetLocalDestination () != clientTunnel->GetLocalDestination ()) { LogPrint (eLogInfo, "Clients: I2P client tunnel destination updated"); + ins.first->second->Stop (); ins.first->second->SetLocalDestination (clientTunnel->GetLocalDestination ()); + ins.first->second->Start (); } ins.first->second->isUpdated = true; LogPrint (eLogInfo, "Clients: I2P client tunnel for endpoint ", clientEndpoint, " already exists"); } } } + else if (type == I2P_TUNNELS_SECTION_TYPE_SERVER || type == I2P_TUNNELS_SECTION_TYPE_HTTP || type == I2P_TUNNELS_SECTION_TYPE_IRC @@ -689,7 +706,7 @@ namespace client accessList=section.second.get (I2P_SERVER_TUNNEL_WHITE_LIST, ""); std::string hostOverride = section.second.get (I2P_SERVER_TUNNEL_HOST_OVERRIDE, ""); std::string webircpass = section.second.get (I2P_SERVER_TUNNEL_WEBIRC_PASSWORD, ""); - bool gzip = section.second.get (I2P_SERVER_TUNNEL_GZIP, true); + bool gzip = section.second.get (I2P_SERVER_TUNNEL_GZIP, false); i2p::data::SigningKeyType sigType = section.second.get (I2P_SERVER_TUNNEL_SIGNATURE_TYPE, i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519); i2p::data::CryptoKeyType cryptoType = section.second.get (I2P_CLIENT_TUNNEL_CRYPTO_TYPE, i2p::data::CRYPTO_KEY_TYPE_ELGAMAL); @@ -703,26 +720,37 @@ namespace client std::shared_ptr localDestination = nullptr; auto it = destinations.find (keys); if (it != destinations.end ()) - localDestination = it->second; - else { + localDestination = it->second; + localDestination->SetPublic (true); + } + else + { i2p::data::PrivateKeys k; if(!LoadPrivateKeys (k, keys, sigType, cryptoType)) continue; localDestination = FindLocalDestination (k.GetPublic ()->GetIdentHash ()); if (!localDestination) - { + { localDestination = CreateNewLocalDestination (k, true, &options); destinations[keys] = localDestination; - } - } + } + else + localDestination->SetPublic (true); + } if (type == I2P_TUNNELS_SECTION_TYPE_UDPSERVER) { // udp server tunnel // TODO: hostnames - if (address.empty ()) address = "127.0.0.1"; - auto localAddress = boost::asio::ip::address::from_string(address); boost::asio::ip::udp::endpoint endpoint(boost::asio::ip::address::from_string(host), port); + if (address.empty ()) + { + if (!endpoint.address ().is_unspecified () && endpoint.address ().is_v6 ()) + address = "::1"; + else + address = "127.0.0.1"; + } + auto localAddress = boost::asio::ip::address::from_string(address); auto serverTunnel = std::make_shared(name, localDestination, localAddress, endpoint, port, gzip); if(!isUniqueLocal) { @@ -730,17 +758,19 @@ namespace client serverTunnel->SetUniqueLocal(isUniqueLocal); } std::lock_guard lock(m_ForwardsMutex); - if(m_ServerForwards.insert( - std::make_pair( - std::make_pair( - localDestination->GetIdentHash(), port), - serverTunnel)).second) + auto ins = m_ServerForwards.insert(std::make_pair( + std::make_pair(localDestination->GetIdentHash(), port), + serverTunnel)); + if (ins.second) { serverTunnel->Start(); LogPrint(eLogInfo, "Clients: I2P Server Forward created for UDP Endpoint ", host, ":", port, " bound on ", address, " for ",localDestination->GetIdentHash().ToBase32()); } else - LogPrint(eLogError, "Clients: I2P Server Forward for destination/port ", m_AddressBook.ToAddress(localDestination->GetIdentHash()), "/", port, "already exists"); + { + ins.first->second->isUpdated = true; + LogPrint(eLogError, "Clients: I2P Server Forward for destination/port ", m_AddressBook.ToAddress(localDestination->GetIdentHash()), "/", port, " already exists"); + } continue; } @@ -789,7 +819,9 @@ namespace client if (ins.first->second->GetLocalDestination () != serverTunnel->GetLocalDestination ()) { LogPrint (eLogInfo, "Clients: I2P server tunnel destination updated"); + ins.first->second->Stop (); ins.first->second->SetLocalDestination (serverTunnel->GetLocalDestination ()); + ins.first->second->Start (); } ins.first->second->isUpdated = true; LogPrint (eLogInfo, "Clients: I2P server tunnel for destination/port ", m_AddressBook.ToAddress(localDestination->GetIdentHash ()), "/", inPort, " already exists"); @@ -866,7 +898,7 @@ namespace client { localDestination = m_HttpProxy->GetLocalDestination (); localDestination->Acquire (); - } + } else if (socksProxyKeys.length () > 0) { i2p::data::PrivateKeys keys; @@ -914,27 +946,52 @@ namespace client } } - template - void VisitTunnelsContainer (Container& c, Visitor v) + void ClientContext::VisitTunnels (bool clean) { - for (auto it = c.begin (); it != c.end ();) + for (auto it = m_ClientTunnels.begin (); it != m_ClientTunnels.end ();) { - if (!v (it->second.get ())) - { + if(clean && !it->second->isUpdated) { it->second->Stop (); - it = c.erase (it); - } - else + it = m_ClientTunnels.erase(it); + } else { + it->second->isUpdated = false; it++; + } } - } - template - void ClientContext::VisitTunnels (Visitor v) - { - VisitTunnelsContainer (m_ClientTunnels, v); - VisitTunnelsContainer (m_ServerTunnels, v); - // TODO: implement UDP forwards + for (auto it = m_ServerTunnels.begin (); it != m_ServerTunnels.end ();) + { + if(clean && !it->second->isUpdated) { + it->second->Stop (); + it = m_ServerTunnels.erase(it); + } else { + it->second->isUpdated = false; + it++; + } + } + + /* // TODO: Write correct UDP tunnels stop + for (auto it = m_ClientForwards.begin (); it != m_ClientForwards.end ();) + { + if(clean && !it->second->isUpdated) { + it->second = nullptr; + it = m_ClientForwards.erase(it); + } else { + it->second->isUpdated = false; + it++; + } + } + + for (auto it = m_ServerForwards.begin (); it != m_ServerForwards.end ();) + { + if(clean && !it->second->isUpdated) { + it->second = nullptr; + it = m_ServerForwards.erase(it); + } else { + it->second->isUpdated = false; + it++; + } + } */ } } } diff --git a/libi2pd_client/ClientContext.h b/libi2pd_client/ClientContext.h index 076aaa5f..cb28e40e 100644 --- a/libi2pd_client/ClientContext.h +++ b/libi2pd_client/ClientContext.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -22,6 +22,7 @@ #include "BOB.h" #include "I2CP.h" #include "AddressBook.h" +#include "I18N_langs.h" namespace i2p { @@ -47,6 +48,7 @@ namespace client const char I2P_CLIENT_TUNNEL_DESTINATION_PORT[] = "destinationport"; const char I2P_CLIENT_TUNNEL_MATCH_TUNNELS[] = "matchtunnels"; const char I2P_CLIENT_TUNNEL_CONNECT_TIMEOUT[] = "connecttimeout"; + const char I2P_CLIENT_TUNNEL_KEEP_ALIVE_INTERVAL[] = "keepaliveinterval"; const char I2P_SERVER_TUNNEL_HOST[] = "host"; const char I2P_SERVER_TUNNEL_HOST_OVERRIDE[] = "hostoverride"; const char I2P_SERVER_TUNNEL_PORT[] = "port"; @@ -102,6 +104,10 @@ namespace client std::vector > GetForwardInfosFor(const i2p::data::IdentHash & destination); + // i18n + std::shared_ptr GetLanguage () { return m_Language; }; + void SetLanguage (const std::shared_ptr language) { m_Language = language; }; + private: void ReadTunnels (); @@ -121,8 +127,7 @@ namespace client void CleanupUDP(const boost::system::error_code & ecode); void ScheduleCleanupUDP(); - template - void VisitTunnels (Visitor v); // Visitor: (I2PService *) -> bool, true means retain + void VisitTunnels (bool clean); void CreateNewSharedLocalDestination (); void AddLocalDestination (std::shared_ptr localDestination); @@ -150,6 +155,9 @@ namespace client std::unique_ptr m_CleanupUDPTimer; + // i18n + std::shared_ptr m_Language; + public: // for HTTP diff --git a/libi2pd_client/HTTPProxy.cpp b/libi2pd_client/HTTPProxy.cpp index d8b84e82..7acdc333 100644 --- a/libi2pd_client/HTTPProxy.cpp +++ b/libi2pd_client/HTTPProxy.cpp @@ -295,7 +295,7 @@ namespace proxy { std::string full_url = m_RequestURL.to_string(); std::stringstream ss; ss << tr("Host") <<" " << m_RequestURL.host << " " << tr("added to router's addressbook from helper") << ". "; - ss << tr("Click") << " " << tr("here") << " " << tr("to proceed") << "."; + ss << tr("Click here to proceed:") << " " << tr("Continue") << "."; GenericProxyInfo(tr("Addresshelper found"), ss.str()); return true; /* request processed */ } @@ -304,8 +304,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") << " " << tr("here") << " " << tr("to update record") << "."; + ss << tr("Click here to update record:") << " " << tr("Continue") << "."; GenericProxyInfo(tr("Addresshelper found"), ss.str()); return true; /* request processed */ } @@ -319,7 +319,7 @@ namespace proxy { auto pos = uri.find(":"); if(pos == std::string::npos || pos == uri.size() - 1) { - GenericProxyError(tr("Invalid Request"), tr("invalid request uri")); + GenericProxyError(tr("Invalid request"), tr("invalid request uri")); return true; } else diff --git a/libi2pd_client/I2CP.cpp b/libi2pd_client/I2CP.cpp index 25a5504a..7c76d359 100644 --- a/libi2pd_client/I2CP.cpp +++ b/libi2pd_client/I2CP.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -52,12 +52,12 @@ namespace client } } - bool I2CPDestination::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, i2p::data::CryptoKeyType preferredCrypto) const + bool I2CPDestination::Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const { if (preferredCrypto == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD && m_ECIESx25519Decryptor) - return m_ECIESx25519Decryptor->Decrypt (encrypted, data, ctx, true); + return m_ECIESx25519Decryptor->Decrypt (encrypted, data); if (m_Decryptor) - return m_Decryptor->Decrypt (encrypted, data, ctx, true); + return m_Decryptor->Decrypt (encrypted, data); else LogPrint (eLogError, "I2CP: decryptor is not set"); return false; diff --git a/libi2pd_client/I2CP.h b/libi2pd_client/I2CP.h index 9a8bda4e..08a96af6 100644 --- a/libi2pd_client/I2CP.h +++ b/libi2pd_client/I2CP.h @@ -83,7 +83,7 @@ namespace client void SendMsgTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash& ident, uint32_t nonce); // called from I2CPSession // implements LocalDestination - bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, i2p::data::CryptoKeyType preferredCrypto) const; + bool Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const; bool SupportsEncryptionType (i2p::data::CryptoKeyType keyType) const; const uint8_t * GetEncryptionPublicKey (i2p::data::CryptoKeyType keyType) const; // for 4 only std::shared_ptr GetIdentity () const { return m_Identity; }; diff --git a/libi2pd_client/I2PTunnel.cpp b/libi2pd_client/I2PTunnel.cpp index 5476bfe2..b97270e4 100644 --- a/libi2pd_client/I2PTunnel.cpp +++ b/libi2pd_client/I2PTunnel.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -303,7 +303,7 @@ namespace client m_ProxyConnectionSent = true; } else - m_OutHeader << line << "\n"; + m_OutHeader << line << "\n"; } } else @@ -532,7 +532,7 @@ namespace client I2PClientTunnel::I2PClientTunnel (const std::string& name, const std::string& destination, const std::string& address, int port, std::shared_ptr localDestination, int destinationPort): TCPIPAcceptor (address, port, localDestination), m_Name (name), m_Destination (destination), - m_DestinationPort (destinationPort) + m_DestinationPort (destinationPort), m_KeepAliveInterval (0) { } @@ -540,14 +540,24 @@ namespace client { TCPIPAcceptor::Start (); GetAddress (); + if (m_KeepAliveInterval) + ScheduleKeepAliveTimer (); } void I2PClientTunnel::Stop () { TCPIPAcceptor::Stop(); m_Address = nullptr; + if (m_KeepAliveTimer) m_KeepAliveTimer->cancel (); } + void I2PClientTunnel::SetKeepAliveInterval (uint32_t keepAliveInterval) + { + m_KeepAliveInterval = keepAliveInterval; + if (m_KeepAliveInterval) + m_KeepAliveTimer.reset (new boost::asio::deadline_timer (GetLocalDestination ()->GetService ())); + } + /* HACK: maybe we should create a caching IdentHash provider in AddressBook */ std::shared_ptr I2PClientTunnel::GetAddress () { @@ -569,6 +579,31 @@ namespace client return nullptr; } + void I2PClientTunnel::ScheduleKeepAliveTimer () + { + if (m_KeepAliveTimer) + { + m_KeepAliveTimer->expires_from_now (boost::posix_time::seconds(m_KeepAliveInterval)); + m_KeepAliveTimer->async_wait (std::bind (&I2PClientTunnel::HandleKeepAliveTimer, + this, std::placeholders::_1)); + } + } + + void I2PClientTunnel::HandleKeepAliveTimer (const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + { + if (m_Address && m_Address->IsValid ()) + { + if (m_Address->IsIdentHash ()) + GetLocalDestination ()->SendPing (m_Address->identHash); + else + GetLocalDestination ()->SendPing (m_Address->blindedPublicKey); + } + ScheduleKeepAliveTimer (); + } + } + I2PServerTunnel::I2PServerTunnel (const std::string& name, const std::string& address, int port, std::shared_ptr localDestination, int inport, bool gzip): I2PService (localDestination), m_IsUniqueLocal(true), m_Name (name), m_Address (address), m_Port (port), m_IsAccessList (false) @@ -605,7 +640,47 @@ namespace client { if (!ecode) { - auto addr = (*it).endpoint ().address (); + bool found = false; + boost::asio::ip::tcp::endpoint ep; + if (m_LocalAddress) + { + boost::asio::ip::tcp::resolver::iterator end; + while (it != end) + { + ep = *it; + if (!ep.address ().is_unspecified ()) + { + if (ep.address ().is_v4 ()) + { + if (m_LocalAddress->is_v4 ()) found = true; + } + else if (ep.address ().is_v6 ()) + { + if (i2p::util::net::IsYggdrasilAddress (ep.address ())) + { + if (i2p::util::net::IsYggdrasilAddress (*m_LocalAddress)) + found = true; + } + else if (m_LocalAddress->is_v6 ()) + found = true; + } + } + if (found) break; + it++; + } + } + else + { + found = true; + ep = *it; // first available + } + if (!found) + { + LogPrint (eLogError, "I2PTunnel: Unable to resolve to compatible address"); + return; + } + + auto addr = ep.address (); LogPrint (eLogInfo, "I2PTunnel: server tunnel ", (*it).host_name (), " has been resolved to ", addr); m_Endpoint.address (addr); Accept (); @@ -848,7 +923,8 @@ namespace client LogPrint(eLogInfo, "UDPServer: done"); } - void I2PUDPServerTunnel::Start() { + void I2PUDPServerTunnel::Start() + { m_LocalDest->Start(); } @@ -888,7 +964,8 @@ namespace client RemotePort(remotePort), m_LastPort (0), m_cancel_resolve(false) { - m_LocalSocket.set_option (boost::asio::socket_base::receive_buffer_size (I2P_UDP_MAX_MTU )); + 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(gzip); dgram->SetReceiver(std::bind(&I2PUDPClientTunnel::HandleRecvFromI2P, this, @@ -915,7 +992,8 @@ namespace client void I2PUDPClientTunnel::HandleRecvFromLocal(const boost::system::error_code & ec, std::size_t transferred) { if(ec) { - LogPrint(eLogError, "UDP Client: ", ec.message()); + LogPrint(eLogError, "UDP Client: Reading from socket error: ", ec.message(), ". Restarting listener..."); + RecvFromLocal(); // Restart listener and continue work return; } if(!m_RemoteIdent) { @@ -940,7 +1018,7 @@ namespace client 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) + 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); @@ -1024,8 +1102,9 @@ namespace client else LogPrint(eLogWarning, "UDP Client: not tracking udp session using port ", (int) toPort); } - - I2PUDPClientTunnel::~I2PUDPClientTunnel() { + + I2PUDPClientTunnel::~I2PUDPClientTunnel() + { auto dgram = m_LocalDest->GetDatagramDestination(); if (dgram) dgram->ResetReceiver(); diff --git a/libi2pd_client/I2PTunnel.h b/libi2pd_client/I2PTunnel.h index 3b52ea1a..d21b2e11 100644 --- a/libi2pd_client/I2PTunnel.h +++ b/libi2pd_client/I2PTunnel.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -153,16 +153,22 @@ namespace client void Stop (); const char* GetName() { return m_Name.c_str (); } - + void SetKeepAliveInterval (uint32_t keepAliveInterval); + private: std::shared_ptr GetAddress (); + + void ScheduleKeepAliveTimer (); + void HandleKeepAliveTimer (const boost::system::error_code& ecode); private: std::string m_Name, m_Destination; std::shared_ptr m_Address; int m_DestinationPort; + uint32_t m_KeepAliveInterval; + std::unique_ptr m_KeepAliveTimer; }; @@ -254,6 +260,10 @@ namespace client std::vector m_Sessions; std::shared_ptr m_LocalDest; UDPSessionPtr m_LastSession; + + public: + + bool isUpdated; // transient, used during reload only }; class I2PUDPClientTunnel @@ -283,7 +293,7 @@ namespace client void TryResolving(); private: - + const std::string m_Name; std::mutex m_SessionsMutex; std::unordered_map > m_Sessions; // maps i2p port -> local udp convo @@ -298,6 +308,10 @@ namespace client uint16_t RemotePort, m_LastPort; bool m_cancel_resolve; std::shared_ptr m_LastSession; + + public: + + bool isUpdated; // transient, used during reload only }; class I2PServerTunnel: public I2PService diff --git a/libi2pd_client/MatchedDestination.cpp b/libi2pd_client/MatchedDestination.cpp index c0766096..8d17632b 100644 --- a/libi2pd_client/MatchedDestination.cpp +++ b/libi2pd_client/MatchedDestination.cpp @@ -79,23 +79,23 @@ namespace client if(!inbound && m_RemoteLeaseSet) { if(m_RemoteLeaseSet->IsExpired()) - { ResolveCurrentLeaseSet(); - } if(m_RemoteLeaseSet && !m_RemoteLeaseSet->IsExpired()) { // remote lease set is good auto leases = m_RemoteLeaseSet->GetNonExpiredLeases(); // pick lease std::shared_ptr obep; - while(!obep && leases.size() > 0) { + while(!obep && leases.size() > 0) + { auto idx = rand() % leases.size(); auto lease = leases[idx]; obep = i2p::data::netdb.FindRouter(lease->tunnelGateway); leases.erase(leases.begin()+idx); } - if(obep) { - path.push_back(obep->GetRouterIdentity()); + if(obep) + { + path.Add (obep); LogPrint(eLogDebug, "Destination: found OBEP matching IBGW"); } else LogPrint(eLogWarning, "Destination: could not find proper IBGW for matched outbound tunnel"); diff --git a/libi2pd_client/SAM.cpp b/libi2pd_client/SAM.cpp index af9ba6a8..c2983f43 100644 --- a/libi2pd_client/SAM.cpp +++ b/libi2pd_client/SAM.cpp @@ -1207,7 +1207,11 @@ namespace client void SAMSingleSession::StopLocalDestination () { localDestination->Release (); + // stop accepting new streams localDestination->StopAcceptingStreams (); + // terminate existing streams + auto s = localDestination->GetStreamingDestination (); // TODO: take care about datagrams + if (s) s->Stop (); } void SAMMasterSession::Close () diff --git a/libi2pd_wrapper/api.go b/libi2pd_wrapper/api.go new file mode 100644 index 00000000..64403aae --- /dev/null +++ b/libi2pd_wrapper/api.go @@ -0,0 +1,15 @@ +package api + +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +/* +#cgo CXXFLAGS: -I${SRCDIR}/../i18n -I${SRCDIR}/../libi2pd_client -I${SRCDIR}/../libi2pd -g -Wall -Wextra -Wno-unused-parameter -pedantic -Wno-psabi -fPIC -D__AES__ -maes +#cgo LDFLAGS: -L${SRCDIR}/../ -l:libi2pd.a -l:libi2pdlang.a -latomic -lcrypto -lssl -lz -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread -lstdc++ +*/ +import "C" diff --git a/libi2pd_wrapper/api.swigcxx b/libi2pd_wrapper/api.swigcxx new file mode 100644 index 00000000..3ef6bd36 --- /dev/null +++ b/libi2pd_wrapper/api.swigcxx @@ -0,0 +1,8 @@ +// See swig.org for more inteface options, +// e.g. map std::string to Go string + +%{ +#include "capi.h" +%} + +%include "capi.h" diff --git a/libi2pd_wrapper/capi.cpp b/libi2pd_wrapper/capi.cpp new file mode 100644 index 00000000..fc4df917 --- /dev/null +++ b/libi2pd_wrapper/capi.cpp @@ -0,0 +1,106 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#include "api.h" +#include "capi.h" +#include +#include +#include +#include + + +// Uses the example from: https://stackoverflow.com/a/9210560 +// See also https://stackoverflow.com/questions/9210528/split-string-with-delimiters-in-c/9210560# +// Does not handle consecutive delimiters, this is only for passing +// lists of arguments by value to InitI2P from C_InitI2P +char** str_split(char* a_str, const char a_delim) +{ + char** result = 0; + size_t count = 0; + char* tmp = a_str; + char* last_comma = 0; + char delim[2]; + delim[0] = a_delim; + delim[1] = 0; + + /* Count how many elements will be extracted. */ + while (*tmp) + { + if (a_delim == *tmp) + { + count++; + last_comma = tmp; + } + tmp++; + } + + /* Add space for trailing token. */ + count += last_comma < (a_str + strlen(a_str) - 1); + + /* Add space for terminating null string so caller + knows where the list of returned strings ends. */ + count++; + + result = (char**) malloc(sizeof(char*) * count); + + if (result) + { + size_t idx = 0; + char* token = strtok(a_str, delim); + + while (token) + { + assert(idx < count); + *(result + idx++) = strdup(token); + token = strtok(0, delim); + } + assert(idx == count - 1); + *(result + idx) = 0; + } + + return result; +} + + +#ifdef __cplusplus +extern "C" { +#endif + +void C_InitI2P (int argc, char argv[], const char * appName) +{ + const char* delim = " "; + char* vargs = strdup(argv); + char** args = str_split(vargs, *delim); + std::cout << argv; + return i2p::api::InitI2P(argc, args, appName); +} + +void C_TerminateI2P () +{ + return i2p::api::TerminateI2P(); +} + +void C_StartI2P () +{ + std::shared_ptr logStream; + return i2p::api::StartI2P(logStream); +} + +void C_StopI2P () +{ + return i2p::api::StopI2P(); +} + +void C_RunPeerTest () +{ + return i2p::api::RunPeerTest(); +} + +#ifdef __cplusplus +} +#endif diff --git a/libi2pd_wrapper/capi.h b/libi2pd_wrapper/capi.h new file mode 100644 index 00000000..bfc4f88b --- /dev/null +++ b/libi2pd_wrapper/capi.h @@ -0,0 +1,29 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#ifndef CAPI_H__ +#define CAPI_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +// initialization start and stop +void C_InitI2P (int argc, char argv[], const char * appName); +//void C_InitI2P (int argc, char** argv, const char * appName); +void C_TerminateI2P (); +void C_StartI2P (); +// write system log to logStream, if not specified to .log in application's folder +void C_StopI2P (); +void C_RunPeerTest (); // should be called after UPnP + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/tests/Makefile b/tests/Makefile index 4c80c37c..8eb52fde 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -1,4 +1,5 @@ -CXXFLAGS += -Wall -Wextra -pedantic -O0 -g -std=c++11 -D_GLIBCXX_USE_NANOSLEEP=1 -I../libi2pd/ -pthread -Wl,--unresolved-symbols=ignore-in-object-files +CXXFLAGS += -Wall -Wno-unused-parameter -Wextra -pedantic -O0 -g -std=c++11 -D_GLIBCXX_USE_NANOSLEEP=1 -pthread -Wl,--unresolved-symbols=ignore-in-object-files +INCFLAGS += -I../libi2pd TESTS = test-gost test-gost-sig test-base-64 test-x25519 test-aeadchacha20poly1305 test-blinding test-elligator