Merge tag '2.39.0'
2.39.0
yangfl
2 years ago
9 | 9 | - uses: actions/checkout@v2 |
10 | 10 | - name: Test in FreeBSD |
11 | 11 | id: test |
12 | uses: vmactions/freebsd-vm@v0.1.2 | |
12 | uses: vmactions/freebsd-vm@v0.1.4 | |
13 | 13 | with: |
14 | 14 | usesh: true |
15 | 15 | prepare: pkg install -y devel/cmake devel/gmake devel/boost-libs security/openssl net/miniupnpc |
2 | 2 | on: [push, pull_request] |
3 | 3 | |
4 | 4 | jobs: |
5 | build: | |
6 | name: With USE_UPNP=${{ matrix.with_upnp }} | |
7 | runs-on: ubuntu-16.04 | |
5 | build-make: | |
6 | name: Make with USE_UPNP=${{ matrix.with_upnp }} | |
7 | runs-on: ubuntu-18.04 | |
8 | 8 | strategy: |
9 | 9 | fail-fast: true |
10 | 10 | matrix: |
18 | 18 | sudo apt-get install build-essential libboost1.74-dev libminiupnpc-dev libssl-dev zlib1g-dev |
19 | 19 | - name: build application |
20 | 20 | run: make USE_UPNP=${{ matrix.with_upnp }} -j3 |
21 | build-cmake: | |
22 | name: CMake with -DWITH_UPNP=${{ matrix.with_upnp }} | |
23 | runs-on: ubuntu-18.04 | |
24 | strategy: | |
25 | fail-fast: true | |
26 | matrix: | |
27 | with_upnp: ['ON', 'OFF'] | |
28 | steps: | |
29 | - uses: actions/checkout@v2 | |
30 | - name: install packages | |
31 | run: | | |
32 | sudo add-apt-repository ppa:mhier/libboost-latest | |
33 | sudo apt-get update | |
34 | sudo apt-get install build-essential cmake libboost1.74-dev libminiupnpc-dev libssl-dev zlib1g-dev | |
35 | - name: build application | |
36 | run: | | |
37 | cd build | |
38 | cmake -DWITH_UPNP=${{ matrix.with_upnp }} . | |
39 | make -j3 | |
40 | build-deb-stretch: | |
41 | name: Build package for stretch | |
42 | runs-on: ubuntu-latest | |
43 | steps: | |
44 | - uses: actions/checkout@v2 | |
45 | with: | |
46 | fetch-depth: 0 | |
47 | - name: change debian changelog | |
48 | run: | | |
49 | sudo apt-get update | |
50 | sudo apt-get install devscripts | |
51 | debchange -v "`git describe --tags`-stretch" -M --distribution stretch "trunk build" | |
52 | - uses: singingwolfboy/build-dpkg-stretch@v1 | |
53 | id: build | |
54 | with: | |
55 | args: --unsigned-source --unsigned-changes -b | |
56 | - uses: actions/upload-artifact@v1 | |
57 | with: | |
58 | name: ${{ steps.build.outputs.filename }} | |
59 | path: ${{ steps.build.outputs.filename }} | |
60 | - uses: actions/upload-artifact@v1 | |
61 | with: | |
62 | name: ${{ steps.build.outputs.filename-dbgsym }} | |
63 | path: ${{ steps.build.outputs.filename-dbgsym }} | |
64 | build-deb-buster: | |
65 | name: Build package for buster | |
66 | runs-on: ubuntu-latest | |
67 | steps: | |
68 | - uses: actions/checkout@v2 | |
69 | with: | |
70 | fetch-depth: 0 | |
71 | - name: change debian changelog | |
72 | run: | | |
73 | sudo apt-get update | |
74 | sudo apt-get install devscripts | |
75 | debchange -v "`git describe --tags`-buster" -M --distribution buster "trunk build" | |
76 | - uses: singingwolfboy/build-dpkg-buster@v1 | |
77 | id: build | |
78 | with: | |
79 | args: --unsigned-source --unsigned-changes -b | |
80 | - uses: actions/upload-artifact@v1 | |
81 | with: | |
82 | name: ${{ steps.build.outputs.filename }} | |
83 | path: ${{ steps.build.outputs.filename }} | |
84 | - uses: actions/upload-artifact@v1 | |
85 | with: | |
86 | name: ${{ steps.build.outputs.filename-dbgsym }} | |
87 | path: ${{ steps.build.outputs.filename-dbgsym }} |
0 | name: Build containers | |
1 | ||
2 | on: [push] | |
3 | ||
4 | jobs: | |
5 | docker: | |
6 | runs-on: ubuntu-latest | |
7 | permissions: | |
8 | packages: write | |
9 | contents: read | |
10 | ||
11 | steps: | |
12 | - name: Checkout | |
13 | uses: actions/checkout@v2 | |
14 | ||
15 | - name: Set up QEMU | |
16 | uses: docker/setup-qemu-action@v1 | |
17 | ||
18 | - name: Set up Docker Buildx | |
19 | uses: docker/setup-buildx-action@v1 | |
20 | ||
21 | - name: Login to DockerHub | |
22 | uses: docker/login-action@v1 | |
23 | with: | |
24 | username: ${{ secrets.DOCKERHUB_USERNAME }} | |
25 | password: ${{ secrets.DOCKERHUB_TOKEN }} | |
26 | ||
27 | - name: Login to GitHub Container registry | |
28 | uses: docker/login-action@v1 | |
29 | with: | |
30 | registry: ghcr.io | |
31 | username: ${{ github.actor }} | |
32 | password: ${{ secrets.GITHUB_TOKEN }} | |
33 | ||
34 | - name: Build and push trunk container | |
35 | if: ${{ !startsWith(github.ref, 'refs/tags/') }} | |
36 | uses: docker/build-push-action@v2 | |
37 | with: | |
38 | context: ./contrib/docker | |
39 | file: ./contrib/docker/Dockerfile | |
40 | platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v7 | |
41 | push: true | |
42 | tags: | | |
43 | purplei2p/i2pd:latest | |
44 | ghcr.io/purplei2p/i2pd:latest | |
45 | ||
46 | - name: Set env | |
47 | if: ${{ startsWith(github.ref, 'refs/tags/') }} | |
48 | run: echo "RELEASE_VERSION=${GITHUB_REF:10}" >> $GITHUB_ENV | |
49 | ||
50 | - name: Build and push release container | |
51 | if: ${{ startsWith(github.ref, 'refs/tags/') }} | |
52 | uses: docker/build-push-action@v2 | |
53 | with: | |
54 | context: ./contrib/docker | |
55 | file: ./contrib/docker/Dockerfile | |
56 | platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v7 | |
57 | push: true | |
58 | tags: | | |
59 | purplei2p/i2pd:latest | |
60 | purplei2p/i2pd:release-${{ env.RELEASE_VERSION }} | |
61 | ghcr.io/purplei2p/i2pd:latest | |
62 | ghcr.io/purplei2p/i2pd:release-${{ env.RELEASE_VERSION }} |
6 | 6 | /i2pd |
7 | 7 | /libi2pd.a |
8 | 8 | /libi2pdclient.a |
9 | /libi2pdlang.a | |
9 | 10 | /libi2pd.so |
10 | 11 | /libi2pdclient.so |
12 | /libi2pdlang.so | |
13 | /libi2pd.dll | |
14 | /libi2pdclient.dll | |
15 | /libi2pdlang.dll | |
11 | 16 | *.exe |
12 | 17 | |
13 | 18 |
0 | language: cpp | |
1 | cache: | |
2 | apt: true | |
3 | os: | |
4 | - linux | |
5 | #- osx | |
6 | dist: xenial | |
7 | sudo: required | |
8 | compiler: | |
9 | - g++ | |
10 | - clang++ | |
11 | env: | |
12 | global: | |
13 | - MAKEFLAGS="-j 2" | |
14 | matrix: | |
15 | - BUILD_TYPE=make UPNP=ON MAKE_UPNP=yes | |
16 | - BUILD_TYPE=make UPNP=OFF MAKE_UPNP=no | |
17 | - BUILD_TYPE=cmake UPNP=ON MAKE_UPNP=yes | |
18 | - BUILD_TYPE=cmake UPNP=OFF MAKE_UPNP=no | |
19 | matrix: | |
20 | exclude: | |
21 | - os: osx | |
22 | env: BUILD_TYPE=cmake UPNP=ON MAKE_UPNP=yes | |
23 | - os: osx | |
24 | env: BUILD_TYPE=cmake UPNP=OFF MAKE_UPNP=no | |
25 | - os: linux | |
26 | compiler: clang++ | |
27 | env: BUILD_TYPE=make UPNP=ON MAKE_UPNP=yes | |
28 | - os: linux | |
29 | compiler: clang++ | |
30 | env: BUILD_TYPE=make UPNP=OFF MAKE_UPNP=no | |
31 | addons: | |
32 | apt: | |
33 | packages: | |
34 | - build-essential | |
35 | - cmake | |
36 | - g++ | |
37 | - clang | |
38 | - libboost-chrono-dev | |
39 | - libboost-date-time-dev | |
40 | - libboost-filesystem-dev | |
41 | - libboost-program-options-dev | |
42 | - libboost-system-dev | |
43 | - libboost-thread-dev | |
44 | - libminiupnpc-dev | |
45 | - libssl-dev | |
46 | before_install: | |
47 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update ; fi | |
48 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install libressl miniupnpc ; fi | |
49 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew outdated boost || brew upgrade boost ; fi | |
50 | script: | |
51 | - if [[ "$TRAVIS_OS_NAME" == "linux" && "$BUILD_TYPE" == "cmake" ]]; then cd build && cmake -DCMAKE_BUILD_TYPE=Release -DWITH_UPNP=${UPNP} && make ; fi | |
52 | - if [[ "$TRAVIS_OS_NAME" == "linux" && "$BUILD_TYPE" == "make" ]]; then make USE_UPNP=${MAKE_UPNP} ; fi | |
53 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then make HOMEBREW=1 USE_UPNP=${MAKE_UPNP} ; fi |
0 | 0 | # for this file format description, |
1 | 1 | # see https://github.com/olivierlacan/keep-a-changelog |
2 | 2 | |
3 | ## [2.38.0] - 2021-03-17 | |
3 | ## [2.39.0] - 2021-08-23 | |
4 | ### Added | |
5 | - Short tunnel build messages | |
6 | - Localization. To: Russian, Ukrainian, Turkmen, Uzbek and Afrikaans | |
7 | - Custom CSS styles for webconsole | |
8 | - Avoid slow tunnels with more than 250 ms per hop | |
9 | - Process DELAY_REQUESTED streaming option | |
10 | - "certsdir" options for certificates location | |
11 | - Keep own RouterInfo in NetBb | |
12 | - Pick ECIES routers only for tunnels on non-x64 | |
13 | - NTP sync through ipv6 | |
14 | - Allow ipv6 addresses for UDP server tunnels | |
15 | ### Changed | |
16 | - Rekey of all routers to ECIES | |
17 | - Better distribution for random tunnel's peer selection | |
18 | - Yggdrasil reseed for v0.4, added two more | |
19 | - Encryption type 0,4 by default for server tunnels | |
20 | - Handle i2cp.dontPublishLeaseSet param for all destinations | |
21 | - reg.i2p for subscriptions | |
22 | - LeaseSet type 3 by default | |
23 | - Don't allocate payload buffer for every single ECIESx25519 message | |
24 | - Prefer public ipv6 instead rfc4941 | |
25 | - Optimal padding for one-time ECIESx25519 message | |
26 | - Don't send datetime block for one-time ECIESx25519 message with one-time key | |
27 | - Router with expired introducer is still valid | |
28 | - Don't disable floodfill if still reachable by ipv6 | |
29 | - Set minimal version for floodfill to 0.9.38 | |
30 | - Eliminate extra lookups for sequential fragments on tunnel endpoint | |
31 | - Consistent path for explicit peers | |
32 | - Always create new tunnel from exploratory pool | |
33 | - Don't try to connect to a router not reachable from us | |
34 | - Mark additional ipv6 addresses/nets as reserved (#1679) | |
35 | ### Fixed | |
36 | - Zero-hop tunnels | |
37 | - Crash upon SAM session termination | |
38 | - Build with boost < 1.55.0 | |
39 | - Address type for NTCP2 acceptors | |
40 | - Check of ipv4/ipv6 address | |
41 | - Request router to send to if not in NetDb | |
42 | - Count outbound traffic for zero-hop tunnels | |
43 | - URLdecode domain for registration string generator in webconsole | |
44 | ||
45 | ## [2.38.0] - 2021-05-17 | |
4 | 46 | ### Added |
5 | 47 | - Publish ipv6 introducers |
6 | 48 | - Bind ipv6 or yggdrasil NTCP2 acceptor to specified address |
0 | 0 | SYS := $(shell $(CXX) -dumpmachine) |
1 | SHLIB := libi2pd.so | |
1 | ||
2 | ifneq (, $(findstring darwin, $(SYS))) | |
3 | SHARED_SUFFIX = dylib | |
4 | else ifneq (, $(findstring mingw, $(SYS))$(findstring cygwin, $(SYS))) | |
5 | SHARED_SUFFIX = dll | |
6 | else | |
7 | SHARED_SUFFIX = so | |
8 | endif | |
9 | ||
10 | SHLIB := libi2pd.$(SHARED_SUFFIX) | |
2 | 11 | ARLIB := libi2pd.a |
3 | SHLIB_CLIENT := libi2pdclient.so | |
12 | SHLIB_LANG := libi2pdlang.$(SHARED_SUFFIX) | |
13 | ARLIB_LANG := libi2pdlang.a | |
14 | SHLIB_CLIENT := libi2pdclient.$(SHARED_SUFFIX) | |
4 | 15 | ARLIB_CLIENT := libi2pdclient.a |
16 | SHLIB_WRAP := libi2pdwrapper.$(SHARED_SUFFIX) | |
17 | ARLIB_WRAP := libi2pdwrapper.a | |
5 | 18 | I2PD := i2pd |
6 | 19 | |
7 | 20 | LIB_SRC_DIR := libi2pd |
8 | 21 | LIB_CLIENT_SRC_DIR := libi2pd_client |
22 | WRAP_SRC_DIR := libi2pd_wrapper | |
23 | LANG_SRC_DIR := i18n | |
9 | 24 | DAEMON_SRC_DIR := daemon |
10 | 25 | |
11 | 26 | # import source files lists |
12 | 27 | include filelist.mk |
13 | 28 | |
14 | USE_AESNI := yes | |
15 | USE_STATIC := no | |
16 | USE_MESHNET := no | |
17 | USE_UPNP := no | |
18 | DEBUG := yes | |
29 | USE_AESNI := $(or $(USE_AESNI),yes) | |
30 | USE_STATIC := $(or $(USE_STATIC),no) | |
31 | USE_MESHNET := $(or $(USE_MESHNET),no) | |
32 | USE_UPNP := $(or $(USE_UPNP),no) | |
33 | DEBUG := $(or $(DEBUG),yes) | |
19 | 34 | |
20 | 35 | ifeq ($(DEBUG),yes) |
21 | 36 | CXX_DEBUG = -g |
48 | 63 | NEEDED_CXXFLAGS += -DMESHNET |
49 | 64 | endif |
50 | 65 | |
51 | NEEDED_CXXFLAGS += -MMD -MP -I$(LIB_SRC_DIR) -I$(LIB_CLIENT_SRC_DIR) | |
66 | NEEDED_CXXFLAGS += -MMD -MP -I$(LIB_SRC_DIR) -I$(LIB_CLIENT_SRC_DIR) -I$(LANG_SRC_DIR) | |
52 | 67 | |
53 | 68 | LIB_OBJS += $(patsubst %.cpp,obj/%.o,$(LIB_SRC)) |
54 | 69 | LIB_CLIENT_OBJS += $(patsubst %.cpp,obj/%.o,$(LIB_CLIENT_SRC)) |
70 | LANG_OBJS += $(patsubst %.cpp,obj/%.o,$(LANG_SRC)) | |
55 | 71 | DAEMON_OBJS += $(patsubst %.cpp,obj/%.o,$(DAEMON_SRC)) |
56 | DEPS += $(LIB_OBJS:.o=.d) $(LIB_CLIENT_OBJS:.o=.d) $(DAEMON_OBJS:.o=.d) | |
72 | WRAP_LIB_OBJS += $(patsubst %.cpp,obj/%.o,$(WRAP_LIB_SRC)) | |
73 | DEPS += $(LIB_OBJS:.o=.d) $(LIB_CLIENT_OBJS:.o=.d) $(LANG_OBJS:.o=.d) $(DAEMON_OBJS:.o=.d) $(WRAP_LIB_OBJS:.o=.d) | |
57 | 74 | |
58 | all: mk_obj_dir $(ARLIB) $(ARLIB_CLIENT) $(I2PD) | |
75 | ## Build all code (libi2pd, libi2pdclient, libi2pdlang), link it to .a and build binary | |
76 | all: $(ARLIB) $(ARLIB_CLIENT) $(ARLIB_LANG) $(I2PD) | |
59 | 77 | |
60 | 78 | mk_obj_dir: |
61 | @mkdir -p obj | |
62 | @mkdir -p obj/Win32 | |
63 | 79 | @mkdir -p obj/$(LIB_SRC_DIR) |
64 | 80 | @mkdir -p obj/$(LIB_CLIENT_SRC_DIR) |
81 | @mkdir -p obj/$(LANG_SRC_DIR) | |
65 | 82 | @mkdir -p obj/$(DAEMON_SRC_DIR) |
83 | @mkdir -p obj/$(WRAP_SRC_DIR) | |
84 | @mkdir -p obj/Win32 | |
66 | 85 | |
67 | api: mk_obj_dir $(SHLIB) $(ARLIB) | |
68 | client: mk_obj_dir $(SHLIB_CLIENT) $(ARLIB_CLIENT) | |
69 | api_client: mk_obj_dir $(SHLIB) $(ARLIB) $(SHLIB_CLIENT) $(ARLIB_CLIENT) | |
86 | api: $(SHLIB) $(ARLIB) | |
87 | client: $(SHLIB_CLIENT) $(ARLIB_CLIENT) | |
88 | lang: $(SHLIB_LANG) $(ARLIB_LANG) | |
89 | api_client: api client lang | |
90 | wrapper: api_client $(SHLIB_WRAP) $(ARLIB_WRAP) | |
70 | 91 | |
71 | 92 | ## NOTE: The NEEDED_CXXFLAGS are here so that CXXFLAGS can be specified at build time |
72 | 93 | ## **without** overwriting the CXXFLAGS which we need in order to build. |
75 | 96 | ## -std=c++11. If you want to remove this variable please do so in a way that allows setting |
76 | 97 | ## custom FLAGS to work at build-time. |
77 | 98 | |
78 | obj/%.o: %.cpp | |
99 | obj/%.o: %.cpp | mk_obj_dir | |
79 | 100 | $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -c -o $@ $< |
80 | 101 | |
81 | 102 | # '-' is 'ignore if missing' on first run |
82 | 103 | -include $(DEPS) |
83 | 104 | |
84 | $(I2PD): $(DAEMON_OBJS) $(ARLIB) $(ARLIB_CLIENT) | |
105 | $(I2PD): $(DAEMON_OBJS) $(ARLIB) $(ARLIB_CLIENT) $(ARLIB_LANG) | |
85 | 106 | $(CXX) -o $@ $(LDFLAGS) $^ $(LDLIBS) |
86 | 107 | |
87 | $(SHLIB): $(LIB_OBJS) | |
108 | $(SHLIB): $(LIB_OBJS) $(SHLIB_LANG) | |
109 | ifneq ($(USE_STATIC),yes) | |
110 | $(CXX) $(LDFLAGS) -shared -o $@ $^ $(LDLIBS) $(SHLIB_LANG) | |
111 | endif | |
112 | ||
113 | $(SHLIB_CLIENT): $(LIB_CLIENT_OBJS) $(SHLIB) $(SHLIB_LANG) | |
114 | ifneq ($(USE_STATIC),yes) | |
115 | $(CXX) $(LDFLAGS) -shared -o $@ $^ $(LDLIBS) $(SHLIB) $(SHLIB_LANG) | |
116 | endif | |
117 | ||
118 | $(SHLIB_WRAP): $(WRAP_LIB_OBJS) | |
88 | 119 | ifneq ($(USE_STATIC),yes) |
89 | 120 | $(CXX) $(LDFLAGS) -shared -o $@ $^ $(LDLIBS) |
90 | 121 | endif |
91 | 122 | |
92 | $(SHLIB_CLIENT): $(LIB_CLIENT_OBJS) | |
123 | $(SHLIB_LANG): $(LANG_OBJS) | |
93 | 124 | ifneq ($(USE_STATIC),yes) |
94 | $(CXX) $(LDFLAGS) -shared -o $@ $^ $(LDLIBS) $(SHLIB) | |
125 | $(CXX) $(LDFLAGS) -shared -o $@ $^ $(LDLIBS) | |
95 | 126 | endif |
96 | 127 | |
97 | 128 | $(ARLIB): $(LIB_OBJS) |
100 | 131 | $(ARLIB_CLIENT): $(LIB_CLIENT_OBJS) |
101 | 132 | $(AR) -r $@ $^ |
102 | 133 | |
134 | $(ARLIB_WRAP): $(WRAP_LIB_OBJS) | |
135 | $(AR) -r $@ $^ | |
136 | ||
137 | $(ARLIB_LANG): $(LANG_OBJS) | |
138 | $(AR) -r $@ $^ | |
139 | ||
103 | 140 | clean: |
104 | 141 | $(RM) -r obj |
105 | 142 | $(RM) -r docs/generated |
106 | $(RM) $(I2PD) $(SHLIB) $(ARLIB) $(SHLIB_CLIENT) $(ARLIB_CLIENT) | |
143 | $(RM) $(I2PD) $(SHLIB) $(ARLIB) $(SHLIB_CLIENT) $(ARLIB_CLIENT) $(SHLIB_LANG) $(ARLIB_LANG) $(SHLIB_WRAP) $(ARLIB_WRAP) | |
107 | 144 | |
108 | strip: $(I2PD) $(SHLIB_CLIENT) $(SHLIB) | |
145 | strip: $(I2PD) $(SHLIB) $(SHLIB_CLIENT) $(SHLIB_LANG) | |
109 | 146 | strip $^ |
110 | 147 | |
111 | 148 | LATEST_TAG=$(shell git describe --tags --abbrev=0 openssl) |
129 | 166 | .PHONY: api |
130 | 167 | .PHONY: api_client |
131 | 168 | .PHONY: client |
169 | .PHONY: lang | |
132 | 170 | .PHONY: mk_obj_dir |
133 | 171 | .PHONY: install |
134 | 172 | .PHONY: strip |
2 | 2 | |
3 | 3 | WINDRES = windres |
4 | 4 | |
5 | CXXFLAGS := $(CXX_DEBUG) -D_MT -DWIN32_LEAN_AND_MEAN -fPIC -msse | |
5 | CXXFLAGS := $(CXX_DEBUG) -DWIN32_LEAN_AND_MEAN -fPIC -msse | |
6 | 6 | INCFLAGS = -I$(DAEMON_SRC_DIR) -IWin32 |
7 | LDFLAGS := ${LD_DEBUG} -Wl,-Bstatic -static-libgcc | |
7 | LDFLAGS := ${LD_DEBUG} -static | |
8 | 8 | |
9 | 9 | # detect proper flag for c++11 support by compilers |
10 | 10 | CXXVER := $(shell $(CXX) -dumpversion) |
60 | 60 | LDFLAGS += -Wl,--nxcompat -Wl,--high-entropy-va -Wl,--dynamicbase,--export-all-symbols |
61 | 61 | endif |
62 | 62 | |
63 | obj/%.o : %.rc | |
63 | obj/%.o : %.rc | mk_obj_dir | |
64 | 64 | $(WINDRES) -i $< -o $@ |
2 | 2 | [![License](https://img.shields.io/github/license/PurpleI2P/i2pd.svg)](https://github.com/PurpleI2P/i2pd/blob/openssl/LICENSE) |
3 | 3 | [![Packaging status](https://repology.org/badge/tiny-repos/i2pd.svg)](https://repology.org/project/i2pd/versions) |
4 | 4 | [![Docker Pulls](https://img.shields.io/docker/pulls/purplei2p/i2pd)](https://hub.docker.com/r/purplei2p/i2pd) |
5 | [![Crowdin](https://badges.crowdin.net/i2pd/localized.svg)](https://crowdin.com/project/i2pd) | |
5 | 6 | |
6 | 7 | *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* |
7 | 8 | |
67 | 68 | |
68 | 69 | **Supported systems:** |
69 | 70 | |
70 | * GNU/Linux - [![Build Status](https://travis-ci.org/PurpleI2P/i2pd.svg?branch=openssl)](https://travis-ci.org/PurpleI2P/i2pd) | |
71 | * 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) | |
71 | 72 | * 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/) |
72 | 73 | * Alpine, ArchLinux, openSUSE, Gentoo, Debian, Ubuntu, etc. |
73 | * Windows - [![Build status](https://ci.appveyor.com/api/projects/status/1908qe4p48ff1x23?svg=true)](https://ci.appveyor.com/project/PurpleI2P/i2pd) | |
74 | * Mac OS X - [![Build Status](https://travis-ci.org/PurpleI2P/i2pd.svg?branch=openssl)](https://travis-ci.org/PurpleI2P/i2pd) | |
75 | * Docker image - [![Build Status](https://img.shields.io/docker/cloud/build/purplei2p/i2pd)](https://hub.docker.com/r/purplei2p/i2pd/builds/) | |
76 | * Snap | |
77 | * FreeBSD | |
78 | * Android | |
74 | * 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) | |
75 | * 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) | |
76 | * 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) | |
77 | * 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) | |
78 | * 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) | |
79 | * 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) | |
79 | 80 | * iOS |
80 | 81 | |
81 | 82 | Using i2pd |
83 | 84 | |
84 | 85 | See [documentation](https://i2pd.readthedocs.io/en/latest/user-guide/run/) and |
85 | 86 | [example config file](https://github.com/PurpleI2P/i2pd/blob/openssl/contrib/i2pd.conf). |
87 | ||
88 | Localization | |
89 | ------------ | |
90 | ||
91 | You can help us with translation i2pd to your language using Crowdin platform! | |
92 | Translation project can be found [here](https://crowdin.com/project/i2pd). | |
93 | ||
94 | New languages can be requested on project's [discussion page](https://crowdin.com/project/i2pd/discussions). | |
95 | ||
96 | Current status: [![Crowdin](https://badges.crowdin.net/i2pd/localized.svg)](https://crowdin.com/project/i2pd) | |
86 | 97 | |
87 | 98 | Donations |
88 | 99 | --------- |
93 | 104 | DASH: Xw8YUrQpYzP9tZBmbjqxS3M97Q7v3vJKUF |
94 | 105 | ZEC: t1cTckLuXsr1dwVrK4NDzfhehss4NvMadAJ |
95 | 106 | GST: GbD2JSQHBHCKLa9WTHmigJRpyFgmBj4woG |
107 | XMR: 497pJc7X4xqKvcLBLpSUtRgWqMMyo24u4btCos3cak6gbMkpobgSU6492ztUcUBghyeHpYeczB55s38NpuHoH5WGNSPDRMH | |
96 | 108 | |
97 | 109 | License |
98 | 110 | ------- |
130 | 130 | transfer >>= 10; |
131 | 131 | auto mbytes = transfer & 0x03ff; |
132 | 132 | transfer >>= 10; |
133 | auto gbytes = transfer & 0x03ff; | |
133 | auto gbytes = transfer; | |
134 | 134 | |
135 | 135 | if (gbytes) |
136 | 136 | s << gbytes << " GB, "; |
0 | version: 2.38.0.{build} | |
1 | pull_requests: | |
2 | do_not_increment_build_number: true | |
3 | branches: | |
4 | only: | |
5 | - openssl | |
6 | skip_tags: true | |
7 | os: Visual Studio 2015 | |
8 | shallow_clone: true | |
9 | clone_depth: 1 | |
10 | ||
11 | # avoid building 32-bit if 64-bit failed already | |
12 | matrix: | |
13 | fast_finish: true | |
14 | ||
15 | environment: | |
16 | APPVEYOR_SAVE_CACHE_ON_ERROR: true | |
17 | MSYS2_PATH_TYPE: inherit | |
18 | CHERE_INVOKING: enabled_from_arguments | |
19 | matrix: | |
20 | - MSYSTEM: MINGW64 | |
21 | - MSYSTEM: MINGW32 | |
22 | ||
23 | cache: | |
24 | - c:\msys64\var\cache\pacman\pkg\ | |
25 | ||
26 | install: | |
27 | # install new signing keyring | |
28 | - 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" | |
29 | - 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" | |
30 | - c:\msys64\usr\bin\bash -lc "pacman-key --verify msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz.sig" | |
31 | - c:\msys64\usr\bin\bash -lc "pacman --noconfirm -U msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz" | |
32 | # remove packages which can break build | |
33 | - 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" | |
34 | # update runtime | |
35 | - c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Syuu" | |
36 | # Kill bash before next try | |
37 | - taskkill /T /F /IM bash.exe /IM gpg.exe /IM gpg-agent.exe | exit /B 0 | |
38 | # update packages and install required | |
39 | - c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Syuu $MINGW_PACKAGE_PREFIX-boost $MINGW_PACKAGE_PREFIX-miniupnpc" | |
40 | ||
41 | build_script: | |
42 | - c:\msys64\usr\bin\bash -lc "make USE_UPNP=yes DEBUG=no -j3" | |
43 | # prepare archive for uploading | |
44 | - set "FILELIST=i2pd.exe README.txt contrib/i2pd.conf contrib/tunnels.conf contrib/certificates contrib/tunnels.d" | |
45 | - echo This is development build, use it carefully! For running in portable mode, move all files from contrib directory here. > README.txt | |
46 | - 7z a -tzip -mx9 -mmt i2pd-%APPVEYOR_BUILD_VERSION%-%APPVEYOR_REPO_COMMIT:~0,7%-mingw-win%MSYSTEM:~-2%.zip %FILELIST% | |
47 | ||
48 | after_build: | |
49 | - c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Sc" | |
50 | ||
51 | test: off | |
52 | ||
53 | deploy: off | |
54 | ||
55 | artifacts: | |
56 | - path: i2pd-*.zip |
2 | 2 | /i2pd |
3 | 3 | /libi2pd.a |
4 | 4 | /libi2pdclient.a |
5 | /libi2pdlang.a | |
5 | 6 | /cmake_install.cmake |
6 | 7 | /CMakeCache.txt |
7 | 8 | /CPackConfig.cmake |
10 | 11 | /arch.c |
11 | 12 | # windows build script |
12 | 13 | i2pd*.zip |
13 | build*.log⏎ | |
14 | build*.log |
26 | 26 | set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules") |
27 | 27 | set(CMAKE_SOURCE_DIR "..") |
28 | 28 | |
29 | #Handle paths nicely | |
30 | include(GNUInstallDirs) | |
31 | ||
29 | 32 | # architecture |
30 | 33 | include(TargetArch) |
31 | 34 | target_architecture(ARCHITECTURE) |
32 | 35 | |
33 | 36 | set(LIBI2PD_SRC_DIR ../libi2pd) |
34 | 37 | set(LIBI2PD_CLIENT_SRC_DIR ../libi2pd_client) |
38 | set(LANG_SRC_DIR ../i18n) | |
35 | 39 | set(DAEMON_SRC_DIR ../daemon) |
36 | 40 | |
37 | 41 | include_directories(${LIBI2PD_SRC_DIR}) |
38 | 42 | include_directories(${LIBI2PD_CLIENT_SRC_DIR}) |
43 | include_directories(${LANG_SRC_DIR}) | |
39 | 44 | include_directories(${DAEMON_SRC_DIR}) |
40 | 45 | |
41 | set(LIBI2PD_SRC | |
42 | "${LIBI2PD_SRC_DIR}/api.cpp" | |
43 | "${LIBI2PD_SRC_DIR}/Base.cpp" | |
44 | "${LIBI2PD_SRC_DIR}/Blinding.cpp" | |
45 | "${LIBI2PD_SRC_DIR}/BloomFilter.cpp" | |
46 | "${LIBI2PD_SRC_DIR}/ChaCha20.cpp" | |
47 | "${LIBI2PD_SRC_DIR}/Config.cpp" | |
48 | "${LIBI2PD_SRC_DIR}/CPU.cpp" | |
49 | "${LIBI2PD_SRC_DIR}/Crypto.cpp" | |
50 | "${LIBI2PD_SRC_DIR}/CryptoKey.cpp" | |
51 | "${LIBI2PD_SRC_DIR}/Datagram.cpp" | |
52 | "${LIBI2PD_SRC_DIR}/Destination.cpp" | |
53 | "${LIBI2PD_SRC_DIR}/ECIESX25519AEADRatchetSession.cpp" | |
54 | "${LIBI2PD_SRC_DIR}/Ed25519.cpp" | |
55 | "${LIBI2PD_SRC_DIR}/Elligator.cpp" | |
56 | "${LIBI2PD_SRC_DIR}/Family.cpp" | |
57 | "${LIBI2PD_SRC_DIR}/FS.cpp" | |
58 | "${LIBI2PD_SRC_DIR}/Garlic.cpp" | |
59 | "${LIBI2PD_SRC_DIR}/Gost.cpp" | |
60 | "${LIBI2PD_SRC_DIR}/Gzip.cpp" | |
61 | "${LIBI2PD_SRC_DIR}/HTTP.cpp" | |
62 | "${LIBI2PD_SRC_DIR}/I2NPProtocol.cpp" | |
63 | "${LIBI2PD_SRC_DIR}/Identity.cpp" | |
64 | "${LIBI2PD_SRC_DIR}/LeaseSet.cpp" | |
65 | "${LIBI2PD_SRC_DIR}/Log.cpp" | |
66 | "${LIBI2PD_SRC_DIR}/NetDb.cpp" | |
67 | "${LIBI2PD_SRC_DIR}/NetDbRequests.cpp" | |
68 | "${LIBI2PD_SRC_DIR}/NTCP2.cpp" | |
69 | "${LIBI2PD_SRC_DIR}/Poly1305.cpp" | |
70 | "${LIBI2PD_SRC_DIR}/Profiling.cpp" | |
71 | "${LIBI2PD_SRC_DIR}/Reseed.cpp" | |
72 | "${LIBI2PD_SRC_DIR}/RouterContext.cpp" | |
73 | "${LIBI2PD_SRC_DIR}/RouterInfo.cpp" | |
74 | "${LIBI2PD_SRC_DIR}/Signature.cpp" | |
75 | "${LIBI2PD_SRC_DIR}/SSU.cpp" | |
76 | "${LIBI2PD_SRC_DIR}/SSUData.cpp" | |
77 | "${LIBI2PD_SRC_DIR}/SSUSession.cpp" | |
78 | "${LIBI2PD_SRC_DIR}/Streaming.cpp" | |
79 | "${LIBI2PD_SRC_DIR}/Timestamp.cpp" | |
80 | "${LIBI2PD_SRC_DIR}/TransitTunnel.cpp" | |
81 | "${LIBI2PD_SRC_DIR}/Transports.cpp" | |
82 | "${LIBI2PD_SRC_DIR}/Tunnel.cpp" | |
83 | "${LIBI2PD_SRC_DIR}/TunnelEndpoint.cpp" | |
84 | "${LIBI2PD_SRC_DIR}/TunnelGateway.cpp" | |
85 | "${LIBI2PD_SRC_DIR}/TunnelPool.cpp" | |
86 | "${LIBI2PD_SRC_DIR}/TunnelConfig.cpp" | |
87 | "${LIBI2PD_SRC_DIR}/util.cpp" | |
88 | ) | |
89 | ||
46 | FILE(GLOB LIBI2PD_SRC ${LIBI2PD_SRC_DIR}/*.cpp) | |
90 | 47 | add_library(libi2pd ${LIBI2PD_SRC}) |
91 | 48 | set_target_properties(libi2pd PROPERTIES PREFIX "") |
92 | 49 | |
93 | 50 | if(WITH_LIBRARY) |
94 | 51 | install(TARGETS libi2pd |
95 | 52 | EXPORT libi2pd |
96 | ARCHIVE DESTINATION lib | |
97 | LIBRARY DESTINATION lib | |
53 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} | |
54 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} | |
98 | 55 | COMPONENT Libraries) |
99 | 56 | # TODO Make libi2pd available to 3rd party projects via CMake as imported target |
100 | 57 | # FIXME This pulls stdafx |
101 | 58 | # install(EXPORT libi2pd DESTINATION ${CMAKE_INSTALL_LIBDIR}) |
102 | 59 | endif() |
103 | 60 | |
104 | set(CLIENT_SRC | |
105 | "${LIBI2PD_CLIENT_SRC_DIR}/AddressBook.cpp" | |
106 | "${LIBI2PD_CLIENT_SRC_DIR}/BOB.cpp" | |
107 | "${LIBI2PD_CLIENT_SRC_DIR}/ClientContext.cpp" | |
108 | "${LIBI2PD_CLIENT_SRC_DIR}/MatchedDestination.cpp" | |
109 | "${LIBI2PD_CLIENT_SRC_DIR}/I2PTunnel.cpp" | |
110 | "${LIBI2PD_CLIENT_SRC_DIR}/I2PService.cpp" | |
111 | "${LIBI2PD_CLIENT_SRC_DIR}/SAM.cpp" | |
112 | "${LIBI2PD_CLIENT_SRC_DIR}/SOCKS.cpp" | |
113 | "${LIBI2PD_CLIENT_SRC_DIR}/HTTPProxy.cpp" | |
114 | "${LIBI2PD_CLIENT_SRC_DIR}/I2CP.cpp" | |
115 | ) | |
116 | ||
61 | FILE(GLOB CLIENT_SRC ${LIBI2PD_CLIENT_SRC_DIR}/*.cpp) | |
117 | 62 | add_library(libi2pdclient ${CLIENT_SRC}) |
118 | 63 | set_target_properties(libi2pdclient PROPERTIES PREFIX "") |
119 | 64 | |
120 | 65 | if(WITH_LIBRARY) |
121 | 66 | install(TARGETS libi2pdclient |
122 | 67 | EXPORT libi2pdclient |
123 | ARCHIVE DESTINATION lib | |
124 | LIBRARY DESTINATION lib | |
68 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} | |
69 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} | |
70 | COMPONENT Libraries) | |
71 | endif() | |
72 | ||
73 | FILE(GLOB LANG_SRC ${LANG_SRC_DIR}/*.cpp) | |
74 | add_library(libi2pdlang ${LANG_SRC}) | |
75 | set_target_properties(libi2pdlang PROPERTIES PREFIX "") | |
76 | ||
77 | if(WITH_LIBRARY) | |
78 | install(TARGETS libi2pdlang | |
79 | EXPORT libi2pdlang | |
80 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} | |
81 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} | |
125 | 82 | COMPONENT Libraries) |
126 | 83 | endif() |
127 | 84 | |
253 | 210 | ) |
254 | 211 | target_compile_options(libi2pd PRIVATE -include libi2pd/stdafx.h) |
255 | 212 | target_compile_options(libi2pdclient PRIVATE -include libi2pd/stdafx.h) |
213 | target_compile_options(libi2pdlang PRIVATE -include libi2pd/stdafx.h) | |
256 | 214 | target_link_libraries(libi2pd stdafx) |
257 | 215 | endif() |
258 | 216 | |
259 | target_link_libraries(libi2pdclient libi2pd) | |
217 | target_link_libraries(libi2pdclient libi2pd libi2pdlang) | |
260 | 218 | |
261 | 219 | find_package(Boost COMPONENTS system filesystem program_options date_time REQUIRED) |
262 | 220 | if(NOT DEFINED Boost_INCLUDE_DIRS) |
316 | 274 | message(STATUS " THREADSANITIZER : ${WITH_THREADSANITIZER}") |
317 | 275 | message(STATUS "---------------------------------------") |
318 | 276 | |
319 | #Handle paths nicely | |
320 | include(GNUInstallDirs) | |
321 | ||
322 | 277 | if(WITH_BINARY) |
323 | 278 | add_executable("${PROJECT_NAME}" ${DAEMON_SRC}) |
324 | 279 | |
350 | 305 | endif() |
351 | 306 | |
352 | 307 | target_link_libraries(libi2pd ${Boost_LIBRARIES} ${ZLIB_LIBRARY}) |
353 | 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}) | |
308 | 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}) | |
354 | 309 | |
355 | 310 | install(TARGETS "${PROJECT_NAME}" RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT Runtime) |
356 | 311 | set(APPS "\${CMAKE_INSTALL_PREFIX}/bin/${PROJECT_NAME}${CMAKE_EXECUTABLE_SUFFIX}") |
0 | Howto build & run | |
1 | ================== | |
2 | ||
3 | **Build** | |
4 | ||
5 | Assuming you're in the root directory of the anoncoin source code. | |
6 | ||
7 | $ `cd build/docker` | |
8 | $ `docker -t meeh/i2pd:latest .` | |
9 | ||
10 | **Run** | |
11 | ||
12 | To run either the local build, or if not found - fetched prebuild from hub.docker.io, run the following command. | |
13 | ||
14 | $ `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` | |
15 | ||
16 | All the ports ( -p HOSTPORT:DOCKERPORT ) is optional. However the command above enable all features (Webconsole, HTTP Proxy, BOB, SAM, i2cp, etc) | |
17 | ||
18 | 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. | |
19 | ||
20 | **Options** | |
21 | ||
22 | Options are set via docker environment variables. This can be set at run with -e parameters. | |
23 | ||
24 | * **ENABLE_IPV6** - Enable IPv6 support. Any value can be used - it triggers as long as it's not empty. | |
25 | * **LOGLEVEL** - Set the loglevel. | |
26 | * **ENABLE_AUTH** - Enable auth for the webconsole. Username and password needs to be set manually in i2pd.conf cause security reasons. | |
27 | ||
28 | **Logging** | |
29 | ||
30 | 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. :) | |
31 | ||
32 | ||
33 |
0 | FROM ubuntu | |
1 | ||
2 | RUN apt-get update && apt-get install -y libboost-dev libboost-filesystem-dev \ | |
3 | libboost-program-options-dev libboost-date-time-dev \ | |
4 | libssl-dev git build-essential | |
5 | ||
6 | RUN git clone https://github.com/PurpleI2P/i2pd.git | |
7 | WORKDIR /i2pd | |
8 | RUN make | |
9 | ||
10 | CMD ./i2pd |
0 | -----BEGIN CERTIFICATE----- | |
1 | MIIFfzCCA2egAwIBAgIEbNbRPjANBgkqhkiG9w0BAQ0FADBwMQswCQYDVQQGEwJY | |
2 | WDELMAkGA1UECAwCWFgxCzAJBgNVBAcMAlhYMR4wHAYDVQQKDBVJMlAgQW5vbnlt | |
3 | b3VzIE5ldHdvcmsxDDAKBgNVBAsMA0kyUDEZMBcGA1UEAwwQb3JpZ25hbEBtYWls | |
4 | LmkycDAeFw0yMTA3MDYyMjExMDFaFw0zMTA3MDQyMjExMDFaMHAxCzAJBgNVBAYT | |
5 | AlhYMQswCQYDVQQIDAJYWDELMAkGA1UEBwwCWFgxHjAcBgNVBAoMFUkyUCBBbm9u | |
6 | eW1vdXMgTmV0d29yazEMMAoGA1UECwwDSTJQMRkwFwYDVQQDDBBvcmlnbmFsQG1h | |
7 | aWwuaTJwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvNJz2KGuAkHP | |
8 | tGFobfLvpybtxB50fkcEsTc9opmiy7wBKK9rSI01VS616IhABkWKZVfK2A9NqpGv | |
9 | v/CyhTKoaeSNeXY7+zORUWgWK/zA9fA4GRZFqlW8j4tbompDwcLYNqRBCsn1C0OY | |
10 | YA5JhXPBixMcnXl8N8x4sXhQ4l9R3+QrydhUHRvgDc8dOxRyIX7zuQAyf8tmA2Xo | |
11 | xZLdvDcCJdLBIbFwxhIceIhgcOwaOx7oRkZDZdYcLJd3zjyPbu8JtOM2ZkwH7r+0 | |
12 | ro5PktuDp2LAS6SII5yYNcwcrvPZGPqhLdifIw1BrdTIb/rIkQZ5iXOOdyPmT7e8 | |
13 | IwAJcPFlfvrS4Vbi9oDqyx3aDUBoubgmFnO1TirL56ck83R/ubcKtdnyzAn5dp+f | |
14 | ZNYW6/foSBpDDOCViylbFAR5H0HJEbBns7PZx6mGEEI4tUAJdNYl7Ly7Df60a9Rz | |
15 | cD/gz08U9UwFXYKoT6roEjToADGAzb5MI4cVlAb2AmQaMNXNe04HcDL1bU50mkNU | |
16 | amqPv8nxf72fBQCEmZz2G57T6QiYTtcCwiWS1QdWsuaOtCo9zO0MKcjzSdUxuxEc | |
17 | dXhjQdNegsgg/Xk7bJ8lKOsACqMpFftdPmuyeZU2t+3RPuBpV/0j2qUfg/y6kb0z | |
18 | CxAOYmlcL4kqw4VT+5V/EeZLIG0h9I0CAwEAAaMhMB8wHQYDVR0OBBYEFD/wJObg | |
19 | CCDuhMJCVWTSTj+B3rsUMA0GCSqGSIb3DQEBDQUAA4ICAQC0PjsTSPWlGbLNeeI8 | |
20 | F0B5xAwXYJzZ7/LRxh8u42HDUqVIDjqkuls1l3v9D7htty2Gr3Ws2dcvcOr2KcOy | |
21 | mEWg+jdP/N3vt9IkZeVS4YQoPgq6orn7lVkk00bcKb24f7ZnoQnnVV0/m42Y5P4j | |
22 | LLh+8MBxsez9azXyZbDVEkgsMUAkdVO6KNz6scqz7wb8egV2GAMAp7cwChC6lanK | |
23 | gv9ZyJhG/HdTv6VyuMZhJy6rX4geM97tm1iHu1VLsQcIzBKAdEvWJv8ofMeiyINe | |
24 | hqAP9NYaeowKi975NOrmf+XZwxd0niApIohV684RCVUfL8H7HSPbdXhBJ/WslyDP | |
25 | cTGhA2BLqEXZBn/nLQknlnl0SZTQxG2n4fEgD1E5YS/aoBrig/uXtWm2Zdf8U3mM | |
26 | +bNXhbi9s7LneN2ye8LlNJBSRklNn/bNo8OmzLII1RQwf1+vaHT96lASbTVepMZ/ | |
27 | Y9VcC8fAmho/zfQEKueLEB03K+gr2dGD+1crmMtUBjWJ9vPjtooZArtkDbh+kVYA | |
28 | cx4N4NXULRwxVWZe5wTQOqcZ3qSS1ClMwaziwychGaj8xRAirHMZnlPOZO1UK4+5 | |
29 | 8F4RMJktyZjNgSLP76XPS4rJK5fobuPqFeA4OpDFn/5+/XeQFF6i6wntx1tzztzH | |
30 | zc+BrVZOdcYPqu9iLXyRQ9JwwA== | |
31 | -----END CERTIFICATE----- |
0 | -----BEGIN CERTIFICATE----- | |
1 | MIIFVDCCAzwCCQC2r1XWYtqtAzANBgkqhkiG9w0BAQsFADBsMQswCQYDVQQGEwJY | |
2 | WDELMAkGA1UECAwCWFgxCzAJBgNVBAcMAlhYMRMwEQYDVQQKDApQdXJwbGUgSTJQ | |
3 | MQ0wCwYDVQQLDARJMlBEMR8wHQYJKoZIhvcNAQkBFhBvcmlnbmFsQG1haWwuaTJw | |
4 | MB4XDTE1MDIyMjEzNTgxOFoXDTI1MDIxOTEzNTgxOFowbDELMAkGA1UEBhMCWFgx | |
5 | CzAJBgNVBAgMAlhYMQswCQYDVQQHDAJYWDETMBEGA1UECgwKUHVycGxlIEkyUDEN | |
6 | MAsGA1UECwwESTJQRDEfMB0GCSqGSIb3DQEJARYQb3JpZ25hbEBtYWlsLmkycDCC | |
7 | AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALp3D/gdvFjrMm+IE8tHZCWE | |
8 | hQ6Pp0CCgCGDBC3WQFLqR98bqVPl4UwRG/MKY/LY7Woai06JNmGcpfw0LMoNnHxT | |
9 | bvKtDRe/8kQdhdLHhgIkWKSbMvTAl7uUdV6FzsPgDR0x7scoFVWEhkF0wfmzGF2V | |
10 | yr/WCBQejFPu69z03m5tRQ8Xjp2txWV45RawUmFu50bgbZvLCSLfTkIvxmfJzgPN | |
11 | pJ3sPa/g7TBZl2uEiAu4uaEKvTuuzStOWCGgFaHYFVlTfFXTvmhFMqHfaidtzrlu | |
12 | H35WGrmIWTDl6uGPC5QkSppvkj73rDj5aEyPzWMz5DN3YeECoVSchN+OJJCM6m7+ | |
13 | rLFYXghVEp2h+T9O1GBRfcHlQ2E3CrWWvxhmK8dfteJmd501dyNX2paeuIg/aPFO | |
14 | 54/8m2r11uyF29hgY8VWLdXtqvwhKuK36PCzofEwDp9QQX8GRsEV4pZTrn4bDhGo | |
15 | kb9BF7TZTqtL3uyiRmIyBXrNNiYlA1Xm4fyKRtxl0mrPaUXdgdnCt3KxOAJ8WM2B | |
16 | 7L/kk9U8C/nexHbMxIZfTap49XcUg5dxSO9kOBosIOcCUms8sAzBPDV2tWAByhYF | |
17 | jI/Tutbd3F0+fvcmTcIFOlGbOxKgO2SfwXjv/44g/3LMK6IAMFB9UOc8KhnnJP0f | |
18 | uAHvMXn1ahRs4pM1VizLAgMBAAEwDQYJKoZIhvcNAQELBQADggIBAIOxdaXT+wfu | |
19 | nv/+1hy5T4TlRMNNsuj79ROcy6Mp+JwMG50HjTc0qTlXh8C7nHybDJn4v7DA+Nyn | |
20 | RxT0J5I+Gqn+Na9TaC9mLeX/lwe8/KomyhBWxjrsyWj1V6v/cLO924S2rtcfzMDm | |
21 | l3SFh9YHM1KF/R9N1XYBwtMzr3bupWDnE1yycYp1F4sMLr5SMzMQ0svQpQEM2/y5 | |
22 | kly8+eUzryhm+ag9x1686uEG5gxhQ1eHQoZEaClHUOsV+28+d5If7cqcYx9Hf5Tt | |
23 | CiVjJQzdxBF+6GeiJtKxnLtevqlkbyIJt6Cm9/7YIy/ovRGF2AKSYN6oCwmZQ6i1 | |
24 | 8nRnFq5zE7O94m+GXconWZxy0wVqA6472HThMi7S+Tk/eLYen2ilGY+KCb9a0FH5 | |
25 | 5MOuWSoJZ8/HfW2VeQmL8EjhWm5F2ybg28wgXK4BOGR3jQi03Fsc+AFidnWxSKo0 | |
26 | aiJoPgOsfyu8/fnCcAi07kSmjzUKIWskApgcpGQLNXHFK9mtg7+VA8esRnfLlKtP | |
27 | tJf+nNAPY1sqHfGBzh7WWGWal5RGHF5nEm3ta3oiFF5sMKCJ6C87zVwFkEcRytGC | |
28 | xOGmiG1O1RPrO5NG7rZUaQ4y1OKl2Y1H+nGONzZ3mvoAOvxEq6JtUnU2kZscpPlk | |
29 | fpeOSDoGBYJGbIpzDreBDhxaZrwGq36k | |
30 | -----END CERTIFICATE----- |
24 | 24 | # 1. install deps, clone and build. |
25 | 25 | # 2. strip binaries. |
26 | 26 | # 3. Purge all dependencies and other unrelated packages, including build directory. |
27 | RUN apk --no-cache --virtual build-dependendencies add make gcc g++ libtool zlib-dev boost-dev build-base openssl-dev openssl git \ | |
27 | RUN apk update \ | |
28 | && apk --no-cache --virtual build-dependendencies add make gcc g++ libtool zlib-dev boost-dev build-base openssl-dev openssl miniupnpc-dev git \ | |
28 | 29 | && mkdir -p /tmp/build \ |
29 | 30 | && cd /tmp/build && git clone -b ${GIT_BRANCH} ${REPO_URL} \ |
30 | 31 | && cd i2pd \ |
31 | 32 | && if [ -n "${GIT_TAG}" ]; then git checkout tags/${GIT_TAG}; fi \ |
32 | && make \ | |
33 | && make USE_UPNP=yes \ | |
33 | 34 | && cp -R contrib/certificates /i2pd_certificates \ |
34 | 35 | && mkdir -p /usr/local/bin \ |
35 | 36 | && mv i2pd /usr/local/bin \ |
36 | 37 | && cd /usr/local/bin \ |
37 | 38 | && strip i2pd \ |
38 | 39 | && rm -fr /tmp/build && apk --no-cache --purge del build-dependendencies build-base fortify-headers boost-dev zlib-dev openssl-dev \ |
39 | boost-python3 python3 gdbm boost-unit_test_framework linux-headers boost-prg_exec_monitor \ | |
40 | miniupnpc-dev boost-python3 python3 gdbm boost-unit_test_framework linux-headers boost-prg_exec_monitor \ | |
40 | 41 | boost-serialization boost-wave boost-wserialization boost-math boost-graph boost-regex git pcre2 \ |
41 | 42 | libtool g++ gcc |
42 | 43 | |
43 | 44 | # 2. Adding required libraries to run i2pd to ensure it will run. |
44 | RUN apk --no-cache add boost-filesystem boost-system boost-program_options boost-date_time boost-thread boost-iostreams openssl musl-utils libstdc++ | |
45 | RUN apk --no-cache add boost-filesystem boost-system boost-program_options boost-date_time boost-thread boost-iostreams openssl miniupnpc musl-utils libstdc++ | |
45 | 46 | |
46 | 47 | COPY entrypoint.sh /entrypoint.sh |
47 | 48 | RUN chmod a+x /entrypoint.sh |
0 | # i2pd | |
1 | # Copyright (C) 2021 PurpleI2P team | |
2 | # This file is distributed under the same license as the i2pd package. | |
3 | # R4SAS <r4sas@i2pmail.org>, 2021. | |
4 | # | |
5 | msgid "" | |
6 | msgstr "" | |
7 | "Project-Id-Version: i2pd\n" | |
8 | "Report-Msgid-Bugs-To: https://github.com/PurpleI2P/i2pd/issues\n" | |
9 | "POT-Creation-Date: 2021-08-06 17:12\n" | |
10 | "MIME-Version: 1.0\n" | |
11 | "Content-Type: text/plain; charset=UTF-8\n" | |
12 | "Content-Transfer-Encoding: 8bit\n" | |
13 | "X-Generator: Poedit 3.0\n" | |
14 | "X-Poedit-SourceCharset: UTF-8\n" | |
15 | "X-Poedit-Basepath: .\n" | |
16 | "X-Poedit-KeywordsList: ;tr\n" | |
17 | "X-Poedit-SearchPath-0: daemon/HTTPServer.cpp\n" | |
18 | "X-Poedit-SearchPath-1: libi2pd_client/HTTPProxy.cpp\n" | |
19 | ||
20 | #: daemon/HTTPServer.cpp:177 | |
21 | msgid "day" | |
22 | msgid_plural "days" | |
23 | msgstr[0] "" | |
24 | msgstr[1] "" | |
25 | ||
26 | #: daemon/HTTPServer.cpp:181 | |
27 | msgid "hour" | |
28 | msgid_plural "hours" | |
29 | msgstr[0] "" | |
30 | msgstr[1] "" | |
31 | ||
32 | #: daemon/HTTPServer.cpp:185 | |
33 | msgid "minute" | |
34 | msgid_plural "minutes" | |
35 | msgstr[0] "" | |
36 | msgstr[1] "" | |
37 | ||
38 | #: daemon/HTTPServer.cpp:188 | |
39 | msgid "second" | |
40 | msgid_plural "seconds" | |
41 | msgstr[0] "" | |
42 | msgstr[1] "" | |
43 | ||
44 | #. tr: Kibibit | |
45 | #: daemon/HTTPServer.cpp:196 daemon/HTTPServer.cpp:224 | |
46 | msgid "KiB" | |
47 | msgstr "" | |
48 | ||
49 | #. tr: Mebibit | |
50 | #: daemon/HTTPServer.cpp:198 | |
51 | msgid "MiB" | |
52 | msgstr "" | |
53 | ||
54 | #. tr: Gibibit | |
55 | #: daemon/HTTPServer.cpp:200 | |
56 | msgid "GiB" | |
57 | msgstr "" | |
58 | ||
59 | #: daemon/HTTPServer.cpp:217 | |
60 | msgid "building" | |
61 | msgstr "" | |
62 | ||
63 | #: daemon/HTTPServer.cpp:218 | |
64 | msgid "failed" | |
65 | msgstr "" | |
66 | ||
67 | #: daemon/HTTPServer.cpp:219 | |
68 | msgid "expiring" | |
69 | msgstr "" | |
70 | ||
71 | #: daemon/HTTPServer.cpp:220 | |
72 | msgid "established" | |
73 | msgstr "" | |
74 | ||
75 | #: daemon/HTTPServer.cpp:221 | |
76 | msgid "unknown" | |
77 | msgstr "" | |
78 | ||
79 | #: daemon/HTTPServer.cpp:223 | |
80 | msgid "exploratory" | |
81 | msgstr "" | |
82 | ||
83 | #: daemon/HTTPServer.cpp:259 | |
84 | msgid "<b>i2pd</b> webconsole" | |
85 | msgstr "" | |
86 | ||
87 | #: daemon/HTTPServer.cpp:262 | |
88 | msgid "Main page" | |
89 | msgstr "" | |
90 | ||
91 | #: daemon/HTTPServer.cpp:263 daemon/HTTPServer.cpp:725 | |
92 | msgid "Router commands" | |
93 | msgstr "" | |
94 | ||
95 | #: daemon/HTTPServer.cpp:264 daemon/HTTPServer.cpp:448 | |
96 | #: daemon/HTTPServer.cpp:460 | |
97 | msgid "Local Destinations" | |
98 | msgstr "" | |
99 | ||
100 | #: daemon/HTTPServer.cpp:266 daemon/HTTPServer.cpp:418 | |
101 | #: daemon/HTTPServer.cpp:504 daemon/HTTPServer.cpp:510 | |
102 | #: daemon/HTTPServer.cpp:641 daemon/HTTPServer.cpp:684 | |
103 | #: daemon/HTTPServer.cpp:688 | |
104 | msgid "LeaseSets" | |
105 | msgstr "" | |
106 | ||
107 | #: daemon/HTTPServer.cpp:268 daemon/HTTPServer.cpp:694 | |
108 | msgid "Tunnels" | |
109 | msgstr "" | |
110 | ||
111 | #: daemon/HTTPServer.cpp:269 daemon/HTTPServer.cpp:425 | |
112 | #: daemon/HTTPServer.cpp:787 daemon/HTTPServer.cpp:803 | |
113 | msgid "Transit Tunnels" | |
114 | msgstr "" | |
115 | ||
116 | #: daemon/HTTPServer.cpp:270 daemon/HTTPServer.cpp:852 | |
117 | msgid "Transports" | |
118 | msgstr "" | |
119 | ||
120 | #: daemon/HTTPServer.cpp:271 | |
121 | msgid "I2P tunnels" | |
122 | msgstr "" | |
123 | ||
124 | #: daemon/HTTPServer.cpp:273 daemon/HTTPServer.cpp:914 | |
125 | #: daemon/HTTPServer.cpp:924 | |
126 | msgid "SAM sessions" | |
127 | msgstr "" | |
128 | ||
129 | #: daemon/HTTPServer.cpp:289 daemon/HTTPServer.cpp:1306 | |
130 | #: daemon/HTTPServer.cpp:1309 daemon/HTTPServer.cpp:1312 | |
131 | #: daemon/HTTPServer.cpp:1326 daemon/HTTPServer.cpp:1371 | |
132 | #: daemon/HTTPServer.cpp:1374 daemon/HTTPServer.cpp:1377 | |
133 | msgid "ERROR" | |
134 | msgstr "" | |
135 | ||
136 | #: daemon/HTTPServer.cpp:296 | |
137 | msgid "OK" | |
138 | msgstr "" | |
139 | ||
140 | #: daemon/HTTPServer.cpp:297 | |
141 | msgid "Testing" | |
142 | msgstr "" | |
143 | ||
144 | #: daemon/HTTPServer.cpp:298 | |
145 | msgid "Firewalled" | |
146 | msgstr "" | |
147 | ||
148 | #: daemon/HTTPServer.cpp:299 daemon/HTTPServer.cpp:320 | |
149 | #: daemon/HTTPServer.cpp:406 | |
150 | msgid "Unknown" | |
151 | msgstr "" | |
152 | ||
153 | #: daemon/HTTPServer.cpp:300 daemon/HTTPServer.cpp:435 | |
154 | #: daemon/HTTPServer.cpp:436 daemon/HTTPServer.cpp:982 | |
155 | #: daemon/HTTPServer.cpp:991 | |
156 | msgid "Proxy" | |
157 | msgstr "" | |
158 | ||
159 | #: daemon/HTTPServer.cpp:301 | |
160 | msgid "Mesh" | |
161 | msgstr "" | |
162 | ||
163 | #: daemon/HTTPServer.cpp:304 | |
164 | msgid "Error" | |
165 | msgstr "" | |
166 | ||
167 | #: daemon/HTTPServer.cpp:308 | |
168 | msgid "Clock skew" | |
169 | msgstr "" | |
170 | ||
171 | #: daemon/HTTPServer.cpp:311 | |
172 | msgid "Offline" | |
173 | msgstr "" | |
174 | ||
175 | #: daemon/HTTPServer.cpp:314 | |
176 | msgid "Symmetric NAT" | |
177 | msgstr "" | |
178 | ||
179 | #: daemon/HTTPServer.cpp:326 | |
180 | msgid "Uptime" | |
181 | msgstr "" | |
182 | ||
183 | #: daemon/HTTPServer.cpp:329 | |
184 | msgid "Network status" | |
185 | msgstr "" | |
186 | ||
187 | #: daemon/HTTPServer.cpp:334 | |
188 | msgid "Network status v6" | |
189 | msgstr "" | |
190 | ||
191 | #: daemon/HTTPServer.cpp:340 daemon/HTTPServer.cpp:347 | |
192 | msgid "Stopping in" | |
193 | msgstr "" | |
194 | ||
195 | #: daemon/HTTPServer.cpp:354 | |
196 | msgid "Family" | |
197 | msgstr "" | |
198 | ||
199 | #: daemon/HTTPServer.cpp:355 | |
200 | msgid "Tunnel creation success rate" | |
201 | msgstr "" | |
202 | ||
203 | #: daemon/HTTPServer.cpp:356 | |
204 | msgid "Received" | |
205 | msgstr "" | |
206 | ||
207 | #. tr: Kibibit/s | |
208 | #: daemon/HTTPServer.cpp:358 daemon/HTTPServer.cpp:361 | |
209 | #: daemon/HTTPServer.cpp:364 | |
210 | msgid "KiB/s" | |
211 | msgstr "" | |
212 | ||
213 | #: daemon/HTTPServer.cpp:359 | |
214 | msgid "Sent" | |
215 | msgstr "" | |
216 | ||
217 | #: daemon/HTTPServer.cpp:362 | |
218 | msgid "Transit" | |
219 | msgstr "" | |
220 | ||
221 | #: daemon/HTTPServer.cpp:365 | |
222 | msgid "Data path" | |
223 | msgstr "" | |
224 | ||
225 | #: daemon/HTTPServer.cpp:368 | |
226 | msgid "Hidden content. Press on text to see." | |
227 | msgstr "" | |
228 | ||
229 | #: daemon/HTTPServer.cpp:371 | |
230 | msgid "Router Ident" | |
231 | msgstr "" | |
232 | ||
233 | #: daemon/HTTPServer.cpp:373 | |
234 | msgid "Router Family" | |
235 | msgstr "" | |
236 | ||
237 | #: daemon/HTTPServer.cpp:374 | |
238 | msgid "Router Caps" | |
239 | msgstr "" | |
240 | ||
241 | #: daemon/HTTPServer.cpp:375 | |
242 | msgid "Version" | |
243 | msgstr "" | |
244 | ||
245 | #: daemon/HTTPServer.cpp:376 | |
246 | msgid "Our external address" | |
247 | msgstr "" | |
248 | ||
249 | #: daemon/HTTPServer.cpp:384 | |
250 | msgid "supported" | |
251 | msgstr "" | |
252 | ||
253 | #: daemon/HTTPServer.cpp:416 | |
254 | msgid "Routers" | |
255 | msgstr "" | |
256 | ||
257 | #: daemon/HTTPServer.cpp:417 | |
258 | msgid "Floodfills" | |
259 | msgstr "" | |
260 | ||
261 | #: daemon/HTTPServer.cpp:424 daemon/HTTPServer.cpp:968 | |
262 | msgid "Client Tunnels" | |
263 | msgstr "" | |
264 | ||
265 | #: daemon/HTTPServer.cpp:434 | |
266 | msgid "Services" | |
267 | msgstr "" | |
268 | ||
269 | #: daemon/HTTPServer.cpp:435 daemon/HTTPServer.cpp:436 | |
270 | #: daemon/HTTPServer.cpp:437 daemon/HTTPServer.cpp:438 | |
271 | #: daemon/HTTPServer.cpp:439 daemon/HTTPServer.cpp:440 | |
272 | msgid "Enabled" | |
273 | msgstr "" | |
274 | ||
275 | #: daemon/HTTPServer.cpp:435 daemon/HTTPServer.cpp:436 | |
276 | #: daemon/HTTPServer.cpp:437 daemon/HTTPServer.cpp:438 | |
277 | #: daemon/HTTPServer.cpp:439 daemon/HTTPServer.cpp:440 | |
278 | msgid "Disabled" | |
279 | msgstr "" | |
280 | ||
281 | #: daemon/HTTPServer.cpp:483 | |
282 | msgid "Encrypted B33 address" | |
283 | msgstr "" | |
284 | ||
285 | #: daemon/HTTPServer.cpp:492 | |
286 | msgid "Address registration line" | |
287 | msgstr "" | |
288 | ||
289 | #: daemon/HTTPServer.cpp:497 | |
290 | msgid "Domain" | |
291 | msgstr "" | |
292 | ||
293 | #: daemon/HTTPServer.cpp:498 | |
294 | msgid "Generate" | |
295 | msgstr "" | |
296 | ||
297 | #: daemon/HTTPServer.cpp:499 | |
298 | msgid "" | |
299 | "<b>Note:</b> result string can be used only for registering 2LD domains " | |
300 | "(example.i2p). For registering subdomains please use i2pd-tools." | |
301 | msgstr "" | |
302 | ||
303 | #: daemon/HTTPServer.cpp:505 | |
304 | msgid "Address" | |
305 | msgstr "" | |
306 | ||
307 | #: daemon/HTTPServer.cpp:505 | |
308 | msgid "Type" | |
309 | msgstr "" | |
310 | ||
311 | #: daemon/HTTPServer.cpp:505 | |
312 | msgid "EncType" | |
313 | msgstr "" | |
314 | ||
315 | #: daemon/HTTPServer.cpp:515 daemon/HTTPServer.cpp:699 | |
316 | msgid "Inbound tunnels" | |
317 | msgstr "" | |
318 | ||
319 | #. tr: Milliseconds | |
320 | #: daemon/HTTPServer.cpp:520 daemon/HTTPServer.cpp:530 | |
321 | #: daemon/HTTPServer.cpp:704 daemon/HTTPServer.cpp:714 | |
322 | msgid "ms" | |
323 | msgstr "" | |
324 | ||
325 | #: daemon/HTTPServer.cpp:525 daemon/HTTPServer.cpp:709 | |
326 | msgid "Outbound tunnels" | |
327 | msgstr "" | |
328 | ||
329 | #: daemon/HTTPServer.cpp:537 | |
330 | msgid "Tags" | |
331 | msgstr "" | |
332 | ||
333 | #: daemon/HTTPServer.cpp:537 | |
334 | msgid "Incoming" | |
335 | msgstr "" | |
336 | ||
337 | #: daemon/HTTPServer.cpp:544 daemon/HTTPServer.cpp:547 | |
338 | msgid "Outgoing" | |
339 | msgstr "" | |
340 | ||
341 | #: daemon/HTTPServer.cpp:545 daemon/HTTPServer.cpp:561 | |
342 | msgid "Destination" | |
343 | msgstr "" | |
344 | ||
345 | #: daemon/HTTPServer.cpp:545 | |
346 | msgid "Amount" | |
347 | msgstr "" | |
348 | ||
349 | #: daemon/HTTPServer.cpp:552 | |
350 | msgid "Incoming Tags" | |
351 | msgstr "" | |
352 | ||
353 | #: daemon/HTTPServer.cpp:560 daemon/HTTPServer.cpp:563 | |
354 | msgid "Tags sessions" | |
355 | msgstr "" | |
356 | ||
357 | #: daemon/HTTPServer.cpp:561 | |
358 | msgid "Status" | |
359 | msgstr "" | |
360 | ||
361 | #: daemon/HTTPServer.cpp:570 daemon/HTTPServer.cpp:626 | |
362 | msgid "Local Destination" | |
363 | msgstr "" | |
364 | ||
365 | #: daemon/HTTPServer.cpp:580 daemon/HTTPServer.cpp:947 | |
366 | msgid "Streams" | |
367 | msgstr "" | |
368 | ||
369 | #: daemon/HTTPServer.cpp:602 | |
370 | msgid "Close stream" | |
371 | msgstr "" | |
372 | ||
373 | #: daemon/HTTPServer.cpp:631 | |
374 | msgid "I2CP session not found" | |
375 | msgstr "" | |
376 | ||
377 | #: daemon/HTTPServer.cpp:634 | |
378 | msgid "I2CP is not enabled" | |
379 | msgstr "" | |
380 | ||
381 | #: daemon/HTTPServer.cpp:660 | |
382 | msgid "Invalid" | |
383 | msgstr "" | |
384 | ||
385 | #: daemon/HTTPServer.cpp:663 | |
386 | msgid "Store type" | |
387 | msgstr "" | |
388 | ||
389 | #: daemon/HTTPServer.cpp:664 | |
390 | msgid "Expires" | |
391 | msgstr "" | |
392 | ||
393 | #: daemon/HTTPServer.cpp:669 | |
394 | msgid "Non Expired Leases" | |
395 | msgstr "" | |
396 | ||
397 | #: daemon/HTTPServer.cpp:672 | |
398 | msgid "Gateway" | |
399 | msgstr "" | |
400 | ||
401 | #: daemon/HTTPServer.cpp:673 | |
402 | msgid "TunnelID" | |
403 | msgstr "" | |
404 | ||
405 | #: daemon/HTTPServer.cpp:674 | |
406 | msgid "EndDate" | |
407 | msgstr "" | |
408 | ||
409 | #: daemon/HTTPServer.cpp:684 | |
410 | msgid "not floodfill" | |
411 | msgstr "" | |
412 | ||
413 | #: daemon/HTTPServer.cpp:695 | |
414 | msgid "Queue size" | |
415 | msgstr "" | |
416 | ||
417 | #: daemon/HTTPServer.cpp:726 | |
418 | msgid "Run peer test" | |
419 | msgstr "" | |
420 | ||
421 | #: daemon/HTTPServer.cpp:731 | |
422 | msgid "Decline transit tunnels" | |
423 | msgstr "" | |
424 | ||
425 | #: daemon/HTTPServer.cpp:733 | |
426 | msgid "Accept transit tunnels" | |
427 | msgstr "" | |
428 | ||
429 | #: daemon/HTTPServer.cpp:737 daemon/HTTPServer.cpp:742 | |
430 | msgid "Cancel graceful shutdown" | |
431 | msgstr "" | |
432 | ||
433 | #: daemon/HTTPServer.cpp:739 daemon/HTTPServer.cpp:744 | |
434 | msgid "Start graceful shutdown" | |
435 | msgstr "" | |
436 | ||
437 | #: daemon/HTTPServer.cpp:747 | |
438 | msgid "Force shutdown" | |
439 | msgstr "" | |
440 | ||
441 | #: daemon/HTTPServer.cpp:748 | |
442 | msgid "Reload external CSS styles" | |
443 | msgstr "" | |
444 | ||
445 | #: daemon/HTTPServer.cpp:751 | |
446 | msgid "" | |
447 | "<b>Note:</b> any action done here are not persistent and not changes your " | |
448 | "config files." | |
449 | msgstr "" | |
450 | ||
451 | #: daemon/HTTPServer.cpp:753 | |
452 | msgid "Logging level" | |
453 | msgstr "" | |
454 | ||
455 | #: daemon/HTTPServer.cpp:761 | |
456 | msgid "Transit tunnels limit" | |
457 | msgstr "" | |
458 | ||
459 | #: daemon/HTTPServer.cpp:766 daemon/HTTPServer.cpp:778 | |
460 | msgid "Change" | |
461 | msgstr "" | |
462 | ||
463 | #: daemon/HTTPServer.cpp:770 | |
464 | msgid "Change language" | |
465 | msgstr "" | |
466 | ||
467 | #: daemon/HTTPServer.cpp:803 | |
468 | msgid "no transit tunnels currently built" | |
469 | msgstr "" | |
470 | ||
471 | #: daemon/HTTPServer.cpp:908 daemon/HTTPServer.cpp:931 | |
472 | msgid "SAM disabled" | |
473 | msgstr "" | |
474 | ||
475 | #: daemon/HTTPServer.cpp:924 | |
476 | msgid "no sessions currently running" | |
477 | msgstr "" | |
478 | ||
479 | #: daemon/HTTPServer.cpp:937 | |
480 | msgid "SAM session not found" | |
481 | msgstr "" | |
482 | ||
483 | #: daemon/HTTPServer.cpp:942 | |
484 | msgid "SAM Session" | |
485 | msgstr "" | |
486 | ||
487 | #: daemon/HTTPServer.cpp:999 | |
488 | msgid "Server Tunnels" | |
489 | msgstr "" | |
490 | ||
491 | #: daemon/HTTPServer.cpp:1015 | |
492 | msgid "Client Forwards" | |
493 | msgstr "" | |
494 | ||
495 | #: daemon/HTTPServer.cpp:1029 | |
496 | msgid "Server Forwards" | |
497 | msgstr "" | |
498 | ||
499 | #: daemon/HTTPServer.cpp:1227 | |
500 | msgid "Unknown page" | |
501 | msgstr "" | |
502 | ||
503 | #: daemon/HTTPServer.cpp:1246 | |
504 | msgid "Invalid token" | |
505 | msgstr "" | |
506 | ||
507 | #: daemon/HTTPServer.cpp:1304 daemon/HTTPServer.cpp:1361 | |
508 | #: daemon/HTTPServer.cpp:1401 | |
509 | msgid "SUCCESS" | |
510 | msgstr "" | |
511 | ||
512 | #: daemon/HTTPServer.cpp:1304 | |
513 | msgid "Stream closed" | |
514 | msgstr "" | |
515 | ||
516 | #: daemon/HTTPServer.cpp:1306 | |
517 | msgid "Stream not found or already was closed" | |
518 | msgstr "" | |
519 | ||
520 | #: daemon/HTTPServer.cpp:1309 | |
521 | msgid "Destination not found" | |
522 | msgstr "" | |
523 | ||
524 | #: daemon/HTTPServer.cpp:1312 | |
525 | msgid "StreamID can't be null" | |
526 | msgstr "" | |
527 | ||
528 | #: daemon/HTTPServer.cpp:1314 daemon/HTTPServer.cpp:1379 | |
529 | msgid "Return to destination page" | |
530 | msgstr "" | |
531 | ||
532 | #: daemon/HTTPServer.cpp:1315 daemon/HTTPServer.cpp:1328 | |
533 | #: daemon/HTTPServer.cpp:1403 | |
534 | msgid "You will be redirected in 5 seconds" | |
535 | msgstr "" | |
536 | ||
537 | #: daemon/HTTPServer.cpp:1326 | |
538 | msgid "Transit tunnels count must not exceed 65535" | |
539 | msgstr "" | |
540 | ||
541 | #: daemon/HTTPServer.cpp:1327 daemon/HTTPServer.cpp:1402 | |
542 | msgid "Back to commands list" | |
543 | msgstr "" | |
544 | ||
545 | #: daemon/HTTPServer.cpp:1363 | |
546 | msgid "Register at reg.i2p" | |
547 | msgstr "" | |
548 | ||
549 | #: daemon/HTTPServer.cpp:1364 | |
550 | msgid "Description" | |
551 | msgstr "" | |
552 | ||
553 | #: daemon/HTTPServer.cpp:1364 | |
554 | msgid "A bit information about service on domain" | |
555 | msgstr "" | |
556 | ||
557 | #: daemon/HTTPServer.cpp:1365 | |
558 | msgid "Submit" | |
559 | msgstr "" | |
560 | ||
561 | #: daemon/HTTPServer.cpp:1371 | |
562 | msgid "Domain can't end with .b32.i2p" | |
563 | msgstr "" | |
564 | ||
565 | #: daemon/HTTPServer.cpp:1374 | |
566 | msgid "Domain must end with .i2p" | |
567 | msgstr "" | |
568 | ||
569 | #: daemon/HTTPServer.cpp:1377 | |
570 | msgid "Such destination is not found" | |
571 | msgstr "" | |
572 | ||
573 | #: daemon/HTTPServer.cpp:1397 | |
574 | msgid "Unknown command" | |
575 | msgstr "" | |
576 | ||
577 | #: daemon/HTTPServer.cpp:1401 | |
578 | msgid "Command accepted" | |
579 | msgstr "" | |
580 | ||
581 | #: libi2pd_client/HTTPProxy.cpp:157 | |
582 | msgid "Proxy error" | |
583 | msgstr "" | |
584 | ||
585 | #: libi2pd_client/HTTPProxy.cpp:165 | |
586 | msgid "Proxy info" | |
587 | msgstr "" | |
588 | ||
589 | #: libi2pd_client/HTTPProxy.cpp:173 | |
590 | msgid "Proxy error: Host not found" | |
591 | msgstr "" | |
592 | ||
593 | #: libi2pd_client/HTTPProxy.cpp:174 | |
594 | msgid "Remote host not found in router's addressbook" | |
595 | msgstr "" | |
596 | ||
597 | #: libi2pd_client/HTTPProxy.cpp:175 | |
598 | msgid "You may try to find this host on jump services below" | |
599 | msgstr "" | |
600 | ||
601 | #: libi2pd_client/HTTPProxy.cpp:273 libi2pd_client/HTTPProxy.cpp:288 | |
602 | #: libi2pd_client/HTTPProxy.cpp:322 libi2pd_client/HTTPProxy.cpp:365 | |
603 | msgid "Invalid request" | |
604 | msgstr "" | |
605 | ||
606 | #: libi2pd_client/HTTPProxy.cpp:273 | |
607 | msgid "Proxy unable to parse your request" | |
608 | msgstr "" | |
609 | ||
610 | #: libi2pd_client/HTTPProxy.cpp:288 | |
611 | msgid "addresshelper is not supported" | |
612 | msgstr "" | |
613 | ||
614 | #: libi2pd_client/HTTPProxy.cpp:297 libi2pd_client/HTTPProxy.cpp:306 | |
615 | #: libi2pd_client/HTTPProxy.cpp:385 | |
616 | msgid "Host" | |
617 | msgstr "" | |
618 | ||
619 | #: libi2pd_client/HTTPProxy.cpp:297 | |
620 | msgid "added to router's addressbook from helper" | |
621 | msgstr "" | |
622 | ||
623 | #: libi2pd_client/HTTPProxy.cpp:298 | |
624 | msgid "Click here to proceed:" | |
625 | msgstr "" | |
626 | ||
627 | #: libi2pd_client/HTTPProxy.cpp:298 libi2pd_client/HTTPProxy.cpp:308 | |
628 | msgid "Continue" | |
629 | msgstr "" | |
630 | ||
631 | #: libi2pd_client/HTTPProxy.cpp:299 libi2pd_client/HTTPProxy.cpp:309 | |
632 | msgid "Addresshelper found" | |
633 | msgstr "" | |
634 | ||
635 | #: libi2pd_client/HTTPProxy.cpp:306 | |
636 | msgid "already in router's addressbook" | |
637 | msgstr "" | |
638 | ||
639 | #: libi2pd_client/HTTPProxy.cpp:307 | |
640 | msgid "Click here to update record:" | |
641 | msgstr "" | |
642 | ||
643 | #: libi2pd_client/HTTPProxy.cpp:322 | |
644 | msgid "invalid request uri" | |
645 | msgstr "" | |
646 | ||
647 | #: libi2pd_client/HTTPProxy.cpp:365 | |
648 | msgid "Can't detect destination host from request" | |
649 | msgstr "" | |
650 | ||
651 | #: libi2pd_client/HTTPProxy.cpp:382 libi2pd_client/HTTPProxy.cpp:386 | |
652 | msgid "Outproxy failure" | |
653 | msgstr "" | |
654 | ||
655 | #: libi2pd_client/HTTPProxy.cpp:382 | |
656 | msgid "bad outproxy settings" | |
657 | msgstr "" | |
658 | ||
659 | #: libi2pd_client/HTTPProxy.cpp:385 | |
660 | msgid "not inside I2P network, but outproxy is not enabled" | |
661 | msgstr "" | |
662 | ||
663 | #: libi2pd_client/HTTPProxy.cpp:474 | |
664 | msgid "unknown outproxy url" | |
665 | msgstr "" | |
666 | ||
667 | #: libi2pd_client/HTTPProxy.cpp:480 | |
668 | msgid "cannot resolve upstream proxy" | |
669 | msgstr "" | |
670 | ||
671 | #: libi2pd_client/HTTPProxy.cpp:488 | |
672 | msgid "hostname too long" | |
673 | msgstr "" | |
674 | ||
675 | #: libi2pd_client/HTTPProxy.cpp:515 | |
676 | msgid "cannot connect to upstream socks proxy" | |
677 | msgstr "" | |
678 | ||
679 | #: libi2pd_client/HTTPProxy.cpp:521 | |
680 | msgid "Cannot negotiate with socks proxy" | |
681 | msgstr "" | |
682 | ||
683 | #: libi2pd_client/HTTPProxy.cpp:563 | |
684 | msgid "CONNECT error" | |
685 | msgstr "" | |
686 | ||
687 | #: libi2pd_client/HTTPProxy.cpp:563 | |
688 | msgid "Failed to Connect" | |
689 | msgstr "" | |
690 | ||
691 | #: libi2pd_client/HTTPProxy.cpp:574 libi2pd_client/HTTPProxy.cpp:600 | |
692 | msgid "socks proxy error" | |
693 | msgstr "" | |
694 | ||
695 | #: libi2pd_client/HTTPProxy.cpp:582 | |
696 | msgid "failed to send request to upstream" | |
697 | msgstr "" | |
698 | ||
699 | #: libi2pd_client/HTTPProxy.cpp:603 | |
700 | msgid "No Reply From socks proxy" | |
701 | msgstr "" | |
702 | ||
703 | #: libi2pd_client/HTTPProxy.cpp:610 | |
704 | msgid "cannot connect" | |
705 | msgstr "" | |
706 | ||
707 | #: libi2pd_client/HTTPProxy.cpp:610 | |
708 | msgid "http out proxy not implemented" | |
709 | msgstr "" | |
710 | ||
711 | #: libi2pd_client/HTTPProxy.cpp:611 | |
712 | msgid "cannot connect to upstream http proxy" | |
713 | msgstr "" | |
714 | ||
715 | #: libi2pd_client/HTTPProxy.cpp:644 | |
716 | msgid "Host is down" | |
717 | msgstr "" | |
718 | ||
719 | #: libi2pd_client/HTTPProxy.cpp:644 | |
720 | msgid "" | |
721 | "Can't create connection to requested host, it may be down. Please try again " | |
722 | "later." | |
723 | msgstr "" |
0 | `xgettext` command for extracting translation | |
1 | --- | |
2 | ||
3 | ``` | |
4 | xgettext --omit-header -ctr: -ktr -ktr:1,2 daemon/HTTPServer.cpp libi2pd_client/HTTPProxy.cpp | |
5 | ``` | |
6 | ||
7 | Regex for transforming gettext translations to our format: | |
8 | --- | |
9 | ||
10 | ``` | |
11 | in: msgid\ \"(.*)\"\nmsgid_plural\ \"(.*)\"\nmsgstr\[0\]\ \"(.*)\"\nmsgstr\[1\]\ \"(.*)\"\n(msgstr\[2\]\ \"(.*)\"\n)?(msgstr\[3\]\ \"(.*)\"\n)?(msgstr\[4\]\ \"(.*)\"\n)?(msgstr\[5\]\ \"(.*)\"\n)? | |
12 | out: #{"$2", {"$3", "$4", "$6", "$8", "$10"}},\n | |
13 | ``` | |
14 | ||
15 | ``` | |
16 | in: msgid\ \"(.*)\"\nmsgstr\ \"(.*)\"\n | |
17 | out: {"$1", "$2"},\n | |
18 | ``` | |
19 | ||
20 | ``` | |
21 | in: ^#[:.](.*)$\n | |
22 | out: <to empty line> | |
23 | ``` | |
24 | ||
25 | ``` | |
26 | in: \n\n | |
27 | out: \n | |
28 | ``` |
13 | 13 | ## Use that path to store separated tunnels in different config files. |
14 | 14 | ## Default: ~/.i2pd/tunnels.d or /var/lib/i2pd/tunnels.d |
15 | 15 | # tunnelsdir = /var/lib/i2pd/tunnels.d |
16 | ||
17 | ## Path to certificates used for verifying .su3, families | |
18 | ## Default: ~/.i2pd/certificates or /var/lib/i2pd/certificates | |
19 | # certsdir = /var/lib/i2pd/certificates | |
16 | 20 | |
17 | 21 | ## Where to write pidfile (default: i2pd.pid, not used in Windows) |
18 | 22 | # pidfile = /run/i2pd.pid |
102 | 106 | # auth = true |
103 | 107 | # user = i2pd |
104 | 108 | # pass = changeme |
109 | ## Select webconsole language | |
110 | ## Currently supported english (default), afrikaans, russian, turkmen, ukrainian and uzbek languages | |
111 | # lang = english | |
105 | 112 | |
106 | 113 | [httpproxy] |
107 | 114 | ## Uncomment and set to 'false' to disable HTTP Proxy |
0 | #!/sbin/openrc-run | |
1 | ||
2 | pidfile="/var/run/i2pd/i2pd.pid" | |
3 | logfile="/var/log/i2pd/i2pd.log" | |
4 | mainconf="/etc/i2pd/i2pd.conf" | |
5 | tunconf="/etc/i2pd/tunnels.conf" | |
6 | tundir="/etc/i2pd/tunnels.conf.d" | |
7 | ||
8 | name="i2pd" | |
9 | command="/usr/sbin/i2pd" | |
10 | command_args="--service --daemon --log=file --logfile=$logfile --conf=$mainconf --tunconf=$tunconf --tunnelsdir=$tundir --pidfile=$pidfile" | |
11 | description="i2p router written in C++" | |
12 | required_dirs="/var/lib/i2pd" | |
13 | required_files="$mainconf" | |
14 | start_stop_daemon_args="--chuid i2pd" | |
15 | ||
16 | depend() { | |
17 | need mountall | |
18 | use net | |
19 | after bootmisc | |
20 | } | |
21 | ||
22 | start_pre() { | |
23 | if [ -r /etc/default/i2pd ]; then | |
24 | . /etc/default/i2pd | |
25 | fi | |
26 | ||
27 | if [ "x$I2PD_ENABLED" != "xyes" ]; then | |
28 | ewarn "i2pd disabled in /etc/default/i2pd" | |
29 | exit 1 | |
30 | fi | |
31 | ||
32 | checkpath -f -o i2pd:adm $logfile | |
33 | checkpath -f -o i2pd:adm $pidfile | |
34 | ||
35 | if [ -n "$DAEMON_OPTS" ]; then | |
36 | command_args="$command_args $DAEMON_OPTS" | |
37 | fi | |
38 | } |
0 | 0 | %define git_hash %(git rev-parse HEAD | cut -c -7) |
1 | 1 | |
2 | 2 | Name: i2pd-git |
3 | Version: 2.38.0 | |
3 | Version: 2.39.0 | |
4 | 4 | Release: git%{git_hash}%{?dist} |
5 | 5 | Summary: I2P router written in C++ |
6 | 6 | Conflicts: i2pd |
55 | 55 | %endif |
56 | 56 | %endif |
57 | 57 | |
58 | ||
59 | %if 0%{?fedora} >= 36 | |
60 | pushd redhat-linux-build | |
61 | %else | |
58 | 62 | %if 0%{?fedora} >= 33 |
59 | 63 | pushd %{_target_platform} |
60 | 64 | %endif |
65 | %endif | |
61 | 66 | |
62 | 67 | %if 0%{?mageia} > 7 |
63 | 68 | pushd build |
76 | 81 | %install |
77 | 82 | pushd build |
78 | 83 | |
84 | %if 0%{?fedora} >= 36 | |
85 | pushd redhat-linux-build | |
86 | %else | |
79 | 87 | %if 0%{?fedora} >= 33 |
80 | 88 | pushd %{_target_platform} |
89 | %endif | |
81 | 90 | %endif |
82 | 91 | |
83 | 92 | %if 0%{?mageia} |
136 | 145 | |
137 | 146 | |
138 | 147 | %changelog |
148 | * Mon Aug 23 2021 orignal <i2porignal@yandex.ru> - 2.39.0 | |
149 | - update to 2.39.0 | |
150 | - fixed build on fedora 36 | |
151 | ||
139 | 152 | * Mon May 17 2021 orignal <i2porignal@yandex.ru> - 2.38.0 |
140 | 153 | - update to 2.38.0 |
141 | 154 |
0 | 0 | Name: i2pd |
1 | Version: 2.38.0 | |
1 | Version: 2.39.0 | |
2 | 2 | Release: 1%{?dist} |
3 | 3 | Summary: I2P router written in C++ |
4 | 4 | Conflicts: i2pd-git |
53 | 53 | %endif |
54 | 54 | %endif |
55 | 55 | |
56 | %if 0%{?fedora} >= 36 | |
57 | pushd redhat-linux-build | |
58 | %else | |
56 | 59 | %if 0%{?fedora} >= 33 |
57 | 60 | pushd %{_target_platform} |
58 | 61 | %endif |
62 | %endif | |
59 | 63 | |
60 | 64 | %if 0%{?mageia} > 7 |
61 | 65 | pushd build |
74 | 78 | %install |
75 | 79 | pushd build |
76 | 80 | |
81 | %if 0%{?fedora} >= 36 | |
82 | pushd redhat-linux-build | |
83 | %else | |
77 | 84 | %if 0%{?fedora} >= 33 |
78 | 85 | pushd %{_target_platform} |
86 | %endif | |
79 | 87 | %endif |
80 | 88 | |
81 | 89 | %if 0%{?mageia} |
134 | 142 | |
135 | 143 | |
136 | 144 | %changelog |
145 | * Mon Aug 23 2021 orignal <i2porignal@yandex.ru> - 2.39.0 | |
146 | - update to 2.39.0 | |
147 | - fixed build on fedora 36 | |
148 | ||
137 | 149 | * Mon May 17 2021 orignal <i2porignal@yandex.ru> - 2.38.0 |
138 | 150 | - update to 2.38.0 |
139 | 151 |
0 | [IRC-IRC2P] | |
0 | [IRC-ILITA] | |
1 | 1 | type = client |
2 | 2 | address = 127.0.0.1 |
3 | 3 | port = 6668 |
4 | destination = irc.postman.i2p | |
4 | destination = irc.ilita.i2p | |
5 | 5 | destinationport = 6667 |
6 | 6 | keys = irc-keys.dat |
7 | 7 | |
8 | #[IRC-ILITA] | |
8 | #[IRC-IRC2P] | |
9 | 9 | #type = client |
10 | 10 | #address = 127.0.0.1 |
11 | 11 | #port = 6669 |
12 | #destination = irc.ilita.i2p | |
12 | #destination = irc.postman.i2p | |
13 | 13 | #destinationport = 6667 |
14 | 14 | #keys = irc-keys.dat |
15 | 15 |
0 | description "i2p client daemon" | |
1 | ||
2 | start on runlevel [2345] | |
3 | stop on runlevel [016] or unmounting-filesystem | |
4 | ||
5 | # these can be overridden in /etc/init/i2pd.override | |
6 | env LOGFILE="/var/log/i2pd/i2pd.log" | |
7 | ||
8 | expect fork | |
9 | ||
10 | exec /usr/sbin/i2pd --daemon --service --log=file --logfile=$LOGFILE |
0 | body { | |
1 | font: 100%/1.5em sans-serif; | |
2 | margin: 0; | |
3 | padding: 1.5em; | |
4 | background: #FAFAFA; | |
5 | color: #103456; | |
6 | } | |
7 | ||
8 | a, .slide label { | |
9 | text-decoration: none; | |
10 | color: #894C84; | |
11 | } | |
12 | ||
13 | a:hover, .slide label:hover { | |
14 | color: #FAFAFA; | |
15 | background: #894C84; | |
16 | } | |
17 | ||
18 | a.button { | |
19 | -webkit-appearance: button; | |
20 | -moz-appearance: button; | |
21 | appearance: button; | |
22 | text-decoration: none; | |
23 | padding: 0 5px; | |
24 | border: 1px solid #894C84; | |
25 | } | |
26 | ||
27 | .header { | |
28 | font-size: 2.5em; | |
29 | text-align: center; | |
30 | margin: 1em 0; | |
31 | color: #894C84; | |
32 | } | |
33 | ||
34 | .wrapper { | |
35 | margin: 0 auto; | |
36 | padding: 1em; | |
37 | max-width: 64em; | |
38 | } | |
39 | ||
40 | .menu { | |
41 | display: block; | |
42 | float: left; | |
43 | overflow: hidden; | |
44 | max-width: 12em; | |
45 | white-space: nowrap; | |
46 | text-overflow: ellipsis; | |
47 | } | |
48 | ||
49 | .listitem { | |
50 | display: block; | |
51 | font-family: monospace; | |
52 | font-size: 1.2em; | |
53 | white-space: nowrap; | |
54 | } | |
55 | ||
56 | .tableitem { | |
57 | font-family: monospace; | |
58 | font-size: 1.2em; | |
59 | white-space: nowrap; | |
60 | } | |
61 | ||
62 | .content { | |
63 | float: left; | |
64 | font-size: 1em; | |
65 | margin-left: 4em; | |
66 | max-width: 48em; | |
67 | overflow: auto; | |
68 | } | |
69 | ||
70 | .tunnel.established { | |
71 | color: #56B734; | |
72 | } | |
73 | ||
74 | .tunnel.expiring { | |
75 | color: #D3AE3F; | |
76 | } | |
77 | ||
78 | .tunnel.failed { | |
79 | color: #D33F3F; | |
80 | } | |
81 | ||
82 | .tunnel.building { | |
83 | color: #434343; | |
84 | } | |
85 | ||
86 | caption { | |
87 | font-size: 1.5em; | |
88 | text-align: center; | |
89 | color: #894C84; | |
90 | } | |
91 | ||
92 | table { | |
93 | display: table; | |
94 | border-collapse: collapse; | |
95 | text-align: center; | |
96 | } | |
97 | ||
98 | table.extaddr { | |
99 | text-align: left; | |
100 | } | |
101 | ||
102 | table.services { | |
103 | width: 100%; | |
104 | } | |
105 | ||
106 | textarea { | |
107 | word-break: break-all; | |
108 | } | |
109 | ||
110 | .streamdest { | |
111 | width: 120px; | |
112 | max-width: 240px; | |
113 | overflow: hidden; | |
114 | text-overflow: ellipsis; | |
115 | } | |
116 | ||
117 | .slide div.slidecontent, .slide [type="checkbox"] { | |
118 | display: none; | |
119 | } | |
120 | ||
121 | .slide [type="checkbox"]:checked ~ div.slidecontent { | |
122 | display: block; | |
123 | margin-top: 0; | |
124 | padding: 0; | |
125 | } | |
126 | ||
127 | .disabled { | |
128 | color: #D33F3F; | |
129 | } | |
130 | ||
131 | .enabled { | |
132 | color: #56B734; | |
133 | } | |
134 | ||
135 | @media screen and (max-width: 1150px) { /* adaptive style */ | |
136 | .wrapper { | |
137 | max-width: 58em; | |
138 | } | |
139 | ||
140 | .menu { | |
141 | max-width: 10em; | |
142 | } | |
143 | ||
144 | .content { | |
145 | margin-left: 2em; | |
146 | max-width: 42em; | |
147 | } | |
148 | } | |
149 | ||
150 | @media screen and (max-width: 980px) { | |
151 | body { | |
152 | padding: 1.5em 0 0 0; | |
153 | } | |
154 | ||
155 | .menu { | |
156 | width: 100%; | |
157 | max-width: unset; | |
158 | display: block; | |
159 | float: none; | |
160 | position: unset; | |
161 | font-size: 16px; | |
162 | text-align: center; | |
163 | } | |
164 | ||
165 | .menu a, .commands a { | |
166 | display: inline-block; | |
167 | padding: 4px; | |
168 | } | |
169 | ||
170 | .content { | |
171 | float: none; | |
172 | margin-left: unset; | |
173 | margin-top: 16px; | |
174 | max-width: 100%; | |
175 | width: 100%; | |
176 | text-align: center; | |
177 | } | |
178 | ||
179 | a, .slide label { | |
180 | /* margin-right: 10px; */ | |
181 | display: block; | |
182 | /* font-size: 18px; */ | |
183 | } | |
184 | ||
185 | .header { | |
186 | margin: unset; | |
187 | font-size: 1.5em; | |
188 | } | |
189 | ||
190 | small { | |
191 | display: block | |
192 | } | |
193 | ||
194 | a.button { | |
195 | -webkit-appearance: button; | |
196 | -moz-appearance: button; | |
197 | appearance: button; | |
198 | text-decoration: none; | |
199 | margin-top: 10px; | |
200 | padding: 6px; | |
201 | border: 1px solid #894c84; | |
202 | width: -webkit-fill-available; | |
203 | } | |
204 | ||
205 | input, select { | |
206 | width: 35%; | |
207 | text-align: center; | |
208 | padding: 5px; | |
209 | border: 2px solid #ccc; | |
210 | -webkit-border-radius: 5px; | |
211 | border-radius: 5px; | |
212 | font-size: 18px; | |
213 | } | |
214 | ||
215 | table.extaddr { | |
216 | margin: auto; | |
217 | text-align: unset; | |
218 | } | |
219 | ||
220 | textarea { | |
221 | width: -webkit-fill-available; | |
222 | height: auto; | |
223 | padding:5px; | |
224 | border:2px solid #ccc; | |
225 | -webkit-border-radius: 5px; | |
226 | border-radius: 5px; | |
227 | font-size: 12px; | |
228 | } | |
229 | ||
230 | button[type=submit] { | |
231 | padding: 5px 15px; | |
232 | background: #ccc; | |
233 | border: 0 none; | |
234 | cursor: pointer; | |
235 | -webkit-border-radius: 5px; | |
236 | border-radius: 5px; | |
237 | position: relative; | |
238 | height: 36px; | |
239 | display: -webkit-inline-box; | |
240 | margin-top: 10px; | |
241 | } | |
242 | }⏎ |
31 | 31 | #include "UPnP.h" |
32 | 32 | #include "Timestamp.h" |
33 | 33 | #include "util.h" |
34 | #include "I18N.h" | |
34 | 35 | |
35 | 36 | namespace i2p |
36 | 37 | { |
91 | 92 | i2p::config::Finalize(); |
92 | 93 | |
93 | 94 | i2p::config::GetOption("daemon", isDaemon); |
95 | ||
96 | std::string certsdir; i2p::config::GetOption("certsdir", certsdir); | |
97 | i2p::fs::SetCertsDir(certsdir); | |
98 | ||
99 | certsdir = i2p::fs::GetCertsDir(); | |
94 | 100 | |
95 | 101 | std::string logs = ""; i2p::config::GetOption("log", logs); |
96 | 102 | std::string logfile = ""; i2p::config::GetOption("logfile", logfile); |
130 | 136 | LogPrint(eLogNone, "i2pd v", VERSION, " starting"); |
131 | 137 | LogPrint(eLogDebug, "FS: main config file: ", config); |
132 | 138 | LogPrint(eLogDebug, "FS: data directory: ", datadir); |
139 | LogPrint(eLogDebug, "FS: certificates directory: ", certsdir); | |
133 | 140 | |
134 | 141 | bool precomputation; i2p::config::GetOption("precomputation.elgamal", precomputation); |
135 | 142 | bool aesni; i2p::config::GetOption("cpuext.aesni", aesni); |
342 | 349 | LogPrint(eLogInfo, "Daemon: using hidden mode"); |
343 | 350 | i2p::data::netdb.SetHidden(true); |
344 | 351 | } |
352 | ||
353 | std::string httpLang; i2p::config::GetOption("http.lang", httpLang); | |
354 | i2p::i18n::SetLanguage(httpLang); | |
355 | ||
345 | 356 | return true; |
346 | 357 | } |
347 | 358 |
29 | 29 | #include "Daemon.h" |
30 | 30 | #include "util.h" |
31 | 31 | #include "ECIESX25519AEADRatchetSession.h" |
32 | #include "I18N.h" | |
32 | 33 | |
33 | 34 | #ifdef WIN32_APP |
34 | 35 | #include "Win32App.h" |
39 | 40 | |
40 | 41 | namespace i2p { |
41 | 42 | namespace http { |
42 | const char *itoopieFavicon = | |
43 | const std::string itoopieFavicon = | |
43 | 44 | "data:image/png;base64," |
44 | 45 | "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACx" |
45 | 46 | "jwv8YQUAAAAJcEhZcwAALiIAAC4iAari3ZIAAAAHdElNRQfgCQsUNSZrkhi1AAAAGXRFWHRTb2Z0" |
57 | 58 | "JHYnlIsfzJjIp9xZKswL5YKBHL+coKJoRDaUSzoozxHVrygQU4JykQADAwAT5b1NHtwZugAAAABJ" |
58 | 59 | "RU5ErkJggg=="; |
59 | 60 | |
60 | const char *cssStyles = | |
61 | // Bundled style | |
62 | const std::string internalCSS = | |
61 | 63 | "<style>\r\n" |
62 | 64 | " body { font: 100%/1.5em sans-serif; margin: 0; padding: 1.5em; background: #FAFAFA; color: #103456; }\r\n" |
63 | 65 | " a, .slide label { text-decoration: none; color: #894C84; }\r\n" |
64 | 66 | " a:hover, .slide label:hover { color: #FAFAFA; background: #894C84; }\r\n" |
65 | 67 | " a.button { -webkit-appearance: button; -moz-appearance: button; appearance: button; text-decoration: none;\r\n" |
66 | " color: initial; padding: 0 5px; border: 1px solid #894C84; }\r\n" | |
68 | " padding: 0 5px; border: 1px solid #894C84; }\r\n" | |
67 | 69 | " .header { font-size: 2.5em; text-align: center; margin: 1em 0; color: #894C84; }\r\n" |
68 | " .wrapper { margin: 0 auto; padding: 1em; max-width: 58em; }\r\n" | |
69 | " .menu { float: left; } .menu a, .commands a { display: block; }\r\n" | |
70 | " .wrapper { margin: 0 auto; padding: 1em; max-width: 64em; }\r\n" | |
71 | " .menu { display: block; float: left; overflow: hidden; max-width: 12em; white-space: nowrap; text-overflow: ellipsis; }\r\n" | |
70 | 72 | " .listitem { display: block; font-family: monospace; font-size: 1.2em; white-space: nowrap; }\r\n" |
71 | 73 | " .tableitem { font-family: monospace; font-size: 1.2em; white-space: nowrap; }\r\n" |
72 | " .content { float: left; font-size: 1em; margin-left: 4em; max-width: 45em; overflow: auto; }\r\n" | |
74 | " .content { float: left; font-size: 1em; margin-left: 4em; max-width: 48em; overflow: auto; }\r\n" | |
73 | 75 | " .tunnel.established { color: #56B734; } .tunnel.expiring { color: #D3AE3F; }\r\n" |
74 | 76 | " .tunnel.failed { color: #D33F3F; } .tunnel.building { color: #434343; }\r\n" |
75 | 77 | " caption { font-size: 1.5em; text-align: center; color: #894C84; }\r\n" |
79 | 81 | " .streamdest { width: 120px; max-width: 240px; overflow: hidden; text-overflow: ellipsis;}\r\n" |
80 | 82 | " .slide div.slidecontent, .slide [type=\"checkbox\"] { display: none; }\r\n" |
81 | 83 | " .slide [type=\"checkbox\"]:checked ~ div.slidecontent { display: block; margin-top: 0; padding: 0; }\r\n" |
82 | " .disabled:after { color: #D33F3F; content: \"Disabled\" }\r\n" | |
83 | " .enabled:after { color: #56B734; content: \"Enabled\" }\r\n" | |
84 | " @media screen and (max-width: 980px) {\r\n" /* adaptive style */ | |
84 | " .disabled { color: #D33F3F; } .enabled { color: #56B734; }\r\n" | |
85 | " @media screen and (max-width: 1150px) {\r\n" /* adaptive style */ | |
86 | " .wrapper { max-width: 58em; } .menu { max-width: 10em; }\r\n" | |
87 | " .content { margin-left: 2em; max-width: 42em; }\r\n" | |
88 | " }\r\n" | |
89 | " @media screen and (max-width: 980px) {\r\n" | |
85 | 90 | " body { padding: 1.5em 0 0 0; }\r\n" |
86 | " .menu { width: 100%; display: block; float: none; position: unset; font-size: 16px;\r\n" | |
91 | " .menu { width: 100%; max-width: unset; display: block; float: none; position: unset; font-size: 16px;\r\n" | |
87 | 92 | " text-align: center; }\r\n" |
88 | " .menu a, .commands a { padding: 2px; }\r\n" | |
93 | " .menu a, .commands a { display: inline-block; padding: 4px; }\r\n" | |
89 | 94 | " .content { float: none; margin-left: unset; margin-top: 16px; max-width: 100%; width: 100%;\r\n" |
90 | 95 | " text-align: center; }\r\n" |
91 | 96 | " a, .slide label { /* margin-right: 10px; */ display: block; /* font-size: 18px; */ }\r\n" |
92 | 97 | " .header { margin: unset; font-size: 1.5em; } small {display: block}\r\n" |
93 | 98 | " a.button { -webkit-appearance: button; -moz-appearance: button; appearance: button; text-decoration: none;\r\n" |
94 | " color: initial; margin-top: 10px; padding: 6px; border: 1px solid #894c84; width: -webkit-fill-available; }\r\n" | |
95 | " input { width: 35%; text-align: center; padding: 5px;\r\n" | |
99 | " margin-top: 10px; padding: 6px; border: 1px solid #894c84; width: -webkit-fill-available; }\r\n" | |
100 | " input, select { width: 35%; text-align: center; padding: 5px;\r\n" | |
96 | 101 | " border: 2px solid #ccc; -webkit-border-radius: 5px; border-radius: 5px; font-size: 18px; }\r\n" |
102 | " table.extaddr { margin: auto; text-align: unset; }\r\n" | |
97 | 103 | " textarea { width: -webkit-fill-available; height: auto; padding:5px; border:2px solid #ccc;\r\n" |
98 | 104 | " -webkit-border-radius: 5px; border-radius: 5px; font-size: 12px; }\r\n" |
99 | 105 | " button[type=submit] { padding: 5px 15px; background: #ccc; border: 0 none; cursor: pointer;\r\n" |
100 | 106 | " -webkit-border-radius: 5px; border-radius: 5px; position: relative; height: 36px; display: -webkit-inline-box; margin-top: 10px; }\r\n" |
101 | " }\r\n" /* adaptive style */ | |
107 | " }\r\n" | |
102 | 108 | "</style>\r\n"; |
109 | ||
110 | // for external style sheet | |
111 | std::string externalCSS; | |
112 | ||
113 | static void LoadExtCSS () | |
114 | { | |
115 | std::stringstream s; | |
116 | std::string styleFile = i2p::fs::DataDirPath ("webconsole/style.css"); | |
117 | if (i2p::fs::Exists(styleFile)) { | |
118 | std::ifstream f(styleFile, std::ifstream::binary); | |
119 | s << f.rdbuf(); | |
120 | externalCSS = s.str(); | |
121 | } else if (externalCSS.length() != 0) { // clean up external style if file was removed | |
122 | externalCSS = ""; | |
123 | } | |
124 | } | |
125 | ||
126 | static void GetStyles (std::stringstream& s) | |
127 | { | |
128 | if (externalCSS.length() != 0) | |
129 | s << "<style>\r\n" << externalCSS << "</style>\r\n"; | |
130 | else | |
131 | s << internalCSS; | |
132 | } | |
103 | 133 | |
104 | 134 | const char HTTP_PAGE_TUNNELS[] = "tunnels"; |
105 | 135 | const char HTTP_PAGE_TRANSIT_TUNNELS[] = "transit_tunnels"; |
123 | 153 | const char HTTP_COMMAND_KILLSTREAM[] = "closestream"; |
124 | 154 | const char HTTP_COMMAND_LIMITTRANSIT[] = "limittransit"; |
125 | 155 | const char HTTP_COMMAND_GET_REG_STRING[] = "get_reg_string"; |
156 | const char HTTP_COMMAND_SETLANGUAGE[] = "setlanguage"; | |
157 | const char HTTP_COMMAND_RELOAD_CSS[] = "reload_css"; | |
126 | 158 | const char HTTP_PARAM_SAM_SESSION_ID[] = "id"; |
127 | 159 | const char HTTP_PARAM_ADDRESS[] = "address"; |
128 | 160 | |
129 | static std::string ConvertTime (uint64_t time); | |
130 | std::map<uint32_t, uint32_t> HTTPConnection::m_Tokens; | |
161 | static std::string ConvertTime (uint64_t time) | |
162 | { | |
163 | lldiv_t divTime = lldiv(time, 1000); | |
164 | time_t t = divTime.quot; | |
165 | struct tm *tm = localtime(&t); | |
166 | char date[128]; | |
167 | 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); | |
168 | return date; | |
169 | } | |
131 | 170 | |
132 | 171 | static void ShowUptime (std::stringstream& s, int seconds) |
133 | 172 | { |
134 | 173 | int num; |
135 | 174 | |
136 | 175 | if ((num = seconds / 86400) > 0) { |
137 | s << num << " days, "; | |
176 | s << num << " " << tr("day", "days", num) << ", "; | |
138 | 177 | seconds -= num * 86400; |
139 | 178 | } |
140 | 179 | if ((num = seconds / 3600) > 0) { |
141 | s << num << " hours, "; | |
180 | s << num << " " << tr("hour", "hours", num) << ", "; | |
142 | 181 | seconds -= num * 3600; |
143 | 182 | } |
144 | 183 | if ((num = seconds / 60) > 0) { |
145 | s << num << " min, "; | |
184 | s << num << " " << tr("minute", "minutes", num) << ", "; | |
146 | 185 | seconds -= num * 60; |
147 | 186 | } |
148 | s << seconds << " seconds"; | |
187 | s << seconds << " " << tr("second", "seconds", seconds); | |
149 | 188 | } |
150 | 189 | |
151 | 190 | static void ShowTraffic (std::stringstream& s, uint64_t bytes) |
153 | 192 | s << std::fixed << std::setprecision(2); |
154 | 193 | auto numKBytes = (double) bytes / 1024; |
155 | 194 | if (numKBytes < 1024) |
156 | s << numKBytes << " KiB"; | |
195 | s << numKBytes << " " << tr(/* tr: Kibibit */ "KiB"); | |
157 | 196 | else if (numKBytes < 1024 * 1024) |
158 | s << numKBytes / 1024 << " MiB"; | |
197 | s << numKBytes / 1024 << " " << tr(/* tr: Mebibit */ "MiB"); | |
159 | 198 | else |
160 | s << numKBytes / 1024 / 1024 << " GiB"; | |
199 | s << numKBytes / 1024 / 1024 << " " << tr(/* tr: Gibibit */ "GiB"); | |
161 | 200 | } |
162 | 201 | |
163 | 202 | static void ShowTunnelDetails (std::stringstream& s, enum i2p::tunnel::TunnelState eState, bool explr, int bytes) |
164 | 203 | { |
165 | std::string state; | |
204 | std::string state, stateText; | |
166 | 205 | switch (eState) { |
167 | 206 | case i2p::tunnel::eTunnelStateBuildReplyReceived : |
168 | case i2p::tunnel::eTunnelStatePending : state = "building"; break; | |
207 | case i2p::tunnel::eTunnelStatePending : state = "building"; break; | |
169 | 208 | case i2p::tunnel::eTunnelStateBuildFailed : |
170 | 209 | case i2p::tunnel::eTunnelStateTestFailed : |
171 | case i2p::tunnel::eTunnelStateFailed : state = "failed"; break; | |
172 | case i2p::tunnel::eTunnelStateExpiring : state = "expiring"; break; | |
210 | case i2p::tunnel::eTunnelStateFailed : state = "failed"; break; | |
211 | case i2p::tunnel::eTunnelStateExpiring : state = "expiring"; break; | |
173 | 212 | case i2p::tunnel::eTunnelStateEstablished : state = "established"; break; |
174 | 213 | default: state = "unknown"; break; |
175 | 214 | } |
176 | s << "<span class=\"tunnel " << state << "\"> " << state << ((explr) ? " (exploratory)" : "") << "</span>, "; | |
177 | s << " " << (int) (bytes / 1024) << " KiB\r\n"; | |
215 | ||
216 | if (state == "building") stateText = tr("building"); | |
217 | else if (state == "failed") stateText = tr("failed"); | |
218 | else if (state == "expiring") stateText = tr("expiring"); | |
219 | else if (state == "established") stateText = tr("established"); | |
220 | else stateText = tr("unknown"); | |
221 | ||
222 | s << "<span class=\"tunnel " << state << "\"> " << stateText << ((explr) ? " (" + tr("exploratory") + ")" : "") << "</span>, "; | |
223 | s << " " << (int) (bytes / 1024) << " " << tr(/* tr: Kibibit */ "KiB") << "\r\n"; | |
178 | 224 | } |
179 | 225 | |
180 | 226 | static void SetLogLevel (const std::string& level) |
190 | 236 | |
191 | 237 | static void ShowPageHead (std::stringstream& s) |
192 | 238 | { |
193 | std::string webroot; | |
194 | i2p::config::GetOption("http.webroot", webroot); | |
239 | std::string webroot; i2p::config::GetOption("http.webroot", webroot); | |
240 | ||
241 | // Page language | |
242 | std::string currLang = i2p::context.GetLanguage ()->GetLanguage(); // get current used language | |
243 | auto it = i2p::i18n::languages.find(currLang); | |
244 | std::string langCode = it->second.ShortCode; | |
245 | ||
195 | 246 | s << |
196 | 247 | "<!DOCTYPE html>\r\n" |
197 | "<html lang=\"en\">\r\n" /* TODO: Add support for locale */ | |
248 | "<html lang=\"" << langCode << "\">\r\n" | |
198 | 249 | " <head>\r\n" /* TODO: Find something to parse html/template system. This is horrible. */ |
199 | #if (!defined(WIN32)) | |
200 | 250 | " <meta charset=\"UTF-8\">\r\n" |
201 | #else | |
202 | " <meta charset=\"windows-1251\">\r\n" | |
203 | #endif | |
204 | 251 | " <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n" |
205 | 252 | " <link rel=\"shortcut icon\" href=\"" << itoopieFavicon << "\">\r\n" |
206 | " <title>Purple I2P " VERSION " Webconsole</title>\r\n" | |
207 | << cssStyles << | |
208 | "</head>\r\n"; | |
253 | " <title>Purple I2P " VERSION " Webconsole</title>\r\n"; | |
254 | GetStyles(s); | |
209 | 255 | s << |
256 | "</head>\r\n" | |
210 | 257 | "<body>\r\n" |
211 | "<div class=\"header\"><b>i2pd</b> webconsole</div>\r\n" | |
258 | "<div class=\"header\">" << tr("<b>i2pd</b> webconsole") << "</div>\r\n" | |
212 | 259 | "<div class=\"wrapper\">\r\n" |
213 | 260 | "<div class=\"menu\">\r\n" |
214 | " <a href=\"" << webroot << "\">Main page</a><br>\r\n" | |
215 | " <a href=\"" << webroot << "?page=" << HTTP_PAGE_COMMANDS << "\">Router commands</a>\r\n" | |
216 | " <a href=\"" << webroot << "?page=" << HTTP_PAGE_LOCAL_DESTINATIONS << "\">Local destinations</a>\r\n"; | |
261 | " <a href=\"" << webroot << "\">" << tr("Main page") << "</a><br><br>\r\n" | |
262 | " <a href=\"" << webroot << "?page=" << HTTP_PAGE_COMMANDS << "\">" << tr("Router commands") << "</a><br>\r\n" | |
263 | " <a href=\"" << webroot << "?page=" << HTTP_PAGE_LOCAL_DESTINATIONS << "\">" << tr("Local Destinations") << "</a><br>\r\n"; | |
217 | 264 | if (i2p::context.IsFloodfill ()) |
218 | s << " <a href=\"" << webroot << "?page=" << HTTP_PAGE_LEASESETS << "\">LeaseSets</a>\r\n"; | |
265 | s << " <a href=\"" << webroot << "?page=" << HTTP_PAGE_LEASESETS << "\">" << tr("LeaseSets") << "</a><br>\r\n"; | |
219 | 266 | s << |
220 | " <a href=\"" << webroot << "?page=" << HTTP_PAGE_TUNNELS << "\">Tunnels</a>\r\n" | |
221 | " <a href=\"" << webroot << "?page=" << HTTP_PAGE_TRANSIT_TUNNELS << "\">Transit tunnels</a>\r\n" | |
222 | " <a href=\"" << webroot << "?page=" << HTTP_PAGE_TRANSPORTS << "\">Transports</a>\r\n" | |
223 | " <a href=\"" << webroot << "?page=" << HTTP_PAGE_I2P_TUNNELS << "\">I2P tunnels</a>\r\n"; | |
267 | " <a href=\"" << webroot << "?page=" << HTTP_PAGE_TUNNELS << "\">" << tr("Tunnels") << "</a><br>\r\n" | |
268 | " <a href=\"" << webroot << "?page=" << HTTP_PAGE_TRANSIT_TUNNELS << "\">" << tr("Transit Tunnels") << "</a><br>\r\n" | |
269 | " <a href=\"" << webroot << "?page=" << HTTP_PAGE_TRANSPORTS << "\">" << tr ("Transports") << "</a><br>\r\n" | |
270 | " <a href=\"" << webroot << "?page=" << HTTP_PAGE_I2P_TUNNELS << "\">" << tr("I2P tunnels") << "</a><br>\r\n"; | |
224 | 271 | if (i2p::client::context.GetSAMBridge ()) |
225 | s << " <a href=\"" << webroot << "?page=" << HTTP_PAGE_SAM_SESSIONS << "\">SAM sessions</a>\r\n"; | |
272 | s << " <a href=\"" << webroot << "?page=" << HTTP_PAGE_SAM_SESSIONS << "\">" << tr("SAM sessions") << "</a><br>\r\n"; | |
226 | 273 | s << |
227 | 274 | "</div>\r\n" |
228 | 275 | "<div class=\"content\">"; |
238 | 285 | |
239 | 286 | static void ShowError(std::stringstream& s, const std::string& string) |
240 | 287 | { |
241 | s << "<b>ERROR:</b> " << string << "<br>\r\n"; | |
288 | s << "<b>" << tr("ERROR") << ":</b> " << string << "<br>\r\n"; | |
242 | 289 | } |
243 | 290 | |
244 | 291 | static void ShowNetworkStatus (std::stringstream& s, RouterStatus status) |
245 | 292 | { |
246 | 293 | switch (status) |
247 | 294 | { |
248 | case eRouterStatusOK: s << "OK"; break; | |
249 | case eRouterStatusTesting: s << "Testing"; break; | |
250 | case eRouterStatusFirewalled: s << "Firewalled"; break; | |
251 | case eRouterStatusUnknown: s << "Unknown"; break; | |
252 | case eRouterStatusProxy: s << "Proxy"; break; | |
253 | case eRouterStatusMesh: s << "Mesh"; break; | |
295 | case eRouterStatusOK: s << tr("OK"); break; | |
296 | case eRouterStatusTesting: s << tr("Testing"); break; | |
297 | case eRouterStatusFirewalled: s << tr("Firewalled"); break; | |
298 | case eRouterStatusUnknown: s << tr("Unknown"); break; | |
299 | case eRouterStatusProxy: s << tr("Proxy"); break; | |
300 | case eRouterStatusMesh: s << tr("Mesh"); break; | |
254 | 301 | case eRouterStatusError: |
255 | 302 | { |
256 | s << "Error"; | |
303 | s << tr("Error"); | |
257 | 304 | switch (i2p::context.GetError ()) |
258 | 305 | { |
259 | 306 | case eRouterErrorClockSkew: |
260 | s << " - Clock skew"; | |
307 | s << " - " << tr("Clock skew"); | |
261 | 308 | break; |
262 | 309 | case eRouterErrorOffline: |
263 | s << " - Offline"; | |
310 | s << " - " << tr("Offline"); | |
264 | 311 | break; |
265 | 312 | case eRouterErrorSymmetricNAT: |
266 | s << " - Symmetric NAT"; | |
313 | s << " - " << tr("Symmetric NAT"); | |
267 | 314 | break; |
268 | 315 | default: ; |
269 | 316 | } |
270 | 317 | break; |
271 | 318 | } |
272 | default: s << "Unknown"; | |
319 | default: s << tr("Unknown"); | |
273 | 320 | } |
274 | 321 | } |
275 | 322 | |
276 | 323 | void ShowStatus (std::stringstream& s, bool includeHiddenContent, i2p::http::OutputFormatEnum outputFormat) |
277 | 324 | { |
278 | s << "<b>Uptime:</b> "; | |
325 | s << "<b>" << tr("Uptime") << ":</b> "; | |
279 | 326 | ShowUptime(s, i2p::context.GetUptime ()); |
280 | 327 | s << "<br>\r\n"; |
281 | s << "<b>Network status:</b> "; | |
328 | s << "<b>" << tr("Network status") << ":</b> "; | |
282 | 329 | ShowNetworkStatus (s, i2p::context.GetStatus ()); |
283 | 330 | s << "<br>\r\n"; |
284 | 331 | if (i2p::context.SupportsV6 ()) |
285 | 332 | { |
286 | s << "<b>Network status 6:</b> "; | |
333 | s << "<b>" << tr("Network status v6") << ":</b> "; | |
287 | 334 | ShowNetworkStatus (s, i2p::context.GetStatusV6 ()); |
288 | 335 | s << "<br>\r\n"; |
289 | 336 | } |
290 | 337 | #if ((!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) || defined(ANDROID_BINARY)) |
291 | 338 | if (auto remains = Daemon.gracefulShutdownInterval) { |
292 | s << "<b>Stopping in:</b> "; | |
339 | s << "<b>" << tr("Stopping in") << ":</b> "; | |
293 | 340 | ShowUptime(s, remains); |
294 | 341 | s << "<br>\r\n"; |
295 | 342 | } |
296 | 343 | #elif defined(WIN32_APP) |
297 | 344 | if (i2p::win32::g_GracefulShutdownEndtime != 0) { |
298 | 345 | uint16_t remains = (i2p::win32::g_GracefulShutdownEndtime - GetTickCount()) / 1000; |
299 | s << "<b>Stopping in:</b> "; | |
346 | s << "<b>" << tr("Stopping in") << ":</b> "; | |
300 | 347 | ShowUptime(s, remains); |
301 | 348 | s << "<br>\r\n"; |
302 | 349 | } |
303 | 350 | #endif |
304 | 351 | auto family = i2p::context.GetFamily (); |
305 | 352 | if (family.length () > 0) |
306 | s << "<b>Family:</b> " << family << "<br>\r\n"; | |
307 | s << "<b>Tunnel creation success rate:</b> " << i2p::tunnel::tunnels.GetTunnelCreationSuccessRate () << "%<br>\r\n"; | |
308 | s << "<b>Received:</b> "; | |
353 | s << "<b>"<< tr("Family") << ":</b> " << family << "<br>\r\n"; | |
354 | s << "<b>" << tr("Tunnel creation success rate") << ":</b> " << i2p::tunnel::tunnels.GetTunnelCreationSuccessRate () << "%<br>\r\n"; | |
355 | s << "<b>" << tr("Received") << ":</b> "; | |
309 | 356 | ShowTraffic (s, i2p::transport::transports.GetTotalReceivedBytes ()); |
310 | s << " (" << (double) i2p::transport::transports.GetInBandwidth () / 1024 << " KiB/s)<br>\r\n"; | |
311 | s << "<b>Sent:</b> "; | |
357 | s << " (" << (double) i2p::transport::transports.GetInBandwidth () / 1024 << " " << tr(/* tr: Kibibit/s */ "KiB/s") << ")<br>\r\n"; | |
358 | s << "<b>" << tr("Sent") << ":</b> "; | |
312 | 359 | ShowTraffic (s, i2p::transport::transports.GetTotalSentBytes ()); |
313 | s << " (" << (double) i2p::transport::transports.GetOutBandwidth () / 1024 << " KiB/s)<br>\r\n"; | |
314 | s << "<b>Transit:</b> "; | |
360 | s << " (" << (double) i2p::transport::transports.GetOutBandwidth () / 1024 << " " << tr(/* tr: Kibibit/s */ "KiB/s") << ")<br>\r\n"; | |
361 | s << "<b>" << tr("Transit") << ":</b> "; | |
315 | 362 | ShowTraffic (s, i2p::transport::transports.GetTotalTransitTransmittedBytes ()); |
316 | s << " (" << (double) i2p::transport::transports.GetTransitBandwidth () / 1024 << " KiB/s)<br>\r\n"; | |
317 | s << "<b>Data path:</b> " << i2p::fs::GetDataDir() << "<br>\r\n"; | |
363 | s << " (" << (double) i2p::transport::transports.GetTransitBandwidth () / 1024 << " " << tr(/* tr: Kibibit/s */ "KiB/s") << ")<br>\r\n"; | |
364 | s << "<b>" << tr("Data path") << ":</b> " << i2p::fs::GetUTF8DataDir() << "<br>\r\n"; | |
318 | 365 | s << "<div class='slide'>"; |
319 | if((outputFormat==OutputFormatEnum::forWebConsole)||!includeHiddenContent) { | |
320 | s << "<label for=\"slide-info\">Hidden content. Press on text to see.</label>\r\n<input type=\"checkbox\" id=\"slide-info\" />\r\n<div class=\"slidecontent\">\r\n"; | |
366 | if((outputFormat == OutputFormatEnum::forWebConsole) || !includeHiddenContent) { | |
367 | s << "<label for=\"slide-info\">" << tr("Hidden content. Press on text to see.") << "</label>\r\n<input type=\"checkbox\" id=\"slide-info\" />\r\n<div class=\"slidecontent\">\r\n"; | |
321 | 368 | } |
322 | 369 | if(includeHiddenContent) { |
323 | s << "<b>Router Ident:</b> " << i2p::context.GetRouterInfo().GetIdentHashBase64() << "<br>\r\n"; | |
370 | s << "<b>" << tr("Router Ident") << ":</b> " << i2p::context.GetRouterInfo().GetIdentHashBase64() << "<br>\r\n"; | |
324 | 371 | if (!i2p::context.GetRouterInfo().GetProperty("family").empty()) |
325 | s << "<b>Router Family:</b> " << i2p::context.GetRouterInfo().GetProperty("family") << "<br>\r\n"; | |
326 | s << "<b>Router Caps:</b> " << i2p::context.GetRouterInfo().GetProperty("caps") << "<br>\r\n"; | |
327 | s << "<b>Version:</b> " VERSION "<br>\r\n"; | |
328 | s << "<b>Our external address:</b>" << "<br>\r\n<table class=\"extaddr\"><tbody>\r\n"; | |
372 | s << "<b>" << tr("Router Family") << ":</b> " << i2p::context.GetRouterInfo().GetProperty("family") << "<br>\r\n"; | |
373 | s << "<b>" << tr("Router Caps") << ":</b> " << i2p::context.GetRouterInfo().GetProperty("caps") << "<br>\r\n"; | |
374 | s << "<b>" << tr("Version") << ":</b> " VERSION "<br>\r\n"; | |
375 | s << "<b>"<< tr("Our external address") << ":</b>" << "<br>\r\n<table class=\"extaddr\"><tbody>\r\n"; | |
329 | 376 | for (const auto& address : i2p::context.GetRouterInfo().GetAddresses()) |
330 | 377 | { |
331 | 378 | s << "<tr>\r\n"; |
333 | 380 | { |
334 | 381 | s << "<td>NTCP2"; |
335 | 382 | if (address->host.is_v6 ()) s << "v6"; |
336 | s << "</td><td>supported</td>\r\n</tr>\r\n"; | |
383 | s << "</td><td>" << tr("supported") << "</td>\r\n</tr>\r\n"; | |
337 | 384 | continue; |
338 | 385 | } |
339 | 386 | switch (address->transportStyle) |
355 | 402 | break; |
356 | 403 | } |
357 | 404 | default: |
358 | s << "<td>Unknown</td>\r\n"; | |
405 | s << "<td>" << tr("Unknown") << "</td>\r\n"; | |
359 | 406 | } |
360 | 407 | s << "<td>" << address->host.to_string() << ":" << address->port << "</td>\r\n</tr>\r\n"; |
361 | 408 | } |
362 | 409 | s << "</tbody></table>\r\n"; |
363 | 410 | } |
364 | 411 | s << "</div>\r\n</div>\r\n"; |
365 | if(outputFormat==OutputFormatEnum::forQtUi) { | |
412 | if(outputFormat == OutputFormatEnum::forQtUi) { | |
366 | 413 | s << "<br>"; |
367 | 414 | } |
368 | s << "<b>Routers:</b> " << i2p::data::netdb.GetNumRouters () << " "; | |
369 | s << "<b>Floodfills:</b> " << i2p::data::netdb.GetNumFloodfills () << " "; | |
370 | s << "<b>LeaseSets:</b> " << i2p::data::netdb.GetNumLeaseSets () << "<br>\r\n"; | |
415 | s << "<b>" << tr("Routers") << ":</b> " << i2p::data::netdb.GetNumRouters () << " "; | |
416 | s << "<b>" << tr("Floodfills") << ":</b> " << i2p::data::netdb.GetNumFloodfills () << " "; | |
417 | s << "<b>" << tr("LeaseSets") << ":</b> " << i2p::data::netdb.GetNumLeaseSets () << "<br>\r\n"; | |
371 | 418 | |
372 | 419 | size_t clientTunnelCount = i2p::tunnel::tunnels.CountOutboundTunnels(); |
373 | 420 | clientTunnelCount += i2p::tunnel::tunnels.CountInboundTunnels(); |
374 | 421 | size_t transitTunnelCount = i2p::tunnel::tunnels.CountTransitTunnels(); |
375 | 422 | |
376 | s << "<b>Client Tunnels:</b> " << std::to_string(clientTunnelCount) << " "; | |
377 | s << "<b>Transit Tunnels:</b> " << std::to_string(transitTunnelCount) << "<br>\r\n<br>\r\n"; | |
423 | s << "<b>" << tr("Client Tunnels") << ":</b> " << std::to_string(clientTunnelCount) << " "; | |
424 | s << "<b>" << tr("Transit Tunnels") << ":</b> " << std::to_string(transitTunnelCount) << "<br>\r\n<br>\r\n"; | |
378 | 425 | |
379 | 426 | if(outputFormat==OutputFormatEnum::forWebConsole) { |
380 | bool i2pcontrol; i2p::config::GetOption("i2pcontrol.enabled", i2pcontrol); | |
381 | s << "<table class=\"services\"><caption>Services</caption><tbody>\r\n"; | |
382 | s << "<tr><td>" << "HTTP Proxy" << "</td><td><div class='" << ((i2p::client::context.GetHttpProxy ()) ? "enabled" : "disabled") << "'></div></td></tr>\r\n"; | |
383 | s << "<tr><td>" << "SOCKS Proxy" << "</td><td><div class='" << ((i2p::client::context.GetSocksProxy ()) ? "enabled" : "disabled") << "'></div></td></tr>\r\n"; | |
384 | s << "<tr><td>" << "BOB" << "</td><td><div class='" << ((i2p::client::context.GetBOBCommandChannel ()) ? "enabled" : "disabled") << "'></div></td></tr>\r\n"; | |
385 | s << "<tr><td>" << "SAM" << "</td><td><div class='" << ((i2p::client::context.GetSAMBridge ()) ? "enabled" : "disabled") << "'></div></td></tr>\r\n"; | |
386 | s << "<tr><td>" << "I2CP" << "</td><td><div class='" << ((i2p::client::context.GetI2CPServer ()) ? "enabled" : "disabled") << "'></div></td></tr>\r\n"; | |
387 | s << "<tr><td>" << "I2PControl" << "</td><td><div class='" << ((i2pcontrol) ? "enabled" : "disabled") << "'></div></td></tr>\r\n"; | |
427 | bool httpproxy = i2p::client::context.GetHttpProxy () ? true : false; | |
428 | bool socksproxy = i2p::client::context.GetSocksProxy () ? true : false; | |
429 | bool bob = i2p::client::context.GetBOBCommandChannel () ? true : false; | |
430 | bool sam = i2p::client::context.GetSAMBridge () ? true : false; | |
431 | bool i2cp = i2p::client::context.GetI2CPServer () ? true : false; | |
432 | bool i2pcontrol; i2p::config::GetOption("i2pcontrol.enabled", i2pcontrol); | |
433 | s << "<table class=\"services\"><caption>" << tr("Services") << "</caption><tbody>\r\n"; | |
434 | s << "<tr><td>" << "HTTP " << tr("Proxy") << "</td><td class='" << (httpproxy ? "enabled" : "disabled") << "'>" << (httpproxy ? tr("Enabled") : tr("Disabled")) << "</td></tr>\r\n"; | |
435 | s << "<tr><td>" << "SOCKS " << tr("Proxy") << "</td><td class='" << (socksproxy ? "enabled" : "disabled") << "'>" << (socksproxy ? tr("Enabled") : tr("Disabled")) << "</td></tr>\r\n"; | |
436 | s << "<tr><td>" << "BOB" << "</td><td class='" << (bob ? "enabled" : "disabled") << "'>" << (bob ? tr("Enabled") : tr("Disabled")) << "</td></tr>\r\n"; | |
437 | s << "<tr><td>" << "SAM" << "</td><td class='" << (sam ? "enabled" : "disabled") << "'>" << (sam ? tr("Enabled") : tr("Disabled")) << "</td></tr>\r\n"; | |
438 | s << "<tr><td>" << "I2CP" << "</td><td class='" << (i2cp ? "enabled" : "disabled") << "'>" << (i2cp ? tr("Enabled") : tr("Disabled")) << "</td></tr>\r\n"; | |
439 | s << "<tr><td>" << "I2PControl" << "</td><td class='" << (i2pcontrol ? "enabled" : "disabled") << "'>" << (i2pcontrol ? tr("Enabled") : tr("Disabled")) << "</td></tr>\r\n"; | |
388 | 440 | s << "</tbody></table>\r\n"; |
389 | 441 | } |
390 | 442 | } |
392 | 444 | void ShowLocalDestinations (std::stringstream& s) |
393 | 445 | { |
394 | 446 | std::string webroot; i2p::config::GetOption("http.webroot", webroot); |
395 | s << "<b>Local Destinations:</b><br>\r\n<div class=\"list\">\r\n"; | |
447 | s << "<b>" << tr("Local Destinations") << ":</b><br>\r\n<div class=\"list\">\r\n"; | |
396 | 448 | for (auto& it: i2p::client::context.GetDestinations ()) |
397 | 449 | { |
398 | 450 | auto ident = it.second->GetIdentHash (); |
404 | 456 | auto i2cpServer = i2p::client::context.GetI2CPServer (); |
405 | 457 | if (i2cpServer && !(i2cpServer->GetSessions ().empty ())) |
406 | 458 | { |
407 | s << "<br><b>I2CP Local Destinations:</b><br>\r\n<div class=\"list\">\r\n"; | |
459 | s << "<br><b>I2CP "<< tr("Local Destinations") << ":</b><br>\r\n<div class=\"list\">\r\n"; | |
408 | 460 | for (auto& it: i2cpServer->GetSessions ()) |
409 | 461 | { |
410 | 462 | auto dest = it.second->GetDestination (); |
427 | 479 | if (dest->IsEncryptedLeaseSet ()) |
428 | 480 | { |
429 | 481 | i2p::data::BlindedPublicKey blinded (dest->GetIdentity (), dest->IsPerClientAuth ()); |
430 | s << "<div class='slide'><label for='slide-b33'><b>Encrypted B33 address:</b></label>\r\n<input type=\"checkbox\" id=\"slide-b33\" />\r\n<div class=\"slidecontent\">\r\n"; | |
482 | s << "<div class='slide'><label for='slide-b33'><b>" << tr("Encrypted B33 address") << ":</b></label>\r\n<input type=\"checkbox\" id=\"slide-b33\" />\r\n<div class=\"slidecontent\">\r\n"; | |
431 | 483 | s << blinded.ToB33 () << ".b32.i2p<br>\r\n"; |
432 | 484 | s << "</div>\r\n</div>\r\n"; |
433 | 485 | } |
436 | 488 | { |
437 | 489 | std::string webroot; i2p::config::GetOption("http.webroot", webroot); |
438 | 490 | auto base32 = dest->GetIdentHash ().ToBase32 (); |
439 | s << "<div class='slide'><label for='slide-regaddr'><b>Address registration line</b></label>\r\n<input type=\"checkbox\" id=\"slide-regaddr\" />\r\n<div class=\"slidecontent\">\r\n" | |
491 | s << "<div class='slide'><label for='slide-regaddr'><b>" << tr("Address registration line") << "</b></label>\r\n<input type=\"checkbox\" id=\"slide-regaddr\" />\r\n<div class=\"slidecontent\">\r\n" | |
440 | 492 | "<form method=\"get\" action=\"" << webroot << "\">\r\n" |
441 | 493 | " <input type=\"hidden\" name=\"cmd\" value=\"" << HTTP_COMMAND_GET_REG_STRING << "\">\r\n" |
442 | 494 | " <input type=\"hidden\" name=\"token\" value=\"" << token << "\">\r\n" |
443 | 495 | " <input type=\"hidden\" name=\"b32\" value=\"" << base32 << "\">\r\n" |
444 | " <b>Domain:</b>\r\n<input type=\"text\" maxlength=\"67\" name=\"name\" placeholder=\"domain.i2p\" required>\r\n" | |
445 | " <button type=\"submit\">Generate</button>\r\n" | |
446 | "</form>\r\n<small><b>Note:</b> result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.</small>\r\n</div>\r\n</div>\r\n<br>\r\n"; | |
496 | " <b>" << tr("Domain") << ":</b>\r\n<input type=\"text\" maxlength=\"67\" name=\"name\" placeholder=\"domain.i2p\" required>\r\n" | |
497 | " <button type=\"submit\">" << tr("Generate") << "</button>\r\n" | |
498 | "</form>\r\n<small>" << tr("<b>Note:</b> result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.") << "</small>\r\n</div>\r\n</div>\r\n<br>\r\n"; | |
447 | 499 | } |
448 | 500 | |
449 | 501 | if(dest->GetNumRemoteLeaseSets()) |
450 | 502 | { |
451 | s << "<div class='slide'><label for='slide-lease'><b>LeaseSets:</b> <i>" << dest->GetNumRemoteLeaseSets () | |
452 | << "</i></label>\r\n<input type=\"checkbox\" id=\"slide-lease\" />\r\n<div class=\"slidecontent\">\r\n<table><thead><th>Address</th><th>Type</th><th>EncType</th></thead><tbody class=\"tableitem\">"; | |
503 | s << "<div class='slide'><label for='slide-lease'><b>" << tr("LeaseSets") << ":</b> <i>" << dest->GetNumRemoteLeaseSets () | |
504 | << "</i></label>\r\n<input type=\"checkbox\" id=\"slide-lease\" />\r\n<div class=\"slidecontent\">\r\n<table><thead><th>"<< tr("Address") << "</th><th>" << tr("Type") << "</th><th>" << tr("EncType") << "</th></thead><tbody class=\"tableitem\">"; | |
453 | 505 | for(auto& it: dest->GetLeaseSets ()) |
454 | 506 | s << "<tr><td>" << it.first.ToBase32 () << "</td><td>" << (int)it.second->GetStoreType () << "</td><td>" << (int)it.second->GetEncryptionType () <<"</td></tr>\r\n"; |
455 | 507 | s << "</tbody></table>\r\n</div>\r\n</div>\r\n<br>\r\n"; |
456 | 508 | } else |
457 | s << "<b>LeaseSets:</b> <i>0</i><br>\r\n<br>\r\n"; | |
509 | s << "<b>" << tr("LeaseSets") << ":</b> <i>0</i><br>\r\n<br>\r\n"; | |
458 | 510 | |
459 | 511 | auto pool = dest->GetTunnelPool (); |
460 | 512 | if (pool) |
461 | 513 | { |
462 | s << "<b>Inbound tunnels:</b><br>\r\n<div class=\"list\">\r\n"; | |
514 | s << "<b>" << tr("Inbound tunnels") << ":</b><br>\r\n<div class=\"list\">\r\n"; | |
463 | 515 | for (auto & it : pool->GetInboundTunnels ()) { |
464 | 516 | s << "<div class=\"listitem\">"; |
465 | 517 | it->Print(s); |
466 | 518 | if(it->LatencyIsKnown()) |
467 | s << " ( " << it->GetMeanLatency() << "ms )"; | |
519 | s << " ( " << it->GetMeanLatency() << tr(/* tr: Milliseconds */ "ms") << " )"; | |
468 | 520 | ShowTunnelDetails(s, it->GetState (), false, it->GetNumReceivedBytes ()); |
469 | 521 | s << "</div>\r\n"; |
470 | 522 | } |
471 | 523 | s << "<br>\r\n"; |
472 | s << "<b>Outbound tunnels:</b><br>\r\n<div class=\"list\">\r\n"; | |
524 | s << "<b>" << tr("Outbound tunnels") << ":</b><br>\r\n<div class=\"list\">\r\n"; | |
473 | 525 | for (auto & it : pool->GetOutboundTunnels ()) { |
474 | 526 | s << "<div class=\"listitem\">"; |
475 | 527 | it->Print(s); |
476 | 528 | if(it->LatencyIsKnown()) |
477 | s << " ( " << it->GetMeanLatency() << "ms )"; | |
529 | s << " ( " << it->GetMeanLatency() << tr("ms") << " )"; | |
478 | 530 | ShowTunnelDetails(s, it->GetState (), false, it->GetNumSentBytes ()); |
479 | 531 | s << "</div>\r\n"; |
480 | 532 | } |
481 | 533 | } |
482 | 534 | s << "<br>\r\n"; |
483 | 535 | |
484 | s << "<b>Tags</b><br>\r\nIncoming: <i>" << dest->GetNumIncomingTags () << "</i><br>\r\n"; | |
536 | s << "<b>" << tr("Tags") << "</b><br>\r\n" << tr("Incoming") << ": <i>" << dest->GetNumIncomingTags () << "</i><br>\r\n"; | |
485 | 537 | if (!dest->GetSessions ().empty ()) { |
486 | 538 | std::stringstream tmp_s; uint32_t out_tags = 0; |
487 | 539 | for (const auto& it: dest->GetSessions ()) { |
488 | 540 | tmp_s << "<tr><td>" << i2p::client::context.GetAddressBook ().ToAddress(it.first) << "</td><td>" << it.second->GetNumOutgoingTags () << "</td></tr>\r\n"; |
489 | 541 | out_tags += it.second->GetNumOutgoingTags (); |
490 | 542 | } |
491 | s << "<div class='slide'><label for='slide-tags'>Outgoing: <i>" << out_tags << "</i></label>\r\n<input type=\"checkbox\" id=\"slide-tags\" />\r\n" | |
492 | << "<div class=\"slidecontent\">\r\n<table>\r\n<thead><th>Destination</th><th>Amount</th></thead>\r\n<tbody class=\"tableitem\">\r\n" << tmp_s.str () << "</tbody></table>\r\n</div>\r\n</div>\r\n"; | |
543 | s << "<div class='slide'><label for='slide-tags'>" << tr("Outgoing") << ": <i>" << out_tags << "</i></label>\r\n<input type=\"checkbox\" id=\"slide-tags\" />\r\n" | |
544 | << "<div class=\"slidecontent\">\r\n<table>\r\n<thead><th>" << tr("Destination") << "</th><th>" << tr("Amount") << "</th></thead>\r\n<tbody class=\"tableitem\">\r\n" << tmp_s.str () << "</tbody></table>\r\n</div>\r\n</div>\r\n"; | |
493 | 545 | } else |
494 | s << "Outgoing: <i>0</i><br>\r\n"; | |
546 | s << tr("Outgoing") << ": <i>0</i><br>\r\n"; | |
495 | 547 | s << "<br>\r\n"; |
496 | 548 | |
497 | 549 | auto numECIESx25519Tags = dest->GetNumIncomingECIESx25519Tags (); |
498 | 550 | if (numECIESx25519Tags > 0) { |
499 | s << "<b>ECIESx25519</b><br>\r\nIncoming Tags: <i>" << numECIESx25519Tags << "</i><br>\r\n"; | |
551 | s << "<b>ECIESx25519</b><br>\r\n" << tr("Incoming Tags") << ": <i>" << numECIESx25519Tags << "</i><br>\r\n"; | |
500 | 552 | if (!dest->GetECIESx25519Sessions ().empty ()) |
501 | 553 | { |
502 | 554 | std::stringstream tmp_s; uint32_t ecies_sessions = 0; |
504 | 556 | tmp_s << "<tr><td>" << i2p::client::context.GetAddressBook ().ToAddress(it.second->GetDestination ()) << "</td><td>" << it.second->GetState () << "</td></tr>\r\n"; |
505 | 557 | ecies_sessions++; |
506 | 558 | } |
507 | s << "<div class='slide'><label for='slide-ecies-sessions'>Tags sessions: <i>" << ecies_sessions << "</i></label>\r\n<input type=\"checkbox\" id=\"slide-ecies-sessions\" />\r\n" | |
508 | << "<div class=\"slidecontent\">\r\n<table>\r\n<thead><th>Destination</th><th>Status</th></thead>\r\n<tbody class=\"tableitem\">\r\n" << tmp_s.str () << "</tbody></table>\r\n</div>\r\n</div>\r\n"; | |
559 | s << "<div class='slide'><label for='slide-ecies-sessions'>" << tr("Tags sessions") << ": <i>" << ecies_sessions << "</i></label>\r\n<input type=\"checkbox\" id=\"slide-ecies-sessions\" />\r\n" | |
560 | << "<div class=\"slidecontent\">\r\n<table>\r\n<thead><th>" << tr("Destination") << "</th><th>" << tr("Status") << "</th></thead>\r\n<tbody class=\"tableitem\">\r\n" << tmp_s.str () << "</tbody></table>\r\n</div>\r\n</div>\r\n"; | |
509 | 561 | } else |
510 | s << "Tags sessions: <i>0</i><br>\r\n"; | |
562 | s << tr("Tags sessions") << ": <i>0</i><br>\r\n"; | |
511 | 563 | s << "<br>\r\n"; |
512 | 564 | } |
513 | 565 | } |
514 | 566 | |
515 | 567 | void ShowLocalDestination (std::stringstream& s, const std::string& b32, uint32_t token) |
516 | 568 | { |
517 | s << "<b>Local Destination:</b><br>\r\n<br>\r\n"; | |
569 | s << "<b>" << tr("Local Destination") << ":</b><br>\r\n<br>\r\n"; | |
518 | 570 | i2p::data::IdentHash ident; |
519 | 571 | ident.FromBase32 (b32); |
520 | 572 | auto dest = i2p::client::context.FindLocalDestination (ident); |
522 | 574 | if (dest) |
523 | 575 | { |
524 | 576 | ShowLeaseSetDestination (s, dest, token); |
525 | // show streams | |
526 | s << "<table>\r\n<caption>Streams</caption>\r\n<thead>\r\n<tr>"; | |
577 | ||
578 | // Print table with streams information | |
579 | s << "<table>\r\n<caption>" << tr("Streams") << "</caption>\r\n<thead>\r\n<tr>"; | |
527 | 580 | s << "<th style=\"width:25px;\">StreamID</th>"; |
528 | 581 | s << "<th style=\"width:5px;\" \\>"; // Stream closing button column |
529 | 582 | s << "<th class=\"streamdest\">Destination</th>"; |
545 | 598 | s << "<td>" << it->GetRecvStreamID () << "</td>"; |
546 | 599 | if (it->GetRecvStreamID ()) { |
547 | 600 | s << "<td><a class=\"button\" href=\"/?cmd=" << HTTP_COMMAND_KILLSTREAM << "&b32=" << b32 << "&streamID=" |
548 | << it->GetRecvStreamID () << "&token=" << token << "\" title=\"Close stream\"> ✘ </a></td>"; | |
601 | << it->GetRecvStreamID () << "&token=" << token << "\" title=\"" << tr("Close stream") << "\"> ✘ </a></td>"; | |
549 | 602 | } else { |
550 | 603 | s << "<td \\>"; |
551 | 604 | } |
569 | 622 | auto i2cpServer = i2p::client::context.GetI2CPServer (); |
570 | 623 | if (i2cpServer) |
571 | 624 | { |
572 | s << "<b>I2CP Local Destination:</b><br>\r\n<br>\r\n"; | |
625 | s << "<b>I2CP " << tr("Local Destination") << ":</b><br>\r\n<br>\r\n"; | |
573 | 626 | auto it = i2cpServer->GetSessions ().find (std::stoi (id)); |
574 | 627 | if (it != i2cpServer->GetSessions ().end ()) |
575 | 628 | ShowLeaseSetDestination (s, it->second->GetDestination (), 0); |
576 | 629 | else |
577 | ShowError(s, "I2CP session not found"); | |
630 | ShowError(s, tr("I2CP session not found")); | |
578 | 631 | } |
579 | 632 | else |
580 | ShowError(s, "I2CP is not enabled"); | |
633 | ShowError(s, tr("I2CP is not enabled")); | |
581 | 634 | } |
582 | 635 | |
583 | 636 | void ShowLeasesSets(std::stringstream& s) |
584 | 637 | { |
585 | 638 | if (i2p::data::netdb.GetNumLeaseSets ()) |
586 | 639 | { |
587 | s << "<b>LeaseSets:</b><br>\r\n<div class=\"list\">\r\n"; | |
640 | s << "<b>" << tr("LeaseSets") << ":</b><br>\r\n<div class=\"list\">\r\n"; | |
588 | 641 | int counter = 1; |
589 | 642 | // for each lease set |
590 | 643 | i2p::data::netdb.VisitLeaseSets( |
603 | 656 | s << " expired"; // additional css class for expired |
604 | 657 | s << "\">\r\n"; |
605 | 658 | if (!ls->IsValid()) |
606 | s << "<div class=\"invalid\">!! Invalid !! </div>\r\n"; | |
659 | s << "<div class=\"invalid\">!! " << tr("Invalid") << " !! </div>\r\n"; | |
607 | 660 | s << "<div class=\"slide\"><label for=\"slide" << counter << "\">" << dest.ToBase32() << "</label>\r\n"; |
608 | 661 | s << "<input type=\"checkbox\" id=\"slide" << (counter++) << "\" />\r\n<div class=\"slidecontent\">\r\n"; |
609 | s << "<b>Store type:</b> " << (int)storeType << "<br>\r\n"; | |
610 | s << "<b>Expires:</b> " << ConvertTime(ls->GetExpirationTime()) << "<br>\r\n"; | |
662 | s << "<b>" << tr("Store type") << ":</b> " << (int)storeType << "<br>\r\n"; | |
663 | s << "<b>" << tr("Expires") << ":</b> " << ConvertTime(ls->GetExpirationTime()) << "<br>\r\n"; | |
611 | 664 | if (storeType == i2p::data::NETDB_STORE_TYPE_LEASESET || storeType == i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2) |
612 | 665 | { |
613 | 666 | // leases information is available |
614 | 667 | auto leases = ls->GetNonExpiredLeases(); |
615 | s << "<b>Non Expired Leases: " << leases.size() << "</b><br>\r\n"; | |
668 | s << "<b>" << tr("Non Expired Leases") << ": " << leases.size() << "</b><br>\r\n"; | |
616 | 669 | for ( auto & l : leases ) |
617 | 670 | { |
618 | s << "<b>Gateway:</b> " << l->tunnelGateway.ToBase64() << "<br>\r\n"; | |
619 | s << "<b>TunnelID:</b> " << l->tunnelID << "<br>\r\n"; | |
620 | s << "<b>EndDate:</b> " << ConvertTime(l->endDate) << "<br>\r\n"; | |
671 | s << "<b>" << tr("Gateway") << ":</b> " << l->tunnelGateway.ToBase64() << "<br>\r\n"; | |
672 | s << "<b>" << tr("TunnelID") << ":</b> " << l->tunnelID << "<br>\r\n"; | |
673 | s << "<b>" << tr("EndDate") << ":</b> " << ConvertTime(l->endDate) << "<br>\r\n"; | |
621 | 674 | } |
622 | 675 | } |
623 | 676 | s << "</div>\r\n</div>\r\n</div>\r\n"; |
627 | 680 | } |
628 | 681 | else if (!i2p::context.IsFloodfill ()) |
629 | 682 | { |
630 | s << "<b>LeaseSets:</b> not floodfill.<br>\r\n"; | |
683 | s << "<b>" << tr("LeaseSets") << ":</b> " << tr("not floodfill") << ".<br>\r\n"; | |
631 | 684 | } |
632 | 685 | else |
633 | 686 | { |
634 | s << "<b>LeaseSets:</b> 0<br>\r\n"; | |
687 | s << "<b>" << tr("LeaseSets") << ":</b> 0<br>\r\n"; | |
635 | 688 | } |
636 | 689 | } |
637 | 690 | |
638 | 691 | void ShowTunnels (std::stringstream& s) |
639 | 692 | { |
640 | s << "<b>Tunnels:</b><br>\r\n"; | |
641 | s << "<b>Queue size:</b> " << i2p::tunnel::tunnels.GetQueueSize () << "<br>\r\n<br>\r\n"; | |
693 | s << "<b>" << tr("Tunnels") << ":</b><br>\r\n"; | |
694 | s << "<b>" << tr("Queue size") << ":</b> " << i2p::tunnel::tunnels.GetQueueSize () << "<br>\r\n<br>\r\n"; | |
642 | 695 | |
643 | 696 | auto ExplPool = i2p::tunnel::tunnels.GetExploratoryPool (); |
644 | 697 | |
645 | s << "<b>Inbound tunnels:</b><br>\r\n<div class=\"list\">\r\n"; | |
698 | s << "<b>" << tr("Inbound tunnels") << ":</b><br>\r\n<div class=\"list\">\r\n"; | |
646 | 699 | for (auto & it : i2p::tunnel::tunnels.GetInboundTunnels ()) { |
647 | 700 | s << "<div class=\"listitem\">"; |
648 | 701 | it->Print(s); |
649 | 702 | if(it->LatencyIsKnown()) |
650 | s << " ( " << it->GetMeanLatency() << "ms )"; | |
703 | s << " ( " << it->GetMeanLatency() << tr("ms") << " )"; | |
651 | 704 | ShowTunnelDetails(s, it->GetState (), (it->GetTunnelPool () == ExplPool), it->GetNumReceivedBytes ()); |
652 | 705 | s << "</div>\r\n"; |
653 | 706 | } |
654 | 707 | s << "</div>\r\n<br>\r\n"; |
655 | s << "<b>Outbound tunnels:</b><br>\r\n<div class=\"list\">\r\n"; | |
708 | s << "<b>" << tr("Outbound tunnels") << ":</b><br>\r\n<div class=\"list\">\r\n"; | |
656 | 709 | for (auto & it : i2p::tunnel::tunnels.GetOutboundTunnels ()) { |
657 | 710 | s << "<div class=\"listitem\">"; |
658 | 711 | it->Print(s); |
659 | 712 | if(it->LatencyIsKnown()) |
660 | s << " ( " << it->GetMeanLatency() << "ms )"; | |
713 | s << " ( " << it->GetMeanLatency() << tr("ms") << " )"; | |
661 | 714 | ShowTunnelDetails(s, it->GetState (), (it->GetTunnelPool () == ExplPool), it->GetNumSentBytes ()); |
662 | 715 | s << "</div>\r\n"; |
663 | 716 | } |
667 | 720 | static void ShowCommands (std::stringstream& s, uint32_t token) |
668 | 721 | { |
669 | 722 | std::string webroot; i2p::config::GetOption("http.webroot", webroot); |
670 | /* commands */ | |
671 | s << "<b>Router Commands</b><br>\r\n<br>\r\n<div class=\"commands\">\r\n"; | |
672 | s << " <a href=\"" << webroot << "?cmd=" << HTTP_COMMAND_RUN_PEER_TEST << "&token=" << token << "\">Run peer test</a>\r\n"; | |
673 | //s << " <a href=\"/?cmd=" << HTTP_COMMAND_RELOAD_CONFIG << "\">Reload config</a><br>\r\n"; | |
723 | ||
724 | s << "<b>" << tr("Router commands") << "</b><br>\r\n<br>\r\n<div class=\"commands\">\r\n"; | |
725 | s << " <a href=\"" << webroot << "?cmd=" << HTTP_COMMAND_RUN_PEER_TEST << "&token=" << token << "\">" << tr("Run peer test") << "</a><br>\r\n"; | |
726 | ||
727 | // s << " <a href=\"/?cmd=" << HTTP_COMMAND_RELOAD_CONFIG << "\">Reload config</a><br>\r\n"; | |
728 | ||
674 | 729 | if (i2p::context.AcceptsTunnels ()) |
675 | s << " <a href=\"" << webroot << "?cmd=" << HTTP_COMMAND_DISABLE_TRANSIT << "&token=" << token << "\">Decline transit tunnels</a>\r\n"; | |
730 | s << " <a href=\"" << webroot << "?cmd=" << HTTP_COMMAND_DISABLE_TRANSIT << "&token=" << token << "\">" << tr("Decline transit tunnels") << "</a><br>\r\n"; | |
676 | 731 | else |
677 | s << " <a href=\"" << webroot << "?cmd=" << HTTP_COMMAND_ENABLE_TRANSIT << "&token=" << token << "\">Accept transit tunnels</a>\r\n"; | |
732 | s << " <a href=\"" << webroot << "?cmd=" << HTTP_COMMAND_ENABLE_TRANSIT << "&token=" << token << "\">" << tr("Accept transit tunnels") << "</a><br>\r\n"; | |
733 | ||
678 | 734 | #if ((!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) || defined(ANDROID_BINARY)) |
679 | 735 | if (Daemon.gracefulShutdownInterval) |
680 | s << " <a href=\"" << webroot << "?cmd=" << HTTP_COMMAND_SHUTDOWN_CANCEL << "&token=" << token << "\">Cancel graceful shutdown</a>\r\n"; | |
736 | s << " <a href=\"" << webroot << "?cmd=" << HTTP_COMMAND_SHUTDOWN_CANCEL << "&token=" << token << "\">" << tr("Cancel graceful shutdown") << "</a><br>\r\n"; | |
681 | 737 | else |
682 | s << " <a href=\"" << webroot << "?cmd=" << HTTP_COMMAND_SHUTDOWN_START << "&token=" << token << "\">Start graceful shutdown</a><br>\r\n"; | |
738 | s << " <a href=\"" << webroot << "?cmd=" << HTTP_COMMAND_SHUTDOWN_START << "&token=" << token << "\">" << tr("Start graceful shutdown") << "</a><br>\r\n"; | |
683 | 739 | #elif defined(WIN32_APP) |
684 | 740 | if (i2p::util::DaemonWin32::Instance().isGraceful) |
685 | s << " <a href=\"" << webroot << "?cmd=" << HTTP_COMMAND_SHUTDOWN_CANCEL << "&token=" << token << "\">Cancel graceful shutdown</a>\r\n"; | |
741 | s << " <a href=\"" << webroot << "?cmd=" << HTTP_COMMAND_SHUTDOWN_CANCEL << "&token=" << token << "\">" << tr("Cancel graceful shutdown") << "</a><br>\r\n"; | |
686 | 742 | else |
687 | s << " <a href=\"" << webroot << "?cmd=" << HTTP_COMMAND_SHUTDOWN_START << "&token=" << token << "\">Graceful shutdown</a>\r\n"; | |
743 | s << " <a href=\"" << webroot << "?cmd=" << HTTP_COMMAND_SHUTDOWN_START << "&token=" << token << "\">" << tr("Start graceful shutdown") << "</a><br>\r\n"; | |
688 | 744 | #endif |
689 | s << " <a href=\"" << webroot << "?cmd=" << HTTP_COMMAND_SHUTDOWN_NOW << "&token=" << token << "\">Force shutdown</a>\r\n"; | |
745 | ||
746 | s << " <a href=\"" << webroot << "?cmd=" << HTTP_COMMAND_SHUTDOWN_NOW << "&token=" << token << "\">" << tr("Force shutdown") << "</a><br><br>\r\n"; | |
747 | s << " <a href=\"" << webroot << "?cmd=" << HTTP_COMMAND_RELOAD_CSS << "&token=" << token << "\">" << tr("Reload external CSS styles") << "</a>\r\n"; | |
690 | 748 | s << "</div>"; |
691 | 749 | |
692 | s << "<br>\r\n<small><b>Note:</b> any action done here are not persistent and not changes your config files.</small>\r\n<br>\r\n"; | |
693 | ||
694 | s << "<b>Logging level</b><br>\r\n"; | |
750 | s << "<br>\r\n<small>" << tr("<b>Note:</b> any action done here are not persistent and not changes your config files.") << "</small>\r\n<br>\r\n"; | |
751 | ||
752 | s << "<b>" << tr("Logging level") << "</b><br>\r\n"; | |
695 | 753 | s << " <a class=\"button\" href=\"" << webroot << "?cmd=" << HTTP_COMMAND_LOGLEVEL << "&level=none&token=" << token << "\"> none </a> \r\n"; |
696 | 754 | s << " <a class=\"button\" href=\"" << webroot << "?cmd=" << HTTP_COMMAND_LOGLEVEL << "&level=error&token=" << token << "\"> error </a> \r\n"; |
697 | 755 | s << " <a class=\"button\" href=\"" << webroot << "?cmd=" << HTTP_COMMAND_LOGLEVEL << "&level=warn&token=" << token << "\"> warn </a> \r\n"; |
699 | 757 | s << " <a class=\"button\" href=\"" << webroot << "?cmd=" << HTTP_COMMAND_LOGLEVEL << "&level=debug&token=" << token << "\"> debug </a><br>\r\n<br>\r\n"; |
700 | 758 | |
701 | 759 | uint16_t maxTunnels = GetMaxNumTransitTunnels (); |
702 | s << "<b>Transit tunnels limit</b><br>\r\n"; | |
760 | s << "<b>" << tr("Transit tunnels limit") << "</b><br>\r\n"; | |
703 | 761 | s << "<form method=\"get\" action=\"" << webroot << "\">\r\n"; |
704 | 762 | s << " <input type=\"hidden\" name=\"cmd\" value=\"" << HTTP_COMMAND_LIMITTRANSIT << "\">\r\n"; |
705 | 763 | s << " <input type=\"hidden\" name=\"token\" value=\"" << token << "\">\r\n"; |
706 | 764 | s << " <input type=\"number\" min=\"0\" max=\"65535\" name=\"limit\" value=\"" << maxTunnels << "\">\r\n"; |
707 | s << " <button type=\"submit\">Change</button>\r\n"; | |
765 | s << " <button type=\"submit\">" << tr("Change") << "</button>\r\n"; | |
708 | 766 | s << "</form>\r\n<br>\r\n"; |
767 | ||
768 | std::string currLang = i2p::context.GetLanguage ()->GetLanguage(); // get current used language | |
769 | s << "<b>" << tr("Change language") << "</b><br>\r\n"; | |
770 | s << "<form method=\"get\" action=\"" << webroot << "\">\r\n"; | |
771 | s << " <input type=\"hidden\" name=\"cmd\" value=\"" << HTTP_COMMAND_SETLANGUAGE << "\">\r\n"; | |
772 | s << " <input type=\"hidden\" name=\"token\" value=\"" << token << "\">\r\n"; | |
773 | s << " <select name=\"lang\" id=\"lang\">\r\n"; | |
774 | for (const auto& it: i2p::i18n::languages) | |
775 | s << " <option value=\"" << it.first << "\"" << ((it.first.compare(currLang) == 0) ? " selected" : "") << ">" << it.second.LocaleName << "</option>\r\n"; | |
776 | s << " </select>\r\n"; | |
777 | s << " <button type=\"submit\">" << tr("Change") << "</button>\r\n"; | |
778 | s << "</form>\r\n<br>\r\n"; | |
779 | ||
709 | 780 | } |
710 | 781 | |
711 | 782 | void ShowTransitTunnels (std::stringstream& s) |
712 | 783 | { |
713 | 784 | if(i2p::tunnel::tunnels.CountTransitTunnels()) |
714 | 785 | { |
715 | s << "<b>Transit tunnels:</b><br>\r\n<div class=\"list\">\r\n"; | |
786 | s << "<b>" << tr("Transit Tunnels") << ":</b><br>\r\n<div class=\"list\">\r\n"; | |
716 | 787 | for (const auto& it: i2p::tunnel::tunnels.GetTransitTunnels ()) |
717 | 788 | { |
718 | 789 | s << "<div class=\"listitem\">\r\n"; |
728 | 799 | } |
729 | 800 | else |
730 | 801 | { |
731 | s << "<b>Transit tunnels:</b> no transit tunnels currently built.<br>\r\n"; | |
802 | s << "<b>" << tr("Transit Tunnels") << ":</b> " << tr("no transit tunnels currently built") << ".<br>\r\n"; | |
732 | 803 | } |
733 | 804 | } |
734 | 805 | |
777 | 848 | |
778 | 849 | void ShowTransports (std::stringstream& s) |
779 | 850 | { |
780 | s << "<b>Transports:</b><br>\r\n"; | |
851 | s << "<b>" << tr("Transports") << ":</b><br>\r\n"; | |
781 | 852 | auto ntcp2Server = i2p::transport::transports.GetNTCP2Server (); |
782 | 853 | if (ntcp2Server) |
783 | 854 | { |
833 | 904 | auto sam = i2p::client::context.GetSAMBridge (); |
834 | 905 | if (!sam) |
835 | 906 | { |
836 | ShowError(s, "SAM disabled"); | |
907 | ShowError(s, tr("SAM disabled")); | |
837 | 908 | return; |
838 | 909 | } |
839 | 910 | |
840 | 911 | if(sam->GetSessions ().size ()) |
841 | 912 | { |
842 | s << "<b>SAM Sessions:</b><br>\r\n<div class=\"list\">\r\n"; | |
913 | s << "<b>" << tr("SAM sessions") << ":</b><br>\r\n<div class=\"list\">\r\n"; | |
843 | 914 | for (auto& it: sam->GetSessions ()) |
844 | 915 | { |
845 | 916 | auto& name = it.second->GetLocalDestination ()->GetNickname (); |
849 | 920 | s << "</div>\r\n"; |
850 | 921 | } |
851 | 922 | else |
852 | s << "<b>SAM Sessions:</b> no sessions currently running.<br>\r\n"; | |
923 | s << "<b>" << tr("SAM sessions") << ":</b> " << tr("no sessions currently running") << ".<br>\r\n"; | |
853 | 924 | } |
854 | 925 | |
855 | 926 | void ShowSAMSession (std::stringstream& s, const std::string& id) |
856 | 927 | { |
857 | 928 | auto sam = i2p::client::context.GetSAMBridge (); |
858 | 929 | if (!sam) { |
859 | ShowError(s, "SAM disabled"); | |
930 | ShowError(s, tr("SAM disabled")); | |
860 | 931 | return; |
861 | 932 | } |
862 | 933 | |
863 | 934 | auto session = sam->FindSession (id); |
864 | 935 | if (!session) { |
865 | ShowError(s, "SAM session not found"); | |
936 | ShowError(s, tr("SAM session not found")); | |
866 | 937 | return; |
867 | 938 | } |
868 | 939 | |
869 | 940 | std::string webroot; i2p::config::GetOption("http.webroot", webroot); |
870 | s << "<b>SAM Session:</b><br>\r\n<div class=\"list\">\r\n"; | |
941 | s << "<b>" << tr("SAM Session") << ":</b><br>\r\n<div class=\"list\">\r\n"; | |
871 | 942 | auto& ident = session->GetLocalDestination ()->GetIdentHash(); |
872 | 943 | s << "<div class=\"listitem\"><a href=\"" << webroot << "?page=" << HTTP_PAGE_LOCAL_DESTINATION << "&b32=" << ident.ToBase32 () << "\">"; |
873 | 944 | s << i2p::client::context.GetAddressBook ().ToAddress(ident) << "</a></div>\r\n"; |
874 | 945 | s << "<br>\r\n"; |
875 | s << "<b>Streams:</b><br>\r\n<div class=\"list\">\r\n"; | |
946 | s << "<b>" << tr("Streams") << ":</b><br>\r\n<div class=\"list\">\r\n"; | |
876 | 947 | for (const auto& it: sam->ListSockets(id)) |
877 | 948 | { |
878 | 949 | s << "<div class=\"listitem\">"; |
881 | 952 | case i2p::client::eSAMSocketTypeSession : s << "session"; break; |
882 | 953 | case i2p::client::eSAMSocketTypeStream : s << "stream"; break; |
883 | 954 | case i2p::client::eSAMSocketTypeAcceptor : s << "acceptor"; break; |
884 | case i2p::client::eSAMSocketTypeForward : s << "forward"; break; | |
955 | case i2p::client::eSAMSocketTypeForward : s << "forward"; break; | |
885 | 956 | default: s << "unknown"; break; |
886 | 957 | } |
887 | 958 | s << " [" << it->GetSocket ().remote_endpoint() << "]"; |
893 | 964 | void ShowI2PTunnels (std::stringstream& s) |
894 | 965 | { |
895 | 966 | std::string webroot; i2p::config::GetOption("http.webroot", webroot); |
896 | s << "<b>Client Tunnels:</b><br>\r\n<div class=\"list\">\r\n"; | |
967 | s << "<b>" << tr("Client Tunnels") << ":</b><br>\r\n<div class=\"list\">\r\n"; | |
897 | 968 | for (auto& it: i2p::client::context.GetClientTunnels ()) |
898 | 969 | { |
899 | 970 | auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); |
907 | 978 | { |
908 | 979 | auto& ident = httpProxy->GetLocalDestination ()->GetIdentHash(); |
909 | 980 | s << "<div class=\"listitem\"><a href=\"" << webroot << "?page=" << HTTP_PAGE_LOCAL_DESTINATION << "&b32=" << ident.ToBase32 () << "\">"; |
910 | s << "HTTP Proxy" << "</a> ⇐ "; | |
981 | s << "HTTP " << tr("Proxy") << "</a> ⇐ "; | |
911 | 982 | s << i2p::client::context.GetAddressBook ().ToAddress(ident); |
912 | 983 | s << "</div>\r\n"<< std::endl; |
913 | 984 | } |
916 | 987 | { |
917 | 988 | auto& ident = socksProxy->GetLocalDestination ()->GetIdentHash(); |
918 | 989 | s << "<div class=\"listitem\"><a href=\"" << webroot << "?page=" << HTTP_PAGE_LOCAL_DESTINATION << "&b32=" << ident.ToBase32 () << "\">"; |
919 | s << "SOCKS Proxy" << "</a> ⇐ "; | |
990 | s << "SOCKS " << tr("Proxy") << "</a> ⇐ "; | |
920 | 991 | s << i2p::client::context.GetAddressBook ().ToAddress(ident); |
921 | 992 | s << "</div>\r\n"<< std::endl; |
922 | 993 | } |
924 | 995 | |
925 | 996 | auto& serverTunnels = i2p::client::context.GetServerTunnels (); |
926 | 997 | if (!serverTunnels.empty ()) { |
927 | s << "<br>\r\n<b>Server Tunnels:</b><br>\r\n<div class=\"list\">\r\n"; | |
998 | s << "<br>\r\n<b>" << tr("Server Tunnels") << ":</b><br>\r\n<div class=\"list\">\r\n"; | |
928 | 999 | for (auto& it: serverTunnels) |
929 | 1000 | { |
930 | 1001 | auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); |
940 | 1011 | auto& clientForwards = i2p::client::context.GetClientForwards (); |
941 | 1012 | if (!clientForwards.empty ()) |
942 | 1013 | { |
943 | s << "<br>\r\n<b>Client Forwards:</b><br>\r\n<div class=\"list\">\r\n"; | |
1014 | s << "<br>\r\n<b>" << tr("Client Forwards") << ":</b><br>\r\n<div class=\"list\">\r\n"; | |
944 | 1015 | for (auto& it: clientForwards) |
945 | 1016 | { |
946 | 1017 | auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); |
954 | 1025 | auto& serverForwards = i2p::client::context.GetServerForwards (); |
955 | 1026 | if (!serverForwards.empty ()) |
956 | 1027 | { |
957 | s << "<br>\r\n<b>Server Forwards:</b><br>\r\n<div class=\"list\">\r\n"; | |
1028 | s << "<br>\r\n<b>" << tr("Server Forwards") << ":</b><br>\r\n<div class=\"list\">\r\n"; | |
958 | 1029 | for (auto& it: serverForwards) |
959 | 1030 | { |
960 | 1031 | auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); |
965 | 1036 | } |
966 | 1037 | s << "</div>\r\n"; |
967 | 1038 | } |
968 | } | |
969 | ||
970 | std::string ConvertTime (uint64_t time) | |
971 | { | |
972 | lldiv_t divTime = lldiv(time, 1000); | |
973 | time_t t = divTime.quot; | |
974 | struct tm *tm = localtime(&t); | |
975 | char date[128]; | |
976 | 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); | |
977 | return date; | |
978 | 1039 | } |
979 | 1040 | |
980 | 1041 | HTTPConnection::HTTPConnection (std::string hostname, std::shared_ptr<boost::asio::ip::tcp::socket> socket): |
1086 | 1147 | return; |
1087 | 1148 | } |
1088 | 1149 | } |
1089 | // Html5 head start | |
1150 | // HTML head start | |
1090 | 1151 | ShowPageHead (s); |
1091 | 1152 | if (req.uri.find("page=") != std::string::npos) { |
1092 | 1153 | HandlePage (req, res, s); |
1102 | 1163 | content = s.str (); |
1103 | 1164 | SendReply (res, content); |
1104 | 1165 | } |
1166 | ||
1167 | std::map<uint32_t, uint32_t> HTTPConnection::m_Tokens; | |
1105 | 1168 | |
1106 | 1169 | uint32_t HTTPConnection::CreateToken () |
1107 | 1170 | { |
1160 | 1223 | ShowLeasesSets(s); |
1161 | 1224 | else { |
1162 | 1225 | res.code = 400; |
1163 | ShowError(s, "Unknown page: " + page); | |
1226 | ShowError(s, tr("Unknown page") + ": " + page); | |
1164 | 1227 | return; |
1165 | 1228 | } |
1166 | 1229 | } |
1179 | 1242 | |
1180 | 1243 | if (token.empty () || m_Tokens.find (std::stoi (token)) == m_Tokens.end ()) |
1181 | 1244 | { |
1182 | ShowError(s, "Invalid token"); | |
1245 | ShowError(s, tr("Invalid token")); | |
1183 | 1246 | return; |
1184 | 1247 | } |
1185 | 1248 | |
1237 | 1300 | if (dest) |
1238 | 1301 | { |
1239 | 1302 | if(dest->DeleteStream (streamID)) |
1240 | s << "<b>SUCCESS</b>: Stream closed<br>\r\n<br>\r\n"; | |
1303 | s << "<b>" << tr("SUCCESS") << "</b>: " << tr("Stream closed") << "<br>\r\n<br>\r\n"; | |
1241 | 1304 | else |
1242 | s << "<b>ERROR</b>: Stream not found or already was closed<br>\r\n<br>\r\n"; | |
1305 | s << "<b>" << tr("ERROR") << "</b>: " << tr("Stream not found or already was closed") << "<br>\r\n<br>\r\n"; | |
1243 | 1306 | } |
1244 | 1307 | else |
1245 | s << "<b>ERROR</b>: Destination not found<br>\r\n<br>\r\n"; | |
1308 | s << "<b>" << tr("ERROR") << "</b>: " << tr("Destination not found") << "<br>\r\n<br>\r\n"; | |
1246 | 1309 | } |
1247 | 1310 | else |
1248 | s << "<b>ERROR</b>: StreamID can be null<br>\r\n<br>\r\n"; | |
1249 | ||
1250 | s << "<a href=\"" << webroot << "?page=local_destination&b32=" << b32 << "\">Return to destination page</a><br>\r\n"; | |
1251 | s << "<p>You will be redirected back in 5 seconds</b>"; | |
1311 | s << "<b>" << tr("ERROR") << "</b>: " << tr("StreamID can't be null") << "<br>\r\n<br>\r\n"; | |
1312 | ||
1313 | s << "<a href=\"" << webroot << "?page=local_destination&b32=" << b32 << "\">" << tr("Return to destination page") << "</a><br>\r\n"; | |
1314 | s << "<p>" << tr("You will be redirected in 5 seconds") << "</b>"; | |
1252 | 1315 | redirect = "5; url=" + webroot + "?page=local_destination&b32=" + b32; |
1253 | 1316 | res.add_header("Refresh", redirect.c_str()); |
1254 | 1317 | return; |
1259 | 1322 | if (limit > 0 && limit <= 65535) |
1260 | 1323 | SetMaxNumTransitTunnels (limit); |
1261 | 1324 | else { |
1262 | s << "<b>ERROR</b>: Transit tunnels count must not exceed 65535\r\n<br>\r\n<br>\r\n"; | |
1263 | s << "<a href=\"" << webroot << "?page=commands\">Back to commands list</a>\r\n<br>\r\n"; | |
1264 | s << "<p>You will be redirected back in 5 seconds</b>"; | |
1325 | s << "<b>" << tr("ERROR") << "</b>: " << tr("Transit tunnels count must not exceed 65535") << "\r\n<br>\r\n<br>\r\n"; | |
1326 | s << "<a href=\"" << webroot << "?page=commands\">" << tr("Back to commands list") << "</a>\r\n<br>\r\n"; | |
1327 | s << "<p>" << tr("You will be redirected in 5 seconds") << "</b>"; | |
1265 | 1328 | res.add_header("Refresh", redirect.c_str()); |
1266 | 1329 | return; |
1267 | 1330 | } |
1269 | 1332 | else if (cmd == HTTP_COMMAND_GET_REG_STRING) |
1270 | 1333 | { |
1271 | 1334 | std::string b32 = params["b32"]; |
1272 | std::string name = params["name"]; | |
1335 | std::string name = i2p::http::UrlDecode(params["name"]); | |
1273 | 1336 | |
1274 | 1337 | i2p::data::IdentHash ident; |
1275 | 1338 | ident.FromBase32 (b32); |
1294 | 1357 | auto len = i2p::data::ByteStreamToBase64 (signature, signatureLen, sig, signatureLen*2); |
1295 | 1358 | sig[len] = 0; |
1296 | 1359 | out << "#!sig=" << sig; |
1297 | s << "<b>SUCCESS</b>:<br>\r\n<form action=\"http://shx5vqsw7usdaunyzr2qmes2fq37oumybpudrd4jjj4e4vk4uusa.b32.i2p/add\" method=\"post\" rel=\"noreferrer\" target=\"_blank\">\r\n" | |
1360 | s << "<b>" << tr("SUCCESS") << "</b>:<br>\r\n<form action=\"http://shx5vqsw7usdaunyzr2qmes2fq37oumybpudrd4jjj4e4vk4uusa.b32.i2p/add\" method=\"post\" rel=\"noreferrer\" target=\"_blank\">\r\n" | |
1298 | 1361 | "<textarea readonly name=\"record\" cols=\"80\" rows=\"10\">" << out.str () << "</textarea>\r\n<br>\r\n<br>\r\n" |
1299 | "<b>Register at reg.i2p:</b>\r\n<br>\r\n" | |
1300 | "<b>Description:</b>\r\n<input type=\"text\" maxlength=\"64\" name=\"desc\" placeholder=\"A bit information about service on domain\">\r\n" | |
1301 | "<input type=\"submit\" value=\"Submit\">\r\n" | |
1362 | "<b>" << tr("Register at reg.i2p") << ":</b>\r\n<br>\r\n" | |
1363 | "<b>" << tr("Description") << ":</b>\r\n<input type=\"text\" maxlength=\"64\" name=\"desc\" placeholder=\"" << tr("A bit information about service on domain") << "\">\r\n" | |
1364 | "<input type=\"submit\" value=\"" << tr("Submit") << "\">\r\n" | |
1302 | 1365 | "</form>\r\n<br>\r\n"; |
1303 | 1366 | delete[] signature; |
1304 | 1367 | delete[] sig; |
1305 | 1368 | } |
1306 | 1369 | else |
1307 | s << "<b>ERROR</b>: Domain can't end with .b32.i2p\r\n<br>\r\n<br>\r\n"; | |
1370 | s << "<b>" << tr("ERROR") << "</b>: " << tr("Domain can't end with .b32.i2p") << "\r\n<br>\r\n<br>\r\n"; | |
1308 | 1371 | } |
1309 | 1372 | else |
1310 | s << "<b>ERROR</b>: Domain must end with .i2p\r\n<br>\r\n<br>\r\n"; | |
1373 | s << "<b>" << tr("ERROR") << "</b>: " << tr("Domain must end with .i2p") << "\r\n<br>\r\n<br>\r\n"; | |
1311 | 1374 | } |
1312 | 1375 | else |
1313 | s << "<b>ERROR</b>: Such destination is not found\r\n<br>\r\n<br>\r\n"; | |
1314 | ||
1315 | s << "<a href=\"" << webroot << "?page=local_destination&b32=" << b32 << "\">Return to destination page</a>\r\n"; | |
1376 | s << "<b>" << tr("ERROR") << "</b>: " << tr("Such destination is not found") << "\r\n<br>\r\n<br>\r\n"; | |
1377 | ||
1378 | s << "<a href=\"" << webroot << "?page=local_destination&b32=" << b32 << "\">" << tr("Return to destination page") << "</a>\r\n"; | |
1316 | 1379 | return; |
1317 | 1380 | } |
1381 | else if (cmd == HTTP_COMMAND_SETLANGUAGE) | |
1382 | { | |
1383 | std::string lang = params["lang"]; | |
1384 | std::string currLang = i2p::context.GetLanguage ()->GetLanguage(); | |
1385 | ||
1386 | if (currLang.compare(lang) != 0) | |
1387 | i2p::i18n::SetLanguage(lang); | |
1388 | } | |
1389 | else if (cmd == HTTP_COMMAND_RELOAD_CSS) | |
1390 | { | |
1391 | LoadExtCSS(); | |
1392 | } | |
1318 | 1393 | else |
1319 | 1394 | { |
1320 | 1395 | res.code = 400; |
1321 | ShowError(s, "Unknown command: " + cmd); | |
1396 | ShowError(s, tr("Unknown command") + ": " + cmd); | |
1322 | 1397 | return; |
1323 | 1398 | } |
1324 | 1399 | |
1325 | s << "<b>SUCCESS</b>: Command accepted<br><br>\r\n"; | |
1326 | s << "<a href=\"" << webroot << "?page=commands\">Back to commands list</a><br>\r\n"; | |
1327 | s << "<p>You will be redirected in 5 seconds</b>"; | |
1400 | s << "<b>" << tr("SUCCESS") << "</b>: " << tr("Command accepted") << "<br><br>\r\n"; | |
1401 | s << "<a href=\"" << webroot << "?page=commands\">" << tr("Back to commands list") << "</a><br>\r\n"; | |
1402 | s << "<p>" << tr("You will be redirected in 5 seconds") << "</b>"; | |
1328 | 1403 | res.add_header("Refresh", redirect.c_str()); |
1329 | 1404 | } |
1330 | 1405 | |
1377 | 1452 | m_Thread.reset (new std::thread (std::bind (&HTTPServer::Run, this))); |
1378 | 1453 | m_Acceptor.listen (); |
1379 | 1454 | Accept (); |
1455 | ||
1456 | LoadExtCSS(); | |
1380 | 1457 | } |
1381 | 1458 | |
1382 | 1459 | void HTTPServer::Stop () |
405 | 405 | |
406 | 406 | void I2PControlService::UptimeHandler (std::ostringstream& results) |
407 | 407 | { |
408 | InsertParam (results, "i2p.router.uptime", (int)i2p::context.GetUptime ()*1000); | |
408 | InsertParam (results, "i2p.router.uptime", std::to_string (i2p::context.GetUptime ()*1000LL)); | |
409 | 409 | } |
410 | 410 | |
411 | 411 | void I2PControlService::VersionHandler (std::ostringstream& results) |
12 | 12 | Architecture: any |
13 | 13 | Pre-Depends: ${misc:Pre-Depends}, adduser, |
14 | 14 | Depends: ${shlibs:Depends}, ${misc:Depends}, lsb-base, |
15 | Description: I2P Router written in C++ | |
15 | Description: Full-featured C++ implementation of I2P client | |
16 | 16 | I2P (Invisible Internet Protocol) is a universal anonymous network layer. All |
17 | 17 | communications over I2P are anonymous and end-to-end encrypted, participants |
18 | 18 | don't reveal their real IP addresses. |
0 | 0 | obj-*/i2pd usr/sbin/ |
1 | contrib/i2pd.conf etc/i2pd/ | |
1 | contrib/i2pd.conf etc/i2pd/ | |
2 | 2 | contrib/tunnels.conf etc/i2pd/ |
3 | 3 | contrib/subscriptions.txt etc/i2pd/ |
4 | 4 | contrib/certificates/ usr/share/i2pd/ |
18 | 18 | #DAEMON_SRC = \ |
19 | 19 | # HTTPServer.cpp I2PControl.cpp UPnP.cpp Daemon.cpp i2pd.cpp |
20 | 20 | |
21 | LANG_SRC = $(wildcard $(LANG_SRC_DIR)/*.cpp) | |
22 | ||
23 | WRAP_LIB_SRC = $(wildcard $(WRAP_SRC_DIR)/*.cpp) | |
24 | ||
21 | 25 | DAEMON_SRC = $(wildcard $(DAEMON_SRC_DIR)/*.cpp) |
0 | /* | |
1 | * Copyright (c) 2021, The PurpleI2P Project | |
2 | * | |
3 | * This file is part of Purple i2pd project and licensed under BSD3 | |
4 | * | |
5 | * See full license text in LICENSE file at top of project tree | |
6 | */ | |
7 | ||
8 | #include <map> | |
9 | #include <vector> | |
10 | #include <string> | |
11 | #include <memory> | |
12 | #include "I18N.h" | |
13 | ||
14 | // Afrikaans localization file | |
15 | ||
16 | namespace i2p | |
17 | { | |
18 | namespace i18n | |
19 | { | |
20 | namespace afrikaans // language namespace | |
21 | { | |
22 | // language name in lowercase | |
23 | static std::string language = "afrikaans"; | |
24 | ||
25 | // See for language plural forms here: | |
26 | // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html | |
27 | static int plural (int n) { | |
28 | return n != 1 ? 1 : 0; | |
29 | } | |
30 | ||
31 | static std::map<std::string, std::string> strings | |
32 | { | |
33 | {"failed", "Het misluk"}, | |
34 | {"unknown", "onbekend"}, | |
35 | {"Tunnels", "Tonnels"}, | |
36 | {"I2P tunnels", "I2P tonnels"}, | |
37 | {"SAM sessions", "SAM sessies"}, | |
38 | {"OK", "LEKKER"}, | |
39 | {"Testing", "Besig om te toets"}, | |
40 | {"Firewalled", "Vuurmuur'd"}, | |
41 | {"Unknown", "Onbekend"}, | |
42 | {"Error", "Fout"}, | |
43 | {"Offline", "Aflyn"}, | |
44 | {"Uptime", "Optyd"}, | |
45 | {"Network status", "Netwerk status"}, | |
46 | {"Network status v6", "Netwerk status v6"}, | |
47 | {"Family", "Familie"}, | |
48 | {"Received", "Ontvang"}, | |
49 | {"Sent", "Gestuur"}, | |
50 | {"Hidden content. Press on text to see.", "Hidden content. Druk om te sien."}, | |
51 | {"Router Ident", "Router Ident"}, | |
52 | {"Router Family", "Router Familie"}, | |
53 | {"Enabled", "Geaktiveer"}, | |
54 | {"Disabled", "Gedeaktiveer"}, | |
55 | {"Change", "Verander"}, | |
56 | {"Change language", "Verander taal"}, | |
57 | {"Description", "Beskrywing"}, | |
58 | {"Submit", "Stuur"}, | |
59 | {"Proxy error", "Proxy-fout"}, | |
60 | {"Host", "Gasheer"}, | |
61 | {"", ""}, | |
62 | }; | |
63 | ||
64 | static std::map<std::string, std::vector<std::string>> plurals | |
65 | { | |
66 | {"days", {"dag", "dae"}}, | |
67 | {"hours", {"uur", "ure"}}, | |
68 | {"minutes", {"minuut", "minute"}}, | |
69 | {"seconds", {"seconde", "sekondes"}}, | |
70 | {"", {"", ""}}, | |
71 | }; | |
72 | ||
73 | std::shared_ptr<const i2p::i18n::Locale> GetLocale() | |
74 | { | |
75 | return std::make_shared<i2p::i18n::Locale>(language, strings, plurals, [] (int n)->int { return plural(n); }); | |
76 | } | |
77 | ||
78 | } // language | |
79 | } // i18n | |
80 | } // i2p |
0 | /* | |
1 | * Copyright (c) 2021, The PurpleI2P Project | |
2 | * | |
3 | * This file is part of Purple i2pd project and licensed under BSD3 | |
4 | * | |
5 | * See full license text in LICENSE file at top of project tree | |
6 | */ | |
7 | ||
8 | #include <map> | |
9 | #include <vector> | |
10 | #include <string> | |
11 | #include <memory> | |
12 | #include "I18N.h" | |
13 | ||
14 | // English localization file | |
15 | // This is an example translation file without strings in it. | |
16 | ||
17 | namespace i2p | |
18 | { | |
19 | namespace i18n | |
20 | { | |
21 | namespace english // language namespace | |
22 | { | |
23 | // language name in lowercase | |
24 | static std::string language = "english"; | |
25 | ||
26 | // See for language plural forms here: | |
27 | // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html | |
28 | static int plural (int n) { | |
29 | return n != 1 ? 1 : 0; | |
30 | } | |
31 | ||
32 | static std::map<std::string, std::string> strings | |
33 | { | |
34 | {"", ""}, | |
35 | }; | |
36 | ||
37 | static std::map<std::string, std::vector<std::string>> plurals | |
38 | { | |
39 | {"", {"", ""}}, | |
40 | }; | |
41 | ||
42 | std::shared_ptr<const i2p::i18n::Locale> GetLocale() | |
43 | { | |
44 | return std::make_shared<i2p::i18n::Locale>(language, strings, plurals, [] (int n)->int { return plural(n); }); | |
45 | } | |
46 | ||
47 | } // language | |
48 | } // i18n | |
49 | } // i2p |
0 | /* | |
1 | * Copyright (c) 2021, The PurpleI2P Project | |
2 | * | |
3 | * This file is part of Purple i2pd project and licensed under BSD3 | |
4 | * | |
5 | * See full license text in LICENSE file at top of project tree | |
6 | */ | |
7 | ||
8 | #ifndef __I18N_H__ | |
9 | #define __I18N_H__ | |
10 | ||
11 | #include "RouterContext.h" | |
12 | ||
13 | namespace i2p | |
14 | { | |
15 | namespace i18n | |
16 | { | |
17 | inline void SetLanguage(const std::string &lang) | |
18 | { | |
19 | const auto it = i2p::i18n::languages.find(lang); | |
20 | if (it == i2p::i18n::languages.end()) // fallback | |
21 | i2p::context.SetLanguage (i2p::i18n::english::GetLocale()); | |
22 | else | |
23 | i2p::context.SetLanguage (it->second.LocaleFunc()); | |
24 | } | |
25 | ||
26 | inline std::string translate (const std::string& arg) | |
27 | { | |
28 | return i2p::context.GetLanguage ()->GetString (arg); | |
29 | } | |
30 | ||
31 | inline std::string translate (const std::string& arg, const std::string& arg2, const int& n) | |
32 | { | |
33 | return i2p::context.GetLanguage ()->GetPlural (arg, arg2, n); | |
34 | } | |
35 | } // i18n | |
36 | } // i2p | |
37 | ||
38 | template<typename... TArgs> | |
39 | std::string tr (TArgs&&... args) | |
40 | { | |
41 | return i2p::i18n::translate(std::forward<TArgs>(args)...); | |
42 | } | |
43 | ||
44 | #endif // __I18N_H__ |
0 | /* | |
1 | * Copyright (c) 2021, The PurpleI2P Project | |
2 | * | |
3 | * This file is part of Purple i2pd project and licensed under BSD3 | |
4 | * | |
5 | * See full license text in LICENSE file at top of project tree | |
6 | */ | |
7 | ||
8 | #ifndef __I18N_LANGS_H__ | |
9 | #define __I18N_LANGS_H__ | |
10 | ||
11 | namespace i2p | |
12 | { | |
13 | namespace i18n | |
14 | { | |
15 | class Locale | |
16 | { | |
17 | public: | |
18 | Locale ( | |
19 | const std::string& language, | |
20 | const std::map<std::string, std::string>& strings, | |
21 | const std::map<std::string, std::vector<std::string>>& plurals, | |
22 | std::function<int(int)> formula | |
23 | ): m_Language (language), m_Strings (strings), m_Plurals (plurals), m_Formula (formula) { }; | |
24 | ||
25 | // Get activated language name for webconsole | |
26 | std::string GetLanguage() const | |
27 | { | |
28 | return m_Language; | |
29 | } | |
30 | ||
31 | std::string GetString (const std::string& arg) const | |
32 | { | |
33 | const auto it = m_Strings.find(arg); | |
34 | if (it == m_Strings.end()) | |
35 | { | |
36 | return arg; | |
37 | } | |
38 | else | |
39 | { | |
40 | return it->second; | |
41 | } | |
42 | } | |
43 | ||
44 | std::string GetPlural (const std::string& arg, const std::string& arg2, const int& n) const | |
45 | { | |
46 | const auto it = m_Plurals.find(arg2); | |
47 | if (it == m_Plurals.end()) // not found, fallback to english | |
48 | { | |
49 | return n == 1 ? arg : arg2; | |
50 | } | |
51 | else | |
52 | { | |
53 | int form = m_Formula(n); | |
54 | return it->second[form]; | |
55 | } | |
56 | } | |
57 | ||
58 | private: | |
59 | const std::string m_Language; | |
60 | const std::map<std::string, std::string> m_Strings; | |
61 | const std::map<std::string, std::vector<std::string>> m_Plurals; | |
62 | std::function<int(int)> m_Formula; | |
63 | }; | |
64 | ||
65 | struct langData | |
66 | { | |
67 | std::string LocaleName; // localized name | |
68 | std::string ShortCode; // short language code, like "en" | |
69 | std::function<std::shared_ptr<const i2p::i18n::Locale> (void)> LocaleFunc; | |
70 | }; | |
71 | ||
72 | // Add localization here with language name as namespace | |
73 | namespace afrikaans { std::shared_ptr<const i2p::i18n::Locale> GetLocale (); } | |
74 | namespace english { std::shared_ptr<const i2p::i18n::Locale> GetLocale (); } | |
75 | namespace russian { std::shared_ptr<const i2p::i18n::Locale> GetLocale (); } | |
76 | namespace turkmen { std::shared_ptr<const i2p::i18n::Locale> GetLocale (); } | |
77 | namespace ukrainian { std::shared_ptr<const i2p::i18n::Locale> GetLocale (); } | |
78 | namespace uzbek { std::shared_ptr<const i2p::i18n::Locale> GetLocale (); } | |
79 | ||
80 | /** | |
81 | * That map contains international language name lower-case and name in it's language | |
82 | */ | |
83 | static std::map<std::string, langData> languages | |
84 | { | |
85 | { "afrikaans", {"Afrikaans", "af", i2p::i18n::afrikaans::GetLocale} }, | |
86 | { "english", {"English", "en", i2p::i18n::english::GetLocale} }, | |
87 | { "russian", {"русский язык", "ru", i2p::i18n::russian::GetLocale} }, | |
88 | { "turkmen", {"türkmen dili", "tk", i2p::i18n::turkmen::GetLocale} }, | |
89 | { "ukrainian", {"украї́нська мо́ва", "uk", i2p::i18n::ukrainian::GetLocale} }, | |
90 | { "uzbek", {"Oʻzbek", "uz", i2p::i18n::uzbek::GetLocale} }, | |
91 | }; | |
92 | ||
93 | } // i18n | |
94 | } // i2p | |
95 | ||
96 | #endif // __I18N_LANGS_H__ |
0 | /* | |
1 | * Copyright (c) 2021, The PurpleI2P Project | |
2 | * | |
3 | * This file is part of Purple i2pd project and licensed under BSD3 | |
4 | * | |
5 | * See full license text in LICENSE file at top of project tree | |
6 | */ | |
7 | ||
8 | #include <map> | |
9 | #include <vector> | |
10 | #include <string> | |
11 | #include <memory> | |
12 | #include "I18N.h" | |
13 | ||
14 | // Russian localization file | |
15 | ||
16 | namespace i2p | |
17 | { | |
18 | namespace i18n | |
19 | { | |
20 | namespace russian // language namespace | |
21 | { | |
22 | // language name in lowercase | |
23 | static std::string language = "russian"; | |
24 | ||
25 | // See for language plural forms here: | |
26 | // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html | |
27 | static int plural (int n) { | |
28 | return n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2; | |
29 | } | |
30 | ||
31 | static std::map<std::string, std::string> strings | |
32 | { | |
33 | {"KiB", "КиБ"}, | |
34 | {"MiB", "МиБ"}, | |
35 | {"GiB", "ГиБ"}, | |
36 | {"building", "строится"}, | |
37 | {"failed", "неудачный"}, | |
38 | {"expiring", "истекает"}, | |
39 | {"established", "работает"}, | |
40 | {"unknown", "неизвестно"}, | |
41 | {"exploratory", "исследовательский"}, | |
42 | {"<b>i2pd</b> webconsole", "Веб-консоль <b>i2pd</b>"}, | |
43 | {"Main page", "Главная"}, | |
44 | {"Router commands", "Команды роутера"}, | |
45 | {"Local Destinations", "Локальные назначения"}, | |
46 | {"LeaseSets", "Лизсеты"}, | |
47 | {"Tunnels", "Туннели"}, | |
48 | {"Transit Tunnels", "Транзитные туннели"}, | |
49 | {"Transports", "Транспорты"}, | |
50 | {"I2P tunnels", "I2P туннели"}, | |
51 | {"SAM sessions", "SAM сессии"}, | |
52 | {"ERROR", "ОШИБКА"}, | |
53 | {"OK", "OK"}, | |
54 | {"Testing", "Тестирование"}, | |
55 | {"Firewalled", "Заблокировано извне"}, | |
56 | {"Unknown", "Неизвестно"}, | |
57 | {"Proxy", "Прокси"}, | |
58 | {"Mesh", "MESH-сеть"}, | |
59 | {"Error", "Ошибка"}, | |
60 | {"Clock skew", "Не точное время"}, | |
61 | {"Offline", "Оффлайн"}, | |
62 | {"Symmetric NAT", "Симметричный NAT"}, | |
63 | {"Uptime", "В сети"}, | |
64 | {"Network status", "Сетевой статус"}, | |
65 | {"Network status v6", "Сетевой статус v6"}, | |
66 | {"Stopping in", "Остановка через"}, | |
67 | {"Family", "Семейство"}, | |
68 | {"Tunnel creation success rate", "Успешно построенных туннелей"}, | |
69 | {"Received", "Получено"}, | |
70 | {"KiB/s", "КиБ/с"}, | |
71 | {"Sent", "Отправлено"}, | |
72 | {"Transit", "Транзит"}, | |
73 | {"Data path", "Путь к данным"}, | |
74 | {"Hidden content. Press on text to see.", "Скрытый контент. Нажмите на текст чтобы отобразить."}, | |
75 | {"Router Ident", "Идентификатор роутера"}, | |
76 | {"Router Family", "Семейство роутера"}, | |
77 | {"Router Caps", "Флаги роутера"}, | |
78 | {"Version", "Версия"}, | |
79 | {"Our external address", "Наш внешний адрес"}, | |
80 | {"supported", "поддерживается"}, | |
81 | {"Routers", "Роутеры"}, | |
82 | {"Floodfills", "Флудфилы"}, | |
83 | {"Client Tunnels", "Клиентские туннели"}, | |
84 | {"Services", "Сервисы"}, | |
85 | {"Enabled", "Включено"}, | |
86 | {"Disabled", "Выключено"}, | |
87 | {"Encrypted B33 address", "Шифрованные B33 адреса"}, | |
88 | {"Address registration line", "Строка регистрации адреса"}, | |
89 | {"Domain", "Домен"}, | |
90 | {"Generate", "Сгенерировать"}, | |
91 | {"<b>Note:</b> result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", "<b>Примечание:</b> полученная строка может быть использована только для регистрации доменов второго уровня (example.i2p). Для регистрации поддоменов используйте i2pd-tools."}, | |
92 | {"Address", "Адрес"}, | |
93 | {"Type", "Тип"}, | |
94 | {"EncType", "ТипШифр"}, | |
95 | {"Inbound tunnels", "Входящие туннели"}, | |
96 | {"ms", "мс"}, | |
97 | {"Outbound tunnels", "Исходящие туннели"}, | |
98 | {"Tags", "Теги"}, | |
99 | {"Incoming", "Входящие"}, | |
100 | {"Outgoing", "Исходящие"}, | |
101 | {"Destination", "Назначение"}, | |
102 | {"Amount", "Количество"}, | |
103 | {"Incoming Tags", "Входящие теги"}, | |
104 | {"Tags sessions", "Сессии тегов"}, | |
105 | {"Status", "Статус"}, | |
106 | {"Local Destination", "Локальное назначение"}, | |
107 | {"Streams", "Стримы"}, | |
108 | {"Close stream", "Закрыть стрим"}, | |
109 | {"I2CP session not found", "I2CP сессия не найдена"}, | |
110 | {"I2CP is not enabled", "I2CP не включен"}, | |
111 | {"Invalid", "Некорректный"}, | |
112 | {"Store type", "Тип хранилища"}, | |
113 | {"Expires", "Истекает"}, | |
114 | {"Non Expired Leases", "Не истекшие Lease-ы"}, | |
115 | {"Gateway", "Шлюз"}, | |
116 | {"TunnelID", "ID туннеля"}, | |
117 | {"EndDate", "Заканчивается"}, | |
118 | {"not floodfill", "не флудфил"}, | |
119 | {"Queue size", "Размер очереди"}, | |
120 | {"Run peer test", "Запустить тестирование"}, | |
121 | {"Decline transit tunnels", "Отклонять транзитные туннели"}, | |
122 | {"Accept transit tunnels", "Принимать транзитные туннели"}, | |
123 | {"Cancel graceful shutdown", "Отменить плавную остановку"}, | |
124 | {"Start graceful shutdown", "Запустить плавную остановку"}, | |
125 | {"Force shutdown", "Принудительная остановка"}, | |
126 | {"Reload external CSS styles", "Перезагрузить внешние CSS стили"}, | |
127 | {"<b>Note:</b> any action done here are not persistent and not changes your config files.", "<b>Примечание:</b> любое действие произведенное здесь не является постоянным и не изменяет ваши конфигурационные файлы."}, | |
128 | {"Logging level", "Уровень логирования"}, | |
129 | {"Transit tunnels limit", "Лимит транзитных туннелей"}, | |
130 | {"Change", "Изменить"}, | |
131 | {"Change language", "Изменение языка"}, | |
132 | {"no transit tunnels currently built", "нет построенных транзитных туннелей"}, | |
133 | {"SAM disabled", "SAM выключен"}, | |
134 | {"no sessions currently running", "нет запущенных сессий"}, | |
135 | {"SAM session not found", "SAM сессия не найдена"}, | |
136 | {"SAM Session", "SAM сессия"}, | |
137 | {"Server Tunnels", "Серверные туннели"}, | |
138 | {"Client Forwards", "Клиентские перенаправления"}, | |
139 | {"Server Forwards", "Серверные перенаправления"}, | |
140 | {"Unknown page", "Неизвестная страница"}, | |
141 | {"Invalid token", "Неверный токен"}, | |
142 | {"SUCCESS", "УСПЕШНО"}, | |
143 | {"Stream closed", "Стрим закрыт"}, | |
144 | {"Stream not found or already was closed", "Стрим не найден или уже закрыт"}, | |
145 | {"Destination not found", "Точка назначения не найдена"}, | |
146 | {"StreamID can't be null", "StreamID не может быть пустым"}, | |
147 | {"Return to destination page", "Вернуться на страницу точки назначения"}, | |
148 | {"You will be redirected in 5 seconds", "Вы будете переадресованы через 5 секунд"}, | |
149 | {"Transit tunnels count must not exceed 65535", "Число транзитных туннелей не должно превышать 65535"}, | |
150 | {"Back to commands list", "Вернуться к списку команд"}, | |
151 | {"Register at reg.i2p", "Зарегистрировать на reg.i2p"}, | |
152 | {"Description", "Описание"}, | |
153 | {"A bit information about service on domain", "Немного информации о сервисе на домене"}, | |
154 | {"Submit", "Отправить"}, | |
155 | {"Domain can't end with .b32.i2p", "Домен не может заканчиваться на .b32.i2p"}, | |
156 | {"Domain must end with .i2p", "Домен должен заканчиваться на .i2p"}, | |
157 | {"Such destination is not found", "Такая точка назначения не найдена"}, | |
158 | {"Unknown command", "Неизвестная команда"}, | |
159 | {"Command accepted", "Команда принята"}, | |
160 | {"Proxy error", "Ошибка прокси"}, | |
161 | {"Proxy info", "Информация прокси"}, | |
162 | {"Proxy error: Host not found", "Ошибка прокси: Узел не найден"}, | |
163 | {"Remote host not found in router's addressbook", "Запрошенный узел не найден в адресной книге роутера"}, | |
164 | {"You may try to find this host on jump services below", "Вы можете попробовать найти узел через джамп сервисы ниже"}, | |
165 | {"Invalid request", "Некорректный запрос"}, | |
166 | {"Proxy unable to parse your request", "Прокси не может разобрать ваш запрос"}, | |
167 | {"addresshelper is not supported", "addresshelper не поддерживается"}, | |
168 | {"Host", "Узел"}, | |
169 | {"added to router's addressbook from helper", "добавлен в адресную книгу роутера через хелпер"}, | |
170 | {"Click here to proceed:", "Нажмите здесь, чтобы продолжить:"}, | |
171 | {"Continue", "Продолжить"}, | |
172 | {"Addresshelper found", "Найден addresshelper"}, | |
173 | {"already in router's addressbook", "уже в адресной книге роутера"}, | |
174 | {"Click here to update record:", "Нажмите здесь, чтобы обновить запись:"}, | |
175 | {"invalid request uri", "некорректный URI запроса"}, | |
176 | {"Can't detect destination host from request", "Не удалось определить адрес назначения из запроса"}, | |
177 | {"Outproxy failure", "Ошибка внешнего прокси"}, | |
178 | {"bad outproxy settings", "некорректные настройки внешнего прокси"}, | |
179 | {"not inside I2P network, but outproxy is not enabled", "не в I2P сети, но внешний прокси не включен"}, | |
180 | {"unknown outproxy url", "неизвестный URL внешнего прокси"}, | |
181 | {"cannot resolve upstream proxy", "не удается определить вышестоящий прокси"}, | |
182 | {"hostname too long", "имя хоста слишком длинное"}, | |
183 | {"cannot connect to upstream socks proxy", "не удается подключиться к вышестоящему SOCKS прокси"}, | |
184 | {"Cannot negotiate with socks proxy", "Не удается договориться с вышестоящим SOCKS прокси"}, | |
185 | {"CONNECT error", "Ошибка CONNECT запроса"}, | |
186 | {"Failed to Connect", "Не удалось подключиться"}, | |
187 | {"socks proxy error", "ошибка SOCKS прокси"}, | |
188 | {"failed to send request to upstream", "не удалось отправить запрос вышестоящему прокси"}, | |
189 | {"No Reply From socks proxy", "Нет ответа от SOCKS прокси сервера"}, | |
190 | {"cannot connect", "не удалось подключиться"}, | |
191 | {"http out proxy not implemented", "поддержка внешнего HTTP прокси сервера не реализована"}, | |
192 | {"cannot connect to upstream http proxy", "не удалось подключиться к вышестоящему HTTP прокси серверу"}, | |
193 | {"Host is down", "Узел недоступен"}, | |
194 | {"Can't create connection to requested host, it may be down. Please try again later.", "Не удалось установить соединение к запрошенному узлу, возможно он не в сети. Попробуйте повторить запрос позже."}, | |
195 | {"", ""}, | |
196 | }; | |
197 | ||
198 | static std::map<std::string, std::vector<std::string>> plurals | |
199 | { | |
200 | {"days", {"день", "дня", "дней"}}, | |
201 | {"hours", {"час", "часа", "часов"}}, | |
202 | {"minutes", {"минуту", "минуты", "минут"}}, | |
203 | {"seconds", {"секунду", "секунды", "секунд"}}, | |
204 | {"", {"", "", ""}}, | |
205 | }; | |
206 | ||
207 | std::shared_ptr<const i2p::i18n::Locale> GetLocale() | |
208 | { | |
209 | return std::make_shared<i2p::i18n::Locale>(language, strings, plurals, [] (int n)->int { return plural(n); }); | |
210 | } | |
211 | ||
212 | } // language | |
213 | } // i18n | |
214 | } // i2p |
0 | /* | |
1 | * Copyright (c) 2021, The PurpleI2P Project | |
2 | * | |
3 | * This file is part of Purple i2pd project and licensed under BSD3 | |
4 | * | |
5 | * See full license text in LICENSE file at top of project tree | |
6 | */ | |
7 | ||
8 | #include <map> | |
9 | #include <vector> | |
10 | #include <string> | |
11 | #include <memory> | |
12 | #include "I18N.h" | |
13 | ||
14 | // Turkmen localization file | |
15 | ||
16 | namespace i2p | |
17 | { | |
18 | namespace i18n | |
19 | { | |
20 | namespace turkmen // language namespace | |
21 | { | |
22 | // language name in lowercase | |
23 | static std::string language = "turkmen"; | |
24 | ||
25 | // See for language plural forms here: | |
26 | // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html | |
27 | static int plural (int n) { | |
28 | return n != 1 ? 1 : 0; | |
29 | } | |
30 | ||
31 | static std::map<std::string, std::string> strings | |
32 | { | |
33 | {"KiB", "KiB"}, | |
34 | {"MiB", "MiB"}, | |
35 | {"GiB", "GiB"}, | |
36 | {"building", "bina"}, | |
37 | {"failed", "şowsuz"}, | |
38 | {"expiring", "möhleti gutarýar"}, | |
39 | {"established", "işleýär"}, | |
40 | {"unknown", "näbelli"}, | |
41 | {"exploratory", "gözleg"}, | |
42 | {"<b>i2pd</b> webconsole", "Web konsoly <b>i2pd</b>"}, | |
43 | {"Main page", "Esasy sahypa"}, | |
44 | {"Router commands", "Marşrutizator buýruklary"}, | |
45 | {"Local Destinations", "Ýerli ýerler"}, | |
46 | {"LeaseSets", "Lizset"}, | |
47 | {"Tunnels", "Tuneller"}, | |
48 | {"Transit Tunnels", "Tranzit Tunelleri"}, | |
49 | {"Transports", "Daşamak"}, | |
50 | {"I2P tunnels", "I2P tuneller"}, | |
51 | {"SAM sessions", "SAM Sessiýasy"}, | |
52 | {"ERROR", "Ýalňyşlyk"}, | |
53 | {"OK", "OK"}, | |
54 | {"Testing", "Synag etmek"}, | |
55 | {"Firewalled", "Daşynda petiklendi"}, | |
56 | {"Unknown", "Näbelli"}, | |
57 | {"Proxy", "Proksi"}, | |
58 | {"Mesh", "MESH-tor"}, | |
59 | {"Error", "Ýalňyşlyk"}, | |
60 | {"Clock skew", "Takyk wagt däl"}, | |
61 | {"Offline", "Awtonom"}, | |
62 | {"Symmetric NAT", "Simmetriklik NAT"}, | |
63 | {"Uptime", "Onlaýn onlaýn sözlügi"}, | |
64 | {"Network status", "Tor ýagdaýy"}, | |
65 | {"Network status v6", "Tor ýagdaýy v6"}, | |
66 | {"Stopping in", "Soň duruň"}, | |
67 | {"Family", "Maşgala"}, | |
68 | {"Tunnel creation success rate", "Gurlan teneller üstünlikli gurlan teneller"}, | |
69 | {"Received", "Alnan"}, | |
70 | {"KiB/s", "KiB/s"}, | |
71 | {"Sent", "Ýerleşdirildi"}, | |
72 | {"Transit", "Tranzit"}, | |
73 | {"Data path", "Maglumat ýoly"}, | |
74 | {"Hidden content. Press on text to see.", "Gizlin mazmun. Görkezmek üçin tekste basyň."}, | |
75 | {"Router Ident", "Marşrutly kesgitleýji"}, | |
76 | {"Router Family", "Marşrutler maşgalasy"}, | |
77 | {"Router Caps", "Baýdaklar marşruteri"}, | |
78 | {"Version", "Wersiýasy"}, | |
79 | {"Our external address", "Daşarky salgymyz"}, | |
80 | {"supported", "goldanýar"}, | |
81 | {"Routers", "Marşrutizatorlar"}, | |
82 | {"Floodfills", "Fludfillar"}, | |
83 | {"Client Tunnels", "Müşderi tunelleri"}, | |
84 | {"Services", "Hyzmatlar"}, | |
85 | {"Enabled", "Goşuldy"}, | |
86 | {"Disabled", "Öçürildi"}, | |
87 | {"Encrypted B33 address", "Şifrlenen B33 salgylar"}, | |
88 | {"Address registration line", "Hasaba alyş salgysy"}, | |
89 | {"Domain", "Domen"}, | |
90 | {"Generate", "Öndürmek"}, | |
91 | {"<b>Note:</b> result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", "<b>Bellik:</b> Alnan setir diňe ikinji derejeli domenleri bellige almak üçin ulanylyp bilner (example.i2p). Subýutmalary hasaba almak üçin i2pd ulanyň-tools."}, | |
92 | {"Address", "Salgysy"}, | |
93 | {"Type", "Görnüş"}, | |
94 | {"EncType", "Şifrlemek görnüşi"}, | |
95 | {"Inbound tunnels", "Gelýän tuneller"}, | |
96 | {"ms", "ms"}, | |
97 | {"Outbound tunnels", "Çykýan tuneller"}, | |
98 | {"Tags", "Bellikler"}, | |
99 | {"Incoming", "Gelýän"}, | |
100 | {"Outgoing", "Çykýan"}, | |
101 | {"Destination", "Maksat"}, | |
102 | {"Amount", "Sany"}, | |
103 | {"Incoming Tags", "Gelýän bellikler"}, | |
104 | {"Tags sessions", "Sapaklar bellikler"}, | |
105 | {"Status", "Ýagdaýy"}, | |
106 | {"Local Destination", "Ýerli maksat"}, | |
107 | {"Streams", "Strimlary"}, | |
108 | {"Close stream", "Yap strim"}, | |
109 | {"I2CP session not found", "I2CP Sessiýa tapylmady"}, | |
110 | {"I2CP is not enabled", "I2CP goşulmaýar"}, | |
111 | {"Invalid", "Nädogry"}, | |
112 | {"Store type", "Ammar görnüşi"}, | |
113 | {"Expires", "Möhleti gutarýar"}, | |
114 | {"Non Expired Leases", "Möhleti gutarmady Lizsetlary"}, | |
115 | {"Gateway", "Derweze"}, | |
116 | {"TunnelID", "Tuneliň ID"}, | |
117 | {"EndDate", "Gutarýar"}, | |
118 | {"not floodfill", "fludfil däl"}, | |
119 | {"Queue size", "Nobatyň ululygy"}, | |
120 | {"Run peer test", "Synag başlaň"}, | |
121 | {"Decline transit tunnels", "Tranzit tunellerini ret ediň"}, | |
122 | {"Accept transit tunnels", "Tranzit tunellerini alyň"}, | |
123 | {"Cancel graceful shutdown", "Tekiz durmagy ýatyryň"}, | |
124 | {"Start graceful shutdown", "Tekiz durmak"}, | |
125 | {"Force shutdown", "Mejbury duralga"}, | |
126 | {"Reload external CSS styles", "Daşarky CSS stillerini täzeden ýükläň"}, | |
127 | {"<b>Note:</b> any action done here are not persistent and not changes your config files.", "<b>Bellik:</b> Bu ýerde öndürilen islendik çäre hemişelik däl we konfigurasiýa faýllaryňyzy üýtgetmeýär."}, | |
128 | {"Logging level", "Giriş derejesi"}, | |
129 | {"Transit tunnels limit", "Tranzit tunelleriniň çägi"}, | |
130 | {"Change", "Üýtgetmek"}, | |
131 | {"Change language", "Dil üýtgetmek"}, | |
132 | {"no transit tunnels currently built", "gurlan tranzit tunelleri ýok"}, | |
133 | {"SAM disabled", "SAM öçürilen"}, | |
134 | {"no sessions currently running", "başlamagyň sessiýalary ýok"}, | |
135 | {"SAM session not found", "SAM Sessiýa tapylmady"}, | |
136 | {"SAM Session", "SAM Sessiýa"}, | |
137 | {"Server Tunnels", "Serwer tunelleri"}, | |
138 | {"Client Forwards", "Müşderi gönükdirýär"}, | |
139 | {"Server Forwards", "Serweriň täzeden düzlüleri"}, | |
140 | {"Unknown page", "Näbelli sahypa"}, | |
141 | {"Invalid token", "Nädogry token"}, | |
142 | {"SUCCESS", "Üstünlikli"}, | |
143 | {"Stream closed", "Strim ýapyk"}, | |
144 | {"Stream not found or already was closed", "Strim tapylmady ýa-da eýýäm ýapyldy"}, | |
145 | {"Destination not found", "Niýetlenen ýeri tapylmady"}, | |
146 | {"StreamID can't be null", "StreamID boş bolup bilmez"}, | |
147 | {"Return to destination page", "Barmaly nokadynyň nokadyna gaýdyp geliň"}, | |
148 | {"You will be redirected in 5 seconds", "5 sekuntdan soň täzeden ugrukdyrylarsyňyz"}, | |
149 | {"Transit tunnels count must not exceed 65535", "Tranzit tagtalaryň sany 65535-den geçmeli däldir"}, | |
150 | {"Back to commands list", "Topar sanawyna dolan"}, | |
151 | {"Register at reg.i2p", "Reg.i2P-de hasaba duruň"}, | |
152 | {"Description", "Beýany"}, | |
153 | {"A bit information about service on domain", "Domendäki hyzmat barada käbir maglumatlar"}, | |
154 | {"Submit", "Iber"}, | |
155 | {"Domain can't end with .b32.i2p", "Domain .b32.i2p bilen gutaryp bilmez"}, | |
156 | {"Domain must end with .i2p", "Domeni .i2p bilen gutarmaly"}, | |
157 | {"Such destination is not found", "Bu barmaly ýer tapylmady"}, | |
158 | {"Unknown command", "Näbelli topar"}, | |
159 | {"Command accepted", "Topar kabul edilýär"}, | |
160 | {"Proxy error", "Proksi ýalňyşlygy"}, | |
161 | {"Proxy info", "Proksi maglumat"}, | |
162 | {"Proxy error: Host not found", "Proksi ýalňyşlygy: Host tapylmady"}, | |
163 | {"Remote host not found in router's addressbook", "Uzakdaky öý eýesi marşruteriň salgy kitabynda tapylmady"}, | |
164 | {"You may try to find this host on jump services below", "Aşakdaky böküş hyzmatlarynda bu öý eýesini tapmaga synanyşyp bilersiňiz"}, | |
165 | {"Invalid request", "Nädogry haýyş"}, | |
166 | {"Proxy unable to parse your request", "Proksi haýyşyňyzy derňäp bilmeýär"}, | |
167 | {"addresshelper is not supported", "Salgylandyryjy goldanok"}, | |
168 | {"Host", "Adres"}, | |
169 | {"added to router's addressbook from helper", "marşruteriň adresini kömekçiden goşdy"}, | |
170 | {"Click here to proceed:", "Dowam etmek bu ýerde basyň:"}, | |
171 | {"Continue", "Dowam et"}, | |
172 | {"Addresshelper found", "Forgelper tapyldy"}, | |
173 | {"already in router's addressbook", "marşruteriň adres kitaby"}, | |
174 | {"Click here to update record:", "Recordazgyny täzelemek üçin bu ýerde basyň:"}, | |
175 | {"invalid request uri", "nädogry haýyş URI"}, | |
176 | {"Can't detect destination host from request", "Haýyşdan barmaly ýerini tapyp bilemok"}, | |
177 | {"Outproxy failure", "Daşarky proksi ýalňyşlyk"}, | |
178 | {"bad outproxy settings", "daşarky daşarky proksi sazlamalary nädogry"}, | |
179 | {"not inside I2P network, but outproxy is not enabled", "I2P torunda däl, ýöne daşarky proksi goşulmaýar"}, | |
180 | {"unknown outproxy url", "näbelli daşarky proksi URL"}, | |
181 | {"cannot resolve upstream proxy", "has ýokary proksi kesgitläp bilmeýär"}, | |
182 | {"hostname too long", "hoster eýesi ady gaty uzyn"}, | |
183 | {"cannot connect to upstream socks proxy", "ýokary jorap SOCKS proksi bilen birigip bolmaýar"}, | |
184 | {"Cannot negotiate with socks proxy", "Iň ýokary jorap SOCKS proksi bilen ylalaşyp bilmeýärler"}, | |
185 | {"CONNECT error", "Bagyr haýyşy säwligi"}, | |
186 | {"Failed to Connect", "Birikdirip bilmedi"}, | |
187 | {"socks proxy error", "socks proksi ýalňyşlygy"}, | |
188 | {"failed to send request to upstream", "öý eýesi proksi üçin haýyş iberip bilmedi"}, | |
189 | {"No Reply From socks proxy", "Jorap proksi serwerinden hiç hili jogap ýok"}, | |
190 | {"cannot connect", "birikdirip bilmedi"}, | |
191 | {"http out proxy not implemented", "daşarky HTTP proksi serwerini goldamak amala aşyrylmaýar"}, | |
192 | {"cannot connect to upstream http proxy", "ýokary akym HTTP proksi serwerine birigip bilmedi"}, | |
193 | {"Host is down", "Salgy elýeterli däl"}, | |
194 | {"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ň."}, | |
195 | {"", ""}, | |
196 | }; | |
197 | ||
198 | static std::map<std::string, std::vector<std::string>> plurals | |
199 | { | |
200 | // ShowUptime | |
201 | {"days", {"gün", "gün"}}, | |
202 | {"hours", {"sagat", "sagat"}}, | |
203 | {"minutes", {"minut", "minut"}}, | |
204 | {"seconds", {"sekunt", "sekunt"}}, | |
205 | {"", {"", ""}}, | |
206 | }; | |
207 | ||
208 | std::shared_ptr<const i2p::i18n::Locale> GetLocale() | |
209 | { | |
210 | return std::make_shared<i2p::i18n::Locale>(language, strings, plurals, [] (int n)->int { return plural(n); }); | |
211 | } | |
212 | ||
213 | } // language | |
214 | } // i18n | |
215 | } // i2p |
0 | /* | |
1 | * Copyright (c) 2021, The PurpleI2P Project | |
2 | * | |
3 | * This file is part of Purple i2pd project and licensed under BSD3 | |
4 | * | |
5 | * See full license text in LICENSE file at top of project tree | |
6 | */ | |
7 | ||
8 | #include <map> | |
9 | #include <vector> | |
10 | #include <string> | |
11 | #include <memory> | |
12 | #include "I18N.h" | |
13 | ||
14 | // Ukrainian localization file | |
15 | ||
16 | namespace i2p | |
17 | { | |
18 | namespace i18n | |
19 | { | |
20 | namespace ukrainian // language namespace | |
21 | { | |
22 | // language name in lowercase | |
23 | static std::string language = "ukrainian"; | |
24 | ||
25 | // See for language plural forms here: | |
26 | // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html | |
27 | static int plural (int n) { | |
28 | return n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2; | |
29 | } | |
30 | ||
31 | static std::map<std::string, std::string> strings | |
32 | { | |
33 | {"KiB", "КіБ"}, | |
34 | {"MiB", "МіБ"}, | |
35 | {"GiB", "ГіБ"}, | |
36 | {"building", "будується"}, | |
37 | {"failed", "невдалий"}, | |
38 | {"expiring", "завершується"}, | |
39 | {"established", "працює"}, | |
40 | {"unknown", "невідомо"}, | |
41 | {"exploratory", "дослідницький"}, | |
42 | {"<b>i2pd</b> webconsole", "Веб-консоль <b>i2pd</b>"}, | |
43 | {"Main page", "Головна"}, | |
44 | {"Router commands", "Команди маршрутизатора"}, | |
45 | {"Local Destinations", "Локальні Призначення"}, | |
46 | {"LeaseSets", "Лізсети"}, | |
47 | {"Tunnels", "Тунелі"}, | |
48 | {"Transit Tunnels", "Транзитні Тунелі"}, | |
49 | {"Transports", "Транспорти"}, | |
50 | {"I2P tunnels", "I2P тунелі"}, | |
51 | {"SAM sessions", "SAM сесії"}, | |
52 | {"ERROR", "ПОМИЛКА"}, | |
53 | {"OK", "OK"}, | |
54 | {"Testing", "Тестування"}, | |
55 | {"Firewalled", "Заблоковано ззовні"}, | |
56 | {"Unknown", "Невідомо"}, | |
57 | {"Proxy", "Проксі"}, | |
58 | {"Mesh", "MESH-мережа"}, | |
59 | {"Error", "Помилка"}, | |
60 | {"Clock skew", "Неточний час"}, | |
61 | {"Offline", "Офлайн"}, | |
62 | {"Symmetric NAT", "Симетричний NAT"}, | |
63 | {"Uptime", "У мережі"}, | |
64 | {"Network status", "Мережевий статус"}, | |
65 | {"Network status v6", "Мережевий статус v6"}, | |
66 | {"Stopping in", "Зупинка через"}, | |
67 | {"Family", "Сімейство"}, | |
68 | {"Tunnel creation success rate", "Успішно побудованих тунелів"}, | |
69 | {"Received", "Отримано"}, | |
70 | {"KiB/s", "КіБ/с"}, | |
71 | {"Sent", "Відправлено"}, | |
72 | {"Transit", "Транзит"}, | |
73 | {"Data path", "Шлях до даних"}, | |
74 | {"Hidden content. Press on text to see.", "Прихований вміст. Щоб відобразити, натисніть на текст."}, | |
75 | {"Router Ident", "Ідентифікатор маршрутизатора"}, | |
76 | {"Router Family", "Сімейство маршрутизатора"}, | |
77 | {"Router Caps", "Прапорці маршрутизатора"}, | |
78 | {"Version", "Версія"}, | |
79 | {"Our external address", "Наша зовнішня адреса"}, | |
80 | {"supported", "підтримується"}, | |
81 | {"Routers", "Маршрутизатори"}, | |
82 | {"Floodfills", "Флудфіли"}, | |
83 | {"Client Tunnels", "Клієнтські Тунелі"}, | |
84 | {"Services", "Сервіси"}, | |
85 | {"Enabled", "Увімкнуто"}, | |
86 | {"Disabled", "Вимкнуто"}, | |
87 | {"Encrypted B33 address", "Шифровані B33 адреси"}, | |
88 | {"Address registration line", "Рядок реєстрації адреси"}, | |
89 | {"Domain", "Домен"}, | |
90 | {"Generate", "Згенерувати"}, | |
91 | {"<b>Note:</b> result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", "<b>Примітка:</b> отриманий рядок може бути використаний тільки для реєстрації доменів другого рівня (example.i2p). Для реєстрації піддоменів використовуйте i2pd-tools."}, | |
92 | {"Address", "Адреса"}, | |
93 | {"Type", "Тип"}, | |
94 | {"EncType", "ТипШифр"}, | |
95 | {"Inbound tunnels", "Вхідні тунелі"}, | |
96 | {"ms", "мс"}, | |
97 | {"Outbound tunnels", "Вихідні тунелі"}, | |
98 | {"Tags", "Теги"}, | |
99 | {"Incoming", "Вхідні"}, | |
100 | {"Outgoing", "Вихідні"}, | |
101 | {"Destination", "Призначення"}, | |
102 | {"Amount", "Кількість"}, | |
103 | {"Incoming Tags", "Вхідні Теги"}, | |
104 | {"Tags sessions", "Сесії Тегів"}, | |
105 | {"Status", "Статус"}, | |
106 | {"Local Destination", "Локальні Призначення"}, | |
107 | {"Streams", "Потоки"}, | |
108 | {"Close stream", "Закрити потік"}, | |
109 | {"I2CP session not found", "I2CP сесія не знайдена"}, | |
110 | {"I2CP is not enabled", "I2CP не увікнуто"}, | |
111 | {"Invalid", "Некоректний"}, | |
112 | {"Store type", "Тип сховища"}, | |
113 | {"Expires", "Завершується"}, | |
114 | {"Non Expired Leases", "Не завершені Lease-и"}, | |
115 | {"Gateway", "Шлюз"}, | |
116 | {"TunnelID", "ID тунеля"}, | |
117 | {"EndDate", "Закінчується"}, | |
118 | {"not floodfill", "не флудфіл"}, | |
119 | {"Queue size", "Розмір черги"}, | |
120 | {"Run peer test", "Запустити тестування"}, | |
121 | {"Decline transit tunnels", "Відхиляти транзитні тунелі"}, | |
122 | {"Accept transit tunnels", "Ухвалювати транзитні тунелі"}, | |
123 | {"Cancel graceful shutdown", "Скасувати плавну зупинку"}, | |
124 | {"Start graceful shutdown", "Запустити плавну зупинку"}, | |
125 | {"Force shutdown", "Примусова зупинка"}, | |
126 | {"Reload external CSS styles", "Перезавантажити зовнішні стилі CSS"}, | |
127 | {"<b>Note:</b> any action done here are not persistent and not changes your config files.", "<b>Примітка:</b> будь-яка зроблена тут дія не є постійною та не змінює ваші конфігураційні файли."}, | |
128 | {"Logging level", "Рівень логування"}, | |
129 | {"Transit tunnels limit", "Обмеження транзитних тунелів"}, | |
130 | {"Change", "Змінити"}, | |
131 | {"Change language", "Змінити мову"}, | |
132 | {"no transit tunnels currently built", "немає побудованих транзитних тунелів"}, | |
133 | {"SAM disabled", "SAM вимкнуто"}, | |
134 | {"no sessions currently running", "немає запущених сесій"}, | |
135 | {"SAM session not found", "SAM сесія не знайдена"}, | |
136 | {"SAM Session", "SAM сесія"}, | |
137 | {"Server Tunnels", "Серверні Тунелі"}, | |
138 | {"Client Forwards", "Клієнтські Переспрямування"}, | |
139 | {"Server Forwards", "Серверні Переспрямування"}, | |
140 | {"Unknown page", "Невідома сторінка"}, | |
141 | {"Invalid token", "Невірний токен"}, | |
142 | {"SUCCESS", "УСПІШНО"}, | |
143 | {"Stream closed", "Потік зачинений"}, | |
144 | {"Stream not found or already was closed", "Потік не знайдений або вже зачинений"}, | |
145 | {"Destination not found", "Точка призначення не знайдена"}, | |
146 | {"StreamID can't be null", "Ідентифікатор потоку не може бути порожнім"}, | |
147 | {"Return to destination page", "Повернутися на сторінку точки призначення"}, | |
148 | {"You will be redirected in 5 seconds", "Ви будете переадресовані через 5 секунд"}, | |
149 | {"Transit tunnels count must not exceed 65535", "Кількість транзитних тунелів не повинна перевищувати 65535"}, | |
150 | {"Back to commands list", "Повернутися до списку команд"}, | |
151 | {"Register at reg.i2p", "Зареєструвати на reg.i2p"}, | |
152 | {"Description", "Опис"}, | |
153 | {"A bit information about service on domain", "Трохи інформації про сервіс на домені"}, | |
154 | {"Submit", "Надіслати"}, | |
155 | {"Domain can't end with .b32.i2p", "Домен не може закінчуватися на .b32.i2p"}, | |
156 | {"Domain must end with .i2p", "Домен повинен закінчуватися на .i2p"}, | |
157 | {"Such destination is not found", "Така точка призначення не знайдена"}, | |
158 | {"Unknown command", "Невідома команда"}, | |
159 | {"Command accepted", "Команда прийнята"}, | |
160 | {"Proxy error", "Помилка проксі"}, | |
161 | {"Proxy info", "Інформація проксі"}, | |
162 | {"Proxy error: Host not found", "Помилка проксі: Адреса не знайдена"}, | |
163 | {"Remote host not found in router's addressbook", "Віддалена адреса не знайдена в адресній книзі маршрутизатора"}, | |
164 | {"You may try to find this host on jump services below", "Ви можете спробувати знайти дану адресу на джамп сервісах нижче"}, | |
165 | {"Invalid request", "Некоректний запит"}, | |
166 | {"Proxy unable to parse your request", "Проксі не може розібрати ваш запит"}, | |
167 | {"addresshelper is not supported", "addresshelper не підтримується"}, | |
168 | {"Host", "Адреса"}, | |
169 | {"added to router's addressbook from helper", "доданий в адресну книгу маршрутизатора через хелпер"}, | |
170 | {"Click here to proceed:", "Натисніть тут щоб продовжити:"}, | |
171 | {"Continue", "Продовжити"}, | |
172 | {"Addresshelper found", "Знайдено addresshelper"}, | |
173 | {"already in router's addressbook", "вже в адресній книзі маршрутизатора"}, | |
174 | {"Click here to update record:", "Натисніть тут щоб оновити запис:"}, | |
175 | {"invalid request uri", "некоректний URI запиту"}, | |
176 | {"Can't detect destination host from request", "Не вдалось визначити адресу призначення з запиту"}, | |
177 | {"Outproxy failure", "Помилка зовнішнього проксі"}, | |
178 | {"bad outproxy settings", "некоректні налаштування зовнішнього проксі"}, | |
179 | {"not inside I2P network, but outproxy is not enabled", "не в I2P мережі, але зовнішній проксі не включений"}, | |
180 | {"unknown outproxy url", "невідомий URL зовнішнього проксі"}, | |
181 | {"cannot resolve upstream proxy", "не вдається визначити висхідний проксі"}, | |
182 | {"hostname too long", "ім'я вузла надто довге"}, | |
183 | {"cannot connect to upstream socks proxy", "не вдається підключитися до висхідного SOCKS проксі"}, | |
184 | {"Cannot negotiate with socks proxy", "Не вдається домовитися з висхідним SOCKS проксі"}, | |
185 | {"CONNECT error", "Помилка CONNECT запиту"}, | |
186 | {"Failed to Connect", "Не вдалося підключитися"}, | |
187 | {"socks proxy error", "помилка SOCKS проксі"}, | |
188 | {"failed to send request to upstream", "не вдалося відправити запит висхідному проксі"}, | |
189 | {"No Reply From socks proxy", "Немає відповіді від SOCKS проксі сервера"}, | |
190 | {"cannot connect", "не вдалося підключитися"}, | |
191 | {"http out proxy not implemented", "підтримка зовнішнього HTTP проксі сервера не реалізована"}, | |
192 | {"cannot connect to upstream http proxy", "не вдалося підключитися до висхідного HTTP проксі сервера"}, | |
193 | {"Host is down", "Вузол недоступний"}, | |
194 | {"Can't create connection to requested host, it may be down. Please try again later.", "Не вдалося встановити з'єднання до запитаного вузла, можливо він не в мережі. Спробуйте повторити запит пізніше."}, | |
195 | {"", ""}, | |
196 | }; | |
197 | ||
198 | static std::map<std::string, std::vector<std::string>> plurals | |
199 | { | |
200 | {"days", {"день", "дня", "днів"}}, | |
201 | {"hours", {"годину", "години", "годин"}}, | |
202 | {"minutes", {"хвилину", "хвилини", "хвилин"}}, | |
203 | {"seconds", {"секунду", "секунди", "секунд"}}, | |
204 | {"", {"", "", ""}}, | |
205 | }; | |
206 | ||
207 | std::shared_ptr<const i2p::i18n::Locale> GetLocale() | |
208 | { | |
209 | return std::make_shared<i2p::i18n::Locale>(language, strings, plurals, [] (int n)->int { return plural(n); }); | |
210 | } | |
211 | ||
212 | } // language | |
213 | } // i18n | |
214 | } // i2p |
0 | /* | |
1 | * Copyright (c) 2021, The PurpleI2P Project | |
2 | * | |
3 | * This file is part of Purple i2pd project and licensed under BSD3 | |
4 | * | |
5 | * See full license text in LICENSE file at top of project tree | |
6 | */ | |
7 | ||
8 | #include <map> | |
9 | #include <vector> | |
10 | #include <string> | |
11 | #include <memory> | |
12 | #include "I18N.h" | |
13 | ||
14 | // Ukrainian localization file | |
15 | ||
16 | namespace i2p | |
17 | { | |
18 | namespace i18n | |
19 | { | |
20 | namespace uzbek // language namespace | |
21 | { | |
22 | // language name in lowercase | |
23 | static std::string language = "uzbek"; | |
24 | ||
25 | // See for language plural forms here: | |
26 | // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html | |
27 | static int plural (int n) { | |
28 | return n > 1 ? 1 : 0; | |
29 | } | |
30 | ||
31 | static std::map<std::string, std::string> strings | |
32 | { | |
33 | {"KiB", "KiB"}, | |
34 | {"MiB", "MiB"}, | |
35 | {"GiB", "GiB"}, | |
36 | {"building", "qurilish"}, | |
37 | {"failed", "muvaffaqiyatsiz"}, | |
38 | {"expiring", "muddati tugaydi"}, | |
39 | {"established", "aloqa o'rnatildi"}, | |
40 | {"unknown", "noma'lum"}, | |
41 | {"exploratory", "tadqiqiy"}, | |
42 | {"<b>i2pd</b> webconsole", "<b>i2pd</b> veb -konsoli"}, | |
43 | {"Main page", "Asosiy sahifa"}, | |
44 | {"Router commands", "Router buyruqlari"}, | |
45 | {"LeaseSets", "LeaseSets"}, | |
46 | {"Tunnels", "Tunnellar"}, | |
47 | {"Transit Tunnels", "Tranzit Tunellar"}, | |
48 | {"Transports", "Transportlar"}, | |
49 | {"I2P tunnels", "I2P tunnellar"}, | |
50 | {"SAM sessions", "SAM sessiyalari"}, | |
51 | {"ERROR", "XATO"}, | |
52 | {"OK", "OK"}, | |
53 | {"Testing", "Testlash"}, | |
54 | {"Firewalled", "Xavfsizlik devori bilan himoyalangan"}, | |
55 | {"Unknown", "Notanish"}, | |
56 | {"Proxy", "Proksi"}, | |
57 | {"Mesh", "Mesh To'r"}, | |
58 | {"Error", "Xato"}, | |
59 | {"Clock skew", "Aniq vaqt emas"}, | |
60 | {"Offline", "Oflayn"}, | |
61 | {"Symmetric NAT", "Simmetrik NAT"}, | |
62 | {"Uptime", "Ish vaqti"}, | |
63 | {"Network status", "Tarmoq holati"}, | |
64 | {"Network status v6", "Tarmoq holati v6"}, | |
65 | {"Stopping in", "Ichida to'xtatish"}, | |
66 | {"Family", "Oila"}, | |
67 | {"Tunnel creation success rate", "Tunnel yaratish muvaffaqiyat darajasi"}, | |
68 | {"Received", "Qabul qilindi"}, | |
69 | {"KiB/s", "KiB/s"}, | |
70 | {"Sent", "Yuborilgan"}, | |
71 | {"Transit", "Tranzit"}, | |
72 | {"Data path", "Ma'lumotlar yo'li"}, | |
73 | {"Hidden content. Press on text to see.", "Yashirin tarkib. Ko'rish uchun matn ustida bosing."}, | |
74 | {"Router Ident", "Router identifikatori"}, | |
75 | {"Router Family", "Router Oila"}, | |
76 | {"Router Caps", "Router bayroqlari"}, | |
77 | {"Version", "Versiya"}, | |
78 | {"Our external address", "Bizning tashqi manzilimiz"}, | |
79 | {"supported", "qo'llab -quvvatlanadi"}, | |
80 | {"Routers", "Routerlar"}, | |
81 | {"Floodfills", "Floodfills"}, | |
82 | {"Client Tunnels", "Mijoz tunellari"}, | |
83 | {"Services", "Xizmatlar"}, | |
84 | {"Enabled", "Yoqilgan"}, | |
85 | {"Disabled", "O'chirilgan"}, | |
86 | {"Encrypted B33 address", "Shifrlangan B33 manzil"}, | |
87 | {"Address registration line", "Manzilni ro'yxatga olish liniyasi"}, | |
88 | {"Domain", "Domen"}, | |
89 | {"Generate", "Varatish"}, | |
90 | {"<b>Note:</b> result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", "<b>Eslatma:</b> natija satridan faqat 2LD domenlarini ro'yxatdan o'tkazish uchun foydalanish mumkin (example.i2p). Subdomenlarni ro'yxatdan o'tkazish uchun i2pd-tools dan foydalaning."}, | |
91 | {"Address", "Manzil"}, | |
92 | {"Type", "Turi"}, | |
93 | {"EncType", "ShifrlashTuri"}, | |
94 | {"Inbound tunnels", "Kirish tunnellari"}, | |
95 | {"ms", "ms"}, | |
96 | {"Outbound tunnels", "Chiquvchi tunnellar"}, | |
97 | {"Tags", "Teglar"}, | |
98 | {"Incoming", "Kiruvchi"}, | |
99 | {"Outgoing", "Chiquvchi"}, | |
100 | {"Destination", "Manzilgoh"}, | |
101 | {"Amount", "Yig'indi"}, | |
102 | {"Incoming Tags", "Kiruvchi teglar"}, | |
103 | {"Tags sessions", "Teglar sessiyalari"}, | |
104 | {"Status", "Holat"}, | |
105 | {"Streams", "Strim"}, | |
106 | {"Close stream", "Strimni o'chirish"}, | |
107 | {"I2CP session not found", "I2CP sessiyasi topilmadi"}, | |
108 | {"I2CP is not enabled", "I2CP yoqilmagan"}, | |
109 | {"Invalid", "Noto'g'ri"}, | |
110 | {"Store type", "Saqlash turi"}, | |
111 | {"Expires", "Muddati tugaydi"}, | |
112 | {"Non Expired Leases", "Muddati O'tmagan Leases"}, | |
113 | {"Gateway", "Kirish yo'li"}, | |
114 | {"TunnelID", "TunnelID"}, | |
115 | {"EndDate", "Tugash Sanasi"}, | |
116 | {"not floodfill", "floodfill emas"}, | |
117 | {"Queue size", "Navbat hajmi"}, | |
118 | {"Run peer test", "Sinovni boshlang"}, | |
119 | {"Decline transit tunnels", "Tranzit tunnellarni rad etish"}, | |
120 | {"Accept transit tunnels", "Tranzit tunnellarni qabul qilish"}, | |
121 | {"Cancel graceful shutdown", "Yumshoq to'xtashni bekor qiling"}, | |
122 | {"Start graceful shutdown", "Yumshoq to'xtashni boshlang"}, | |
123 | {"Force shutdown", "Bizning tashqi manzilimiz"}, | |
124 | {"Reload external CSS styles", "Tashqi CSS uslublarini qayta yuklang"}, | |
125 | {"<b>Note:</b> any action done here are not persistent and not changes your config files.", "<b>Eslatma:</b> bu erda qilingan har qanday harakat doimiy emas va konfiguratsiya fayllarini o'zgartirmaydi."}, | |
126 | {"Transit tunnels limit", "Tranzit tunellar chegarasi"}, | |
127 | {"Change", "O'zgartirish"}, | |
128 | {"Change language", "Tilni o'zgartirish"}, | |
129 | {"no transit tunnels currently built", "qurilgan tranzit tunnellari yo'q"}, | |
130 | {"SAM disabled", "SAM o'chirilgan"}, | |
131 | {"no sessions currently running", "hech qanday ishlaydigan sessiyalar yo'q"}, | |
132 | {"SAM session not found", "SAM sessiyasi topilmadi"}, | |
133 | {"SAM Session", "SAM sessiyasi"}, | |
134 | {"Server Tunnels", "Server Tunellari"}, | |
135 | {"Client Forwards", "Mijozlarni Yo'naltirish"}, | |
136 | {"Server Forwards", "Serverni Yo'naltirish"}, | |
137 | {"Unknown page", "Noma'lum sahifa"}, | |
138 | {"Invalid token", "Noto‘g‘ri belgi"}, | |
139 | {"SUCCESS", "Muvaffaqiyat"}, | |
140 | {"Stream closed", "Strim yopiq"}, | |
141 | {"Stream not found or already was closed", "Strim topilmadi yoki allaqachon yopilgan"}, | |
142 | {"Destination not found", "Yo'nalish topilmadi"}, | |
143 | {"StreamID can't be null", "StreamID bo'sh bo'lishi mumkin emas"}, | |
144 | {"Return to destination page", "Belgilangan sahifaga qaytish"}, | |
145 | {"You will be redirected in 5 seconds", "Siz 5 soniyada qayta yo'naltirilasiz"}, | |
146 | {"Transit tunnels count must not exceed 65535", "Tranzit tunnellar soni 65535 dan oshmasligi kerak"}, | |
147 | {"Back to commands list", "Buyruqlar ro'yxatiga qaytish"}, | |
148 | {"Register at reg.i2p", "Reg.i2p-da ro'yxatdan o'ting"}, | |
149 | {"Description", "Tavsif"}, | |
150 | {"A bit information about service on domain", "Domen xizmatlari haqida bir oz ma'lumot"}, | |
151 | {"Submit", "Yuborish"}, | |
152 | {"Domain can't end with .b32.i2p", "Domen .b32.i2p bilan tugashi mumkin emas"}, | |
153 | {"Domain must end with .i2p", "Domen .i2p bilan tugashi kerak"}, | |
154 | {"Such destination is not found", "Bunday yo'nalish topilmadi"}, | |
155 | {"Unknown command", "Noma'lum buyruq"}, | |
156 | {"Command accepted", "Buyruq qabul qilindi"}, | |
157 | {"Proxy error", "Proksi xatosi"}, | |
158 | {"Proxy info", "Proksi ma'lumotlari"}, | |
159 | {"Proxy error: Host not found", "Proksi xatosi: Xost topilmadi"}, | |
160 | {"Remote host not found in router's addressbook", "Masofaviy xost yo'riqnoma manzillar kitobida topilmadi"}, | |
161 | {"Invalid request", "Noto‘g‘ri so‘rov"}, | |
162 | {"Proxy unable to parse your request", "Proksi sizning so'rovingizni tahlil qila olmaydi"}, | |
163 | {"addresshelper is not supported", "addresshelper qo'llab -quvvatlanmaydi"}, | |
164 | {"Host", "Xost"}, | |
165 | {"Addresshelper found", "Addresshelper topildi"}, | |
166 | {"invalid request uri", "noto'g'ri URI so'rovi"}, | |
167 | {"Can't detect destination host from request", "So‘rov orqali manzil xostini aniqlab bo'lmayapti"}, | |
168 | {"Outproxy failure", "Tashqi proksi muvaffaqiyatsizligi"}, | |
169 | {"bad outproxy settings", "noto'g'ri tashqi proksi -server sozlamalari"}, | |
170 | {"not inside I2P network, but outproxy is not enabled", "I2P tarmog'ida emas, lekin tashqi proksi yoqilmagan"}, | |
171 | {"unknown outproxy url", "noma'lum outproxy url"}, | |
172 | {"cannot resolve upstream proxy", "yuqoridagi proksi -serverni aniqlab olib bolmaydi"}, | |
173 | {"hostname too long", "xost nomi juda uzun"}, | |
174 | {"cannot connect to upstream socks proxy", "yuqori soks proksi -serveriga ulanib bo'lmaydi"}, | |
175 | {"Cannot negotiate with socks proxy", "Soks proksi bilan muzokara olib bo'lmaydi"}, | |
176 | {"CONNECT error", "CONNECT xatosi"}, | |
177 | {"Failed to Connect", "Ulanmadi"}, | |
178 | {"socks proxy error", "soks proksi xatosi"}, | |
179 | {"failed to send request to upstream", "yuqori http proksi-serveriga ulanib bo'lmadi"}, | |
180 | {"No Reply From socks proxy", "Soks-proksidan javob yo'q"}, | |
181 | {"cannot connect", "ulab bo'lmaydi"}, | |
182 | {"http out proxy not implemented", "tashqi HTTP proksi -serverni qo'llab -quvvatlash amalga oshirilmagan"}, | |
183 | {"cannot connect to upstream http proxy", "yuqori http proksi-serveriga ulanib bo'lmadi"}, | |
184 | {"Host is down", "Xost ishlamayapti"}, | |
185 | {"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."}, | |
186 | {"", ""}, | |
187 | }; | |
188 | ||
189 | static std::map<std::string, std::vector<std::string>> plurals | |
190 | { | |
191 | {"days", {"kun", "kunlar"}}, | |
192 | {"hours", {"soat", "soat"}}, | |
193 | {"minutes", {"daqiqa", "daqiqalar"}}, | |
194 | {"seconds", {"soniya", "soniyalar"}}, | |
195 | {"", {"", ""}}, | |
196 | }; | |
197 | ||
198 | std::shared_ptr<const i2p::i18n::Locale> GetLocale() | |
199 | { | |
200 | return std::make_shared<i2p::i18n::Locale>(language, strings, plurals, [] (int n)->int { return plural(n); }); | |
201 | } | |
202 | ||
203 | } // language | |
204 | } // i18n | |
205 | } // i2p |
36 | 36 | ("conf", value<std::string>()->default_value(""), "Path to main i2pd config file (default: try ~/.i2pd/i2pd.conf or /var/lib/i2pd/i2pd.conf)") |
37 | 37 | ("tunconf", value<std::string>()->default_value(""), "Path to config with tunnels list and options (default: try ~/.i2pd/tunnels.conf or /var/lib/i2pd/tunnels.conf)") |
38 | 38 | ("tunnelsdir", value<std::string>()->default_value(""), "Path to extra tunnels' configs folder (default: ~/.i2pd/tunnels.d or /var/lib/i2pd/tunnels.d") |
39 | ("certsdir", value<std::string>()->default_value(""), "Path to certificates used for verifying .su3, families (default: ~/.i2pd/certificates or /var/lib/i2pd/certificates") | |
39 | 40 | ("pidfile", value<std::string>()->default_value(""), "Path to pidfile (default: ~/i2pd/i2pd.pid or /var/lib/i2pd/i2pd.pid)") |
40 | 41 | ("log", value<std::string>()->default_value(""), "Logs destination: stdout, file, syslog (stdout if not set)") |
41 | 42 | ("logfile", value<std::string>()->default_value(""), "Path to logfile (stdout if not set, autodetect if daemon)") |
61 | 62 | ("floodfill", bool_switch()->default_value(false), "Router will be floodfill (default: disabled)") |
62 | 63 | ("bandwidth", value<std::string>()->default_value(""), "Bandwidth limit: integer in KBps or letters: L (32), O (256), P (2048), X (>9000)") |
63 | 64 | ("share", value<int>()->default_value(100), "Limit of transit traffic from max bandwidth in percents. (default: 100)") |
64 | ("ntcp", bool_switch()->default_value(false), "Deprecated option. Always false") | |
65 | ("ntcp", bool_switch()->default_value(false), "Ignored. Always false") | |
65 | 66 | ("ssu", bool_switch()->default_value(true), "Enable SSU transport (default: enabled)") |
66 | ("ntcpproxy", value<std::string>()->default_value(""), "Deprecated option") | |
67 | ("ntcpproxy", value<std::string>()->default_value(""), "Ignored") | |
67 | 68 | #ifdef _WIN32 |
68 | ("svcctl", value<std::string>()->default_value(""), "Deprecated option") | |
69 | ("svcctl", value<std::string>()->default_value(""), "Ignored") | |
69 | 70 | ("insomnia", bool_switch()->default_value(false), "Prevent system from sleeping (default: disabled)") |
70 | 71 | ("close", value<std::string>()->default_value("ask"), "Action on close: minimize, exit, ask") |
71 | 72 | #endif |
76 | 77 | ("limits.coresize", value<uint32_t>()->default_value(0), "Maximum size of corefile in Kb (0 - use system limit)") |
77 | 78 | ("limits.openfiles", value<uint16_t>()->default_value(0), "Maximum number of open files (0 - use system default)") |
78 | 79 | ("limits.transittunnels", value<uint16_t>()->default_value(2500), "Maximum active transit sessions (default:2500)") |
79 | ("limits.ntcpsoft", value<uint16_t>()->default_value(0), "Deprecated option") | |
80 | ("limits.ntcphard", value<uint16_t>()->default_value(0), "Deprecated option") | |
81 | ("limits.ntcpthreads", value<uint16_t>()->default_value(1), "Deprecated option") | |
80 | ("limits.ntcpsoft", value<uint16_t>()->default_value(0), "Threshold to start probabilistic backoff with ntcp sessions (default: use system limit)") | |
81 | ("limits.ntcphard", value<uint16_t>()->default_value(0), "Maximum number of ntcp sessions (default: use system limit)") | |
82 | ("limits.ntcpthreads", value<uint16_t>()->default_value(1), "Maximum number of threads used by NTCP DH worker (default: 1)") | |
82 | 83 | ; |
83 | 84 | |
84 | 85 | options_description httpserver("HTTP Server options"); |
92 | 93 | ("http.strictheaders", value<bool>()->default_value(true), "Enable strict host checking on WebUI") |
93 | 94 | ("http.hostname", value<std::string>()->default_value("localhost"), "Expected hostname for WebUI") |
94 | 95 | ("http.webroot", value<std::string>()->default_value("/"), "WebUI root path (default: / )") |
96 | ("http.lang", value<std::string>()->default_value("english"), "WebUI language (default: english )") | |
95 | 97 | ; |
96 | 98 | |
97 | 99 | options_description httpproxy("HTTP Proxy options"); |
112 | 114 | ("httpproxy.addresshelper", value<bool>()->default_value(true), "Enable or disable addresshelper") |
113 | 115 | ("httpproxy.i2cp.leaseSetType", value<std::string>()->default_value("3"), "Local destination's LeaseSet type") |
114 | 116 | ("httpproxy.i2cp.leaseSetEncType", value<std::string>()->default_value("0,4"), "Local destination's LeaseSet encryption type") |
117 | ("httpproxy.i2cp.leaseSetPrivKey", value<std::string>()->default_value(""), "LeaseSet private key") | |
115 | 118 | ; |
116 | 119 | |
117 | 120 | options_description socksproxy("SOCKS Proxy options"); |
133 | 136 | ("socksproxy.outproxyport", value<uint16_t>()->default_value(9050), "Upstream outproxy port for SOCKS Proxy") |
134 | 137 | ("socksproxy.i2cp.leaseSetType", value<std::string>()->default_value("3"), "Local destination's LeaseSet type") |
135 | 138 | ("socksproxy.i2cp.leaseSetEncType", value<std::string>()->default_value("0,4"), "Local destination's LeaseSet encryption type") |
139 | ("socksproxy.i2cp.leaseSetPrivKey", value<std::string>()->default_value(""), "LeaseSet private key") | |
136 | 140 | ; |
137 | 141 | |
138 | 142 | options_description sam("SAM bridge options"); |
208 | 212 | "https://i2p.novg.net/" |
209 | 213 | ), "Reseed URLs, separated by comma") |
210 | 214 | ("reseed.yggurls", value<std::string>()->default_value( |
211 | "http://[324:9de3:fea4:f6ac::ace]:7070/" | |
215 | "http://[324:71e:281a:9ed3::ace]:7070/," | |
216 | "http://[301:65b9:c7cd:9a36::1]:18801/," | |
217 | "http://[320:8936:ec1a:31f1::216]/" | |
212 | 218 | ), "Reseed URLs through the Yggdrasil, separated by comma") |
213 | 219 | ; |
214 | 220 | |
217 | 223 | ("addressbook.defaulturl", value<std::string>()->default_value( |
218 | 224 | "http://shx5vqsw7usdaunyzr2qmes2fq37oumybpudrd4jjj4e4vk4uusa.b32.i2p/hosts.txt" |
219 | 225 | ), "AddressBook subscription URL for initial setup") |
220 | ("addressbook.subscriptions", value<std::string>()->default_value(""), "AddressBook subscriptions URLs, separated by comma") | |
226 | ("addressbook.subscriptions", value<std::string>()->default_value("http://reg.i2p/hosts.txt"), "AddressBook subscriptions URLs, separated by comma") | |
221 | 227 | ("addressbook.hostsfile", value<std::string>()->default_value(""), "File to dump addresses in hosts.txt format"); |
222 | 228 | |
223 | 229 | options_description trust("Trust options"); |
81 | 81 | if (it != params->end ()) m_Nickname = it->second; |
82 | 82 | // otherwise we set default nickname in Start when we know local address |
83 | 83 | } |
84 | it = params->find (I2CP_PARAM_DONT_PUBLISH_LEASESET); | |
85 | if (it != params->end ()) | |
86 | { | |
87 | // oveeride isPublic | |
88 | bool dontpublish = false; | |
89 | i2p::config::GetOption (it->second, dontpublish); | |
90 | m_IsPublic = !dontpublish; | |
91 | } | |
84 | 92 | it = params->find (I2CP_PARAM_LEASESET_TYPE); |
85 | 93 | if (it != params->end ()) |
86 | 94 | m_LeaseSetType = std::stoi(it->second); |
336 | 344 | void LeaseSetDestination::HandleI2NPMessage (const uint8_t * buf, size_t len) |
337 | 345 | { |
338 | 346 | I2NPMessageType typeID = (I2NPMessageType)(buf[I2NP_HEADER_TYPEID_OFFSET]); |
339 | LeaseSetDestination::HandleCloveI2NPMessage (typeID, buf + I2NP_HEADER_SIZE, GetI2NPMessageLength(buf, len) - I2NP_HEADER_SIZE); | |
340 | } | |
341 | ||
342 | bool LeaseSetDestination::HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len) | |
347 | uint32_t msgID = bufbe32toh (buf + I2NP_HEADER_MSGID_OFFSET); | |
348 | LeaseSetDestination::HandleCloveI2NPMessage (typeID, buf + I2NP_HEADER_SIZE, GetI2NPMessageLength(buf, len) - I2NP_HEADER_SIZE, msgID); | |
349 | } | |
350 | ||
351 | bool LeaseSetDestination::HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID) | |
343 | 352 | { |
344 | 353 | switch (typeID) |
345 | 354 | { |
356 | 365 | case eI2NPDatabaseSearchReply: |
357 | 366 | HandleDatabaseSearchReplyMessage (payload, len); |
358 | 367 | break; |
368 | case eI2NPShortTunnelBuildReply: // might come as garlic encrypted | |
369 | i2p::HandleI2NPMessage (CreateI2NPMessage (typeID, payload, len, msgID)); | |
370 | break; | |
359 | 371 | default: |
360 | 372 | LogPrint (eLogWarning, "Destination: Unexpected I2NP message type ", typeID); |
361 | 373 | return false; |
508 | 520 | // schedule verification |
509 | 521 | m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_VERIFICATION_TIMEOUT)); |
510 | 522 | m_PublishVerificationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishVerificationTimer, |
511 | shared_from_this (), std::placeholders::_1)); | |
523 | shared_from_this (), std::placeholders::_1)); | |
512 | 524 | } |
513 | 525 | else |
514 | 526 | i2p::garlic::GarlicDestination::HandleDeliveryStatusMessage (msgID); |
591 | 603 | // assume it successive and try to verify |
592 | 604 | m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_VERIFICATION_TIMEOUT)); |
593 | 605 | m_PublishVerificationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishVerificationTimer, |
594 | shared_from_this (), std::placeholders::_1)); | |
606 | shared_from_this (), std::placeholders::_1)); | |
607 | ||
595 | 608 | } |
596 | 609 | } |
597 | 610 | } |
60 | 60 | const char I2CP_PARAM_RATCHET_OUTBOUND_TAGS[] = "crypto.ratchet.outboundTags"; // not used yet |
61 | 61 | const char I2CP_PARAM_INBOUND_NICKNAME[] = "inbound.nickname"; |
62 | 62 | const char I2CP_PARAM_OUTBOUND_NICKNAME[] = "outbound.nickname"; |
63 | const char I2CP_PARAM_DONT_PUBLISH_LEASESET[] = "i2cp.dontPublishLeaseSet"; | |
63 | 64 | const char I2CP_PARAM_LEASESET_TYPE[] = "i2cp.leaseSetType"; |
64 | const int DEFAULT_LEASESET_TYPE = 1; | |
65 | const int DEFAULT_LEASESET_TYPE = 3; | |
65 | 66 | const char I2CP_PARAM_LEASESET_ENCRYPTION_TYPE[] = "i2cp.leaseSetEncType"; |
66 | 67 | const char I2CP_PARAM_LEASESET_PRIV_KEY[] = "i2cp.leaseSetPrivKey"; // PSK decryption key, base64 |
67 | 68 | const char I2CP_PARAM_LEASESET_AUTH_TYPE[] = "i2cp.leaseSetAuthType"; |
68 | 69 | const char I2CP_PARAM_LEASESET_CLIENT_DH[] = "i2cp.leaseSetClient.dh"; // group of i2cp.leaseSetClient.dh.nnn |
69 | const char I2CP_PARAM_LEASESET_CLIENT_PSK[] = "i2cp.leaseSetClient.psk"; // group of i2cp.leaseSetClient.psk.nnn | |
70 | ||
70 | const char I2CP_PARAM_LEASESET_CLIENT_PSK[] = "i2cp.leaseSetClient.psk"; // group of i2cp.leaseSetClient.psk.nnn | |
71 | ||
71 | 72 | // latency |
72 | 73 | const char I2CP_PARAM_MIN_TUNNEL_LATENCY[] = "latency.min"; |
73 | 74 | const int DEFAULT_MIN_TUNNEL_LATENCY = 0; |
142 | 143 | |
143 | 144 | // implements GarlicDestination |
144 | 145 | void HandleI2NPMessage (const uint8_t * buf, size_t len); |
145 | bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len); | |
146 | bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID); | |
146 | 147 | |
147 | 148 | void SetLeaseSet (std::shared_ptr<const i2p::data::LocalLeaseSet> newLeaseSet); |
148 | 149 | int GetLeaseSetType () const { return m_LeaseSetType; }; |
0 | 0 | /* |
1 | * Copyright (c) 2013-2020, The PurpleI2P Project | |
1 | * Copyright (c) 2013-2021, The PurpleI2P Project | |
2 | 2 | * |
3 | 3 | * This file is part of Purple i2pd project and licensed under BSD3 |
4 | 4 | * |
30 | 30 | uint8_t keydata[64]; |
31 | 31 | i2p::crypto::HKDF (rootKey, k, 32, "KDFDHRatchetStep", keydata); // keydata = HKDF(rootKey, k, "KDFDHRatchetStep", 64) |
32 | 32 | memcpy (m_NextRootKey, keydata, 32); // nextRootKey = keydata[0:31] |
33 | i2p::crypto::HKDF (keydata + 32, nullptr, 0, "TagAndKeyGenKeys", m_KeyData.buf); | |
33 | i2p::crypto::HKDF (keydata + 32, nullptr, 0, "TagAndKeyGenKeys", m_SessionTagKeyData); | |
34 | 34 | // [sessTag_ck, symmKey_ck] = HKDF(keydata[32:63], ZEROLEN, "TagAndKeyGenKeys", 64) |
35 | memcpy (m_SymmKeyCK, m_KeyData.buf + 32, 32); | |
35 | memcpy (m_SymmKeyCK, (const uint8_t *)m_SessionTagKeyData + 32, 32); | |
36 | 36 | m_NextSymmKeyIndex = 0; |
37 | 37 | } |
38 | 38 | |
39 | 39 | void RatchetTagSet::NextSessionTagRatchet () |
40 | 40 | { |
41 | i2p::crypto::HKDF (m_KeyData.GetSessTagCK (), nullptr, 0, "STInitialization", m_KeyData.buf); // [sessTag_ck, sesstag_constant] = HKDF(sessTag_ck, ZEROLEN, "STInitialization", 64) | |
42 | memcpy (m_SessTagConstant, m_KeyData.GetSessTagConstant (), 32); | |
41 | i2p::crypto::HKDF (m_SessionTagKeyData, nullptr, 0, "STInitialization", m_SessionTagKeyData); // [sessTag_ck, sesstag_constant] = HKDF(sessTag_ck, ZEROLEN, "STInitialization", 64) | |
42 | memcpy (m_SessTagConstant, (const uint8_t *)m_SessionTagKeyData + 32, 32); // SESSTAG_CONSTANT = keydata[32:63] | |
43 | 43 | m_NextIndex = 0; |
44 | 44 | } |
45 | 45 | |
51 | 51 | LogPrint (eLogError, "Garlic: Tagset ", GetTagSetID (), " is empty"); |
52 | 52 | return 0; |
53 | 53 | } |
54 | i2p::crypto::HKDF (m_KeyData.GetSessTagCK (), m_SessTagConstant, 32, "SessionTagKeyGen", m_KeyData.buf); // [sessTag_ck, tag] = HKDF(sessTag_chainkey, SESSTAG_CONSTANT, "SessionTagKeyGen", 64) | |
55 | return m_KeyData.GetTag (); | |
54 | i2p::crypto::HKDF (m_SessionTagKeyData, m_SessTagConstant, 32, "SessionTagKeyGen", m_SessionTagKeyData); // [sessTag_ck, tag] = HKDF(sessTag_chainkey, SESSTAG_CONSTANT, "SessionTagKeyGen", 64) | |
55 | return m_SessionTagKeyData.GetLL ()[4]; // tag = keydata[32:39] | |
56 | 56 | } |
57 | 57 | |
58 | 58 | void RatchetTagSet::GetSymmKey (int index, uint8_t * key) |
116 | 116 | return session->HandleNextMessage (buf, len, shared_from_this (), index); |
117 | 117 | } |
118 | 118 | |
119 | DatabaseLookupTagSet::DatabaseLookupTagSet (GarlicDestination * destination, const uint8_t * key): | |
119 | SymmetricKeyTagSet::SymmetricKeyTagSet (GarlicDestination * destination, const uint8_t * key): | |
120 | 120 | ReceiveRatchetTagSet (nullptr), m_Destination (destination) |
121 | 121 | { |
122 | 122 | memcpy (m_Key, key, 32); |
123 | 123 | Expire (); |
124 | 124 | } |
125 | 125 | |
126 | bool DatabaseLookupTagSet::HandleNextMessage (uint8_t * buf, size_t len, int index) | |
126 | bool SymmetricKeyTagSet::HandleNextMessage (uint8_t * buf, size_t len, int index) | |
127 | 127 | { |
128 | 128 | if (len < 24) return false; |
129 | 129 | uint8_t nonce[12]; |
132 | 132 | len -= 16; // poly1305 |
133 | 133 | if (!i2p::crypto::AEADChaCha20Poly1305 (buf + offset, len - offset, buf, 8, m_Key, nonce, buf + offset, len - offset, false)) // decrypt |
134 | 134 | { |
135 | LogPrint (eLogWarning, "Garlic: Lookup reply AEAD decryption failed"); | |
135 | LogPrint (eLogWarning, "Garlic: Symmetric key tagset AEAD decryption failed"); | |
136 | 136 | return false; |
137 | 137 | } |
138 | 138 | // we assume 1 I2NP block with delivery type local |
139 | 139 | if (offset + 3 > len) |
140 | 140 | { |
141 | LogPrint (eLogWarning, "Garlic: Lookup reply is too short ", len); | |
141 | LogPrint (eLogWarning, "Garlic: Symmetric key tagset is too short ", len); | |
142 | 142 | return false; |
143 | 143 | } |
144 | 144 | if (buf[offset] != eECIESx25519BlkGalicClove) |
145 | 145 | { |
146 | LogPrint (eLogWarning, "Garlic: Lookup reply unexpected block ", (int)buf[offset]); | |
146 | LogPrint (eLogWarning, "Garlic: Symmetric key tagset unexpected block ", (int)buf[offset]); | |
147 | 147 | return false; |
148 | 148 | } |
149 | 149 | offset++; |
151 | 151 | offset += 2; |
152 | 152 | if (offset + size > len) |
153 | 153 | { |
154 | LogPrint (eLogWarning, "Garlic: Lookup reply block is too long ", size); | |
154 | LogPrint (eLogWarning, "Garlic: Symmetric key tagset block is too long ", size); | |
155 | 155 | return false; |
156 | 156 | } |
157 | 157 | if (m_Destination) |
517 | 517 | } |
518 | 518 | return true; |
519 | 519 | } |
520 | ||
521 | bool ECIESX25519AEADRatchetSession::NewOutgoingMessageForRouter (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen) | |
522 | { | |
523 | // we are Alice, router's bpk is m_RemoteStaticKey | |
524 | i2p::crypto::InitNoiseNState (GetNoiseState (), m_RemoteStaticKey); | |
525 | size_t offset = 0; | |
526 | m_EphemeralKeys = i2p::transport::transports.GetNextX25519KeysPair (); | |
527 | memcpy (out + offset, m_EphemeralKeys->GetPublicKey (), 32); | |
528 | MixHash (out + offset, 32); // h = SHA256(h || aepk) | |
529 | offset += 32; | |
530 | uint8_t sharedSecret[32]; | |
531 | if (!m_EphemeralKeys->Agree (m_RemoteStaticKey, sharedSecret)) // x25519(aesk, bpk) | |
532 | { | |
533 | LogPrint (eLogWarning, "Garlic: Incorrect Bob static key"); | |
534 | return false; | |
535 | } | |
536 | MixKey (sharedSecret); | |
537 | uint8_t nonce[12]; | |
538 | CreateNonce (0, nonce); | |
539 | // encrypt payload | |
540 | if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, m_H, 32, m_CK + 32, nonce, out + offset, len + 16, true)) // encrypt | |
541 | { | |
542 | LogPrint (eLogWarning, "Garlic: Payload for router AEAD encryption failed"); | |
543 | return false; | |
544 | } | |
545 | ||
546 | m_State = eSessionStateNewSessionSent; | |
547 | return true; | |
548 | } | |
549 | ||
520 | ||
550 | 521 | bool ECIESX25519AEADRatchetSession::NewSessionReplyMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen) |
551 | 522 | { |
552 | 523 | // we are Bob |
823 | 794 | |
824 | 795 | std::shared_ptr<I2NPMessage> ECIESX25519AEADRatchetSession::WrapSingleMessage (std::shared_ptr<const I2NPMessage> msg) |
825 | 796 | { |
826 | auto payload = CreatePayload (msg, m_State != eSessionStateEstablished); | |
827 | size_t len = payload.size (); | |
797 | uint8_t * payload = GetOwner ()->GetPayloadBuffer (); | |
798 | if (!payload) return nullptr; | |
799 | size_t len = CreatePayload (msg, m_State != eSessionStateEstablished, payload); | |
828 | 800 | if (!len) return nullptr; |
829 | 801 | auto m = NewI2NPMessage (len + 100); // 96 + 4 |
830 | 802 | m->Align (12); // in order to get buf aligned to 16 (12 + 4) |
833 | 805 | switch (m_State) |
834 | 806 | { |
835 | 807 | case eSessionStateEstablished: |
836 | if (!NewExistingSessionMessage (payload.data (), payload.size (), buf, m->maxLen)) | |
808 | if (!NewExistingSessionMessage (payload, len, buf, m->maxLen)) | |
837 | 809 | return nullptr; |
838 | 810 | len += 24; |
839 | 811 | break; |
840 | 812 | case eSessionStateNew: |
841 | if (!NewOutgoingSessionMessage (payload.data (), payload.size (), buf, m->maxLen)) | |
813 | if (!NewOutgoingSessionMessage (payload, len, buf, m->maxLen)) | |
842 | 814 | return nullptr; |
843 | 815 | len += 96; |
844 | 816 | break; |
845 | 817 | case eSessionStateNewSessionReceived: |
846 | if (!NewSessionReplyMessage (payload.data (), payload.size (), buf, m->maxLen)) | |
818 | if (!NewSessionReplyMessage (payload, len, buf, m->maxLen)) | |
847 | 819 | return nullptr; |
848 | 820 | len += 72; |
849 | 821 | break; |
850 | 822 | case eSessionStateNewSessionReplySent: |
851 | if (!NextNewSessionReplyMessage (payload.data (), payload.size (), buf, m->maxLen)) | |
823 | if (!NextNewSessionReplyMessage (payload, len, buf, m->maxLen)) | |
852 | 824 | return nullptr; |
853 | 825 | len += 72; |
854 | 826 | break; |
855 | 827 | case eSessionStateOneTime: |
856 | if (!NewOutgoingSessionMessage (payload.data (), payload.size (), buf, m->maxLen, false)) | |
828 | if (!NewOutgoingSessionMessage (payload, len, buf, m->maxLen, false)) | |
857 | 829 | return nullptr; |
858 | 830 | len += 96; |
859 | break; | |
860 | case eSessionStateForRouter: | |
861 | if (!NewOutgoingMessageForRouter (payload.data (), payload.size (), buf, m->maxLen)) | |
862 | return nullptr; | |
863 | len += 48; | |
864 | 831 | break; |
865 | 832 | default: |
866 | 833 | return nullptr; |
872 | 839 | return m; |
873 | 840 | } |
874 | 841 | |
875 | std::shared_ptr<I2NPMessage> ECIESX25519AEADRatchetSession::WrapOneTimeMessage (std::shared_ptr<const I2NPMessage> msg, bool isForRouter) | |
876 | { | |
877 | m_State = isForRouter ? eSessionStateForRouter : eSessionStateOneTime; | |
842 | std::shared_ptr<I2NPMessage> ECIESX25519AEADRatchetSession::WrapOneTimeMessage (std::shared_ptr<const I2NPMessage> msg) | |
843 | { | |
844 | m_State = eSessionStateOneTime; | |
878 | 845 | return WrapSingleMessage (msg); |
879 | 846 | } |
880 | 847 | |
881 | std::vector<uint8_t> ECIESX25519AEADRatchetSession::CreatePayload (std::shared_ptr<const I2NPMessage> msg, bool first) | |
848 | size_t ECIESX25519AEADRatchetSession::CreatePayload (std::shared_ptr<const I2NPMessage> msg, bool first, uint8_t * payload) | |
882 | 849 | { |
883 | 850 | uint64_t ts = i2p::util::GetMillisecondsSinceEpoch (); |
884 | 851 | size_t payloadLen = 0; |
940 | 907 | payloadLen += paddingSize + 3; |
941 | 908 | } |
942 | 909 | } |
943 | std::vector<uint8_t> v(payloadLen); | |
944 | 910 | if (payloadLen) |
945 | { | |
911 | { | |
912 | if (payloadLen > I2NP_MAX_MESSAGE_SIZE) | |
913 | { | |
914 | LogPrint (eLogError, "Garlic: payload length ", payloadLen, " is too long"); | |
915 | return 0; | |
916 | } | |
946 | 917 | m_LastSentTimestamp = ts; |
947 | 918 | size_t offset = 0; |
948 | 919 | // DateTime |
949 | 920 | if (first) |
950 | 921 | { |
951 | v[offset] = eECIESx25519BlkDateTime; offset++; | |
952 | htobe16buf (v.data () + offset, 4); offset += 2; | |
953 | htobe32buf (v.data () + offset, ts/1000); offset += 4; // in seconds | |
922 | payload[offset] = eECIESx25519BlkDateTime; offset++; | |
923 | htobe16buf (payload + offset, 4); offset += 2; | |
924 | htobe32buf (payload + offset, ts/1000); offset += 4; // in seconds | |
954 | 925 | } |
955 | 926 | // LeaseSet |
956 | 927 | if (leaseSet) |
957 | 928 | { |
958 | offset += CreateLeaseSetClove (leaseSet, ts, v.data () + offset, payloadLen - offset); | |
929 | offset += CreateLeaseSetClove (leaseSet, ts, payload + offset, payloadLen - offset); | |
959 | 930 | if (!first) |
960 | 931 | { |
961 | 932 | // ack request |
962 | v[offset] = eECIESx25519BlkAckRequest; offset++; | |
963 | htobe16buf (v.data () + offset, 1); offset += 2; | |
964 | v[offset] = 0; offset++; // flags | |
933 | payload[offset] = eECIESx25519BlkAckRequest; offset++; | |
934 | htobe16buf (payload + offset, 1); offset += 2; | |
935 | payload[offset] = 0; offset++; // flags | |
965 | 936 | } |
966 | 937 | } |
967 | 938 | // msg |
968 | 939 | if (msg) |
969 | offset += CreateGarlicClove (msg, v.data () + offset, payloadLen - offset); | |
940 | offset += CreateGarlicClove (msg, payload + offset, payloadLen - offset); | |
970 | 941 | // ack |
971 | 942 | if (m_AckRequests.size () > 0) |
972 | 943 | { |
973 | v[offset] = eECIESx25519BlkAck; offset++; | |
974 | htobe16buf (v.data () + offset, m_AckRequests.size () * 4); offset += 2; | |
944 | payload[offset] = eECIESx25519BlkAck; offset++; | |
945 | htobe16buf (payload + offset, m_AckRequests.size () * 4); offset += 2; | |
975 | 946 | for (auto& it: m_AckRequests) |
976 | 947 | { |
977 | htobe16buf (v.data () + offset, it.first); offset += 2; | |
978 | htobe16buf (v.data () + offset, it.second); offset += 2; | |
948 | htobe16buf (payload + offset, it.first); offset += 2; | |
949 | htobe16buf (payload + offset, it.second); offset += 2; | |
979 | 950 | } |
980 | 951 | m_AckRequests.clear (); |
981 | 952 | } |
982 | 953 | // next keys |
983 | 954 | if (m_SendReverseKey) |
984 | 955 | { |
985 | v[offset] = eECIESx25519BlkNextKey; offset++; | |
986 | htobe16buf (v.data () + offset, m_NextReceiveRatchet->newKey ? 35 : 3); offset += 2; | |
987 | v[offset] = ECIESX25519_NEXT_KEY_REVERSE_KEY_FLAG; | |
956 | payload[offset] = eECIESx25519BlkNextKey; offset++; | |
957 | htobe16buf (payload + offset, m_NextReceiveRatchet->newKey ? 35 : 3); offset += 2; | |
958 | payload[offset] = ECIESX25519_NEXT_KEY_REVERSE_KEY_FLAG; | |
988 | 959 | int keyID = m_NextReceiveRatchet->keyID - 1; |
989 | 960 | if (m_NextReceiveRatchet->newKey) |
990 | 961 | { |
991 | v[offset] |= ECIESX25519_NEXT_KEY_KEY_PRESENT_FLAG; | |
962 | payload[offset] |= ECIESX25519_NEXT_KEY_KEY_PRESENT_FLAG; | |
992 | 963 | keyID++; |
993 | 964 | } |
994 | 965 | offset++; // flag |
995 | htobe16buf (v.data () + offset, keyID); offset += 2; // keyid | |
966 | htobe16buf (payload + offset, keyID); offset += 2; // keyid | |
996 | 967 | if (m_NextReceiveRatchet->newKey) |
997 | 968 | { |
998 | memcpy (v.data () + offset, m_NextReceiveRatchet->key->GetPublicKey (), 32); | |
969 | memcpy (payload + offset, m_NextReceiveRatchet->key->GetPublicKey (), 32); | |
999 | 970 | offset += 32; // public key |
1000 | 971 | } |
1001 | 972 | m_SendReverseKey = false; |
1002 | 973 | } |
1003 | 974 | if (m_SendForwardKey) |
1004 | 975 | { |
1005 | v[offset] = eECIESx25519BlkNextKey; offset++; | |
1006 | htobe16buf (v.data () + offset, m_NextSendRatchet->newKey ? 35 : 3); offset += 2; | |
1007 | v[offset] = m_NextSendRatchet->newKey ? ECIESX25519_NEXT_KEY_KEY_PRESENT_FLAG : ECIESX25519_NEXT_KEY_REQUEST_REVERSE_KEY_FLAG; | |
1008 | if (!m_NextSendRatchet->keyID) v[offset] |= ECIESX25519_NEXT_KEY_REQUEST_REVERSE_KEY_FLAG; // for first key only | |
976 | payload[offset] = eECIESx25519BlkNextKey; offset++; | |
977 | htobe16buf (payload + offset, m_NextSendRatchet->newKey ? 35 : 3); offset += 2; | |
978 | payload[offset] = m_NextSendRatchet->newKey ? ECIESX25519_NEXT_KEY_KEY_PRESENT_FLAG : ECIESX25519_NEXT_KEY_REQUEST_REVERSE_KEY_FLAG; | |
979 | if (!m_NextSendRatchet->keyID) payload[offset] |= ECIESX25519_NEXT_KEY_REQUEST_REVERSE_KEY_FLAG; // for first key only | |
1009 | 980 | offset++; // flag |
1010 | htobe16buf (v.data () + offset, m_NextSendRatchet->keyID); offset += 2; // keyid | |
981 | htobe16buf (payload + offset, m_NextSendRatchet->keyID); offset += 2; // keyid | |
1011 | 982 | if (m_NextSendRatchet->newKey) |
1012 | 983 | { |
1013 | memcpy (v.data () + offset, m_NextSendRatchet->key->GetPublicKey (), 32); | |
984 | memcpy (payload + offset, m_NextSendRatchet->key->GetPublicKey (), 32); | |
1014 | 985 | offset += 32; // public key |
1015 | 986 | } |
1016 | 987 | } |
1017 | 988 | // padding |
1018 | 989 | if (paddingSize) |
1019 | 990 | { |
1020 | v[offset] = eECIESx25519BlkPadding; offset++; | |
1021 | htobe16buf (v.data () + offset, paddingSize); offset += 2; | |
1022 | memset (v.data () + offset, 0, paddingSize); offset += paddingSize; | |
1023 | } | |
1024 | } | |
1025 | return v; | |
991 | payload[offset] = eECIESx25519BlkPadding; offset++; | |
992 | htobe16buf (payload + offset, paddingSize); offset += 2; | |
993 | memset (payload + offset, 0, paddingSize); offset += paddingSize; | |
994 | } | |
995 | } | |
996 | return payloadLen; | |
1026 | 997 | } |
1027 | 998 | |
1028 | 999 | size_t ECIESX25519AEADRatchetSession::CreateGarlicClove (std::shared_ptr<const I2NPMessage> msg, uint8_t * buf, size_t len) |
1108 | 1079 | bool RouterIncomingRatchetSession::HandleNextMessage (const uint8_t * buf, size_t len) |
1109 | 1080 | { |
1110 | 1081 | if (!GetOwner ()) return false; |
1111 | i2p::crypto::NoiseSymmetricState state (GetNoiseState ()); | |
1082 | m_CurrentNoiseState = GetNoiseState (); | |
1112 | 1083 | // we are Bob |
1113 | state.MixHash (buf, 32); | |
1084 | m_CurrentNoiseState.MixHash (buf, 32); | |
1114 | 1085 | uint8_t sharedSecret[32]; |
1115 | 1086 | if (!GetOwner ()->Decrypt (buf, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) // x25519(bsk, aepk) |
1116 | 1087 | { |
1117 | 1088 | LogPrint (eLogWarning, "Garlic: Incorrect N ephemeral public key"); |
1118 | 1089 | return false; |
1119 | 1090 | } |
1120 | state.MixKey (sharedSecret); | |
1091 | m_CurrentNoiseState.MixKey (sharedSecret); | |
1121 | 1092 | buf += 32; len -= 32; |
1122 | 1093 | uint8_t nonce[12]; |
1123 | 1094 | CreateNonce (0, nonce); |
1124 | 1095 | std::vector<uint8_t> payload (len - 16); |
1125 | if (!i2p::crypto::AEADChaCha20Poly1305 (buf, len - 16, state.m_H, 32, state.m_CK + 32, nonce, payload.data (), len - 16, false)) // decrypt | |
1096 | if (!i2p::crypto::AEADChaCha20Poly1305 (buf, len - 16, m_CurrentNoiseState.m_H, 32, | |
1097 | m_CurrentNoiseState.m_CK + 32, nonce, payload.data (), len - 16, false)) // decrypt | |
1126 | 1098 | { |
1127 | 1099 | LogPrint (eLogWarning, "Garlic: Payload for router AEAD verification failed"); |
1128 | 1100 | return false; |
1130 | 1102 | HandlePayload (payload.data (), len - 16, nullptr, 0); |
1131 | 1103 | return true; |
1132 | 1104 | } |
1105 | ||
1106 | static size_t CreateGarlicPayload (std::shared_ptr<const I2NPMessage> msg, uint8_t * payload, | |
1107 | bool datetime, size_t optimalSize) | |
1108 | { | |
1109 | size_t len = 0; | |
1110 | if (datetime) | |
1111 | { | |
1112 | // DateTime | |
1113 | payload[0] = eECIESx25519BlkDateTime; | |
1114 | htobe16buf (payload + 1, 4); | |
1115 | htobe32buf (payload + 3, i2p::util::GetSecondsSinceEpoch ()); | |
1116 | len = 7; | |
1117 | } | |
1118 | // I2NP | |
1119 | payload += len; | |
1120 | uint16_t cloveSize = msg->GetPayloadLength () + 10; | |
1121 | payload[0] = eECIESx25519BlkGalicClove; // clove type | |
1122 | htobe16buf (payload + 1, cloveSize); // size | |
1123 | payload += 3; | |
1124 | payload[0] = 0; // flag and delivery instructions | |
1125 | payload[1] = msg->GetTypeID (); // I2NP msg type | |
1126 | htobe32buf (payload + 2, msg->GetMsgID ()); // msgID | |
1127 | htobe32buf (payload + 6, msg->GetExpiration () / 1000); // expiration in seconds | |
1128 | memcpy (payload + 10, msg->GetPayload (), msg->GetPayloadLength ()); | |
1129 | len += cloveSize + 3; | |
1130 | payload += cloveSize; | |
1131 | // padding | |
1132 | int delta = (int)optimalSize - (int)len; | |
1133 | if (delta < 0 || delta > 3) // don't create padding if we are close to optimal size | |
1134 | { | |
1135 | uint8_t paddingSize = rand () & 0x0F; // 0 - 15 | |
1136 | if (delta > 3) | |
1137 | { | |
1138 | delta -= 3; | |
1139 | if (paddingSize > delta) paddingSize %= delta; | |
1140 | } | |
1141 | payload[0] = eECIESx25519BlkPadding; | |
1142 | htobe16buf (payload + 1, paddingSize); | |
1143 | if (paddingSize) memset (payload + 3, 0, paddingSize); | |
1144 | len += paddingSize + 3; | |
1145 | } | |
1146 | return len; | |
1147 | } | |
1133 | 1148 | |
1134 | std::shared_ptr<I2NPMessage> WrapECIESX25519AEADRatchetMessage (std::shared_ptr<const I2NPMessage> msg, const uint8_t * key, uint64_t tag) | |
1149 | std::shared_ptr<I2NPMessage> WrapECIESX25519Message (std::shared_ptr<const I2NPMessage> msg, const uint8_t * key, uint64_t tag) | |
1135 | 1150 | { |
1136 | 1151 | auto m = NewI2NPMessage (); |
1137 | 1152 | m->Align (12); // in order to get buf aligned to 16 (12 + 4) |
1138 | 1153 | uint8_t * buf = m->GetPayload () + 4; // 4 bytes for length |
1139 | uint8_t nonce[12]; | |
1140 | memset (nonce, 0, 12); // n = 0 | |
1141 | 1154 | size_t offset = 0; |
1142 | 1155 | memcpy (buf + offset, &tag, 8); offset += 8; |
1143 | 1156 | auto payload = buf + offset; |
1144 | uint16_t cloveSize = msg->GetPayloadLength () + 9 + 1; | |
1145 | size_t len = cloveSize + 3; | |
1146 | payload[0] = eECIESx25519BlkGalicClove; // clove type | |
1147 | htobe16buf (payload + 1, cloveSize); // size | |
1148 | payload += 3; | |
1149 | *payload = 0; payload++; // flag and delivery instructions | |
1150 | *payload = msg->GetTypeID (); // I2NP msg type | |
1151 | htobe32buf (payload + 1, msg->GetMsgID ()); // msgID | |
1152 | htobe32buf (payload + 5, msg->GetExpiration () / 1000); // expiration in seconds | |
1153 | memcpy (payload + 9, msg->GetPayload (), msg->GetPayloadLength ()); | |
1154 | ||
1155 | if (!i2p::crypto::AEADChaCha20Poly1305 (buf + offset, len, buf, 8, key, nonce, buf + offset, len + 16, true)) // encrypt | |
1157 | size_t len = CreateGarlicPayload (msg, payload, false, 956); // 1003 - 8 tag - 16 Poly1305 hash - 16 I2NP header - 4 garlic length - 3 local tunnel delivery | |
1158 | uint8_t nonce[12]; | |
1159 | memset (nonce, 0, 12); // n = 0 | |
1160 | if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, buf, 8, key, nonce, payload, len + 16, true)) // encrypt | |
1156 | 1161 | { |
1157 | 1162 | LogPrint (eLogWarning, "Garlic: Payload section AEAD encryption failed"); |
1158 | 1163 | return nullptr; |
1159 | 1164 | } |
1160 | 1165 | offset += len + 16; |
1161 | ||
1162 | 1166 | htobe32buf (m->GetPayload (), offset); |
1163 | 1167 | m->len += offset + 4; |
1164 | 1168 | m->FillI2NPMessageHeader (eI2NPGarlic); |
1165 | 1169 | return m; |
1166 | 1170 | } |
1167 | 1171 | |
1172 | std::shared_ptr<I2NPMessage> WrapECIESX25519MessageForRouter (std::shared_ptr<const I2NPMessage> msg, const uint8_t * routerPublicKey) | |
1173 | { | |
1174 | // Noise_N, we are Alice, routerPublicKey is Bob's | |
1175 | i2p::crypto::NoiseSymmetricState noiseState; | |
1176 | i2p::crypto::InitNoiseNState (noiseState, routerPublicKey); | |
1177 | auto m = NewI2NPMessage (); | |
1178 | m->Align (12); // in order to get buf aligned to 16 (12 + 4) | |
1179 | uint8_t * buf = m->GetPayload () + 4; // 4 bytes for length | |
1180 | size_t offset = 0; | |
1181 | auto ephemeralKeys = i2p::transport::transports.GetNextX25519KeysPair (); | |
1182 | memcpy (buf + offset, ephemeralKeys->GetPublicKey (), 32); | |
1183 | noiseState.MixHash (buf + offset, 32); // h = SHA256(h || aepk) | |
1184 | offset += 32; | |
1185 | uint8_t sharedSecret[32]; | |
1186 | if (!ephemeralKeys->Agree (routerPublicKey, sharedSecret)) // x25519(aesk, bpk) | |
1187 | { | |
1188 | LogPrint (eLogWarning, "Garlic: Incorrect Bob static key"); | |
1189 | return nullptr; | |
1190 | } | |
1191 | noiseState.MixKey (sharedSecret); | |
1192 | auto payload = buf + offset; | |
1193 | 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 | |
1194 | uint8_t nonce[12]; | |
1195 | memset (nonce, 0, 12); | |
1196 | // encrypt payload | |
1197 | if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, noiseState.m_H, 32, noiseState.m_CK + 32, nonce, payload, len + 16, true)) // encrypt | |
1198 | { | |
1199 | LogPrint (eLogWarning, "Garlic: Payload for router AEAD encryption failed"); | |
1200 | return nullptr; | |
1201 | } | |
1202 | offset += len + 16; | |
1203 | htobe32buf (m->GetPayload (), offset); | |
1204 | m->len += offset + 4; | |
1205 | m->FillI2NPMessageHeader (eI2NPGarlic); | |
1206 | return m; | |
1207 | } | |
1168 | 1208 | } |
1169 | 1209 | } |
0 | ||
0 | 1 | /* |
1 | * Copyright (c) 2013-2020, The PurpleI2P Project | |
2 | * Copyright (c) 2013-2021, The PurpleI2P Project | |
2 | 3 | * |
3 | 4 | * This file is part of Purple i2pd project and licensed under BSD3 |
4 | 5 | * |
58 | 59 | |
59 | 60 | private: |
60 | 61 | |
61 | union | |
62 | { | |
63 | uint64_t ll[8]; | |
64 | uint8_t buf[64]; | |
65 | ||
66 | const uint8_t * GetSessTagCK () const { return buf; }; // sessTag_chainKey = keydata[0:31] | |
67 | const uint8_t * GetSessTagConstant () const { return buf + 32; }; // SESSTAG_CONSTANT = keydata[32:63] | |
68 | uint64_t GetTag () const { return ll[4]; }; // tag = keydata[32:39] | |
69 | ||
70 | } m_KeyData; | |
62 | i2p::data::Tag<64> m_SessionTagKeyData; | |
71 | 63 | uint8_t m_SessTagConstant[32], m_SymmKeyCK[32], m_CurrentSymmKeyCK[64], m_NextRootKey[32]; |
72 | 64 | int m_NextIndex, m_NextSymmKeyIndex; |
73 | 65 | std::unordered_map<int, i2p::data::Tag<32> > m_ItermediateSymmKeys; |
103 | 95 | uint64_t m_ExpirationTimestamp = 0; |
104 | 96 | }; |
105 | 97 | |
106 | class DatabaseLookupTagSet: public ReceiveRatchetTagSet | |
107 | { | |
108 | public: | |
109 | ||
110 | DatabaseLookupTagSet (GarlicDestination * destination, const uint8_t * key); | |
98 | class SymmetricKeyTagSet: public ReceiveRatchetTagSet | |
99 | { | |
100 | public: | |
101 | ||
102 | SymmetricKeyTagSet (GarlicDestination * destination, const uint8_t * key); | |
111 | 103 | |
112 | 104 | bool IsIndexExpired (int index) const { return false; }; |
113 | 105 | bool HandleNextMessage (uint8_t * buf, size_t len, int index); |
146 | 138 | eSessionStateNewSessionSent, |
147 | 139 | eSessionStateNewSessionReplySent, |
148 | 140 | eSessionStateEstablished, |
149 | eSessionStateOneTime, | |
150 | eSessionStateForRouter | |
141 | eSessionStateOneTime | |
151 | 142 | }; |
152 | 143 | |
153 | 144 | struct DHRatchet |
165 | 156 | |
166 | 157 | bool HandleNextMessage (uint8_t * buf, size_t len, std::shared_ptr<ReceiveRatchetTagSet> receiveTagset, int index = 0); |
167 | 158 | std::shared_ptr<I2NPMessage> WrapSingleMessage (std::shared_ptr<const I2NPMessage> msg); |
168 | std::shared_ptr<I2NPMessage> WrapOneTimeMessage (std::shared_ptr<const I2NPMessage> msg, bool isForRouter = false); | |
159 | std::shared_ptr<I2NPMessage> WrapOneTimeMessage (std::shared_ptr<const I2NPMessage> msg); | |
169 | 160 | |
170 | 161 | const uint8_t * GetRemoteStaticKey () const { return m_RemoteStaticKey; } |
171 | 162 | void SetRemoteStaticKey (const uint8_t * key) { memcpy (m_RemoteStaticKey, key, 32); } |
206 | 197 | bool NewSessionReplyMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); |
207 | 198 | bool NextNewSessionReplyMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); |
208 | 199 | bool NewExistingSessionMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); |
209 | bool NewOutgoingMessageForRouter (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); | |
210 | ||
211 | std::vector<uint8_t> CreatePayload (std::shared_ptr<const I2NPMessage> msg, bool first); | |
200 | ||
201 | size_t CreatePayload (std::shared_ptr<const I2NPMessage> msg, bool first, uint8_t * payload); | |
212 | 202 | size_t CreateGarlicClove (std::shared_ptr<const I2NPMessage> msg, uint8_t * buf, size_t len); |
213 | 203 | size_t CreateLeaseSetClove (std::shared_ptr<const i2p::data::LocalLeaseSet> ls, uint64_t ts, uint8_t * buf, size_t len); |
214 | 204 | |
248 | 238 | |
249 | 239 | RouterIncomingRatchetSession (const i2p::crypto::NoiseSymmetricState& initState); |
250 | 240 | bool HandleNextMessage (const uint8_t * buf, size_t len); |
241 | i2p::crypto::NoiseSymmetricState& GetCurrentNoiseState () { return m_CurrentNoiseState; }; | |
242 | ||
243 | private: | |
244 | ||
245 | i2p::crypto::NoiseSymmetricState m_CurrentNoiseState; | |
251 | 246 | }; |
252 | 247 | |
253 | std::shared_ptr<I2NPMessage> WrapECIESX25519AEADRatchetMessage (std::shared_ptr<const I2NPMessage> msg, const uint8_t * key, uint64_t tag); | |
248 | std::shared_ptr<I2NPMessage> WrapECIESX25519Message (std::shared_ptr<const I2NPMessage> msg, const uint8_t * key, uint64_t tag); | |
249 | std::shared_ptr<I2NPMessage> WrapECIESX25519MessageForRouter (std::shared_ptr<const I2NPMessage> msg, const uint8_t * routerPublicKey); | |
254 | 250 | } |
255 | 251 | } |
256 | 252 | |
257 | 253 | #endif |
254 |
11 | 11 | #ifdef _WIN32 |
12 | 12 | #include <shlobj.h> |
13 | 13 | #include <windows.h> |
14 | #include <codecvt> | |
14 | 15 | #endif |
15 | 16 | |
16 | 17 | #include "Base.h" |
22 | 23 | namespace fs { |
23 | 24 | std::string appName = "i2pd"; |
24 | 25 | std::string dataDir = ""; |
26 | std::string certsDir = ""; | |
25 | 27 | #ifdef _WIN32 |
26 | 28 | std::string dirSep = "\\"; |
27 | 29 | #else |
38 | 40 | |
39 | 41 | const std::string & GetDataDir () { |
40 | 42 | return dataDir; |
43 | } | |
44 | ||
45 | const std::string & GetCertsDir () { | |
46 | return certsDir; | |
47 | } | |
48 | ||
49 | const std::string GetUTF8DataDir () { | |
50 | #ifdef _WIN32 | |
51 | boost::filesystem::wpath path (dataDir); | |
52 | auto loc = boost::filesystem::path::imbue(std::locale( std::locale(), new std::codecvt_utf8_utf16<wchar_t>() ) ); // convert path to UTF-8 | |
53 | auto dataDirUTF8 = path.string(); | |
54 | boost::filesystem::path::imbue(loc); // Return locale settings back | |
55 | return dataDirUTF8; | |
56 | #else | |
57 | return dataDir; // linux, osx, android uses UTF-8 by default | |
58 | #endif | |
41 | 59 | } |
42 | 60 | |
43 | 61 | void DetectDataDir(const std::string & cmdline_param, bool isService) { |
46 | 64 | return; |
47 | 65 | } |
48 | 66 | #ifdef _WIN32 |
49 | char localAppData[MAX_PATH]; | |
67 | wchar_t localAppData[MAX_PATH]; | |
50 | 68 | |
51 | 69 | // check executable directory first |
52 | if(!GetModuleFileName(NULL, localAppData, MAX_PATH)) | |
70 | if(!GetModuleFileNameW(NULL, localAppData, MAX_PATH)) | |
53 | 71 | { |
54 | 72 | #ifdef WIN32_APP |
55 | 73 | MessageBox(NULL, TEXT("Unable to get application path!"), TEXT("I2Pd: error"), MB_ICONERROR | MB_OK); |
60 | 78 | } |
61 | 79 | else |
62 | 80 | { |
63 | auto execPath = boost::filesystem::path(localAppData).parent_path(); | |
81 | auto execPath = boost::filesystem::wpath(localAppData).parent_path(); | |
64 | 82 | |
65 | 83 | // if config file exists in .exe's folder use it |
66 | 84 | if(boost::filesystem::exists(execPath/"i2pd.conf")) // TODO: magic string |
85 | { | |
67 | 86 | dataDir = execPath.string (); |
68 | else // otherwise %appdata% | |
87 | } else // otherwise %appdata% | |
69 | 88 | { |
70 | if(SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, localAppData) != S_OK) | |
89 | if(SHGetFolderPathW(NULL, CSIDL_APPDATA, NULL, 0, localAppData) != S_OK) | |
71 | 90 | { |
72 | 91 | #ifdef WIN32_APP |
73 | 92 | MessageBox(NULL, TEXT("Unable to get AppData path!"), TEXT("I2Pd: error"), MB_ICONERROR | MB_OK); |
77 | 96 | exit(1); |
78 | 97 | } |
79 | 98 | else |
80 | dataDir = std::string(localAppData) + "\\" + appName; | |
99 | { | |
100 | dataDir = boost::filesystem::wpath(localAppData).string() + "\\" + appName; | |
101 | } | |
81 | 102 | } |
82 | 103 | } |
83 | 104 | return; |
109 | 130 | #endif |
110 | 131 | } |
111 | 132 | |
133 | void SetCertsDir(const std::string & cmdline_certsdir) { | |
134 | if (cmdline_certsdir != "") | |
135 | { | |
136 | if (cmdline_certsdir[cmdline_certsdir.length()-1] == '/') | |
137 | certsDir = cmdline_certsdir.substr(0, cmdline_certsdir.size()-1); // strip trailing slash | |
138 | else | |
139 | certsDir = cmdline_certsdir; | |
140 | } | |
141 | else | |
142 | { | |
143 | certsDir = i2p::fs::DataDirPath("certificates"); | |
144 | } | |
145 | return; | |
146 | } | |
147 | ||
112 | 148 | bool Init() { |
113 | 149 | if (!boost::filesystem::exists(dataDir)) |
114 | 150 | boost::filesystem::create_directory(dataDir); |
74 | 74 | /** @brief Returns datadir path */ |
75 | 75 | const std::string & GetDataDir(); |
76 | 76 | |
77 | /** @brief Returns certsdir path */ | |
78 | const std::string & GetCertsDir(); | |
79 | ||
80 | /** @brief Returns datadir path in UTF-8 encoding */ | |
81 | const std::string GetUTF8DataDir(); | |
82 | ||
77 | 83 | /** |
78 | 84 | * @brief Set datadir either from cmdline option or using autodetection |
79 | 85 | * @param cmdline_param Value of cmdline parameter --datadir=<something> |
86 | 92 | * Mac: /Library/Application Support/i2pd/ or ~/Library/Application Support/i2pd/ |
87 | 93 | * Unix: /var/lib/i2pd/ (system=1) >> ~/.i2pd/ or /tmp/i2pd/ |
88 | 94 | */ |
89 | void DetectDataDir(const std::string & cmdline_datadir, bool isService = false); | |
95 | void DetectDataDir(const std::string & cmdline_datadir, bool isService = false); | |
96 | ||
97 | /** | |
98 | * @brief Set certsdir either from cmdline option or using autodetection | |
99 | * @param cmdline_param Value of cmdline parameter --certsdir=<something> | |
100 | * | |
101 | * Examples of autodetected paths: | |
102 | * | |
103 | * Windows < Vista: C:\Documents and Settings\Username\Application Data\i2pd\certificates | |
104 | * Windows >= Vista: C:\Users\Username\AppData\Roaming\i2pd\certificates | |
105 | * Mac: /Library/Application Support/i2pd/ or ~/Library/Application Support/i2pd/certificates | |
106 | * Unix: /var/lib/i2pd/certificates (system=1) >> ~/.i2pd/ or /tmp/i2pd/certificates | |
107 | */ | |
108 | void SetCertsDir(const std::string & cmdline_certsdir); | |
90 | 109 | |
91 | 110 | /** |
92 | 111 | * @brief Create subdirectories inside datadir |
12 | 12 | #include "FS.h" |
13 | 13 | #include "Log.h" |
14 | 14 | #include "Family.h" |
15 | #include "Config.h" | |
15 | 16 | |
16 | 17 | namespace i2p |
17 | 18 | { |
97 | 98 | |
98 | 99 | void Families::LoadCertificates () |
99 | 100 | { |
100 | std::string certDir = i2p::fs::DataDirPath("certificates", "family"); | |
101 | std::string certDir = i2p::fs::GetCertsDir() + i2p::fs::dirSep + "family"; | |
102 | ||
101 | 103 | std::vector<std::string> files; |
102 | 104 | int numCertificates = 0; |
103 | 105 |
432 | 432 | } |
433 | 433 | |
434 | 434 | GarlicDestination::GarlicDestination (): m_NumTags (32), // 32 tags by default |
435 | m_NumRatchetInboundTags (0) // 0 means standard | |
435 | m_PayloadBuffer (nullptr), m_NumRatchetInboundTags (0) // 0 means standard | |
436 | 436 | { |
437 | 437 | m_Ctx = BN_CTX_new (); |
438 | 438 | } |
440 | 440 | GarlicDestination::~GarlicDestination () |
441 | 441 | { |
442 | 442 | BN_CTX_free (m_Ctx); |
443 | if (m_PayloadBuffer) | |
444 | delete[] m_PayloadBuffer; | |
443 | 445 | } |
444 | 446 | |
445 | 447 | void GarlicDestination::CleanUp () |
470 | 472 | { |
471 | 473 | uint64_t t; |
472 | 474 | memcpy (&t, tag, 8); |
473 | auto tagset = std::make_shared<DatabaseLookupTagSet>(this, key); | |
474 | m_ECIESx25519Tags.emplace (t, ECIESX25519AEADRatchetIndexTagset{0, tagset}); | |
475 | AddECIESx25519Key (key, t); | |
476 | } | |
477 | ||
478 | void GarlicDestination::AddECIESx25519Key (const uint8_t * key, uint64_t tag) | |
479 | { | |
480 | auto tagset = std::make_shared<SymmetricKeyTagSet>(this, key); | |
481 | m_ECIESx25519Tags.emplace (tag, ECIESX25519AEADRatchetIndexTagset{0, tagset}); | |
475 | 482 | } |
476 | 483 | |
477 | 484 | bool GarlicDestination::SubmitSessionKey (const uint8_t * key, const uint8_t * tag) |
493 | 500 | buf += 4; // length |
494 | 501 | |
495 | 502 | bool found = false; |
496 | uint64_t tag; | |
497 | 503 | if (SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) |
498 | { | |
499 | 504 | // try ECIESx25519 tag |
500 | memcpy (&tag, buf, 8); | |
501 | auto it1 = m_ECIESx25519Tags.find (tag); | |
502 | if (it1 != m_ECIESx25519Tags.end ()) | |
503 | { | |
504 | found = true; | |
505 | if (it1->second.tagset->HandleNextMessage (buf, length, it1->second.index)) | |
506 | m_LastTagset = it1->second.tagset; | |
507 | else | |
508 | LogPrint (eLogError, "Garlic: can't handle ECIES-X25519-AEAD-Ratchet message"); | |
509 | m_ECIESx25519Tags.erase (it1); | |
510 | } | |
511 | } | |
505 | found = HandleECIESx25519TagMessage (buf, length); | |
512 | 506 | if (!found) |
513 | 507 | { |
514 | 508 | auto it = !mod ? m_Tags.find (SessionTag(buf)) : m_Tags.end (); // AES block is multiple of 16 |
554 | 548 | // try to gererate more tags for last tagset |
555 | 549 | if (m_LastTagset && (m_LastTagset->GetNextIndex () - m_LastTagset->GetTrimBehind () < 3*ECIESX25519_MAX_NUM_GENERATED_TAGS)) |
556 | 550 | { |
551 | uint64_t missingTag; memcpy (&missingTag, buf, 8); | |
557 | 552 | auto maxTags = std::max (m_NumRatchetInboundTags, ECIESX25519_MAX_NUM_GENERATED_TAGS); |
558 | 553 | LogPrint (eLogWarning, "Garlic: trying to generate more ECIES-X25519-AEAD-Ratchet tags"); |
559 | 554 | for (int i = 0; i < maxTags; i++) |
564 | 559 | LogPrint (eLogError, "Garlic: can't create new ECIES-X25519-AEAD-Ratchet tag for last tagset"); |
565 | 560 | break; |
566 | 561 | } |
567 | if (nextTag == tag) | |
562 | if (nextTag == missingTag) | |
568 | 563 | { |
569 | 564 | LogPrint (eLogDebug, "Garlic: Missing ECIES-X25519-AEAD-Ratchet tag was generated"); |
570 | if (m_LastTagset->HandleNextMessage (buf, length, m_ECIESx25519Tags[tag].index)) | |
565 | if (m_LastTagset->HandleNextMessage (buf, length, m_ECIESx25519Tags[nextTag].index)) | |
571 | 566 | found = true; |
572 | 567 | break; |
573 | 568 | } |
584 | 579 | } |
585 | 580 | } |
586 | 581 | |
582 | bool GarlicDestination::HandleECIESx25519TagMessage (uint8_t * buf, size_t len) | |
583 | { | |
584 | uint64_t tag; | |
585 | memcpy (&tag, buf, 8); | |
586 | auto it = m_ECIESx25519Tags.find (tag); | |
587 | if (it != m_ECIESx25519Tags.end ()) | |
588 | { | |
589 | if (it->second.tagset->HandleNextMessage (buf, len, it->second.index)) | |
590 | m_LastTagset = it->second.tagset; | |
591 | else | |
592 | LogPrint (eLogError, "Garlic: can't handle ECIES-X25519-AEAD-Ratchet message"); | |
593 | m_ECIESx25519Tags.erase (it); | |
594 | return true; | |
595 | } | |
596 | return false; | |
597 | } | |
598 | ||
587 | 599 | void GarlicDestination::HandleAESBlock (uint8_t * buf, size_t len, std::shared_ptr<AESDecryption> decryption, |
588 | 600 | std::shared_ptr<i2p::tunnel::InboundTunnel> from) |
589 | 601 | { |
748 | 760 | std::shared_ptr<I2NPMessage> msg) |
749 | 761 | { |
750 | 762 | if (router->GetEncryptionType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) |
751 | { | |
752 | auto session = std::make_shared<ECIESX25519AEADRatchetSession>(this, false); | |
753 | session->SetRemoteStaticKey (router->GetIdentity ()->GetEncryptionPublicKey ()); | |
754 | return session->WrapOneTimeMessage (msg, true); | |
755 | } | |
763 | return WrapECIESX25519MessageForRouter (msg, router->GetIdentity ()->GetEncryptionPublicKey ()); | |
756 | 764 | else |
757 | 765 | { |
758 | 766 | auto session = GetRoutingSession (router, false); |
1035 | 1043 | { |
1036 | 1044 | LogPrint (eLogDebug, "Garlic: type local"); |
1037 | 1045 | I2NPMessageType typeID = (I2NPMessageType)(buf[0]); buf++; // typeid |
1038 | buf += (4 + 4); // msgID + expiration | |
1046 | int32_t msgID = bufbe32toh (buf); buf += 4; // msgID | |
1047 | buf += 4; // expiration | |
1039 | 1048 | ptrdiff_t offset = buf - buf1; |
1040 | 1049 | if (offset <= (int)len) |
1041 | HandleCloveI2NPMessage (typeID, buf, len - offset); | |
1050 | HandleCloveI2NPMessage (typeID, buf, len - offset, msgID); | |
1042 | 1051 | else |
1043 | 1052 | LogPrint (eLogError, "Garlic: clove is too long"); |
1044 | 1053 | break; |
1057 | 1066 | } |
1058 | 1067 | uint32_t gwTunnel = bufbe32toh (buf); buf += 4; |
1059 | 1068 | I2NPMessageType typeID = (I2NPMessageType)(buf[0]); buf++; // typeid |
1060 | buf += (4 + 4); // msgID + expiration | |
1069 | uint32_t msgID = bufbe32toh (buf); buf += 4; // msgID | |
1070 | buf += 4; // expiration | |
1061 | 1071 | offset += 13; |
1062 | 1072 | if (GetTunnelPool ()) |
1063 | 1073 | { |
1064 | 1074 | auto tunnel = GetTunnelPool ()->GetNextOutboundTunnel (); |
1065 | 1075 | if (tunnel) |
1066 | tunnel->SendTunnelDataMsg (gwHash, gwTunnel, CreateI2NPMessage (typeID, buf, len - offset)); | |
1076 | tunnel->SendTunnelDataMsg (gwHash, gwTunnel, CreateI2NPMessage (typeID, buf, len - offset, msgID)); | |
1067 | 1077 | else |
1068 | 1078 | LogPrint (eLogWarning, "Garlic: No outbound tunnels available for garlic clove"); |
1069 | 1079 | } |
1114 | 1124 | m_ECIESx25519Sessions.erase (it); |
1115 | 1125 | } |
1116 | 1126 | } |
1127 | ||
1128 | uint8_t * GarlicDestination::GetPayloadBuffer () | |
1129 | { | |
1130 | if (!m_PayloadBuffer) | |
1131 | m_PayloadBuffer = new uint8_t[I2NP_MAX_MESSAGE_SIZE]; | |
1132 | return m_PayloadBuffer; | |
1133 | } | |
1117 | 1134 | } |
1118 | 1135 | } |
242 | 242 | std::shared_ptr<I2NPMessage> msg); |
243 | 243 | |
244 | 244 | void AddSessionKey (const uint8_t * key, const uint8_t * tag); // one tag |
245 | void AddECIESx25519Key (const uint8_t * key, const uint8_t * tag); // one tag | |
245 | void AddECIESx25519Key (const uint8_t * key, uint64_t tag); // one tag | |
246 | 246 | virtual bool SubmitSessionKey (const uint8_t * key, const uint8_t * tag); // from different thread |
247 | 247 | void DeliveryStatusSent (GarlicRoutingSessionPtr session, uint32_t msgID); |
248 | 248 | uint64_t AddECIESx25519SessionNextTag (ReceiveRatchetTagSetPtr tagset); |
249 | 249 | void AddECIESx25519Session (const uint8_t * staticKey, ECIESX25519AEADRatchetSessionPtr session); |
250 | 250 | void RemoveECIESx25519Session (const uint8_t * staticKey); |
251 | 251 | void HandleECIESx25519GarlicClove (const uint8_t * buf, size_t len); |
252 | ||
252 | uint8_t * GetPayloadBuffer (); | |
253 | ||
253 | 254 | virtual void ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg); |
254 | 255 | virtual void ProcessDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg); |
255 | 256 | virtual void SetLeaseSetUpdated (); |
259 | 260 | |
260 | 261 | protected: |
261 | 262 | |
263 | void AddECIESx25519Key (const uint8_t * key, const uint8_t * tag); // one tag | |
264 | bool HandleECIESx25519TagMessage (uint8_t * buf, size_t len); // return true if found | |
262 | 265 | virtual void HandleI2NPMessage (const uint8_t * buf, size_t len) = 0; // called from clove only |
263 | virtual bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len) = 0; | |
266 | virtual bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID) = 0; | |
264 | 267 | void HandleGarlicMessage (std::shared_ptr<I2NPMessage> msg); |
265 | 268 | void HandleDeliveryStatusMessage (uint32_t msgID); |
266 | 269 | |
281 | 284 | std::mutex m_SessionsMutex; |
282 | 285 | std::unordered_map<i2p::data::IdentHash, ElGamalAESSessionPtr> m_Sessions; |
283 | 286 | std::unordered_map<i2p::data::Tag<32>, ECIESX25519AEADRatchetSessionPtr> m_ECIESx25519Sessions; // static key -> session |
287 | uint8_t * m_PayloadBuffer; // for ECIESX25519AEADRatchet | |
284 | 288 | // incoming |
285 | 289 | int m_NumRatchetInboundTags; |
286 | 290 | std::unordered_map<SessionTag, std::shared_ptr<AESDecryption>, std::hash<i2p::data::Tag<32> > > m_Tags; |
186 | 186 | |
187 | 187 | params.clear(); |
188 | 188 | for (const auto& it : tokens) { |
189 | if (!it.length()) // empty | |
190 | continue; | |
189 | 191 | std::size_t eq = it.find ('='); |
190 | 192 | if (eq != std::string::npos) { |
191 | 193 | auto e = std::pair<std::string, std::string>(it.substr(0, eq), it.substr(eq + 1)); |
0 | 0 | /* |
1 | * Copyright (c) 2013-2020, The PurpleI2P Project | |
1 | * Copyright (c) 2013-2021, The PurpleI2P Project | |
2 | 2 | * |
3 | 3 | * This file is part of Purple i2pd project and licensed under BSD3 |
4 | 4 | * |
17 | 17 | #include "Tunnel.h" |
18 | 18 | #include "Transports.h" |
19 | 19 | #include "Garlic.h" |
20 | #include "ECIESX25519AEADRatchetSession.h" | |
20 | 21 | #include "I2NPProtocol.h" |
21 | 22 | #include "version.h" |
22 | 23 | |
34 | 35 | return std::make_shared<I2NPMessageBuffer<I2NP_MAX_SHORT_MESSAGE_SIZE> >(); |
35 | 36 | } |
36 | 37 | |
37 | std::shared_ptr<I2NPMessage> NewI2NPTunnelMessage () | |
38 | { | |
39 | auto msg = new I2NPMessageBuffer<i2p::tunnel::TUNNEL_DATA_MSG_SIZE + I2NP_HEADER_SIZE + 34>(); // reserved for alignment and NTCP 16 + 6 + 12 | |
40 | msg->Align (12); | |
38 | std::shared_ptr<I2NPMessage> NewI2NPTunnelMessage (bool endpoint) | |
39 | { | |
40 | I2NPMessage * msg = nullptr; | |
41 | if (endpoint) | |
42 | { | |
43 | // should fit two tunnel message + tunnel gateway header, enough for one garlic encrypted streaming packet | |
44 | msg = new I2NPMessageBuffer<2*i2p::tunnel::TUNNEL_DATA_MSG_SIZE + I2NP_HEADER_SIZE + TUNNEL_GATEWAY_HEADER_SIZE + 28>(); // reserved for alignment and NTCP 16 + 6 + 6 | |
45 | msg->Align (6); | |
46 | msg->offset += TUNNEL_GATEWAY_HEADER_SIZE; // reserve room for TunnelGateway header | |
47 | } | |
48 | else | |
49 | { | |
50 | msg = new I2NPMessageBuffer<i2p::tunnel::TUNNEL_DATA_MSG_SIZE + I2NP_HEADER_SIZE + 34>(); // reserved for alignment and NTCP 16 + 6 + 12 | |
51 | msg->Align (12); | |
52 | } | |
41 | 53 | return std::shared_ptr<I2NPMessage>(msg); |
42 | 54 | } |
43 | 55 | |
249 | 261 | if (!router) // we send own RouterInfo |
250 | 262 | router = context.GetSharedRouterInfo (); |
251 | 263 | |
264 | if (!router->GetBuffer ()) | |
265 | { | |
266 | LogPrint (eLogError, "I2NP: Invalid RouterInfo buffer for DatabaseStore"); | |
267 | return nullptr; | |
268 | } | |
269 | ||
252 | 270 | auto m = NewI2NPShortMessage (); |
253 | 271 | uint8_t * payload = m->GetPayload (); |
254 | 272 | |
362 | 380 | return g_MaxNumTransitTunnels; |
363 | 381 | } |
364 | 382 | |
365 | bool HandleBuildRequestRecords (int num, uint8_t * records, uint8_t * clearText) | |
383 | static bool HandleBuildRequestRecords (int num, uint8_t * records, uint8_t * clearText) | |
366 | 384 | { |
367 | 385 | for (int i = 0; i < num; i++) |
368 | 386 | { |
386 | 404 | bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), |
387 | 405 | clearText + ECIES_BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET, |
388 | 406 | clearText + ECIES_BUILD_REQUEST_RECORD_IV_KEY_OFFSET, |
389 | clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & 0x80, | |
390 | clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & 0x40) : | |
407 | clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_GATEWAY_FLAG, | |
408 | clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG) : | |
391 | 409 | i2p::tunnel::CreateTransitTunnel ( |
392 | 410 | bufbe32toh (clearText + BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET), |
393 | 411 | clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, |
394 | 412 | bufbe32toh (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), |
395 | 413 | clearText + BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET, |
396 | 414 | clearText + BUILD_REQUEST_RECORD_IV_KEY_OFFSET, |
397 | clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] & 0x80, | |
398 | clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] & 0x40); | |
415 | clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_GATEWAY_FLAG, | |
416 | clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG); | |
399 | 417 | i2p::tunnel::tunnels.AddTransitTunnel (transitTunnel); |
400 | 418 | } |
401 | 419 | else |
423 | 441 | { |
424 | 442 | uint8_t nonce[12]; |
425 | 443 | memset (nonce, 0, 12); |
426 | auto noiseState = std::move (i2p::context.GetCurrentNoiseState ()); | |
427 | if (!noiseState || !i2p::crypto::AEADChaCha20Poly1305 (reply, TUNNEL_BUILD_RECORD_SIZE - 16, | |
428 | noiseState->m_H, 32, noiseState->m_CK, nonce, reply, TUNNEL_BUILD_RECORD_SIZE, true)) // encrypt | |
444 | auto& noiseState = i2p::context.GetCurrentNoiseState (); | |
445 | if (!i2p::crypto::AEADChaCha20Poly1305 (reply, TUNNEL_BUILD_RECORD_SIZE - 16, | |
446 | noiseState.m_H, 32, noiseState.m_CK, nonce, reply, TUNNEL_BUILD_RECORD_SIZE, true)) // encrypt | |
429 | 447 | { |
430 | 448 | LogPrint (eLogWarning, "I2NP: Reply AEAD encryption failed"); |
431 | 449 | return false; |
451 | 469 | return false; |
452 | 470 | } |
453 | 471 | |
454 | void HandleVariableTunnelBuildMsg (uint32_t replyMsgID, uint8_t * buf, size_t len) | |
472 | static void HandleVariableTunnelBuildMsg (uint32_t replyMsgID, uint8_t * buf, size_t len) | |
455 | 473 | { |
456 | 474 | int num = buf[0]; |
457 | 475 | LogPrint (eLogDebug, "I2NP: VariableTunnelBuild ", num, " records"); |
458 | 476 | if (len < num*TUNNEL_BUILD_RECORD_SIZE + 1) |
459 | 477 | { |
460 | LogPrint (eLogError, "VaribleTunnelBuild message of ", num, " records is too short ", len); | |
478 | LogPrint (eLogError, "I2NP: VaribleTunnelBuild message of ", num, " records is too short ", len); | |
461 | 479 | return; |
462 | 480 | } |
463 | 481 | |
485 | 503 | uint8_t clearText[ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; |
486 | 504 | if (HandleBuildRequestRecords (num, buf + 1, clearText)) |
487 | 505 | { |
488 | if (clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & 0x40) // we are endpoint of outboud tunnel | |
506 | if (clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG) // we are endpoint of outboud tunnel | |
489 | 507 | { |
490 | 508 | // so we send it to reply tunnel |
491 | 509 | transports.SendMessage (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, |
504 | 522 | uint8_t clearText[BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; |
505 | 523 | if (HandleBuildRequestRecords (num, buf + 1, clearText)) |
506 | 524 | { |
507 | if (clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] & 0x40) // we are endpoint of outboud tunnel | |
525 | if (clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG) // we are endpoint of outboud tunnel | |
508 | 526 | { |
509 | 527 | // so we send it to reply tunnel |
510 | 528 | transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, |
521 | 539 | } |
522 | 540 | } |
523 | 541 | |
524 | void HandleTunnelBuildMsg (uint8_t * buf, size_t len) | |
542 | static void HandleTunnelBuildMsg (uint8_t * buf, size_t len) | |
525 | 543 | { |
526 | 544 | if (i2p::context.IsECIES ()) |
527 | 545 | { |
528 | LogPrint (eLogWarning, "TunnelBuild is too old for ECIES router"); | |
546 | LogPrint (eLogWarning, "I2NP: TunnelBuild is too old for ECIES router"); | |
529 | 547 | return; |
530 | 548 | } |
531 | 549 | if (len < NUM_TUNNEL_BUILD_RECORDS*TUNNEL_BUILD_RECORD_SIZE) |
532 | 550 | { |
533 | LogPrint (eLogError, "TunnelBuild message is too short ", len); | |
551 | LogPrint (eLogError, "I2NP: TunnelBuild message is too short ", len); | |
534 | 552 | return; |
535 | 553 | } |
536 | 554 | uint8_t clearText[BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; |
537 | 555 | if (HandleBuildRequestRecords (NUM_TUNNEL_BUILD_RECORDS, buf, clearText)) |
538 | 556 | { |
539 | if (clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] & 0x40) // we are endpoint of outbound tunnel | |
557 | if (clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG) // we are endpoint of outbound tunnel | |
540 | 558 | { |
541 | 559 | // so we send it to reply tunnel |
542 | 560 | transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, |
551 | 569 | } |
552 | 570 | } |
553 | 571 | |
554 | void HandleVariableTunnelBuildReplyMsg (uint32_t replyMsgID, uint8_t * buf, size_t len) | |
572 | static void HandleTunnelBuildReplyMsg (uint32_t replyMsgID, uint8_t * buf, size_t len, bool isShort) | |
555 | 573 | { |
556 | 574 | int num = buf[0]; |
557 | LogPrint (eLogDebug, "I2NP: VariableTunnelBuildReplyMsg of ", num, " records replyMsgID=", replyMsgID); | |
558 | if (len < num*BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE + 1) | |
559 | { | |
560 | LogPrint (eLogError, "VaribleTunnelBuildReply message of ", num, " records is too short ", len); | |
575 | LogPrint (eLogDebug, "I2NP: TunnelBuildReplyMsg of ", num, " records replyMsgID=", replyMsgID); | |
576 | size_t recordSize = isShort ? SHORT_TUNNEL_BUILD_RECORD_SIZE : TUNNEL_BUILD_RECORD_SIZE; | |
577 | if (len < num*recordSize + 1) | |
578 | { | |
579 | LogPrint (eLogError, "I2NP: TunnelBuildReply message of ", num, " records is too short ", len); | |
561 | 580 | return; |
562 | 581 | } |
563 | 582 | |
581 | 600 | LogPrint (eLogWarning, "I2NP: Pending tunnel for message ", replyMsgID, " not found"); |
582 | 601 | } |
583 | 602 | |
584 | ||
603 | static void HandleShortTunnelBuildMsg (uint32_t replyMsgID, uint8_t * buf, size_t len) | |
604 | { | |
605 | if (!i2p::context.IsECIES ()) | |
606 | { | |
607 | LogPrint (eLogWarning, "I2NP: ShortTunnelBuild can be handled by ECIES router only"); | |
608 | return; | |
609 | } | |
610 | int num = buf[0]; | |
611 | LogPrint (eLogDebug, "I2NP: ShortTunnelBuild ", num, " records"); | |
612 | if (len < num*SHORT_TUNNEL_BUILD_RECORD_SIZE + 1) | |
613 | { | |
614 | LogPrint (eLogError, "I2NP: ShortTunnelBuild message of ", num, " records is too short ", len); | |
615 | return; | |
616 | } | |
617 | auto tunnel = i2p::tunnel::tunnels.GetPendingInboundTunnel (replyMsgID); | |
618 | if (tunnel) | |
619 | { | |
620 | // endpoint of inbound tunnel | |
621 | LogPrint (eLogDebug, "I2NP: ShortTunnelBuild reply for tunnel ", tunnel->GetTunnelID ()); | |
622 | if (tunnel->HandleTunnelBuildResponse (buf, len)) | |
623 | { | |
624 | LogPrint (eLogInfo, "I2NP: Inbound tunnel ", tunnel->GetTunnelID (), " has been created"); | |
625 | tunnel->SetState (i2p::tunnel::eTunnelStateEstablished); | |
626 | i2p::tunnel::tunnels.AddInboundTunnel (tunnel); | |
627 | } | |
628 | else | |
629 | { | |
630 | LogPrint (eLogInfo, "I2NP: Inbound tunnel ", tunnel->GetTunnelID (), " has been declined"); | |
631 | tunnel->SetState (i2p::tunnel::eTunnelStateBuildFailed); | |
632 | } | |
633 | return; | |
634 | } | |
635 | const uint8_t * record = buf + 1; | |
636 | for (int i = 0; i < num; i++) | |
637 | { | |
638 | if (!memcmp (record, (const uint8_t *)i2p::context.GetRouterInfo ().GetIdentHash (), 16)) | |
639 | { | |
640 | LogPrint (eLogDebug, "I2NP: Short request record ", i, " is ours"); | |
641 | uint8_t clearText[SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE]; | |
642 | if (!i2p::context.DecryptTunnelShortRequestRecord (record + SHORT_REQUEST_RECORD_ENCRYPTED_OFFSET, clearText)) | |
643 | { | |
644 | LogPrint (eLogWarning, "I2NP: Can't decrypt short request record ", i); | |
645 | return; | |
646 | } | |
647 | if (clearText[SHORT_REQUEST_RECORD_LAYER_ENCRYPTION_TYPE]) // not AES | |
648 | { | |
649 | LogPrint (eLogWarning, "I2NP: Unknown layer encryption type ", clearText[SHORT_REQUEST_RECORD_LAYER_ENCRYPTION_TYPE], " in short request record"); | |
650 | return; | |
651 | } | |
652 | auto& noiseState = i2p::context.GetCurrentNoiseState (); | |
653 | uint8_t replyKey[32], layerKey[32], ivKey[32]; | |
654 | i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "SMTunnelReplyKey", noiseState.m_CK); | |
655 | memcpy (replyKey, noiseState.m_CK + 32, 32); | |
656 | i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "SMTunnelLayerKey", noiseState.m_CK); | |
657 | memcpy (layerKey, noiseState.m_CK + 32, 32); | |
658 | bool isEndpoint = clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG; | |
659 | if (isEndpoint) | |
660 | { | |
661 | i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "TunnelLayerIVKey", noiseState.m_CK); | |
662 | memcpy (ivKey, noiseState.m_CK + 32, 32); | |
663 | } | |
664 | else | |
665 | memcpy (ivKey, noiseState.m_CK , 32); | |
666 | ||
667 | // check if we accept this tunnel | |
668 | uint8_t retCode = 0; | |
669 | if (!i2p::context.AcceptsTunnels () || | |
670 | i2p::tunnel::tunnels.GetTransitTunnels ().size () > g_MaxNumTransitTunnels || | |
671 | i2p::transport::transports.IsBandwidthExceeded () || | |
672 | i2p::transport::transports.IsTransitBandwidthExceeded ()) | |
673 | retCode = 30; | |
674 | if (!retCode) | |
675 | { | |
676 | // create new transit tunnel | |
677 | auto transitTunnel = i2p::tunnel::CreateTransitTunnel ( | |
678 | bufbe32toh (clearText + SHORT_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET), | |
679 | clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, | |
680 | bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), | |
681 | layerKey, ivKey, | |
682 | clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_GATEWAY_FLAG, | |
683 | clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG); | |
684 | i2p::tunnel::tunnels.AddTransitTunnel (transitTunnel); | |
685 | } | |
686 | ||
687 | // encrypt reply | |
688 | uint8_t nonce[12]; | |
689 | memset (nonce, 0, 12); | |
690 | uint8_t * reply = buf + 1; | |
691 | for (int j = 0; j < num; j++) | |
692 | { | |
693 | nonce[4] = j; // nonce is record # | |
694 | if (j == i) | |
695 | { | |
696 | memset (reply + SHORT_RESPONSE_RECORD_OPTIONS_OFFSET, 0, 2); // no options | |
697 | reply[SHORT_RESPONSE_RECORD_RET_OFFSET] = retCode; | |
698 | if (!i2p::crypto::AEADChaCha20Poly1305 (reply, SHORT_TUNNEL_BUILD_RECORD_SIZE - 16, | |
699 | noiseState.m_H, 32, replyKey, nonce, reply, SHORT_TUNNEL_BUILD_RECORD_SIZE, true)) // encrypt | |
700 | { | |
701 | LogPrint (eLogWarning, "I2NP: Short reply AEAD encryption failed"); | |
702 | return; | |
703 | } | |
704 | } | |
705 | else | |
706 | i2p::crypto::ChaCha20 (reply, SHORT_TUNNEL_BUILD_RECORD_SIZE, replyKey, nonce, reply); | |
707 | reply += SHORT_TUNNEL_BUILD_RECORD_SIZE; | |
708 | } | |
709 | // send reply | |
710 | if (isEndpoint) | |
711 | { | |
712 | auto replyMsg = NewI2NPShortMessage (); | |
713 | replyMsg->Concat (buf, len); | |
714 | replyMsg->FillI2NPMessageHeader (eI2NPShortTunnelBuildReply, bufbe32toh (clearText + SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET)); | |
715 | if (memcmp ((const uint8_t *)i2p::context.GetIdentHash (), | |
716 | clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, 32)) // reply IBGW is not local? | |
717 | { | |
718 | i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "RGarlicKeyAndTag", noiseState.m_CK); | |
719 | uint64_t tag; | |
720 | memcpy (&tag, noiseState.m_CK, 8); | |
721 | // we send it to reply tunnel | |
722 | transports.SendMessage (clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, | |
723 | CreateTunnelGatewayMsg (bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), | |
724 | i2p::garlic::WrapECIESX25519Message (replyMsg, noiseState.m_CK + 32, tag))); | |
725 | } | |
726 | else | |
727 | { | |
728 | // IBGW is local | |
729 | uint32_t tunnelID = bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET); | |
730 | auto tunnel = i2p::tunnel::tunnels.GetTunnel (tunnelID); | |
731 | if (tunnel) | |
732 | tunnel->SendTunnelDataMsg (replyMsg); | |
733 | else | |
734 | LogPrint (eLogWarning, "I2NP: Tunnel ", tunnelID, " not found for short tunnel build reply"); | |
735 | } | |
736 | } | |
737 | else | |
738 | transports.SendMessage (clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, | |
739 | CreateI2NPMessage (eI2NPShortTunnelBuild, buf, len, | |
740 | bufbe32toh (clearText + SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); | |
741 | return; | |
742 | } | |
743 | record += SHORT_TUNNEL_BUILD_RECORD_SIZE; | |
744 | } | |
745 | } | |
746 | ||
585 | 747 | std::shared_ptr<I2NPMessage> CreateTunnelDataMsg (const uint8_t * buf) |
586 | 748 | { |
587 | auto msg = NewI2NPTunnelMessage (); | |
749 | auto msg = NewI2NPTunnelMessage (false); | |
588 | 750 | msg->Concat (buf, i2p::tunnel::TUNNEL_DATA_MSG_SIZE); |
589 | 751 | msg->FillI2NPMessageHeader (eI2NPTunnelData); |
590 | 752 | return msg; |
592 | 754 | |
593 | 755 | std::shared_ptr<I2NPMessage> CreateTunnelDataMsg (uint32_t tunnelID, const uint8_t * payload) |
594 | 756 | { |
595 | auto msg = NewI2NPTunnelMessage (); | |
757 | auto msg = NewI2NPTunnelMessage (false); | |
596 | 758 | htobe32buf (msg->GetPayload (), tunnelID); |
597 | 759 | msg->len += 4; // tunnelID |
598 | 760 | msg->Concat (payload, i2p::tunnel::TUNNEL_DATA_MSG_SIZE - 4); |
600 | 762 | return msg; |
601 | 763 | } |
602 | 764 | |
603 | std::shared_ptr<I2NPMessage> CreateEmptyTunnelDataMsg () | |
604 | { | |
605 | auto msg = NewI2NPTunnelMessage (); | |
765 | std::shared_ptr<I2NPMessage> CreateEmptyTunnelDataMsg (bool endpoint) | |
766 | { | |
767 | auto msg = NewI2NPTunnelMessage (endpoint); | |
606 | 768 | msg->len += i2p::tunnel::TUNNEL_DATA_MSG_SIZE; |
607 | 769 | return msg; |
608 | 770 | } |
696 | 858 | case eI2NPVariableTunnelBuild: |
697 | 859 | HandleVariableTunnelBuildMsg (msgID, buf, size); |
698 | 860 | break; |
861 | case eI2NPShortTunnelBuild: | |
862 | HandleShortTunnelBuildMsg (msgID, buf, size); | |
863 | break; | |
699 | 864 | case eI2NPVariableTunnelBuildReply: |
700 | HandleVariableTunnelBuildReplyMsg (msgID, buf, size); | |
865 | HandleTunnelBuildReplyMsg (msgID, buf, size, false); | |
701 | 866 | break; |
867 | case eI2NPShortTunnelBuildReply: | |
868 | HandleTunnelBuildReplyMsg (msgID, buf, size, true); | |
869 | break; | |
702 | 870 | case eI2NPTunnelBuild: |
703 | 871 | HandleTunnelBuildMsg (buf, size); |
704 | 872 | break; |
755 | 923 | case eI2NPVariableTunnelBuildReply: |
756 | 924 | case eI2NPTunnelBuild: |
757 | 925 | case eI2NPTunnelBuildReply: |
926 | case eI2NPShortTunnelBuild: | |
927 | case eI2NPShortTunnelBuildReply: | |
758 | 928 | // forward to tunnel thread |
759 | 929 | i2p::tunnel::tunnels.PostTunnelData (msg); |
760 | 930 | break; |
0 | 0 | /* |
1 | * Copyright (c) 2013-2020, The PurpleI2P Project | |
1 | * Copyright (c) 2013-2021, The PurpleI2P Project | |
2 | 2 | * |
3 | 3 | * This file is part of Purple i2pd project and licensed under BSD3 |
4 | 4 | * |
54 | 54 | |
55 | 55 | // TunnelBuild |
56 | 56 | const size_t TUNNEL_BUILD_RECORD_SIZE = 528; |
57 | ||
57 | const size_t SHORT_TUNNEL_BUILD_RECORD_SIZE = 218; | |
58 | ||
58 | 59 | //BuildRequestRecordClearText |
59 | 60 | const size_t BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET = 0; |
60 | 61 | const size_t BUILD_REQUEST_RECORD_OUR_IDENT_OFFSET = BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET + 4; |
99 | 100 | // ECIES BuildResponseRecord |
100 | 101 | const size_t ECIES_BUILD_RESPONSE_RECORD_OPTIONS_OFFSET = 0; |
101 | 102 | const size_t ECIES_BUILD_RESPONSE_RECORD_RET_OFFSET = 511; |
103 | ||
104 | // ShortRequestRecordClearText | |
105 | const size_t SHORT_REQUEST_RECORD_ENCRYPTED_OFFSET = 16; | |
106 | const size_t SHORT_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET = 0; | |
107 | const size_t SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET = SHORT_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET + 4; | |
108 | const size_t SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET = SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET + 4; | |
109 | const size_t SHORT_REQUEST_RECORD_FLAG_OFFSET = SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET + 32; | |
110 | const size_t SHORT_REQUEST_RECORD_MORE_FLAGS_OFFSET = SHORT_REQUEST_RECORD_FLAG_OFFSET + 1; | |
111 | const size_t SHORT_REQUEST_RECORD_LAYER_ENCRYPTION_TYPE = SHORT_REQUEST_RECORD_MORE_FLAGS_OFFSET + 2; | |
112 | const size_t SHORT_REQUEST_RECORD_REQUEST_TIME_OFFSET = SHORT_REQUEST_RECORD_LAYER_ENCRYPTION_TYPE + 1; | |
113 | const size_t SHORT_REQUEST_RECORD_REQUEST_EXPIRATION_OFFSET = SHORT_REQUEST_RECORD_REQUEST_TIME_OFFSET + 4; | |
114 | const size_t SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET = SHORT_REQUEST_RECORD_REQUEST_EXPIRATION_OFFSET + 4; | |
115 | const size_t SHORT_REQUEST_RECORD_PADDING_OFFSET = SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET + 4; | |
116 | const size_t SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE = 154; | |
117 | ||
118 | // ShortResponseRecord | |
119 | const size_t SHORT_RESPONSE_RECORD_OPTIONS_OFFSET = 0; | |
120 | const size_t SHORT_RESPONSE_RECORD_RET_OFFSET = 201; | |
102 | 121 | |
103 | 122 | enum I2NPMessageType |
104 | 123 | { |
114 | 133 | eI2NPTunnelBuild = 21, |
115 | 134 | eI2NPTunnelBuildReply = 22, |
116 | 135 | eI2NPVariableTunnelBuild = 23, |
117 | eI2NPVariableTunnelBuildReply = 24 | |
136 | eI2NPVariableTunnelBuildReply = 24, | |
137 | eI2NPShortTunnelBuild = 25, | |
138 | eI2NPShortTunnelBuildReply = 26 | |
118 | 139 | }; |
119 | 140 | |
141 | const uint8_t TUNNEL_BUILD_RECORD_GATEWAY_FLAG = 0x80; | |
142 | const uint8_t TUNNEL_BUILD_RECORD_ENDPOINT_FLAG = 0x40; | |
120 | 143 | const int NUM_TUNNEL_BUILD_RECORDS = 8; |
121 | 144 | |
122 | 145 | // DatabaseLookup flags |
259 | 282 | |
260 | 283 | std::shared_ptr<I2NPMessage> NewI2NPMessage (); |
261 | 284 | std::shared_ptr<I2NPMessage> NewI2NPShortMessage (); |
262 | std::shared_ptr<I2NPMessage> NewI2NPTunnelMessage (); | |
285 | std::shared_ptr<I2NPMessage> NewI2NPTunnelMessage (bool endpoint); | |
263 | 286 | std::shared_ptr<I2NPMessage> NewI2NPMessage (size_t len); |
264 | 287 | |
265 | 288 | std::shared_ptr<I2NPMessage> CreateI2NPMessage (I2NPMessageType msgType, const uint8_t * buf, size_t len, uint32_t replyMsgID = 0); |
280 | 303 | std::shared_ptr<I2NPMessage> CreateDatabaseStoreMsg (std::shared_ptr<const i2p::data::LocalLeaseSet> leaseSet, uint32_t replyToken = 0, std::shared_ptr<const i2p::tunnel::InboundTunnel> replyTunnel = nullptr); |
281 | 304 | bool IsRouterInfoMsg (std::shared_ptr<I2NPMessage> msg); |
282 | 305 | |
283 | bool HandleBuildRequestRecords (int num, uint8_t * records, uint8_t * clearText); | |
284 | void HandleVariableTunnelBuildMsg (uint32_t replyMsgID, uint8_t * buf, size_t len); | |
285 | void HandleVariableTunnelBuildReplyMsg (uint32_t replyMsgID, uint8_t * buf, size_t len); | |
286 | void HandleTunnelBuildMsg (uint8_t * buf, size_t len); | |
287 | ||
288 | 306 | std::shared_ptr<I2NPMessage> CreateTunnelDataMsg (const uint8_t * buf); |
289 | 307 | std::shared_ptr<I2NPMessage> CreateTunnelDataMsg (uint32_t tunnelID, const uint8_t * payload); |
290 | std::shared_ptr<I2NPMessage> CreateEmptyTunnelDataMsg (); | |
308 | std::shared_ptr<I2NPMessage> CreateEmptyTunnelDataMsg (bool endpoint); | |
291 | 309 | |
292 | 310 | std::shared_ptr<I2NPMessage> CreateTunnelGatewayMsg (uint32_t tunnelID, const uint8_t * buf, size_t len); |
293 | 311 | std::shared_ptr<I2NPMessage> CreateTunnelGatewayMsg (uint32_t tunnelID, I2NPMessageType msgType, |
21 | 21 | #include "NTCP2.h" |
22 | 22 | #include "HTTP.h" |
23 | 23 | #include "util.h" |
24 | ||
25 | #ifdef __linux__ | |
26 | #include <linux/in6.h> | |
27 | #endif | |
24 | 28 | |
25 | 29 | namespace i2p |
26 | 30 | { |
327 | 331 | m_SendMDCtx(nullptr), m_ReceiveMDCtx (nullptr), |
328 | 332 | #endif |
329 | 333 | m_NextReceivedLen (0), m_NextReceivedBuffer (nullptr), m_NextSendBuffer (nullptr), |
330 | m_ReceiveSequenceNumber (0), m_SendSequenceNumber (0), m_IsSending (false) | |
334 | m_ReceiveSequenceNumber (0), m_SendSequenceNumber (0), m_IsSending (false), | |
335 | m_NextPaddingSize (16) | |
331 | 336 | { |
332 | 337 | if (in_RemoteRouter) // Alice |
333 | 338 | { |
1053 | 1058 | size_t paddingSize = (msgLen*NTCP2_MAX_PADDING_RATIO)/100; |
1054 | 1059 | if (msgLen + paddingSize + 3 > NTCP2_UNENCRYPTED_FRAME_MAX_SIZE) paddingSize = NTCP2_UNENCRYPTED_FRAME_MAX_SIZE - msgLen -3; |
1055 | 1060 | if (paddingSize > len) paddingSize = len; |
1056 | if (paddingSize) paddingSize = rand () % paddingSize; | |
1061 | if (paddingSize) | |
1062 | { | |
1063 | if (m_NextPaddingSize >= 16) | |
1064 | { | |
1065 | RAND_bytes ((uint8_t *)m_PaddingSizes, sizeof (m_PaddingSizes)); | |
1066 | m_NextPaddingSize = 0; | |
1067 | } | |
1068 | paddingSize = m_PaddingSizes[m_NextPaddingSize++] % paddingSize; | |
1069 | } | |
1057 | 1070 | buf[0] = eNTCP2BlkPadding; // blk |
1058 | 1071 | htobe16buf (buf + 1, paddingSize); // size |
1059 | 1072 | memset (buf + 3, 0, paddingSize); |
1165 | 1178 | if (!address) continue; |
1166 | 1179 | if (address->IsPublishedNTCP2 () && address->port) |
1167 | 1180 | { |
1168 | if (address->host.is_v4()) | |
1181 | if (address->IsV4()) | |
1169 | 1182 | { |
1170 | 1183 | try |
1171 | 1184 | { |
1184 | 1197 | auto conn = std::make_shared<NTCP2Session>(*this); |
1185 | 1198 | m_NTCP2Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCP2Server::HandleAccept, this, conn, std::placeholders::_1)); |
1186 | 1199 | } |
1187 | else if (address->host.is_v6() && (context.SupportsV6 () || context.SupportsMesh ())) | |
1200 | else if (address->IsV6() && (context.SupportsV6 () || context.SupportsMesh ())) | |
1188 | 1201 | { |
1189 | 1202 | m_NTCP2V6Acceptor.reset (new boost::asio::ip::tcp::acceptor (GetService ())); |
1190 | 1203 | try |
1192 | 1205 | m_NTCP2V6Acceptor->open (boost::asio::ip::tcp::v6()); |
1193 | 1206 | m_NTCP2V6Acceptor->set_option (boost::asio::ip::v6_only (true)); |
1194 | 1207 | m_NTCP2V6Acceptor->set_option (boost::asio::socket_base::reuse_address (true)); |
1208 | #ifdef __linux__ | |
1209 | if (!m_Address6 && !m_YggdrasilAddress) // only if not binded to address | |
1210 | { | |
1211 | // Set preference to use public IPv6 address -- tested on linux, not works on windows, and not tested on others | |
1212 | #if (BOOST_VERSION >= 105500) | |
1213 | typedef boost::asio::detail::socket_option::integer<BOOST_ASIO_OS_DEF(IPPROTO_IPV6), IPV6_ADDR_PREFERENCES> ipv6PreferAddr; | |
1214 | #else | |
1215 | typedef boost::asio::detail::socket_option::integer<IPPROTO_IPV6, IPV6_ADDR_PREFERENCES> ipv6PreferAddr; | |
1216 | #endif | |
1217 | m_NTCP2V6Acceptor->set_option (ipv6PreferAddr(IPV6_PREFER_SRC_PUBLIC | IPV6_PREFER_SRC_HOME | IPV6_PREFER_SRC_NONCGA)); | |
1218 | } | |
1219 | #endif | |
1195 | 1220 | auto ep = boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v6(), address->port); |
1196 | 1221 | if (m_Address6 && !context.SupportsMesh ()) |
1197 | 1222 | ep = boost::asio::ip::tcp::endpoint (m_Address6->address(), address->port); |
217 | 217 | bool m_IsSending; |
218 | 218 | std::list<std::shared_ptr<I2NPMessage> > m_SendQueue; |
219 | 219 | uint64_t m_NextRouterInfoResendTime; // seconds since epoch |
220 | ||
221 | uint16_t m_PaddingSizes[16]; | |
222 | int m_NextPaddingSize; | |
220 | 223 | }; |
221 | 224 | |
222 | 225 | class NTCP2Server: private i2p::util::RunnableServiceWithWork |
58 | 58 | Reseed (); |
59 | 59 | else if (!GetRandomRouter (i2p::context.GetSharedRouterInfo (), false)) |
60 | 60 | Reseed (); // we don't have a router we can connect to. Trying to reseed |
61 | ||
62 | auto it = m_RouterInfos.find (i2p::context.GetIdentHash ()); | |
63 | if (it != m_RouterInfos.end ()) | |
64 | { | |
65 | // remove own router | |
66 | m_RouterInfos.erase (it); | |
67 | m_Floodfills.remove (it->second); | |
68 | } | |
69 | // insert own router | |
70 | m_RouterInfos.emplace (i2p::context.GetIdentHash (), i2p::context.GetSharedRouterInfo ()); | |
71 | if (i2p::context.IsFloodfill ()) | |
72 | m_Floodfills.push_back (i2p::context.GetSharedRouterInfo ()); | |
61 | 73 | |
62 | 74 | i2p::config::GetOption("persist.profiles", m_PersistProfiles); |
63 | 75 | |
161 | 173 | bool publish = false; |
162 | 174 | if (m_PublishReplyToken) |
163 | 175 | { |
176 | // next publishing attempt | |
164 | 177 | if (ts - lastPublish >= NETDB_PUBLISH_CONFIRMATION_TIMEOUT) publish = true; |
165 | 178 | } |
166 | 179 | else if (i2p::context.GetLastUpdateTime () > lastPublish || |
167 | ts - lastPublish >= NETDB_PUBLISH_INTERVAL) publish = true; | |
180 | ts - lastPublish >= NETDB_PUBLISH_INTERVAL) | |
181 | { | |
182 | // new publish | |
183 | m_PublishExcluded.clear (); | |
184 | if (i2p::context.IsFloodfill ()) | |
185 | m_PublishExcluded.insert (i2p::context.GetIdentHash ()); // do publish to ourselves | |
186 | publish = true; | |
187 | } | |
168 | 188 | if (publish) // update timestamp and publish |
169 | 189 | { |
170 | 190 | i2p::context.UpdateTimestamp (ts); |
452 | 472 | bool NetDb::LoadRouterInfo (const std::string & path) |
453 | 473 | { |
454 | 474 | auto r = std::make_shared<RouterInfo>(path); |
455 | if (r->GetRouterIdentity () && !r->IsUnreachable () && | |
456 | (r->IsReachable () || !r->IsSSU (false) || m_LastLoad < r->GetTimestamp () + NETDB_INTRODUCEE_EXPIRATION_TIMEOUT*1000LL)) // 1 hour | |
457 | { | |
475 | if (r->GetRouterIdentity () && !r->IsUnreachable () && r->HasValidAddresses ()) | |
476 | { | |
458 | 477 | r->DeleteBuffer (); |
459 | 478 | r->ClearProperties (); // properties are not used for regular routers |
460 | m_RouterInfos[r->GetIdentHash ()] = r; | |
461 | if (r->IsFloodfill () && r->IsReachable ()) // floodfill must be reachable | |
462 | m_Floodfills.push_back (r); | |
479 | if (m_RouterInfos.emplace (r->GetIdentHash (), r).second) | |
480 | { | |
481 | if (r->IsFloodfill () && r->IsEligibleFloodfill ()) | |
482 | m_Floodfills.push_back (r); | |
483 | } | |
463 | 484 | } |
464 | 485 | else |
465 | 486 | { |
566 | 587 | expirationTimeout = i2p::context.IsFloodfill () ? NETDB_FLOODFILL_EXPIRATION_TIMEOUT*1000LL : |
567 | 588 | NETDB_MIN_EXPIRATION_TIMEOUT*1000LL + (NETDB_MAX_EXPIRATION_TIMEOUT - NETDB_MIN_EXPIRATION_TIMEOUT)*1000LL*NETDB_MIN_ROUTERS/total; |
568 | 589 | |
590 | auto own = i2p::context.GetSharedRouterInfo (); | |
569 | 591 | for (auto& it: m_RouterInfos) |
570 | 592 | { |
593 | if (it.second == own) continue; // skip own | |
571 | 594 | std::string ident = it.second->GetIdentHashBase64(); |
572 | 595 | std::string path = m_Storage.Path(ident); |
573 | 596 | if (it.second->IsUpdated ()) |
987 | 1010 | { |
988 | 1011 | uint64_t tag; |
989 | 1012 | memcpy (&tag, excluded + 33, 8); |
990 | replyMsg = i2p::garlic::WrapECIESX25519AEADRatchetMessage (replyMsg, sessionKey, tag); | |
1013 | replyMsg = i2p::garlic::WrapECIESX25519Message (replyMsg, sessionKey, tag); | |
991 | 1014 | } |
992 | 1015 | else |
993 | 1016 | { |
1186 | 1209 | (reverse ? compatibleWith->IsReachableFrom (*router) : |
1187 | 1210 | router->IsReachableFrom (*compatibleWith)) && |
1188 | 1211 | (router->GetCaps () & RouterInfo::eHighBandwidth) && |
1212 | #if defined(__x86_64__) | |
1189 | 1213 | router->GetVersion () >= NETDB_MIN_HIGHBANDWIDTH_VERSION; |
1214 | #else | |
1215 | router->GetIdentity ()->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD; | |
1216 | #endif | |
1190 | 1217 | }); |
1191 | 1218 | } |
1192 | 1219 | |
1195 | 1222 | { |
1196 | 1223 | if (m_RouterInfos.empty()) |
1197 | 1224 | return 0; |
1198 | uint32_t ind = rand () % m_RouterInfos.size (); | |
1199 | for (int j = 0; j < 2; j++) | |
1200 | { | |
1201 | uint32_t i = 0; | |
1202 | std::unique_lock<std::mutex> l(m_RouterInfosMutex); | |
1203 | for (const auto& it: m_RouterInfos) | |
1204 | { | |
1205 | if (i >= ind) | |
1206 | { | |
1207 | if (!it.second->IsUnreachable () && filter (it.second)) | |
1208 | return it.second; | |
1209 | } | |
1210 | else | |
1211 | i++; | |
1212 | } | |
1213 | // we couldn't find anything, try second pass | |
1214 | ind = 0; | |
1225 | uint16_t inds[3]; | |
1226 | RAND_bytes ((uint8_t *)inds, sizeof (inds)); | |
1227 | std::unique_lock<std::mutex> l(m_RouterInfosMutex); | |
1228 | inds[0] %= m_RouterInfos.size (); | |
1229 | auto it = m_RouterInfos.begin (); | |
1230 | std::advance (it, inds[0]); | |
1231 | // try random router | |
1232 | if (it != m_RouterInfos.end () && !it->second->IsUnreachable () && filter (it->second)) | |
1233 | return it->second; | |
1234 | // try some routers around | |
1235 | auto it1 = m_RouterInfos.begin (); | |
1236 | if (inds[0]) | |
1237 | { | |
1238 | // before | |
1239 | inds[1] %= inds[0]; | |
1240 | std::advance (it1, (inds[1] + inds[0])/2); | |
1241 | } | |
1242 | else | |
1243 | it1 = it; | |
1244 | auto it2 = it; | |
1245 | if (inds[0] < m_RouterInfos.size () - 1) | |
1246 | { | |
1247 | // after | |
1248 | inds[2] %= (m_RouterInfos.size () - 1 - inds[0]); inds[2] /= 2; | |
1249 | std::advance (it2, inds[2]); | |
1250 | } | |
1251 | // it1 - from, it2 - to | |
1252 | it = it1; | |
1253 | while (it != it2 && it != m_RouterInfos.end ()) | |
1254 | { | |
1255 | if (!it->second->IsUnreachable () && filter (it->second)) | |
1256 | return it->second; | |
1257 | it++; | |
1258 | } | |
1259 | // still not found, try from the begining | |
1260 | it = m_RouterInfos.begin (); | |
1261 | while (it != it1 && it != m_RouterInfos.end ()) | |
1262 | { | |
1263 | if (!it->second->IsUnreachable () && filter (it->second)) | |
1264 | return it->second; | |
1265 | it++; | |
1266 | } | |
1267 | // still not found, try to the begining | |
1268 | it = it2; | |
1269 | while (it != m_RouterInfos.end ()) | |
1270 | { | |
1271 | if (!it->second->IsUnreachable () && filter (it->second)) | |
1272 | return it->second; | |
1273 | it++; | |
1215 | 1274 | } |
1216 | 1275 | return nullptr; // seems we have too few routers |
1217 | 1276 | } |
43 | 43 | const int NETDB_PUBLISH_CONFIRMATION_TIMEOUT = 5; // in seconds |
44 | 44 | const int NETDB_MAX_PUBLISH_EXCLUDED_FLOODFILLS = 15; |
45 | 45 | const int NETDB_MIN_HIGHBANDWIDTH_VERSION = MAKE_VERSION_NUMBER(0, 9, 36); // 0.9.36 |
46 | const int NETDB_MIN_FLOODFILL_VERSION = MAKE_VERSION_NUMBER(0, 9, 28); // 0.9.28 | |
46 | const int NETDB_MIN_FLOODFILL_VERSION = MAKE_VERSION_NUMBER(0, 9, 38); // 0.9.38 | |
47 | const int NETDB_MIN_SHORT_TUNNEL_BUILD_VERSION = MAKE_VERSION_NUMBER(0, 9, 51); // 0.9.51 | |
47 | 48 | |
48 | 49 | /** function for visiting a leaseset stored in a floodfill */ |
49 | 50 | typedef std::function<void(const IdentHash, std::shared_ptr<LeaseSet>)> LeaseSetVisitor; |
496 | 496 | |
497 | 497 | void Reseeder::LoadCertificates () |
498 | 498 | { |
499 | std::string certDir = i2p::fs::DataDirPath("certificates", "reseed"); | |
499 | std::string certDir = i2p::fs::GetCertsDir() + i2p::fs::dirSep + "reseed"; | |
500 | ||
500 | 501 | std::vector<std::string> files; |
501 | 502 | int numCertificates = 0; |
502 | 503 |
44 | 44 | UpdateRouterInfo (); |
45 | 45 | if (IsECIES ()) |
46 | 46 | { |
47 | auto initState = new i2p::crypto::NoiseSymmetricState (); | |
48 | i2p::crypto::InitNoiseNState (*initState, GetIdentity ()->GetEncryptionPublicKey ()); | |
49 | m_InitialNoiseState.reset (initState); | |
50 | m_ECIESSession = std::make_shared<i2p::garlic::RouterIncomingRatchetSession>(*initState); | |
47 | i2p::crypto::InitNoiseNState (m_InitialNoiseState, GetIdentity ()->GetEncryptionPublicKey ()); | |
48 | m_ECIESSession = std::make_shared<i2p::garlic::RouterIncomingRatchetSession>(m_InitialNoiseState); | |
51 | 49 | } |
52 | 50 | } |
53 | 51 | |
471 | 469 | uint8_t caps = m_RouterInfo.GetCaps (); |
472 | 470 | caps &= ~i2p::data::RouterInfo::eReachable; |
473 | 471 | caps |= i2p::data::RouterInfo::eUnreachable; |
474 | caps &= ~i2p::data::RouterInfo::eFloodfill; // can't be floodfill | |
472 | if (v6 || !SupportsV6 ()) | |
473 | caps &= ~i2p::data::RouterInfo::eFloodfill; // can't be floodfill | |
475 | 474 | m_RouterInfo.SetCaps (caps); |
476 | 475 | } |
477 | 476 | uint16_t port = 0; |
485 | 484 | addr->ssu->introducers.clear (); |
486 | 485 | port = addr->port; |
487 | 486 | } |
488 | // unpiblish NTCP2 addreeses | |
487 | // unpublish NTCP2 addreeses | |
489 | 488 | bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); |
490 | 489 | if (ntcp2) |
491 | 490 | PublishNTCP2Address (port, false, v4, v6, false); |
492 | 491 | // update |
492 | m_RouterInfo.UpdateSupportedTransports (); | |
493 | 493 | UpdateRouterInfo (); |
494 | 494 | } |
495 | 495 | |
529 | 529 | } |
530 | 530 | } |
531 | 531 | // update |
532 | m_RouterInfo.UpdateSupportedTransports (); | |
532 | 533 | UpdateRouterInfo (); |
533 | 534 | } |
534 | 535 | |
567 | 568 | { |
568 | 569 | bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); |
569 | 570 | bool ntcp2Published; i2p::config::GetOption("ntcp2.published", ntcp2Published); |
570 | if (ntcp2 && ntcp2Published) | |
571 | { | |
572 | std::string ntcp2Host; | |
573 | if (!i2p::config::IsDefault ("ntcp2.addressv6")) | |
574 | i2p::config::GetOption ("ntcp2.addressv6", ntcp2Host); | |
571 | if (ntcp2) | |
572 | { | |
573 | if (ntcp2Published) | |
574 | { | |
575 | std::string ntcp2Host; | |
576 | if (!i2p::config::IsDefault ("ntcp2.addressv6")) | |
577 | i2p::config::GetOption ("ntcp2.addressv6", ntcp2Host); | |
578 | else | |
579 | ntcp2Host = "::1"; | |
580 | uint16_t ntcp2Port; i2p::config::GetOption ("ntcp2.port", ntcp2Port); | |
581 | if (!ntcp2Port) ntcp2Port = port; | |
582 | m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, boost::asio::ip::address::from_string (ntcp2Host), ntcp2Port); | |
583 | } | |
575 | 584 | else |
576 | ntcp2Host = "::1"; | |
577 | uint16_t ntcp2Port; i2p::config::GetOption ("ntcp2.port", ntcp2Port); | |
578 | if (!ntcp2Port) ntcp2Port = port; | |
579 | m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, boost::asio::ip::address::from_string (ntcp2Host), ntcp2Port); | |
585 | m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, boost::asio::ip::address(), 0, i2p::data::RouterInfo::eV6); | |
580 | 586 | } |
581 | 587 | } |
582 | 588 | m_RouterInfo.EnableV6 (); |
631 | 637 | m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, boost::asio::ip::address::from_string (host), ntcp2Port); |
632 | 638 | } |
633 | 639 | else |
634 | m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv); | |
640 | m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, boost::asio::ip::address(), 0, i2p::data::RouterInfo::eV4); | |
635 | 641 | } |
636 | 642 | } |
637 | 643 | m_RouterInfo.EnableV4 (); |
678 | 684 | if (addr->IsPublishedNTCP2 ()) |
679 | 685 | { |
680 | 686 | bool isYgg1 = i2p::util::net::IsYggdrasilAddress (addr->host); |
681 | if (addr->host.is_v6 () && ((isYgg && isYgg1) || (!isYgg && !isYgg1))) | |
687 | if (addr->IsV6 () && ((isYgg && isYgg1) || (!isYgg && !isYgg1))) | |
682 | 688 | { |
683 | 689 | if (addr->host != host) |
684 | 690 | { |
735 | 741 | } |
736 | 742 | } |
737 | 743 | std::shared_ptr<const i2p::data::IdentityEx> oldIdentity; |
738 | bool rekey = m_Keys.GetPublic ()->GetSigningKeyType () == i2p::data::SIGNING_KEY_TYPE_DSA_SHA1; | |
739 | if (!rekey && m_Keys.GetPublic ()->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ELGAMAL) | |
740 | { | |
741 | // rekey routers with bandwidth = L (or default) this time | |
742 | bool isFloodfill; i2p::config::GetOption("floodfill", isFloodfill); | |
743 | if (!isFloodfill) rekey = true; | |
744 | } | |
745 | if (rekey) | |
744 | if (m_Keys.GetPublic ()->GetSigningKeyType () == i2p::data::SIGNING_KEY_TYPE_DSA_SHA1 || | |
745 | m_Keys.GetPublic ()->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ELGAMAL) | |
746 | 746 | { |
747 | 747 | // update keys |
748 | 748 | LogPrint (eLogInfo, "Router: router keys are obsolete. Creating new"); |
820 | 820 | i2p::HandleI2NPMessage (CreateI2NPMessage (buf, GetI2NPMessageLength (buf, len))); |
821 | 821 | } |
822 | 822 | |
823 | bool RouterContext::HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len) | |
824 | { | |
825 | auto msg = CreateI2NPMessage (typeID, payload, len); | |
823 | bool RouterContext::HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID) | |
824 | { | |
825 | auto msg = CreateI2NPMessage (typeID, payload, len, msgID); | |
826 | 826 | if (!msg) return false; |
827 | 827 | i2p::HandleI2NPMessage (msg); |
828 | 828 | return true; |
842 | 842 | return; |
843 | 843 | } |
844 | 844 | buf += 4; |
845 | if (m_ECIESSession) | |
846 | m_ECIESSession->HandleNextMessage (buf, len); | |
847 | else | |
848 | LogPrint (eLogError, "Router: Session is not set for ECIES router"); | |
845 | if (!HandleECIESx25519TagMessage (buf, len)) // try tag first | |
846 | { | |
847 | // then Noise_N one-time decryption | |
848 | if (m_ECIESSession) | |
849 | m_ECIESSession->HandleNextMessage (buf, len); | |
850 | else | |
851 | LogPrint (eLogError, "Router: Session is not set for ECIES router"); | |
852 | } | |
849 | 853 | } |
850 | 854 | else |
851 | 855 | i2p::garlic::GarlicDestination::ProcessGarlicMessage (msg); |
880 | 884 | |
881 | 885 | bool RouterContext::DecryptTunnelBuildRecord (const uint8_t * encrypted, uint8_t * data) |
882 | 886 | { |
883 | if (!m_TunnelDecryptor) return false; | |
884 | 887 | if (IsECIES ()) |
885 | { | |
886 | if (!m_InitialNoiseState) return false; | |
887 | // m_InitialNoiseState is h = SHA256(h || hepk) | |
888 | m_CurrentNoiseState.reset (new i2p::crypto::NoiseSymmetricState (*m_InitialNoiseState)); | |
889 | m_CurrentNoiseState->MixHash (encrypted, 32); // h = SHA256(h || sepk) | |
890 | uint8_t sharedSecret[32]; | |
891 | if (!m_TunnelDecryptor->Decrypt (encrypted, sharedSecret, nullptr, false)) | |
892 | { | |
893 | LogPrint (eLogWarning, "Router: Incorrect ephemeral public key"); | |
894 | return false; | |
895 | } | |
896 | m_CurrentNoiseState->MixKey (sharedSecret); | |
897 | encrypted += 32; | |
898 | uint8_t nonce[12]; | |
899 | memset (nonce, 0, 12); | |
900 | if (!i2p::crypto::AEADChaCha20Poly1305 (encrypted, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE, | |
901 | m_CurrentNoiseState->m_H, 32, m_CurrentNoiseState->m_CK + 32, nonce, data, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE, false)) // decrypt | |
902 | { | |
903 | LogPrint (eLogWarning, "Router: Tunnel record AEAD decryption failed"); | |
904 | return false; | |
905 | } | |
906 | m_CurrentNoiseState->MixHash (encrypted, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE + 16); // h = SHA256(h || ciphertext) | |
907 | return true; | |
908 | } | |
909 | else | |
910 | { | |
888 | return DecryptECIESTunnelBuildRecord (encrypted, data, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE); | |
889 | else | |
890 | { | |
891 | if (!m_TunnelDecryptor) return false; | |
911 | 892 | BN_CTX * ctx = BN_CTX_new (); |
912 | 893 | bool success = m_TunnelDecryptor->Decrypt (encrypted, data, ctx, false); |
913 | 894 | BN_CTX_free (ctx); |
915 | 896 | } |
916 | 897 | } |
917 | 898 | |
899 | bool RouterContext::DecryptECIESTunnelBuildRecord (const uint8_t * encrypted, uint8_t * data, size_t clearTextSize) | |
900 | { | |
901 | // m_InitialNoiseState is h = SHA256(h || hepk) | |
902 | m_CurrentNoiseState = m_InitialNoiseState; | |
903 | m_CurrentNoiseState.MixHash (encrypted, 32); // h = SHA256(h || sepk) | |
904 | uint8_t sharedSecret[32]; | |
905 | if (!m_TunnelDecryptor->Decrypt (encrypted, sharedSecret, nullptr, false)) | |
906 | { | |
907 | LogPrint (eLogWarning, "Router: Incorrect ephemeral public key"); | |
908 | return false; | |
909 | } | |
910 | m_CurrentNoiseState.MixKey (sharedSecret); | |
911 | encrypted += 32; | |
912 | uint8_t nonce[12]; | |
913 | memset (nonce, 0, 12); | |
914 | if (!i2p::crypto::AEADChaCha20Poly1305 (encrypted, clearTextSize, m_CurrentNoiseState.m_H, 32, | |
915 | m_CurrentNoiseState.m_CK + 32, nonce, data, clearTextSize, false)) // decrypt | |
916 | { | |
917 | LogPrint (eLogWarning, "Router: Tunnel record AEAD decryption failed"); | |
918 | return false; | |
919 | } | |
920 | m_CurrentNoiseState.MixHash (encrypted, clearTextSize + 16); // h = SHA256(h || ciphertext) | |
921 | return true; | |
922 | } | |
923 | ||
924 | bool RouterContext::DecryptTunnelShortRequestRecord (const uint8_t * encrypted, uint8_t * data) | |
925 | { | |
926 | if (IsECIES ()) | |
927 | return DecryptECIESTunnelBuildRecord (encrypted, data, SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE); | |
928 | else | |
929 | { | |
930 | LogPrint (eLogWarning, "Router: Can't decrypt short request record on non-ECIES router"); | |
931 | return false; | |
932 | } | |
933 | } | |
934 | ||
918 | 935 | i2p::crypto::X25519Keys& RouterContext::GetStaticKeys () |
919 | 936 | { |
920 | 937 | if (!m_StaticKeys) |
17 | 17 | #include "Identity.h" |
18 | 18 | #include "RouterInfo.h" |
19 | 19 | #include "Garlic.h" |
20 | #include "I18N_langs.h" | |
20 | 21 | |
21 | 22 | namespace i2p |
22 | 23 | { |
23 | 24 | namespace garlic |
24 | 25 | { |
25 | 26 | class RouterIncomingRatchetSession; |
26 | } | |
27 | } | |
27 | 28 | |
28 | 29 | const char ROUTER_INFO[] = "router.info"; |
29 | 30 | const char ROUTER_KEYS[] = "router.keys"; |
38 | 39 | eRouterStatusError = 3, |
39 | 40 | eRouterStatusUnknown = 4, |
40 | 41 | eRouterStatusProxy = 5, |
41 | eRouterStatusMesh = 6 | |
42 | eRouterStatusMesh = 6 | |
42 | 43 | }; |
43 | 44 | |
44 | 45 | enum RouterError |
48 | 49 | eRouterErrorOffline = 2, |
49 | 50 | eRouterErrorSymmetricNAT = 3 |
50 | 51 | }; |
51 | ||
52 | ||
52 | 53 | class RouterContext: public i2p::garlic::GarlicDestination |
53 | 54 | { |
54 | 55 | private: |
66 | 67 | void Init (); |
67 | 68 | |
68 | 69 | const i2p::data::PrivateKeys& GetPrivateKeys () const { return m_Keys; }; |
69 | i2p::data::RouterInfo& GetRouterInfo () { return m_RouterInfo; }; | |
70 | std::shared_ptr<const i2p::data::RouterInfo> GetSharedRouterInfo () const | |
70 | i2p::data::RouterInfo& GetRouterInfo () { return m_RouterInfo; }; | |
71 | std::shared_ptr<i2p::data::RouterInfo> GetSharedRouterInfo () | |
71 | 72 | { |
72 | return std::shared_ptr<const i2p::data::RouterInfo> (&m_RouterInfo, | |
73 | [](const i2p::data::RouterInfo *) {}); | |
73 | return std::shared_ptr<i2p::data::RouterInfo> (&m_RouterInfo, | |
74 | [](i2p::data::RouterInfo *) {}); | |
74 | 75 | } |
75 | 76 | std::shared_ptr<i2p::garlic::GarlicDestination> GetSharedDestination () |
76 | 77 | { |
95 | 96 | int GetNetID () const { return m_NetID; }; |
96 | 97 | void SetNetID (int netID) { m_NetID = netID; }; |
97 | 98 | bool DecryptTunnelBuildRecord (const uint8_t * encrypted, uint8_t * data); |
98 | ||
99 | bool DecryptTunnelShortRequestRecord (const uint8_t * encrypted, uint8_t * data); | |
100 | ||
99 | 101 | void UpdatePort (int port); // called from Daemon |
100 | void UpdateAddress (const boost::asio::ip::address& host); // called from SSU or Daemon | |
102 | void UpdateAddress (const boost::asio::ip::address& host); // called from SSU or Daemon | |
101 | 103 | void PublishNTCP2Address (int port, bool publish, bool v4, bool v6, bool ygg); |
102 | 104 | void UpdateNTCP2Address (bool enable); |
103 | 105 | void RemoveNTCPAddress (bool v4only = true); // delete NTCP address for older routers. TODO: remove later |
122 | 124 | void SetSupportsV4 (bool supportsV4); |
123 | 125 | void SetSupportsMesh (bool supportsmesh, const boost::asio::ip::address_v6& host); |
124 | 126 | bool IsECIES () const { return GetIdentity ()->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD; }; |
125 | std::unique_ptr<i2p::crypto::NoiseSymmetricState>& GetCurrentNoiseState () { return m_CurrentNoiseState; }; | |
126 | ||
127 | i2p::crypto::NoiseSymmetricState& GetCurrentNoiseState () { return m_CurrentNoiseState; }; | |
128 | ||
127 | 129 | void UpdateNTCP2V6Address (const boost::asio::ip::address& host); // called from Daemon. TODO: remove |
128 | 130 | void UpdateStats (); |
129 | 131 | void UpdateTimestamp (uint64_t ts); // in seconds, called from NetDb before publishing |
130 | void CleanupDestination (); // garlic destination | |
132 | void CleanupDestination (); // garlic destination | |
131 | 133 | |
132 | 134 | // implements LocalDestination |
133 | 135 | std::shared_ptr<const i2p::data::IdentityEx> GetIdentity () const { return m_Keys.GetPublic (); }; |
143 | 145 | void ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg); |
144 | 146 | void ProcessDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg); |
145 | 147 | |
148 | // i18n | |
149 | std::shared_ptr<const i2p::i18n::Locale> GetLanguage () { return m_Language; }; | |
150 | void SetLanguage (const std::shared_ptr<const i2p::i18n::Locale> language) { m_Language = language; }; | |
151 | ||
146 | 152 | protected: |
147 | 153 | |
148 | 154 | // implements GarlicDestination |
149 | 155 | void HandleI2NPMessage (const uint8_t * buf, size_t len); |
150 | bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len); | |
156 | bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID); | |
151 | 157 | |
152 | 158 | private: |
153 | 159 | |
158 | 164 | bool Load (); |
159 | 165 | void SaveKeys (); |
160 | 166 | |
167 | bool DecryptECIESTunnelBuildRecord (const uint8_t * encrypted, uint8_t * data, size_t clearTextSize); | |
168 | ||
161 | 169 | private: |
162 | 170 | |
163 | 171 | i2p::data::RouterInfo m_RouterInfo; |
176 | 184 | std::unique_ptr<NTCP2PrivateKeys> m_NTCP2Keys; |
177 | 185 | std::unique_ptr<i2p::crypto::X25519Keys> m_StaticKeys; |
178 | 186 | // for ECIESx25519 |
179 | std::unique_ptr<i2p::crypto::NoiseSymmetricState> m_InitialNoiseState, m_CurrentNoiseState; | |
187 | i2p::crypto::NoiseSymmetricState m_InitialNoiseState, m_CurrentNoiseState; | |
188 | ||
189 | // i18n | |
190 | std::shared_ptr<const i2p::i18n::Locale> m_Language; | |
180 | 191 | }; |
181 | 192 | |
182 | 193 | extern RouterContext context; |
35 | 35 | |
36 | 36 | RouterInfo::RouterInfo (const std::string& fullPath): |
37 | 37 | m_FullPath (fullPath), m_IsUpdated (false), m_IsUnreachable (false), |
38 | m_SupportedTransports (0), m_Caps (0), m_Version (0) | |
38 | m_SupportedTransports (0), m_ReachableTransports (0), m_Caps (0), m_Version (0) | |
39 | 39 | { |
40 | 40 | m_Addresses = boost::make_shared<Addresses>(); // create empty list |
41 | 41 | m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE]; |
44 | 44 | |
45 | 45 | RouterInfo::RouterInfo (const uint8_t * buf, int len): |
46 | 46 | m_IsUpdated (true), m_IsUnreachable (false), m_SupportedTransports (0), |
47 | m_Caps (0), m_Version (0) | |
47 | m_ReachableTransports (0), m_Caps (0), m_Version (0) | |
48 | 48 | { |
49 | 49 | m_Addresses = boost::make_shared<Addresses>(); // create empty list |
50 | 50 | if (len <= MAX_RI_BUFFER_SIZE) |
83 | 83 | m_IsUpdated = true; |
84 | 84 | m_IsUnreachable = false; |
85 | 85 | m_SupportedTransports = 0; |
86 | m_ReachableTransports = 0; | |
86 | 87 | m_Caps = 0; |
87 | 88 | // don't clean up m_Addresses, it will be replaced in ReadFromStream |
88 | 89 | m_Properties.clear (); |
304 | 305 | if (isHost) |
305 | 306 | { |
306 | 307 | if (address->host.is_v6 ()) |
307 | supportedTransports |= i2p::util::net::IsYggdrasilAddress (address->host) ? eNTCP2V6Mesh : eNTCP2V6; | |
308 | supportedTransports |= (i2p::util::net::IsYggdrasilAddress (address->host) ? eNTCP2V6Mesh : eNTCP2V6); | |
308 | 309 | else |
309 | 310 | supportedTransports |= eNTCP2V4; |
311 | m_ReachableTransports |= supportedTransports; | |
310 | 312 | } |
311 | 313 | else if (!address->published) |
312 | 314 | { |
340 | 342 | int numValid = 0; |
341 | 343 | for (auto& it: address->ssu->introducers) |
342 | 344 | { |
343 | if ((!it.iExp || ts <= it.iExp) && it.iPort > 0 && | |
345 | if (!it.iExp) it.iExp = m_Timestamp/1000 + NETDB_INTRODUCEE_EXPIRATION_TIMEOUT; | |
346 | if (ts <= it.iExp && it.iPort > 0 && | |
344 | 347 | ((it.iHost.is_v4 () && address->IsV4 ()) || (it.iHost.is_v6 () && address->IsV6 ()))) |
345 | 348 | numValid++; |
346 | 349 | else |
347 | 350 | it.iPort = 0; |
348 | 351 | } |
349 | if (!numValid) address->ssu->introducers.resize (0); | |
352 | if (numValid) | |
353 | m_ReachableTransports |= supportedTransports; | |
354 | else | |
355 | address->ssu->introducers.resize (0); | |
350 | 356 | } |
351 | 357 | else if (isHost && address->port) |
358 | { | |
352 | 359 | address->published = true; |
360 | m_ReachableTransports |= supportedTransports; | |
361 | } | |
353 | 362 | } |
354 | 363 | } |
355 | 364 | if (supportedTransports) |
545 | 554 | if (address.IsNTCP2 ()) |
546 | 555 | { |
547 | 556 | WriteString ("NTCP2", s); |
548 | if (address.IsPublishedNTCP2 () && !address.host.is_unspecified ()) | |
557 | if (address.IsPublishedNTCP2 () && !address.host.is_unspecified () && address.port) | |
549 | 558 | isPublished = true; |
550 | 559 | else |
551 | 560 | { |
831 | 840 | for (const auto& it: *m_Addresses) // don't insert same address twice |
832 | 841 | if (*it == *addr) return; |
833 | 842 | m_SupportedTransports |= addr->host.is_v6 () ? eSSUV6 : eSSUV4; |
843 | m_ReachableTransports |= addr->host.is_v6 () ? eSSUV6 : eSSUV4; | |
834 | 844 | m_Addresses->push_back(std::move(addr)); |
835 | 845 | } |
836 | 846 | |
837 | void RouterInfo::AddNTCP2Address (const uint8_t * staticKey, const uint8_t * iv, const boost::asio::ip::address& host, int port) | |
847 | void RouterInfo::AddNTCP2Address (const uint8_t * staticKey, const uint8_t * iv, | |
848 | const boost::asio::ip::address& host, int port, uint8_t caps) | |
838 | 849 | { |
839 | 850 | auto addr = std::make_shared<Address>(); |
840 | 851 | addr->host = host; |
841 | 852 | addr->port = port; |
842 | 853 | addr->transportStyle = eTransportNTCP; |
843 | addr->caps = 0; | |
854 | addr->caps = caps; | |
844 | 855 | addr->date = 0; |
845 | 856 | addr->ntcp2.reset (new NTCP2Ext ()); |
846 | 857 | if (port) addr->published = true; |
847 | 858 | memcpy (addr->ntcp2->staticKey, staticKey, 32); |
848 | 859 | memcpy (addr->ntcp2->iv, iv, 16); |
860 | if (addr->IsV4 ()) | |
861 | { | |
862 | m_SupportedTransports |= eNTCP2V4; | |
863 | if (addr->published) m_ReachableTransports |= eNTCP2V4; | |
864 | } | |
865 | if (addr->IsV6 ()) | |
866 | { | |
867 | m_SupportedTransports |= eNTCP2V6; | |
868 | if (addr->published) m_ReachableTransports |= eNTCP2V6; | |
869 | } | |
849 | 870 | m_Addresses->push_back(std::move(addr)); |
850 | 871 | } |
851 | 872 | |
859 | 880 | for (auto& intro: addr->ssu->introducers) |
860 | 881 | if (intro.iTag == introducer.iTag) return false; // already presented |
861 | 882 | addr->ssu->introducers.push_back (introducer); |
883 | m_ReachableTransports |= (addr->IsV4 () ? eSSUV4 : eSSUV6); | |
862 | 884 | return true; |
863 | 885 | } |
864 | 886 | } |
876 | 898 | if (boost::asio::ip::udp::endpoint (it->iHost, it->iPort) == e) |
877 | 899 | { |
878 | 900 | addr->ssu->introducers.erase (it); |
901 | if (addr->ssu->introducers.empty ()) | |
902 | m_ReachableTransports &= ~(addr->IsV4 () ? eSSUV4 : eSSUV6); | |
879 | 903 | return true; |
880 | 904 | } |
881 | 905 | } |
959 | 983 | { |
960 | 984 | if (!IsV6 ()) |
961 | 985 | { |
962 | m_SupportedTransports |= eSSUV6 | eNTCP2V6; | |
963 | 986 | uint8_t addressCaps = AddressCaps::eV6; |
964 | 987 | if (IsV4 ()) addressCaps |= AddressCaps::eV4; |
965 | 988 | SetUnreachableAddressesTransportCaps (addressCaps); |
989 | UpdateSupportedTransports (); | |
966 | 990 | } |
967 | 991 | } |
968 | 992 | |
970 | 994 | { |
971 | 995 | if (!IsV4 ()) |
972 | 996 | { |
973 | m_SupportedTransports |= eSSUV4 | eNTCP2V4; | |
974 | 997 | uint8_t addressCaps = AddressCaps::eV4; |
975 | 998 | if (IsV6 ()) addressCaps |= AddressCaps::eV6; |
976 | 999 | SetUnreachableAddressesTransportCaps (addressCaps); |
1000 | UpdateSupportedTransports (); | |
977 | 1001 | } |
978 | 1002 | } |
979 | 1003 | |
982 | 1006 | { |
983 | 1007 | if (IsV6 ()) |
984 | 1008 | { |
985 | m_SupportedTransports &= ~(eSSUV6 | eNTCP2V6); | |
986 | 1009 | for (auto it = m_Addresses->begin (); it != m_Addresses->end ();) |
987 | 1010 | { |
988 | 1011 | auto addr = *it; |
989 | addr->caps &= ~AddressCaps::eV6; | |
990 | if (addr->host.is_v6 ()) | |
991 | it = m_Addresses->erase (it); | |
1012 | if (addr->IsV6 ()) | |
1013 | { | |
1014 | if (addr->IsV4 ()) | |
1015 | { | |
1016 | addr->caps &= ~AddressCaps::eV6; | |
1017 | ++it; | |
1018 | } | |
1019 | else | |
1020 | it = m_Addresses->erase (it); | |
1021 | } | |
992 | 1022 | else |
993 | 1023 | ++it; |
994 | 1024 | } |
1025 | UpdateSupportedTransports (); | |
995 | 1026 | } |
996 | 1027 | } |
997 | 1028 | |
999 | 1030 | { |
1000 | 1031 | if (IsV4 ()) |
1001 | 1032 | { |
1002 | m_SupportedTransports &= ~(eSSUV4 | eNTCP2V4); | |
1003 | 1033 | for (auto it = m_Addresses->begin (); it != m_Addresses->end ();) |
1004 | 1034 | { |
1005 | 1035 | auto addr = *it; |
1006 | addr->caps &= ~AddressCaps::eV4; | |
1007 | if (addr->host.is_v4 ()) | |
1008 | it = m_Addresses->erase (it); | |
1036 | if (addr->IsV4 ()) | |
1037 | { | |
1038 | if (addr->IsV6 ()) | |
1039 | { | |
1040 | addr->caps &= ~AddressCaps::eV4; | |
1041 | ++it; | |
1042 | } | |
1043 | else | |
1044 | it = m_Addresses->erase (it); | |
1045 | } | |
1009 | 1046 | else |
1010 | 1047 | ++it; |
1011 | 1048 | } |
1049 | UpdateSupportedTransports (); | |
1012 | 1050 | } |
1013 | 1051 | } |
1014 | 1052 | |
1015 | 1053 | void RouterInfo::EnableMesh () |
1016 | 1054 | { |
1017 | 1055 | if (!IsMesh ()) |
1056 | { | |
1018 | 1057 | m_SupportedTransports |= eNTCP2V6Mesh; |
1058 | m_ReachableTransports |= eNTCP2V6Mesh; | |
1059 | } | |
1019 | 1060 | } |
1020 | 1061 | |
1021 | 1062 | void RouterInfo::DisableMesh () |
1023 | 1064 | if (IsMesh ()) |
1024 | 1065 | { |
1025 | 1066 | m_SupportedTransports &= ~eNTCP2V6Mesh; |
1067 | m_ReachableTransports &= ~eNTCP2V6Mesh; | |
1026 | 1068 | for (auto it = m_Addresses->begin (); it != m_Addresses->end ();) |
1027 | 1069 | { |
1028 | 1070 | auto addr = *it; |
1121 | 1163 | |
1122 | 1164 | bool RouterInfo::IsEligibleFloodfill () const |
1123 | 1165 | { |
1124 | // floodfill must be reachable somehow, >= 0.9.28 and not DSA | |
1125 | return (IsReachable () || (m_SupportedTransports & eSSUV4)) && | |
1126 | m_Version >= NETDB_MIN_FLOODFILL_VERSION && | |
1166 | // floodfill must be reachable by ipv4, >= 0.9.38 and not DSA | |
1167 | return IsReachableBy (eNTCP2V4 | eSSUV4) && m_Version >= NETDB_MIN_FLOODFILL_VERSION && | |
1127 | 1168 | GetIdentity ()->GetSigningKeyType () != SIGNING_KEY_TYPE_DSA_SHA1; |
1128 | 1169 | } |
1129 | 1170 | |
1149 | 1190 | }); |
1150 | 1191 | } |
1151 | 1192 | |
1152 | bool RouterInfo::IsReachableFrom (const RouterInfo& other) const | |
1153 | { | |
1154 | auto commonTransports = m_SupportedTransports & other.m_SupportedTransports; | |
1155 | if (!commonTransports) return false; | |
1156 | if (commonTransports & eNTCP2V6Mesh) return true; | |
1157 | return (bool)GetAddress ( | |
1158 | [commonTransports](std::shared_ptr<const RouterInfo::Address> address)->bool | |
1159 | { | |
1160 | if (address->IsPublishedNTCP2 ()) | |
1161 | { | |
1162 | if ((commonTransports & eNTCP2V4) && address->IsV4 ()) return true; | |
1163 | if ((commonTransports & eNTCP2V6) && address->IsV6 ()) return true; | |
1164 | } | |
1165 | else if (address->IsReachableSSU ()) | |
1166 | { | |
1167 | if ((commonTransports & eSSUV4) && address->IsV4 ()) return true; | |
1168 | if ((commonTransports & eSSUV6) && address->IsV6 ()) return true; | |
1169 | } | |
1170 | return false; | |
1171 | }); | |
1172 | } | |
1173 | ||
1174 | 1193 | void RouterInfo::SetUnreachableAddressesTransportCaps (uint8_t transports) |
1175 | 1194 | { |
1176 | 1195 | for (auto& addr: *m_Addresses) |
1177 | 1196 | { |
1178 | 1197 | // TODO: implement SSU |
1179 | if (addr->transportStyle == eTransportNTCP && (!addr->IsPublishedNTCP2 () || addr->port)) | |
1198 | if (addr->transportStyle == eTransportNTCP && !addr->IsPublishedNTCP2 ()) | |
1180 | 1199 | { |
1181 | 1200 | addr->caps &= ~(eV4 | eV6); |
1182 | 1201 | addr->caps |= transports; |
1183 | 1202 | } |
1184 | 1203 | } |
1185 | 1204 | } |
1205 | ||
1206 | void RouterInfo::UpdateSupportedTransports () | |
1207 | { | |
1208 | m_SupportedTransports = 0; | |
1209 | m_ReachableTransports = 0; | |
1210 | for (const auto& addr: *m_Addresses) | |
1211 | { | |
1212 | uint8_t transports = 0; | |
1213 | if (addr->transportStyle == eTransportNTCP) | |
1214 | { | |
1215 | if (addr->IsV4 ()) transports |= eNTCP2V4; | |
1216 | if (addr->IsV6 ()) | |
1217 | transports |= (i2p::util::net::IsYggdrasilAddress (addr->host) ? eNTCP2V6Mesh : eNTCP2V6); | |
1218 | if (addr->IsPublishedNTCP2 ()) | |
1219 | m_ReachableTransports |= transports; | |
1220 | } | |
1221 | else if (addr->transportStyle == eTransportSSU) | |
1222 | { | |
1223 | if (addr->IsV4 ()) transports |= eSSUV4; | |
1224 | if (addr->IsV6 ()) transports |= eSSUV6; | |
1225 | if (addr->IsReachableSSU ()) | |
1226 | m_ReachableTransports |= transports; | |
1227 | } | |
1228 | m_SupportedTransports |= transports; | |
1229 | } | |
1230 | } | |
1186 | 1231 | } |
1187 | 1232 | } |
58 | 58 | { |
59 | 59 | public: |
60 | 60 | |
61 | enum SupportedTranports | |
61 | enum SupportedTransports | |
62 | 62 | { |
63 | 63 | eNTCP2V4 = 0x01, |
64 | 64 | eNTCP2V6 = 0x02, |
146 | 146 | |
147 | 147 | bool IsNTCP2 () const { return (bool)ntcp2; }; |
148 | 148 | bool IsPublishedNTCP2 () const { return IsNTCP2 () && published; }; |
149 | bool IsReachableSSU () const { return (bool)ssu && (!host.is_unspecified () || !ssu->introducers.empty ()); }; | |
149 | bool IsReachableSSU () const { return (bool)ssu && (published || !ssu->introducers.empty ()); }; | |
150 | 150 | bool UsesIntroducer () const { return (bool)ssu && !ssu->introducers.empty (); }; |
151 | 151 | |
152 | 152 | bool IsIntroducer () const { return caps & eSSUIntroducer; }; |
178 | 178 | std::shared_ptr<const Address> GetYggdrasilAddress () const; |
179 | 179 | |
180 | 180 | void AddSSUAddress (const char * host, int port, const uint8_t * key, int mtu = 0); |
181 | void AddNTCP2Address (const uint8_t * staticKey, const uint8_t * iv, const boost::asio::ip::address& host = boost::asio::ip::address(), int port = 0); | |
181 | void AddNTCP2Address (const uint8_t * staticKey, const uint8_t * iv, | |
182 | const boost::asio::ip::address& host = boost::asio::ip::address(), int port = 0, uint8_t caps = 0); | |
182 | 183 | bool AddIntroducer (const Introducer& introducer); |
183 | 184 | bool RemoveIntroducer (const boost::asio::ip::udp::endpoint& e); |
184 | 185 | void SetProperty (const std::string& key, const std::string& value); // called from RouterContext only |
186 | 187 | std::string GetProperty (const std::string& key) const; // called from RouterContext only |
187 | 188 | void ClearProperties () { m_Properties.clear (); }; |
188 | 189 | void SetUnreachableAddressesTransportCaps (uint8_t transports); // bitmask of AddressCaps |
190 | void UpdateSupportedTransports (); | |
189 | 191 | bool IsFloodfill () const { return m_Caps & Caps::eFloodfill; }; |
190 | 192 | bool IsReachable () const { return m_Caps & Caps::eReachable; }; |
191 | 193 | bool IsSSU (bool v4only = true) const; |
202 | 204 | void EnableMesh (); |
203 | 205 | void DisableMesh (); |
204 | 206 | bool IsCompatible (const RouterInfo& other) const { return m_SupportedTransports & other.m_SupportedTransports; }; |
205 | bool IsReachableFrom (const RouterInfo& other) const; | |
207 | bool IsReachableFrom (const RouterInfo& other) const { return m_ReachableTransports & other.m_SupportedTransports; }; | |
208 | bool IsReachableBy (uint8_t transports) const { return m_ReachableTransports & transports; }; | |
206 | 209 | bool HasValidAddresses () const { return m_SupportedTransports; }; |
207 | 210 | bool IsHidden () const { return m_Caps & eHidden; }; |
208 | 211 | bool IsHighBandwidth () const { return m_Caps & RouterInfo::eHighBandwidth; }; |
268 | 271 | boost::shared_ptr<Addresses> m_Addresses; // TODO: use std::shared_ptr and std::atomic_store for gcc >= 4.9 |
269 | 272 | std::map<std::string, std::string> m_Properties; |
270 | 273 | bool m_IsUpdated, m_IsUnreachable; |
271 | uint8_t m_SupportedTransports, m_Caps; | |
274 | uint8_t m_SupportedTransports, m_ReachableTransports, m_Caps; | |
272 | 275 | int m_Version; |
273 | 276 | mutable std::shared_ptr<RouterProfile> m_Profile; |
274 | 277 | }; |
12 | 12 | #include "NetDb.hpp" |
13 | 13 | #include "SSU.h" |
14 | 14 | #include "util.h" |
15 | ||
16 | #ifdef __linux__ | |
17 | #include <linux/in6.h> | |
18 | #endif | |
15 | 19 | |
16 | 20 | #ifdef _WIN32 |
17 | 21 | #include <boost/winapi/error_codes.hpp> |
61 | 65 | m_SocketV6.set_option (boost::asio::ip::v6_only (true)); |
62 | 66 | m_SocketV6.set_option (boost::asio::socket_base::receive_buffer_size (SSU_SOCKET_RECEIVE_BUFFER_SIZE)); |
63 | 67 | m_SocketV6.set_option (boost::asio::socket_base::send_buffer_size (SSU_SOCKET_SEND_BUFFER_SIZE)); |
68 | #ifdef __linux__ | |
69 | if (m_EndpointV6.address() == boost::asio::ip::address().from_string("::")) // only if not binded to address | |
70 | { | |
71 | // Set preference to use public IPv6 address -- tested on linux, not works on windows, and not tested on others | |
72 | #if (BOOST_VERSION >= 105500) | |
73 | typedef boost::asio::detail::socket_option::integer<BOOST_ASIO_OS_DEF(IPPROTO_IPV6), IPV6_ADDR_PREFERENCES> ipv6PreferAddr; | |
74 | #else | |
75 | typedef boost::asio::detail::socket_option::integer<IPPROTO_IPV6, IPV6_ADDR_PREFERENCES> ipv6PreferAddr; | |
76 | #endif | |
77 | m_SocketV6.set_option (ipv6PreferAddr(IPV6_PREFER_SRC_PUBLIC | IPV6_PREFER_SRC_HOME | IPV6_PREFER_SRC_NONCGA)); | |
78 | } | |
79 | #endif | |
64 | 80 | m_SocketV6.bind (m_EndpointV6); |
65 | 81 | LogPrint (eLogInfo, "SSU: Start listening v6 port ", m_EndpointV6.port()); |
66 | 82 | } |
382 | 382 | { |
383 | 383 | // tell out peer to now assign relay tag |
384 | 384 | flag = SSU_HEADER_EXTENDED_OPTIONS_INCLUDED; |
385 | *payload = 2; payload++; // 1 byte length | |
385 | *payload = 2; payload++; // 1 byte length | |
386 | 386 | uint16_t flags = 0; // clear EXTENDED_OPTIONS_FLAG_REQUEST_RELAY_TAG |
387 | 387 | htobe16buf (payload, flags); |
388 | 388 | payload += 2; |
1072 | 1072 | LogPrint (eLogDebug, "SSU: peer test from Charlie. We are Bob"); |
1073 | 1073 | auto session = m_Server.GetPeerTestSession (nonce); // session with Alice from PeerTest |
1074 | 1074 | if (session && session->m_State == eSessionStateEstablished) |
1075 | session->Send (PAYLOAD_TYPE_PEER_TEST, buf, len); // back to Alice | |
1075 | { | |
1076 | const auto& ep = session->GetRemoteEndpoint (); // Alice's endpoint as known to Bob | |
1077 | session->SendPeerTest (nonce, ep.address (), ep.port (), introKey, false, true); // send back to Alice | |
1078 | } | |
1076 | 1079 | m_Server.RemovePeerTest (nonce); // nonce has been used |
1077 | 1080 | break; |
1078 | 1081 | } |
1092 | 1095 | if (port) |
1093 | 1096 | { |
1094 | 1097 | LogPrint (eLogDebug, "SSU: peer test from Bob. We are Charlie"); |
1095 | m_Server.NewPeerTest (nonce, ePeerTestParticipantCharlie); | |
1096 | 1098 | Send (PAYLOAD_TYPE_PEER_TEST, buf, len); // back to Bob |
1097 | SendPeerTest (nonce, addr, port, introKey); // to Alice with her address received from Bob | |
1099 | if (!addr.is_unspecified () && !i2p::util::net::IsInReservedRange(addr)) | |
1100 | { | |
1101 | m_Server.NewPeerTest (nonce, ePeerTestParticipantCharlie); | |
1102 | SendPeerTest (nonce, addr, port, introKey); // to Alice with her address received from Bob | |
1103 | } | |
1098 | 1104 | } |
1099 | 1105 | else |
1100 | 1106 | { |
103 | 103 | |
104 | 104 | void Stream::Terminate (bool deleteFromDestination) // shoudl be called from StreamingDestination::Stop only |
105 | 105 | { |
106 | m_Status = eStreamStatusTerminated; | |
106 | 107 | m_AckSendTimer.cancel (); |
107 | 108 | m_ReceiveTimer.cancel (); |
108 | 109 | m_ResendTimer.cancel (); |
275 | 276 | const uint8_t * optionData = packet->GetOptionData (); |
276 | 277 | size_t optionSize = packet->GetOptionSize (); |
277 | 278 | if (flags & PACKET_FLAG_DELAY_REQUESTED) |
279 | { | |
280 | if (!m_IsAckSendScheduled) | |
281 | { | |
282 | uint16_t delayRequested = bufbe16toh (optionData); | |
283 | if (delayRequested > 0 && delayRequested < m_RTT) | |
284 | { | |
285 | m_IsAckSendScheduled = true; | |
286 | m_AckSendTimer.expires_from_now (boost::posix_time::milliseconds(delayRequested)); | |
287 | m_AckSendTimer.async_wait (std::bind (&Stream::HandleAckSendTimer, | |
288 | shared_from_this (), std::placeholders::_1)); | |
289 | } | |
290 | } | |
278 | 291 | optionData += 2; |
292 | } | |
279 | 293 | |
280 | 294 | if (flags & PACKET_FLAG_FROM_INCLUDED) |
281 | 295 | { |
792 | 806 | if (m_CurrentRemoteLease && ts < m_CurrentRemoteLease->endDate + i2p::data::LEASE_ENDDATE_THRESHOLD) |
793 | 807 | { |
794 | 808 | std::vector<i2p::tunnel::TunnelMessageBlock> msgs; |
795 | for (auto it: packets) | |
809 | for (const auto& it: packets) | |
796 | 810 | { |
797 | 811 | auto msg = m_RoutingSession->WrapSingleMessage (m_LocalDestination.CreateDataMessage ( |
798 | 812 | it->GetBuffer (), it->GetLength (), m_Port, !m_RoutingSession->IsRatchets ())); |
843 | 857 | |
844 | 858 | void Stream::ScheduleResend () |
845 | 859 | { |
846 | m_ResendTimer.cancel (); | |
847 | // check for invalid value | |
848 | if (m_RTO <= 0) m_RTO = INITIAL_RTO; | |
849 | m_ResendTimer.expires_from_now (boost::posix_time::milliseconds(m_RTO)); | |
850 | m_ResendTimer.async_wait (std::bind (&Stream::HandleResendTimer, | |
851 | shared_from_this (), std::placeholders::_1)); | |
860 | if (m_Status != eStreamStatusTerminated) | |
861 | { | |
862 | m_ResendTimer.cancel (); | |
863 | // check for invalid value | |
864 | if (m_RTO <= 0) m_RTO = INITIAL_RTO; | |
865 | m_ResendTimer.expires_from_now (boost::posix_time::milliseconds(m_RTO)); | |
866 | m_ResendTimer.async_wait (std::bind (&Stream::HandleResendTimer, | |
867 | shared_from_this (), std::placeholders::_1)); | |
868 | } | |
852 | 869 | } |
853 | 870 | |
854 | 871 | void Stream::HandleResendTimer (const boost::system::error_code& ecode) |
1049 | 1066 | it.second->Terminate (false); // we delete here |
1050 | 1067 | m_Streams.clear (); |
1051 | 1068 | m_IncomingStreams.clear (); |
1069 | m_LastStream = nullptr; | |
1052 | 1070 | } |
1053 | 1071 | } |
1054 | 1072 | |
1057 | 1075 | uint32_t sendStreamID = packet->GetSendStreamID (); |
1058 | 1076 | if (sendStreamID) |
1059 | 1077 | { |
1060 | auto it = m_Streams.find (sendStreamID); | |
1061 | if (it != m_Streams.end ()) | |
1062 | it->second->HandleNextPacket (packet); | |
1078 | if (!m_LastStream || sendStreamID != m_LastStream->GetRecvStreamID ()) | |
1079 | { | |
1080 | auto it = m_Streams.find (sendStreamID); | |
1081 | if (it != m_Streams.end ()) | |
1082 | m_LastStream = it->second; | |
1083 | else | |
1084 | m_LastStream = nullptr; | |
1085 | } | |
1086 | if (m_LastStream) | |
1087 | m_LastStream->HandleNextPacket (packet); | |
1063 | 1088 | else if (packet->IsEcho () && m_Owner->IsStreamingAnswerPings ()) |
1064 | 1089 | { |
1065 | 1090 | // ping |
1165 | 1190 | { |
1166 | 1191 | auto s = std::make_shared<Stream> (m_Owner->GetService (), *this, remote, port); |
1167 | 1192 | std::unique_lock<std::mutex> l(m_StreamsMutex); |
1168 | m_Streams[s->GetRecvStreamID ()] = s; | |
1193 | m_Streams.emplace (s->GetRecvStreamID (), s); | |
1169 | 1194 | return s; |
1170 | 1195 | } |
1171 | 1196 | |
1173 | 1198 | { |
1174 | 1199 | auto s = std::make_shared<Stream> (m_Owner->GetService (), *this); |
1175 | 1200 | std::unique_lock<std::mutex> l(m_StreamsMutex); |
1176 | m_Streams[s->GetRecvStreamID ()] = s; | |
1177 | m_IncomingStreams[receiveStreamID] = s; | |
1201 | m_Streams.emplace (s->GetRecvStreamID (), s); | |
1202 | m_IncomingStreams.emplace (receiveStreamID, s); | |
1178 | 1203 | return s; |
1179 | 1204 | } |
1180 | 1205 | |
1185 | 1210 | std::unique_lock<std::mutex> l(m_StreamsMutex); |
1186 | 1211 | m_Streams.erase (stream->GetRecvStreamID ()); |
1187 | 1212 | m_IncomingStreams.erase (stream->GetSendStreamID ()); |
1213 | if (m_LastStream == stream) m_LastStream = nullptr; | |
1188 | 1214 | } |
1189 | 1215 | } |
1190 | 1216 |
0 | 0 | /* |
1 | * Copyright (c) 2013-2020, The PurpleI2P Project | |
1 | * Copyright (c) 2013-2021, The PurpleI2P Project | |
2 | 2 | * |
3 | 3 | * This file is part of Purple i2pd project and licensed under BSD3 |
4 | 4 | * |
10 | 10 | |
11 | 11 | #include <inttypes.h> |
12 | 12 | #include <string> |
13 | #include <map> | |
13 | #include <unordered_map> | |
14 | 14 | #include <set> |
15 | 15 | #include <queue> |
16 | 16 | #include <functional> |
151 | 151 | eStreamStatusOpen, |
152 | 152 | eStreamStatusReset, |
153 | 153 | eStreamStatusClosing, |
154 | eStreamStatusClosed | |
154 | eStreamStatusClosed, | |
155 | eStreamStatusTerminated | |
155 | 156 | }; |
156 | 157 | |
157 | 158 | class StreamingDestination; |
296 | 297 | uint16_t m_LocalPort; |
297 | 298 | bool m_Gzip; // gzip compression of data messages |
298 | 299 | std::mutex m_StreamsMutex; |
299 | std::map<uint32_t, std::shared_ptr<Stream> > m_Streams; // sendStreamID->stream | |
300 | std::map<uint32_t, std::shared_ptr<Stream> > m_IncomingStreams; // receiveStreamID->stream | |
300 | std::unordered_map<uint32_t, std::shared_ptr<Stream> > m_Streams; // sendStreamID->stream | |
301 | std::unordered_map<uint32_t, std::shared_ptr<Stream> > m_IncomingStreams; // receiveStreamID->stream | |
302 | std::shared_ptr<Stream> m_LastStream; | |
301 | 303 | Acceptor m_Acceptor; |
302 | 304 | std::list<std::shared_ptr<Stream> > m_PendingIncomingStreams; |
303 | 305 | boost::asio::deadline_timer m_PendingIncomingTimer; |
304 | std::map<uint32_t, std::list<Packet *> > m_SavedPackets; // receiveStreamID->packets, arrived before SYN | |
306 | std::unordered_map<uint32_t, std::list<Packet *> > m_SavedPackets; // receiveStreamID->packets, arrived before SYN | |
305 | 307 | |
306 | 308 | i2p::util::MemoryPool<Packet> m_PacketsPool; |
307 | 309 | i2p::util::MemoryPool<I2NPMessageBuffer<I2NP_MAX_MESSAGE_SIZE> > m_I2NPMsgsPool; |
0 | 0 | /* |
1 | * Copyright (c) 2013-2020, The PurpleI2P Project | |
1 | * Copyright (c) 2013-2021, The PurpleI2P Project | |
2 | 2 | * |
3 | 3 | * This file is part of Purple i2pd project and licensed under BSD3 |
4 | 4 | * |
15 | 15 | #include <boost/algorithm/string.hpp> |
16 | 16 | #include "Config.h" |
17 | 17 | #include "Log.h" |
18 | #include "RouterContext.h" | |
18 | 19 | #include "I2PEndian.h" |
19 | 20 | #include "Timestamp.h" |
20 | 21 | #include "util.h" |
59 | 60 | { |
60 | 61 | LogPrint (eLogInfo, "Timestamp: NTP request to ", address); |
61 | 62 | boost::asio::io_service service; |
62 | boost::asio::ip::udp::resolver::query query (boost::asio::ip::udp::v4 (), address, "ntp"); | |
63 | 63 | boost::system::error_code ec; |
64 | auto it = boost::asio::ip::udp::resolver (service).resolve (query, ec); | |
65 | if (!ec && it != boost::asio::ip::udp::resolver::iterator()) | |
66 | { | |
67 | auto ep = (*it).endpoint (); // take first one | |
64 | auto it = boost::asio::ip::udp::resolver (service).resolve ( | |
65 | boost::asio::ip::udp::resolver::query (address, "ntp"), ec); | |
66 | if (!ec) | |
67 | { | |
68 | bool found = false; | |
69 | boost::asio::ip::udp::resolver::iterator end; | |
70 | boost::asio::ip::udp::endpoint ep; | |
71 | while (it != end) | |
72 | { | |
73 | ep = *it; | |
74 | if (!ep.address ().is_unspecified ()) | |
75 | { | |
76 | if (ep.address ().is_v4 ()) | |
77 | { | |
78 | if (i2p::context.SupportsV4 ()) found = true; | |
79 | } | |
80 | else if (ep.address ().is_v6 ()) | |
81 | { | |
82 | if (i2p::util::net::IsYggdrasilAddress (ep.address ())) | |
83 | { | |
84 | if (i2p::context.SupportsMesh ()) found = true; | |
85 | } | |
86 | else if (i2p::context.SupportsV6 ()) found = true; | |
87 | } | |
88 | } | |
89 | if (found) break; | |
90 | it++; | |
91 | } | |
92 | if (!found) | |
93 | { | |
94 | LogPrint (eLogError, "Timestamp: can't find compatible address for ", address); | |
95 | return; | |
96 | } | |
97 | ||
68 | 98 | boost::asio::ip::udp::socket socket (service); |
69 | socket.open (boost::asio::ip::udp::v4 (), ec); | |
99 | socket.open (ep.protocol (), ec); | |
70 | 100 | if (!ec) |
71 | 101 | { |
72 | 102 | uint8_t buf[48];// 48 bytes NTP request/response |
102 | 132 | LogPrint (eLogError, "Timestamp: Couldn't open UDP socket"); |
103 | 133 | } |
104 | 134 | else |
105 | LogPrint (eLogError, "Timestamp: Couldn't resove address ", address); | |
135 | LogPrint (eLogError, "Timestamp: Couldn't resolve address ", address); | |
106 | 136 | } |
107 | 137 | |
108 | 138 | NTPTimeSync::NTPTimeSync (): m_IsRunning (false), m_Timer (m_Service) |
38 | 38 | |
39 | 39 | void TransitTunnelParticipant::HandleTunnelDataMsg (std::shared_ptr<const i2p::I2NPMessage> tunnelMsg) |
40 | 40 | { |
41 | auto newMsg = CreateEmptyTunnelDataMsg (); | |
41 | auto newMsg = CreateEmptyTunnelDataMsg (false); | |
42 | 42 | EncryptTunnelMsg (tunnelMsg, newMsg); |
43 | 43 | |
44 | 44 | m_NumTransmittedBytes += tunnelMsg->GetLength (); |
86 | 86 | |
87 | 87 | void TransitTunnelEndpoint::HandleTunnelDataMsg (std::shared_ptr<const i2p::I2NPMessage> tunnelMsg) |
88 | 88 | { |
89 | auto newMsg = CreateEmptyTunnelDataMsg (); | |
89 | auto newMsg = CreateEmptyTunnelDataMsg (true); | |
90 | 90 | EncryptTunnelMsg (tunnelMsg, newMsg); |
91 | 91 | |
92 | 92 | LogPrint (eLogDebug, "TransitTunnel: handle msg for endpoint ", GetTunnelID ()); |
400 | 400 | try |
401 | 401 | { |
402 | 402 | auto r = netdb.FindRouter (ident); |
403 | if (!r || r->IsUnreachable () || !r->IsCompatible (i2p::context.GetRouterInfo ())) return; | |
403 | if (r && (r->IsUnreachable () || !r->IsReachableFrom (i2p::context.GetRouterInfo ()))) return; // router found but non-reachable | |
404 | 404 | { |
405 | 405 | std::unique_lock<std::mutex> l(m_PeersMutex); |
406 | 406 | it = m_Peers.insert (std::pair<i2p::data::IdentHash, Peer>(ident, { 0, r, {}, |
446 | 446 | std::shared_ptr<const RouterInfo::Address> address; |
447 | 447 | if (!peer.numAttempts) // NTCP2 ipv6 |
448 | 448 | { |
449 | if (context.GetRouterInfo ().IsNTCP2V6 () && peer.router->IsNTCP2V6 ()) | |
449 | if (context.GetRouterInfo ().IsNTCP2V6 () && peer.router->IsReachableBy (RouterInfo::eNTCP2V6)) | |
450 | 450 | { |
451 | 451 | address = peer.router->GetPublishedNTCP2V6Address (); |
452 | 452 | if (address && m_CheckReserved && i2p::util::net::IsInReservedRange(address->host)) |
456 | 456 | } |
457 | 457 | if (!address && peer.numAttempts == 1) // NTCP2 ipv4 |
458 | 458 | { |
459 | if (context.GetRouterInfo ().IsNTCP2 (true) && peer.router->IsNTCP2 (true) && !peer.router->IsUnreachable ()) | |
459 | if (context.GetRouterInfo ().IsNTCP2 (true) && peer.router->IsReachableBy (RouterInfo::eNTCP2V4)) | |
460 | 460 | { |
461 | 461 | address = peer.router->GetPublishedNTCP2V4Address (); |
462 | 462 | if (address && m_CheckReserved && i2p::util::net::IsInReservedRange(address->host)) |
484 | 484 | std::shared_ptr<const RouterInfo::Address> address; |
485 | 485 | if (peer.numAttempts == 2) // SSU ipv6 |
486 | 486 | { |
487 | if (context.GetRouterInfo ().IsSSUV6 () && peer.router->IsSSUV6 ()) | |
487 | if (context.GetRouterInfo ().IsSSUV6 () && peer.router->IsReachableBy (RouterInfo::eSSUV6)) | |
488 | 488 | { |
489 | 489 | address = peer.router->GetSSUV6Address (); |
490 | 490 | if (address && m_CheckReserved && i2p::util::net::IsInReservedRange(address->host)) |
494 | 494 | } |
495 | 495 | if (!address && peer.numAttempts == 3) // SSU ipv4 |
496 | 496 | { |
497 | if (context.GetRouterInfo ().IsSSU (true) && peer.router->IsSSU (true)) | |
497 | if (context.GetRouterInfo ().IsSSU (true) && peer.router->IsReachableBy (RouterInfo::eSSUV4)) | |
498 | 498 | { |
499 | 499 | address = peer.router->GetSSUAddress (true); |
500 | 500 | if (address && m_CheckReserved && i2p::util::net::IsInReservedRange(address->host)) |
22 | 22 | #include "Tunnel.h" |
23 | 23 | #include "TunnelPool.h" |
24 | 24 | #include "util.h" |
25 | #include "ECIESX25519AEADRatchetSession.h" | |
25 | 26 | |
26 | 27 | namespace i2p |
27 | 28 | { |
41 | 42 | void Tunnel::Build (uint32_t replyMsgID, std::shared_ptr<OutboundTunnel> outboundTunnel) |
42 | 43 | { |
43 | 44 | auto numHops = m_Config->GetNumHops (); |
44 | int numRecords = numHops <= STANDARD_NUM_RECORDS ? STANDARD_NUM_RECORDS : MAX_NUM_RECORDS; | |
45 | const int numRecords = numHops <= STANDARD_NUM_RECORDS ? STANDARD_NUM_RECORDS : MAX_NUM_RECORDS; | |
45 | 46 | auto msg = numRecords <= STANDARD_NUM_RECORDS ? NewI2NPShortMessage () : NewI2NPMessage (); |
46 | 47 | *msg->GetPayload () = numRecords; |
47 | msg->len += numRecords*TUNNEL_BUILD_RECORD_SIZE + 1; | |
48 | const size_t recordSize = m_Config->IsShort () ? SHORT_TUNNEL_BUILD_RECORD_SIZE : TUNNEL_BUILD_RECORD_SIZE; | |
49 | msg->len += numRecords*recordSize + 1; | |
48 | 50 | // shuffle records |
49 | 51 | std::vector<int> recordIndicies; |
50 | 52 | for (int i = 0; i < numRecords; i++) recordIndicies.push_back(i); |
54 | 56 | uint8_t * records = msg->GetPayload () + 1; |
55 | 57 | TunnelHopConfig * hop = m_Config->GetFirstHop (); |
56 | 58 | int i = 0; |
57 | BN_CTX * ctx = BN_CTX_new (); | |
58 | 59 | while (hop) |
59 | 60 | { |
60 | 61 | uint32_t msgID; |
62 | 63 | RAND_bytes ((uint8_t *)&msgID, 4); |
63 | 64 | else |
64 | 65 | msgID = replyMsgID; |
65 | int idx = recordIndicies[i]; | |
66 | hop->CreateBuildRequestRecord (records + idx*TUNNEL_BUILD_RECORD_SIZE, msgID, ctx); | |
67 | hop->recordIndex = idx; | |
68 | i++; | |
66 | hop->recordIndex = recordIndicies[i]; i++; | |
67 | hop->CreateBuildRequestRecord (records, msgID); | |
69 | 68 | hop = hop->next; |
70 | 69 | } |
71 | BN_CTX_free (ctx); | |
72 | 70 | // fill up fake records with random data |
73 | 71 | for (int i = numHops; i < numRecords; i++) |
74 | 72 | { |
75 | 73 | int idx = recordIndicies[i]; |
76 | RAND_bytes (records + idx*TUNNEL_BUILD_RECORD_SIZE, TUNNEL_BUILD_RECORD_SIZE); | |
74 | RAND_bytes (records + idx*recordSize, recordSize); | |
77 | 75 | } |
78 | 76 | |
79 | 77 | // decrypt real records |
80 | i2p::crypto::CBCDecryption decryption; | |
81 | 78 | hop = m_Config->GetLastHop ()->prev; |
82 | 79 | while (hop) |
83 | 80 | { |
84 | decryption.SetKey (hop->replyKey); | |
85 | 81 | // decrypt records after current hop |
86 | 82 | TunnelHopConfig * hop1 = hop->next; |
87 | 83 | while (hop1) |
88 | 84 | { |
89 | decryption.SetIV (hop->replyIV); | |
90 | uint8_t * record = records + hop1->recordIndex*TUNNEL_BUILD_RECORD_SIZE; | |
91 | decryption.Decrypt(record, TUNNEL_BUILD_RECORD_SIZE, record); | |
85 | hop->DecryptRecord (records, hop1->recordIndex); | |
92 | 86 | hop1 = hop1->next; |
93 | 87 | } |
94 | 88 | hop = hop->prev; |
95 | 89 | } |
96 | msg->FillI2NPMessageHeader (eI2NPVariableTunnelBuild); | |
90 | msg->FillI2NPMessageHeader (m_Config->IsShort () ? eI2NPShortTunnelBuild : eI2NPVariableTunnelBuild); | |
97 | 91 | |
98 | 92 | // send message |
99 | 93 | if (outboundTunnel) |
94 | { | |
95 | if (m_Config->IsShort ()) | |
96 | { | |
97 | auto ident = m_Config->GetFirstHop () ? m_Config->GetFirstHop ()->ident : nullptr; | |
98 | if (ident && ident->GetIdentHash () != outboundTunnel->GetNextIdentHash ()) // don't encrypt if IBGW = OBEP | |
99 | { | |
100 | auto msg1 = i2p::garlic::WrapECIESX25519MessageForRouter (msg, ident->GetEncryptionPublicKey ()); | |
101 | if (msg1) msg = msg1; | |
102 | } | |
103 | } | |
100 | 104 | outboundTunnel->SendTunnelDataMsg (GetNextIdentHash (), 0, msg); |
105 | } | |
101 | 106 | else |
107 | { | |
108 | if (m_Config->IsShort () && m_Config->GetLastHop () && | |
109 | m_Config->GetLastHop ()->ident->GetIdentHash () != m_Config->GetLastHop ()->nextIdent) | |
110 | { | |
111 | // add garlic key/tag for reply | |
112 | uint8_t key[32]; | |
113 | uint64_t tag = m_Config->GetLastHop ()->GetGarlicKey (key); | |
114 | if (m_Pool && m_Pool->GetLocalDestination ()) | |
115 | m_Pool->GetLocalDestination ()->AddECIESx25519Key (key, tag); | |
116 | else | |
117 | i2p::context.AddECIESx25519Key (key, tag); | |
118 | } | |
102 | 119 | i2p::transport::transports.SendMessage (GetNextIdentHash (), msg); |
120 | } | |
103 | 121 | } |
104 | 122 | |
105 | 123 | bool Tunnel::HandleTunnelBuildResponse (uint8_t * msg, size_t len) |
106 | 124 | { |
107 | 125 | LogPrint (eLogDebug, "Tunnel: TunnelBuildResponse ", (int)msg[0], " records."); |
108 | 126 | |
109 | i2p::crypto::CBCDecryption decryption; | |
110 | 127 | TunnelHopConfig * hop = m_Config->GetLastHop (); |
111 | 128 | while (hop) |
112 | 129 | { |
113 | decryption.SetKey (hop->replyKey); | |
114 | // decrypt records before and current hop | |
115 | TunnelHopConfig * hop1 = hop; | |
130 | // decrypt current hop | |
131 | if (hop->recordIndex >= 0 && hop->recordIndex < msg[0]) | |
132 | { | |
133 | if (!hop->DecryptBuildResponseRecord (msg + 1)) | |
134 | return false; | |
135 | } | |
136 | else | |
137 | { | |
138 | LogPrint (eLogWarning, "Tunnel: hop index ", hop->recordIndex, " is out of range"); | |
139 | return false; | |
140 | } | |
141 | ||
142 | // decrypt records before current hop | |
143 | TunnelHopConfig * hop1 = hop->prev; | |
116 | 144 | while (hop1) |
117 | 145 | { |
118 | 146 | auto idx = hop1->recordIndex; |
119 | 147 | if (idx >= 0 && idx < msg[0]) |
120 | { | |
121 | uint8_t * record = msg + 1 + idx*TUNNEL_BUILD_RECORD_SIZE; | |
122 | if (hop1 == hop && hop1->IsECIES ()) | |
123 | { | |
124 | uint8_t nonce[12]; | |
125 | memset (nonce, 0, 12); | |
126 | if (!i2p::crypto::AEADChaCha20Poly1305 (record, TUNNEL_BUILD_RECORD_SIZE - 16, | |
127 | hop->m_H, 32, hop->m_CK, nonce, record, TUNNEL_BUILD_RECORD_SIZE - 16, false)) // decrypt | |
128 | { | |
129 | LogPrint (eLogWarning, "Tunnel: Response AEAD decryption failed"); | |
130 | return false; | |
131 | } | |
132 | } | |
133 | else | |
134 | { | |
135 | decryption.SetIV (hop->replyIV); | |
136 | decryption.Decrypt(record, TUNNEL_BUILD_RECORD_SIZE, record); | |
137 | } | |
138 | } | |
148 | hop->DecryptRecord (msg + 1, idx); | |
139 | 149 | else |
140 | 150 | LogPrint (eLogWarning, "Tunnel: hop index ", idx, " is out of range"); |
141 | 151 | hop1 = hop1->prev; |
147 | 157 | hop = m_Config->GetFirstHop (); |
148 | 158 | while (hop) |
149 | 159 | { |
150 | const uint8_t * record = msg + 1 + hop->recordIndex*TUNNEL_BUILD_RECORD_SIZE; | |
151 | uint8_t ret = record[hop->IsECIES () ? ECIES_BUILD_RESPONSE_RECORD_RET_OFFSET : BUILD_RESPONSE_RECORD_RET_OFFSET]; | |
160 | uint8_t ret = hop->GetRetCode (msg + 1); | |
152 | 161 | LogPrint (eLogDebug, "Tunnel: Build response ret code=", (int)ret); |
153 | 162 | auto profile = i2p::data::netdb.FindRouterProfile (hop->ident->GetIdentHash ()); |
154 | 163 | if (profile) |
233 | 242 | void InboundTunnel::HandleTunnelDataMsg (std::shared_ptr<const I2NPMessage> msg) |
234 | 243 | { |
235 | 244 | if (IsFailed ()) SetState (eTunnelStateEstablished); // incoming messages means a tunnel is alive |
236 | auto newMsg = CreateEmptyTunnelDataMsg (); | |
245 | auto newMsg = CreateEmptyTunnelDataMsg (true); | |
237 | 246 | EncryptTunnelMsg (msg, newMsg); |
238 | 247 | newMsg->from = shared_from_this (); |
239 | 248 | m_Endpoint.HandleDecryptedTunnelDataMsg (newMsg); |
318 | 327 | for (auto& msg : msgs) |
319 | 328 | { |
320 | 329 | if (!msg.data) continue; |
330 | m_NumSentBytes += msg.data->GetLength (); | |
321 | 331 | switch (msg.deliveryType) |
322 | 332 | { |
323 | 333 | case eDeliveryTypeLocal: |
324 | i2p::HandleI2NPMessage (msg.data); | |
334 | HandleI2NPMessage (msg.data); | |
325 | 335 | break; |
326 | 336 | case eDeliveryTypeTunnel: |
327 | 337 | i2p::transport::transports.SendMessage (msg.hash, i2p::CreateTunnelGatewayMsg (msg.tunnelID, msg.data)); |
516 | 526 | } |
517 | 527 | case eI2NPVariableTunnelBuild: |
518 | 528 | case eI2NPVariableTunnelBuildReply: |
529 | case eI2NPShortTunnelBuild: | |
530 | case eI2NPShortTunnelBuildReply: | |
519 | 531 | case eI2NPTunnelBuild: |
520 | case eI2NPTunnelBuildReply: | |
532 | case eI2NPTunnelBuildReply: | |
521 | 533 | HandleI2NPMessage (msg->GetBuffer (), msg->GetLength ()); |
522 | 534 | break; |
523 | 535 | default: |
702 | 714 | LogPrint (eLogDebug, "Tunnel: creating one hop outbound tunnel"); |
703 | 715 | CreateTunnel<OutboundTunnel> ( |
704 | 716 | std::make_shared<TunnelConfig> (std::vector<std::shared_ptr<const i2p::data::IdentityEx> > { router->GetRouterIdentity () }, |
705 | inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash ()) | |
717 | inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash ()), nullptr | |
706 | 718 | ); |
707 | 719 | } |
708 | 720 | } |
751 | 763 | if (m_InboundTunnels.empty ()) |
752 | 764 | { |
753 | 765 | LogPrint (eLogDebug, "Tunnel: Creating zero hops inbound tunnel"); |
754 | CreateZeroHopsInboundTunnel (); | |
755 | CreateZeroHopsOutboundTunnel (); | |
766 | CreateZeroHopsInboundTunnel (nullptr); | |
767 | CreateZeroHopsOutboundTunnel (nullptr); | |
756 | 768 | if (!m_ExploratoryPool) |
757 | 769 | { |
758 | 770 | int ibLen; i2p::config::GetOption("exploratory.inbound.length", ibLen); |
778 | 790 | } |
779 | 791 | LogPrint (eLogDebug, "Tunnel: creating one hop inbound tunnel"); |
780 | 792 | CreateTunnel<InboundTunnel> ( |
781 | std::make_shared<TunnelConfig> (std::vector<std::shared_ptr<const i2p::data::IdentityEx> > { router->GetRouterIdentity () }) | |
793 | std::make_shared<TunnelConfig> (std::vector<std::shared_ptr<const i2p::data::IdentityEx> > { router->GetRouterIdentity () }), nullptr | |
782 | 794 | ); |
783 | 795 | } |
784 | 796 | } |
824 | 836 | } |
825 | 837 | |
826 | 838 | template<class TTunnel> |
827 | std::shared_ptr<TTunnel> Tunnels::CreateTunnel (std::shared_ptr<TunnelConfig> config, std::shared_ptr<OutboundTunnel> outboundTunnel) | |
839 | std::shared_ptr<TTunnel> Tunnels::CreateTunnel (std::shared_ptr<TunnelConfig> config, | |
840 | std::shared_ptr<TunnelPool> pool, std::shared_ptr<OutboundTunnel> outboundTunnel) | |
828 | 841 | { |
829 | 842 | auto newTunnel = std::make_shared<TTunnel> (config); |
843 | newTunnel->SetTunnelPool (pool); | |
830 | 844 | uint32_t replyMsgID; |
831 | 845 | RAND_bytes ((uint8_t *)&replyMsgID, 4); |
832 | 846 | AddPendingTunnel (replyMsgID, newTunnel); |
834 | 848 | return newTunnel; |
835 | 849 | } |
836 | 850 | |
837 | std::shared_ptr<InboundTunnel> Tunnels::CreateInboundTunnel (std::shared_ptr<TunnelConfig> config, std::shared_ptr<OutboundTunnel> outboundTunnel) | |
851 | std::shared_ptr<InboundTunnel> Tunnels::CreateInboundTunnel (std::shared_ptr<TunnelConfig> config, | |
852 | std::shared_ptr<TunnelPool> pool, std::shared_ptr<OutboundTunnel> outboundTunnel) | |
838 | 853 | { |
839 | 854 | if (config) |
840 | return CreateTunnel<InboundTunnel>(config, outboundTunnel); | |
855 | return CreateTunnel<InboundTunnel>(config, pool, outboundTunnel); | |
841 | 856 | else |
842 | return CreateZeroHopsInboundTunnel (); | |
843 | } | |
844 | ||
845 | std::shared_ptr<OutboundTunnel> Tunnels::CreateOutboundTunnel (std::shared_ptr<TunnelConfig> config) | |
857 | return CreateZeroHopsInboundTunnel (pool); | |
858 | } | |
859 | ||
860 | std::shared_ptr<OutboundTunnel> Tunnels::CreateOutboundTunnel (std::shared_ptr<TunnelConfig> config, std::shared_ptr<TunnelPool> pool) | |
846 | 861 | { |
847 | 862 | if (config) |
848 | return CreateTunnel<OutboundTunnel>(config); | |
863 | return CreateTunnel<OutboundTunnel>(config, pool); | |
849 | 864 | else |
850 | return CreateZeroHopsOutboundTunnel (); | |
865 | return CreateZeroHopsOutboundTunnel (pool); | |
851 | 866 | } |
852 | 867 | |
853 | 868 | void Tunnels::AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr<InboundTunnel> tunnel) |
881 | 896 | { |
882 | 897 | // build symmetric outbound tunnel |
883 | 898 | CreateTunnel<OutboundTunnel> (std::make_shared<TunnelConfig>(newTunnel->GetInvertedPeers (), |
884 | newTunnel->GetNextTunnelID (), newTunnel->GetNextIdentHash ()), | |
899 | newTunnel->GetNextTunnelID (), newTunnel->GetNextIdentHash ()), nullptr, | |
885 | 900 | GetNextOutboundTunnel ()); |
886 | 901 | } |
887 | 902 | else |
897 | 912 | } |
898 | 913 | |
899 | 914 | |
900 | std::shared_ptr<ZeroHopsInboundTunnel> Tunnels::CreateZeroHopsInboundTunnel () | |
915 | std::shared_ptr<ZeroHopsInboundTunnel> Tunnels::CreateZeroHopsInboundTunnel (std::shared_ptr<TunnelPool> pool) | |
901 | 916 | { |
902 | 917 | auto inboundTunnel = std::make_shared<ZeroHopsInboundTunnel> (); |
918 | inboundTunnel->SetTunnelPool (pool); | |
903 | 919 | inboundTunnel->SetState (eTunnelStateEstablished); |
904 | 920 | m_InboundTunnels.push_back (inboundTunnel); |
905 | 921 | m_Tunnels[inboundTunnel->GetTunnelID ()] = inboundTunnel; |
906 | 922 | return inboundTunnel; |
907 | 923 | } |
908 | 924 | |
909 | std::shared_ptr<ZeroHopsOutboundTunnel> Tunnels::CreateZeroHopsOutboundTunnel () | |
925 | std::shared_ptr<ZeroHopsOutboundTunnel> Tunnels::CreateZeroHopsOutboundTunnel (std::shared_ptr<TunnelPool> pool) | |
910 | 926 | { |
911 | 927 | auto outboundTunnel = std::make_shared<ZeroHopsOutboundTunnel> (); |
928 | outboundTunnel->SetTunnelPool (pool); | |
912 | 929 | outboundTunnel->SetState (eTunnelStateEstablished); |
913 | 930 | m_OutboundTunnels.push_back (outboundTunnel); |
914 | 931 | // we don't insert into m_Tunnels |
37 | 37 | const int TUNNEL_CREATION_TIMEOUT = 30; // 30 seconds |
38 | 38 | const int STANDARD_NUM_RECORDS = 4; // in VariableTunnelBuild message |
39 | 39 | const int MAX_NUM_RECORDS = 8; |
40 | const int HIGH_LATENCY_PER_HOP = 250; // in milliseconds | |
40 | 41 | |
41 | 42 | enum TunnelState |
42 | 43 | { |
97 | 98 | bool LatencyFitsRange(uint64_t lowerbound, uint64_t upperbound) const; |
98 | 99 | |
99 | 100 | bool LatencyIsKnown() const { return m_Latency > 0; } |
101 | bool IsSlow () const { return LatencyIsKnown() && (int)m_Latency > HIGH_LATENCY_PER_HOP*GetNumHops (); } | |
102 | ||
100 | 103 | protected: |
101 | 104 | |
102 | 105 | void PrintHops (std::stringstream& s) const; |
201 | 204 | void AddTransitTunnel (std::shared_ptr<TransitTunnel> tunnel); |
202 | 205 | void AddOutboundTunnel (std::shared_ptr<OutboundTunnel> newTunnel); |
203 | 206 | void AddInboundTunnel (std::shared_ptr<InboundTunnel> newTunnel); |
204 | std::shared_ptr<InboundTunnel> CreateInboundTunnel (std::shared_ptr<TunnelConfig> config, std::shared_ptr<OutboundTunnel> outboundTunnel); | |
205 | std::shared_ptr<OutboundTunnel> CreateOutboundTunnel (std::shared_ptr<TunnelConfig> config); | |
207 | std::shared_ptr<InboundTunnel> CreateInboundTunnel (std::shared_ptr<TunnelConfig> config, std::shared_ptr<TunnelPool> pool, std::shared_ptr<OutboundTunnel> outboundTunnel); | |
208 | std::shared_ptr<OutboundTunnel> CreateOutboundTunnel (std::shared_ptr<TunnelConfig> config, std::shared_ptr<TunnelPool> pool); | |
206 | 209 | void PostTunnelData (std::shared_ptr<I2NPMessage> msg); |
207 | 210 | void PostTunnelData (const std::vector<std::shared_ptr<I2NPMessage> >& msgs); |
208 | 211 | void AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr<InboundTunnel> tunnel); |
215 | 218 | private: |
216 | 219 | |
217 | 220 | template<class TTunnel> |
218 | std::shared_ptr<TTunnel> CreateTunnel (std::shared_ptr<TunnelConfig> config, std::shared_ptr<OutboundTunnel> outboundTunnel = nullptr); | |
221 | std::shared_ptr<TTunnel> CreateTunnel (std::shared_ptr<TunnelConfig> config, | |
222 | std::shared_ptr<TunnelPool> pool, std::shared_ptr<OutboundTunnel> outboundTunnel = nullptr); | |
219 | 223 | |
220 | 224 | template<class TTunnel> |
221 | 225 | std::shared_ptr<TTunnel> GetPendingTunnel (uint32_t replyMsgID, const std::map<uint32_t, std::shared_ptr<TTunnel> >& pendingTunnels); |
232 | 236 | void ManagePendingTunnels (PendingTunnels& pendingTunnels); |
233 | 237 | void ManageTunnelPools (uint64_t ts); |
234 | 238 | |
235 | std::shared_ptr<ZeroHopsInboundTunnel> CreateZeroHopsInboundTunnel (); | |
236 | std::shared_ptr<ZeroHopsOutboundTunnel> CreateZeroHopsOutboundTunnel (); | |
239 | std::shared_ptr<ZeroHopsInboundTunnel> CreateZeroHopsInboundTunnel (std::shared_ptr<TunnelPool> pool); | |
240 | std::shared_ptr<ZeroHopsOutboundTunnel> CreateZeroHopsOutboundTunnel (std::shared_ptr<TunnelPool> pool); | |
237 | 241 | |
238 | 242 | private: |
239 | 243 |
0 | 0 | /* |
1 | * Copyright (c) 2013-2020, The PurpleI2P Project | |
1 | * Copyright (c) 2013-2021, The PurpleI2P Project | |
2 | 2 | * |
3 | 3 | * This file is part of Purple i2pd project and licensed under BSD3 |
4 | 4 | * |
22 | 22 | { |
23 | 23 | TunnelHopConfig::TunnelHopConfig (std::shared_ptr<const i2p::data::IdentityEx> r) |
24 | 24 | { |
25 | RAND_bytes (layerKey, 32); | |
26 | RAND_bytes (ivKey, 32); | |
27 | RAND_bytes (replyKey, 32); | |
28 | RAND_bytes (replyIV, 16); | |
29 | 25 | RAND_bytes ((uint8_t *)&tunnelID, 4); |
30 | 26 | if (!tunnelID) tunnelID = 1; // tunnelID can't be zero |
31 | 27 | isGateway = true; |
76 | 72 | isGateway = false; |
77 | 73 | } |
78 | 74 | } |
75 | ||
76 | void TunnelHopConfig::DecryptRecord (uint8_t * records, int index) const | |
77 | { | |
78 | uint8_t * record = records + index*TUNNEL_BUILD_RECORD_SIZE; | |
79 | i2p::crypto::CBCDecryption decryption; | |
80 | decryption.SetKey (replyKey); | |
81 | decryption.SetIV (replyIV); | |
82 | decryption.Decrypt(record, TUNNEL_BUILD_RECORD_SIZE, record); | |
83 | } | |
79 | 84 | |
80 | void TunnelHopConfig::CreateBuildRequestRecord (uint8_t * record, uint32_t replyMsgID, BN_CTX * ctx) | |
81 | { | |
85 | void ElGamalTunnelHopConfig::CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID) | |
86 | { | |
87 | // generate keys | |
88 | RAND_bytes (layerKey, 32); | |
89 | RAND_bytes (ivKey, 32); | |
90 | RAND_bytes (replyKey, 32); | |
91 | RAND_bytes (replyIV, 16); | |
92 | // fill clear text | |
82 | 93 | uint8_t flag = 0; |
83 | if (isGateway) flag |= 0x80; | |
84 | if (isEndpoint) flag |= 0x40; | |
94 | if (isGateway) flag |= TUNNEL_BUILD_RECORD_GATEWAY_FLAG; | |
95 | if (isEndpoint) flag |= TUNNEL_BUILD_RECORD_ENDPOINT_FLAG; | |
96 | uint8_t clearText[BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; | |
97 | htobe32buf (clearText + BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET, tunnelID); | |
98 | memcpy (clearText + BUILD_REQUEST_RECORD_OUR_IDENT_OFFSET, ident->GetIdentHash (), 32); | |
99 | htobe32buf (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET, nextTunnelID); | |
100 | memcpy (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, nextIdent, 32); | |
101 | memcpy (clearText + BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET, layerKey, 32); | |
102 | memcpy (clearText + BUILD_REQUEST_RECORD_IV_KEY_OFFSET, ivKey, 32); | |
103 | memcpy (clearText + BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET, replyKey, 32); | |
104 | memcpy (clearText + BUILD_REQUEST_RECORD_REPLY_IV_OFFSET, replyIV, 16); | |
105 | clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] = flag; | |
106 | htobe32buf (clearText + BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET, i2p::util::GetHoursSinceEpoch ()); | |
107 | htobe32buf (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET, replyMsgID); | |
108 | RAND_bytes (clearText + BUILD_REQUEST_RECORD_PADDING_OFFSET, BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE - BUILD_REQUEST_RECORD_PADDING_OFFSET); | |
109 | // encrypt | |
110 | uint8_t * record = records + recordIndex*TUNNEL_BUILD_RECORD_SIZE; | |
85 | 111 | auto encryptor = ident->CreateEncryptor (nullptr); |
86 | if (IsECIES ()) | |
87 | { | |
88 | uint8_t clearText[ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; | |
89 | htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET, tunnelID); | |
90 | htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET, nextTunnelID); | |
91 | memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, nextIdent, 32); | |
92 | memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET, layerKey, 32); | |
93 | memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_IV_KEY_OFFSET, ivKey, 32); | |
94 | memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET, replyKey, 32); | |
95 | memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_IV_OFFSET, replyIV, 16); | |
96 | clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] = flag; | |
97 | memset (clearText + ECIES_BUILD_REQUEST_RECORD_MORE_FLAGS_OFFSET, 0, 3); // set to 0 for compatibility | |
98 | htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET, i2p::util::GetMinutesSinceEpoch ()); | |
99 | htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_REQUEST_EXPIRATION_OFFSET, 600); // +10 minutes | |
100 | htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET, replyMsgID); | |
101 | memset (clearText + ECIES_BUILD_REQUEST_RECORD_PADDING_OFFSET, 0, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE - ECIES_BUILD_REQUEST_RECORD_PADDING_OFFSET); | |
102 | if (encryptor) | |
103 | EncryptECIES (encryptor, clearText, record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, ctx); | |
104 | } | |
105 | else | |
106 | { | |
107 | uint8_t clearText[BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; | |
108 | htobe32buf (clearText + BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET, tunnelID); | |
109 | memcpy (clearText + BUILD_REQUEST_RECORD_OUR_IDENT_OFFSET, ident->GetIdentHash (), 32); | |
110 | htobe32buf (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET, nextTunnelID); | |
111 | memcpy (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, nextIdent, 32); | |
112 | memcpy (clearText + BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET, layerKey, 32); | |
113 | memcpy (clearText + BUILD_REQUEST_RECORD_IV_KEY_OFFSET, ivKey, 32); | |
114 | memcpy (clearText + BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET, replyKey, 32); | |
115 | memcpy (clearText + BUILD_REQUEST_RECORD_REPLY_IV_OFFSET, replyIV, 16); | |
116 | clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] = flag; | |
117 | htobe32buf (clearText + BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET, i2p::util::GetHoursSinceEpoch ()); | |
118 | htobe32buf (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET, replyMsgID); | |
119 | RAND_bytes (clearText + BUILD_REQUEST_RECORD_PADDING_OFFSET, BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE - BUILD_REQUEST_RECORD_PADDING_OFFSET); | |
120 | if (encryptor) | |
121 | encryptor->Encrypt (clearText, record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, ctx, false); | |
112 | if (encryptor) | |
113 | { | |
114 | BN_CTX * ctx = BN_CTX_new (); | |
115 | encryptor->Encrypt (clearText, record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, ctx, false); | |
116 | BN_CTX_free (ctx); | |
122 | 117 | } |
123 | 118 | memcpy (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)ident->GetIdentHash (), 16); |
124 | } | |
125 | ||
126 | void TunnelHopConfig::EncryptECIES (std::shared_ptr<i2p::crypto::CryptoKeyEncryptor>& encryptor, | |
127 | const uint8_t * plainText, uint8_t * encrypted, BN_CTX * ctx) | |
128 | { | |
129 | uint8_t hepk[32]; | |
130 | encryptor->Encrypt (nullptr, hepk, nullptr, false); | |
131 | i2p::crypto::InitNoiseNState (*this, hepk); | |
119 | } | |
120 | ||
121 | bool ElGamalTunnelHopConfig::DecryptBuildResponseRecord (uint8_t * records) const | |
122 | { | |
123 | uint8_t * record = records + recordIndex*TUNNEL_BUILD_RECORD_SIZE; | |
124 | i2p::crypto::CBCDecryption decryption; | |
125 | decryption.SetKey (replyKey); | |
126 | decryption.SetIV (replyIV); | |
127 | decryption.Decrypt (record, TUNNEL_BUILD_RECORD_SIZE, record); | |
128 | return true; | |
129 | } | |
130 | ||
131 | void ECIESTunnelHopConfig::EncryptECIES (const uint8_t * plainText, size_t len, uint8_t * encrypted) | |
132 | { | |
133 | if (!ident) return; | |
134 | i2p::crypto::InitNoiseNState (*this, ident->GetEncryptionPublicKey ()); | |
132 | 135 | auto ephemeralKeys = i2p::transport::transports.GetNextX25519KeysPair (); |
133 | 136 | memcpy (encrypted, ephemeralKeys->GetPublicKey (), 32); |
134 | 137 | MixHash (encrypted, 32); // h = SHA256(h || sepk) |
135 | 138 | encrypted += 32; |
136 | 139 | uint8_t sharedSecret[32]; |
137 | ephemeralKeys->Agree (hepk, sharedSecret); // x25519(sesk, hepk) | |
140 | ephemeralKeys->Agree (ident->GetEncryptionPublicKey (), sharedSecret); // x25519(sesk, hepk) | |
138 | 141 | MixKey (sharedSecret); |
139 | 142 | uint8_t nonce[12]; |
140 | 143 | memset (nonce, 0, 12); |
141 | if (!i2p::crypto::AEADChaCha20Poly1305 (plainText, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE, m_H, 32, | |
142 | m_CK + 32, nonce, encrypted, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE + 16, true)) // encrypt | |
144 | if (!i2p::crypto::AEADChaCha20Poly1305 (plainText, len, m_H, 32, m_CK + 32, nonce, encrypted, len + 16, true)) // encrypt | |
143 | 145 | { |
144 | 146 | LogPrint (eLogWarning, "Tunnel: Plaintext AEAD encryption failed"); |
145 | 147 | return; |
146 | 148 | } |
147 | MixHash (encrypted, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE + 16); // h = SHA256(h || ciphertext) | |
149 | MixHash (encrypted, len + 16); // h = SHA256(h || ciphertext) | |
150 | } | |
151 | ||
152 | bool ECIESTunnelHopConfig::DecryptECIES (const uint8_t * key, const uint8_t * nonce, const uint8_t * encrypted, size_t len, uint8_t * clearText) const | |
153 | { | |
154 | return i2p::crypto::AEADChaCha20Poly1305 (encrypted, len - 16, m_H, 32, key, nonce, clearText, len - 16, false); // decrypt | |
155 | } | |
156 | ||
157 | void LongECIESTunnelHopConfig::CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID) | |
158 | { | |
159 | // generate keys | |
160 | RAND_bytes (layerKey, 32); | |
161 | RAND_bytes (ivKey, 32); | |
162 | RAND_bytes (replyKey, 32); | |
163 | RAND_bytes (replyIV, 16); | |
164 | // fill clear text | |
165 | uint8_t flag = 0; | |
166 | if (isGateway) flag |= TUNNEL_BUILD_RECORD_GATEWAY_FLAG; | |
167 | if (isEndpoint) flag |= TUNNEL_BUILD_RECORD_ENDPOINT_FLAG; | |
168 | uint8_t clearText[ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; | |
169 | htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET, tunnelID); | |
170 | htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET, nextTunnelID); | |
171 | memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, nextIdent, 32); | |
172 | memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET, layerKey, 32); | |
173 | memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_IV_KEY_OFFSET, ivKey, 32); | |
174 | memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET, replyKey, 32); | |
175 | memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_IV_OFFSET, replyIV, 16); | |
176 | clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] = flag; | |
177 | memset (clearText + ECIES_BUILD_REQUEST_RECORD_MORE_FLAGS_OFFSET, 0, 3); // set to 0 for compatibility | |
178 | htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET, i2p::util::GetMinutesSinceEpoch ()); | |
179 | htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_REQUEST_EXPIRATION_OFFSET, 600); // +10 minutes | |
180 | htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET, replyMsgID); | |
181 | memset (clearText + ECIES_BUILD_REQUEST_RECORD_PADDING_OFFSET, 0, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE - ECIES_BUILD_REQUEST_RECORD_PADDING_OFFSET); | |
182 | // encrypt | |
183 | uint8_t * record = records + recordIndex*TUNNEL_BUILD_RECORD_SIZE; | |
184 | EncryptECIES (clearText, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE, record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET); | |
185 | memcpy (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)ident->GetIdentHash (), 16); | |
186 | } | |
187 | ||
188 | bool LongECIESTunnelHopConfig::DecryptBuildResponseRecord (uint8_t * records) const | |
189 | { | |
190 | uint8_t * record = records + recordIndex*TUNNEL_BUILD_RECORD_SIZE; | |
191 | uint8_t nonce[12]; | |
192 | memset (nonce, 0, 12); | |
193 | if (!DecryptECIES (m_CK, nonce, record, TUNNEL_BUILD_RECORD_SIZE, record)) | |
194 | { | |
195 | LogPrint (eLogWarning, "Tunnel: Response AEAD decryption failed"); | |
196 | return false; | |
197 | } | |
198 | return true; | |
199 | } | |
200 | ||
201 | void ShortECIESTunnelHopConfig::CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID) | |
202 | { | |
203 | // fill clear text | |
204 | uint8_t flag = 0; | |
205 | if (isGateway) flag |= TUNNEL_BUILD_RECORD_GATEWAY_FLAG; | |
206 | if (isEndpoint) flag |= TUNNEL_BUILD_RECORD_ENDPOINT_FLAG; | |
207 | uint8_t clearText[SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE ]; | |
208 | htobe32buf (clearText + SHORT_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET, tunnelID); | |
209 | htobe32buf (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET, nextTunnelID); | |
210 | memcpy (clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, nextIdent, 32); | |
211 | clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] = flag; | |
212 | memset (clearText + SHORT_REQUEST_RECORD_MORE_FLAGS_OFFSET, 0, 2); | |
213 | clearText[SHORT_REQUEST_RECORD_LAYER_ENCRYPTION_TYPE] = 0; // AES | |
214 | htobe32buf (clearText + SHORT_REQUEST_RECORD_REQUEST_TIME_OFFSET, i2p::util::GetMinutesSinceEpoch ()); | |
215 | htobe32buf (clearText + SHORT_REQUEST_RECORD_REQUEST_EXPIRATION_OFFSET , 600); // +10 minutes | |
216 | htobe32buf (clearText + SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET, replyMsgID); | |
217 | memset (clearText + SHORT_REQUEST_RECORD_PADDING_OFFSET, 0, SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE - SHORT_REQUEST_RECORD_PADDING_OFFSET); | |
218 | // encrypt | |
219 | uint8_t * record = records + recordIndex*SHORT_TUNNEL_BUILD_RECORD_SIZE; | |
220 | EncryptECIES (clearText, SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE, record + SHORT_REQUEST_RECORD_ENCRYPTED_OFFSET); | |
221 | // derive keys | |
222 | i2p::crypto::HKDF (m_CK, nullptr, 0, "SMTunnelReplyKey", m_CK); | |
223 | memcpy (replyKey, m_CK + 32, 32); | |
224 | i2p::crypto::HKDF (m_CK, nullptr, 0, "SMTunnelLayerKey", m_CK); | |
225 | memcpy (layerKey, m_CK + 32, 32); | |
226 | if (isEndpoint) | |
227 | { | |
228 | i2p::crypto::HKDF (m_CK, nullptr, 0, "TunnelLayerIVKey", m_CK); | |
229 | memcpy (ivKey, m_CK + 32, 32); | |
230 | i2p::crypto::HKDF (m_CK, nullptr, 0, "RGarlicKeyAndTag", m_CK); // OTBRM garlic key m_CK + 32, tag first 8 bytes of m_CK | |
231 | } | |
232 | else | |
233 | memcpy (ivKey, m_CK, 32); // last HKDF | |
234 | memcpy (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)ident->GetIdentHash (), 16); | |
235 | } | |
236 | ||
237 | bool ShortECIESTunnelHopConfig::DecryptBuildResponseRecord (uint8_t * records) const | |
238 | { | |
239 | uint8_t * record = records + recordIndex*SHORT_TUNNEL_BUILD_RECORD_SIZE; | |
240 | uint8_t nonce[12]; | |
241 | memset (nonce, 0, 12); | |
242 | nonce[4] = recordIndex; // nonce is record index | |
243 | if (!DecryptECIES (replyKey, nonce, record, SHORT_TUNNEL_BUILD_RECORD_SIZE, record)) | |
244 | { | |
245 | LogPrint (eLogWarning, "Tunnel: Response AEAD decryption failed"); | |
246 | return false; | |
247 | } | |
248 | return true; | |
249 | } | |
250 | ||
251 | void ShortECIESTunnelHopConfig::DecryptRecord (uint8_t * records, int index) const | |
252 | { | |
253 | uint8_t * record = records + index*SHORT_TUNNEL_BUILD_RECORD_SIZE; | |
254 | uint8_t nonce[12]; | |
255 | memset (nonce, 0, 12); | |
256 | nonce[4] = index; // nonce is index | |
257 | i2p::crypto::ChaCha20 (record, SHORT_TUNNEL_BUILD_RECORD_SIZE, replyKey, nonce, record); | |
258 | } | |
259 | ||
260 | uint64_t ShortECIESTunnelHopConfig::GetGarlicKey (uint8_t * key) const | |
261 | { | |
262 | uint64_t tag; | |
263 | memcpy (&tag, m_CK, 8); | |
264 | memcpy (key, m_CK + 32, 32); | |
265 | return tag; | |
148 | 266 | } |
149 | 267 | } |
150 | 268 | }⏎ |
17 | 17 | { |
18 | 18 | namespace tunnel |
19 | 19 | { |
20 | struct TunnelHopConfig: public i2p::crypto::NoiseSymmetricState | |
20 | struct TunnelHopConfig | |
21 | 21 | { |
22 | 22 | std::shared_ptr<const i2p::data::IdentityEx> ident; |
23 | 23 | i2p::data::IdentHash nextIdent; |
32 | 32 | int recordIndex; // record # in tunnel build message |
33 | 33 | |
34 | 34 | TunnelHopConfig (std::shared_ptr<const i2p::data::IdentityEx> r); |
35 | virtual ~TunnelHopConfig () {}; | |
35 | 36 | |
36 | 37 | void SetNextIdent (const i2p::data::IdentHash& ident); |
37 | 38 | void SetReplyHop (uint32_t replyTunnelID, const i2p::data::IdentHash& replyIdent); |
38 | 39 | void SetNext (TunnelHopConfig * n); |
39 | 40 | void SetPrev (TunnelHopConfig * p); |
40 | 41 | |
41 | bool IsECIES () const { return ident->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD; }; | |
42 | void CreateBuildRequestRecord (uint8_t * record, uint32_t replyMsgID, BN_CTX * ctx); | |
43 | void EncryptECIES (std::shared_ptr<i2p::crypto::CryptoKeyEncryptor>& encryptor, | |
44 | const uint8_t * clearText, uint8_t * encrypted, BN_CTX * ctx); | |
45 | }; | |
42 | virtual uint8_t GetRetCode (const uint8_t * records) const = 0; | |
43 | virtual void CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID) = 0; | |
44 | virtual bool DecryptBuildResponseRecord (uint8_t * records) const = 0; | |
45 | virtual void DecryptRecord (uint8_t * records, int index) const; // AES | |
46 | virtual uint64_t GetGarlicKey (uint8_t * key) const { return 0; }; // return tag | |
47 | }; | |
48 | ||
49 | struct ElGamalTunnelHopConfig: public TunnelHopConfig | |
50 | { | |
51 | ElGamalTunnelHopConfig (std::shared_ptr<const i2p::data::IdentityEx> r): | |
52 | TunnelHopConfig (r) {}; | |
53 | uint8_t GetRetCode (const uint8_t * records) const | |
54 | { return (records + recordIndex*TUNNEL_BUILD_RECORD_SIZE)[BUILD_RESPONSE_RECORD_RET_OFFSET]; }; | |
55 | void CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID); | |
56 | bool DecryptBuildResponseRecord (uint8_t * records) const; | |
57 | }; | |
58 | ||
59 | struct ECIESTunnelHopConfig: public TunnelHopConfig, public i2p::crypto::NoiseSymmetricState | |
60 | { | |
61 | ECIESTunnelHopConfig (std::shared_ptr<const i2p::data::IdentityEx> r): | |
62 | TunnelHopConfig (r) {}; | |
63 | void EncryptECIES (const uint8_t * clearText, size_t len, uint8_t * encrypted); | |
64 | bool DecryptECIES (const uint8_t * key, const uint8_t * nonce, const uint8_t * encrypted, size_t len, uint8_t * clearText) const; | |
65 | }; | |
66 | ||
67 | struct LongECIESTunnelHopConfig: public ECIESTunnelHopConfig | |
68 | { | |
69 | LongECIESTunnelHopConfig (std::shared_ptr<const i2p::data::IdentityEx> r): | |
70 | ECIESTunnelHopConfig (r) {}; | |
71 | uint8_t GetRetCode (const uint8_t * records) const | |
72 | { return (records + recordIndex*TUNNEL_BUILD_RECORD_SIZE)[ECIES_BUILD_RESPONSE_RECORD_RET_OFFSET]; }; | |
73 | void CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID); | |
74 | bool DecryptBuildResponseRecord (uint8_t * records) const; | |
75 | }; | |
76 | ||
77 | struct ShortECIESTunnelHopConfig: public ECIESTunnelHopConfig | |
78 | { | |
79 | ShortECIESTunnelHopConfig (std::shared_ptr<const i2p::data::IdentityEx> r): | |
80 | ECIESTunnelHopConfig (r) {}; | |
81 | uint8_t GetRetCode (const uint8_t * records) const | |
82 | { return (records + recordIndex*SHORT_TUNNEL_BUILD_RECORD_SIZE)[SHORT_RESPONSE_RECORD_RET_OFFSET]; }; | |
83 | void CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID); | |
84 | bool DecryptBuildResponseRecord (uint8_t * records) const; | |
85 | void DecryptRecord (uint8_t * records, int index) const override; // Chacha20 | |
86 | uint64_t GetGarlicKey (uint8_t * key) const override; | |
87 | }; | |
46 | 88 | |
47 | 89 | class TunnelConfig |
48 | 90 | { |
49 | 91 | public: |
50 | 92 | |
51 | TunnelConfig (std::vector<std::shared_ptr<const i2p::data::IdentityEx> > peers) // inbound | |
93 | TunnelConfig (const std::vector<std::shared_ptr<const i2p::data::IdentityEx> >& peers, | |
94 | bool isShort = false): // inbound | |
95 | m_IsShort (isShort) | |
52 | 96 | { |
53 | 97 | CreatePeers (peers); |
54 | 98 | m_LastHop->SetNextIdent (i2p::context.GetIdentHash ()); |
55 | 99 | } |
56 | 100 | |
57 | TunnelConfig (std::vector<std::shared_ptr<const i2p::data::IdentityEx> > peers, | |
58 | uint32_t replyTunnelID, const i2p::data::IdentHash& replyIdent) // outbound | |
101 | TunnelConfig (const std::vector<std::shared_ptr<const i2p::data::IdentityEx> >& peers, | |
102 | uint32_t replyTunnelID, const i2p::data::IdentHash& replyIdent, bool isShort = false): // outbound | |
103 | m_IsShort (isShort) | |
59 | 104 | { |
60 | 105 | CreatePeers (peers); |
61 | 106 | m_FirstHop->isGateway = false; |
74 | 119 | } |
75 | 120 | } |
76 | 121 | |
122 | bool IsShort () const { return m_IsShort; } | |
123 | ||
77 | 124 | TunnelHopConfig * GetFirstHop () const |
78 | 125 | { |
79 | 126 | return m_FirstHop; |
140 | 187 | protected: |
141 | 188 | |
142 | 189 | // this constructor can't be called from outside |
143 | TunnelConfig (): m_FirstHop (nullptr), m_LastHop (nullptr) | |
190 | TunnelConfig (): m_FirstHop (nullptr), m_LastHop (nullptr), m_IsShort (false) | |
144 | 191 | { |
145 | 192 | } |
146 | 193 | |
147 | 194 | private: |
148 | 195 | |
149 | template<class Peers> | |
150 | void CreatePeers (const Peers& peers) | |
196 | void CreatePeers (const std::vector<std::shared_ptr<const i2p::data::IdentityEx> >& peers) | |
151 | 197 | { |
152 | 198 | TunnelHopConfig * prev = nullptr; |
153 | 199 | for (const auto& it: peers) |
154 | 200 | { |
155 | auto hop = new TunnelHopConfig (it); | |
201 | TunnelHopConfig * hop; | |
202 | if (m_IsShort) | |
203 | hop = new ShortECIESTunnelHopConfig (it); | |
204 | else | |
205 | { | |
206 | if (it->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) | |
207 | hop = new LongECIESTunnelHopConfig (it); | |
208 | else | |
209 | hop = new ElGamalTunnelHopConfig (it); | |
210 | } | |
156 | 211 | if (prev) |
157 | 212 | prev->SetNext (hop); |
158 | 213 | else |
165 | 220 | private: |
166 | 221 | |
167 | 222 | TunnelHopConfig * m_FirstHop, * m_LastHop; |
223 | bool m_IsShort; | |
168 | 224 | }; |
169 | 225 | |
170 | 226 | class ZeroHopsTunnelConfig: public TunnelConfig |
51 | 51 | bool isFollowOnFragment = flag & 0x80, isLastFragment = true; |
52 | 52 | uint32_t msgID = 0; |
53 | 53 | int fragmentNum = 0; |
54 | TunnelMessageBlockEx m; | |
55 | 54 | if (!isFollowOnFragment) |
56 | 55 | { |
57 | 56 | // first fragment |
58 | ||
59 | m.deliveryType = (TunnelDeliveryType)((flag >> 5) & 0x03); | |
60 | switch (m.deliveryType) | |
57 | if (m_CurrentMsgID) | |
58 | AddIncompleteCurrentMessage (); // we have got a new message while previous is not complete | |
59 | ||
60 | m_CurrentMessage.deliveryType = (TunnelDeliveryType)((flag >> 5) & 0x03); | |
61 | switch (m_CurrentMessage.deliveryType) | |
61 | 62 | { |
62 | 63 | case eDeliveryTypeLocal: // 0 |
63 | 64 | break; |
64 | 65 | case eDeliveryTypeTunnel: // 1 |
65 | m.tunnelID = bufbe32toh (fragment); | |
66 | m_CurrentMessage.tunnelID = bufbe32toh (fragment); | |
66 | 67 | fragment += 4; // tunnelID |
67 | m.hash = i2p::data::IdentHash (fragment); | |
68 | m_CurrentMessage.hash = i2p::data::IdentHash (fragment); | |
68 | 69 | fragment += 32; // hash |
69 | 70 | break; |
70 | 71 | case eDeliveryTypeRouter: // 2 |
71 | m.hash = i2p::data::IdentHash (fragment); | |
72 | m_CurrentMessage.hash = i2p::data::IdentHash (fragment); | |
72 | 73 | fragment += 32; // to hash |
73 | 74 | break; |
74 | 75 | default: ; |
80 | 81 | // Message ID |
81 | 82 | msgID = bufbe32toh (fragment); |
82 | 83 | fragment += 4; |
84 | m_CurrentMsgID = msgID; | |
83 | 85 | isLastFragment = false; |
84 | 86 | } |
85 | 87 | } |
95 | 97 | uint16_t size = bufbe16toh (fragment); |
96 | 98 | fragment += 2; |
97 | 99 | |
98 | msg->offset = fragment - msg->buf; | |
99 | msg->len = msg->offset + size; | |
100 | if (msg->len > msg->maxLen) | |
101 | { | |
102 | LogPrint (eLogError, "TunnelMessage: fragment is too long ", (int)size); | |
103 | return; | |
104 | } | |
105 | if (fragment + size < decrypted + TUNNEL_DATA_ENCRYPTED_SIZE) | |
106 | { | |
107 | // this is not last message. we have to copy it | |
108 | m.data = NewI2NPTunnelMessage (); | |
109 | m.data->offset += TUNNEL_GATEWAY_HEADER_SIZE; // reserve room for TunnelGateway header | |
110 | m.data->len += TUNNEL_GATEWAY_HEADER_SIZE; | |
111 | *(m.data) = *msg; | |
112 | } | |
100 | // handle fragment | |
101 | if (isFollowOnFragment) | |
102 | { | |
103 | // existing message | |
104 | if (m_CurrentMsgID && m_CurrentMsgID == msgID && m_CurrentMessage.nextFragmentNum == fragmentNum) | |
105 | HandleCurrenMessageFollowOnFragment (fragment, size, isLastFragment); // previous | |
106 | else | |
107 | { | |
108 | HandleFollowOnFragment (msgID, isLastFragment, fragmentNum, fragment, size); // another | |
109 | m_CurrentMsgID = 0; m_CurrentMessage.data = nullptr; | |
110 | } | |
111 | } | |
113 | 112 | else |
114 | m.data = msg; | |
115 | ||
116 | if (!isFollowOnFragment && isLastFragment) | |
117 | HandleNextMessage (m); | |
118 | else | |
119 | { | |
120 | if (msgID) // msgID is presented, assume message is fragmented | |
121 | { | |
122 | if (!isFollowOnFragment) // create new incomlete message | |
123 | { | |
124 | m.nextFragmentNum = 1; | |
125 | m.receiveTime = i2p::util::GetMillisecondsSinceEpoch (); | |
126 | auto ret = m_IncompleteMessages.insert (std::pair<uint32_t, TunnelMessageBlockEx>(msgID, m)); | |
127 | if (ret.second) | |
128 | HandleOutOfSequenceFragments (msgID, ret.first->second); | |
129 | else | |
130 | LogPrint (eLogError, "TunnelMessage: Incomplete message ", msgID, " already exists"); | |
131 | } | |
132 | else | |
133 | { | |
134 | m.nextFragmentNum = fragmentNum; | |
135 | HandleFollowOnFragment (msgID, isLastFragment, m); | |
136 | } | |
113 | { | |
114 | // new message | |
115 | msg->offset = fragment - msg->buf; | |
116 | msg->len = msg->offset + size; | |
117 | // check message size | |
118 | if (msg->len > msg->maxLen) | |
119 | { | |
120 | LogPrint (eLogError, "TunnelMessage: fragment is too long ", (int)size); | |
121 | m_CurrentMsgID = 0; m_CurrentMessage.data = nullptr; | |
122 | return; | |
123 | } | |
124 | // create new or assign I2NP message | |
125 | if (fragment + size < decrypted + TUNNEL_DATA_ENCRYPTED_SIZE) | |
126 | { | |
127 | // this is not last message. we have to copy it | |
128 | m_CurrentMessage.data = NewI2NPTunnelMessage (true); | |
129 | *(m_CurrentMessage.data) = *msg; | |
137 | 130 | } |
138 | 131 | else |
132 | m_CurrentMessage.data = msg; | |
133 | ||
134 | if (isLastFragment) | |
135 | { | |
136 | // single message | |
137 | HandleNextMessage (m_CurrentMessage); | |
138 | m_CurrentMsgID = 0; m_CurrentMessage.data = nullptr; | |
139 | } | |
140 | else if (msgID) | |
141 | { | |
142 | // first fragment of a new message | |
143 | m_CurrentMessage.nextFragmentNum = 1; | |
144 | m_CurrentMessage.receiveTime = i2p::util::GetMillisecondsSinceEpoch (); | |
145 | HandleOutOfSequenceFragments (msgID, m_CurrentMessage); | |
146 | } | |
147 | else | |
148 | { | |
139 | 149 | LogPrint (eLogError, "TunnelMessage: Message is fragmented, but msgID is not presented"); |
140 | } | |
141 | ||
150 | m_CurrentMsgID = 0; m_CurrentMessage.data = nullptr; | |
151 | } | |
152 | } | |
153 | ||
142 | 154 | fragment += size; |
143 | 155 | } |
144 | 156 | } |
146 | 158 | LogPrint (eLogError, "TunnelMessage: zero not found"); |
147 | 159 | } |
148 | 160 | |
149 | void TunnelEndpoint::HandleFollowOnFragment (uint32_t msgID, bool isLastFragment, const TunnelMessageBlockEx& m) | |
150 | { | |
151 | auto fragment = m.data->GetBuffer (); | |
152 | auto size = m.data->GetLength (); | |
161 | void TunnelEndpoint::HandleFollowOnFragment (uint32_t msgID, bool isLastFragment, | |
162 | uint8_t fragmentNum, const uint8_t * fragment, size_t size) | |
163 | { | |
153 | 164 | auto it = m_IncompleteMessages.find (msgID); |
154 | 165 | if (it != m_IncompleteMessages.end()) |
155 | 166 | { |
156 | 167 | auto& msg = it->second; |
157 | if (m.nextFragmentNum == msg.nextFragmentNum) | |
158 | { | |
159 | if (msg.data->len + size < I2NP_MAX_MESSAGE_SIZE) // check if message is not too long | |
160 | { | |
161 | if (msg.data->len + size > msg.data->maxLen) | |
162 | { | |
163 | // LogPrint (eLogWarning, "TunnelMessage: I2NP message size ", msg.data->maxLen, " is not enough"); | |
164 | auto newMsg = NewI2NPMessage (); | |
165 | *newMsg = *(msg.data); | |
166 | msg.data = newMsg; | |
167 | } | |
168 | if (msg.data->Concat (fragment, size) < size) // concatenate fragment | |
169 | LogPrint (eLogError, "TunnelMessage: I2NP buffer overflow ", msg.data->maxLen); | |
168 | if (fragmentNum == msg.nextFragmentNum) | |
169 | { | |
170 | if (ConcatFollowOnFragment (msg, fragment, size)) | |
171 | { | |
170 | 172 | if (isLastFragment) |
171 | 173 | { |
172 | 174 | // message complete |
181 | 183 | } |
182 | 184 | else |
183 | 185 | { |
184 | LogPrint (eLogError, "TunnelMessage: Fragment ", m.nextFragmentNum, " of message ", msgID, "exceeds max I2NP message size, message dropped"); | |
186 | LogPrint (eLogError, "TunnelMessage: Fragment ", fragmentNum, " of message ", msgID, "exceeds max I2NP message size, message dropped"); | |
185 | 187 | m_IncompleteMessages.erase (it); |
186 | 188 | } |
187 | 189 | } |
188 | 190 | else |
189 | 191 | { |
190 | LogPrint (eLogWarning, "TunnelMessage: Unexpected fragment ", (int)m.nextFragmentNum, " instead ", (int)msg.nextFragmentNum, " of message ", msgID, ", saved"); | |
191 | AddOutOfSequenceFragment (msgID, m.nextFragmentNum, isLastFragment, m.data); | |
192 | LogPrint (eLogWarning, "TunnelMessage: Unexpected fragment ", (int)fragmentNum, " instead ", (int)msg.nextFragmentNum, " of message ", msgID, ", saved"); | |
193 | AddOutOfSequenceFragment (msgID, fragmentNum, isLastFragment, fragment, size); | |
192 | 194 | } |
193 | 195 | } |
194 | 196 | else |
195 | 197 | { |
196 | LogPrint (eLogWarning, "TunnelMessage: First fragment of message ", msgID, " not found, saved"); | |
197 | AddOutOfSequenceFragment (msgID, m.nextFragmentNum, isLastFragment, m.data); | |
198 | } | |
199 | } | |
200 | ||
201 | void TunnelEndpoint::AddOutOfSequenceFragment (uint32_t msgID, uint8_t fragmentNum, bool isLastFragment, std::shared_ptr<I2NPMessage> data) | |
202 | { | |
203 | if (!m_OutOfSequenceFragments.insert ({{msgID, fragmentNum}, {isLastFragment, data, i2p::util::GetMillisecondsSinceEpoch () }}).second) | |
198 | LogPrint (eLogDebug, "TunnelMessage: First fragment of message ", msgID, " not found, saved"); | |
199 | AddOutOfSequenceFragment (msgID, fragmentNum, isLastFragment, fragment, size); | |
200 | } | |
201 | } | |
202 | ||
203 | bool TunnelEndpoint::ConcatFollowOnFragment (TunnelMessageBlockEx& msg, const uint8_t * fragment, size_t size) const | |
204 | { | |
205 | if (msg.data->len + size < I2NP_MAX_MESSAGE_SIZE) // check if message is not too long | |
206 | { | |
207 | if (msg.data->len + size > msg.data->maxLen) | |
208 | { | |
209 | // LogPrint (eLogWarning, "TunnelMessage: I2NP message size ", msg.data->maxLen, " is not enough"); | |
210 | auto newMsg = NewI2NPMessage (); | |
211 | *newMsg = *(msg.data); | |
212 | msg.data = newMsg; | |
213 | } | |
214 | if (msg.data->Concat (fragment, size) < size) // concatenate fragment | |
215 | { | |
216 | LogPrint (eLogError, "TunnelMessage: I2NP buffer overflow ", msg.data->maxLen); | |
217 | return false; | |
218 | } | |
219 | } | |
220 | else | |
221 | return false; | |
222 | return true; | |
223 | } | |
224 | ||
225 | void TunnelEndpoint::HandleCurrenMessageFollowOnFragment (const uint8_t * fragment, size_t size, bool isLastFragment) | |
226 | { | |
227 | if (ConcatFollowOnFragment (m_CurrentMessage, fragment, size)) | |
228 | { | |
229 | if (isLastFragment) | |
230 | { | |
231 | // message complete | |
232 | HandleNextMessage (m_CurrentMessage); | |
233 | m_CurrentMsgID = 0; m_CurrentMessage.data = nullptr; | |
234 | } | |
235 | else | |
236 | { | |
237 | m_CurrentMessage.nextFragmentNum++; | |
238 | HandleOutOfSequenceFragments (m_CurrentMsgID, m_CurrentMessage); | |
239 | } | |
240 | } | |
241 | else | |
242 | { | |
243 | LogPrint (eLogError, "TunnelMessage: Fragment ", m_CurrentMessage.nextFragmentNum, " of message ", m_CurrentMsgID, " exceeds max I2NP message size, message dropped"); | |
244 | m_CurrentMsgID = 0; m_CurrentMessage.data = nullptr; | |
245 | } | |
246 | } | |
247 | ||
248 | void TunnelEndpoint::AddIncompleteCurrentMessage () | |
249 | { | |
250 | if (m_CurrentMsgID) | |
251 | { | |
252 | auto ret = m_IncompleteMessages.emplace (m_CurrentMsgID, m_CurrentMessage); | |
253 | if (!ret.second) | |
254 | LogPrint (eLogError, "TunnelMessage: Incomplete message ", m_CurrentMsgID, " already exists"); | |
255 | m_CurrentMessage.data = nullptr; | |
256 | m_CurrentMsgID = 0; | |
257 | } | |
258 | } | |
259 | ||
260 | void TunnelEndpoint::AddOutOfSequenceFragment (uint32_t msgID, uint8_t fragmentNum, | |
261 | bool isLastFragment, const uint8_t * fragment, size_t size) | |
262 | { | |
263 | std::unique_ptr<Fragment> f(new Fragment (isLastFragment, i2p::util::GetMillisecondsSinceEpoch (), size)); | |
264 | memcpy (f->data.data (), fragment, size); | |
265 | if (!m_OutOfSequenceFragments.emplace ((uint64_t)msgID << 32 | fragmentNum, std::move (f)).second) | |
204 | 266 | LogPrint (eLogInfo, "TunnelMessage: duplicate out-of-sequence fragment ", fragmentNum, " of message ", msgID); |
205 | 267 | } |
206 | 268 | |
211 | 273 | if (!msg.nextFragmentNum) // message complete |
212 | 274 | { |
213 | 275 | HandleNextMessage (msg); |
214 | m_IncompleteMessages.erase (msgID); | |
276 | if (&msg == &m_CurrentMessage) | |
277 | { | |
278 | m_CurrentMsgID = 0; | |
279 | m_CurrentMessage.data = nullptr; | |
280 | } | |
281 | else | |
282 | m_IncompleteMessages.erase (msgID); | |
283 | LogPrint (eLogDebug, "TunnelMessage: All fragments of message ", msgID, " found"); | |
215 | 284 | break; |
216 | 285 | } |
217 | 286 | } |
219 | 288 | |
220 | 289 | bool TunnelEndpoint::ConcatNextOutOfSequenceFragment (uint32_t msgID, TunnelMessageBlockEx& msg) |
221 | 290 | { |
222 | auto it = m_OutOfSequenceFragments.find ({msgID, msg.nextFragmentNum}); | |
291 | auto it = m_OutOfSequenceFragments.find ((uint64_t)msgID << 32 | msg.nextFragmentNum); | |
223 | 292 | if (it != m_OutOfSequenceFragments.end ()) |
224 | 293 | { |
225 | 294 | LogPrint (eLogDebug, "TunnelMessage: Out-of-sequence fragment ", (int)msg.nextFragmentNum, " of message ", msgID, " found"); |
226 | size_t size = it->second.data->GetLength (); | |
295 | size_t size = it->second->data.size (); | |
227 | 296 | if (msg.data->len + size > msg.data->maxLen) |
228 | 297 | { |
229 | 298 | LogPrint (eLogWarning, "TunnelMessage: Tunnel endpoint I2NP message size ", msg.data->maxLen, " is not enough"); |
231 | 300 | *newMsg = *(msg.data); |
232 | 301 | msg.data = newMsg; |
233 | 302 | } |
234 | if (msg.data->Concat (it->second.data->GetBuffer (), size) < size) // concatenate out-of-sync fragment | |
303 | if (msg.data->Concat (it->second->data.data (), size) < size) // concatenate out-of-sync fragment | |
235 | 304 | LogPrint (eLogError, "TunnelMessage: Tunnel endpoint I2NP buffer overflow ", msg.data->maxLen); |
236 | if (it->second.isLastFragment) | |
305 | if (it->second->isLastFragment) | |
237 | 306 | // message complete |
238 | 307 | msg.nextFragmentNum = 0; |
239 | 308 | else |
286 | 355 | // out-of-sequence fragments |
287 | 356 | for (auto it = m_OutOfSequenceFragments.begin (); it != m_OutOfSequenceFragments.end ();) |
288 | 357 | { |
289 | if (ts > it->second.receiveTime + i2p::I2NP_MESSAGE_EXPIRATION_TIMEOUT) | |
358 | if (ts > it->second->receiveTime + i2p::I2NP_MESSAGE_EXPIRATION_TIMEOUT) | |
290 | 359 | it = m_OutOfSequenceFragments.erase (it); |
291 | 360 | else |
292 | 361 | ++it; |
0 | 0 | /* |
1 | * Copyright (c) 2013-2020, The PurpleI2P Project | |
1 | * Copyright (c) 2013-2021, The PurpleI2P Project | |
2 | 2 | * |
3 | 3 | * This file is part of Purple i2pd project and licensed under BSD3 |
4 | 4 | * |
9 | 9 | #define TUNNEL_ENDPOINT_H__ |
10 | 10 | |
11 | 11 | #include <inttypes.h> |
12 | #include <map> | |
12 | #include <unordered_map> | |
13 | #include <vector> | |
13 | 14 | #include <string> |
14 | 15 | #include "I2NPProtocol.h" |
15 | 16 | #include "TunnelBase.h" |
28 | 29 | |
29 | 30 | struct Fragment |
30 | 31 | { |
32 | Fragment (bool last, uint64_t t, size_t size): isLastFragment (last), receiveTime (t), data (size) {}; | |
31 | 33 | bool isLastFragment; |
32 | std::shared_ptr<I2NPMessage> data; | |
33 | 34 | uint64_t receiveTime; // milliseconds since epoch |
35 | std::vector<uint8_t> data; | |
34 | 36 | }; |
35 | 37 | |
36 | 38 | public: |
37 | 39 | |
38 | TunnelEndpoint (bool isInbound): m_IsInbound (isInbound), m_NumReceivedBytes (0) {}; | |
40 | TunnelEndpoint (bool isInbound): m_IsInbound (isInbound), m_NumReceivedBytes (0), m_CurrentMsgID (0) {}; | |
39 | 41 | ~TunnelEndpoint (); |
40 | 42 | size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; }; |
41 | 43 | void Cleanup (); |
44 | 46 | |
45 | 47 | private: |
46 | 48 | |
47 | void HandleFollowOnFragment (uint32_t msgID, bool isLastFragment, const TunnelMessageBlockEx& m); | |
49 | void HandleFollowOnFragment (uint32_t msgID, bool isLastFragment, uint8_t fragmentNum, const uint8_t * fragment, size_t size); | |
50 | bool ConcatFollowOnFragment (TunnelMessageBlockEx& msg, const uint8_t * fragment, size_t size) const; // true if success | |
51 | void HandleCurrenMessageFollowOnFragment (const uint8_t * frgament, size_t size, bool isLastFragment); | |
48 | 52 | void HandleNextMessage (const TunnelMessageBlock& msg); |
49 | 53 | |
50 | void AddOutOfSequenceFragment (uint32_t msgID, uint8_t fragmentNum, bool isLastFragment, std::shared_ptr<I2NPMessage> data); | |
54 | void AddOutOfSequenceFragment (uint32_t msgID, uint8_t fragmentNum, bool isLastFragment, const uint8_t * fragment, size_t size); | |
51 | 55 | bool ConcatNextOutOfSequenceFragment (uint32_t msgID, TunnelMessageBlockEx& msg); // true if something added |
52 | 56 | void HandleOutOfSequenceFragments (uint32_t msgID, TunnelMessageBlockEx& msg); |
53 | ||
57 | void AddIncompleteCurrentMessage (); | |
58 | ||
54 | 59 | private: |
55 | 60 | |
56 | std::map<uint32_t, TunnelMessageBlockEx> m_IncompleteMessages; | |
57 | std::map<std::pair<uint32_t, uint8_t>, Fragment> m_OutOfSequenceFragments; // (msgID, fragment#)->fragment | |
61 | std::unordered_map<uint32_t, TunnelMessageBlockEx> m_IncompleteMessages; | |
62 | std::unordered_map<uint64_t, std::unique_ptr<Fragment> > m_OutOfSequenceFragments; // ((msgID << 8) + fragment#)->fragment | |
58 | 63 | bool m_IsInbound; |
59 | 64 | size_t m_NumReceivedBytes; |
65 | TunnelMessageBlockEx m_CurrentMessage; | |
66 | uint32_t m_CurrentMsgID; | |
60 | 67 | }; |
61 | 68 | } |
62 | 69 | } |
214 | 214 | const auto& tunnelDataMsgs = m_Buffer.GetTunnelDataMsgs (); |
215 | 215 | for (auto& tunnelMsg : tunnelDataMsgs) |
216 | 216 | { |
217 | auto newMsg = CreateEmptyTunnelDataMsg (); | |
217 | auto newMsg = CreateEmptyTunnelDataMsg (false); | |
218 | 218 | m_Tunnel->EncryptTunnelMsg (tunnelMsg, newMsg); |
219 | 219 | htobe32buf (newMsg->GetPayload (), m_Tunnel->GetNextTunnelID ()); |
220 | 220 | newMsg->FillI2NPMessageHeader (eI2NPTunnelData); |
23 | 23 | { |
24 | 24 | namespace tunnel |
25 | 25 | { |
26 | void Path::Add (std::shared_ptr<const i2p::data::RouterInfo> r) | |
27 | { | |
28 | if (r) | |
29 | { | |
30 | peers.push_back (r->GetRouterIdentity ()); | |
31 | if (r->GetVersion () < i2p::data::NETDB_MIN_SHORT_TUNNEL_BUILD_VERSION || | |
32 | r->GetRouterIdentity ()->GetCryptoKeyType () != i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) | |
33 | isShort = false; | |
34 | } | |
35 | } | |
36 | ||
37 | void Path::Reverse () | |
38 | { | |
39 | std::reverse (peers.begin (), peers.end ()); | |
40 | } | |
41 | ||
26 | 42 | TunnelPool::TunnelPool (int numInboundHops, int numOutboundHops, int numInboundTunnels, int numOutboundTunnels): |
27 | 43 | m_NumInboundHops (numInboundHops), m_NumOutboundHops (numOutboundHops), |
28 | 44 | m_NumInboundTunnels (numInboundTunnels), m_NumOutboundTunnels (numOutboundTunnels), |
141 | 157 | { |
142 | 158 | std::vector<std::shared_ptr<InboundTunnel> > v; |
143 | 159 | int i = 0; |
160 | std::shared_ptr<InboundTunnel> slowTunnel; | |
144 | 161 | std::unique_lock<std::mutex> l(m_InboundTunnelsMutex); |
145 | 162 | for (const auto& it : m_InboundTunnels) |
146 | 163 | { |
147 | 164 | if (i >= num) break; |
148 | 165 | if (it->IsEstablished ()) |
149 | 166 | { |
150 | v.push_back (it); | |
151 | i++; | |
152 | } | |
153 | } | |
167 | if (it->IsSlow () && !slowTunnel) | |
168 | slowTunnel = it; | |
169 | else | |
170 | { | |
171 | v.push_back (it); | |
172 | i++; | |
173 | } | |
174 | } | |
175 | } | |
176 | if (slowTunnel && (int)v.size () < (num/2+1)) | |
177 | v.push_back (slowTunnel); | |
154 | 178 | return v; |
155 | 179 | } |
156 | 180 | |
171 | 195 | { |
172 | 196 | if (tunnels.empty ()) return nullptr; |
173 | 197 | uint32_t ind = rand () % (tunnels.size ()/2 + 1), i = 0; |
198 | bool skipped = false; | |
174 | 199 | typename TTunnels::value_type tunnel = nullptr; |
175 | 200 | for (const auto& it: tunnels) |
176 | 201 | { |
177 | 202 | if (it->IsEstablished () && it != excluded) |
178 | 203 | { |
179 | if(HasLatencyRequirement() && it->LatencyIsKnown() && !it->LatencyFitsRange(m_MinLatency, m_MaxLatency)) { | |
180 | i ++; | |
204 | if (it->IsSlow () || (HasLatencyRequirement() && it->LatencyIsKnown() && | |
205 | !it->LatencyFitsRange(m_MinLatency, m_MaxLatency))) | |
206 | { | |
207 | i++; skipped = true; | |
181 | 208 | continue; |
182 | 209 | } |
183 | 210 | tunnel = it; |
185 | 212 | } |
186 | 213 | if (i > ind && tunnel) break; |
187 | 214 | } |
188 | if(HasLatencyRequirement() && !tunnel) { | |
215 | if (!tunnel && skipped) | |
216 | { | |
189 | 217 | ind = rand () % (tunnels.size ()/2 + 1), i = 0; |
190 | 218 | for (const auto& it: tunnels) |
191 | 219 | { |
392 | 420 | } |
393 | 421 | } |
394 | 422 | |
423 | bool TunnelPool::IsExploratory () const | |
424 | { | |
425 | return i2p::tunnel::tunnels.GetExploratoryPool () == shared_from_this (); | |
426 | } | |
427 | ||
395 | 428 | std::shared_ptr<const i2p::data::RouterInfo> TunnelPool::SelectNextHop (std::shared_ptr<const i2p::data::RouterInfo> prevHop, bool reverse) const |
396 | 429 | { |
397 | bool isExploratory = (i2p::tunnel::tunnels.GetExploratoryPool () == shared_from_this ()); | |
398 | auto hop = isExploratory ? i2p::data::netdb.GetRandomRouter (prevHop, reverse): | |
430 | auto hop = IsExploratory () ? i2p::data::netdb.GetRandomRouter (prevHop, reverse): | |
399 | 431 | i2p::data::netdb.GetHighBandwidthRandomRouter (prevHop, reverse); |
400 | 432 | |
401 | 433 | if (!hop || hop->GetProfile ()->IsBad ()) |
403 | 435 | return hop; |
404 | 436 | } |
405 | 437 | |
406 | bool StandardSelectPeers(Path & peers, int numHops, bool inbound, SelectHopFunc nextHop) | |
438 | bool StandardSelectPeers(Path & path, int numHops, bool inbound, SelectHopFunc nextHop) | |
407 | 439 | { |
408 | 440 | int start = 0; |
409 | auto prevHop = i2p::context.GetSharedRouterInfo (); | |
441 | std::shared_ptr<const i2p::data::RouterInfo> prevHop = i2p::context.GetSharedRouterInfo (); | |
410 | 442 | if(i2p::transport::transports.RoutesRestricted()) |
411 | 443 | { |
412 | 444 | /** if routes are restricted prepend trusted first hop */ |
413 | 445 | auto hop = i2p::transport::transports.GetRestrictedPeer(); |
414 | 446 | if(!hop) return false; |
415 | peers.push_back(hop->GetRouterIdentity()); | |
447 | path.Add (hop); | |
416 | 448 | prevHop = hop; |
417 | 449 | start++; |
418 | 450 | } |
424 | 456 | (numHops > 1 || (r->IsV4 () && (!inbound || r->IsReachable ())))) // first inbound must be reachable |
425 | 457 | { |
426 | 458 | prevHop = r; |
427 | peers.push_back (r->GetRouterIdentity ()); | |
459 | path.Add (r); | |
428 | 460 | start++; |
429 | 461 | } |
430 | 462 | } |
449 | 481 | if (hop1) hop = hop1; |
450 | 482 | } |
451 | 483 | prevHop = hop; |
452 | peers.push_back (hop->GetRouterIdentity ()); | |
484 | path.Add (hop); | |
453 | 485 | } |
454 | 486 | return true; |
455 | 487 | } |
456 | 488 | |
457 | bool TunnelPool::SelectPeers (std::vector<std::shared_ptr<const i2p::data::IdentityEx> >& peers, bool isInbound) | |
489 | bool TunnelPool::SelectPeers (Path& path, bool isInbound) | |
458 | 490 | { |
459 | 491 | int numHops = isInbound ? m_NumInboundHops : m_NumOutboundHops; |
460 | 492 | // peers is empty |
463 | 495 | { |
464 | 496 | std::lock_guard<std::mutex> lock(m_CustomPeerSelectorMutex); |
465 | 497 | if (m_CustomPeerSelector) |
466 | return m_CustomPeerSelector->SelectPeers(peers, numHops, isInbound); | |
498 | return m_CustomPeerSelector->SelectPeers(path, numHops, isInbound); | |
467 | 499 | } |
468 | 500 | // explicit peers in use |
469 | if (m_ExplicitPeers) return SelectExplicitPeers (peers, isInbound); | |
470 | return StandardSelectPeers(peers, numHops, isInbound, std::bind(&TunnelPool::SelectNextHop, this, std::placeholders::_1, std::placeholders::_2)); | |
471 | } | |
472 | ||
473 | bool TunnelPool::SelectExplicitPeers (std::vector<std::shared_ptr<const i2p::data::IdentityEx> >& peers, bool isInbound) | |
474 | { | |
475 | int size = m_ExplicitPeers->size (); | |
476 | std::vector<int> peerIndicies; | |
477 | for (int i = 0; i < size; i++) peerIndicies.push_back(i); | |
478 | std::shuffle (peerIndicies.begin(), peerIndicies.end(), std::mt19937(std::random_device()())); | |
479 | ||
501 | if (m_ExplicitPeers) return SelectExplicitPeers (path, isInbound); | |
502 | return StandardSelectPeers(path, numHops, isInbound, std::bind(&TunnelPool::SelectNextHop, this, std::placeholders::_1, std::placeholders::_2)); | |
503 | } | |
504 | ||
505 | bool TunnelPool::SelectExplicitPeers (Path& path, bool isInbound) | |
506 | { | |
480 | 507 | int numHops = isInbound ? m_NumInboundHops : m_NumOutboundHops; |
508 | if (numHops > (int)m_ExplicitPeers->size ()) numHops = m_ExplicitPeers->size (); | |
509 | if (!numHops) return false; | |
481 | 510 | for (int i = 0; i < numHops; i++) |
482 | 511 | { |
483 | auto& ident = (*m_ExplicitPeers)[peerIndicies[i]]; | |
512 | auto& ident = (*m_ExplicitPeers)[i]; | |
484 | 513 | auto r = i2p::data::netdb.FindRouter (ident); |
485 | 514 | if (r) |
486 | peers.push_back (r->GetRouterIdentity ()); | |
515 | path.Add (r); | |
487 | 516 | else |
488 | 517 | { |
489 | 518 | LogPrint (eLogInfo, "Tunnels: Can't find router for ", ident.ToBase64 ()); |
500 | 529 | if (!outboundTunnel) |
501 | 530 | outboundTunnel = tunnels.GetNextOutboundTunnel (); |
502 | 531 | LogPrint (eLogDebug, "Tunnels: Creating destination inbound tunnel..."); |
503 | std::vector<std::shared_ptr<const i2p::data::IdentityEx> > peers; | |
504 | if (SelectPeers (peers, true)) | |
532 | Path path; | |
533 | if (SelectPeers (path, true)) | |
505 | 534 | { |
506 | 535 | std::shared_ptr<TunnelConfig> config; |
507 | 536 | if (m_NumInboundHops > 0) |
508 | 537 | { |
509 | std::reverse (peers.begin (), peers.end ()); | |
510 | config = std::make_shared<TunnelConfig> (peers); | |
511 | } | |
512 | auto tunnel = tunnels.CreateInboundTunnel (config, outboundTunnel); | |
513 | tunnel->SetTunnelPool (shared_from_this ()); | |
538 | path.Reverse (); | |
539 | config = std::make_shared<TunnelConfig> (path.peers, path.isShort); | |
540 | } | |
541 | auto tunnel = tunnels.CreateInboundTunnel (config, shared_from_this (), outboundTunnel); | |
514 | 542 | if (tunnel->IsEstablished ()) // zero hops |
515 | 543 | TunnelCreated (tunnel); |
516 | 544 | } |
520 | 548 | |
521 | 549 | void TunnelPool::RecreateInboundTunnel (std::shared_ptr<InboundTunnel> tunnel) |
522 | 550 | { |
551 | if (IsExploratory () || tunnel->IsSlow ()) // always create new exploratory tunnel or if slow | |
552 | { | |
553 | CreateInboundTunnel (); | |
554 | return; | |
555 | } | |
523 | 556 | auto outboundTunnel = GetNextOutboundTunnel (); |
524 | 557 | if (!outboundTunnel) |
525 | 558 | outboundTunnel = tunnels.GetNextOutboundTunnel (); |
529 | 562 | { |
530 | 563 | config = std::make_shared<TunnelConfig>(tunnel->GetPeers ()); |
531 | 564 | } |
532 | if (m_NumInboundHops == 0 || config) | |
533 | { | |
534 | auto newTunnel = tunnels.CreateInboundTunnel (config, outboundTunnel); | |
535 | newTunnel->SetTunnelPool (shared_from_this()); | |
565 | if (!m_NumInboundHops || config) | |
566 | { | |
567 | auto newTunnel = tunnels.CreateInboundTunnel (config, shared_from_this(), outboundTunnel); | |
536 | 568 | if (newTunnel->IsEstablished ()) // zero hops |
537 | 569 | TunnelCreated (newTunnel); |
538 | 570 | } |
546 | 578 | if (inboundTunnel) |
547 | 579 | { |
548 | 580 | LogPrint (eLogDebug, "Tunnels: Creating destination outbound tunnel..."); |
549 | std::vector<std::shared_ptr<const i2p::data::IdentityEx> > peers; | |
550 | if (SelectPeers (peers, false)) | |
581 | Path path; | |
582 | if (SelectPeers (path, false)) | |
551 | 583 | { |
552 | 584 | std::shared_ptr<TunnelConfig> config; |
553 | 585 | if (m_NumOutboundHops > 0) |
554 | config = std::make_shared<TunnelConfig>(peers, inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash ()); | |
555 | auto tunnel = tunnels.CreateOutboundTunnel (config); | |
556 | tunnel->SetTunnelPool (shared_from_this ()); | |
557 | if (tunnel->IsEstablished ()) // zero hops | |
586 | config = std::make_shared<TunnelConfig>(path.peers, inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash (), path.isShort); | |
587 | ||
588 | std::shared_ptr<OutboundTunnel> tunnel; | |
589 | if (path.isShort) | |
590 | { | |
591 | // TODO: implement it better | |
592 | tunnel = tunnels.CreateOutboundTunnel (config, inboundTunnel->GetTunnelPool ()); | |
593 | tunnel->SetTunnelPool (shared_from_this ()); | |
594 | } | |
595 | else | |
596 | tunnel = tunnels.CreateOutboundTunnel (config, shared_from_this ()); | |
597 | if (tunnel && tunnel->IsEstablished ()) // zero hops | |
558 | 598 | TunnelCreated (tunnel); |
559 | 599 | } |
560 | 600 | else |
566 | 606 | |
567 | 607 | void TunnelPool::RecreateOutboundTunnel (std::shared_ptr<OutboundTunnel> tunnel) |
568 | 608 | { |
609 | if (IsExploratory () || tunnel->IsSlow ()) // always create new exploratory tunnel or if slow | |
610 | { | |
611 | CreateOutboundTunnel (); | |
612 | return; | |
613 | } | |
569 | 614 | auto inboundTunnel = GetNextInboundTunnel (); |
570 | 615 | if (!inboundTunnel) |
571 | 616 | inboundTunnel = tunnels.GetNextInboundTunnel (); |
579 | 624 | } |
580 | 625 | if (!m_NumOutboundHops || config) |
581 | 626 | { |
582 | auto newTunnel = tunnels.CreateOutboundTunnel (config); | |
583 | newTunnel->SetTunnelPool (shared_from_this ()); | |
627 | auto newTunnel = tunnels.CreateOutboundTunnel (config, shared_from_this ()); | |
584 | 628 | if (newTunnel->IsEstablished ()) // zero hops |
585 | 629 | TunnelCreated (newTunnel); |
586 | 630 | } |
594 | 638 | LogPrint (eLogDebug, "Tunnels: Creating paired inbound tunnel..."); |
595 | 639 | auto tunnel = tunnels.CreateInboundTunnel ( |
596 | 640 | m_NumOutboundHops > 0 ? std::make_shared<TunnelConfig>(outboundTunnel->GetInvertedPeers ()) : nullptr, |
597 | outboundTunnel); | |
598 | tunnel->SetTunnelPool (shared_from_this ()); | |
641 | shared_from_this (), outboundTunnel); | |
599 | 642 | if (tunnel->IsEstablished ()) // zero hops |
600 | 643 | TunnelCreated (tunnel); |
601 | 644 | } |
35 | 35 | class OutboundTunnel; |
36 | 36 | |
37 | 37 | typedef std::shared_ptr<const i2p::data::IdentityEx> Peer; |
38 | typedef std::vector<Peer> Path; | |
38 | struct Path | |
39 | { | |
40 | std::vector<Peer> peers; | |
41 | bool isShort = true; | |
42 | ||
43 | void Add (std::shared_ptr<const i2p::data::RouterInfo> r); | |
44 | void Reverse (); | |
45 | }; | |
39 | 46 | |
40 | 47 | /** interface for custom tunnel peer selection algorithm */ |
41 | 48 | struct ITunnelPeerSelector |
46 | 53 | |
47 | 54 | |
48 | 55 | typedef std::function<std::shared_ptr<const i2p::data::RouterInfo>(std::shared_ptr<const i2p::data::RouterInfo>, bool)> SelectHopFunc; |
49 | // standard peer selection algorithm | |
50 | bool StandardSelectPeers(Path & path, int hops, bool inbound, SelectHopFunc nextHop); | |
51 | ||
56 | bool StandardSelectPeers(Path & path, int numHops, bool inbound, SelectHopFunc nextHop); | |
57 | ||
52 | 58 | class TunnelPool: public std::enable_shared_from_this<TunnelPool> // per local destination |
53 | 59 | { |
54 | 60 | public: |
76 | 82 | void ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg); |
77 | 83 | void ProcessDeliveryStatus (std::shared_ptr<I2NPMessage> msg); |
78 | 84 | |
85 | bool IsExploratory () const; | |
79 | 86 | bool IsActive () const { return m_IsActive; }; |
80 | 87 | void SetActive (bool isActive) { m_IsActive = isActive; }; |
81 | 88 | void DetachTunnels (); |
112 | 119 | void CreatePairedInboundTunnel (std::shared_ptr<OutboundTunnel> outboundTunnel); |
113 | 120 | template<class TTunnels> |
114 | 121 | typename TTunnels::value_type GetNextTunnel (TTunnels& tunnels, typename TTunnels::value_type excluded) const; |
115 | bool SelectPeers (std::vector<std::shared_ptr<const i2p::data::IdentityEx> >& hops, bool isInbound); | |
116 | bool SelectExplicitPeers (std::vector<std::shared_ptr<const i2p::data::IdentityEx> >& hops, bool isInbound); | |
122 | bool SelectPeers (Path& path, bool isInbound); | |
123 | bool SelectExplicitPeers (Path& path, bool isInbound); | |
117 | 124 | |
118 | 125 | private: |
119 | 126 |
343 | 343 | if(fd > 0) |
344 | 344 | { |
345 | 345 | ifreq ifr; |
346 | strncpy(ifr.ifr_name, ifa->ifa_name, IFNAMSIZ); // set interface for query | |
346 | strncpy(ifr.ifr_name, ifa->ifa_name, IFNAMSIZ-1); // set interface for query | |
347 | 347 | if(ioctl(fd, SIOCGIFMTU, &ifr) >= 0) |
348 | 348 | mtu = ifr.ifr_mtu; // MTU |
349 | 349 | else |
554 | 554 | static const std::vector< std::pair<boost::asio::ip::address_v6::bytes_type, boost::asio::ip::address_v6::bytes_type> > reservedIPv6Ranges { |
555 | 555 | address_pair_v6("2001:db8::", "2001:db8:ffff:ffff:ffff:ffff:ffff:ffff"), |
556 | 556 | address_pair_v6("fc00::", "fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"), |
557 | address_pair_v6("fe80::", "febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff") | |
557 | address_pair_v6("fe80::", "febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff"), | |
558 | address_pair_v6("ff00::", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"), | |
559 | address_pair_v6("::", "::"), | |
560 | address_pair_v6("::1", "::1") | |
558 | 561 | }; |
559 | 562 | |
560 | 563 | boost::asio::ip::address_v6::bytes_type ipv6_address = host.to_v6 ().to_bytes (); |
176 | 176 | |
177 | 177 | SaveStateHelper (T& orig): m_Original (orig), m_Copy (orig) {}; |
178 | 178 | ~SaveStateHelper () { m_Original = m_Copy; }; |
179 | ||
179 | ||
180 | 180 | private: |
181 | 181 | |
182 | 182 | T& m_Original; |
183 | 183 | T m_Copy; |
184 | }; | |
185 | ||
184 | }; | |
185 | ||
186 | 186 | namespace net |
187 | 187 | { |
188 | 188 | int GetMTU (const boost::asio::ip::address& localAddress); |
0 | 0 | /* |
1 | * Copyright (c) 2013-2020, The PurpleI2P Project | |
1 | * Copyright (c) 2013-2021, The PurpleI2P Project | |
2 | 2 | * |
3 | 3 | * This file is part of Purple i2pd project and licensed under BSD3 |
4 | 4 | * |
15 | 15 | #define MAKE_VERSION_NUMBER(a,b,c) ((a*100+b)*100+c) |
16 | 16 | |
17 | 17 | #define I2PD_VERSION_MAJOR 2 |
18 | #define I2PD_VERSION_MINOR 38 | |
18 | #define I2PD_VERSION_MINOR 39 | |
19 | 19 | #define I2PD_VERSION_MICRO 0 |
20 | 20 | #define I2PD_VERSION_PATCH 0 |
21 | 21 | #define I2PD_VERSION MAKE_VERSION(I2PD_VERSION_MAJOR, I2PD_VERSION_MINOR, I2PD_VERSION_MICRO) |
29 | 29 | |
30 | 30 | #define I2P_VERSION_MAJOR 0 |
31 | 31 | #define I2P_VERSION_MINOR 9 |
32 | #define I2P_VERSION_MICRO 50 | |
32 | #define I2P_VERSION_MICRO 51 | |
33 | 33 | #define I2P_VERSION_PATCH 0 |
34 | 34 | #define I2P_VERSION MAKE_VERSION(I2P_VERSION_MAJOR, I2P_VERSION_MINOR, I2P_VERSION_MICRO) |
35 | 35 | #define I2P_VERSION_NUMBER MAKE_VERSION_NUMBER(I2P_VERSION_MAJOR, I2P_VERSION_MINOR, I2P_VERSION_MICRO) |
456 | 456 | options[I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY] = GetI2CPOption(section, I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY, DEFAULT_INITIAL_ACK_DELAY); |
457 | 457 | options[I2CP_PARAM_STREAMING_ANSWER_PINGS] = GetI2CPOption(section, I2CP_PARAM_STREAMING_ANSWER_PINGS, isServer ? DEFAULT_ANSWER_PINGS : false); |
458 | 458 | options[I2CP_PARAM_LEASESET_TYPE] = GetI2CPOption(section, I2CP_PARAM_LEASESET_TYPE, DEFAULT_LEASESET_TYPE); |
459 | std::string encType = GetI2CPStringOption(section, I2CP_PARAM_LEASESET_ENCRYPTION_TYPE, isServer ? "" : "0,4"); | |
459 | std::string encType = GetI2CPStringOption(section, I2CP_PARAM_LEASESET_ENCRYPTION_TYPE, "0,4"); | |
460 | 460 | if (encType.length () > 0) options[I2CP_PARAM_LEASESET_ENCRYPTION_TYPE] = encType; |
461 | 461 | std::string privKey = GetI2CPStringOption(section, I2CP_PARAM_LEASESET_PRIV_KEY, ""); |
462 | 462 | if (privKey.length () > 0) options[I2CP_PARAM_LEASESET_PRIV_KEY] = privKey; |
494 | 494 | options[I2CP_PARAM_LEASESET_TYPE] = value; |
495 | 495 | if (i2p::config::GetOption(prefix + I2CP_PARAM_LEASESET_ENCRYPTION_TYPE, value)) |
496 | 496 | options[I2CP_PARAM_LEASESET_ENCRYPTION_TYPE] = value; |
497 | if (i2p::config::GetOption(prefix + I2CP_PARAM_LEASESET_PRIV_KEY, value) && !value.empty ()) | |
498 | options[I2CP_PARAM_LEASESET_PRIV_KEY] = value; | |
497 | 499 | } |
498 | 500 | |
499 | 501 | void ClientContext::ReadTunnels () |
717 | 719 | { |
718 | 720 | // udp server tunnel |
719 | 721 | // TODO: hostnames |
720 | if (address.empty ()) address = "127.0.0.1"; | |
721 | auto localAddress = boost::asio::ip::address::from_string(address); | |
722 | 722 | boost::asio::ip::udp::endpoint endpoint(boost::asio::ip::address::from_string(host), port); |
723 | if (address.empty ()) | |
724 | { | |
725 | if (!endpoint.address ().is_unspecified () && endpoint.address ().is_v6 ()) | |
726 | address = "::1"; | |
727 | else | |
728 | address = "127.0.0.1"; | |
729 | } | |
730 | auto localAddress = boost::asio::ip::address::from_string(address); | |
723 | 731 | auto serverTunnel = std::make_shared<I2PUDPServerTunnel>(name, localDestination, localAddress, endpoint, port, gzip); |
724 | 732 | if(!isUniqueLocal) |
725 | 733 | { |
27 | 27 | #include "I2PTunnel.h" |
28 | 28 | #include "Config.h" |
29 | 29 | #include "HTTP.h" |
30 | #include "I18N.h" | |
30 | 31 | |
31 | 32 | namespace i2p { |
32 | 33 | namespace proxy { |
70 | 71 | void SentHTTPFailed(const boost::system::error_code & ecode); |
71 | 72 | void HandleStreamRequestComplete (std::shared_ptr<i2p::stream::Stream> stream); |
72 | 73 | /* error helpers */ |
73 | void GenericProxyError(const char *title, const char *description); | |
74 | void GenericProxyInfo(const char *title, const char *description); | |
74 | void GenericProxyError(const std::string& title, const std::string& description); | |
75 | void GenericProxyInfo(const std::string& title, const std::string& description); | |
75 | 76 | void HostNotFound(std::string & host); |
76 | 77 | void SendProxyError(std::string & content); |
77 | 78 | |
150 | 151 | Done(shared_from_this()); |
151 | 152 | } |
152 | 153 | |
153 | void HTTPReqHandler::GenericProxyError(const char *title, const char *description) { | |
154 | void HTTPReqHandler::GenericProxyError(const std::string& title, const std::string& description) { | |
154 | 155 | std::stringstream ss; |
155 | ss << "<h1>Proxy error: " << title << "</h1>\r\n"; | |
156 | ss << "<h1>" << tr("Proxy error") << ": " << title << "</h1>\r\n"; | |
156 | 157 | ss << "<p>" << description << "</p>\r\n"; |
157 | 158 | std::string content = ss.str(); |
158 | 159 | SendProxyError(content); |
159 | 160 | } |
160 | 161 | |
161 | void HTTPReqHandler::GenericProxyInfo(const char *title, const char *description) { | |
162 | void HTTPReqHandler::GenericProxyInfo(const std::string& title, const std::string& description) { | |
162 | 163 | std::stringstream ss; |
163 | ss << "<h1>Proxy info: " << title << "</h1>\r\n"; | |
164 | ss << "<h1>" << tr("Proxy info") << ": " << title << "</h1>\r\n"; | |
164 | 165 | ss << "<p>" << description << "</p>\r\n"; |
165 | 166 | std::string content = ss.str(); |
166 | 167 | SendProxyError(content); |
168 | 169 | |
169 | 170 | void HTTPReqHandler::HostNotFound(std::string & host) { |
170 | 171 | std::stringstream ss; |
171 | ss << "<h1>Proxy error: Host not found</h1>\r\n" | |
172 | << "<p>Remote host not found in router's addressbook</p>\r\n" | |
173 | << "<p>You may try to find this host on jump services below:</p>\r\n" | |
172 | ss << "<h1>" << tr("Proxy error: Host not found") << "</h1>\r\n" | |
173 | << "<p>" << tr("Remote host not found in router's addressbook") << "</p>\r\n" | |
174 | << "<p>" << tr("You may try to find this host on jump services below") << ":</p>\r\n" | |
174 | 175 | << "<ul>\r\n"; |
175 | 176 | for (const auto& js : jumpservices) { |
176 | 177 | ss << " <li><a href=\"" << js.second << host << "\">" << js.first << "</a></li>\r\n"; |
215 | 216 | b64 = i2p::http::UrlDecode(value); |
216 | 217 | // if we need update exists, request formed with update param |
217 | 218 | if (params["update"] == "true") { len += std::strlen("&update=true"); confirm = true; } |
219 | if (pos != 0 && url.query[pos-1] == '&') { pos--; len++; } // if helper is not only one query option | |
218 | 220 | url.query.replace(pos, len, ""); |
219 | 221 | return true; |
220 | 222 | } |
267 | 269 | |
268 | 270 | if (m_req_len < 0) { |
269 | 271 | LogPrint(eLogError, "HTTPProxy: unable to parse request"); |
270 | GenericProxyError("Invalid request", "Proxy unable to parse your request"); | |
272 | GenericProxyError(tr("Invalid request"), tr("Proxy unable to parse your request")); | |
271 | 273 | return true; /* parse error */ |
272 | 274 | } |
273 | 275 | |
282 | 284 | if (!m_Addresshelper) |
283 | 285 | { |
284 | 286 | LogPrint(eLogWarning, "HTTPProxy: addresshelper request rejected"); |
285 | GenericProxyError("Invalid request", "addresshelper is not supported"); | |
287 | GenericProxyError(tr("Invalid request"), tr("addresshelper is not supported")); | |
286 | 288 | return true; |
287 | 289 | } |
288 | 290 | if (!i2p::client::context.GetAddressBook ().FindAddress (m_RequestURL.host) || m_Confirm) |
291 | 293 | LogPrint (eLogInfo, "HTTPProxy: added address from addresshelper for ", m_RequestURL.host); |
292 | 294 | std::string full_url = m_RequestURL.to_string(); |
293 | 295 | std::stringstream ss; |
294 | ss << "Host " << m_RequestURL.host << " added to router's addressbook from helper. " | |
295 | << "Click <a href=\"" << full_url << "\">here</a> to proceed."; | |
296 | GenericProxyInfo("Addresshelper found", ss.str().c_str()); | |
296 | ss << tr("Host") <<" " << m_RequestURL.host << " " << tr("added to router's addressbook from helper") << ". "; | |
297 | ss << tr("Click here to proceed:") << " <a href=\"" << full_url << "\">" << tr("Continue") << "</a>."; | |
298 | GenericProxyInfo(tr("Addresshelper found"), ss.str()); | |
297 | 299 | return true; /* request processed */ |
298 | 300 | } |
299 | 301 | else |
300 | 302 | { |
303 | std::string full_url = m_RequestURL.to_string(); | |
301 | 304 | std::stringstream ss; |
302 | ss << "Host " << m_RequestURL.host << " <font color=red>already in router's addressbook</font>. " | |
303 | << "Click <a href=\"" << m_RequestURL.query << "?i2paddresshelper=" << jump << "&update=true\">here</a> to update record."; | |
304 | GenericProxyInfo("Addresshelper found", ss.str().c_str()); | |
305 | ss << tr("Host") << " " << m_RequestURL.host << " <font color=red>" << tr("already in router's addressbook") << "</font>. "; | |
306 | ss << tr("Click here to update record:") << " <a href=\"" << full_url << (full_url.find('?') != std::string::npos ? "&i2paddresshelper=" : "?i2paddresshelper="); | |
307 | ss << jump << "&update=true\">" << tr("Continue") << "</a>."; | |
308 | GenericProxyInfo(tr("Addresshelper found"), ss.str()); | |
305 | 309 | return true; /* request processed */ |
306 | 310 | } |
307 | 311 | } |
314 | 318 | auto pos = uri.find(":"); |
315 | 319 | if(pos == std::string::npos || pos == uri.size() - 1) |
316 | 320 | { |
317 | GenericProxyError("Invalid Request", "invalid request uri"); | |
321 | GenericProxyError(tr("Invalid request"), tr("invalid request uri")); | |
318 | 322 | return true; |
319 | 323 | } |
320 | 324 | else |
357 | 361 | else |
358 | 362 | { |
359 | 363 | /* relative url and missing 'Host' header */ |
360 | GenericProxyError("Invalid request", "Can't detect destination host from request"); | |
364 | GenericProxyError(tr("Invalid request"), tr("Can't detect destination host from request")); | |
361 | 365 | return true; |
362 | 366 | } |
363 | 367 | } |
374 | 378 | if(m_ProxyURL.parse(m_OutproxyUrl)) |
375 | 379 | ForwardToUpstreamProxy(); |
376 | 380 | else |
377 | GenericProxyError("Outproxy failure", "bad outproxy settings"); | |
381 | GenericProxyError(tr("Outproxy failure"), tr("bad outproxy settings")); | |
378 | 382 | } else { |
379 | 383 | LogPrint (eLogWarning, "HTTPProxy: outproxy failure for ", dest_host, ": no outproxy enabled"); |
380 | std::string message = "Host " + dest_host + " not inside I2P network, but outproxy is not enabled"; | |
381 | GenericProxyError("Outproxy failure", message.c_str()); | |
384 | std::stringstream ss; ss << tr("Host") << " " << dest_host << " " << tr("not inside I2P network, but outproxy is not enabled"); | |
385 | GenericProxyError(tr("Outproxy failure"), ss.str()); | |
382 | 386 | } |
383 | 387 | return true; |
384 | 388 | } |
466 | 470 | else |
467 | 471 | { |
468 | 472 | // unknown type, complain |
469 | GenericProxyError("unknown outproxy url", m_ProxyURL.to_string().c_str()); | |
473 | GenericProxyError(tr("unknown outproxy url"), m_ProxyURL.to_string()); | |
470 | 474 | } |
471 | 475 | } |
472 | 476 | |
473 | 477 | void HTTPReqHandler::HandleUpstreamProxyResolved(const boost::system::error_code & ec, boost::asio::ip::tcp::resolver::iterator it, ProxyResolvedHandler handler) |
474 | 478 | { |
475 | if(ec) GenericProxyError("cannot resolve upstream proxy", ec.message().c_str()); | |
479 | if(ec) GenericProxyError(tr("cannot resolve upstream proxy"), ec.message()); | |
476 | 480 | else handler(*it); |
477 | 481 | } |
478 | 482 | |
480 | 484 | { |
481 | 485 | if(!ec) { |
482 | 486 | if(m_RequestURL.host.size() > 255) { |
483 | GenericProxyError("hostname too long", m_RequestURL.host.c_str()); | |
487 | GenericProxyError(tr("hostname too long"), m_RequestURL.host); | |
484 | 488 | return; |
485 | 489 | } |
486 | 490 | uint16_t port = m_RequestURL.port; |
507 | 511 | reqsize += host.size(); |
508 | 512 | m_socks_buf[++reqsize] = 0; |
509 | 513 | boost::asio::async_write(*m_proxysock, boost::asio::buffer(m_socks_buf, reqsize), boost::asio::transfer_all(), std::bind(&HTTPReqHandler::HandleSocksProxySendHandshake, this, std::placeholders::_1, std::placeholders::_2)); |
510 | } else GenericProxyError("cannot connect to upstream socks proxy", ec.message().c_str()); | |
514 | } else GenericProxyError(tr("cannot connect to upstream socks proxy"), ec.message()); | |
511 | 515 | } |
512 | 516 | |
513 | 517 | void HTTPReqHandler::HandleSocksProxySendHandshake(const boost::system::error_code & ec, std::size_t bytes_transferred) |
514 | 518 | { |
515 | 519 | LogPrint(eLogDebug, "HTTPProxy: upstream socks handshake sent"); |
516 | if(ec) GenericProxyError("Cannot negotiate with socks proxy", ec.message().c_str()); | |
520 | if(ec) GenericProxyError(tr("Cannot negotiate with socks proxy"), ec.message()); | |
517 | 521 | else m_proxysock->async_read_some(boost::asio::buffer(m_socks_buf, 8), std::bind(&HTTPReqHandler::HandleSocksProxyReply, this, std::placeholders::_1, std::placeholders::_2)); |
518 | 522 | } |
519 | 523 | |
555 | 559 | } |
556 | 560 | else |
557 | 561 | { |
558 | GenericProxyError("CONNECT error", "Failed to Connect"); | |
562 | GenericProxyError(tr("CONNECT error"), tr("Failed to Connect")); | |
559 | 563 | } |
560 | 564 | } |
561 | 565 | |
566 | 570 | m_send_buf = m_ClientResponse.to_string(); |
567 | 571 | boost::asio::async_write(*m_sock, boost::asio::buffer(m_send_buf), boost::asio::transfer_all(), [&] (const boost::system::error_code & ec, std::size_t transferred) |
568 | 572 | { |
569 | if(ec) GenericProxyError("socks proxy error", ec.message().c_str()); | |
573 | if(ec) GenericProxyError(tr("socks proxy error"), ec.message()); | |
570 | 574 | else HandoverToUpstreamProxy(); |
571 | 575 | }); |
572 | 576 | } else { |
574 | 578 | LogPrint(eLogDebug, "HTTPProxy: send ", m_send_buf.size(), " bytes"); |
575 | 579 | boost::asio::async_write(*m_proxysock, boost::asio::buffer(m_send_buf), boost::asio::transfer_all(), [&](const boost::system::error_code & ec, std::size_t transferred) |
576 | 580 | { |
577 | if(ec) GenericProxyError("failed to send request to upstream", ec.message().c_str()); | |
581 | if(ec) GenericProxyError(tr("failed to send request to upstream"), ec.message()); | |
578 | 582 | else HandoverToUpstreamProxy(); |
579 | 583 | }); |
580 | 584 | } |
592 | 596 | ss << "error code: "; |
593 | 597 | ss << (int) m_socks_buf[1]; |
594 | 598 | std::string msg = ss.str(); |
595 | GenericProxyError("Socks Proxy error", msg.c_str()); | |
596 | } | |
597 | } | |
598 | else GenericProxyError("No Reply From socks proxy", ec.message().c_str()); | |
599 | GenericProxyError(tr("socks proxy error"), msg); | |
600 | } | |
601 | } | |
602 | else GenericProxyError(tr("No Reply From socks proxy"), ec.message()); | |
599 | 603 | } |
600 | 604 | |
601 | 605 | void HTTPReqHandler::HandleUpstreamHTTPProxyConnect(const boost::system::error_code & ec) |
602 | 606 | { |
603 | 607 | if(!ec) { |
604 | 608 | LogPrint(eLogDebug, "HTTPProxy: connected to http upstream"); |
605 | GenericProxyError("cannot connect", "http out proxy not implemented"); | |
606 | } else GenericProxyError("cannot connect to upstream http proxy", ec.message().c_str()); | |
609 | GenericProxyError(tr("cannot connect"), tr("http out proxy not implemented")); | |
610 | } else GenericProxyError(tr("cannot connect to upstream http proxy"), ec.message()); | |
607 | 611 | } |
608 | 612 | |
609 | 613 | /* will be called after some data received from client */ |
636 | 640 | { |
637 | 641 | if (!stream) { |
638 | 642 | LogPrint (eLogError, "HTTPProxy: error when creating the stream, check the previous warnings for more info"); |
639 | GenericProxyError("Host is down", "Can't create connection to requested host, it may be down. Please try again later."); | |
643 | GenericProxyError(tr("Host is down"), tr("Can't create connection to requested host, it may be down. Please try again later.")); | |
640 | 644 | return; |
641 | 645 | } |
642 | 646 | if (Kill()) |
543 | 543 | offset += 8; // date |
544 | 544 | if (identity->Verify (buf, offset, buf + offset)) // signature |
545 | 545 | { |
546 | bool isPublic = true; | |
547 | if (params[I2CP_PARAM_DONT_PUBLISH_LEASESET] == "true") isPublic = false; | |
548 | 546 | if (!m_Destination) |
549 | 547 | { |
550 | 548 | m_Destination = m_Owner.IsSingleThread () ? |
551 | std::make_shared<I2CPDestination>(m_Owner.GetService (), shared_from_this (), identity, isPublic, params): | |
552 | std::make_shared<RunnableI2CPDestination>(shared_from_this (), identity, isPublic, params); | |
549 | std::make_shared<I2CPDestination>(m_Owner.GetService (), shared_from_this (), identity, true, params): | |
550 | std::make_shared<RunnableI2CPDestination>(shared_from_this (), identity, true, params); | |
553 | 551 | SendSessionStatusMessage (1); // created |
554 | 552 | LogPrint (eLogDebug, "I2CP: session ", m_SessionID, " created"); |
555 | 553 | m_Destination->Start (); |
61 | 61 | }; |
62 | 62 | |
63 | 63 | // params |
64 | const char I2CP_PARAM_DONT_PUBLISH_LEASESET[] = "i2cp.dontPublishLeaseSet"; | |
65 | 64 | const char I2CP_PARAM_MESSAGE_RELIABILITY[] = "i2cp.messageReliability"; |
66 | 65 | |
67 | 66 | class I2CPSession; |
604 | 604 | { |
605 | 605 | if (!ecode) |
606 | 606 | { |
607 | auto addr = (*it).endpoint ().address (); | |
607 | bool found = false; | |
608 | boost::asio::ip::tcp::endpoint ep; | |
609 | if (m_LocalAddress) | |
610 | { | |
611 | boost::asio::ip::tcp::resolver::iterator end; | |
612 | while (it != end) | |
613 | { | |
614 | ep = *it; | |
615 | if (!ep.address ().is_unspecified ()) | |
616 | { | |
617 | if (ep.address ().is_v4 ()) | |
618 | { | |
619 | if (m_LocalAddress->is_v4 ()) found = true; | |
620 | } | |
621 | else if (ep.address ().is_v6 ()) | |
622 | { | |
623 | if (i2p::util::net::IsYggdrasilAddress (ep.address ())) | |
624 | { | |
625 | if (i2p::util::net::IsYggdrasilAddress (*m_LocalAddress)) | |
626 | found = true; | |
627 | } | |
628 | else if (m_LocalAddress->is_v6 ()) | |
629 | found = true; | |
630 | } | |
631 | } | |
632 | if (found) break; | |
633 | it++; | |
634 | } | |
635 | } | |
636 | else | |
637 | { | |
638 | found = true; | |
639 | ep = *it; // first available | |
640 | } | |
641 | if (!found) | |
642 | { | |
643 | LogPrint (eLogError, "I2PTunnel: Unable to resolve to compatible address"); | |
644 | return; | |
645 | } | |
646 | ||
647 | auto addr = ep.address (); | |
608 | 648 | LogPrint (eLogInfo, "I2PTunnel: server tunnel ", (*it).host_name (), " has been resolved to ", addr); |
609 | 649 | m_Endpoint.address (addr); |
610 | 650 | Accept (); |
78 | 78 | if(!inbound && m_RemoteLeaseSet) |
79 | 79 | { |
80 | 80 | if(m_RemoteLeaseSet->IsExpired()) |
81 | { | |
82 | 81 | ResolveCurrentLeaseSet(); |
83 | } | |
84 | 82 | if(m_RemoteLeaseSet && !m_RemoteLeaseSet->IsExpired()) |
85 | 83 | { |
86 | 84 | // remote lease set is good |
87 | 85 | auto leases = m_RemoteLeaseSet->GetNonExpiredLeases(); |
88 | 86 | // pick lease |
89 | 87 | std::shared_ptr<i2p::data::RouterInfo> obep; |
90 | while(!obep && leases.size() > 0) { | |
88 | while(!obep && leases.size() > 0) | |
89 | { | |
91 | 90 | auto idx = rand() % leases.size(); |
92 | 91 | auto lease = leases[idx]; |
93 | 92 | obep = i2p::data::netdb.FindRouter(lease->tunnelGateway); |
94 | 93 | leases.erase(leases.begin()+idx); |
95 | 94 | } |
96 | if(obep) { | |
97 | path.push_back(obep->GetRouterIdentity()); | |
95 | if(obep) | |
96 | { | |
97 | path.Add (obep); | |
98 | 98 | LogPrint(eLogDebug, "Destination: found OBEP matching IBGW"); |
99 | 99 | } else |
100 | 100 | LogPrint(eLogWarning, "Destination: could not find proper IBGW for matched outbound tunnel"); |
1206 | 1206 | void SAMSingleSession::StopLocalDestination () |
1207 | 1207 | { |
1208 | 1208 | localDestination->Release (); |
1209 | // stop accepting new streams | |
1209 | 1210 | localDestination->StopAcceptingStreams (); |
1211 | // terminate existing streams | |
1212 | auto s = localDestination->GetStreamingDestination (); // TODO: take care about datagrams | |
1213 | if (s) s->Stop (); | |
1210 | 1214 | } |
1211 | 1215 | |
1212 | 1216 | void SAMMasterSession::Close () |
0 | package api | |
1 | ||
2 | /* | |
3 | * Copyright (c) 2013-2020, The PurpleI2P Project | |
4 | * | |
5 | * This file is part of Purple i2pd project and licensed under BSD3 | |
6 | * | |
7 | * See full license text in LICENSE file at top of project tree | |
8 | */ | |
9 | ||
10 | /* | |
11 | #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 | |
12 | #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++ | |
13 | */ | |
14 | import "C" |
0 | // See swig.org for more inteface options, | |
1 | // e.g. map std::string to Go string | |
2 | ||
3 | %{ | |
4 | #include "capi.h" | |
5 | %} | |
6 | ||
7 | %include "capi.h" |
0 | /* | |
1 | * Copyright (c) 2013-2020, The PurpleI2P Project | |
2 | * | |
3 | * This file is part of Purple i2pd project and licensed under BSD3 | |
4 | * | |
5 | * See full license text in LICENSE file at top of project tree | |
6 | */ | |
7 | ||
8 | #include "api.h" | |
9 | #include "capi.h" | |
10 | #include <stdio.h> | |
11 | #include <stdlib.h> | |
12 | #include <string.h> | |
13 | #include <assert.h> | |
14 | ||
15 | ||
16 | // Uses the example from: https://stackoverflow.com/a/9210560 | |
17 | // See also https://stackoverflow.com/questions/9210528/split-string-with-delimiters-in-c/9210560# | |
18 | // Does not handle consecutive delimiters, this is only for passing | |
19 | // lists of arguments by value to InitI2P from C_InitI2P | |
20 | char** str_split(char* a_str, const char a_delim) | |
21 | { | |
22 | char** result = 0; | |
23 | size_t count = 0; | |
24 | char* tmp = a_str; | |
25 | char* last_comma = 0; | |
26 | char delim[2]; | |
27 | delim[0] = a_delim; | |
28 | delim[1] = 0; | |
29 | ||
30 | /* Count how many elements will be extracted. */ | |
31 | while (*tmp) | |
32 | { | |
33 | if (a_delim == *tmp) | |
34 | { | |
35 | count++; | |
36 | last_comma = tmp; | |
37 | } | |
38 | tmp++; | |
39 | } | |
40 | ||
41 | /* Add space for trailing token. */ | |
42 | count += last_comma < (a_str + strlen(a_str) - 1); | |
43 | ||
44 | /* Add space for terminating null string so caller | |
45 | knows where the list of returned strings ends. */ | |
46 | count++; | |
47 | ||
48 | result = (char**) malloc(sizeof(char*) * count); | |
49 | ||
50 | if (result) | |
51 | { | |
52 | size_t idx = 0; | |
53 | char* token = strtok(a_str, delim); | |
54 | ||
55 | while (token) | |
56 | { | |
57 | assert(idx < count); | |
58 | *(result + idx++) = strdup(token); | |
59 | token = strtok(0, delim); | |
60 | } | |
61 | assert(idx == count - 1); | |
62 | *(result + idx) = 0; | |
63 | } | |
64 | ||
65 | return result; | |
66 | } | |
67 | ||
68 | ||
69 | #ifdef __cplusplus | |
70 | extern "C" { | |
71 | #endif | |
72 | ||
73 | void C_InitI2P (int argc, char argv[], const char * appName) | |
74 | { | |
75 | const char* delim = " "; | |
76 | char* vargs = strdup(argv); | |
77 | char** args = str_split(vargs, *delim); | |
78 | std::cout << argv; | |
79 | return i2p::api::InitI2P(argc, args, appName); | |
80 | } | |
81 | ||
82 | void C_TerminateI2P () | |
83 | { | |
84 | return i2p::api::TerminateI2P(); | |
85 | } | |
86 | ||
87 | void C_StartI2P () | |
88 | { | |
89 | std::shared_ptr<std::ostream> logStream; | |
90 | return i2p::api::StartI2P(logStream); | |
91 | } | |
92 | ||
93 | void C_StopI2P () | |
94 | { | |
95 | return i2p::api::StopI2P(); | |
96 | } | |
97 | ||
98 | void C_RunPeerTest () | |
99 | { | |
100 | return i2p::api::RunPeerTest(); | |
101 | } | |
102 | ||
103 | #ifdef __cplusplus | |
104 | } | |
105 | #endif |
0 | /* | |
1 | * Copyright (c) 2013-2020, The PurpleI2P Project | |
2 | * | |
3 | * This file is part of Purple i2pd project and licensed under BSD3 | |
4 | * | |
5 | * See full license text in LICENSE file at top of project tree | |
6 | */ | |
7 | ||
8 | #ifndef CAPI_H__ | |
9 | #define CAPI_H__ | |
10 | ||
11 | #ifdef __cplusplus | |
12 | extern "C" { | |
13 | #endif | |
14 | ||
15 | // initialization start and stop | |
16 | void C_InitI2P (int argc, char argv[], const char * appName); | |
17 | //void C_InitI2P (int argc, char** argv, const char * appName); | |
18 | void C_TerminateI2P (); | |
19 | void C_StartI2P (); | |
20 | // write system log to logStream, if not specified to <appName>.log in application's folder | |
21 | void C_StopI2P (); | |
22 | void C_RunPeerTest (); // should be called after UPnP | |
23 | ||
24 | #ifdef __cplusplus | |
25 | } | |
26 | #endif | |
27 | ||
28 | #endif |