Compare commits

...

151 Commits

Author SHA1 Message Date
R4SAS
a7a882582f [gha] test again with 20.04
Signed-off-by: R4SAS <r4sas@i2pmail.org>
2023-01-22 20:42:32 +03:00
R4SAS
250696a7b5 [gha] revert to 18.04, change static build options
Signed-off-by: R4SAS <r4sas@i2pmail.org>
2023-01-22 20:34:44 +03:00
R4SAS
af7905744e [gha] update ubuntu build base to 20.04
Signed-off-by: R4SAS <r4sas@i2pmail.org>
2023-01-22 19:12:01 +03:00
R4SAS
db2364e9aa [gha] add ubuntu static build
Signed-off-by: R4SAS <r4sas@i2pmail.org>
2023-01-22 19:03:49 +03:00
orignal
067fb45a25 exclude router from tunnel build for 2.5 minutes if doesn't reply too often 2023-01-21 19:40:43 -05:00
R4SAS
ac287a896c [websonsole] use a function to format the amount of tunnel traffic
Signed-off-by: R4SAS <r4sas@i2pmail.org>
2023-01-20 23:03:00 +00:00
R4SAS
8baf62eb2c [websonsole] fix int concatenation with char strings
Signed-off-by: R4SAS <r4sas@i2pmail.org>
2023-01-21 01:55:43 +03:00
R4SAS
e1ec79daf2 [webconsole] format transit tunnels with table
Signed-off-by: R4SAS <r4sas@i2pmail.org>
2023-01-21 00:51:07 +03:00
orignal
d4426118c5 exclude router from tunnel build for 2.5 minutes if declined 2023-01-20 15:34:40 -05:00
orignal
64fe56aa07 Merge pull request #1854 from Vort/server_forwards_style
use correct style for Server Forwards section
2023-01-20 11:58:10 -05:00
Vort
47eb49c34e use correct style for Server Forwards section 2023-01-20 18:52:56 +02:00
orignal
cd6d86c8c3 make sure that async CreateStream complete 2023-01-19 13:40:12 -05:00
R4SAS
84d4e074ce add loglevel checker, fix fields passing to translated string formatter
Signed-off-by: R4SAS <r4sas@i2pmail.org>
2023-01-19 08:33:03 +03:00
R4SAS
a0e71c4173 [i18n] update strings and translation file
Signed-off-by: R4SAS <r4sas@i2pmail.org>
2023-01-19 07:46:54 +03:00
R4SAS
533c8a8a55 [i18n] set decimal point based on language
Signed-off-by: R4SAS <r4sas@i2pmail.org>
2023-01-19 06:40:32 +03:00
R4SAS
a57ae4dc56 [i18n] add sweedish translation
Translation author: corona@mail.i2p

Signed-off-by: R4SAS <r4sas@i2pmail.org>
2023-01-19 05:53:29 +03:00
R4SAS
88dfe3ca4e [i18n] fix build on macos
Signed-off-by: R4SAS <r4sas@i2pmail.org>
2023-01-18 18:56:21 +03:00
R4SAS
d68c7f8ea7 [i18n] fix build on macos
Signed-off-by: R4SAS <r4sas@i2pmail.org>
2023-01-18 08:19:45 +03:00
R4SAS
e8ace998ba [i18n] add support of string formatting
Signed-off-by: R4SAS <r4sas@i2pmail.org>
2023-01-18 07:21:44 +03:00
orignal
e8be39af17 memrory pool for SSU2IncompleteMessage 2023-01-17 21:32:36 -05:00
orignal
7196db09d6 Merge pull request #1852 from freeacetone/openssl
HTTPProxy message stream correcting and comments
2023-01-17 07:48:05 -05:00
acetone
b290ee1aa0 Cfg example: verbose comments for Web Console auth and addresshelper for public proxy 2023-01-17 09:00:11 +03:00
acetone
d105ab11af Joining two strings to one and correct comments 2023-01-17 08:45:18 +03:00
orignal
bc888167a7 use linked list for out of sequence fragments 2023-01-16 21:40:23 -05:00
R4SAS
6ca6591c43 [make] set PREFIX from DESTDIR if it present
Signed-off-by: R4SAS <r4sas@i2pmail.org>
2023-01-17 01:30:31 +03:00
orignal
36cb707e47 Merge pull request #1849 from freeacetone/openssl
Addresshelper updating: referer check
2023-01-16 08:33:07 -05:00
acetone
013d5ff74f Addresshelper request validation by Refer-header 2023-01-16 16:16:16 +03:00
acetone
9af5a90757 Copyright year updated 2023-01-16 13:57:15 +03:00
acetone
d8b6f5438c log typo 2023-01-16 13:56:36 +03:00
acetone
10030a4e0d Addresshelper updating: referer check 2023-01-16 13:31:13 +03:00
orignal
993dc72ce6 use separate pointer to second fragment of incomplete message 2023-01-15 22:50:54 -05:00
orignal
324ace103b memoery pool for fragments 2023-01-14 17:05:09 -05:00
orignal
d530269e4f try to insert received msgid instead lookup 2023-01-13 19:23:26 -05:00
orignal
7146a4dbae check if session socket was closed before tunnels were built 2023-01-12 15:41:57 -05:00
R4SAS
f79900653b Update README.md 2023-01-12 15:49:02 +03:00
orignal
f172f44f32 Merge pull request #1833 from TomasGlgg/feature
Использование скользящего среднего для рассчета tunnel creation success rate
2023-01-11 15:36:37 -05:00
orignal
f34abe60fa 2.45.1 2023-01-11 13:52:20 -05:00
orignal
a3c305032a don't set Firewalled upon SessionCreated if ports mismatch 2023-01-08 08:25:23 -05:00
orignal
2921eaa055 differentiate symmetric and full cone NAT 2023-01-07 12:11:51 -05:00
orignal
1cc68ea402 differentiate symmetric and full cone NAT 2023-01-07 12:06:26 -05:00
orignal
c18e8f6c78 drop too long LeaseSet without processing 2023-01-07 10:54:49 -05:00
orignal
e59ca8420e temporary change back to C++17 2023-01-06 18:20:26 -05:00
orignal
a6f9a56e40 support C++20 2023-01-06 14:08:39 -05:00
R4SAS
4011502f5f [docker] put config in correct place
Signed-off-by: R4SAS <r4sas@i2pmail.org>
2023-01-06 03:59:03 +03:00
orignal
acd6af709e don't use netdb memory pool for local RouterInfo 2023-01-05 18:16:36 -05:00
orignal
55704ece3a drop duplicated I2NP messages 2023-01-05 15:33:41 -05:00
orignal
0d3ede56cb reject duplicated trnsit tunnel 2023-01-05 11:59:47 -05:00
R4SAS
06fae976b3 [style] update editorconfig
Signed-off-by: R4SAS <r4sas@i2pmail.org>
2023-01-04 17:10:06 +03:00
orignal
e95035c143 Merge pull request #1835 from kleenexi2p/openssl
Fix segfault when UPnP is enabled
2023-01-04 08:45:17 -05:00
kleenex
55be5c74f0 Fix segfault when UPnP is enabled
Added null check.

Signed-off-by: kleenex <kleenex@i2pmail.org>
2023-01-04 22:41:54 +09:00
R4SAS
a1c16e129d [rpm] fix date
Signed-off-by: R4SAS <r4sas@i2pmail.org>
2023-01-04 16:28:42 +03:00
R4SAS
321c7cb7cf [docs] note about doubled transit tunnels limit for floodfill mode
Signed-off-by: R4SAS <r4sas@i2pmail.org>
2023-01-03 21:42:41 +03:00
R4SAS
5f8d45154d [docs] update transit limit description in config file
Signed-off-by: R4SAS <r4sas@i2pmail.org>
2023-01-03 18:34:49 +00:00
R4SAS
503f522cc3 [style] clean trailing spaces and tabs
Signed-off-by: R4SAS <r4sas@i2pmail.org>
2023-01-03 21:25:19 +03:00
R4SAS
22179400c7 2.45.0
Signed-off-by: R4SAS <r4sas@i2pmail.org>
2023-01-03 18:16:34 +00:00
Tomas Globis
66f82cb43f Use moving average to calculate tunnel creation success rate 2023-01-03 03:07:07 +03:00
orignal
1df67bd43c 2.45.0 2023-01-02 18:59:13 -05:00
orignal
d5b03f214b double default number of transit tunnels for floodfill 2023-01-02 15:03:00 -05:00
R4SAS
cfb773351d [debian] update packaging files
Signed-off-by: R4SAS <r4sas@i2pmail.org>
2023-01-02 21:36:51 +03:00
orignal
6942c20879 don't select overloaded peer 2023-01-01 19:42:40 -05:00
orignal
7b341d5d30 update and show send queue size for transports 2023-01-01 16:03:53 -05:00
R4SAS
e93718456f [debian] update patch series list
Signed-off-by: R4SAS <r4sas@i2pmail.org>
2023-01-01 14:32:53 +03:00
R4SAS
af838196be [debian] remove #1210 patch
Signed-off-by: R4SAS <r4sas@i2pmail.org>
2023-01-01 14:30:04 +03:00
R4SAS
f8ba5b8c63 increase nofile limit in service
Signed-off-by: R4SAS <r4sas@i2pmail.org>
2023-01-01 14:29:58 +03:00
R4SAS
446bbf6b93 Merge pull request #1812 from barracuda156/tests
Add implementation of tests with CMake, plus a few minor fixes
2023-01-01 10:55:29 +00:00
R4SAS
2c19da9aa7 fix warnings about unused code
Signed-off-by: R4SAS <r4sas@i2pmail.org>
2023-01-01 13:48:41 +03:00
R4SAS
01fc21ffb9 [webconsole] remove newline break
Signed-off-by: R4SAS <r4sas@i2pmail.org>
2022-12-31 11:55:29 +00:00
orignal
3e3e2c41bd unlock mutex for good after wait 2022-12-30 22:12:49 -05:00
orignal
cb139226df ilimit umner of simultaneous tunnel build requests per pool 2022-12-30 18:06:47 -05:00
orignal
84d6028454 limit number of message to handle at the time 2022-12-29 17:26:57 -05:00
orignal
126dc0ebe0 inseer incoming session into sessions list 2022-12-28 16:47:50 -05:00
orignal
099d9d977f terminate duplcated session properly 2022-12-28 16:47:12 -05:00
orignal
5a167316cb don't filter yggdrasil address 2022-12-27 22:24:20 -05:00
orignal
85e31f84ec reset termination timer for new incoming connection 2022-12-27 22:07:26 -05:00
orignal
edb7a0e23c Refuse dulicated incoming pending session from same IP 2022-12-27 21:54:32 -05:00
R4SAS
f401ccf5dd Merge pull request #1829 from rex4539/typos
Fix typos
2022-12-27 09:21:18 +00:00
Dimitris Apostolou
9f9e8bfa14 Fix typos 2022-12-27 10:44:23 +02:00
R4SAS
61168d5c9d Merge pull request #1828 from hack3ric/openssl
Fix CheckAtomic.cmake
2022-12-26 20:24:08 +00:00
R4SAS
8500aaa26f [gha] fix MacOS build
More info: https://github.com/orgs/Homebrew/discussions/3895

Signed-off-by: R4SAS <r4sas@i2pmail.org>
2022-12-26 09:43:29 +03:00
R4SAS
445cff0025 add editorconfig
Signed-off-by: R4SAS <r4sas@i2pmail.org>
2022-12-26 09:42:35 +03:00
orignal
99356fd24d make sure that AsycReceive complete 2022-12-25 15:35:00 -05:00
orignal
5ef5f5a170 don't create encryption for new transit tunnel immediately 2022-12-25 09:36:16 -05:00
Eric Long
54b7d4ef32 Fix CheckAtomic.cmake 2022-12-24 22:04:57 +08:00
R4SAS
6376328c98 [http proxy] do not remove X-Requested-With for *HttpRequest (#1816)
Signed-off-by: R4SAS <r4sas@i2pmail.org>
2022-12-22 17:55:51 +00:00
orignal
b6f83dfe9f set error before status 2022-12-22 08:36:07 -05:00
orignal
3f728149ab bump default max number of transit tunnels to 5000 2022-12-21 18:27:42 -05:00
orignal
36501fe31e change network status back to OK if port in msg 7 matches 2022-12-21 18:14:19 -05:00
orignal
e4ddc883d2 drop future RouterInfo and LeaseSet upon receive 2022-12-20 18:41:19 -05:00
orignal
5ac01ddce8 delete routers with timestmep from future 2022-12-20 15:23:54 -05:00
orignal
d3656fcb3f don't publish ::1 2022-12-20 14:16:23 -05:00
orignal
d6c101d261 try to handle RelayIntro again is Alice's RouterInfo not found 2022-12-19 13:28:21 -05:00
orignal
eeea02d834 rounded to seconds timestamps 2022-12-19 12:56:19 -05:00
R4SAS
0e0cd555eb [i18n] odd space in localized name
Signed-off-by: R4SAS <r4sas@i2pmail.org>
2022-12-19 01:00:50 +03:00
R4SAS
5dfe483152 [i18n] add Czech translation
Signed-off-by: R4SAS <r4sas@i2pmail.org>
2022-12-19 00:56:41 +03:00
R4SAS
c210553a39 getting rid of old c99 array designators
Signed-off-by: R4SAS <r4sas@i2pmail.org>
2022-12-18 23:26:18 +03:00
R4SAS
a315e4ce62 more overrides
Signed-off-by: R4SAS <r4sas@i2pmail.org>
2022-12-18 22:53:14 +03:00
R4SAS
96cfd9acc2 fix override
Signed-off-by: R4SAS <r4sas@i2pmail.org>
2022-12-18 22:37:26 +03:00
R4SAS
d869bb25ed fix missing override warning
Signed-off-by: R4SAS <r4sas@i2pmail.org>
2022-12-18 22:32:23 +03:00
orignal
476e6aae35 don't iterate through addresses when it's not necessary 2022-12-16 19:57:19 -05:00
orignal
d30ee99cf1 fixed crash 2022-12-16 15:33:56 -05:00
orignal
84d9c8f1b8 access to RouterInfo's addresses by index 2022-12-16 15:12:30 -05:00
orignal
df737a65b2 SessionRequest must be min 88 bytes 2022-12-15 15:08:56 -05:00
orignal
c5230ca44b don't accept incoming session from invalid endpoint 2022-12-14 15:44:31 -05:00
R4SAS
3471e6fe16 use deadline_timer for bandwidth calculation timer
Signed-off-by: R4SAS <r4sas@i2pmail.org>
2022-12-13 23:25:16 +03:00
R4SAS
f1437feede SSU2: handle standard network errors more correctly
Signed-off-by: R4SAS <r4sas@i2pmail.org>
2022-12-13 22:04:21 +03:00
R4SAS
0d523bd2a6 use 15s average bw for transit limits check
Signed-off-by: R4SAS <r4sas@i2pmail.org>
2022-12-13 22:04:20 +03:00
R4SAS
8943200ffa calculate bandwidth every 1 and 15 seconds
Signed-off-by: R4SAS <r4sas@i2pmail.org>
2022-12-13 22:04:19 +03:00
orignal
a902d68669 enable compressible padding 2022-12-12 18:36:29 -05:00
orignal
f6ca7c19af allow zero length padding 2022-12-12 14:18:36 -05:00
orignal
3458665df8 don't delete unreachable routers if tunnel creation success rate is too low 2022-12-11 17:03:53 -05:00
orignal
8320987124 delete invalid sessions from peers 2022-12-10 19:09:37 -05:00
orignal
648b09d45f try to restart acceptors after termination of expired NTCP2 if no descriptors 2022-12-10 15:44:37 -05:00
R4SAS
857df5c734 prevent log spamming with no_descriptors error
Signed-off-by: R4SAS <r4sas@i2pmail.org>
2022-12-10 21:57:23 +03:00
orignal
737603e81b No descriptors error 2022-12-10 13:22:37 -05:00
orignal
53ca5dc67a memory pool for address vectors 2022-12-07 14:08:27 -05:00
orignal
8ad5696e50 don't send termination again if termination received block received 2022-12-06 21:40:33 -05:00
R4SAS
ef9d27e424 Merge pull request #1816 from jiigen/openssl
Remove "X-Requested-With"
2022-12-06 21:18:44 +00:00
orignal
2bb5ff7184 eliminate RTT self-reduction 2022-12-05 15:48:11 -05:00
orignal
753c7efde8 fixed build error 2022-12-04 21:12:48 -05:00
orignal
d0d0cd8445 use shared pointer for RouterInfo's addreses list 2022-12-04 20:04:06 -05:00
orignal
410d2c2fa9 use shared pointer for RouterInfo's addreses list 2022-12-04 19:21:51 -05:00
orignal
6a743f66e8 eliminate RTT self-reduction 2022-12-04 17:52:44 -05:00
orignal
709c451400 disable MemoryPool for RouterInfo's address 2022-12-04 17:49:59 -05:00
orignal
cb73c7c72e make sure that only *Mt method can be called for MemoryPoolMt 2022-12-04 17:43:29 -05:00
orignal
50abeea82a print endpoint if more than two fragments of SessionConfirmed 2022-12-04 17:08:38 -05:00
orignal
8db352b4d0 recognize symmetric NAT from peer test msg 7 2022-12-04 16:30:45 -05:00
orignal
6589bdf6b5 delete expired token right away 2022-12-03 15:05:27 -05:00
orignal
1ac171152a don't print error message if incomplete 2022-12-03 14:27:52 -05:00
orignal
629c718527 fixed races condition with pending outgoing sessions 2022-12-03 14:18:40 -05:00
Jigen
519c0fe81d Remove "X-Requested-With"
When any app uses Android’s WebView to load a web page, WebView attaches an extra header, named X-Requested-With, with the value set to the application ID. 
That include Lightning-I2P browser by R4SAS. 
Google doesn’t want to make it easy to get rid of the X-Requested-With header. However, there is a mechanism for replacing header information. This doesn’t allow a program to stop sending the X-Requested-With header.

More info on: https://www.stoutner.com/the-x-requested-with-header/
2022-12-02 16:09:23 +00:00
R4SAS
eb0ef80a17 Merge pull request #1813 from Vort/clang-build-fix3
Clang compatibility improved
2022-11-29 22:07:01 +00:00
Vort
9763499dbe Clang compatibility improved
x86_64-w64-windows-gnu platform should be detected as Windows, not Linux
2022-11-29 23:36:18 +02:00
orignal
949c38f5f0 reuse list of addresses 2022-11-28 19:16:21 -05:00
orignal
2a6f906177 print separate message for introducer timeout 2022-11-27 17:14:33 -05:00
orignal
b4c226f4b3 EdDSA blinding test 2022-11-27 10:54:17 -05:00
orignal
64c3282aae check minimal message size 2022-11-27 09:13:24 -05:00
orignal
f5d511ae0f Merge pull request #1811 from rex4539/typos
Fix typos
2022-11-26 14:25:31 -05:00
orignal
aa9a9ef18d memory pool for addreses 2022-11-25 19:18:53 -05:00
barracuda156
aa5e6400e4 CMakeLists: use a fix for atomics on macOS PPC, not PPC in general 2022-11-26 06:35:28 +08:00
barracuda156
61bcfebcc8 Add CMake support for tests 2022-11-26 06:32:56 +08:00
Dimitris Apostolou
73b9c0302b Fix typos 2022-11-25 22:37:52 +02:00
orignal
aead9db971 Read local RouterInfo from file 2022-11-25 14:53:40 -05:00
orignal
d8230644b2 localizations independant from ClientContext 2022-11-24 19:26:38 -05:00
orignal
97ef908b0c removed SSU and NTCP transports 2022-11-23 15:54:08 -05:00
orignal
fb8be32c28 removed SSU and NTCP transports 2022-11-23 15:45:00 -05:00
orignal
a298588943 SSU2 only introducers 2022-11-23 13:44:03 -05:00
orignal
ccfeca728e use only SSU2 for network status 2022-11-22 22:03:19 -05:00
orignal
7705423c42 remove SSU 2022-11-22 20:05:52 -05:00
orignal
379075c594 exluded SSU from transports 2022-11-22 19:29:20 -05:00
orignal
6a23153c0b don't read SSU addresses 2022-11-22 18:28:56 -05:00
orignal
9e02c99db5 check and limit LeaseSet's buffer size 2022-11-22 15:40:48 -05:00
118 changed files with 3241 additions and 5929 deletions

32
.editorconfig Normal file
View File

@@ -0,0 +1,32 @@
# editorconfig.org
root = true
[*]
# Unix style files
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[Makefile,Makefile.*]
indent_style = tab
indent_size = 4
[*.cmd]
indent_style = space
indent_size = 2
end_of_line = crlf
[*.{h,cpp}]
indent_style = tab
indent_size = 4
[*.rc]
indent_style = space
indent_size = 4
[*.{md,markdown}]
indent_style = space
indent_size = 2
trim_trailing_whitespace = false

View File

@@ -14,6 +14,7 @@ jobs:
- uses: actions/checkout@v2
- name: install packages
run: |
find /usr/local/bin -lname '*/Library/Frameworks/Python.framework/*' -delete
brew update
brew install boost miniupnpc openssl@1.1
- name: build application

View File

@@ -5,7 +5,7 @@ on: [push, pull_request]
jobs:
build-make:
name: Make with USE_UPNP=${{ matrix.with_upnp }}
runs-on: ubuntu-18.04
runs-on: ubuntu-20.04
strategy:
fail-fast: true
matrix:
@@ -14,14 +14,13 @@ jobs:
- uses: actions/checkout@v2
- name: install packages
run: |
sudo add-apt-repository ppa:mhier/libboost-latest
sudo apt-get update
sudo apt-get install build-essential libboost1.74-dev libminiupnpc-dev libssl-dev zlib1g-dev
sudo apt-get install build-essential libboost-all-dev libminiupnpc-dev libssl-dev zlib1g-dev
- name: build application
run: make USE_UPNP=${{ matrix.with_upnp }} -j3
build-cmake:
name: CMake with -DWITH_UPNP=${{ matrix.with_upnp }}
runs-on: ubuntu-18.04
runs-on: ubuntu-20.04
strategy:
fail-fast: true
matrix:
@@ -30,11 +29,26 @@ jobs:
- uses: actions/checkout@v2
- name: install packages
run: |
sudo add-apt-repository ppa:mhier/libboost-latest
sudo apt-get update
sudo apt-get install build-essential cmake libboost1.74-dev libminiupnpc-dev libssl-dev zlib1g-dev
sudo apt-get install build-essential cmake libboost-all-dev libminiupnpc-dev libssl-dev zlib1g-dev
- name: build application
run: |
cd build
cmake -DWITH_UPNP=${{ matrix.with_upnp }} .
make -j3
build-static:
name: Static build with UPnP
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- name: install packages
run: |
sudo apt-get update
sudo apt-get install build-essential libboost-all-dev libminiupnpc-dev libssl-dev zlib1g-dev
- name: build application
run: make DEBUG=no USE_STATIC=yes USE_UPNP=yes -j`nproc`
- name: Upload artifacts
uses: actions/upload-artifact@v2
with:
name: i2pd-amd64-static
path: i2pd

View File

@@ -1,6 +1,50 @@
# for this file format description,
# see https://github.com/olivierlacan/keep-a-changelog
## [2.45.1] - 2023-01-11
### Added
- Full Cone NAT status error
### Changed
- Drop duplicated I2NP messages in SSU2
- Set rejection code 30 if tunnel with id already exists
- Network status is always OK if peer test msg 5 received
### Fixed
- UPnP crash if SSU2 or NTCP2 is disabled
- Crash on termination for some platforms
## [2.45.0] - 2023-01-03
### Added
- Test for Symmetric NAT with peer test msgs 6 and 7
- Webconsole "No Descriptors" router error state
- 1 and 15 seconds bandwidth calculation for i2pcontrol
- Show non-zero send queue size for transports in web console
- Compressible padding for I2P addresses
- Localization to Czech
- Don't accept incoming session from invalid/reserved addresses for NTCP2 and SSU2
- Limit simultaneous tunnel build requests by 4 per pool
### Changed
- Removed SSU support
- Reduced bandwidth calculation interval from 60 to 15 seconds
- Increased default max transit tunnels number from 2500 to 5000 or 10000 for floodfill
- Transit tunnels limit is doubled if floodfill mode is enabled
- NTCP2 and SSU2 timestamps are rounded to seconds
- Drop RouterInfos and LeaseSets with timestamp from future
- Don't delete unreachable routers if tunnel creation success rate is too low
- Refuse duplicated incoming pending NTCP2 session from same IP
- Don't send SSU2 termination again if termination received block received
- Handle standard network error for SSU2 without throwing an exception
- Don't select overloaded peer for next tunnel
- Remove "X-Requested-With" in HTTP Proxy for non-AJAX requests
### Fixed
- File descriptors leak
- Random crash on AddressBook update
- Crash if incorrect LeaseSet size
- Spamming to log if no descriptors
- ::1 address in RouterInfo
- SSU2 network error handling (especially for Windows)
- Race condition with pending outgoing SSU2 sessions
- RTT self-reduction for long-live streams
## [2.44.0] - 2022-11-20
### Added
- SSL connection for server I2P tunnels
@@ -11,7 +55,7 @@
- SSU2 send and verify path challenge
- Configurable ssu2.mtu4 and ssu2.mtu6
### Changed
- SSU2 is enbaled and SSU is disabled by default
- SSU2 is enabled and SSU is disabled by default
- Separate network status and error
- Random selection between NTCP2 and SSU2 priority
- Added notbob.i2p to jump services

View File

@@ -47,6 +47,10 @@ else
LD_DEBUG = -s
endif
ifneq (, $(DESTDIR))
PREFIX = $(DESTDIR)
endif
ifneq (, $(findstring darwin, $(SYS)))
DAEMON_SRC += $(DAEMON_SRC_DIR)/UnixDaemon.cpp
ifeq ($(HOMEBREW),1)
@@ -54,15 +58,15 @@ ifneq (, $(findstring darwin, $(SYS)))
else
include Makefile.osx
endif
else ifneq (, $(findstring mingw, $(SYS))$(findstring windows-gnu, $(SYS))$(findstring cygwin, $(SYS)))
DAEMON_SRC += Win32/DaemonWin32.cpp Win32/Win32App.cpp Win32/Win32Service.cpp Win32/Win32NetState.cpp
include Makefile.mingw
else ifneq (, $(findstring linux, $(SYS))$(findstring gnu, $(SYS)))
DAEMON_SRC += $(DAEMON_SRC_DIR)/UnixDaemon.cpp
include Makefile.linux
else ifneq (, $(findstring freebsd, $(SYS))$(findstring openbsd, $(SYS)))
DAEMON_SRC += $(DAEMON_SRC_DIR)/UnixDaemon.cpp
include Makefile.bsd
else ifneq (, $(findstring mingw, $(SYS))$(findstring windows-gnu, $(SYS))$(findstring cygwin, $(SYS)))
DAEMON_SRC += Win32/DaemonWin32.cpp Win32/Win32App.cpp Win32/Win32Service.cpp Win32/Win32NetState.cpp
include Makefile.mingw
else # not supported
$(error Not supported platform)
endif

View File

@@ -20,7 +20,11 @@ else ifeq ($(shell expr match ${CXXVER} "4\.[8-9]"),3) # gcc 4.8 - 4.9
else ifeq ($(shell expr match ${CXXVER} "[5-6]"),1) # gcc 5 - 6
NEEDED_CXXFLAGS += -std=c++11
LDLIBS = -latomic
else ifeq ($(shell expr match ${CXXVER} "[1,7-9]"),1) # gcc >= 7
else ifeq ($(shell expr match ${CXXVER} "[7-9]"),1) # gcc 7 - 9
NEEDED_CXXFLAGS += -std=c++17
LDLIBS = -latomic
else ifeq ($(shell expr match ${CXXVER} "1[0-9]"),2) # gcc 10+
# NEEDED_CXXFLAGS += -std=c++20
NEEDED_CXXFLAGS += -std=c++17
LDLIBS = -latomic
else # not supported
@@ -29,34 +33,23 @@ endif
NEEDED_CXXFLAGS += -fPIC
ifeq ($(USE_STATIC),yes)
# NOTE: on glibc you will get this warning:
# Using 'getaddrinfo' in statically linked applications requires at runtime
# the shared libraries from the glibc version used for linking
LIBDIR := /usr/lib/$(SYS)
LDLIBS += $(LIBDIR)/libboost_system.a
LDLIBS += $(LIBDIR)/libboost_date_time.a
LDLIBS += $(LIBDIR)/libboost_filesystem.a
LDLIBS += $(LIBDIR)/libboost_program_options.a
LDLIBS += $(LIBDIR)/libssl.a
LDLIBS += $(LIBDIR)/libcrypto.a
LDLIBS += $(LIBDIR)/libz.a
ifeq ($(USE_UPNP),yes)
LDLIBS += $(LIBDIR)/libminiupnpc.a
endif
LDLIBS += -lpthread -ldl
else
LDLIBS += -lcrypto -lssl -lz -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread
ifeq ($(USE_UPNP),yes)
LDLIBS += -lminiupnpc
endif
endif
LDLIBS += -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lssl -lcrypto -lz
# UPNP Support (miniupnpc 1.5 and higher)
ifeq ($(USE_UPNP),yes)
LDLIBS += -lminiupnpc
NEEDED_CXXFLAGS += -DUSE_UPNP
endif
# NOTE: on glibc you will get this warning:
# Using 'getaddrinfo' in statically linked applications requires at runtime
# the shared libraries from the glibc version used for linking
ifeq ($(USE_STATIC),yes)
LDLIBS += -static -ldl -lpthread -static-libgcc -static-libstdc++
else
LDLIBS += -lpthread
endif
ifeq ($(USE_AESNI),yes)
ifneq (, $(findstring i386, $(SYS))$(findstring i686, $(SYS))$(findstring x86_64, $(SYS))) # only x86-based CPU supports that
NEEDED_CXXFLAGS += -D__AES__ -maes

View File

@@ -69,12 +69,12 @@ Build instructions:
**Supported systems:**
* GNU/Linux - [![Build on Ubuntu](https://github.com/PurpleI2P/i2pd/actions/workflows/build.yml/badge.svg)](https://github.com/PurpleI2P/i2pd/actions/workflows/build.yml)
* CentOS / Fedora / Mageia - [![Build Status](https://copr.fedorainfracloud.org/coprs/supervillain/i2pd/package/i2pd-git/status_image/last_build.png)](https://copr.fedorainfracloud.org/coprs/supervillain/i2pd/package/i2pd-git/)
* Alpine, ArchLinux, openSUSE, Gentoo, Debian, Ubuntu, etc.
* GNU/Linux (Debian, Ubuntu, etc) - [![Build on Ubuntu](https://github.com/PurpleI2P/i2pd/actions/workflows/build.yml/badge.svg)](https://github.com/PurpleI2P/i2pd/actions/workflows/build.yml)
* CentOS, Fedora, Mageia - [![Build Status](https://copr.fedorainfracloud.org/coprs/supervillain/i2pd/package/i2pd-git/status_image/last_build.png)](https://copr.fedorainfracloud.org/coprs/supervillain/i2pd/package/i2pd-git/)
* Alpine, ArchLinux, openSUSE, Gentoo, etc.
* Windows - [![Build on Windows](https://github.com/PurpleI2P/i2pd/actions/workflows/build-windows.yml/badge.svg)](https://github.com/PurpleI2P/i2pd/actions/workflows/build-windows.yml)
* Mac OS X - [![Build on OSX](https://github.com/PurpleI2P/i2pd/actions/workflows/build-osx.yml/badge.svg)](https://github.com/PurpleI2P/i2pd/actions/workflows/build-osx.yml)
* Docker image - [![Build Status](https://img.shields.io/docker/cloud/build/purplei2p/i2pd)](https://hub.docker.com/r/purplei2p/i2pd/builds/) [![Build containers](https://github.com/PurpleI2P/i2pd/actions/workflows/docker.yml/badge.svg)](https://github.com/PurpleI2P/i2pd/actions/workflows/docker.yml)
* Mac OS - [![Build on OSX](https://github.com/PurpleI2P/i2pd/actions/workflows/build-osx.yml/badge.svg)](https://github.com/PurpleI2P/i2pd/actions/workflows/build-osx.yml)
* Docker image - [![Build containers](https://github.com/PurpleI2P/i2pd/actions/workflows/docker.yml/badge.svg)](https://github.com/PurpleI2P/i2pd/actions/workflows/docker.yml)
* Snap - [![i2pd](https://snapcraft.io/i2pd/badge.svg)](https://snapcraft.io/i2pd) [![i2pd](https://snapcraft.io/i2pd/trending.svg?name=0)](https://snapcraft.io/i2pd)
* FreeBSD - [![Build on FreeBSD](https://github.com/PurpleI2P/i2pd/actions/workflows/build-freebsd.yml/badge.svg)](https://github.com/PurpleI2P/i2pd/actions/workflows/build-freebsd.yml)
* Android - [![Android CI](https://github.com/PurpleI2P/i2pd-android/actions/workflows/android.yml/badge.svg)](https://github.com/PurpleI2P/i2pd-android/actions/workflows/android.yml)

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2022, The PurpleI2P Project
* Copyright (c) 2013-2023, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -29,7 +29,7 @@ namespace util
setlocale(LC_CTYPE, "");
SetConsoleCP(1251);
SetConsoleOutputCP(1251);
setlocale(LC_ALL, "Russian");
//setlocale(LC_ALL, "Russian");
setlocale(LC_TIME, "C");
i2p::log::SetThrowFunction ([](const std::string& s)
@@ -61,7 +61,7 @@ namespace util
setlocale(LC_CTYPE, "");
SetConsoleCP(1251);
SetConsoleOutputCP(1251);
setlocale(LC_ALL, "Russian");
//setlocale(LC_ALL, "Russian");
setlocale(LC_TIME, "C");
#ifdef WIN32_APP
if (!i2p::win32::StartWin32App ()) return false;

View File

@@ -20,12 +20,17 @@ option(WITH_UPNP "Include support for UPnP client" OFF)
option(WITH_GIT_VERSION "Use git commit info as version" OFF)
option(WITH_ADDRSANITIZER "Build with address sanitizer unix only" OFF)
option(WITH_THREADSANITIZER "Build with thread sanitizer unix only" OFF)
option(BUILD_TESTING "Build tests" OFF)
IF(BUILD_TESTING)
enable_testing()
ENDIF()
# paths
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules")
set(CMAKE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/..")
#Handle paths nicely
# Handle paths nicely
include(GNUInstallDirs)
# architecture
@@ -171,14 +176,12 @@ if(WITH_THREADSANITIZER)
endif()
endif()
# Enable usage of STD's Atomic instead of Boost's on PowerPC
# For more information refer to https://github.com/PurpleI2P/i2pd/issues/1726#issuecomment-1306335111
if(ARCHITECTURE MATCHES "ppc")
# Use std::atomic instead of GCC builtins on macOS PowerPC:
# For more information refer to: https://github.com/PurpleI2P/i2pd/issues/1726#issuecomment-1306335111
if(APPLE AND CMAKE_OSX_ARCHITECTURES MATCHES "ppc")
add_definitions(-DBOOST_SP_USE_STD_ATOMIC)
endif()
# libraries
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
@@ -275,9 +278,13 @@ if(WITH_BINARY)
set(DL_LIB ${CMAKE_DL_LIBS})
endif()
target_link_libraries("${PROJECT_NAME}" libi2pd libi2pdclient libi2pdlang ${DL_LIB} ${Boost_LIBRARIES} OpenSSL::SSL OpenSSL::Crypto ${MINIUPNPC_LIBRARY} ZLIB::ZLIB Threads::Threads ${DL_LIB} ${CMAKE_REQUIRED_LIBRARIES})
target_link_libraries("${PROJECT_NAME}" libi2pd libi2pdclient libi2pdlang ${Boost_LIBRARIES} OpenSSL::SSL OpenSSL::Crypto ${MINIUPNPC_LIBRARY} ZLIB::ZLIB Threads::Threads ${DL_LIB} ${CMAKE_REQUIRED_LIBRARIES})
install(TARGETS "${PROJECT_NAME}" RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT Runtime)
set(APPS "\${CMAKE_INSTALL_PREFIX}/bin/${PROJECT_NAME}${CMAKE_EXECUTABLE_SUFFIX}")
set(DIRS "${Boost_LIBRARY_DIR};${OPENSSL_INCLUDE_DIR}/../bin;${ZLIB_INCLUDE_DIR}/../bin;/mingw32/bin")
endif()
if(BUILD_TESTING)
add_subdirectory(${CMAKE_SOURCE_DIR}/tests ${CMAKE_CURRENT_BINARY_DIR}/tests)
endif()

View File

@@ -1,18 +1,23 @@
# atomic builtins are required for threading support.
INCLUDE(CheckCXXSourceCompiles)
INCLUDE(CheckLibraryExists)
# Sometimes linking against libatomic is required for atomic ops, if
# the platform doesn't support lock-free atomics.
function(check_working_cxx_atomics varname)
set(OLD_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS})
set(CMAKE_REQUIRED_FLAGS "-std=c++11")
set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -std=c++11")
CHECK_CXX_SOURCE_COMPILES("
#include <atomic>
std::atomic<int> x;
std::atomic<short> y;
std::atomic<char> z;
int main() {
return x;
++z;
++y;
return ++x;
}
" ${varname})
set(CMAKE_REQUIRED_FLAGS ${OLD_CMAKE_REQUIRED_FLAGS})
@@ -27,6 +32,7 @@ function(check_working_cxx_atomics64 varname)
std::atomic<uint64_t> x (0);
int main() {
uint64_t i = x.load(std::memory_order_relaxed);
(void)i;
return 0;
}
" ${varname})
@@ -34,15 +40,16 @@ int main() {
endfunction(check_working_cxx_atomics64)
# This isn't necessary on MSVC, so avoid command-line switch annoyance
# by only running on GCC-like hosts.
if (LLVM_COMPILER_IS_GCC_COMPATIBLE)
# Check for (non-64-bit) atomic operations.
if(MSVC)
set(HAVE_CXX_ATOMICS_WITHOUT_LIB True)
else()
# First check if atomics work without the library.
check_working_cxx_atomics(HAVE_CXX_ATOMICS_WITHOUT_LIB)
# If not, check if the library exists, and atomics work with it.
if(NOT HAVE_CXX_ATOMICS_WITHOUT_LIB)
check_library_exists(atomic __atomic_fetch_add_4 "" HAVE_LIBATOMIC)
if( HAVE_LIBATOMIC )
if(HAVE_LIBATOMIC)
list(APPEND CMAKE_REQUIRED_LIBRARIES "atomic")
check_working_cxx_atomics(HAVE_CXX_ATOMICS_WITH_LIB)
if (NOT HAVE_CXX_ATOMICS_WITH_LIB)
@@ -58,20 +65,20 @@ endif()
if(MSVC)
set(HAVE_CXX_ATOMICS64_WITHOUT_LIB True)
else()
# First check if atomics work without the library.
check_working_cxx_atomics64(HAVE_CXX_ATOMICS64_WITHOUT_LIB)
endif()
# If not, check if the library exists, and atomics work with it.
if(NOT HAVE_CXX_ATOMICS64_WITHOUT_LIB)
check_library_exists(atomic __atomic_load_8 "" HAVE_CXX_LIBATOMICS64)
if(HAVE_CXX_LIBATOMICS64)
list(APPEND CMAKE_REQUIRED_LIBRARIES "atomic")
check_working_cxx_atomics64(HAVE_CXX_ATOMICS64_WITH_LIB)
if (NOT HAVE_CXX_ATOMICS64_WITH_LIB)
message(FATAL_ERROR "Host compiler must support std::atomic!")
# If not, check if the library exists, and atomics work with it.
if(NOT HAVE_CXX_ATOMICS64_WITHOUT_LIB)
check_library_exists(atomic __atomic_load_8 "" HAVE_CXX_LIBATOMICS64)
if(HAVE_CXX_LIBATOMICS64)
list(APPEND CMAKE_REQUIRED_LIBRARIES "atomic")
check_working_cxx_atomics64(HAVE_CXX_ATOMICS64_WITH_LIB)
if (NOT HAVE_CXX_ATOMICS64_WITH_LIB)
message(FATAL_ERROR "Host compiler must support 64-bit std::atomic!")
endif()
else()
message(FATAL_ERROR "Host compiler appears to require libatomic for 64-bit operations, but cannot find it.")
endif()
else()
message(FATAL_ERROR "Host compiler appears to require libatomic, but cannot find it.")
endif()
endif()
@@ -80,7 +87,6 @@ endif()
## assumes C++11 <atomic> works.
CHECK_CXX_SOURCE_COMPILES("
#ifdef _MSC_VER
#include <Intrin.h> /* Workaround for PR19898. */
#include <windows.h>
#endif
int main() {

View File

@@ -0,0 +1,55 @@
# - Try to find the CHECK libraries
# Once done this will define
#
# CHECK_FOUND - system has check
# CHECK_INCLUDE_DIRS - the check include directory
# CHECK_LIBRARIES - check library
#
# Copyright (c) 2007 Daniel Gollub <gollub@b1-systems.de>
# Copyright (c) 2007-2009 Bjoern Ricks <bjoern.ricks@gmail.com>
#
# Redistribution and use is allowed according to the terms of the New
# BSD license.
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
INCLUDE( FindPkgConfig )
IF ( Check_FIND_REQUIRED )
SET( _pkgconfig_REQUIRED "REQUIRED" )
ELSE( Check_FIND_REQUIRED )
SET( _pkgconfig_REQUIRED "" )
ENDIF ( Check_FIND_REQUIRED )
IF ( CHECK_MIN_VERSION )
PKG_SEARCH_MODULE( CHECK ${_pkgconfig_REQUIRED} check>=${CHECK_MIN_VERSION} )
ELSE ( CHECK_MIN_VERSION )
PKG_SEARCH_MODULE( CHECK ${_pkgconfig_REQUIRED} check )
ENDIF ( CHECK_MIN_VERSION )
# Look for CHECK include dir and libraries
IF( NOT CHECK_FOUND AND NOT PKG_CONFIG_FOUND )
FIND_PATH( CHECK_INCLUDE_DIRS check.h )
FIND_LIBRARY( CHECK_LIBRARIES NAMES check )
IF ( CHECK_INCLUDE_DIRS AND CHECK_LIBRARIES )
SET( CHECK_FOUND 1 )
IF ( NOT Check_FIND_QUIETLY )
MESSAGE ( STATUS "Found CHECK: ${CHECK_LIBRARIES}" )
ENDIF ( NOT Check_FIND_QUIETLY )
ELSE ( CHECK_INCLUDE_DIRS AND CHECK_LIBRARIES )
IF ( Check_FIND_REQUIRED )
MESSAGE( FATAL_ERROR "Could NOT find CHECK" )
ELSE ( Check_FIND_REQUIRED )
IF ( NOT Check_FIND_QUIETLY )
MESSAGE( STATUS "Could NOT find CHECK" )
ENDIF ( NOT Check_FIND_QUIETLY )
ENDIF ( Check_FIND_REQUIRED )
ENDIF ( CHECK_INCLUDE_DIRS AND CHECK_LIBRARIES )
ENDIF( NOT CHECK_FOUND AND NOT PKG_CONFIG_FOUND )
# Hide advanced variables from CMake GUIs
MARK_AS_ADVANCED( CHECK_INCLUDE_DIRS CHECK_LIBRARIES )

View File

@@ -60,8 +60,8 @@ RUN apk update \
RUN apk --no-cache add boost-filesystem boost-system boost-program_options boost-date_time boost-thread boost-iostreams openssl miniupnpc musl-utils libstdc++
# 3. Copy preconfigured config file and entrypoint
COPY i2pd-docker.conf "$I2PD_HOME/i2pd.conf"
RUN chown i2pd:nobody "$I2PD_HOME/i2pd.conf"
COPY i2pd-docker.conf "$DATA_DIR/i2pd.conf"
RUN chown i2pd:nobody "$DATA_DIR/i2pd.conf"
COPY entrypoint.sh /entrypoint.sh
RUN chmod a+x /entrypoint.sh

File diff suppressed because it is too large Load Diff

View File

@@ -122,11 +122,13 @@ port = 7070
## Path to web console, default "/"
# webroot = /
## Uncomment following lines to enable Web Console authentication
## You should not use Web Console via public networks without additional encryption.
## HTTP authentication is not encryption layer!
# auth = true
# user = i2pd
# pass = changeme
## Select webconsole language
## Currently supported english (default), afrikaans, armenian, chinese, french,
## Currently supported english (default), afrikaans, armenian, chinese, czech, french,
## german, italian, russian, spanish, turkmen, ukrainian and uzbek languages
# lang = english
@@ -139,6 +141,8 @@ port = 4444
## Optional keys file for proxy local destination
# keys = http-proxy-keys.dat
## Enable address helper for adding .i2p domains with "jump URLs" (default: true)
## You should disable this feature if your i2pd HTTP Proxy is public,
## because anyone could spoof the short domain via addresshelper and forward other users to phishing links
# addresshelper = true
## Address of a proxy server inside I2P, which is used to visit regular Internet
# outproxy = http://false.i2p
@@ -237,8 +241,9 @@ verify = true
# subscriptions = http://reg.i2p/hosts.txt,http://identiguy.i2p/hosts.txt,http://stats.i2p/cgi-bin/newhosts.txt,http://rus.i2p/hosts.txt
[limits]
## Maximum active transit sessions (default:2500)
# transittunnels = 2500
## Maximum active transit sessions (default: 5000)
## This value is doubled if floodfill mode is enabled!
# transittunnels = 5000
## Limit number of open file descriptors (0 - use system limit)
# openfiles = 0
## Maximum size of corefile in Kb (0 - use system limit)

View File

@@ -29,7 +29,7 @@ SendSIGKILL=yes
#TimeoutStopSec=10m
# If you have problems with hanging i2pd, you can try increase this
LimitNOFILE=4096
LimitNOFILE=8192
# To enable write of coredump uncomment this
#LimitCORE=infinity

View File

@@ -1,7 +1,7 @@
%define git_hash %(git rev-parse HEAD | cut -c -7)
Name: i2pd-git
Version: 2.44.0
Version: 2.45.1
Release: git%{git_hash}%{?dist}
Summary: I2P router written in C++
Conflicts: i2pd
@@ -158,6 +158,12 @@ getent passwd i2pd >/dev/null || \
%changelog
* Wed Jan 11 2023 orignal <orignal@i2pmail.org> - 2.45.1
- update to 2.45.1
* Tue Jan 3 2023 orignal <orignal@i2pmail.org> - 2.45.0
- update to 2.45.0
* Sun Nov 20 2022 orignal <orignal@i2pmail.org> - 2.44.0
- update to 2.44.0

View File

@@ -1,5 +1,5 @@
Name: i2pd
Version: 2.44.0
Version: 2.45.1
Release: 1%{?dist}
Summary: I2P router written in C++
Conflicts: i2pd-git
@@ -155,6 +155,12 @@ getent passwd i2pd >/dev/null || \
%changelog
* Wed Jan 11 2023 orignal <orignal@i2pmail.org> - 2.45.1
- update to 2.45.1
* Tue Jan 3 2023 orignal <orignal@i2pmail.org> - 2.45.0
- update to 2.45.0
* Sun Nov 20 2022 orignal <orignal@i2pmail.org> - 2.44.0
- update to 2.44.0

View File

@@ -1,293 +1,293 @@
/*
* Copyright (c) 2021-2022, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
* See full license text in LICENSE file at top of project tree
*
******************************************************************
*
* This is style sheet for webconsole, with @media selectors for adaptive
* view on desktop and mobile devices, respecting preferred user's color
* scheme used in system/browser.
*
* Minified copy of that style sheet is bundled inside i2pd sources.
*/
:root {
--main-bg-color: #fafafa;
--main-text-color: #103456;
--main-link-color: #894c84;
--main-link-hover-color: #fafafa;
}
@media (prefers-color-scheme: dark) {
:root {
--main-bg-color: #242424;
--main-text-color: #17ab5c;
--main-link-color: #bf64b7;
--main-link-hover-color: #000000;
}
}
body {
font: 100%/1.5em sans-serif;
margin: 0;
padding: 1.5em;
background: var(--main-bg-color);
color: var(--main-text-color);
}
a, .slide label {
text-decoration: none;
color: var(--main-link-color);
}
a:hover, .slide label:hover, button[type=submit]:hover {
color: var(--main-link-hover-color);
background: var(--main-link-color);
}
a.button {
appearance: button;
text-decoration: none;
padding: 0 5px;
border: 1px solid var(--main-link-color);
}
.header {
font-size: 2.5em;
text-align: center;
margin: 1em 0;
color: var(--main-link-color);
}
.wrapper {
margin: 0 auto;
padding: 1em;
max-width: 64em;
}
.menu {
display: block;
float: left;
overflow: hidden;
padding: 4px;
max-width: 12em;
white-space: nowrap;
text-overflow: ellipsis;
}
.listitem {
display: block;
font-family: monospace;
font-size: 1.2em;
white-space: nowrap;
}
.tableitem {
font-family: monospace;
font-size: 1.2em;
white-space: nowrap;
}
.content {
float: left;
font-size: 1em;
margin-left: 2em;
padding: 4px;
max-width: 50em;
overflow: auto;
}
.tunnel.established {
color: #56B734;
}
.tunnel.expiring {
color: #D3AE3F;
}
.tunnel.failed {
color: #D33F3F;
}
.tunnel.building {
color: #434343;
}
caption {
font-size: 1.5em;
text-align: center;
color: var(--main-link-color);
}
table {
display: table;
border-collapse: collapse;
text-align: center;
}
table.extaddr {
text-align: left;
}
table.services {
width: 100%;
}
textarea {
background-color: var(--main-bg-color);
color: var(--main-text-color);
word-break: break-all;
}
.streamdest {
width: 120px;
max-width: 240px;
overflow: hidden;
text-overflow: ellipsis;
}
.slide div.slidecontent, .slide [type="checkbox"] {
display: none;
}
.slide [type="checkbox"]:checked ~ div.slidecontent {
display: block;
margin-top: 0;
padding: 0;
}
.disabled {
color: #D33F3F;
}
.enabled {
color: #56B734;
}
button[type=submit] {
background-color: transparent;
color: var(--main-link-color);
text-decoration: none;
padding: 5px;
border: 1px solid var(--main-link-color);
font-size: 14px;
}
input, select, select option {
background-color: var(--main-bg-color);
color: var(--main-link-color);
padding: 5px;
border: 1px solid var(--main-link-color);
font-size: 14px;
}
input:focus, select:focus, select option:focus {
outline: none;
}
input[type=number]::-webkit-inner-spin-button {
-webkit-appearance: none;
}
@media screen and (max-width: 1150px) { /* adaptive style */
.wrapper {
max-width: 58em;
}
.content {
max-width: 40em;
}
}
@media screen and (max-width: 980px) {
body {
font: 100%/1.2em sans-serif;
padding: 1.2em 0 0 0;
}
.menu {
width: 100%;
max-width: unset;
display: block;
float: none;
position: unset;
font-size: 16px;
text-align: center;
}
.menu a, .commands a {
display: inline-block;
padding: 4px;
}
.content {
float: none;
margin-left: unset;
margin-top: 16px;
max-width: 100%;
width: 100%;
text-align: center;
}
a, .slide label {
display: block;
}
.header {
margin: unset;
font-size: 1.5em;
}
small {
display: block
}
a.button {
appearance: button;
text-decoration: none;
margin-top: 10px;
padding: 6px;
border: 2px solid var(--main-link-color);
border-radius: 5px;
width: -webkit-fill-available;
}
input, select {
width: 35%;
text-align: center;
padding: 5px;
border: 2px solid var(--main-link-color);
border-radius: 5px;
font-size: 18px;
}
table.extaddr {
margin: auto;
text-align: unset;
}
textarea {
width: -webkit-fill-available;
height: auto;
padding: 5px;
border: 2px solid var(--main-link-color);
border-radius: 5px;
font-size: 12px;
}
button[type=submit] {
padding: 5px 15px;
background: transparent;
border: 2px solid var(--main-link-color);
cursor: pointer;
-webkit-border-radius: 5px;
border-radius: 5px;
position: relative;
height: 36px;
display: -webkit-inline-box;
margin-top: 10px;
}
}
/*
* Copyright (c) 2021-2023, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
* See full license text in LICENSE file at top of project tree
*
******************************************************************
*
* This is style sheet for webconsole, with @media selectors for adaptive
* view on desktop and mobile devices, respecting preferred user's color
* scheme used in system/browser.
*
* Minified copy of that style sheet is bundled inside i2pd sources.
*/
:root {
--main-bg-color: #fafafa;
--main-text-color: #103456;
--main-link-color: #894c84;
--main-link-hover-color: #fafafa;
}
@media (prefers-color-scheme: dark) {
:root {
--main-bg-color: #242424;
--main-text-color: #17ab5c;
--main-link-color: #bf64b7;
--main-link-hover-color: #000000;
}
}
body {
font: 100%/1.5em sans-serif;
margin: 0;
padding: 1.5em;
background: var(--main-bg-color);
color: var(--main-text-color);
}
a, .slide label {
text-decoration: none;
color: var(--main-link-color);
}
a:hover, a.button.selected, .slide label:hover, button[type=submit]:hover {
color: var(--main-link-hover-color);
background: var(--main-link-color);
}
a.button {
appearance: button;
text-decoration: none;
padding: 0 5px;
border: 1px solid var(--main-link-color);
}
.header {
font-size: 2.5em;
text-align: center;
margin: 1em 0;
color: var(--main-link-color);
}
.wrapper {
margin: 0 auto;
padding: 1em;
max-width: 64em;
}
.menu {
display: block;
float: left;
overflow: hidden;
padding: 4px;
max-width: 12em;
white-space: nowrap;
text-overflow: ellipsis;
}
.listitem {
display: block;
font-family: monospace;
font-size: 1.2em;
white-space: nowrap;
}
.tableitem {
font-family: monospace;
font-size: 1.2em;
white-space: nowrap;
}
.content {
float: left;
font-size: 1em;
margin-left: 2em;
padding: 4px;
max-width: 50em;
overflow: auto;
}
.tunnel.established {
color: #56B734;
}
.tunnel.expiring {
color: #D3AE3F;
}
.tunnel.failed {
color: #D33F3F;
}
.tunnel.building {
color: #434343;
}
caption {
font-size: 1.5em;
text-align: center;
color: var(--main-link-color);
}
table {
display: table;
border-collapse: collapse;
text-align: center;
}
table.extaddr {
text-align: left;
}
table.services {
width: 100%;
}
textarea {
background-color: var(--main-bg-color);
color: var(--main-text-color);
word-break: break-all;
}
.streamdest {
width: 120px;
max-width: 240px;
overflow: hidden;
text-overflow: ellipsis;
}
.slide div.slidecontent, .slide [type="checkbox"] {
display: none;
}
.slide [type="checkbox"]:checked ~ div.slidecontent {
display: block;
margin-top: 0;
padding: 0;
}
.disabled {
color: #D33F3F;
}
.enabled {
color: #56B734;
}
button[type=submit] {
background-color: transparent;
color: var(--main-link-color);
text-decoration: none;
padding: 5px;
border: 1px solid var(--main-link-color);
font-size: 14px;
}
input, select, select option {
background-color: var(--main-bg-color);
color: var(--main-link-color);
padding: 5px;
border: 1px solid var(--main-link-color);
font-size: 14px;
}
input:focus, select:focus, select option:focus {
outline: none;
}
input[type=number]::-webkit-inner-spin-button {
-webkit-appearance: none;
}
@media screen and (max-width: 1150px) { /* adaptive style */
.wrapper {
max-width: 58em;
}
.content {
max-width: 40em;
}
}
@media screen and (max-width: 980px) {
body {
font: 100%/1.2em sans-serif;
padding: 1.2em 0 0 0;
}
.menu {
width: 100%;
max-width: unset;
display: block;
float: none;
position: unset;
font-size: 16px;
text-align: center;
}
.menu a, .commands a {
display: inline-block;
padding: 4px;
}
.content {
float: none;
margin-left: unset;
margin-top: 16px;
max-width: 100%;
width: 100%;
text-align: center;
}
a, .slide label {
display: block;
}
.header {
margin: unset;
font-size: 1.5em;
}
small {
display: block
}
a.button {
appearance: button;
text-decoration: none;
margin-top: 10px;
padding: 6px;
border: 2px solid var(--main-link-color);
border-radius: 5px;
width: -webkit-fill-available;
}
input, select {
width: 35%;
text-align: center;
padding: 5px;
border: 2px solid var(--main-link-color);
border-radius: 5px;
font-size: 18px;
}
table.extaddr {
margin: auto;
text-align: unset;
}
textarea {
width: -webkit-fill-available;
height: auto;
padding: 5px;
border: 2px solid var(--main-link-color);
border-radius: 5px;
font-size: 12px;
}
button[type=submit] {
padding: 5px 15px;
background: transparent;
border: 2px solid var(--main-link-color);
cursor: pointer;
-webkit-border-radius: 5px;
border-radius: 5px;
position: relative;
height: 36px;
display: -webkit-inline-box;
margin-top: 10px;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2022, The PurpleI2P Project
* Copyright (c) 2013-2023, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -165,20 +165,21 @@ namespace util
i2p::transport::InitTransports ();
bool transit; i2p::config::GetOption("notransit", transit);
i2p::context.SetAcceptsTunnels (!transit);
uint16_t transitTunnels; i2p::config::GetOption("limits.transittunnels", transitTunnels);
SetMaxNumTransitTunnels (transitTunnels);
bool isFloodfill; i2p::config::GetOption("floodfill", isFloodfill);
if (isFloodfill) {
if (isFloodfill)
{
LogPrint(eLogInfo, "Daemon: Router configured as floodfill");
i2p::context.SetFloodfill (true);
}
else
{
i2p::context.SetFloodfill (false);
}
bool transit; i2p::config::GetOption("notransit", transit);
i2p::context.SetAcceptsTunnels (!transit);
uint16_t transitTunnels; i2p::config::GetOption("limits.transittunnels", transitTunnels);
if (isFloodfill && i2p::config::IsDefault ("limits.transittunnels"))
transitTunnels *= 2; // double default number of transit tunnels for floodfill
SetMaxNumTransitTunnels (transitTunnels);
/* this section also honors 'floodfill' flag, if set above */
std::string bandwidth; i2p::config::GetOption("bandwidth", bandwidth);
@@ -298,15 +299,14 @@ namespace util
bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2);
bool ssu2; i2p::config::GetOption("ssu2.enabled", ssu2);
bool ssu; i2p::config::GetOption("ssu", ssu);
bool checkInReserved; i2p::config::GetOption("reservedrange", checkInReserved);
LogPrint(eLogInfo, "Daemon: Starting Transports");
if(!ssu) LogPrint(eLogInfo, "Daemon: SSU disabled");
if(!ssu2) LogPrint(eLogInfo, "Daemon: SSU2 disabled");
if(!ntcp2) LogPrint(eLogInfo, "Daemon: NTCP2 disabled");
i2p::transport::transports.SetCheckReserved(checkInReserved);
i2p::transport::transports.Start(ntcp2, ssu, ssu2);
if (i2p::transport::transports.IsBoundSSU() || i2p::transport::transports.IsBoundSSU2() || i2p::transport::transports.IsBoundNTCP2())
i2p::transport::transports.Start(ntcp2, ssu2);
if (i2p::transport::transports.IsBoundSSU2() || i2p::transport::transports.IsBoundNTCP2())
LogPrint(eLogInfo, "Daemon: Transports started");
else
{

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2022, The PurpleI2P Project
* Copyright (c) 2013-2023, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -87,8 +87,6 @@ namespace http {
const char HTTP_COMMAND_GET_REG_STRING[] = "get_reg_string";
const char HTTP_COMMAND_SETLANGUAGE[] = "setlanguage";
const char HTTP_COMMAND_RELOAD_CSS[] = "reload_css";
const char HTTP_PARAM_SAM_SESSION_ID[] = "id";
const char HTTP_PARAM_ADDRESS[] = "address";
static std::string ConvertTime (uint64_t time)
{
@@ -105,18 +103,18 @@ namespace http {
int num;
if ((num = seconds / 86400) > 0) {
s << num << " " << tr("day", "days", num) << ", ";
s << ntr("%d day", "%d days", num, num) << ", ";
seconds -= num * 86400;
}
if ((num = seconds / 3600) > 0) {
s << num << " " << tr("hour", "hours", num) << ", ";
s << ntr("%d hour", "%d hours", num, num) << ", ";
seconds -= num * 3600;
}
if ((num = seconds / 60) > 0) {
s << num << " " << tr("minute", "minutes", num) << ", ";
s << ntr("%d minute", "%d minutes", num, num) << ", ";
seconds -= num * 60;
}
s << seconds << " " << tr("second", "seconds", seconds);
s << ntr("%d second", "%d seconds", seconds, seconds);
}
static void ShowTraffic (std::stringstream& s, uint64_t bytes)
@@ -124,11 +122,11 @@ namespace http {
s << std::fixed << std::setprecision(2);
auto numKBytes = (double) bytes / 1024;
if (numKBytes < 1024)
s << numKBytes << " " << tr(/* tr: Kibibit */ "KiB");
s << tr(/* tr: Kibibyte */ "%.2f KiB", numKBytes);
else if (numKBytes < 1024 * 1024)
s << numKBytes / 1024 << " " << tr(/* tr: Mebibit */ "MiB");
s << tr(/* tr: Mebibyte */ "%.2f MiB", numKBytes / 1024);
else
s << numKBytes / 1024 / 1024 << " " << tr(/* tr: Gibibit */ "GiB");
s << tr(/* tr: Gibibyte */ "%.2f GiB", numKBytes / 1024 / 1024);
}
static void ShowTunnelDetails (std::stringstream& s, enum i2p::tunnel::TunnelState eState, bool explr, int bytes)
@@ -152,7 +150,8 @@ namespace http {
else stateText = tr("unknown");
s << "<span class=\"tunnel " << state << "\"> " << stateText << ((explr) ? " (" + tr("exploratory") + ")" : "") << "</span>, ";
s << " " << (int) (bytes / 1024) << "&nbsp;" << tr(/* tr: Kibibit */ "KiB") << "\r\n";
ShowTraffic(s, bytes);
s << "\r\n";
}
static void SetLogLevel (const std::string& level)
@@ -200,7 +199,7 @@ namespace http {
if (i2p::context.AcceptsTunnels () || i2p::tunnel::tunnels.CountTransitTunnels())
s << " <a href=\"" << webroot << "?page=" << HTTP_PAGE_TRANSIT_TUNNELS << "\">" << tr("Transit Tunnels") << "</a><br>\r\n";
s <<
" <a href=\"" << webroot << "?page=" << HTTP_PAGE_TRANSPORTS << "\">" << tr ("Transports") << "</a><br>\r\n"
" <a href=\"" << webroot << "?page=" << HTTP_PAGE_TRANSPORTS << "\">" << tr("Transports") << "</a><br>\r\n"
" <a href=\"" << webroot << "?page=" << HTTP_PAGE_I2P_TUNNELS << "\">" << tr("I2P tunnels") << "</a><br>\r\n";
if (i2p::client::context.GetSAMBridge ())
s << " <a href=\"" << webroot << "?page=" << HTTP_PAGE_SAM_SESSIONS << "\">" << tr("SAM sessions") << "</a><br>\r\n";
@@ -236,7 +235,6 @@ namespace http {
}
if (error != eRouterErrorNone)
{
s << "<br>";
switch (error)
{
case eRouterErrorClockSkew:
@@ -248,6 +246,12 @@ namespace http {
case eRouterErrorSymmetricNAT:
s << " - " << tr("Symmetric NAT");
break;
case eRouterErrorFullConeNAT:
s << " - " << tr("Full cone NAT");
break;
case eRouterErrorNoDescriptors:
s << " - " << tr("No Descriptors");
break;
default: ;
}
}
@@ -287,58 +291,61 @@ namespace http {
s << "<b>" << tr("Tunnel creation success rate") << ":</b> " << i2p::tunnel::tunnels.GetTunnelCreationSuccessRate () << "%<br>\r\n";
s << "<b>" << tr("Received") << ":</b> ";
ShowTraffic (s, i2p::transport::transports.GetTotalReceivedBytes ());
s << " (" << (double) i2p::transport::transports.GetInBandwidth () / 1024 << " " << tr(/* tr: Kibibit/s */ "KiB/s") << ")<br>\r\n";
s << " (" << tr(/* tr: Kibibyte/s */ "%.2f KiB/s", (double) i2p::transport::transports.GetInBandwidth15s () / 1024) << ")<br>\r\n";
s << "<b>" << tr("Sent") << ":</b> ";
ShowTraffic (s, i2p::transport::transports.GetTotalSentBytes ());
s << " (" << (double) i2p::transport::transports.GetOutBandwidth () / 1024 << " " << tr(/* tr: Kibibit/s */ "KiB/s") << ")<br>\r\n";
s << " (" << tr(/* tr: Kibibyte/s */ "%.2f KiB/s", (double) i2p::transport::transports.GetOutBandwidth15s () / 1024) << ")<br>\r\n";
s << "<b>" << tr("Transit") << ":</b> ";
ShowTraffic (s, i2p::transport::transports.GetTotalTransitTransmittedBytes ());
s << " (" << (double) i2p::transport::transports.GetTransitBandwidth () / 1024 << " " << tr(/* tr: Kibibit/s */ "KiB/s") << ")<br>\r\n";
s << " (" << tr(/* tr: Kibibyte/s */ "%.2f KiB/s", (double) i2p::transport::transports.GetTransitBandwidth15s () / 1024) << ")<br>\r\n";
s << "<b>" << tr("Data path") << ":</b> " << i2p::fs::GetUTF8DataDir() << "<br>\r\n";
s << "<div class='slide'>";
if ((outputFormat == OutputFormatEnum::forWebConsole) || !includeHiddenContent) {
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";
}
if (includeHiddenContent) {
if (includeHiddenContent)
{
s << "<b>" << tr("Router Ident") << ":</b> " << i2p::context.GetRouterInfo().GetIdentHashBase64() << "<br>\r\n";
if (!i2p::context.GetRouterInfo().GetProperty("family").empty())
s << "<b>" << tr("Router Family") << ":</b> " << i2p::context.GetRouterInfo().GetProperty("family") << "<br>\r\n";
s << "<b>" << tr("Router Caps") << ":</b> " << i2p::context.GetRouterInfo().GetProperty("caps") << "<br>\r\n";
s << "<b>" << tr("Version") << ":</b> " VERSION "<br>\r\n";
s << "<b>"<< tr("Our external address") << ":</b>" << "<br>\r\n<table class=\"extaddr\"><tbody>\r\n";
for (const auto& address : i2p::context.GetRouterInfo().GetAddresses())
auto addresses = i2p::context.GetRouterInfo().GetAddresses ();
if (addresses)
{
s << "<tr>\r\n<td>";
switch (address->transportStyle)
for (const auto& address : *addresses)
{
case i2p::data::RouterInfo::eTransportNTCP:
s << "NTCP2";
break;
case i2p::data::RouterInfo::eTransportSSU:
s << "SSU";
break;
case i2p::data::RouterInfo::eTransportSSU2:
s << "SSU2";
break;
default:
s << tr("Unknown");
}
if (address->IsV6 ())
{
if (address->IsV4 ()) s << "v4";
s << "v6";
}
s << "</td>\r\n";
if (address->published)
s << "<td>" << address->host.to_string() << ":" << address->port << "</td>\r\n";
else
{
s << "<td>" << tr("supported");
if (address->port)
s << " :" << address->port;
if (!address) continue;
s << "<tr>\r\n<td>";
switch (address->transportStyle)
{
case i2p::data::RouterInfo::eTransportNTCP2:
s << "NTCP2";
break;
case i2p::data::RouterInfo::eTransportSSU2:
s << "SSU2";
break;
default:
s << tr("Unknown");
}
if (address->IsV6 ())
{
if (address->IsV4 ()) s << "v4";
s << "v6";
}
s << "</td>\r\n";
if (address->published)
s << "<td>" << address->host.to_string() << ":" << address->port << "</td>\r\n";
else
{
s << "<td>" << tr(/* tr: Shown when router doesn't publish itself and have "Firewalled" state */ "supported");
if (address->port)
s << " :" << address->port;
s << "</td>\r\n";
}
s << "</tr>\r\n";
}
s << "</tr>\r\n";
}
s << "</tbody></table>\r\n";
}
@@ -460,7 +467,7 @@ namespace http {
}
s << "&#8658; " << it->GetTunnelID () << ":me";
if (it->LatencyIsKnown())
s << " ( " << it->GetMeanLatency() << tr(/* tr: Milliseconds */ "ms") << " )";
s << " ( " << tr(/* tr: Milliseconds */ "%dms", it->GetMeanLatency()) << " )";
ShowTunnelDetails(s, it->GetState (), false, it->GetNumReceivedBytes ());
s << "</div>\r\n";
}
@@ -480,22 +487,26 @@ namespace http {
);
}
if (it->LatencyIsKnown())
s << " ( " << it->GetMeanLatency() << tr("ms") << " )";
s << " ( " << tr("%dms", it->GetMeanLatency()) << " )";
ShowTunnelDetails(s, it->GetState (), false, it->GetNumSentBytes ());
s << "</div>\r\n";
}
}
s << "<br>\r\n";
s << "<b>" << tr("Tags") << "</b><br>\r\n" << tr("Incoming") << ": <i>" << dest->GetNumIncomingTags () << "</i><br>\r\n";
s << "<b>" << tr("Tags") << "</b><br>\r\n"
<< tr("Incoming") << ": <i>" << dest->GetNumIncomingTags () << "</i><br>\r\n";
if (!dest->GetSessions ().empty ()) {
std::stringstream tmp_s; uint32_t out_tags = 0;
for (const auto& it: dest->GetSessions ()) {
tmp_s << "<tr><td>" << i2p::client::context.GetAddressBook ().ToAddress(it.first) << "</td><td>" << it.second->GetNumOutgoingTags () << "</td></tr>\r\n";
out_tags += it.second->GetNumOutgoingTags ();
}
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"
<< "<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";
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"
<< "<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";
} else
s << tr("Outgoing") << ": <i>0</i><br>\r\n";
s << "<br>\r\n";
@@ -510,8 +521,11 @@ namespace http {
tmp_s << "<tr><td>" << i2p::client::context.GetAddressBook ().ToAddress(it.second->GetDestination ()) << "</td><td>" << it.second->GetState () << "</td></tr>\r\n";
ecies_sessions++;
}
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"
<< "<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";
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"
<< "<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";
} else
s << tr("Tags sessions") << ": <i>0</i><br>\r\n";
s << "<br>\r\n";
@@ -636,7 +650,7 @@ namespace http {
}
else if (!i2p::context.IsFloodfill ())
{
s << "<b>" << tr("LeaseSets") << ":</b> " << tr("not floodfill") << ".<br>\r\n";
s << "<b>" << tr("LeaseSets") << ":</b> " << tr(/* Message on LeaseSets page */ "floodfill mode is disabled") << ".<br>\r\n";
}
else
{
@@ -665,7 +679,7 @@ namespace http {
}
s << "&#8658; " << it->GetTunnelID () << ":me";
if (it->LatencyIsKnown())
s << " ( " << it->GetMeanLatency() << tr("ms") << " )";
s << " ( " << tr("%dms", it->GetMeanLatency()) << " )";
ShowTunnelDetails(s, it->GetState (), (it->GetTunnelPool () == ExplPool), it->GetNumReceivedBytes ());
s << "</div>\r\n";
}
@@ -685,7 +699,7 @@ namespace http {
);
}
if (it->LatencyIsKnown())
s << " ( " << it->GetMeanLatency() << tr("ms") << " )";
s << " ( " << tr("%dms", it->GetMeanLatency()) << " )";
ShowTunnelDetails(s, it->GetState (), (it->GetTunnelPool () == ExplPool), it->GetNumSentBytes ());
s << "</div>\r\n";
}
@@ -723,19 +737,20 @@ namespace http {
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";
auto loglevel = i2p::log::Logger().GetLogLevel();
s << "<b>" << tr("Logging level") << "</b><br>\r\n";
s << " <a class=\"button\" href=\"" << webroot << "?cmd=" << HTTP_COMMAND_LOGLEVEL << "&level=none&token=" << token << "\"> none </a> \r\n";
s << " <a class=\"button\" href=\"" << webroot << "?cmd=" << HTTP_COMMAND_LOGLEVEL << "&level=error&token=" << token << "\"> error </a> \r\n";
s << " <a class=\"button\" href=\"" << webroot << "?cmd=" << HTTP_COMMAND_LOGLEVEL << "&level=warn&token=" << token << "\"> warn </a> \r\n";
s << " <a class=\"button\" href=\"" << webroot << "?cmd=" << HTTP_COMMAND_LOGLEVEL << "&level=info&token=" << token << "\"> info </a> \r\n";
s << " <a class=\"button\" href=\"" << webroot << "?cmd=" << HTTP_COMMAND_LOGLEVEL << "&level=debug&token=" << token << "\"> debug </a><br>\r\n<br>\r\n";
s << " <a class=\"button" << (loglevel == eLogNone ? " selected" : "") << "\" href=\"" << webroot << "?cmd=" << HTTP_COMMAND_LOGLEVEL << "&level=none&token=" << token << "\"> none </a> \r\n";
s << " <a class=\"button" << (loglevel == eLogError ? " selected" : "") << "\" href=\"" << webroot << "?cmd=" << HTTP_COMMAND_LOGLEVEL << "&level=error&token=" << token << "\"> error </a> \r\n";
s << " <a class=\"button" << (loglevel == eLogWarning ? " selected" : "") << "\" href=\"" << webroot << "?cmd=" << HTTP_COMMAND_LOGLEVEL << "&level=warn&token=" << token << "\"> warn </a> \r\n";
s << " <a class=\"button" << (loglevel == eLogInfo ? " selected" : "") << "\" href=\"" << webroot << "?cmd=" << HTTP_COMMAND_LOGLEVEL << "&level=info&token=" << token << "\"> info </a> \r\n";
s << " <a class=\"button" << (loglevel == eLogDebug ? " selected" : "") << "\" href=\"" << webroot << "?cmd=" << HTTP_COMMAND_LOGLEVEL << "&level=debug&token=" << token << "\"> debug </a><br>\r\n<br>\r\n";
uint16_t maxTunnels = GetMaxNumTransitTunnels ();
s << "<b>" << tr("Transit tunnels limit") << "</b><br>\r\n";
s << "<form method=\"get\" action=\"" << webroot << "\">\r\n";
s << " <input type=\"hidden\" name=\"cmd\" value=\"" << HTTP_COMMAND_LIMITTRANSIT << "\">\r\n";
s << " <input type=\"hidden\" name=\"token\" value=\"" << token << "\">\r\n";
s << " <input type=\"number\" min=\"0\" max=\"65535\" name=\"limit\" value=\"" << maxTunnels << "\">\r\n";
s << " <input type=\"number\" min=\"0\" max=\"" << TRANSIT_TUNNELS_LIMIT <<"\" name=\"limit\" value=\"" << maxTunnels << "\">\r\n";
s << " <button type=\"submit\">" << tr("Change") << "</button>\r\n";
s << "</form>\r\n<br>\r\n";
@@ -765,23 +780,24 @@ namespace http {
{
if (i2p::tunnel::tunnels.CountTransitTunnels())
{
s << "<b>" << tr("Transit Tunnels") << ":</b><br>\r\n<div class=\"list\">\r\n";
s << "<b>" << tr("Transit Tunnels") << ":</b><br>\r\n";
s << "<table><thead><th>&#8658;</th><th>ID</th><th>&#8658;</th><th>" << tr("Amount") << "</th></thead><tbody class=\"tableitem\">";
for (const auto& it: i2p::tunnel::tunnels.GetTransitTunnels ())
{
s << "<div class=\"listitem\">\r\n";
if (std::dynamic_pointer_cast<i2p::tunnel::TransitTunnelGateway>(it))
s << it->GetTunnelID () << " &#8658; ";
s << "<tr><td></td><td>" << it->GetTunnelID () << "</td><td>&#8658;</td><td>";
else if (std::dynamic_pointer_cast<i2p::tunnel::TransitTunnelEndpoint>(it))
s << " &#8658; " << it->GetTunnelID ();
s << "<tr><td>&#8658;</td><td>" << it->GetTunnelID () << "</td><td></td><td>";
else
s << " &#8658; " << it->GetTunnelID () << " &#8658; ";
s << " " << it->GetNumTransmittedBytes () << "</div>\r\n";
s << "<tr><td>&#8658;</td><td>" << it->GetTunnelID () << "</td><td>&#8658;</td><td>";
ShowTraffic(s, it->GetNumTransmittedBytes ());
s << "</td></tr>\r\n";
}
s << "</div>\r\n";
s << "</tbody></table>\r\n";
}
else
{
s << "<b>" << tr("Transit Tunnels") << ":</b> " << tr("no transit tunnels currently built") << ".<br>\r\n";
s << "<b>" << tr("Transit Tunnels") << ":</b> " << tr(/* Message on transit tunnels page */ "no transit tunnels currently built") << ".<br>\r\n";
}
}
@@ -802,6 +818,8 @@ namespace http {
tmp_s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]";
if (it.second->GetRelayTag ())
tmp_s << " [itag:" << it.second->GetRelayTag () << "]";
if (it.second->GetSendQueueSize () > 0)
tmp_s << " [queue:" << it.second->GetSendQueueSize () << "]";
tmp_s << "</div>\r\n" << std::endl;
cnt++;
}
@@ -815,6 +833,8 @@ namespace http {
tmp_s6 << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]";
if (it.second->GetRelayTag ())
tmp_s6 << " [itag:" << it.second->GetRelayTag () << "]";
if (it.second->GetSendQueueSize () > 0)
tmp_s6 << " [queue:" << it.second->GetSendQueueSize () << "]";
tmp_s6 << "</div>\r\n" << std::endl;
cnt6++;
}
@@ -843,46 +863,6 @@ namespace http {
if (!sessions.empty ())
ShowTransportSessions (s, sessions, "NTCP2");
}
auto ssuServer = i2p::transport::transports.GetSSUServer ();
if (ssuServer)
{
auto sessions = ssuServer->GetSessions ();
if (!sessions.empty ())
{
s << "<div class='slide'><label for='slide_ssu'><b>SSU</b> ( " << (int) sessions.size() << " )</label>\r\n<input type=\"checkbox\" id=\"slide_ssu\" />\r\n<div class=\"slidecontent list\">";
for (const auto& it: sessions)
{
s << "<div class=\"listitem\">\r\n";
auto endpoint = it.second->GetRemoteEndpoint ();
if (it.second->IsOutgoing ()) s << " &#8658; ";
s << endpoint.address ().to_string () << ":" << endpoint.port ();
if (!it.second->IsOutgoing ()) s << " &#8658; ";
s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]";
if (it.second->GetRelayTag ())
s << " [itag:" << it.second->GetRelayTag () << "]";
s << "</div>\r\n" << std::endl;
}
s << "</div>\r\n</div>\r\n";
}
auto sessions6 = ssuServer->GetSessionsV6 ();
if (!sessions6.empty ())
{
s << "<div class='slide'><label for='slide_ssuv6'><b>SSUv6</b> ( " << (int) sessions6.size() << " )</label>\r\n<input type=\"checkbox\" id=\"slide_ssuv6\" />\r\n<div class=\"slidecontent list\">";
for (const auto& it: sessions6)
{
s << "<div class=\"listitem\">\r\n";
auto endpoint = it.second->GetRemoteEndpoint ();
if (it.second->IsOutgoing ()) s << " &#8658; ";
s << "[" << endpoint.address ().to_string () << "]:" << endpoint.port ();
if (!it.second->IsOutgoing ()) s << " &#8658; ";
s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]";
if (it.second->GetRelayTag ())
s << " [itag:" << it.second->GetRelayTag () << "]";
s << "</div>\r\n" << std::endl;
}
s << "</div>\r\n</div>\r\n";
}
}
auto ssu2Server = i2p::transport::transports.GetSSU2Server ();
if (ssu2Server)
{
@@ -914,7 +894,7 @@ namespace http {
s << "</div>\r\n";
}
else
s << "<b>" << tr("SAM sessions") << ":</b> " << tr("no sessions currently running") << ".<br>\r\n";
s << "<b>" << tr("SAM sessions") << ":</b> " << tr(/* Message on SAM sessions page */ "no sessions currently running") << ".<br>\r\n";
}
void ShowSAMSession (std::stringstream& s, const std::string& id)
@@ -1023,7 +1003,7 @@ namespace http {
for (auto& it: serverForwards)
{
auto& ident = it.second->GetLocalDestination ()->GetIdentHash();
s << "<a href=\"" << webroot << "?page=" << HTTP_PAGE_LOCAL_DESTINATION << "&b32=" << ident.ToBase32 () << "\">";
s << "<div class=\"listitem\"><a href=\"" << webroot << "?page=" << HTTP_PAGE_LOCAL_DESTINATION << "&b32=" << ident.ToBase32 () << "\">";
s << it.second->GetName () << "</a> &#8656; ";
s << i2p::client::context.GetAddressBook ().ToAddress(ident);
s << "</div>\r\n"<< std::endl;
@@ -1233,7 +1213,7 @@ namespace http {
url.parse_query(params);
std::string webroot; i2p::config::GetOption("http.webroot", webroot);
std::string redirect = "5; url=" + webroot + "?page=commands";
std::string redirect = std::to_string(COMMAND_REDIRECT_TIMEOUT) + "; url=" + webroot + "?page=commands";
std::string token = params["token"];
if (token.empty () || m_Tokens.find (std::stoi (token)) == m_Tokens.end ())
@@ -1307,20 +1287,20 @@ namespace http {
s << "<b>" << tr("ERROR") << "</b>:&nbsp;" << tr("StreamID can't be null") << "<br>\r\n<br>\r\n";
s << "<a href=\"" << webroot << "?page=local_destination&b32=" << b32 << "\">" << tr("Return to destination page") << "</a><br>\r\n";
s << "<p>" << tr("You will be redirected in 5 seconds") << "</b>";
redirect = "5; url=" + webroot + "?page=local_destination&b32=" + b32;
s << "<p>" << tr("You will be redirected in %d seconds", COMMAND_REDIRECT_TIMEOUT) << "</b>";
redirect = std::to_string(COMMAND_REDIRECT_TIMEOUT) + "; url=" + webroot + "?page=local_destination&b32=" + b32;
res.add_header("Refresh", redirect.c_str());
return;
}
else if (cmd == HTTP_COMMAND_LIMITTRANSIT)
{
uint32_t limit = std::stoul(params["limit"], nullptr);
if (limit > 0 && limit <= 65535)
if (limit > 0 && limit <= TRANSIT_TUNNELS_LIMIT)
SetMaxNumTransitTunnels (limit);
else {
s << "<b>" << tr("ERROR") << "</b>:&nbsp;" << tr("Transit tunnels count must not exceed 65535") << "\r\n<br>\r\n<br>\r\n";
s << "<b>" << tr("ERROR") << "</b>:&nbsp;" << tr("Transit tunnels count must not exceed %d", TRANSIT_TUNNELS_LIMIT) << "\r\n<br>\r\n<br>\r\n";
s << "<a href=\"" << webroot << "?page=commands\">" << tr("Back to commands list") << "</a>\r\n<br>\r\n";
s << "<p>" << tr("You will be redirected in 5 seconds") << "</b>";
s << "<p>" << tr("You will be redirected in %d seconds", COMMAND_REDIRECT_TIMEOUT) << "</b>";
res.add_header("Refresh", redirect.c_str());
return;
}
@@ -1395,7 +1375,7 @@ namespace http {
s << "<b>" << tr("SUCCESS") << "</b>:&nbsp;" << tr("Command accepted") << "<br><br>\r\n";
s << "<a href=\"" << webroot << "?page=commands\">" << tr("Back to commands list") << "</a><br>\r\n";
s << "<p>" << tr("You will be redirected in 5 seconds") << "</b>";
s << "<p>" << tr("You will be redirected in %d seconds", COMMAND_REDIRECT_TIMEOUT) << "</b>";
res.add_header("Refresh", redirect.c_str());
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2020, The PurpleI2P Project
* Copyright (c) 2013-2023, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -24,6 +24,8 @@ namespace http
{
const size_t HTTP_CONNECTION_BUFFER_SIZE = 8192;
const int TOKEN_EXPIRATION_TIMEOUT = 30; // in seconds
const int COMMAND_REDIRECT_TIMEOUT = 5; // in seconds
const int TRANSIT_TUNNELS_LIMIT = 65535;
class HTTPConnection: public std::enable_shared_from_this<HTTPConnection>
{

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2022, The PurpleI2P Project
* Copyright (c) 2013-2023, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -38,7 +38,7 @@ namespace http
"@media (prefers-color-scheme: dark) { :root { --main-bg-color: #242424; --main-text-color: #17ab5c; --main-link-color: #bf64b7; --main-link-hover-color: #000000; } }\r\n"
"body { font: 100%/1.5em sans-serif; margin: 0; padding: 1.5em; background: var(--main-bg-color); color: var(--main-text-color); }\r\n"
"a, .slide label { text-decoration: none; color: var(--main-link-color); }\r\n"
"a:hover, .slide label:hover, button[type=submit]:hover { color: var(--main-link-hover-color); background: var(--main-link-color); }\r\n"
"a:hover, a.button.selected, .slide label:hover, button[type=submit]:hover { color: var(--main-link-hover-color); background: var(--main-link-color); }\r\n"
"a.button { appearance: button; text-decoration: none; padding: 0 5px; border: 1px solid var(--main-link-color); }\r\n"
".header { font-size: 2.5em; text-align: center; margin: 1em 0; color: var(--main-link-color); }\r\n"
".wrapper { margin: 0 auto; padding: 1em; max-width: 64em; }\r\n"

View File

@@ -33,7 +33,9 @@ namespace client
m_RouterInfoHandlers["i2p.router.netdb.knownpeers"] = &I2PControlHandlers::NetDbKnownPeersHandler;
m_RouterInfoHandlers["i2p.router.netdb.activepeers"] = &I2PControlHandlers::NetDbActivePeersHandler;
m_RouterInfoHandlers["i2p.router.net.bw.inbound.1s"] = &I2PControlHandlers::InboundBandwidth1S;
m_RouterInfoHandlers["i2p.router.net.bw.inbound.15s"] = &I2PControlHandlers::InboundBandwidth15S;
m_RouterInfoHandlers["i2p.router.net.bw.outbound.1s"] = &I2PControlHandlers::OutboundBandwidth1S;
m_RouterInfoHandlers["i2p.router.net.bw.outbound.15s"] = &I2PControlHandlers::OutboundBandwidth15S;
m_RouterInfoHandlers["i2p.router.net.status"] = &I2PControlHandlers::NetStatusHandler;
m_RouterInfoHandlers["i2p.router.net.tunnels.participating"] = &I2PControlHandlers::TunnelsParticipatingHandler;
m_RouterInfoHandlers["i2p.router.net.tunnels.successrate"] = &I2PControlHandlers::TunnelsSuccessRateHandler;
@@ -153,12 +155,24 @@ namespace client
InsertParam (results, "i2p.router.net.bw.inbound.1s", bw);
}
void I2PControlHandlers::InboundBandwidth15S (std::ostringstream& results)
{
double bw = i2p::transport::transports.GetInBandwidth15s ();
InsertParam (results, "i2p.router.net.bw.inbound.15s", bw);
}
void I2PControlHandlers::OutboundBandwidth1S (std::ostringstream& results)
{
double bw = i2p::transport::transports.GetOutBandwidth ();
InsertParam (results, "i2p.router.net.bw.outbound.1s", bw);
}
void I2PControlHandlers::OutboundBandwidth15S (std::ostringstream& results)
{
double bw = i2p::transport::transports.GetOutBandwidth15s ();
InsertParam (results, "i2p.router.net.bw.outbound.15s", bw);
}
void I2PControlHandlers::NetTotalReceivedBytes (std::ostringstream& results)
{
InsertParam (results, "i2p.router.net.total.received.bytes", (double)i2p::transport::transports.GetTotalReceivedBytes ());

View File

@@ -50,7 +50,9 @@ namespace client
void TunnelsParticipatingHandler (std::ostringstream& results);
void TunnelsSuccessRateHandler (std::ostringstream& results);
void InboundBandwidth1S (std::ostringstream& results);
void InboundBandwidth15S (std::ostringstream& results);
void OutboundBandwidth1S (std::ostringstream& results);
void OutboundBandwidth15S (std::ostringstream& results);
void NetTotalReceivedBytes (std::ostringstream& results);
void NetTotalSentBytes (std::ostringstream& results);

View File

@@ -159,10 +159,11 @@ namespace transport
void UPnP::PortMapping ()
{
const auto& a = context.GetRouterInfo().GetAddresses();
for (const auto& address : a)
auto a = context.GetRouterInfo().GetAddresses();
if (!a) return;
for (const auto& address : *a)
{
if (!address->host.is_v6 () && address->port)
if (address && !address->host.is_v6 () && address->port)
TryPortMapping (address);
}
m_Timer.expires_from_now (boost::posix_time::minutes(20)); // every 20 minutes
@@ -210,10 +211,11 @@ namespace transport
void UPnP::CloseMapping ()
{
const auto& a = context.GetRouterInfo().GetAddresses();
for (const auto& address : a)
auto a = context.GetRouterInfo().GetAddresses();
if (!a) return;
for (const auto& address : *a)
{
if (!address->host.is_v6 () && address->port)
if (address && !address->host.is_v6 () && address->port)
CloseMapping (address);
}
}
@@ -248,10 +250,10 @@ namespace transport
{
switch (address->transportStyle)
{
case i2p::data::RouterInfo::eTransportNTCP:
case i2p::data::RouterInfo::eTransportNTCP2:
return "TCP";
break;
case i2p::data::RouterInfo::eTransportSSU:
case i2p::data::RouterInfo::eTransportSSU2:
default:
return "UDP";
}

17
debian/changelog vendored
View File

@@ -1,3 +1,20 @@
i2pd (2.45.1-1) unstable; urgency=medium
* updated to version 2.45.1/0.9.57
-- orignal <orignal@i2pmail.org> Wed, 11 Jan 2023 19:00:00 +0000
i2pd (2.45.0-1) unstable; urgency=high
* updated to version 2.45.0/0.9.57
* compat level 12
* standards version 4.3.0
* increased nofile limit in service and init.d to 8192
* added conffiles
* removed #1210 patch
-- r4sas <r4sas@i2pmail.org> Tue, 3 Jan 2023 18:00:00 +0000
i2pd (2.44.0-1) unstable; urgency=medium
* updated to version 2.44.0/0.9.56

2
debian/compat vendored
View File

@@ -1 +1 @@
9
12

2
debian/conffiles vendored Normal file
View File

@@ -0,0 +1,2 @@
/etc/i2pd/i2pd.conf
/etc/i2pd/tunnels.conf

4
debian/control vendored
View File

@@ -2,8 +2,8 @@ Source: i2pd
Section: net
Priority: optional
Maintainer: r4sas <r4sas@i2pmail.org>
Build-Depends: debhelper (>= 9), dpkg-dev (>= 1.17.2~), gcc (>= 4.7) | clang (>= 3.3), libboost-system-dev (>= 1.46), libboost-date-time-dev (>= 1.46), libboost-filesystem-dev (>= 1.46), libboost-program-options-dev (>= 1.46), libminiupnpc-dev, libssl-dev, zlib1g-dev
Standards-Version: 3.9.8
Build-Depends: debhelper (>= 12~), libboost-system-dev (>= 1.46), libboost-date-time-dev (>= 1.46), libboost-filesystem-dev (>= 1.46), libboost-program-options-dev (>= 1.46), libminiupnpc-dev, libssl-dev, zlib1g-dev
Standards-Version: 4.3.0
Homepage: http://i2pd.website/
Vcs-Git: git://github.com/PurpleI2P/i2pd.git
Vcs-Browser: https://github.com/PurpleI2P/i2pd

6
debian/copyright vendored
View File

@@ -3,18 +3,18 @@ Upstream-Name: i2pd
Source: https://github.com/PurpleI2P
Files: *
Copyright: 2013-2020 PurpleI2P
Copyright: 2013-2023 PurpleI2P
License: BSD-3-clause
Files: debian/*
Copyright: 2013-2015 Kill Your TV <killyourtv@i2pmail.org>
2014-2016 hagen <hagen@i2pmail.org>
2016-2020 R4SAS <r4sas@i2pmail.org>
2016-2023 R4SAS <r4sas@i2pmail.org>
2017-2020 Yangfl <mmyangfl@gmail.com>
License: GPL-2+
License: BSD-3-clause
Copyright (c) 2013-2017, The PurpleI2P Project
Copyright (c) 2013-2023, The PurpleI2P Project
.
All rights reserved.
.

2
debian/i2pd.default vendored
View File

@@ -8,4 +8,4 @@ I2PD_ENABLED="yes"
DAEMON_OPTS=""
# If you have problems with hunging i2pd, you can try enable this
ulimit -n 4096
ulimit -n 8192

1
debian/i2pd.install vendored
View File

@@ -1,7 +1,6 @@
i2pd usr/sbin/
contrib/i2pd.conf etc/i2pd/
contrib/tunnels.conf etc/i2pd/
contrib/subscriptions.txt etc/i2pd/
contrib/certificates/ usr/share/i2pd/
contrib/tunnels.d/README etc/i2pd/tunnels.conf.d/
contrib/apparmor/usr.sbin.i2pd etc/apparmor.d

3
debian/i2pd.links vendored
View File

@@ -1,5 +1,4 @@
etc/i2pd/i2pd.conf var/lib/i2pd/i2pd.conf
etc/i2pd/i2pd.conf var/lib/i2pd/i2pd.conf
etc/i2pd/tunnels.conf var/lib/i2pd/tunnels.conf
etc/i2pd/subscriptions.txt var/lib/i2pd/subscriptions.txt
etc/i2pd/tunnels.conf.d var/lib/i2pd/tunnels.d
usr/share/i2pd/certificates var/lib/i2pd/certificates

View File

@@ -1,27 +0,0 @@
Description: fix #1210
Disables two options, which not presented in old systemd versions
Author: r4sas <r4sas@i2pmail.org>
Bug: https://github.com/PurpleI2P/i2pd/issues/1210
Reviewed-By: r4sas <r4sas@i2pmail.org>
Last-Update: 2020-05-25
Index: i2pd/contrib/i2pd.service
===================================================================
--- i2pd.orig/contrib/i2pd.service
+++ i2pd/contrib/i2pd.service
@@ -6,10 +6,10 @@ After=network.target
[Service]
User=i2pd
Group=i2pd
-RuntimeDirectory=i2pd
-RuntimeDirectoryMode=0700
-LogsDirectory=i2pd
-LogsDirectoryMode=0700
+#RuntimeDirectory=i2pd
+#RuntimeDirectoryMode=0700
+#LogsDirectory=i2pd
+#LogsDirectoryMode=0700
Type=forking
ExecStart=/usr/sbin/i2pd --conf=/etc/i2pd/i2pd.conf --tunconf=/etc/i2pd/tunnels.conf --tunnelsdir=/etc/i2pd/tunnels.conf.d --pidfile=/run/i2pd/i2pd.pid --logfile=/var/log/i2pd/i2pd.log --daemon --service
ExecReload=/bin/sh -c "kill -HUP $MAINPID"

View File

@@ -1,2 +1 @@
01-fix-1210.patch
02-upnp.patch
01-upnp.patch

3
debian/watch vendored
View File

@@ -1,3 +1,4 @@
version=4 opts="filenamemangle=s%(?:.*?)?v?(\d[\d.]*)\.tar\.gz%i2pd-$1.tar.gz%" \
version=4
opts="filenamemangle=s%(?:.*?)?v?(\d[\d.]*)\.tar\.gz%i2pd-$1.tar.gz%" \
https://github.com/PurpleI2P/i2pd/tags \
(?:.*?/)?(\d[\d.]*)\.tar\.gz debian uupdate

View File

@@ -64,10 +64,10 @@ namespace afrikaans // language namespace
static std::map<std::string, std::vector<std::string>> plurals
{
{"days", {"dag", "dae"}},
{"hours", {"uur", "ure"}},
{"minutes", {"minuut", "minute"}},
{"seconds", {"seconde", "sekondes"}},
{"%d days", {"%d dag", "%d dae"}},
{"%d hours", {"%d uur", "%d ure"}},
{"%d minutes", {"%d minuut", "%d minute"}},
{"%d seconds", {"%d seconde", "%d sekondes"}},
{"", {"", ""}},
};

View File

@@ -31,9 +31,9 @@ namespace armenian // language namespace
static std::map<std::string, std::string> strings
{
{"KiB", "ԿիԲ"},
{"MiB", "ՄիԲ"},
{"GiB", "ԳիԲ"},
{"%.2f KiB", "%.2f ԿիԲ"},
{"%.2f MiB", "%.2f ՄիԲ"},
{"%.2f GiB", "%.2f ԳիԲ"},
{"building", "կառուցվում է"},
{"failed", "Անհաջող"},
{"expiring", "Լրանում է"},
@@ -68,7 +68,7 @@ namespace armenian // language namespace
{"Family", "Խմբատեսակ"},
{"Tunnel creation success rate", "Հաջողությամբ կառուցված թունելներ"},
{"Received", "Ստացվել է"},
{"KiB/s", "ԿիԲ/վ"},
{"%.2f KiB/s", "%.2f ԿիԲ/վ"},
{"Sent", "Ուղարկվել է"},
{"Transit", "Տարանցում"},
{"Data path", "Տվյալների ուղին"},
@@ -94,7 +94,7 @@ namespace armenian // language namespace
{"Type", "Տեսակը"},
{"EncType", "Գաղտնագրի տեսակը"},
{"Inbound tunnels", "Մուտքային թունելներ"},
{"ms", "մլվ"},
{"%dms", "%dմլվ"},
{"Outbound tunnels", "Ելքային թունելներ"},
{"Tags", "Թեգեր"},
{"Incoming", "Մուտքային"},
@@ -198,10 +198,10 @@ namespace armenian // language namespace
static std::map<std::string, std::vector<std::string>> plurals
{
{"days", {"օր", "օր"}},
{"hours", {"ժամ", "ժամ"}},
{"minutes", {"րոպե", "րոպե"}},
{"seconds", {"վարկյան", "վարկյան"}},
{"%d days", {"%d օր", "%d օր"}},
{"%d hours", {"%d ժամ", "%d ժամ"}},
{"%d minutes", {"%d րոպե", "%d րոպե"}},
{"%d seconds", {"%d վարկյան", "%d վարկյան"}},
{"", {"", ""}},
};

View File

@@ -32,9 +32,9 @@ namespace chinese // language namespace
static std::map<std::string, std::string> strings
{
{"KiB", "KiB"},
{"MiB", "MiB"},
{"GiB", "GiB"},
{"%.2f KiB", "%.2f KiB"},
{"%.2f MiB", "%.2f MiB"},
{"%.2f GiB", "%.2f GiB"},
{"building", "正在构建"},
{"failed", "连接失败"},
{"expiring", "即将过期"},
@@ -70,7 +70,7 @@ namespace chinese // language namespace
{"Family", "家族"},
{"Tunnel creation success rate", "隧道创建成功率"},
{"Received", "已接收"},
{"KiB/s", "KiB/s"},
{"%.2f KiB/s", "%.2f KiB/s"},
{"Sent", "已发送"},
{"Transit", "中转"},
{"Data path", "数据文件路径"},
@@ -96,7 +96,7 @@ namespace chinese // language namespace
{"Type", "类型"},
{"EncType", "加密类型"},
{"Inbound tunnels", "入站隧道"},
{"ms", "毫秒"},
{"%dms", "%d毫秒"},
{"Outbound tunnels", "出站隧道"},
{"Tags", "标签"},
{"Incoming", "传入"},
@@ -200,10 +200,10 @@ namespace chinese // language namespace
static std::map<std::string, std::vector<std::string>> plurals
{
{"days", {""}},
{"hours", {""}},
{"minutes", {""}},
{"seconds", {""}},
{"%d days", {"%d "}},
{"%d hours", {"%d "}},
{"%d minutes", {"%d "}},
{"%d seconds", {"%d "}},
{"", {""}},
};

216
i18n/Czech.cpp Normal file
View File

@@ -0,0 +1,216 @@
/*
* Copyright (c) 2022, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
* See full license text in LICENSE file at top of project tree
*/
#include <map>
#include <vector>
#include <string>
#include <memory>
#include "I18N.h"
// Czech localization file
namespace i2p
{
namespace i18n
{
namespace czech // language namespace
{
// language name in lowercase
static std::string language = "czech";
// See for language plural forms here:
// https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html
static int plural (int n) {
return (n == 1) ? 0 : (n >= 2 && n <= 4) ? 1 : 2;
}
static std::map<std::string, std::string> strings
{
{"%.2f KiB", "%.2f KiB"},
{"%.2f MiB", "%.2f MiB"},
{"%.2f GiB", "%.2f GiB"},
{"building", "vytváří se"},
{"failed", "selhalo"},
{"expiring", "končící"},
{"established", "vytvořeno"},
{"unknown", "neznámý"},
{"exploratory", "průzkumné"},
{"Purple I2P Webconsole", "Purple I2P Webkonsole"},
{"<b>i2pd</b> webconsole", "<b>i2pd</b> webkonsole"},
{"Main page", "Hlavní stránka"},
{"Router commands", "Router příkazy"},
{"Local Destinations", "Lokální destinace"},
{"LeaseSets", "LeaseSety"},
{"Tunnels", "Tunely"},
{"Transit Tunnels", "Transitní tunely"},
{"Transports", "Transporty"},
{"I2P tunnels", "I2P tunely"},
{"SAM sessions", "SAM relace"},
{"ERROR", "CHYBA"},
{"OK", "OK"},
{"Testing", "Testuji"},
{"Firewalled", "Za Firewallem"},
{"Unknown", "Neznámý"},
{"Proxy", "Proxy"},
{"Mesh", "Síť"},
{"Error", "Chyba"},
{"Clock skew", "Časová nesrovnalost"},
{"Offline", "Offline"},
{"Symmetric NAT", "Symetrický NAT"},
{"Uptime", "Doba provozu"},
{"Network status", "Status sítě"},
{"Network status v6", "Status sítě v6"},
{"Stopping in", "Zastavuji za"},
{"Family", "Rodina"},
{"Tunnel creation success rate", "Úspěšnost vytváření tunelů"},
{"Received", "Přijato"},
{"%.2f KiB/s", "%.2f KiB/s"},
{"Sent", "Odesláno"},
{"Transit", "Tranzit"},
{"Data path", "Cesta k data souborům"},
{"Hidden content. Press on text to see.", "Skrytý kontent. Pro zobrazení, klikni na text."},
{"Router Ident", "Routerová Identita"},
{"Router Family", "Rodina routerů"},
{"Router Caps", "Omezení Routerů"},
{"Version", "Verze"},
{"Our external address", "Naše externí adresa"},
{"supported", "podporováno"},
{"Routers", "Routery"},
{"Floodfills", "Floodfilly"},
{"Client Tunnels", "Klientské tunely"},
{"Services", "Služby"},
{"Enabled", "Zapnuto"},
{"Disabled", "Vypnuto"},
{"Encrypted B33 address", "Šifrovaná adresa B33"},
{"Address registration line", "Registrační řádek adresy"},
{"Domain", "Doména"},
{"Generate", "Vygenerovat"},
{"<b>Note:</b> result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", "<b>Poznámka:</b> výsledný řetězec může být použit pouze pro registraci 2LD domén (example.i2p). Pro registraci subdomén použijte prosím i2pd-tools."},
{"Address", "Adresa"},
{"Type", "Typ"},
{"EncType", "EncType"},
{"Inbound tunnels", "Příchozí tunely"},
{"%dms", "%dms"},
{"Outbound tunnels", "Odchozí tunely"},
{"Tags", "Štítky"},
{"Incoming", "Příchozí"},
{"Outgoing", "Odchozí"},
{"Destination", "Destinace"},
{"Amount", "Množství"},
{"Incoming Tags", "Příchozí štítky"},
{"Tags sessions", "Relace štítků"},
{"Status", "Status"},
{"Local Destination", "Lokální Destinace"},
{"Streams", "Toky"},
{"Close stream", "Uzavřít tok"},
{"I2CP session not found", "I2CP relace nenalezena"},
{"I2CP is not enabled", "I2CP není zapnuto"},
{"Invalid", "Neplatný"},
{"Store type", "Druh uložení"},
{"Expires", "Vyprší"},
{"Non Expired Leases", "Nevypršené Leasy"},
{"Gateway", "Brána"},
{"TunnelID", "ID tunelu"},
{"EndDate", "Datum ukončení"},
{"not floodfill", "není floodfill"},
{"Queue size", "Velikost fronty"},
{"Run peer test", "Spustit peer test"},
{"Decline transit tunnels", "Odmítnout tranzitní tunely"},
{"Accept transit tunnels", "Přijmout tranzitní tunely"},
{"Cancel graceful shutdown", "Zrušit hladké vypnutí"},
{"Start graceful shutdown", "Zahájit hladké vypnutí"},
{"Force shutdown", "Vynutit vypnutí"},
{"Reload external CSS styles", "Znovu načíst externí CSS"},
{"<b>Note:</b> any action done here are not persistent and not changes your config files.", "<b>Poznámka:</b> žádná vykonaná akce zde není trvalá a nemění konfigurační soubory."},
{"Logging level", "Úroveň logování"},
{"Transit tunnels limit", "Limit tranzitních tunelů"},
{"Change", "Změnit"},
{"Change language", "Změnit jazyk"},
{"no transit tunnels currently built", "Žádný tranzitní tunel není momentálně vytvořen"},
{"SAM disabled", "SAM vypnutý"},
{"no sessions currently running", "Momentálně nejsou spuštěné žádné relace"},
{"SAM session not found", "SAM relace nenalezena"},
{"SAM Session", "SAM Relace"},
{"Server Tunnels", "Server Tunely"},
{"Client Forwards", "Přesměrování Klienta"},
{"Server Forwards", "Přesměrování Serveru"},
{"Unknown page", "Neznámá stránka"},
{"Invalid token", "Neplatný token"},
{"SUCCESS", "ÚSPĚCH"},
{"Stream closed", "Tok uzavřen"},
{"Stream not found or already was closed", "Tok nenalezen nebo byl již uzavřen"},
{"Destination not found", "Destinace nenalezena"},
{"StreamID can't be null", "StreamID nemůže být null"},
{"Return to destination page", "Zpět na stránku destinací"},
{"You will be redirected in 5 seconds", "Budete přesměrováni za 5 vteřin"},
{"Transit tunnels count must not exceed 65535", "Počet tranzitních tunelů nesmí přesáhnout 65535"},
{"Back to commands list", "Zpět na list příkazů"},
{"Register at reg.i2p", "Zaregistrovat na reg.i2p"},
{"Description", "Popis"},
{"A bit information about service on domain", "Trochu informací o službě na doméně"},
{"Submit", "Odeslat"},
{"Domain can't end with .b32.i2p", "Doména nesmí končit na .b32.i2p"},
{"Domain must end with .i2p", "Doména musí končit s .i2p"},
{"Such destination is not found", "Takováto destinace nebyla nalezena"},
{"Unknown command", "Neznámý příkaz"},
{"Command accepted", "Příkaz přijat"},
{"Proxy error", "Chyba proxy serveru"},
{"Proxy info", "Proxy informace"},
{"Proxy error: Host not found", "Chyba proxy serveru: Hostitel nenalezen"},
{"Remote host not found in router's addressbook", "Vzdálený hostitel nebyl nalezen v adresáři routeru"},
{"You may try to find this host on jump services below", "Můžete se pokusit najít tohoto hostitele na startovacích službách níže"},
{"Invalid request", "Neplatný požadavek"},
{"Proxy unable to parse your request", "Proxy server nemohl zpracovat váš požadavek"},
{"addresshelper is not supported", "Adresshelper není podporován"},
{"Host", "Hostitel"},
{"added to router's addressbook from helper", "přidáno do adresáře routeru od pomocníka"},
{"Click here to proceed:", "Pro pokračování, klikněte zde:"},
{"Continue", "Pokračovat"},
{"Addresshelper found", "Adresář nalezen"},
{"already in router's addressbook", "je již v adresáři routeru"},
{"Click here to update record:", "Pro aktualizaci záznamu, klikněte zde:"},
{"invalid request uri", "neplatný URI požadavek"},
{"Can't detect destination host from request", "Nelze zjistit cílového hostitele z požadavku"},
{"Outproxy failure", "Outproxy selhání"},
{"bad outproxy settings", "špatné outproxy nastavení"},
{"not inside I2P network, but outproxy is not enabled", "není uvnitř I2P sítě a outproxy není nastavena"},
{"unknown outproxy url", "neznámá outproxy URL"},
{"cannot resolve upstream proxy", "nelze rozluštit upstream proxy server"},
{"hostname too long", "Název hostitele je příliš dlouhý"},
{"cannot connect to upstream socks proxy", "nelze se připojit k upstream socks proxy serveru"},
{"Cannot negotiate with socks proxy", "Nelze vyjednávat se socks proxy serverem"},
{"CONNECT error", "Chyba PŘIPOJENÍ"},
{"Failed to Connect", "Připojení se nezdařilo"},
{"socks proxy error", "chyba socks proxy serveru"},
{"failed to send request to upstream", "odeslání žádosti upstream serveru se nezdařilo"},
{"No Reply From socks proxy", "Žádná odpověď od socks proxy serveru"},
{"cannot connect", "nelze se připojit"},
{"http out proxy not implemented", "http out proxy není implementován"},
{"cannot connect to upstream http proxy", "nelze se připojit k upstream socks proxy serveru"},
{"Host is down", "Hostitel je nedostupný"},
{"Can't create connection to requested host, it may be down. Please try again later.", "Připojení k požadovanému hostiteli nelze vytvořit, může být nedostupný. Zkuste to, prosím, znovu později."},
{"", ""},
};
static std::map<std::string, std::vector<std::string>> plurals
{
{"%d days", {"%d den", "%d dny", "%d dní", "%d dní"}},
{"%d hours", {"%d hodina", "%d hodiny", "%d hodin", "%d hodin"}},
{"%d minutes", {"%d minuta", "%d minuty", "%d minut", "%d minut"}},
{"%d seconds", {"%d vteřina", "%d vteřiny", "%d vteřin", "%d vteřin"}},
{"", {"", "", "", ""}},
};
std::shared_ptr<const i2p::i18n::Locale> GetLocale()
{
return std::make_shared<i2p::i18n::Locale>(language, strings, plurals, [] (int n)->int { return plural(n); });
}
} // language
} // i18n
} // i2p

View File

@@ -31,9 +31,9 @@ namespace french // language namespace
static std::map<std::string, std::string> strings
{
{"KiB", "Kio"},
{"MiB", "Mio"},
{"GiB", "Gio"},
{"%.2f KiB", "%.2f Kio"},
{"%.2f MiB", "%.2f Mio"},
{"%.2f GiB", "%.2f Gio"},
{"building", "En construction"},
{"failed", "échoué"},
{"expiring", "expiré"},
@@ -69,7 +69,7 @@ namespace french // language namespace
{"Family", "Famille"},
{"Tunnel creation success rate", "Taux de succès de création de tunnels"},
{"Received", "Reçu"},
{"KiB/s", "kio/s"},
{"%.2f KiB/s", "%.2f kio/s"},
{"Sent", "Envoyé"},
{"Transit", "Transité"},
{"Data path", "Emplacement des données"},
@@ -93,7 +93,7 @@ namespace french // language namespace
{"Address", "Adresse"},
{"Type", "Type"},
{"Inbound tunnels", "Tunnels entrants"},
{"ms", "ms"},
{"%dms", "%dms"},
{"Outbound tunnels", "Tunnels sortants"},
{"Tags", "Balises"},
{"Incoming", "Entrant"},
@@ -194,10 +194,10 @@ namespace french // language namespace
static std::map<std::string, std::vector<std::string>> plurals
{
{"days", {"jour", "jours"}},
{"hours", {"heure", "heures"}},
{"minutes", {"minute", "minutes"}},
{"seconds", {"seconde", "secondes"}},
{"%d days", {"%d jour", "%d jours"}},
{"%d hours", {"%d heure", "%d heures"}},
{"%d minutes", {"%d minute", "%d minutes"}},
{"%d seconds", {"%d seconde", "%d secondes"}},
{"", {"", ""}},
};

View File

@@ -31,9 +31,9 @@ namespace german // language namespace
static std::map<std::string, std::string> strings
{
{"KiB", "KiB"},
{"MiB", "MiB"},
{"GiB", "GiB"},
{"%.2f KiB", "%.2f KiB"},
{"%.2f MiB", "%.2f MiB"},
{"%.2f GiB", "%.2f GiB"},
{"building", "In Bau"},
{"failed", "fehlgeschlagen"},
{"expiring", "läuft ab"},
@@ -69,7 +69,7 @@ namespace german // language namespace
{"Family", "Familie"},
{"Tunnel creation success rate", "Erfolgsrate der Tunnelerstellung"},
{"Received", "Eingegangen"},
{"KiB/s", "KiB/s"},
{"%.2f KiB/s", "%.2f KiB/s"},
{"Sent", "Gesendet"},
{"Transit", "Transit"},
{"Data path", "Datenpfad"},
@@ -95,7 +95,7 @@ namespace german // language namespace
{"Type", "Typ"},
{"EncType", "Verschlüsselungstyp"},
{"Inbound tunnels", "Eingehende Tunnel"},
{"ms", "ms"},
{"%dms", "%dms"},
{"Outbound tunnels", "Ausgehende Tunnel"},
{"Tags", "Tags"},
{"Incoming", "Eingehend"},
@@ -199,10 +199,10 @@ namespace german // language namespace
static std::map<std::string, std::vector<std::string>> plurals
{
{"days", {"Tag", "Tage"}},
{"hours", {"Stunde", "Stunden"}},
{"minutes", {"Minute", "Minuten"}},
{"seconds", {"Sekunde", "Sekunden"}},
{"%d days", {"%d Tag", "%d Tage"}},
{"%d hours", {"%d Stunde", "%d Stunden"}},
{"%d minutes", {"%d Minute", "%d Minuten"}},
{"%d seconds", {"%d Sekunde", "%d Sekunden"}},
{"", {"", ""}},
};

43
i18n/I18N.cpp Normal file
View File

@@ -0,0 +1,43 @@
/*
* Copyright (c) 2021-2023, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
* See full license text in LICENSE file at top of project tree
*/
#include <clocale>
#include "ClientContext.h"
#include "I18N_langs.h"
#include "I18N.h"
namespace i2p
{
namespace i18n
{
void SetLanguage(const std::string &lang)
{
const auto it = i2p::i18n::languages.find(lang);
if (it == i2p::i18n::languages.end()) // fallback
{
i2p::client::context.SetLanguage (i2p::i18n::english::GetLocale());
setlocale(LC_NUMERIC, "english");
}
else
{
i2p::client::context.SetLanguage (it->second.LocaleFunc());
setlocale(LC_NUMERIC, lang.c_str()); // set decimal point based on language
}
}
std::string translate (const std::string& arg)
{
return i2p::client::context.GetLanguage ()->GetString (arg);
}
std::string translate (const std::string& arg, const std::string& arg2, const int& n)
{
return i2p::client::context.GetLanguage ()->GetPlural (arg, arg2, n);
}
} // i18n
} // i2p

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2021-2022, The PurpleI2P Project
* Copyright (c) 2021-2023, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -9,37 +9,129 @@
#ifndef __I18N_H__
#define __I18N_H__
#include "ClientContext.h"
#include <string>
#include <map>
#include <utility>
#include <functional>
namespace i2p
{
namespace i18n
{
inline void SetLanguage(const std::string &lang)
class Locale
{
const auto it = i2p::i18n::languages.find(lang);
if (it == i2p::i18n::languages.end()) // fallback
i2p::client::context.SetLanguage (i2p::i18n::english::GetLocale());
else
i2p::client::context.SetLanguage (it->second.LocaleFunc());
}
public:
Locale (
const std::string& language,
const std::map<std::string, std::string>& strings,
const std::map<std::string, std::vector<std::string>>& plurals,
std::function<int(int)> formula
): m_Language (language), m_Strings (strings), m_Plurals (plurals), m_Formula (formula) { };
inline std::string translate (const std::string& arg)
{
return i2p::client::context.GetLanguage ()->GetString (arg);
}
// Get activated language name for webconsole
std::string GetLanguage() const
{
return m_Language;
}
inline std::string translate (const std::string& arg, const std::string& arg2, const int& n)
{
return i2p::client::context.GetLanguage ()->GetPlural (arg, arg2, n);
}
std::string GetString (const std::string& arg) const
{
const auto it = m_Strings.find(arg);
if (it == m_Strings.end())
{
return arg;
}
else
{
return it->second;
}
}
std::string GetPlural (const std::string& arg, const std::string& arg2, const int& n) const
{
const auto it = m_Plurals.find(arg2);
if (it == m_Plurals.end()) // not found, fallback to english
{
return n == 1 ? arg : arg2;
}
else
{
int form = m_Formula(n);
return it->second[form];
}
}
private:
const std::string m_Language;
const std::map<std::string, std::string> m_Strings;
const std::map<std::string, std::vector<std::string>> m_Plurals;
std::function<int(int)> m_Formula;
};
void SetLanguage(const std::string &lang);
std::string translate (const std::string& arg);
std::string translate (const std::string& arg, const std::string& arg2, const int& n);
} // i18n
} // i2p
template<typename... TArgs>
std::string tr (TArgs&&... args)
/**
* @brief Get translation of string
* @param arg String with message
*/
template<typename TValue>
std::string tr (TValue&& arg)
{
return i2p::i18n::translate(std::forward<TArgs>(args)...);
return i2p::i18n::translate(std::forward<TValue>(arg));
}
/**
* @brief Get translation of string and format it
* @param arg String with message
* @param args Array of arguments for string formatting
*/
template<typename TValue, typename... TArgs>
std::string tr (TValue&& arg, TArgs&&... args)
{
std::string tr_str = i2p::i18n::translate(std::forward<TValue>(arg));
size_t size = std::snprintf(NULL, 0, tr_str.c_str(), std::forward<TArgs>(args)...);
size = size + 1;
std::string str(size, 0);
std::snprintf(&str.front(), size, tr_str.c_str(), std::forward<TArgs>(args)...);
return str;
}
/**
* @brief Get translation of string with plural forms
* @param arg String with message in singular form
* @param arg2 String with message in plural form
* @param n Integer, used for selection of form
*/
template<typename TValue, typename TValue2>
std::string ntr (TValue&& arg, TValue2&& arg2, int& n)
{
return i2p::i18n::translate(std::forward<TValue>(arg), std::forward<TValue2>(arg2), std::forward<int>(n));
}
/**
* @brief Get translation of string with plural forms and format it
* @param arg String with message in singular form
* @param arg2 String with message in plural form
* @param n Integer, used for selection of form
* @param args Array of arguments for string formatting
*/
template<typename TValue, typename TValue2, typename... TArgs>
std::string ntr (TValue&& arg, TValue2&& arg2, int& n, TArgs&&... args)
{
std::string tr_str = i2p::i18n::translate(std::forward<TValue>(arg), std::forward<TValue2>(arg2), std::forward<int>(n));
size_t size = std::snprintf(NULL, 0, tr_str.c_str(), std::forward<TArgs>(args)...);
size = size + 1;
std::string str(size, 0);
std::snprintf(&str.front(), size, tr_str.c_str(), std::forward<TArgs>(args)...);
return str;
}
#endif // __I18N_H__

View File

@@ -9,60 +9,12 @@
#ifndef __I18N_LANGS_H__
#define __I18N_LANGS_H__
#include "I18N.h"
namespace i2p
{
namespace i18n
{
class Locale
{
public:
Locale (
const std::string& language,
const std::map<std::string, std::string>& strings,
const std::map<std::string, std::vector<std::string>>& plurals,
std::function<int(int)> formula
): m_Language (language), m_Strings (strings), m_Plurals (plurals), m_Formula (formula) { };
// Get activated language name for webconsole
std::string GetLanguage() const
{
return m_Language;
}
std::string GetString (const std::string& arg) const
{
const auto it = m_Strings.find(arg);
if (it == m_Strings.end())
{
return arg;
}
else
{
return it->second;
}
}
std::string GetPlural (const std::string& arg, const std::string& arg2, const int& n) const
{
const auto it = m_Plurals.find(arg2);
if (it == m_Plurals.end()) // not found, fallback to english
{
return n == 1 ? arg : arg2;
}
else
{
int form = m_Formula(n);
return it->second[form];
}
}
private:
const std::string m_Language;
const std::map<std::string, std::string> m_Strings;
const std::map<std::string, std::vector<std::string>> m_Plurals;
std::function<int(int)> m_Formula;
};
struct langData
{
std::string LocaleName; // localized name
@@ -74,12 +26,14 @@ namespace i18n
namespace afrikaans { std::shared_ptr<const i2p::i18n::Locale> GetLocale (); }
namespace armenian { std::shared_ptr<const i2p::i18n::Locale> GetLocale (); }
namespace chinese { std::shared_ptr<const i2p::i18n::Locale> GetLocale (); }
namespace czech { std::shared_ptr<const i2p::i18n::Locale> GetLocale (); }
namespace english { std::shared_ptr<const i2p::i18n::Locale> GetLocale (); }
namespace french { std::shared_ptr<const i2p::i18n::Locale> GetLocale (); }
namespace german { std::shared_ptr<const i2p::i18n::Locale> GetLocale (); }
namespace italian { std::shared_ptr<const i2p::i18n::Locale> GetLocale (); }
namespace russian { std::shared_ptr<const i2p::i18n::Locale> GetLocale (); }
namespace spanish { std::shared_ptr<const i2p::i18n::Locale> GetLocale (); }
namespace swedish { std::shared_ptr<const i2p::i18n::Locale> GetLocale (); }
namespace turkmen { std::shared_ptr<const i2p::i18n::Locale> GetLocale (); }
namespace ukrainian { std::shared_ptr<const i2p::i18n::Locale> GetLocale (); }
namespace uzbek { std::shared_ptr<const i2p::i18n::Locale> GetLocale (); }
@@ -92,12 +46,14 @@ namespace i18n
{ "afrikaans", {"Afrikaans", "af", i2p::i18n::afrikaans::GetLocale} },
{ "armenian", {"hայերէն", "hy", i2p::i18n::armenian::GetLocale} },
{ "chinese", {"简体字", "zh-CN", i2p::i18n::chinese::GetLocale} },
{ "czech", {"čeština", "cs", i2p::i18n::czech::GetLocale} },
{ "english", {"English", "en", i2p::i18n::english::GetLocale} },
{ "french", {"Français", "fr", i2p::i18n::french::GetLocale} },
{ "german", {"Deutsch", "de", i2p::i18n::german::GetLocale} },
{ "italian", {"Italiano", "it", i2p::i18n::italian::GetLocale} },
{ "russian", {"Русский язык", "ru", i2p::i18n::russian::GetLocale} },
{ "spanish", {"Español", "es", i2p::i18n::spanish::GetLocale} },
{ "swedish", {"Svenska", "sv", i2p::i18n::swedish::GetLocale} },
{ "turkmen", {"Türkmen dili", "tk", i2p::i18n::turkmen::GetLocale} },
{ "ukrainian", {"Украї́нська мо́ва", "uk", i2p::i18n::ukrainian::GetLocale} },
{ "uzbek", {"Oʻzbek", "uz", i2p::i18n::uzbek::GetLocale} },

View File

@@ -31,9 +31,9 @@ namespace italian // language namespace
static std::map<std::string, std::string> strings
{
{"KiB", "KiB"},
{"MiB", "MiB"},
{"GiB", "GiB"},
{"%.2f KiB", "%.2f KiB"},
{"%.2f MiB", "%.2f MiB"},
{"%.2f GiB", "%.2f GiB"},
{"building", "in costruzione"},
{"failed", "fallito"},
{"expiring", "in scadenza"},
@@ -69,7 +69,7 @@ namespace italian // language namespace
{"Family", "Famiglia"},
{"Tunnel creation success rate", "Percentuale di tunnel creati con successo"},
{"Received", "Ricevuti"},
{"KiB/s", "KiB/s"},
{"%.2f KiB/s", "%.2f KiB/s"},
{"Sent", "Inviati"},
{"Transit", "Transitati"},
{"Data path", "Percorso dati"},
@@ -95,7 +95,7 @@ namespace italian // language namespace
{"Type", "Tipologia"},
{"EncType", "Tipo di crittografia"},
{"Inbound tunnels", "Tunnel in entrata"},
{"ms", "ms"},
{"%dms", "%dms"},
{"Outbound tunnels", "Tunnel in uscita"},
{"Tags", "Tag"},
{"Incoming", "In entrata"},
@@ -199,10 +199,10 @@ namespace italian // language namespace
static std::map<std::string, std::vector<std::string>> plurals
{
{"days", {"giorno", "giorni"}},
{"hours", {"ora", "ore"}},
{"minutes", {"minuto", "minuti"}},
{"seconds", {"secondo", "secondi"}},
{"%d days", {"%d giorno", "%d giorni"}},
{"%d hours", {"%d ora", "%d ore"}},
{"%d minutes", {"%d minuto", "%d minuti"}},
{"%d seconds", {"%d secondo", "%d secondi"}},
{"", {"", ""}},
};

View File

@@ -31,9 +31,9 @@ namespace russian // language namespace
static std::map<std::string, std::string> strings
{
{"KiB", "КиБ"},
{"MiB", "МиБ"},
{"GiB", "ГиБ"},
{"%.2f KiB", "%.2f КиБ"},
{"%.2f MiB", "%.2f МиБ"},
{"%.2f GiB", "%.2f ГиБ"},
{"building", "строится"},
{"failed", "неудачный"},
{"expiring", "истекает"},
@@ -68,7 +68,7 @@ namespace russian // language namespace
{"Family", "Семейство"},
{"Tunnel creation success rate", "Успешно построенных туннелей"},
{"Received", "Получено"},
{"KiB/s", "КиБ/с"},
{"%.2f KiB/s", "%.2f КиБ/с"},
{"Sent", "Отправлено"},
{"Transit", "Транзит"},
{"Data path", "Путь к данным"},
@@ -94,7 +94,7 @@ namespace russian // language namespace
{"Type", "Тип"},
{"EncType", "ТипШифр"},
{"Inbound tunnels", "Входящие туннели"},
{"ms", "мс"},
{"%dms", "%dмс"},
{"Outbound tunnels", "Исходящие туннели"},
{"Tags", "Теги"},
{"Incoming", "Входящие"},
@@ -198,10 +198,10 @@ namespace russian // language namespace
static std::map<std::string, std::vector<std::string>> plurals
{
{"days", {"день", "дня", "дней"}},
{"hours", {"час", "часа", "часов"}},
{"minutes", {"минуту", "минуты", "минут"}},
{"seconds", {"секунду", "секунды", "секунд"}},
{"%d days", {"%d день", "%d дня", "%d дней"}},
{"%d hours", {"%d час", "%d часа", "%d часов"}},
{"%d minutes", {"%d минуту", "%d минуты", "%d минут"}},
{"%d seconds", {"%d секунду", "%d секунды", "%d секунд"}},
{"", {"", "", ""}},
};

View File

@@ -31,9 +31,9 @@ namespace spanish // language namespace
static std::map<std::string, std::string> strings
{
{"KiB", "KiB"},
{"MiB", "MiB"},
{"GiB", "GiB"},
{"%.2f KiB", "%.2f KiB"},
{"%.2f MiB", "%.2f MiB"},
{"%.2f GiB", "%.2f GiB"},
{"building", "pendiente"},
{"failed", "fallido"},
{"expiring", "expiró"},
@@ -69,7 +69,7 @@ namespace spanish // language namespace
{"Family", "Familia"},
{"Tunnel creation success rate", "Tasa de éxito de creación de túneles"},
{"Received", "Recibido"},
{"KiB/s", "KiB/s"},
{"%.2f KiB/s", "%.2f KiB/s"},
{"Sent", "Enviado"},
{"Transit", "Tránsito"},
{"Data path", "Ruta de datos"},
@@ -95,7 +95,7 @@ namespace spanish // language namespace
{"Type", "Tipo"},
{"EncType", "TipoEncrip"},
{"Inbound tunnels", "Túneles entrantes"},
{"ms", "ms"},
{"%dms", "%dms"},
{"Outbound tunnels", "Túneles salientes"},
{"Tags", "Etiquetas"},
{"Incoming", "Entrante"},
@@ -199,10 +199,10 @@ namespace spanish // language namespace
static std::map<std::string, std::vector<std::string>> plurals
{
{"days", {"día", "días"}},
{"hours", {"hora", "horas"}},
{"minutes", {"minuto", "minutos"}},
{"seconds", {"segundo", "segundos"}},
{"%d days", {"%d día", "%d días"}},
{"%d hours", {"%d hora", "%d horas"}},
{"%d minutes", {"%d minuto", "%d minutos"}},
{"%d seconds", {"%d segundo", "%d segundos"}},
{"", {"", ""}},
};

217
i18n/Swedish.cpp Normal file
View File

@@ -0,0 +1,217 @@
/*
* Copyright (c) 2023, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
* See full license text in LICENSE file at top of project tree
*/
#include <map>
#include <vector>
#include <string>
#include <memory>
#include "I18N.h"
// Swedish localization file
namespace i2p
{
namespace i18n
{
namespace swedish // language namespace
{
// language name in lowercase
static std::string language = "swedish";
// See for language plural forms here:
// https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html
static int plural (int n) {
return n != 1 ? 1 : 0;
}
static std::map<std::string, std::string> strings
{
{"%.2f KiB", "%.2f KiB"},
{"%.2f MiB", "%.2f MiB"},
{"%.2f GiB", "%.2f GiB"},
{"building", "bygger"},
{"failed", "misslyckad"},
{"expiring", "utgår"},
{"established", "upprättad"},
{"unknown", "okänt"},
{"exploratory", "utforskande"},
{"Purple I2P Webconsole", "Purple I2P Webbkonsoll"},
{"<b>i2pd</b> webbkonsoll", "<b>i2pd</b>-Webbkonsoll"},
{"Main page", "Huvudsida"},
{"Router commands", "Routerkommandon"},
{"Local Destinations", "Lokala Platser"},
{"LeaseSets", "Hyresuppsättningar"},
{"Tunnels", "Tunnlar"},
{"Transit Tunnels", "Förmedlande Tunnlar"},
{"Transports", "Transporter"},
{"I2P tunnels", "I2P-tunnlar"},
{"SAM sessions", "SAM-perioder"},
{"ERROR", "FEL"},
{"OK", "OK"},
{"Testing", "Prövar"},
{"Firewalled", "Bakom Brandvägg"},
{"Unknown", "Okänt"},
{"Proxy", "Proxy"},
{"Mesh", "Mesh"},
{"Error", "Fel"},
{"Clock skew", "Tidsförskjutning"},
{"Offline", "Nedkopplad"},
{"Symmetric NAT", "Symmetrisk NAT"},
{"Uptime", "Upptid"},
{"Network status", "Nätverkstillstånd"},
{"Network status v6", "Nätverkstillstånd v6"},
{"Stopping in", "Avstängd om"},
{"Family", "Familj"},
{"Tunnel creation success rate", "Andel framgångsrika tunnlar"},
{"Received", "Mottaget"},
{"%.2f KiB/s", "%.2f KiB/s"},
{"Sent", "Skickat"},
{"Transit", "Förmedlat"},
{"Data path", "Sökväg"},
{"Hidden content. Press on text to see.", "Dolt innehåll. Tryck för att visa."},
{"Router Ident", "Routeridentitet"},
{"Router Family", "Routerfamilj"},
{"Router Caps", "Routerbegränsningar"},
{"Version", "Version"},
{"Our external address", "Vår externa adress"},
{"supported", "stöds"},
{"Routers", "Routrar"},
{"Floodfills", "Översvämningsfyllare"},
{"Client Tunnels", "Klienttunnlar"},
{"Services", "Tjänster"},
{"Enabled", "Påslaget"},
{"Disabled", "Avslaget"},
{"Encrypted B33 address", "Krypterad B33-Adress"},
{"Address registration line", "Adressregistreringsrad"},
{"Domain", "Domän"},
{"Generate", "Skapa"},
{"<b>Note:</b> result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", "<b>Uppmärksamma:</b> den resulterande strängen kan enbart användas för att registrera 2LD-domäner (exempel.i2p). För att registrera underdomäner, vänligen använd i2pd-tools."},
{"Address", "Adress"},
{"Type", "Typ"},
{"EncType", "EncTyp"},
{"Inbound tunnels", "Ingående Tunnlar"},
{"%dms", "%dms"},
{"Outbound tunnels", "Utgående Tunnlar"},
{"Tags", "Taggar"},
{"Incoming", "Ingående"},
{"Outgoing", "Utgående"},
{"Destination", "Plats"},
{"Amount", "Mängd"},
{"Incoming Tags", "Ingående Taggar"},
{"Tags sessions", "Tagg-perioder"},
{"Status", "Tillstånd"},
{"Local Destination", "Lokal Plats"},
{"Streams", "Strömmar"},
{"Close stream", "Stäng strömmen"},
{"I2CP session not found", "I2CP-period hittades inte"},
{"I2CP is not enabled", "I2CP är inte påslaget"},
{"Invalid", "Ogiltig"},
{"Store type", "Lagringstyp"},
{"Expires", "Utgångsdatum"},
{"Non Expired Leases", "Ickeutgångna Hyresuppsättningar"},
{"Gateway", "Gateway"},
{"TunnelID", "TunnelID"},
{"EndDate", "EndDate"},
{"not floodfill", "inte Översvämningsfyllare"},
{"Queue size", "Köstorlek"},
{"Run peer test", "Utför utsiktstest"},
{"Decline transit tunnels", "Avvisa förmedlande tunnlar"},
{"Accept transit tunnels", "Tillåt förmedlande tunnlar"},
{"Cancel graceful shutdown", "Avbryt välvillig avstängning"},
{"Start graceful shutdown", "Påbörja välvillig avstängning"},
{"Force shutdown", "Tvingad avstängning"},
{"Reload external CSS styles", "Ladda om externa CSS-stilar"},
{"<b>Note:</b> any action done here are not persistent and not changes your config files.", "<b>Uppmärksamma:</b> inga ändringar här är beständiga eller påverkar dina inställningsfiler."},
{"Logging level", "Protokollförningsnivå"},
{"Transit tunnels limit", "Begränsa förmedlande tunnlar"},
{"Change", "Ändra"},
{"Change language", "Ändra språk"},
{"no transit tunnels currently built", "inga förmedlande tunnlar har byggts"},
{"SAM disabled", "SAM avslaget"},
{"no sessions currently running", "inga perioder igång"},
{"SAM session not found", "SAM-perioder hittades ej"},
{"SAM Session", "SAM-period"},
{"Server Tunnels", "Värdtunnlar"},
{"Client Forwards", "Klientförpassningar"},
{"Server Forwards", "Värdförpassningar"},
{"Unknown page", "Okänd sida"},
{"Invalid token", "Ogiltig polett"},
{"SUCCESS", "FRAMGÅNG"},
{"Stream closed", "Ström stängd"},
{"Stream not found or already was closed", "Strömmen hittades inte eller var redan avslutad"},
{"Destination not found", "Plats hittades ej"},
{"StreamID can't be null", "Ström-ID kan inte vara null"},
{"Return to destination page", "Återvänd till platssidan"},
{"You will be redirected in 5 seconds", "Du omdirigeras inom fem sekunder"},
{"Transit tunnels count must not exceed 65535", "Förmedlande tunnlar får inte överstiga 65535"},
{"Back to commands list", "Tillbaka till kommandolistan"},
{"Register at reg.i2p", "Registrera vid reg.i2p"},
{"Description", "Beskrivning"},
{"A bit information about service on domain", "Ett stycke information om domänens tjänst"},
{"Submit", "Skicka"},
{"Domain can't end with .b32.i2p", "Domänen får inte sluta med .b32.i2p"},
{"Domain must end with .i2p", "Domänen måste sluta med .i2p"},
{"Such destination is not found", "En sådan plats hittas ej"},
{"Unknown command", "Okänt kommando"},
{"Command accepted", "Kommando accepterades"},
{"Proxy error", "Proxyfel"},
{"Proxy info", "Proxyinfo"},
{"Proxy error: Host not found", "Proxyfel: Värden hittades ej"},
{"Remote host not found in router's addressbook", "Främmande värd hittades inte i routerns adressbok"},
{"You may try to find this host on jump services below", "Du kan försöka att hitta värden genom hopptjänsterna nedan"},
{"Invalid request", "Ogiltig förfrågan"},
{"Proxy unable to parse your request", "Proxyt kan inte behandla din förfrågan"},
{"addresshelper is not supported", "adresshjälparen stöds ej"},
{"Host", "Värd"},
{"added to router's addressbook from helper", "tillagd i routerns adressbok från adresshjälparen"},
{"Click here to proceed:", "Tryck här för att fortsätta:"},
{"Continue", "Fortsätt"},
{"Addresshelper found", "Adresshjälpare hittad"},
{"already in router's addressbook", "finns redan i routerns adressbok"},
{"Click here to update record:", "Tryck här för att uppdatera:"},
{"invalid request uri", "ogiltig förfrågnings-URI"},
{"Can't detect destination host from request", "Kan inte upptäcka platsvärden från förfrågan"},
{"Outproxy failure", "Utproxyfel"},
{"bad outproxy settings", "ogiltig utproxyinställning"},
{"not inside I2P network, but outproxy is not enabled", "adressen är inte inom I2P-näverket, men utproxy är inte påslaget"},
{"unknown outproxy url", "okänt Utproxy-URL"},
{"cannot resolve upstream proxy", "hittar inte uppströmsproxyt"},
{"hostname too long", "värdnamnet är för långt"},
{"cannot connect to upstream socks proxy", "kan inte ansluta till uppströmsproxyt"},
{"Cannot negotiate with socks proxy", "Kan inte förhandla med socksproxyt"},
{"CONNECT error", "CONNECT-fel"},
{"Failed to Connect", "Anslutningen misslyckades"},
{"socks proxy error", "Socksproxyfel"},
{"failed to send request to upstream", "förfrågan uppströms kunde ej skickas"},
{"No Reply From socks proxy", "Fick inget svar från socksproxyt"},
{"cannot connect", "kan inte ansluta"},
{"http out proxy not implemented", "HTTP-Utproxy ej implementerat"},
{"cannot connect to upstream http proxy", "Kan inte ansluta till uppströms HTTP-proxy"},
{"Host is down", "Värden är nere"},
{"Can't create connection to requested host, it may be down. Please try again later.", "Kan inte ansluta till värden, den kan vara nere. Vänligen försök senare."},
{"", ""},
};
static std::map<std::string, std::vector<std::string>> plurals
{
{"%d days", {"%d Dag", "%d Dagar"}},
{"%d hours", {"%d Timme", "%d Timmar"}},
{"%d minutes", {"%d Minut", "%d Minuter"}},
{"%d seconds", {"%d Sekund", "%d Sekunder"}},
{"", {"", ""}},
};
std::shared_ptr<const i2p::i18n::Locale> GetLocale()
{
return std::make_shared<i2p::i18n::Locale>(language, strings, plurals, [] (int n)->int { return plural(n); });
}
} // language
} // i18n
} // i2p

View File

@@ -31,9 +31,9 @@ namespace turkmen // language namespace
static std::map<std::string, std::string> strings
{
{"KiB", "KiB"},
{"MiB", "MiB"},
{"GiB", "GiB"},
{"%.2f KiB", "%.2f KiB"},
{"%.2f MiB", "%.2f MiB"},
{"%.2f GiB", "%.2f GiB"},
{"building", "bina"},
{"failed", "şowsuz"},
{"expiring", "möhleti gutarýar"},
@@ -68,7 +68,7 @@ namespace turkmen // language namespace
{"Family", "Maşgala"},
{"Tunnel creation success rate", "Gurlan teneller üstünlikli gurlan teneller"},
{"Received", "Alnan"},
{"KiB/s", "KiB/s"},
{"%.2f KiB/s", "%.2f KiB/s"},
{"Sent", "Ýerleşdirildi"},
{"Transit", "Tranzit"},
{"Data path", "Maglumat ýoly"},
@@ -94,7 +94,7 @@ namespace turkmen // language namespace
{"Type", "Görnüş"},
{"EncType", "Şifrlemek görnüşi"},
{"Inbound tunnels", "Gelýän tuneller"},
{"ms", "ms"},
{"%dms", "%dms"},
{"Outbound tunnels", "Çykýan tuneller"},
{"Tags", "Bellikler"},
{"Incoming", "Gelýän"},
@@ -198,10 +198,10 @@ namespace turkmen // language namespace
static std::map<std::string, std::vector<std::string>> plurals
{
{"days", {"gün", "gün"}},
{"hours", {"sagat", "sagat"}},
{"minutes", {"minut", "minut"}},
{"seconds", {"sekunt", "sekunt"}},
{"%d days", {"%d gün", "%d gün"}},
{"%d hours", {"%d sagat", "%d sagat"}},
{"%d minutes", {"%d minut", "%d minut"}},
{"%d seconds", {"%d sekunt", "%d sekunt"}},
{"", {"", ""}},
};

View File

@@ -31,9 +31,9 @@ namespace ukrainian // language namespace
static std::map<std::string, std::string> strings
{
{"KiB", "КіБ"},
{"MiB", "МіБ"},
{"GiB", "ГіБ"},
{"%.2f KiB", "%.2f КіБ"},
{"%.2f MiB", "%.2f МіБ"},
{"%.2f GiB", "%.2f ГіБ"},
{"building", "будується"},
{"failed", "невдалий"},
{"expiring", "завершується"},
@@ -68,7 +68,7 @@ namespace ukrainian // language namespace
{"Family", "Сімейство"},
{"Tunnel creation success rate", "Успішно побудованих тунелів"},
{"Received", "Отримано"},
{"KiB/s", "КіБ/с"},
{"%.2f KiB/s", "%.2f КіБ/с"},
{"Sent", "Відправлено"},
{"Transit", "Транзит"},
{"Data path", "Шлях до даних"},
@@ -94,7 +94,7 @@ namespace ukrainian // language namespace
{"Type", "Тип"},
{"EncType", "ТипШифр"},
{"Inbound tunnels", "Вхідні тунелі"},
{"ms", "мс"},
{"%dms", "%dмс"},
{"Outbound tunnels", "Вихідні тунелі"},
{"Tags", "Теги"},
{"Incoming", "Вхідні"},
@@ -198,10 +198,10 @@ namespace ukrainian // language namespace
static std::map<std::string, std::vector<std::string>> plurals
{
{"days", {"день", "дня", "днів"}},
{"hours", {"годину", "години", "годин"}},
{"minutes", {"хвилину", "хвилини", "хвилин"}},
{"seconds", {"секунду", "секунди", "секунд"}},
{"%d days", {"%d день", "%d дня", "%d днів"}},
{"%d hours", {"%d годину", "%d години", "%d годин"}},
{"%d minutes", {"%d хвилину", "%d хвилини", "%d хвилин"}},
{"%d seconds", {"%d секунду", "%d секунди", "%d секунд"}},
{"", {"", "", ""}},
};

View File

@@ -31,9 +31,9 @@ namespace uzbek // language namespace
static std::map<std::string, std::string> strings
{
{"KiB", "KiB"},
{"MiB", "MiB"},
{"GiB", "GiB"},
{"%.2f KiB", "%.2f KiB"},
{"%.2f MiB", "%.2f MiB"},
{"%.2f GiB", "%.2f GiB"},
{"building", "yaratilmoqda"},
{"failed", "muvaffaqiyatsiz"},
{"expiring", "muddati tugaydi"},
@@ -68,7 +68,7 @@ namespace uzbek // language namespace
{"Family", "Oila"},
{"Tunnel creation success rate", "Tunnel yaratish muvaffaqiyat darajasi"},
{"Received", "Qabul qilindi"},
{"KiB/s", "KiB/s"},
{"%.2f KiB/s", "%.2f KiB/s"},
{"Sent", "Yuborilgan"},
{"Transit", "Tranzit"},
{"Data path", "Ma'lumotlar joylanishi"},
@@ -94,7 +94,7 @@ namespace uzbek // language namespace
{"Type", "Turi"},
{"EncType", "ShifrlashTuri"},
{"Inbound tunnels", "Kirish tunnellari"},
{"ms", "ms"},
{"%dms", "%dms"},
{"Outbound tunnels", "Chiquvchi tunnellar"},
{"Tags", "Teglar"},
{"Incoming", "Kiruvchi"},
@@ -198,10 +198,10 @@ namespace uzbek // language namespace
static std::map<std::string, std::vector<std::string>> plurals
{
{"days", {"kun", "kun"}},
{"hours", {"soat", "soat"}},
{"minutes", {"daqiqa", "daqiqa"}},
{"seconds", {"soniya", "soniya"}},
{"%d days", {"%d kun", "%d kun"}},
{"%d hours", {"%d soat", "%d soat"}},
{"%d minutes", {"%d daqiqa", "%d daqiqa"}},
{"%d seconds", {"%d soniya", "%d soniya"}},
{"", {"", ""}},
};

View File

@@ -135,7 +135,7 @@ namespace data
//----------------------------------------------------------
const uint8_t B33_TWO_BYTES_SIGTYPE_FLAG = 0x01;
const uint8_t B33_PER_SECRET_FLAG = 0x02; // not used for now
// const uint8_t B33_PER_SECRET_FLAG = 0x02; // not used for now
const uint8_t B33_PER_CLIENT_AUTH_FLAG = 0x04;
BlindedPublicKey::BlindedPublicKey (std::shared_ptr<const IdentityEx> identity, bool clientAuth):

View File

@@ -1,77 +0,0 @@
/*
* Copyright (c) 2013-2020, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
* See full license text in LICENSE file at top of project tree
*/
#include "BloomFilter.h"
#include "I2PEndian.h"
#include <array>
#include <openssl/sha.h>
namespace i2p
{
namespace util
{
/** @brief decaying bloom filter implementation */
class DecayingBloomFilter : public IBloomFilter
{
public:
DecayingBloomFilter(const std::size_t size)
{
m_Size = size;
m_Data = new uint8_t[size];
}
/** @brief implements IBloomFilter::~IBloomFilter */
~DecayingBloomFilter()
{
delete [] m_Data;
}
/** @brief implements IBloomFilter::Add */
bool Add(const uint8_t * data, std::size_t len)
{
std::size_t idx;
uint8_t mask;
Get(data, len, idx, mask);
if(m_Data[idx] & mask) return false; // filter hit
m_Data[idx] |= mask;
return true;
}
/** @brief implements IBloomFilter::Decay */
void Decay()
{
// reset bloom filter buffer
memset(m_Data, 0, m_Size);
}
private:
/** @brief get bit index for for data */
void Get(const uint8_t * data, std::size_t len, std::size_t & idx, uint8_t & bm)
{
bm = 1;
uint8_t digest[32];
// TODO: use blake2 because it's faster
SHA256(data, len, digest);
uint64_t i = buf64toh(digest);
idx = i % m_Size;
bm <<= (i % 8);
}
uint8_t * m_Data;
std::size_t m_Size;
};
BloomFilterPtr BloomFilter(std::size_t capacity)
{
return std::make_shared<DecayingBloomFilter>(capacity);
}
}
}

View File

@@ -1,39 +0,0 @@
/*
* Copyright (c) 2013-2020, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
* See full license text in LICENSE file at top of project tree
*/
#ifndef BLOOM_FILTER_H_
#define BLOOM_FILTER_H_
#include <memory>
#include <cstdint>
namespace i2p
{
namespace util
{
/** @brief interface for bloom filter */
struct IBloomFilter
{
/** @brief destructor */
virtual ~IBloomFilter() {};
/** @brief add entry to bloom filter, return false if filter hit otherwise return true */
virtual bool Add(const uint8_t * data, std::size_t len) = 0;
/** @brief optionally decay old entries */
virtual void Decay() = 0;
};
typedef std::shared_ptr<IBloomFilter> BloomFilterPtr;
/** @brief create bloom filter */
BloomFilterPtr BloomFilter(std::size_t capacity = 1024 * 8);
}
}
#endif

View File

@@ -64,7 +64,7 @@ namespace config {
("bandwidth", value<std::string>()->default_value(""), "Transit traffic bandwidth limit: integer in KBps or letters: L (32), O (256), P (2048), X (>9000)")
("share", value<int>()->default_value(100), "Limit of transit traffic from max bandwidth in percents. (default: 100)")
("ntcp", bool_switch()->default_value(false), "Ignored. Always false")
("ssu", bool_switch()->default_value(false), "Enable SSU transport (default: disabled)")
("ssu", bool_switch()->default_value(false), "Ignored. Always false")
("ntcpproxy", value<std::string>()->default_value(""), "Ignored")
#ifdef _WIN32
("svcctl", value<std::string>()->default_value(""), "Ignored")
@@ -77,7 +77,7 @@ namespace config {
limits.add_options()
("limits.coresize", value<uint32_t>()->default_value(0), "Maximum size of corefile in Kb (0 - use system limit)")
("limits.openfiles", value<uint16_t>()->default_value(0), "Maximum number of open files (0 - use system default)")
("limits.transittunnels", value<uint16_t>()->default_value(2500), "Maximum active transit sessions (default:2500)")
("limits.transittunnels", value<uint16_t>()->default_value(5000), "Maximum active transit tunnels (default:5000)")
("limits.ntcpsoft", value<uint16_t>()->default_value(0), "Ignored")
("limits.ntcphard", value<uint16_t>()->default_value(0), "Ignored")
("limits.ntcpthreads", value<uint16_t>()->default_value(1), "Ignored")

View File

@@ -159,8 +159,10 @@ namespace crypto
// DH/ElGamal
#if !defined(__x86_64__)
const int ELGAMAL_SHORT_EXPONENT_NUM_BITS = 226;
const int ELGAMAL_SHORT_EXPONENT_NUM_BYTES = ELGAMAL_SHORT_EXPONENT_NUM_BITS/8+1;
#endif
const int ELGAMAL_FULL_EXPONENT_NUM_BITS = 2048;
const int ELGAMAL_FULL_EXPONENT_NUM_BYTES = ELGAMAL_FULL_EXPONENT_NUM_BITS/8;
@@ -239,55 +241,6 @@ namespace crypto
static BIGNUM * (* g_ElggTable)[255] = nullptr;
// DH
DHKeys::DHKeys ()
{
m_DH = DH_new ();
DH_set0_pqg (m_DH, BN_dup (elgp), NULL, BN_dup (elgg));
DH_set0_key (m_DH, NULL, NULL);
}
DHKeys::~DHKeys ()
{
DH_free (m_DH);
}
void DHKeys::GenerateKeys ()
{
BIGNUM * priv_key = NULL, * pub_key = NULL;
#if !defined(__x86_64__) // use short exponent for non x64
priv_key = BN_new ();
BN_rand (priv_key, ELGAMAL_SHORT_EXPONENT_NUM_BITS, 0, 1);
#endif
if (g_ElggTable)
{
#if defined(__x86_64__)
priv_key = BN_new ();
BN_rand (priv_key, ELGAMAL_FULL_EXPONENT_NUM_BITS, 0, 1);
#endif
auto ctx = BN_CTX_new ();
pub_key = ElggPow (priv_key, g_ElggTable, ctx);
DH_set0_key (m_DH, pub_key, priv_key);
BN_CTX_free (ctx);
}
else
{
DH_set0_key (m_DH, NULL, priv_key);
DH_generate_key (m_DH);
DH_get0_key (m_DH, (const BIGNUM **)&pub_key, (const BIGNUM **)&priv_key);
}
bn2buf (pub_key, m_PublicKey, 256);
}
void DHKeys::Agree (const uint8_t * pub, uint8_t * shared)
{
BIGNUM * pk = BN_bin2bn (pub, 256, NULL);
DH_compute_key (shared, pk, m_DH);
BN_free (pk);
}
// x25519
X25519Keys::X25519Keys ()
{
@@ -601,77 +554,6 @@ namespace crypto
BN_CTX_free (ctx);
}
// HMAC
const uint64_t IPAD = 0x3636363636363636;
const uint64_t OPAD = 0x5C5C5C5C5C5C5C5C;
static const uint64_t ipads[] = { IPAD, IPAD, IPAD, IPAD };
static const uint64_t opads[] = { OPAD, OPAD, OPAD, OPAD };
void HMACMD5Digest (uint8_t * msg, size_t len, const MACKey& key, uint8_t * digest)
// key is 32 bytes
// digest is 16 bytes
// block size is 64 bytes
{
uint64_t buf[256];
uint64_t hash[12]; // 96 bytes
#if (defined(__x86_64__) || defined(__i386__)) && defined(__AVX__) // not all X86 targets supports AVX (like old Pentium, see #1600)
if(i2p::cpu::avx)
{
__asm__
(
"vmovups %[key], %%ymm0 \n"
"vmovups %[ipad], %%ymm1 \n"
"vmovups %%ymm1, 32(%[buf]) \n"
"vxorps %%ymm0, %%ymm1, %%ymm1 \n"
"vmovups %%ymm1, (%[buf]) \n"
"vmovups %[opad], %%ymm1 \n"
"vmovups %%ymm1, 32(%[hash]) \n"
"vxorps %%ymm0, %%ymm1, %%ymm1 \n"
"vmovups %%ymm1, (%[hash]) \n"
"vzeroall \n" // end of AVX
"movups %%xmm0, 80(%[hash]) \n" // zero last 16 bytes
:
: [key]"m"(*(const uint8_t *)key), [ipad]"m"(*ipads), [opad]"m"(*opads),
[buf]"r"(buf), [hash]"r"(hash)
: "memory", "%xmm0" // TODO: change to %ymm0 later
);
}
else
#endif
{
// ikeypad
buf[0] = key.GetLL ()[0] ^ IPAD;
buf[1] = key.GetLL ()[1] ^ IPAD;
buf[2] = key.GetLL ()[2] ^ IPAD;
buf[3] = key.GetLL ()[3] ^ IPAD;
buf[4] = IPAD;
buf[5] = IPAD;
buf[6] = IPAD;
buf[7] = IPAD;
// okeypad
hash[0] = key.GetLL ()[0] ^ OPAD;
hash[1] = key.GetLL ()[1] ^ OPAD;
hash[2] = key.GetLL ()[2] ^ OPAD;
hash[3] = key.GetLL ()[3] ^ OPAD;
hash[4] = OPAD;
hash[5] = OPAD;
hash[6] = OPAD;
hash[7] = OPAD;
// fill last 16 bytes with zeros (first hash size assumed 32 bytes in I2P)
memset (hash + 10, 0, 16);
}
// concatenate with msg
memcpy (buf + 8, msg, len);
// calculate first hash
MD5((uint8_t *)buf, len + 64, (uint8_t *)(hash + 8)); // 16 bytes
// calculate digest
MD5((uint8_t *)hash, 96, digest);
}
// AES
#ifdef __AES__
#define KeyExpansion256(round0,round1) \

View File

@@ -62,24 +62,6 @@ namespace crypto
// RSA
const BIGNUM * GetRSAE ();
// DH
class DHKeys
{
public:
DHKeys ();
~DHKeys ();
void GenerateKeys ();
const uint8_t * GetPublicKey () const { return m_PublicKey; };
void Agree (const uint8_t * pub, uint8_t * shared);
private:
DH * m_DH;
uint8_t m_PublicKey[256];
};
// x25519
class X25519Keys
{
@@ -121,10 +103,6 @@ namespace crypto
bool ECIESDecrypt (const EC_GROUP * curve, const BIGNUM * key, const uint8_t * encrypted, uint8_t * data); // 514 bytes encrypted, 222 data
void GenerateECIESKeyPair (const EC_GROUP * curve, BIGNUM *& priv, EC_POINT *& pub);
// HMAC
typedef i2p::data::Tag<32> MACKey;
void HMACMD5Digest (uint8_t * msg, size_t len, const MACKey& key, uint8_t * digest);
// AES
struct ChipherBlock
{

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2022, The PurpleI2P Project
* Copyright (c) 2013-2023, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -1096,13 +1096,13 @@ namespace client
}
auto leaseSet = FindLeaseSet (dest);
if (leaseSet)
{
{
auto stream = CreateStream (leaseSet, port);
GetService ().post ([streamRequestComplete, stream]()
{
GetService ().post ([streamRequestComplete, stream]()
{
streamRequestComplete(stream);
});
}
}
else
{
auto s = GetSharedFromThis ();
@@ -1135,35 +1135,41 @@ namespace client
});
}
template<typename Dest>
std::shared_ptr<i2p::stream::Stream> ClientDestination::CreateStreamSync (const Dest& dest, int port)
template<typename Dest>
std::shared_ptr<i2p::stream::Stream> ClientDestination::CreateStreamSync (const Dest& dest, int port)
{
volatile bool done = false;
std::shared_ptr<i2p::stream::Stream> stream;
std::condition_variable streamRequestComplete;
std::mutex streamRequestCompleteMutex;
std::unique_lock<std::mutex> l(streamRequestCompleteMutex);
CreateStream (
[&streamRequestComplete, &streamRequestCompleteMutex, &stream](std::shared_ptr<i2p::stream::Stream> s)
[&done, &streamRequestComplete, &streamRequestCompleteMutex, &stream](std::shared_ptr<i2p::stream::Stream> s)
{
stream = s;
std::unique_lock<std::mutex> l(streamRequestCompleteMutex);
streamRequestComplete.notify_all ();
done = true;
},
dest, port);
streamRequestComplete.wait (l);
while (!done)
{
std::unique_lock<std::mutex> l(streamRequestCompleteMutex);
if (!done)
streamRequestComplete.wait (l);
}
return stream;
}
}
std::shared_ptr<i2p::stream::Stream> ClientDestination::CreateStream (const i2p::data::IdentHash& dest, int port)
std::shared_ptr<i2p::stream::Stream> ClientDestination::CreateStream (const i2p::data::IdentHash& dest, int port)
{
return CreateStreamSync (dest, port);
}
}
std::shared_ptr<i2p::stream::Stream> ClientDestination::CreateStream (std::shared_ptr<const i2p::data::BlindedPublicKey> dest, int port)
{
return CreateStreamSync (dest, port);
}
}
std::shared_ptr<i2p::stream::Stream> ClientDestination::CreateStream (std::shared_ptr<const i2p::data::LeaseSet> remote, int port)
{
if (m_StreamingDestination)

View File

@@ -284,9 +284,9 @@ namespace client
void PersistTemporaryKeys (EncryptionKey * keys, bool isSingleKey);
void ReadAuthKey (const std::string& group, const std::map<std::string, std::string> * params);
template<typename Dest>
template<typename Dest>
std::shared_ptr<i2p::stream::Stream> CreateStreamSync (const Dest& dest, int port);
private:
i2p::data::PrivateKeys m_Keys;

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2020, The PurpleI2P Project
* Copyright (c) 2013-2022, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -57,7 +57,8 @@ namespace data
if ((err = inflate (&m_Inflator, Z_NO_FLUSH)) == Z_STREAM_END)
return outLen - m_Inflator.avail_out;
// else
LogPrint (eLogError, "Gzip: Inflate error ", err);
if (err)
LogPrint (eLogError, "Gzip: Inflate error ", err);
return 0;
}
}
@@ -128,7 +129,8 @@ namespace data
return outLen - m_Deflator.avail_out;
}
// else
LogPrint (eLogError, "Gzip: Deflate error ", err);
if (err)
LogPrint (eLogError, "Gzip: Deflate error ", err);
return 0;
}
@@ -158,7 +160,8 @@ namespace data
offset = outLen - m_Deflator.avail_out;
}
// else
LogPrint (eLogError, "Gzip: Deflate error ", err);
if (err)
LogPrint (eLogError, "Gzip: Deflate error ", err);
return 0;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2022, The PurpleI2P Project
* Copyright (c) 2013-2023, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -392,7 +392,8 @@ namespace i2p
clearText + ECIES_BUILD_REQUEST_RECORD_IV_KEY_OFFSET,
clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_GATEWAY_FLAG,
clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG);
i2p::tunnel::tunnels.AddTransitTunnel (transitTunnel);
if (!i2p::tunnel::tunnels.AddTransitTunnel (transitTunnel))
retCode = 30;
}
else
retCode = 30; // always reject with bandwidth reason (30)
@@ -558,7 +559,8 @@ namespace i2p
return;
}
auto& noiseState = i2p::context.GetCurrentNoiseState ();
uint8_t replyKey[32], layerKey[32], ivKey[32];
uint8_t replyKey[32]; // AEAD/Chacha20/Poly1305
i2p::crypto::AESKey layerKey, ivKey; // AES
i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "SMTunnelReplyKey", noiseState.m_CK);
memcpy (replyKey, noiseState.m_CK + 32, 32);
i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "SMTunnelLayerKey", noiseState.m_CK);
@@ -589,7 +591,8 @@ namespace i2p
layerKey, ivKey,
clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_GATEWAY_FLAG,
clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG);
i2p::tunnel::tunnels.AddTransitTunnel (transitTunnel);
if (!i2p::tunnel::tunnels.AddTransitTunnel (transitTunnel))
retCode = 30;
}
// encrypt reply

View File

@@ -309,7 +309,7 @@ namespace tunnel
std::vector<std::shared_ptr<I2NPMessage> > m_TunnelMsgs, m_TunnelGatewayMsgs;
};
const uint16_t DEFAULT_MAX_NUM_TRANSIT_TUNNELS = 2500;
const uint16_t DEFAULT_MAX_NUM_TRANSIT_TUNNELS = 5000;
void SetMaxNumTransitTunnels (uint16_t maxNumTransitTunnels);
uint16_t GetMaxNumTransitTunnels ();
}

View File

@@ -49,30 +49,22 @@ namespace data
IdentityEx::IdentityEx(const uint8_t * publicKey, const uint8_t * signingKey, SigningKeyType type, CryptoKeyType cryptoType)
{
/*uint8_t randomPaddingBlock[32];
RAND_bytes (randomPaddingBlock, 32);*/
uint8_t randomPaddingBlock[32];
RAND_bytes (randomPaddingBlock, 32);
if (cryptoType == CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)
{
/*memcpy (m_StandardIdentity.publicKey, publicKey ? publicKey : randomPaddingBlock, 32);
memcpy (m_StandardIdentity.publicKey, publicKey ? publicKey : randomPaddingBlock, 32);
for (int i = 0; i < 7; i++) // 224 bytes
memcpy (m_StandardIdentity.publicKey + 32*(i + 1), randomPaddingBlock, 32);*/
if (publicKey)
{
memcpy (m_StandardIdentity.publicKey, publicKey, 32);
RAND_bytes (m_StandardIdentity.publicKey + 32, 224);
}
else
RAND_bytes (m_StandardIdentity.publicKey, 256);
memcpy (m_StandardIdentity.publicKey + 32*(i + 1), randomPaddingBlock, 32);
}
else
{
if (publicKey)
memcpy (m_StandardIdentity.publicKey, publicKey, 256);
else
RAND_bytes (m_StandardIdentity.publicKey, 256);
/*for (int i = 0; i < 8; i++) // 256 bytes
memcpy (m_StandardIdentity.publicKey + 32*i, randomPaddingBlock, 32);*/
}
for (int i = 0; i < 8; i++) // 256 bytes
memcpy (m_StandardIdentity.publicKey + 32*i, randomPaddingBlock, 32);
}
if (type != SIGNING_KEY_TYPE_DSA_SHA1)
{
size_t excessLen = 0;
@@ -110,9 +102,8 @@ namespace data
case SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519:
{
size_t padding = 128 - i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH; // 96 = 128 - 32
/*for (int i = 0; i < 3; i++) // 96 bytes
memcpy (m_StandardIdentity.signingKey + 32*i, randomPaddingBlock, 32);*/
RAND_bytes (m_StandardIdentity.signingKey, 96);
for (int i = 0; i < 3; i++) // 96 bytes
memcpy (m_StandardIdentity.signingKey + 32*i, randomPaddingBlock, 32);
memcpy (m_StandardIdentity.signingKey + padding, signingKey, i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH);
break;
}
@@ -726,7 +717,7 @@ namespace data
uint8_t publicKey[256];
if (isDestination)
RAND_bytes (keys.m_PrivateKey, 256);
else
else
GenerateCryptoKeyPair (cryptoType, keys.m_PrivateKey, publicKey);
// identity
keys.m_Public = std::make_shared<IdentityEx> (isDestination ? nullptr : publicKey, signingPublicKey, type, cryptoType);

View File

@@ -37,14 +37,7 @@ namespace data
void LeaseSet::Update (const uint8_t * buf, size_t len, bool verifySignature)
{
if (len > m_BufferLen)
{
auto oldBuffer = m_Buffer;
m_Buffer = new uint8_t[len];
delete[] oldBuffer;
}
memcpy (m_Buffer, buf, len);
m_BufferLen = len;
SetBuffer (buf, len);
ReadFromBuffer (false, verifySignature);
}
@@ -264,8 +257,18 @@ namespace data
void LeaseSet::SetBuffer (const uint8_t * buf, size_t len)
{
if (m_Buffer) delete[] m_Buffer;
m_Buffer = new uint8_t[len];
if (len > MAX_LS_BUFFER_SIZE)
{
LogPrint (eLogError, "LeaseSet: Buffer is too long ", len);
len = MAX_LS_BUFFER_SIZE;
}
if (m_Buffer && len > m_BufferLen)
{
delete[] m_Buffer;
m_Buffer = nullptr;
}
if (!m_Buffer)
m_Buffer = new uint8_t[len];
m_BufferLen = len;
memcpy (m_Buffer, buf, len);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2020, The PurpleI2P Project
* Copyright (c) 2013-2022, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -18,11 +18,11 @@ namespace log {
/**
* @brief Maps our loglevel to their symbolic name
*/
static const char * g_LogLevelStr[eNumLogLevels] =
static const char *g_LogLevelStr[eNumLogLevels] =
{
"none", // eLogNone
"error", // eLogError
"warn", // eLogWarn
"warn", // eLogWarning
"info", // eLogInfo
"debug" // eLogDebug
};
@@ -35,12 +35,12 @@ namespace log {
static const char *LogMsgColors[] = { "", "", "", "", "", "" };
#else /* UNIX */
static const char *LogMsgColors[] = {
[eLogNone] = "\033[0m", /* reset */
[eLogError] = "\033[1;31m", /* red */
[eLogWarning] = "\033[1;33m", /* yellow */
[eLogInfo] = "\033[1;36m", /* cyan */
[eLogDebug] = "\033[1;34m", /* blue */
[eNumLogLevels] = "\033[0m", /* reset */
"\033[1;32m", /* none: green */
"\033[1;31m", /* error: red */
"\033[1;33m", /* warning: yellow */
"\033[1;36m", /* info: cyan */
"\033[1;34m", /* debug: blue */
"\033[0m" /* reset */
};
#endif
@@ -126,7 +126,7 @@ namespace log {
if (level == "none") { m_MinLevel = eLogNone; }
else if (level == "error") { m_MinLevel = eLogError; }
else if (level == "warn") { m_MinLevel = eLogWarning; }
else if (level == "info") { m_MinLevel = eLogInfo; }
else if (level == "info") { m_MinLevel = eLogInfo; }
else if (level == "debug") { m_MinLevel = eLogDebug; }
else {
LogPrint(eLogError, "Log: Unknown loglevel: ", level);

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2022, The PurpleI2P Project
* Copyright (c) 2013-2023, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -139,7 +139,7 @@ namespace transport
m3p2[3] = 0; // flag
memcpy (m3p2 + 4, i2p::context.GetRouterInfo ().GetBuffer (), bufLen); // TODO: own RI should be protected by mutex
// 2 bytes reserved
htobe32buf (options + 8, i2p::util::GetSecondsSinceEpoch ()); // tsA
htobe32buf (options + 8, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000); // tsA, rounded to seconds
// 4 bytes reserved
// sign and encrypt options, use m_H as AD
uint8_t nonce[12];
@@ -162,7 +162,7 @@ namespace transport
uint8_t options[16];
memset (options, 0, 16);
htobe16buf (options + 2, paddingLen); // padLen
htobe32buf (options + 8, i2p::util::GetSecondsSinceEpoch ()); // tsB
htobe32buf (options + 8, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000); // tsB, rounded to seconds
// sign and encrypt options, use m_H as AD
uint8_t nonce[12];
memset (nonce, 0, 12); // set nonce to zero
@@ -309,7 +309,7 @@ namespace transport
KDF3Bob ();
if (i2p::crypto::AEADChaCha20Poly1305 (m_SessionConfirmedBuffer + 48, m3p2Len - 16, GetH (), 32, GetK (), nonce, m3p2Buf, m3p2Len - 16, false)) // decrypt
// caclulate new h again for KDF data
// calculate new h again for KDF data
MixHash (m_SessionConfirmedBuffer + 48, m3p2Len); // h = SHA256(h || ciphertext)
else
{
@@ -374,10 +374,16 @@ namespace transport
transports.PeerDisconnected (shared_from_this ());
m_Server.RemoveNTCP2Session (shared_from_this ());
m_SendQueue.clear ();
m_SendQueueSize = 0;
LogPrint (eLogDebug, "NTCP2: Session terminated");
}
}
void NTCP2Session::Close ()
{
m_Socket.close ();
}
void NTCP2Session::TerminateByTimeout ()
{
SendTerminationAndTerminate (eNTCP2IdleTimeout);
@@ -746,6 +752,8 @@ namespace transport
void NTCP2Session::ServerLogin ()
{
SetTerminationTimeout (NTCP2_ESTABLISH_TIMEOUT);
m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch ();
m_Establisher->CreateEphemeralKey ();
boost::asio::async_read (m_Socket, boost::asio::buffer(m_Establisher->m_SessionRequestBuffer, 64), boost::asio::transfer_all (),
std::bind(&NTCP2Session::HandleSessionRequestReceived, shared_from_this (),
@@ -1050,7 +1058,10 @@ namespace transport
SendRouterInfo ();
}
else
{
SendQueue ();
m_SendQueueSize = m_SendQueue.size ();
}
}
}
@@ -1158,7 +1169,7 @@ namespace transport
{
if (m_IsTerminated) return;
for (auto it: msgs)
m_SendQueue.push_back (it);
m_SendQueue.push_back (std::move (it));
if (!m_IsSending)
SendQueue ();
else if (m_SendQueue.size () > NTCP2_MAX_OUTGOING_QUEUE_SIZE)
@@ -1167,6 +1178,7 @@ namespace transport
GetIdentHashBase64(), " exceeds ", NTCP2_MAX_OUTGOING_QUEUE_SIZE);
Terminate ();
}
m_SendQueueSize = m_SendQueue.size ();
}
void NTCP2Session::SendLocalRouterInfo (bool update)
@@ -1210,8 +1222,9 @@ namespace transport
else
LogPrint(eLogInfo, "NTCP2: Proxy is not used");
// start acceptors
auto& addresses = context.GetRouterInfo ().GetAddresses ();
for (const auto& address: addresses)
auto addresses = context.GetRouterInfo ().GetAddresses ();
if (!addresses) return;
for (const auto& address: *addresses)
{
if (!address) continue;
if (address->IsPublishedNTCP2 () && address->port)
@@ -1288,7 +1301,7 @@ namespace transport
for (auto& it: ntcpSessions)
it.second->Terminate ();
for (auto& it: m_PendingIncomingSessions)
it->Terminate ();
it.second->Terminate ();
}
m_NTCP2Sessions.clear ();
@@ -1304,20 +1317,32 @@ namespace transport
{
if (!session) return false;
if (incoming)
m_PendingIncomingSessions.remove (session);
if (!session->GetRemoteIdentity ()) return false;
m_PendingIncomingSessions.erase (session->GetRemoteEndpoint ().address ());
if (!session->GetRemoteIdentity ())
{
LogPrint (eLogWarning, "NTCP2: Unknown identity for ", session->GetRemoteEndpoint ());
session->Terminate ();
return false;
}
auto& ident = session->GetRemoteIdentity ()->GetIdentHash ();
auto it = m_NTCP2Sessions.find (ident);
if (it != m_NTCP2Sessions.end ())
{
LogPrint (eLogWarning, "NTCP2: Session to ", ident.ToBase64 (), " already exists");
LogPrint (eLogWarning, "NTCP2: Session with ", ident.ToBase64 (), " already exists. ", incoming ? "Replaced" : "Dropped");
if (incoming)
{
// replace by new session
it->second->Terminate ();
auto s = it->second;
m_NTCP2Sessions.erase (it);
s->Terminate ();
}
else
{
session->Terminate ();
return false;
}
}
m_NTCP2Sessions.insert (std::make_pair (ident, session));
m_NTCP2Sessions.emplace (ident, session);
return true;
}
@@ -1412,19 +1437,35 @@ namespace transport
if (!ec)
{
LogPrint (eLogDebug, "NTCP2: Connected from ", ep);
if (conn)
if (!i2p::util::net::IsInReservedRange(ep.address ()))
{
conn->SetRemoteEndpoint (ep);
conn->ServerLogin ();
m_PendingIncomingSessions.push_back (conn);
conn = nullptr;
if (conn)
{
if (m_PendingIncomingSessions.emplace (ep.address (), conn).second)
{
conn->SetRemoteEndpoint (ep);
conn->ServerLogin ();
conn = nullptr;
}
else
LogPrint (eLogInfo, "NTCP2: Incoming session from ", ep.address (), " is already pending");
}
}
else
LogPrint (eLogError, "NTCP2: Incoming connection from invalid IP ", ep.address ());
}
else
LogPrint (eLogError, "NTCP2: Connected from error ", ec.message ());
}
else
{
LogPrint (eLogError, "NTCP2: Accept error ", error.message ());
if (error == boost::asio::error::no_descriptors)
{
i2p::context.SetError (eRouterErrorNoDescriptors);
return;
}
}
if (error != boost::asio::error::operation_aborted)
{
@@ -1446,20 +1487,43 @@ namespace transport
if (!ec)
{
LogPrint (eLogDebug, "NTCP2: Connected from ", ep);
if (conn)
if (!i2p::util::net::IsInReservedRange(ep.address ()) ||
i2p::util::net::IsYggdrasilAddress (ep.address ()))
{
conn->SetRemoteEndpoint (ep);
conn->ServerLogin ();
m_PendingIncomingSessions.push_back (conn);
if (conn)
{
if (m_PendingIncomingSessions.emplace (ep.address (), conn).second)
{
conn->SetRemoteEndpoint (ep);
conn->ServerLogin ();
conn = nullptr;
}
else
LogPrint (eLogInfo, "NTCP2: Incoming session from ", ep.address (), " is already pending");
}
}
else
LogPrint (eLogError, "NTCP2: Incoming connection from invalid IP ", ep.address ());
}
else
LogPrint (eLogError, "NTCP2: Connected from error ", ec.message ());
}
else
{
LogPrint (eLogError, "NTCP2: Accept ipv6 error ", error.message ());
if (error == boost::asio::error::no_descriptors)
{
i2p::context.SetErrorV6 (eRouterErrorNoDescriptors);
return;
}
}
if (error != boost::asio::error::operation_aborted)
{
conn = std::make_shared<NTCP2Session> (*this);
if (!conn) // connection is used, create new one
conn = std::make_shared<NTCP2Session> (*this);
else // reuse failed
conn->Close ();
m_NTCP2V6Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCP2Server::HandleAcceptV6, this,
conn, std::placeholders::_1));
}
@@ -1490,18 +1554,34 @@ namespace transport
// pending
for (auto it = m_PendingIncomingSessions.begin (); it != m_PendingIncomingSessions.end ();)
{
if ((*it)->IsEstablished () || (*it)->IsTerminationTimeoutExpired (ts))
if (it->second->IsEstablished () || it->second->IsTerminationTimeoutExpired (ts))
{
(*it)->Terminate ();
it->second->Terminate ();
it = m_PendingIncomingSessions.erase (it); // established of expired
}
else if ((*it)->IsTerminated ())
else if (it->second->IsTerminated ())
it = m_PendingIncomingSessions.erase (it); // already terminated
else
it++;
}
ScheduleTermination ();
// try to restart acceptors if no description
// we do it after timer to let timer take descriptor first
if (i2p::context.GetError () == eRouterErrorNoDescriptors)
{
i2p::context.SetError (eRouterErrorNone);
auto conn = std::make_shared<NTCP2Session> (*this);
m_NTCP2Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCP2Server::HandleAccept, this,
conn, std::placeholders::_1));
}
if (i2p::context.GetErrorV6 () == eRouterErrorNoDescriptors)
{
i2p::context.SetErrorV6 (eRouterErrorNone);
auto conn = std::make_shared<NTCP2Session> (*this);
m_NTCP2V6Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCP2Server::HandleAcceptV6, this,
conn, std::placeholders::_1));
}
}
}
@@ -1709,15 +1789,15 @@ namespace transport
});
boost::asio::async_read(conn->GetSocket(), boost::asio::buffer(readbuff->data (), SOCKS5_UDP_IPV4_REQUEST_HEADER_SIZE), // read min reply size
boost::asio::transfer_all(),
[timer, conn, sz, readbuff](const boost::system::error_code & e, std::size_t transferred)
boost::asio::transfer_all(),
[timer, conn, readbuff](const boost::system::error_code & e, std::size_t transferred)
{
if (e)
LogPrint(eLogError, "NTCP2: SOCKS proxy read error ", e.message());
else if (!(*readbuff)[1]) // succeeded
{
boost::system::error_code ec;
size_t moreBytes = conn->GetSocket ().available(ec);
size_t moreBytes = conn->GetSocket ().available(ec);
if (moreBytes) // read remaining portion of reply if ipv6 received
boost::asio::read (conn->GetSocket (), boost::asio::buffer(readbuff->data (), moreBytes), boost::asio::transfer_all (), ec);
timer->cancel();

View File

@@ -134,22 +134,22 @@ namespace transport
~NTCP2Session ();
void Terminate ();
void TerminateByTimeout ();
void Done ();
void Close () { m_Socket.close (); }; // for accept
void Done () override;
void Close (); // for accept
void DeleteNextReceiveBuffer (uint64_t ts);
boost::asio::ip::tcp::socket& GetSocket () { return m_Socket; };
const boost::asio::ip::tcp::endpoint& GetRemoteEndpoint () { return m_RemoteEndpoint; };
void SetRemoteEndpoint (const boost::asio::ip::tcp::endpoint& ep) { m_RemoteEndpoint = ep; };
bool IsEstablished () const { return m_IsEstablished; };
bool IsEstablished () const override { return m_IsEstablished; };
bool IsTerminated () const { return m_IsTerminated; };
void ClientLogin (); // Alice
void ServerLogin (); // Bob
void SendLocalRouterInfo (bool update); // after handshake or by update
void SendI2NPMessages (const std::vector<std::shared_ptr<I2NPMessage> >& msgs);
void SendLocalRouterInfo (bool update) override; // after handshake or by update
void SendI2NPMessages (const std::vector<std::shared_ptr<I2NPMessage> >& msgs) override;
private:
@@ -277,7 +277,7 @@ namespace transport
boost::asio::deadline_timer m_TerminationTimer;
std::unique_ptr<boost::asio::ip::tcp::acceptor> m_NTCP2Acceptor, m_NTCP2V6Acceptor;
std::map<i2p::data::IdentHash, std::shared_ptr<NTCP2Session> > m_NTCP2Sessions;
std::list<std::shared_ptr<NTCP2Session> > m_PendingIncomingSessions;
std::map<boost::asio::ip::address, std::shared_ptr<NTCP2Session> > m_PendingIncomingSessions;
ProxyType m_ProxyType;
std::string m_ProxyAddress, m_ProxyAuthorization;

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2022, The PurpleI2P Project
* Copyright (c) 2013-2023, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -295,7 +295,8 @@ namespace data
else
{
r = std::make_shared<RouterInfo> (buf, len);
if (!r->IsUnreachable () && r->HasValidAddresses ())
if (!r->IsUnreachable () && r->HasValidAddresses () &&
i2p::util::GetMillisecondsSinceEpoch () + NETDB_EXPIRATION_TIMEOUT_THRESHOLD*1000LL > r->GetTimestamp ())
{
bool inserted = false;
{
@@ -365,15 +366,16 @@ namespace data
bool NetDb::AddLeaseSet2 (const IdentHash& ident, const uint8_t * buf, int len, uint8_t storeType)
{
std::unique_lock<std::mutex> lock(m_LeaseSetsMutex);
auto leaseSet = std::make_shared<LeaseSet2> (storeType, buf, len, false); // we don't need leases in netdb
if (leaseSet->IsValid ())
{
std::unique_lock<std::mutex> lock(m_LeaseSetsMutex);
auto it = m_LeaseSets.find(ident);
if (it == m_LeaseSets.end () || it->second->GetStoreType () != storeType ||
leaseSet->GetPublishedTimestamp () > it->second->GetPublishedTimestamp ())
{
if (leaseSet->IsPublic () && !leaseSet->IsExpired ())
if (leaseSet->IsPublic () && !leaseSet->IsExpired () &&
i2p::util::GetSecondsSinceEpoch () + NETDB_EXPIRATION_TIMEOUT_THRESHOLD > leaseSet->GetPublishedTimestamp ())
{
// TODO: implement actual update
LogPrint (eLogInfo, "NetDb: LeaseSet2 updated: ", ident.ToBase32());
@@ -382,7 +384,7 @@ namespace data
}
else
{
LogPrint (eLogWarning, "NetDb: Unpublished or expired LeaseSet2 received: ", ident.ToBase32());
LogPrint (eLogWarning, "NetDb: Unpublished or expired or future LeaseSet2 received: ", ident.ToBase32());
m_LeaseSets.erase (ident);
}
}
@@ -494,7 +496,7 @@ namespace data
{
auto r = std::make_shared<RouterInfo>(path);
if (r->GetRouterIdentity () && !r->IsUnreachable () && r->HasValidAddresses () &&
ts < r->GetTimestamp () + 24*60*60*NETDB_MAX_OFFLINE_EXPIRATION_TIMEOUT*1000LL)
ts < r->GetTimestamp () + 24*60*60*NETDB_MAX_OFFLINE_EXPIRATION_TIMEOUT*1000LL) // too old
{
r->DeleteBuffer ();
if (m_RouterInfos.emplace (r->GetIdentHash (), r).second)
@@ -603,6 +605,7 @@ namespace data
uint64_t expirationTimeout = NETDB_MAX_EXPIRATION_TIMEOUT*1000LL;
uint64_t ts = i2p::util::GetMillisecondsSinceEpoch();
auto uptime = i2p::context.GetUptime ();
bool isLowRate = i2p::tunnel::tunnels.GetTunnelCreationSuccessRate () < NETDB_MIN_TUNNEL_CREATION_SUCCESS_RATE;
// routers don't expire if less than 90 or uptime is less than 1 hour
bool checkForExpiration = total > NETDB_MIN_ROUTERS && uptime > 600; // 10 minutes
if (checkForExpiration && uptime > 3600) // 1 hour
@@ -624,19 +627,24 @@ namespace data
continue;
}
// make router reachable back if too few routers or floodfills
if (it.second->IsUnreachable () && (total - deletedCount < NETDB_MIN_ROUTERS ||
if (it.second->IsUnreachable () && (total - deletedCount < NETDB_MIN_ROUTERS || isLowRate ||
(it.second->IsFloodfill () && totalFloodfills - deletedFloodfillsCount < NETDB_MIN_FLOODFILLS)))
it.second->SetUnreachable (false);
// find & mark expired routers
if (!it.second->IsReachable () && (it.second->GetCompatibleTransports (true) & (RouterInfo::eSSUV4 | RouterInfo::eSSU2V4)))
// non-reachable router, but reachable by ipv4 SSU or SSU2 means introducers
if (!it.second->IsReachable () && (it.second->GetCompatibleTransports (true) & RouterInfo::eSSU2V4))
// non-reachable router, but reachable by ipv4 SSU2 means introducers
{
if (ts > it.second->GetTimestamp () + NETDB_INTRODUCEE_EXPIRATION_TIMEOUT*1000LL)
// RouterInfo expires after 1 hour if uses introducer
it.second->SetUnreachable (true);
}
else if (checkForExpiration && ts > it.second->GetTimestamp () + expirationTimeout)
it.second->SetUnreachable (true);
it.second->SetUnreachable (true);
else if (ts + NETDB_EXPIRATION_TIMEOUT_THRESHOLD*1000LL < it.second->GetTimestamp ())
{
LogPrint (eLogWarning, "NetDb: RouterInfo is from future for ", (it.second->GetTimestamp () - ts)/1000LL, " seconds");
it.second->SetUnreachable (true);
}
if (it.second->IsUnreachable ())
{
@@ -649,6 +657,8 @@ namespace data
} // m_RouterInfos iteration
m_RouterInfoBuffersPool.CleanUpMt ();
m_RouterInfoAddressesPool.CleanUpMt ();
m_RouterInfoAddressVectorsPool.CleanUpMt ();
if (updatedCount > 0)
LogPrint (eLogInfo, "NetDb: Saved ", updatedCount, " new/updated routers");
@@ -749,6 +759,11 @@ namespace data
{
const uint8_t * buf = m->GetPayload ();
size_t len = m->GetSize ();
if (len < DATABASE_STORE_HEADER_SIZE)
{
LogPrint (eLogError, "NetDb: Database store msg is too short ", len, ". Dropped");
return;
}
IdentHash ident (buf + DATABASE_STORE_KEY_OFFSET);
if (ident.IsZero ())
{
@@ -759,6 +774,11 @@ namespace data
size_t offset = DATABASE_STORE_HEADER_SIZE;
if (replyToken)
{
if (len < offset + 36) // 32 + 4
{
LogPrint (eLogError, "NetDb: Database store msg with reply token is too short ", len, ". Dropped");
return;
}
auto deliveryStatus = CreateDeliveryStatusMsg (replyToken);
uint32_t tunnelID = bufbe32toh (buf + offset);
offset += 4;
@@ -787,6 +807,11 @@ namespace data
uint8_t storeType = buf[DATABASE_STORE_TYPE_OFFSET];
if (storeType) // LeaseSet or LeaseSet2
{
if (len > MAX_LS_BUFFER_SIZE + offset)
{
LogPrint (eLogError, "NetDb: Database store message is too long ", len);
return;
}
if (!m->from) // unsolicited LS must be received directly
{
if (storeType == NETDB_STORE_TYPE_LEASESET) // 1
@@ -1199,16 +1224,6 @@ namespace data
});
}
std::shared_ptr<const RouterInfo> NetDb::GetRandomPeerTestRouter (bool v4, const std::set<IdentHash>& excluded) const
{
return GetRandomRouter (
[v4, &excluded](std::shared_ptr<const RouterInfo> router)->bool
{
return !router->IsHidden () && router->IsECIES () &&
router->IsPeerTesting (v4) && !excluded.count (router->GetIdentHash ());
});
}
std::shared_ptr<const RouterInfo> NetDb::GetRandomSSU2PeerTestRouter (bool v4, const std::set<IdentHash>& excluded) const
{
return GetRandomRouter (
@@ -1219,25 +1234,6 @@ namespace data
});
}
std::shared_ptr<const RouterInfo> NetDb::GetRandomSSUV6Router () const
{
return GetRandomRouter (
[](std::shared_ptr<const RouterInfo> router)->bool
{
return !router->IsHidden () && router->IsECIES () && router->IsSSUV6 ();
});
}
std::shared_ptr<const RouterInfo> NetDb::GetRandomIntroducer (bool v4, const std::set<IdentHash>& excluded) const
{
return GetRandomRouter (
[v4, &excluded](std::shared_ptr<const RouterInfo> router)->bool
{
return !router->IsHidden () && router->IsECIES () && !router->IsFloodfill () && // floodfills don't send relay tag
router->IsIntroducer (v4) && !excluded.count (router->GetIdentHash ());
});
}
std::shared_ptr<const RouterInfo> NetDb::GetRandomSSU2Introducer (bool v4, const std::set<IdentHash>& excluded) const
{
return GetRandomRouter (

View File

@@ -38,11 +38,13 @@ namespace data
{
const int NETDB_MIN_ROUTERS = 90;
const int NETDB_MIN_FLOODFILLS = 5;
const int NETDB_MIN_TUNNEL_CREATION_SUCCESS_RATE = 8; // in percents
const int NETDB_FLOODFILL_EXPIRATION_TIMEOUT = 60 * 60; // 1 hour, in seconds
const int NETDB_INTRODUCEE_EXPIRATION_TIMEOUT = 65 * 60;
const int NETDB_MIN_EXPIRATION_TIMEOUT = 90 * 60; // 1.5 hours
const int NETDB_MAX_EXPIRATION_TIMEOUT = 27 * 60 * 60; // 27 hours
const int NETDB_MAX_OFFLINE_EXPIRATION_TIMEOUT = 180; // in days
const int NETDB_EXPIRATION_TIMEOUT_THRESHOLD = 2*60; // 2 minutes
const int NETDB_PUBLISH_INTERVAL = 60 * 40;
const int NETDB_PUBLISH_CONFIRMATION_TIMEOUT = 5; // in seconds
const int NETDB_MAX_PUBLISH_EXCLUDED_FLOODFILLS = 15;
@@ -89,10 +91,7 @@ namespace data
std::shared_ptr<const RouterInfo> GetRandomRouter () const;
std::shared_ptr<const RouterInfo> GetRandomRouter (std::shared_ptr<const RouterInfo> compatibleWith, bool reverse) const;
std::shared_ptr<const RouterInfo> GetHighBandwidthRandomRouter (std::shared_ptr<const RouterInfo> compatibleWith, bool reverse) const;
std::shared_ptr<const RouterInfo> GetRandomPeerTestRouter (bool v4, const std::set<IdentHash>& excluded) const;
std::shared_ptr<const RouterInfo> GetRandomSSU2PeerTestRouter (bool v4, const std::set<IdentHash>& excluded) const;
std::shared_ptr<const RouterInfo> GetRandomSSUV6Router () const; // TODO: change to v6 peer test later
std::shared_ptr<const RouterInfo> GetRandomIntroducer (bool v4, const std::set<IdentHash>& excluded) const;
std::shared_ptr<const RouterInfo> GetRandomSSU2Introducer (bool v4, const std::set<IdentHash>& excluded) const;
std::shared_ptr<const RouterInfo> GetClosestFloodfill (const IdentHash& destination, const std::set<IdentHash>& excluded, bool closeThanUsOnly = false) const;
std::vector<IdentHash> GetClosestFloodfills (const IdentHash& destination, size_t num,
@@ -126,6 +125,14 @@ namespace data
void ClearRouterInfos () { m_RouterInfos.clear (); };
std::shared_ptr<RouterInfo::Buffer> NewRouterInfoBuffer () { return m_RouterInfoBuffersPool.AcquireSharedMt (); };
void PopulateRouterInfoBuffer (std::shared_ptr<RouterInfo> r);
std::shared_ptr<RouterInfo::Address> NewRouterInfoAddress () { return m_RouterInfoAddressesPool.AcquireSharedMt (); };
boost::shared_ptr<RouterInfo::Addresses> NewRouterInfoAddresses ()
{
return boost::shared_ptr<RouterInfo::Addresses>(m_RouterInfoAddressVectorsPool.AcquireMt (),
std::bind <void (i2p::util::MemoryPoolMt<RouterInfo::Addresses>::*)(RouterInfo::Addresses *)>
(&i2p::util::MemoryPoolMt<RouterInfo::Addresses>::ReleaseMt,
&m_RouterInfoAddressVectorsPool, std::placeholders::_1));
};
std::shared_ptr<Lease> NewLease (const Lease& lease) { return m_LeasesPool.AcquireSharedMt (lease); };
uint32_t GetPublishReplyToken () const { return m_PublishReplyToken; };
@@ -183,6 +190,8 @@ namespace data
uint32_t m_PublishReplyToken = 0;
i2p::util::MemoryPoolMt<RouterInfo::Buffer> m_RouterInfoBuffersPool;
i2p::util::MemoryPoolMt<RouterInfo::Address> m_RouterInfoAddressesPool;
i2p::util::MemoryPoolMt<RouterInfo::Addresses> m_RouterInfoAddressVectorsPool;
i2p::util::MemoryPoolMt<Lease> m_LeasesPool;
};

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2020, The PurpleI2P Project
* Copyright (c) 2013-2023, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -12,6 +12,7 @@
#include "Base.h"
#include "FS.h"
#include "Log.h"
#include "Timestamp.h"
#include "Profiling.h"
namespace i2p
@@ -22,6 +23,7 @@ namespace data
RouterProfile::RouterProfile ():
m_LastUpdateTime (boost::posix_time::second_clock::local_time()),
m_LastDeclineTime (0),
m_NumTunnelsAgreed (0), m_NumTunnelsDeclined (0), m_NumTunnelsNonReplied (0),
m_NumTimesTaken (0), m_NumTimesRejected (0)
{
@@ -131,15 +133,23 @@ namespace data
{
UpdateTime ();
if (ret > 0)
{
m_NumTunnelsDeclined++;
m_LastDeclineTime = i2p::util::GetSecondsSinceEpoch ();
}
else
{
m_NumTunnelsAgreed++;
m_LastDeclineTime = 0;
}
}
void RouterProfile::TunnelNonReplied ()
{
m_NumTunnelsNonReplied++;
UpdateTime ();
if (m_NumTunnelsNonReplied > 2*m_NumTunnelsAgreed && m_NumTunnelsNonReplied > 3)
m_LastDeclineTime = i2p::util::GetSecondsSinceEpoch ();
}
bool RouterProfile::IsLowPartcipationRate () const
@@ -153,8 +163,18 @@ namespace data
return m_NumTunnelsNonReplied > 10*(total + 1);
}
bool RouterProfile::IsDeclinedRecently ()
{
if (!m_LastDeclineTime) return false;
auto ts = i2p::util::GetSecondsSinceEpoch ();
if (ts > m_LastDeclineTime + PEER_PROFILE_DECLINED_RECENTLY_INTERVAL)
m_LastDeclineTime = 0;
return m_LastDeclineTime;
}
bool RouterProfile::IsBad ()
{
if (IsDeclinedRecently ()) return true;
auto isBad = IsAlwaysDeclining () || IsLowPartcipationRate () /*|| IsLowReplyRate ()*/;
if (isBad && m_NumTimesRejected > 10*(m_NumTimesTaken + 1))
{

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2022, The PurpleI2P Project
* Copyright (c) 2013-2023, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -31,6 +31,7 @@ namespace data
const int PEER_PROFILE_EXPIRATION_TIMEOUT = 72; // in hours (3 days)
const int PEER_PROFILE_AUTOCLEAN_TIMEOUT = 24 * 3600; // in seconds (1 day)
const int PEER_PROFILE_AUTOCLEAN_VARIANCE = 3 * 3600; // in seconds (3 hours)
const int PEER_PROFILE_DECLINED_RECENTLY_INTERVAL = 150; // in seconds (2.5 minutes)
class RouterProfile
{
@@ -55,10 +56,12 @@ namespace data
bool IsAlwaysDeclining () const { return !m_NumTunnelsAgreed && m_NumTunnelsDeclined >= 5; };
bool IsLowPartcipationRate () const;
bool IsLowReplyRate () const;
bool IsDeclinedRecently ();
private:
boost::posix_time::ptime m_LastUpdateTime;
boost::posix_time::ptime m_LastUpdateTime; // TODO: use std::chrono
uint64_t m_LastDeclineTime; // in seconds
// participation
uint32_t m_NumTunnelsAgreed;
uint32_t m_NumTunnelsDeclined;

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2022, The PurpleI2P Project
* Copyright (c) 2013-2023, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -63,7 +63,6 @@ namespace i2p
if (!port) port = SelectRandomPort ();
bool ipv4; i2p::config::GetOption("ipv4", ipv4);
bool ipv6; i2p::config::GetOption("ipv6", ipv6);
bool ssu; i2p::config::GetOption("ssu", ssu);
bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2);
bool ssu2; i2p::config::GetOption("ssu2.enabled", ssu2);
bool ygg; i2p::config::GetOption("meshnets.yggdrasil", ygg);
@@ -109,17 +108,12 @@ namespace i2p
routerInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv);
}
}
if (ssu)
{
routerInfo.AddSSUAddress (host.c_str(), port, nullptr);
caps |= i2p::data::RouterInfo::eReachable; // R
}
if (ssu2)
{
if (ssu2Published)
{
uint16_t ssu2Port; i2p::config::GetOption ("ssu2.port", ssu2Port);
if (!ssu2Port) ssu2Port = ssu ? (port + 1) : port;
if (!ssu2Port) ssu2Port = port;
routerInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, boost::asio::ip::address_v4::from_string (host), ssu2Port);
}
else
@@ -131,7 +125,7 @@ namespace i2p
}
if (ipv6)
{
std::string host = "::1";
std::string host;
if (!i2p::config::IsDefault("host") && !ipv4) // override if v6 only
i2p::config::GetOption("host", host);
else
@@ -142,6 +136,7 @@ namespace i2p
if (ntcp2)
{
bool added = false;
if (ntcp2Published)
{
std::string ntcp2Host;
@@ -149,29 +144,33 @@ namespace i2p
i2p::config::GetOption ("ntcp2.addressv6", ntcp2Host);
else
ntcp2Host = host;
routerInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, boost::asio::ip::address_v6::from_string (ntcp2Host), port);
if (!ntcp2Host.empty () && port)
{
routerInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, boost::asio::ip::address_v6::from_string (ntcp2Host), port);
added = true;
}
}
else
if (!added)
{
if (!ipv4) // no other ntcp2 addresses yet
routerInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv);
addressCaps |= i2p::data::RouterInfo::AddressCaps::eV6;
}
}
if (ssu)
{
routerInfo.AddSSUAddress (host.c_str(), port, nullptr);
caps |= i2p::data::RouterInfo::eReachable; // R
}
if (ssu2)
{
bool added = false;
if (ssu2Published)
{
uint16_t ssu2Port; i2p::config::GetOption ("ssu2.port", ssu2Port);
if (!ssu2Port) ssu2Port = ssu ? (port + 1) : port;
routerInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, boost::asio::ip::address_v6::from_string (host), ssu2Port);
if (!ssu2Port) ssu2Port = port;
if (!host.empty () && ssu2Port)
{
routerInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, boost::asio::ip::address_v6::from_string (host), ssu2Port);
added = true;
}
}
else
if (!added)
{
if (!ipv4) // no other ssu2 addresses yet
routerInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro);
@@ -236,19 +235,11 @@ namespace i2p
fk.write ((char *)m_SSU2Keys.get (), sizeof (SSU2PrivateKeys));
}
bool RouterContext::IsSSU2Only () const
{
auto transports = m_RouterInfo.GetCompatibleTransports (false);
return (transports & (i2p::data::RouterInfo::eSSU2V4 | i2p::data::RouterInfo::eSSU2V6)) &&
!(transports & (i2p::data::RouterInfo::eSSUV4 | i2p::data::RouterInfo::eSSUV6));
}
void RouterContext::SetStatus (RouterStatus status)
{
if (status != m_Status)
{
m_Status = status;
m_Error = eRouterErrorNone;
switch (m_Status)
{
case eRouterStatusOK:
@@ -257,24 +248,20 @@ namespace i2p
case eRouterStatusFirewalled:
SetUnreachable (true, false); // ipv4
break;
case eRouterStatusTesting:
m_Error = eRouterErrorNone;
break;
default:
;
}
}
}
void RouterContext::SetStatusSSU2 (RouterStatus status)
{
if (IsSSU2Only ())
SetStatus (status);
}
void RouterContext::SetStatusV6 (RouterStatus status)
{
if (status != m_StatusV6)
{
m_StatusV6 = status;
m_ErrorV6 = eRouterErrorNone;
switch (m_StatusV6)
{
case eRouterStatusOK:
@@ -283,24 +270,23 @@ namespace i2p
case eRouterStatusFirewalled:
SetUnreachable (false, true); // ipv6
break;
case eRouterStatusTesting:
m_ErrorV6 = eRouterErrorNone;
break;
default:
;
}
}
}
void RouterContext::SetStatusV6SSU2 (RouterStatus status)
{
if (IsSSU2Only ())
SetStatusV6 (status);
}
void RouterContext::UpdatePort (int port)
{
auto addresses = m_RouterInfo.GetAddresses ();
if (!addresses) return;
bool updated = false;
for (auto& address : m_RouterInfo.GetAddresses ())
for (auto& address : *addresses)
{
if (address->port != port && (address->transportStyle == i2p::data::RouterInfo::eTransportSSU || IsSSU2Only ()))
if (address && address->port != port && address->transportStyle == i2p::data::RouterInfo::eTransportSSU2)
{
address->port = port;
updated = true;
@@ -313,10 +299,12 @@ namespace i2p
void RouterContext::PublishNTCP2Address (int port, bool publish, bool v4, bool v6, bool ygg)
{
if (!m_NTCP2Keys) return;
auto addresses = m_RouterInfo.GetAddresses ();
if (!addresses) return;
bool updated = false;
for (auto& address : m_RouterInfo.GetAddresses ())
for (auto& address : *addresses)
{
if (address->IsNTCP2 () && (address->port != port || address->published != publish))
if (address && address->IsNTCP2 () && (address->port != port || address->published != publish))
{
bool isAddr = v4 && address->IsV4 ();
if (!isAddr && (v6 || ygg))
@@ -342,25 +330,23 @@ namespace i2p
void RouterContext::UpdateNTCP2Address (bool enable)
{
auto& addresses = m_RouterInfo.GetAddresses ();
auto addresses = m_RouterInfo.GetAddresses ();
if (!addresses) return;
bool found = false, updated = false;
for (auto it = addresses.begin (); it != addresses.end ();)
for (auto& it: *addresses)
{
if ((*it)->IsNTCP2 ())
if (it && it->IsNTCP2 ())
{
found = true;
if (enable)
{
(*it)->s = m_NTCP2Keys->staticPublicKey;
memcpy ((*it)->i, m_NTCP2Keys->iv, 16);
it++;
it->s = m_NTCP2Keys->staticPublicKey;
memcpy (it->i, m_NTCP2Keys->iv, 16);
}
else
it = addresses.erase (it);
it.reset ();
updated = true;
}
else
it++;
}
if (enable && !found)
{
@@ -374,11 +360,13 @@ namespace i2p
void RouterContext::PublishSSU2Address (int port, bool publish, bool v4, bool v6)
{
if (!m_SSU2Keys) return;
auto addresses = m_RouterInfo.GetAddresses ();
if (!addresses) return;
int newPort = 0;
if (!port)
{
for (const auto& address : m_RouterInfo.GetAddresses ())
if (address->port)
for (const auto& address : *addresses)
if (address && address->port)
{
newPort = address->port;
break;
@@ -386,9 +374,9 @@ namespace i2p
if (!newPort) newPort = SelectRandomPort ();
}
bool updated = false;
for (auto& address : m_RouterInfo.GetAddresses ())
for (auto& address : *addresses)
{
if (address->IsSSU2 () && (!address->port || address->port != port || address->published != publish) &&
if (address && address->IsSSU2 () && (!address->port || address->port != port || address->published != publish) &&
((v4 && address->IsV4 ()) || (v6 && address->IsV6 ())))
{
if (port) address->port = port;
@@ -407,25 +395,23 @@ namespace i2p
void RouterContext::UpdateSSU2Address (bool enable)
{
auto& addresses = m_RouterInfo.GetAddresses ();
auto addresses = m_RouterInfo.GetAddresses ();
if (!addresses) return;
bool found = false, updated = false;
for (auto it = addresses.begin (); it != addresses.end ();)
for (auto& it : *addresses)
{
if ((*it)->IsSSU2 ())
if (it && it->IsSSU2 ())
{
found = true;
if (enable)
{
(*it)->s = m_SSU2Keys->staticPublicKey;
(*it)->i = m_SSU2Keys->intro;
it++;
it->s = m_SSU2Keys->staticPublicKey;
it->i = m_SSU2Keys->intro;
}
else
it = addresses.erase (it);
it.reset ();
updated = true;
}
else
it++;
}
if (enable && !found)
{
@@ -452,59 +438,63 @@ namespace i2p
void RouterContext::UpdateAddress (const boost::asio::ip::address& host)
{
auto addresses = m_RouterInfo.GetAddresses ();
if (!addresses) return;
bool updated = false;
for (auto& address : m_RouterInfo.GetAddresses ())
if (host.is_v4 ())
{
if (address->host != host && address->IsCompatible (host) &&
!i2p::util::net::IsYggdrasilAddress (address->host))
auto addr = (*addresses)[i2p::data::RouterInfo::eNTCP2V4Idx];
if (addr && addr->host != host)
{
// update host
address->host = host;
addr->host = host;
updated = true;
}
if (host.is_v6 () && address->IsV6 () && address->ssu &&
(!address->ssu->mtu || updated) && m_StatusV6 != eRouterStatusProxy)
addr = (*addresses)[i2p::data::RouterInfo::eSSU2V4Idx];
if (addr && addr->host != host)
{
// update MTU
auto mtu = i2p::util::net::GetMTU (host);
if (mtu)
{
LogPrint (eLogDebug, "Router: Our v6 MTU=", mtu);
int maxMTU = i2p::util::net::GetMaxMTU (host.to_v6 ());
if (mtu > maxMTU)
{
mtu = maxMTU;
LogPrint(eLogWarning, "Router: MTU dropped to upper limit of ", maxMTU, " bytes");
}
if (mtu && !address->IsSSU2 ()) // SSU1
mtu = (mtu >> 4) << 4; // round to multiple of 16
address->ssu->mtu = mtu;
updated = true;
}
addr->host = host;
updated = true;
}
}
else if (host.is_v6 ())
{
auto addr = (*addresses)[i2p::data::RouterInfo::eNTCP2V6Idx];
if (addr && addr->host != host)
{
addr->host = host;
updated = true;
}
addr = (*addresses)[i2p::data::RouterInfo::eSSU2V6Idx];
if (addr && (addr->host != host || !addr->ssu->mtu))
{
addr->host = host;
if (m_StatusV6 != eRouterStatusProxy)
{
// update MTU
auto mtu = i2p::util::net::GetMTU (host);
if (mtu)
{
LogPrint (eLogDebug, "Router: Our v6 MTU=", mtu);
int maxMTU = i2p::util::net::GetMaxMTU (host.to_v6 ());
if (mtu > maxMTU)
{
mtu = maxMTU;
LogPrint(eLogWarning, "Router: MTU dropped to upper limit of ", maxMTU, " bytes");
}
addr->ssu->mtu = mtu;
}
}
updated = true;
}
}
auto ts = i2p::util::GetSecondsSinceEpoch ();
if (updated || ts > m_LastUpdateTime + ROUTER_INFO_UPDATE_INTERVAL)
UpdateRouterInfo ();
}
bool RouterContext::AddIntroducer (const i2p::data::RouterInfo::Introducer& introducer)
{
bool ret = m_RouterInfo.AddIntroducer (introducer);
if (ret)
UpdateRouterInfo ();
return ret;
}
void RouterContext::RemoveIntroducer (const boost::asio::ip::udp::endpoint& e)
{
if (m_RouterInfo.RemoveIntroducer (e))
UpdateRouterInfo ();
}
bool RouterContext::AddSSU2Introducer (const i2p::data::RouterInfo::Introducer& introducer, bool v4)
{
if (!IsSSU2Only ()) return false;
bool ret = m_RouterInfo.AddSSU2Introducer (introducer, v4);
if (ret)
UpdateRouterInfo ();
@@ -513,24 +503,18 @@ namespace i2p
void RouterContext::RemoveSSU2Introducer (const i2p::data::IdentHash& h, bool v4)
{
if (!IsSSU2Only ()) return;
if (m_RouterInfo.RemoveSSU2Introducer (h, v4))
UpdateRouterInfo ();
}
void RouterContext::ClearSSU2Introducers (bool v4)
{
bool updated = false;
auto& addresses = m_RouterInfo.GetAddresses ();
for (auto& addr : addresses)
if (addr->IsSSU2 () && ((v4 && addr->IsV4 ()) || (!v4 && addr->IsV6 ())) &&
addr->ssu && !addr->ssu->introducers.empty ())
{
addr->ssu->introducers.clear ();
updated = true;
}
if (updated)
auto addr = m_RouterInfo.GetSSU2Address (v4);
if (addr && !addr->ssu->introducers.empty ())
{
addr->ssu->introducers.clear ();
UpdateRouterInfo ();
}
}
void RouterContext::SetFloodfill (bool floodfill)
@@ -631,50 +615,6 @@ namespace i2p
return m_RouterInfo.GetCaps () & i2p::data::RouterInfo::eUnreachable;
}
void RouterContext::RemoveNTCPAddress (bool v4only)
{
bool updated = false;
auto& addresses = m_RouterInfo.GetAddresses ();
for (auto it = addresses.begin (); it != addresses.end ();)
{
if ((*it)->transportStyle == i2p::data::RouterInfo::eTransportNTCP && !(*it)->IsNTCP2 () &&
(!v4only || (*it)->host.is_v4 ()))
{
it = addresses.erase (it);
updated = true;
if (v4only) break; // otherwise might be more than one address
}
else
++it;
}
if (updated)
m_RouterInfo.UpdateSupportedTransports ();
}
void RouterContext::RemoveSSUAddress ()
{
bool updated = false;
auto& addresses = m_RouterInfo.GetAddresses ();
for (auto it = addresses.begin (); it != addresses.end ();)
{
if ((*it)->transportStyle == i2p::data::RouterInfo::eTransportSSU)
{
it = addresses.erase (it);
updated = true;
}
else
++it;
}
if (updated)
m_RouterInfo.UpdateSupportedTransports ();
}
void RouterContext::SetUnreachableSSU2 (bool v4, bool v6)
{
if (IsSSU2Only ())
SetUnreachable (v4, v6);
}
void RouterContext::SetUnreachable (bool v4, bool v6)
{
if (v4 || (v6 && !SupportsV4 ()))
@@ -689,16 +629,18 @@ namespace i2p
}
uint16_t port = 0;
// delete previous introducers
auto& addresses = m_RouterInfo.GetAddresses ();
for (auto& addr : addresses)
if (addr->ssu && (!addr->IsSSU2 () || IsSSU2Only ()) &&
((v4 && addr->IsV4 ()) || (v6 && addr->IsV6 ())))
{
addr->published = false;
addr->caps &= ~i2p::data::RouterInfo::eSSUIntroducer; // can't be introducer
addr->ssu->introducers.clear ();
port = addr->port;
}
auto addresses = m_RouterInfo.GetAddresses ();
if (addresses)
{
for (auto& addr : *addresses)
if (addr && addr->ssu && ((v4 && addr->IsV4 ()) || (v6 && addr->IsV6 ())))
{
addr->published = false;
addr->caps &= ~i2p::data::RouterInfo::eSSUIntroducer; // can't be introducer
addr->ssu->introducers.clear ();
port = addr->port;
}
}
// unpublish NTCP2 addreeses
bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2);
if (ntcp2)
@@ -722,20 +664,19 @@ namespace i2p
}
uint16_t port = 0;
// delete previous introducers
bool isSSU2Published = IsSSU2Only (); // TODO
if (isSSU2Published)
i2p::config::GetOption ("ssu2.published", isSSU2Published);
auto& addresses = m_RouterInfo.GetAddresses ();
for (auto& addr : addresses)
if (addr->ssu && (!addr->IsSSU2 () || isSSU2Published) &&
((v4 && addr->IsV4 ()) || (v6 && addr->IsV6 ())))
{
addr->published = true;
addr->caps |= i2p::data::RouterInfo::eSSUIntroducer;
addr->ssu->introducers.clear ();
if (addr->port && (!addr->IsSSU2 () || IsSSU2Only ()))
port = addr->port;
}
bool isSSU2Published; i2p::config::GetOption ("ssu2.published", isSSU2Published);
auto addresses = m_RouterInfo.GetAddresses ();
if (addresses)
{
for (auto& addr : *addresses)
if (addr && addr->ssu && isSSU2Published && ((v4 && addr->IsV4 ()) || (v6 && addr->IsV6 ())))
{
addr->published = true;
addr->caps |= i2p::data::RouterInfo::eSSUIntroducer;
addr->ssu->introducers.clear ();
if (addr->port) port = addr->port;
}
}
// publish NTCP2
bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2);
if (ntcp2)
@@ -758,41 +699,34 @@ namespace i2p
if (supportsV6)
{
// insert v6 addresses if necessary
bool foundSSU = false, foundNTCP2 = false, foundSSU2 = false;
bool foundNTCP2 = false, foundSSU2 = false;
uint16_t port = 0;
auto& addresses = m_RouterInfo.GetAddresses ();
for (auto& addr: addresses)
auto addresses = m_RouterInfo.GetAddresses ();
if (addresses)
{
if (addr->IsV6 () && !i2p::util::net::IsYggdrasilAddress (addr->host))
for (auto& addr: *addresses)
{
switch (addr->transportStyle)
if (addr && addr->IsV6 () && !i2p::util::net::IsYggdrasilAddress (addr->host))
{
case i2p::data::RouterInfo::eTransportSSU:
foundSSU = true;
break;
case i2p::data::RouterInfo::eTransportNTCP:
foundNTCP2 = true;
break;
case i2p::data::RouterInfo::eTransportSSU2:
foundSSU2 = true;
break;
default: ;
switch (addr->transportStyle)
{
case i2p::data::RouterInfo::eTransportNTCP2:
foundNTCP2 = true;
break;
case i2p::data::RouterInfo::eTransportSSU2:
foundSSU2 = true;
break;
default: ;
}
}
if (addr) port = addr->port;
}
port = addr->port;
}
if (!port)
{
i2p::config::GetOption("port", port);
if (!port) port = SelectRandomPort ();
}
// SSU
bool ssu; i2p::config::GetOption("ssu", ssu);
if (!foundSSU && ssu)
{
std::string host = "::1"; // TODO: read host
m_RouterInfo.AddSSUAddress (host.c_str (), port, nullptr);
}
// NTCP2
if (!foundNTCP2)
{
@@ -825,7 +759,7 @@ namespace i2p
if (ssu2Published)
{
uint16_t ssu2Port; i2p::config::GetOption ("ssu2.port", ssu2Port);
if (!ssu2Port) ssu2Port = ssu ? (port + 1) : port;
if (!ssu2Port) ssu2Port = port;
m_RouterInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, boost::asio::ip::address::from_string ("::1"), ssu2Port);
}
else
@@ -847,40 +781,35 @@ namespace i2p
// update
if (supportsV4)
{
bool foundSSU = false, foundNTCP2 = false, foundSSU2 = false;
bool foundNTCP2 = false, foundSSU2 = false;
std::string host = "127.0.0.1";
uint16_t port = 0;
auto& addresses = m_RouterInfo.GetAddresses ();
for (auto& addr: addresses)
auto addresses = m_RouterInfo.GetAddresses ();
if (addresses)
{
if (addr->IsV4 ())
for (auto& addr: *addresses)
{
switch (addr->transportStyle)
if (addr && addr->IsV4 ())
{
case i2p::data::RouterInfo::eTransportSSU:
foundSSU = true;
break;
case i2p::data::RouterInfo::eTransportNTCP:
foundNTCP2 = true;
break;
case i2p::data::RouterInfo::eTransportSSU2:
foundSSU2 = true;
break;
default: ;
switch (addr->transportStyle)
{
case i2p::data::RouterInfo::eTransportNTCP2:
foundNTCP2 = true;
break;
case i2p::data::RouterInfo::eTransportSSU2:
foundSSU2 = true;
break;
default: ;
}
}
if (addr && addr->port) port = addr->port;
}
if (addr->port) port = addr->port;
}
if (!port)
{
i2p::config::GetOption("port", port);
if (!port) port = SelectRandomPort ();
}
// SSU
bool ssu; i2p::config::GetOption("ssu", ssu);
if (!foundSSU && ssu)
m_RouterInfo.AddSSUAddress (host.c_str (), port, nullptr);
// NTCP2
if (!foundNTCP2)
{
@@ -908,7 +837,7 @@ namespace i2p
if (ssu2Published)
{
uint16_t ssu2Port; i2p::config::GetOption ("ssu2.port", ssu2Port);
if (!ssu2Port) ssu2Port = ssu ? (port + 1) : port;
if (!ssu2Port) ssu2Port = port;
m_RouterInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, boost::asio::ip::address::from_string ("127.0.0.1"), ssu2Port);
}
else
@@ -926,23 +855,26 @@ namespace i2p
{
if (supportsmesh)
{
auto addresses = m_RouterInfo.GetAddresses ();
if (!addresses) return;
m_RouterInfo.EnableMesh ();
if ((*addresses)[i2p::data::RouterInfo::eNTCP2V6MeshIdx]) return; // we have mesh address already
uint16_t port = 0;
i2p::config::GetOption ("ntcp2.port", port);
if (!port) i2p::config::GetOption("port", port);
bool foundMesh = false;
auto& addresses = m_RouterInfo.GetAddresses ();
for (auto& addr: addresses)
if (!port)
{
if (!port) port = addr->port;
if (i2p::util::net::IsYggdrasilAddress (addr->host))
for (auto& addr: *addresses)
{
foundMesh = true;
break;
if (addr && addr->port)
{
port = addr->port;
break;
}
}
}
if (!foundMesh)
m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, host, port);
if (!port) port = SelectRandomPort ();
m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, host, port);
}
else
m_RouterInfo.DisableMesh ();
@@ -952,36 +884,14 @@ namespace i2p
void RouterContext::SetMTU (int mtu, bool v4)
{
if (mtu < 1280 || mtu > 1500) return;
auto& addresses = m_RouterInfo.GetAddresses ();
for (auto& addr: addresses)
auto addresses = m_RouterInfo.GetAddresses ();
if (!addresses) return;
for (auto& addr: *addresses)
{
if (addr->ssu && ((v4 && addr->IsV4 ()) || (!v4 && addr->IsV6 ())))
if (addr && addr->ssu && ((v4 && addr->IsV4 ()) || (!v4 && addr->IsV6 ())))
{
if (!addr->IsSSU2 ()) // SSU1
{
// round to multiple of 16
if (v4)
{
if (mtu > 1484) mtu = 1484;
else
{
mtu -= 12;
mtu = (mtu >> 4) << 4;
mtu += 12;
}
}
else
{
if (mtu > 1488) mtu = 1488;
else
mtu = (mtu >> 4) << 4;
}
}
if (mtu)
{
addr->ssu->mtu = mtu;
LogPrint (eLogDebug, "Router: MTU for ", v4 ? "ipv4" : "ipv6", " address ", addr->host.to_string(), " is set to ", mtu);
}
addr->ssu->mtu = mtu;
LogPrint (eLogDebug, "Router: MTU for ", v4 ? "ipv4" : "ipv6", " address ", addr->host.to_string(), " is set to ", mtu);
}
}
}
@@ -990,10 +900,11 @@ namespace i2p
{
bool isYgg = i2p::util::net::IsYggdrasilAddress (host);
bool updated = false;
auto& addresses = m_RouterInfo.GetAddresses ();
for (auto& addr: addresses)
auto addresses = m_RouterInfo.GetAddresses ();
if (!addresses) return;
for (auto& addr: *addresses)
{
if (addr->IsPublishedNTCP2 ())
if (addr && addr->IsPublishedNTCP2 ())
{
bool isYgg1 = i2p::util::net::IsYggdrasilAddress (addr->host);
if (addr->IsV6 () && ((isYgg && isYgg1) || (!isYgg && !isYgg1)))

View File

@@ -47,7 +47,9 @@ namespace garlic
eRouterErrorNone = 0,
eRouterErrorClockSkew = 1,
eRouterErrorOffline = 2,
eRouterErrorSymmetricNAT = 3
eRouterErrorSymmetricNAT = 3,
eRouterErrorFullConeNAT = 4,
eRouterErrorNoDescriptors = 5
};
class RouterContext: public i2p::garlic::GarlicDestination
@@ -102,12 +104,10 @@ namespace garlic
uint64_t GetTransitBandwidthLimit () const { return (m_BandwidthLimit*m_ShareRatio)/100LL; };
RouterStatus GetStatus () const { return m_Status; };
void SetStatus (RouterStatus status);
void SetStatusSSU2 (RouterStatus status);
RouterError GetError () const { return m_Error; };
void SetError (RouterError error) { m_Error = error; };
RouterStatus GetStatusV6 () const { return m_StatusV6; };
void SetStatusV6 (RouterStatus status);
void SetStatusV6SSU2 (RouterStatus status);
RouterError GetErrorV6 () const { return m_ErrorV6; };
void SetErrorV6 (RouterError error) { m_ErrorV6 = error; };
int GetNetID () const { return m_NetID; };
@@ -121,16 +121,11 @@ namespace garlic
void UpdateNTCP2Address (bool enable);
void PublishSSU2Address (int port, bool publish, bool v4, bool v6);
void UpdateSSU2Address (bool enable);
void RemoveNTCPAddress (bool v4only = true); // delete NTCP address for older routers. TODO: remove later
void RemoveSSUAddress (); // delete SSU address for older routers
bool AddIntroducer (const i2p::data::RouterInfo::Introducer& introducer);
void RemoveIntroducer (const boost::asio::ip::udp::endpoint& e);
bool AddSSU2Introducer (const i2p::data::RouterInfo::Introducer& introducer, bool v4);
void RemoveSSU2Introducer (const i2p::data::IdentHash& h, bool v4);
void ClearSSU2Introducers (bool v4);
bool IsUnreachable () const;
void SetUnreachable (bool v4, bool v6);
void SetUnreachableSSU2 (bool v4, bool v6);
void SetReachable (bool v4, bool v6);
bool IsFloodfill () const { return m_IsFloodfill; };
void SetFloodfill (bool floodfill);

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2022, The PurpleI2P Project
* Copyright (c) 2013-2023, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -206,26 +206,24 @@ namespace data
s.read ((char *)&m_Timestamp, sizeof (m_Timestamp));
m_Timestamp = be64toh (m_Timestamp);
// read addresses
auto addresses = boost::make_shared<Addresses>();
auto addresses = NewAddresses ();
uint8_t numAddresses;
s.read ((char *)&numAddresses, sizeof (numAddresses));
addresses->reserve (numAddresses);
for (int i = 0; i < numAddresses; i++)
{
uint8_t supportedTransports = 0;
auto address = std::make_shared<Address> ();
auto address = NewAddress ();
uint8_t cost; // ignore
s.read ((char *)&cost, sizeof (cost));
s.read ((char *)&address->date, sizeof (address->date));
bool isHost = false, isIntroKey = false, isStaticKey = false, isV2 = false;
Tag<32> iV2; // for 'i' field in SSU, TODO: remove later
bool isHost = false, isStaticKey = false, isV2 = false;
char transportStyle[6];
ReadString (transportStyle, 6, s);
if (!strncmp (transportStyle, "NTCP", 4)) // NTCP or NTCP2
address->transportStyle = eTransportNTCP;
address->transportStyle = eTransportNTCP2;
else if (!strncmp (transportStyle, "SSU", 3)) // SSU or SSU2
{
address->transportStyle = (transportStyle[3] == '2') ? eTransportSSU2 : eTransportSSU;
address->transportStyle = eTransportSSU2;
address->ssu.reset (new SSUExt ());
address->ssu->mtu = 0;
}
@@ -283,13 +281,6 @@ namespace data
else
LogPrint (eLogWarning, "RouterInfo: Unexpected field 'mtu' for NTCP2");
}
else if (!strcmp (key, "key"))
{
if (address->ssu)
isIntroKey = (Base64ToByteStream (value, strlen (value), address->i, 32) == 32);
else
LogPrint (eLogWarning, "RouterInfo: Unexpected field 'key' for NTCP2");
}
else if (!strcmp (key, "caps"))
address->caps = ExtractAddressCaps (value);
else if (!strcmp (key, "s")) // ntcp2 or ssu2 static key
@@ -306,8 +297,6 @@ namespace data
}
else if (address->IsSSU2 ())
Base64ToByteStream (value, strlen (value), address->i, 32);
else
Base64ToByteStream (value, strlen (value), iV2, 32);
}
else if (!strcmp (key, "v"))
{
@@ -340,21 +329,9 @@ namespace data
}
Introducer& introducer = address->ssu->introducers.at (index);
if (!strcmp (key, "ihost"))
{
boost::system::error_code ecode;
introducer.iHost = boost::asio::ip::address::from_string (value, ecode);
}
introducer.isH = false; // SSU1
else if (!strcmp (key, "iport"))
{
try
{
introducer.iPort = boost::lexical_cast<int>(value);
}
catch (std::exception& ex)
{
LogPrint (eLogWarning, "RouterInfo: 'iport' exception ", ex.what ());
}
}
introducer.isH = false; // SSU1
else if (!strcmp (key, "itag"))
{
try
@@ -366,8 +343,11 @@ namespace data
LogPrint (eLogWarning, "RouterInfo: 'itag' exception ", ex.what ());
}
}
else if (!strcmp (key, "ikey") || !strcmp (key, "ih"))
Base64ToByteStream (value, strlen (value), introducer.iKey, 32);
else if (!strcmp (key, "ih"))
{
Base64ToByteStream (value, strlen (value), introducer.iH, 32);
introducer.isH = true;
}
else if (!strcmp (key, "iexp"))
{
try
@@ -382,7 +362,7 @@ namespace data
}
if (!s) return;
}
if (address->transportStyle == eTransportNTCP)
if (address->transportStyle == eTransportNTCP2)
{
if (isStaticKey)
{
@@ -406,49 +386,7 @@ namespace data
}
}
}
else if (address->transportStyle == eTransportSSU)
{
if (isIntroKey)
{
if (isHost)
supportedTransports |= address->host.is_v4 () ? eSSUV4 : eSSUV6;
else if (address->caps & AddressCaps::eV6)
{
supportedTransports |= eSSUV6;
if (address->caps & AddressCaps::eV4) supportedTransports |= eSSUV4; // in additional to v6
}
else
supportedTransports |= eSSUV4; // in case if host or 6 caps is not preasented, we assume 4
if (address->ssu && !address->ssu->introducers.empty ())
{
// exclude invalid introducers
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
int numValid = 0;
for (auto& it: address->ssu->introducers)
{
if (!it.iExp) it.iExp = m_Timestamp/1000 + NETDB_INTRODUCEE_EXPIRATION_TIMEOUT;
if (ts <= it.iExp && it.iPort > 0 &&
((it.iHost.is_v4 () && address->IsV4 ()) || (it.iHost.is_v6 () && address->IsV6 ())))
numValid++;
else
{
it.iPort = 0;
if (isV2) numValid++;
}
}
if (numValid)
m_ReachableTransports |= supportedTransports;
else
address->ssu->introducers.resize (0);
}
else if (isHost && address->port)
{
address->published = true;
m_ReachableTransports |= supportedTransports;
}
}
}
if (address->transportStyle == eTransportSSU2 || (isV2 && address->transportStyle == eTransportSSU))
else if (address->transportStyle == eTransportSSU2 && isV2)
{
if (address->IsV4 ()) supportedTransports |= eSSU2V4;
if (address->IsV6 ()) supportedTransports |= eSSU2V6;
@@ -456,57 +394,38 @@ namespace data
{
if (address->host.is_v4 ()) m_ReachableTransports |= eSSU2V4;
if (address->host.is_v6 ()) m_ReachableTransports |= eSSU2V6;
address->published = true;
}
if (address->transportStyle == eTransportSSU2)
if (address->ssu && !address->ssu->introducers.empty ())
{
if (address->port) address->published = true;
if (address->ssu && !address->ssu->introducers.empty ())
{
// exclude invalid introducers
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
int numValid = 0;
for (auto& it: address->ssu->introducers)
{
if (it.iTag && ts <= it.iExp)
numValid++;
else
it.iTag = 0;
}
if (numValid)
m_ReachableTransports |= supportedTransports;
else
address->ssu->introducers.resize (0);
}
}
else
{
// create additional SSU2 address. TODO: remove later
auto ssu2addr = std::make_shared<Address> ();
ssu2addr->transportStyle = eTransportSSU2;
ssu2addr->host = address->host; ssu2addr->port = address->port;
ssu2addr->s = address->s; ssu2addr->i = iV2;
ssu2addr->date = address->date; ssu2addr->caps = address->caps;
ssu2addr->published = address->published;
ssu2addr->ssu.reset (new SSUExt ()); ssu2addr->ssu->mtu = address->ssu->mtu;
// exclude invalid introducers
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
if (!address->ssu->introducers.empty ())
int numValid = 0;
for (auto& it: address->ssu->introducers)
{
for (const auto& introducer: address->ssu->introducers)
if (!introducer.iPort && introducer.iHost.is_unspecified () && ts < introducer.iExp) // SSU2
ssu2addr->ssu->introducers.push_back (introducer);
if (!ssu2addr->ssu->introducers.empty ())
m_ReachableTransports |= supportedTransports;
if (it.iTag && ts < it.iExp && it.isH)
numValid++;
else
it.iTag = 0;
}
addresses->push_back(ssu2addr);
if (numValid)
m_ReachableTransports |= supportedTransports;
else
address->ssu->introducers.resize (0);
}
}
if (supportedTransports)
{
if (!(m_SupportedTransports & supportedTransports)) // avoid duplicates
addresses->push_back(address);
{
for (uint8_t i = 0; i < eNumTransports; i++)
if ((1 << i) & supportedTransports)
(*addresses)[i] = address;
}
m_SupportedTransports |= supportedTransports;
}
}
// update addresses
#if (BOOST_VERSION >= 105300)
boost::atomic_store (&m_Addresses, addresses);
#else
@@ -635,10 +554,10 @@ namespace data
case CAPS_FLAG_V6:
caps |= AddressCaps::eV6;
break;
case CAPS_FLAG_SSU_TESTING:
case CAPS_FLAG_SSU2_TESTING:
caps |= AddressCaps::eSSUTesting;
break;
case CAPS_FLAG_SSU_INTRODUCER:
case CAPS_FLAG_SSU2_INTRODUCER:
caps |= AddressCaps::eSSUIntroducer;
break;
default: ;
@@ -701,36 +620,13 @@ namespace data
return l+1;
}
void RouterInfo::AddSSUAddress (const char * host, int port, const uint8_t * key, int mtu)
{
auto addr = std::make_shared<Address>();
addr->host = boost::asio::ip::address::from_string (host);
addr->port = port;
addr->transportStyle = eTransportSSU;
addr->published = true;
addr->caps = i2p::data::RouterInfo::eSSUTesting | i2p::data::RouterInfo::eSSUIntroducer; // BC;
addr->date = 0;
addr->ssu.reset (new SSUExt ());
addr->ssu->mtu = mtu;
if (key)
memcpy (addr->i, key, 32);
else
RAND_bytes (addr->i, 32);
for (const auto& it: *m_Addresses) // don't insert same address twice
if (*it == *addr) return;
m_SupportedTransports |= addr->host.is_v6 () ? eSSUV6 : eSSUV4;
m_ReachableTransports |= addr->host.is_v6 () ? eSSUV6 : eSSUV4;
m_Addresses->push_back(std::move(addr));
}
void RouterInfo::AddNTCP2Address (const uint8_t * staticKey, const uint8_t * iv,
const boost::asio::ip::address& host, int port, uint8_t caps)
{
auto addr = std::make_shared<Address>();
addr->host = host;
addr->port = port;
addr->transportStyle = eTransportNTCP;
addr->transportStyle = eTransportNTCP2;
addr->caps = caps;
addr->date = 0;
if (port) addr->published = true;
@@ -740,13 +636,23 @@ namespace data
{
m_SupportedTransports |= eNTCP2V4;
if (addr->published) m_ReachableTransports |= eNTCP2V4;
(*m_Addresses)[eNTCP2V4Idx] = addr;
}
if (addr->IsV6 ())
{
m_SupportedTransports |= eNTCP2V6;
if (addr->published) m_ReachableTransports |= eNTCP2V6;
if (i2p::util::net::IsYggdrasilAddress (addr->host))
{
m_SupportedTransports |= eNTCP2V6Mesh;
m_ReachableTransports |= eNTCP2V6Mesh;
(*m_Addresses)[eNTCP2V6MeshIdx] = addr;
}
else
{
m_SupportedTransports |= eNTCP2V6;
if (addr->published) m_ReachableTransports |= eNTCP2V6;
(*m_Addresses)[eNTCP2V6Idx] = addr;
}
}
m_Addresses->push_back(std::move(addr));
}
void RouterInfo::AddSSU2Address (const uint8_t * staticKey, const uint8_t * introKey, uint8_t caps)
@@ -760,9 +666,16 @@ namespace data
addr->ssu->mtu = 0;
memcpy (addr->s, staticKey, 32);
memcpy (addr->i, introKey, 32);
if (addr->IsV4 ()) m_SupportedTransports |= eSSU2V4;
if (addr->IsV6 ()) m_SupportedTransports |= eSSU2V6;
m_Addresses->push_back(std::move(addr));
if (addr->IsV4 ())
{
m_SupportedTransports |= eSSU2V4;
(*m_Addresses)[eSSU2V4Idx] = addr;
}
if (addr->IsV6 ())
{
m_SupportedTransports |= eSSU2V6;
(*m_Addresses)[eSSU2V6Idx] = addr;
}
}
void RouterInfo::AddSSU2Address (const uint8_t * staticKey, const uint8_t * introKey,
@@ -783,58 +696,14 @@ namespace data
{
m_SupportedTransports |= eSSU2V4;
m_ReachableTransports |= eSSU2V4;
(*m_Addresses)[eSSU2V4Idx] = addr;
}
if (addr->IsV6 ())
{
m_SupportedTransports |= eSSU2V6;
m_ReachableTransports |= eSSU2V6;
(*m_Addresses)[eSSU2V6Idx] = addr;
}
m_Addresses->push_back(std::move(addr));
}
bool RouterInfo::AddIntroducer (const Introducer& introducer)
{
for (auto& addr : *m_Addresses)
{
if (addr->transportStyle == eTransportSSU &&
((addr->IsV4 () && introducer.iHost.is_v4 ()) || (addr->IsV6 () && introducer.iHost.is_v6 ())))
{
for (auto& intro: addr->ssu->introducers)
if (intro.iTag == introducer.iTag) return false; // already presented
addr->ssu->introducers.push_back (introducer);
m_ReachableTransports |= (addr->IsV4 () ? eSSUV4 : eSSUV6);
return true;
}
}
return false;
}
bool RouterInfo::RemoveIntroducer (const boost::asio::ip::udp::endpoint& e)
{
for (auto& addr: *m_Addresses)
{
if (addr->transportStyle == eTransportSSU &&
((addr->IsV4 () && e.address ().is_v4 ()) || (addr->IsV6 () && e.address ().is_v6 ())))
{
for (auto it = addr->ssu->introducers.begin (); it != addr->ssu->introducers.end (); ++it)
if (boost::asio::ip::udp::endpoint (it->iHost, it->iPort) == e)
{
addr->ssu->introducers.erase (it);
if (addr->ssu->introducers.empty ())
m_ReachableTransports &= ~(addr->IsV4 () ? eSSUV4 : eSSUV6);
return true;
}
}
}
return false;
}
bool RouterInfo::IsSSU (bool v4only) const
{
if (v4only)
return m_SupportedTransports & eSSUV4;
else
return m_SupportedTransports & (eSSUV4 | eSSUV6);
}
bool RouterInfo::IsNTCP2 (bool v4only) const
@@ -873,21 +742,17 @@ namespace data
{
if (IsV6 ())
{
for (auto it = m_Addresses->begin (); it != m_Addresses->end ();)
if ((*m_Addresses)[eNTCP2V6Idx])
{
auto addr = *it;
if (addr->IsV6 ())
{
if (addr->IsV4 ())
{
addr->caps &= ~AddressCaps::eV6;
++it;
}
else
it = m_Addresses->erase (it);
}
else
++it;
if ((*m_Addresses)[eNTCP2V6Idx]->IsV4 ())
(*m_Addresses)[eNTCP2V6Idx]->caps &= ~AddressCaps::eV6;
(*m_Addresses)[eNTCP2V6Idx].reset ();
}
if ((*m_Addresses)[eSSU2V6Idx])
{
if ((*m_Addresses)[eSSU2V6Idx]->IsV4 ())
(*m_Addresses)[eSSU2V6Idx]->caps &= ~AddressCaps::eV6;
(*m_Addresses)[eSSU2V6Idx].reset ();
}
UpdateSupportedTransports ();
}
@@ -897,21 +762,17 @@ namespace data
{
if (IsV4 ())
{
for (auto it = m_Addresses->begin (); it != m_Addresses->end ();)
if ((*m_Addresses)[eNTCP2V4Idx])
{
auto addr = *it;
if (addr->IsV4 ())
{
if (addr->IsV6 ())
{
addr->caps &= ~AddressCaps::eV4;
++it;
}
else
it = m_Addresses->erase (it);
}
else
++it;
if ((*m_Addresses)[eNTCP2V4Idx]->IsV6 ())
(*m_Addresses)[eNTCP2V4Idx]->caps &= ~AddressCaps::eV4;
(*m_Addresses)[eNTCP2V4Idx].reset ();
}
if ((*m_Addresses)[eSSU2V4Idx])
{
if ((*m_Addresses)[eSSU2V4Idx]->IsV6 ())
(*m_Addresses)[eSSU2V4Idx]->caps &= ~AddressCaps::eV4;
(*m_Addresses)[eSSU2V4Idx].reset ();
}
UpdateSupportedTransports ();
}
@@ -932,51 +793,18 @@ namespace data
{
m_SupportedTransports &= ~eNTCP2V6Mesh;
m_ReachableTransports &= ~eNTCP2V6Mesh;
for (auto it = m_Addresses->begin (); it != m_Addresses->end ();)
{
auto addr = *it;
if (i2p::util::net::IsYggdrasilAddress (addr->host))
it = m_Addresses->erase (it);
else
++it;
}
(*m_Addresses)[eNTCP2V6MeshIdx].reset ();
}
}
std::shared_ptr<const RouterInfo::Address> RouterInfo::GetSSUAddress (bool v4only) const
{
return GetAddress (
[v4only](std::shared_ptr<const RouterInfo::Address> address)->bool
{
return (address->transportStyle == eTransportSSU) && (!v4only || address->IsV4 ());
});
}
std::shared_ptr<const RouterInfo::Address> RouterInfo::GetSSUV6Address () const
{
return GetAddress (
[](std::shared_ptr<const RouterInfo::Address> address)->bool
{
return (address->transportStyle == eTransportSSU) && address->IsV6();
});
}
std::shared_ptr<const RouterInfo::Address> RouterInfo::GetSSU2V4Address () const
{
return GetAddress (
[](std::shared_ptr<const RouterInfo::Address> address)->bool
{
return (address->transportStyle == eTransportSSU2) && address->IsV4();
});
return (*GetAddresses ())[eSSU2V4Idx];
}
std::shared_ptr<const RouterInfo::Address> RouterInfo::GetSSU2V6Address () const
{
return GetAddress (
[](std::shared_ptr<const RouterInfo::Address> address)->bool
{
return (address->transportStyle == eTransportSSU2) && address->IsV6();
});
return (*GetAddresses ())[eSSU2V6Idx];
}
std::shared_ptr<const RouterInfo::Address> RouterInfo::GetSSU2Address (bool v4) const
@@ -994,6 +822,15 @@ namespace data
return nullptr;
}
boost::shared_ptr<RouterInfo::Addresses> RouterInfo::GetAddresses () const
{
#if (BOOST_VERSION >= 105300)
return boost::atomic_load (&m_Addresses);
#else
return m_Addresses;
#endif
}
template<typename Filter>
std::shared_ptr<const RouterInfo::Address> RouterInfo::GetAddress (Filter filter) const
{
@@ -1004,7 +841,7 @@ namespace data
auto addresses = m_Addresses;
#endif
for (const auto& address : *addresses)
if (filter (address)) return address;
if (address && filter (address)) return address;
return nullptr;
}
@@ -1022,40 +859,29 @@ namespace data
std::shared_ptr<const RouterInfo::Address> RouterInfo::GetSSU2AddressWithStaticKey (const uint8_t * key, bool isV6) const
{
if (!key) return nullptr;
return GetAddress (
[key, isV6](std::shared_ptr<const RouterInfo::Address> address)->bool
{
return address->IsSSU2 () && !memcmp (address->s, key, 32) &&
((isV6 && address->IsV6 ()) || (!isV6 && address->IsV4 ()));
});
auto addr = (*GetAddresses ())[isV6 ? eSSU2V6Idx : eSSU2V4Idx];
if (addr && !memcmp (addr->s, key, 32))
return addr;
return nullptr;
}
std::shared_ptr<const RouterInfo::Address> RouterInfo::GetPublishedNTCP2V4Address () const
{
return GetAddress (
[](std::shared_ptr<const RouterInfo::Address> address)->bool
{
return address->IsPublishedNTCP2 () && address->host.is_v4 ();
});
auto addr = (*GetAddresses ())[eNTCP2V4Idx];
if (addr && addr->IsPublishedNTCP2 ()) return addr;
return nullptr;
}
std::shared_ptr<const RouterInfo::Address> RouterInfo::GetPublishedNTCP2V6Address () const
{
return GetAddress (
[](std::shared_ptr<const RouterInfo::Address> address)->bool
{
return address->IsPublishedNTCP2 () && address->host.is_v6 () &&
!i2p::util::net::IsYggdrasilAddress (address->host);
});
auto addr = (*GetAddresses ())[eNTCP2V6Idx];
if (addr && addr->IsPublishedNTCP2 ()) return addr;
return nullptr;
}
std::shared_ptr<const RouterInfo::Address> RouterInfo::GetYggdrasilAddress () const
{
return GetAddress (
[](std::shared_ptr<const RouterInfo::Address> address)->bool
{
return address->IsPublishedNTCP2 () && i2p::util::net::IsYggdrasilAddress (address->host);
});
return (*GetAddresses ())[eNTCP2V6MeshIdx];
}
std::shared_ptr<RouterProfile> RouterInfo::GetProfile () const
@@ -1075,60 +901,29 @@ namespace data
bool RouterInfo::IsEligibleFloodfill () const
{
// floodfill must be reachable by ipv4, >= 0.9.38 and not DSA
return IsReachableBy (eNTCP2V4 | eSSUV4) && m_Version >= NETDB_MIN_FLOODFILL_VERSION &&
return IsReachableBy (eNTCP2V4 | eSSU2V4) && m_Version >= NETDB_MIN_FLOODFILL_VERSION &&
GetIdentity ()->GetSigningKeyType () != SIGNING_KEY_TYPE_DSA_SHA1;
}
bool RouterInfo::IsPeerTesting (bool v4) const
{
if (!(m_SupportedTransports & (v4 ? eSSUV4 : eSSUV6))) return false;
return (bool)GetAddress (
[v4](std::shared_ptr<const RouterInfo::Address> address)->bool
{
return (address->transportStyle == eTransportSSU) && address->IsPeerTesting () &&
((v4 && address->IsV4 ()) || (!v4 && address->IsV6 ())) && address->IsReachableSSU ();
});
}
bool RouterInfo::IsSSU2PeerTesting (bool v4) const
{
if (!(m_SupportedTransports & (v4 ? eSSU2V4 : eSSU2V6))) return false;
return (bool)GetAddress (
[v4](std::shared_ptr<const RouterInfo::Address> address)->bool
{
return (address->IsSSU2 ()) && address->IsPeerTesting () &&
((v4 && address->IsV4 ()) || (!v4 && address->IsV6 ())) && address->IsReachableSSU ();
});
}
bool RouterInfo::IsIntroducer (bool v4) const
{
if (!(m_SupportedTransports & (v4 ? eSSUV4 : eSSUV6))) return false;
return (bool)GetAddress (
[v4](std::shared_ptr<const RouterInfo::Address> address)->bool
{
return (address->transportStyle == eTransportSSU) && address->IsIntroducer () &&
((v4 && address->IsV4 ()) || (!v4 && address->IsV6 ())) && !address->host.is_unspecified ();
});
auto addr = (*GetAddresses ())[v4 ? eSSU2V4Idx : eSSU2V6Idx];
return addr && addr->IsPeerTesting () && addr->IsReachableSSU ();
}
bool RouterInfo::IsSSU2Introducer (bool v4) const
{
if (!(m_SupportedTransports & (v4 ? eSSU2V4 : eSSU2V6))) return false;
return (bool)GetAddress (
[v4](std::shared_ptr<const RouterInfo::Address> address)->bool
{
return (address->IsSSU2 ()) && address->IsIntroducer () &&
((v4 && address->IsV4 ()) || (!v4 && address->IsV6 ())) && !address->host.is_unspecified ();
});
auto addr = (*GetAddresses ())[v4 ? eSSU2V4Idx : eSSU2V6Idx];
return addr && addr->IsIntroducer () && !addr->host.is_unspecified () && addr->port;
}
void RouterInfo::SetUnreachableAddressesTransportCaps (uint8_t transports)
{
for (auto& addr: *m_Addresses)
{
// TODO: implement SSU
if (!addr->published && (addr->transportStyle == eTransportNTCP || addr->transportStyle == eTransportSSU2))
if (addr && !addr->published)
{
addr->caps &= ~(eV4 | eV6);
addr->caps |= transports;
@@ -1142,22 +937,17 @@ namespace data
m_ReachableTransports = 0;
for (const auto& addr: *m_Addresses)
{
if (!addr) continue;
uint8_t transports = 0;
switch (addr->transportStyle)
{
case eTransportNTCP:
case eTransportNTCP2:
if (addr->IsV4 ()) transports |= eNTCP2V4;
if (addr->IsV6 ())
transports |= (i2p::util::net::IsYggdrasilAddress (addr->host) ? eNTCP2V6Mesh : eNTCP2V6);
if (addr->IsPublishedNTCP2 ())
m_ReachableTransports |= transports;
break;
case eTransportSSU:
if (addr->IsV4 ()) transports |= eSSUV4;
if (addr->IsV6 ()) transports |= eSSUV6;
if (addr->IsReachableSSU ())
m_ReachableTransports |= transports;
break;
case eTransportSSU2:
if (addr->IsV4 ()) transports |= eSSU2V4;
if (addr->IsV6 ()) transports |= eSSU2V6;
@@ -1184,6 +974,16 @@ namespace data
return netdb.NewRouterInfoBuffer ();
}
std::shared_ptr<RouterInfo::Address> RouterInfo::NewAddress () const
{
return netdb.NewRouterInfoAddress ();
}
boost::shared_ptr<RouterInfo::Addresses> RouterInfo::NewAddresses () const
{
return netdb.NewRouterInfoAddresses ();
}
void RouterInfo::RefreshTimestamp ()
{
m_Timestamp = i2p::util::GetMillisecondsSinceEpoch ();
@@ -1245,29 +1045,42 @@ namespace data
void LocalRouterInfo::WriteToStream (std::ostream& s) const
{
auto addresses = GetAddresses ();
if (!addresses) return;
uint64_t ts = htobe64 (GetTimestamp ());
s.write ((const char *)&ts, sizeof (ts));
// addresses
const Addresses& addresses = GetAddresses ();
uint8_t numAddresses = addresses.size ();
s.write ((char *)&numAddresses, sizeof (numAddresses));
for (const auto& addr_ptr : addresses)
uint8_t numAddresses = 0;
for (size_t idx = 0; idx < addresses->size(); idx++)
{
auto addr_ptr = (*addresses)[idx];
if (!addr_ptr) continue;
if (idx == eNTCP2V6Idx && addr_ptr == (*addresses)[eNTCP2V4Idx]) continue;
if (idx == eSSU2V6Idx && addr_ptr == (*addresses)[eSSU2V4Idx]) continue;
numAddresses++;
}
s.write ((char *)&numAddresses, sizeof (numAddresses));
for (size_t idx = 0; idx < addresses->size(); idx++)
{
auto addr_ptr = (*addresses)[idx];
if (!addr_ptr) continue;
if (idx == eNTCP2V6Idx && addr_ptr == (*addresses)[eNTCP2V4Idx]) continue;
if (idx == eSSU2V6Idx && addr_ptr == (*addresses)[eSSU2V4Idx]) continue;
const Address& address = *addr_ptr;
// calculate cost
uint8_t cost = 0x7f;
if (address.transportStyle == eTransportNTCP)
if (address.transportStyle == eTransportNTCP2)
cost = address.published ? COST_NTCP2_PUBLISHED : COST_NTCP2_NON_PUBLISHED;
else if (address.transportStyle == eTransportSSU)
cost = address.published ? COST_SSU_DIRECT : COST_SSU_THROUGH_INTRODUCERS;
else if (address.transportStyle == eTransportSSU2)
cost = address.published ? COST_SSU2_DIRECT : COST_SSU2_NON_PUBLISHED;
else
continue; // skip unknown address
s.write ((const char *)&cost, sizeof (cost));
s.write ((const char *)&address.date, sizeof (address.date));
std::stringstream properties;
bool isPublished = false;
if (address.transportStyle == eTransportNTCP)
if (address.transportStyle == eTransportNTCP2)
{
if (address.IsNTCP2 ())
{
@@ -1289,43 +1102,6 @@ namespace data
else
continue; // don't write NTCP address
}
else if (address.transportStyle == eTransportSSU)
{
WriteString ("SSU", s);
// caps
WriteString ("caps", properties);
properties << '=';
std::string caps;
if (address.IsPeerTesting ()) caps += CAPS_FLAG_SSU_TESTING;
if (address.host.is_v4 ())
{
if (address.published)
{
isPublished = true;
if (address.IsIntroducer ()) caps += CAPS_FLAG_SSU_INTRODUCER;
}
else
caps += CAPS_FLAG_V4;
}
else if (address.host.is_v6 ())
{
if (address.published)
{
isPublished = true;
if (address.IsIntroducer ()) caps += CAPS_FLAG_SSU_INTRODUCER;
}
else
caps += CAPS_FLAG_V6;
}
else
{
if (address.IsV4 ()) caps += CAPS_FLAG_V4;
if (address.IsV6 ()) caps += CAPS_FLAG_V6;
if (caps.empty ()) caps += CAPS_FLAG_V4;
}
WriteString (caps, properties);
properties << ';';
}
else if (address.transportStyle == eTransportSSU2)
{
WriteString ("SSU2", s);
@@ -1334,8 +1110,8 @@ namespace data
if (address.published)
{
isPublished = true;
if (address.IsPeerTesting ()) caps += CAPS_FLAG_SSU_TESTING;
if (address.IsIntroducer ()) caps += CAPS_FLAG_SSU_INTRODUCER;
if (address.IsPeerTesting ()) caps += CAPS_FLAG_SSU2_TESTING;
if (address.IsIntroducer ()) caps += CAPS_FLAG_SSU2_INTRODUCER;
}
else
{
@@ -1368,7 +1144,7 @@ namespace data
size_t len = address.IsSSU2 () ? 32 : 16;
WriteString (address.i.ToBase64 (len), properties); properties << ';';
}
if (address.transportStyle == eTransportSSU || address.IsSSU2 ())
if (address.transportStyle == eTransportSSU2)
{
// write introducers if any
if (address.ssu && !address.ssu->introducers.empty())
@@ -1385,45 +1161,18 @@ namespace data
}
i++;
}
if (address.transportStyle == eTransportSSU)
{
i = 0;
for (const auto& introducer: address.ssu->introducers)
{
WriteString ("ihost" + boost::lexical_cast<std::string>(i), properties);
properties << '=';
WriteString (introducer.iHost.to_string (), properties);
properties << ';';
i++;
}
}
i = 0;
for (const auto& introducer: address.ssu->introducers)
{
if (address.IsSSU2 ())
WriteString ("ih" + boost::lexical_cast<std::string>(i), properties);
else
WriteString ("ikey" + boost::lexical_cast<std::string>(i), properties);
WriteString ("ih" + boost::lexical_cast<std::string>(i), properties);
properties << '=';
char value[64];
size_t l = ByteStreamToBase64 (introducer.iKey, 32, value, 64);
size_t l = ByteStreamToBase64 (introducer.iH, 32, value, 64);
value[l] = 0;
WriteString (value, properties);
properties << ';';
i++;
}
if (address.transportStyle == eTransportSSU)
{
i = 0;
for (const auto& introducer: address.ssu->introducers)
{
WriteString ("iport" + boost::lexical_cast<std::string>(i), properties);
properties << '=';
WriteString (boost::lexical_cast<std::string>(introducer.iPort), properties);
properties << ';';
i++;
}
}
i = 0;
for (const auto& introducer: address.ssu->introducers)
{
@@ -1435,18 +1184,8 @@ namespace data
}
}
}
if (address.transportStyle == eTransportSSU)
{
// write intro key
WriteString ("key", properties);
properties << '=';
char value[64];
size_t l = ByteStreamToBase64 (address.i, 32, value, 64);
value[l] = 0;
WriteString (value, properties);
properties << ';';
}
if (address.transportStyle == eTransportSSU || address.IsSSU2 ())
if (address.transportStyle == eTransportSSU2)
{
// write mtu
if (address.ssu && address.ssu->mtu)
@@ -1457,7 +1196,7 @@ namespace data
properties << ';';
}
}
if ((isPublished || (address.ssu && !address.IsSSU2 ())) && address.port)
if (isPublished && address.port)
{
WriteString ("port", properties);
properties << '=';
@@ -1526,37 +1265,47 @@ namespace data
return std::make_shared<Buffer> ();
}
std::shared_ptr<RouterInfo::Address> LocalRouterInfo::NewAddress () const
{
return std::make_shared<Address> ();
}
boost::shared_ptr<RouterInfo::Addresses> LocalRouterInfo::NewAddresses () const
{
return boost::make_shared<Addresses> ();
}
bool LocalRouterInfo::AddSSU2Introducer (const Introducer& introducer, bool v4)
{
for (auto& addr : GetAddresses ())
auto addresses = GetAddresses ();
if (!addresses) return false;
auto addr = (*addresses)[v4 ? eSSU2V4Idx : eSSU2V6Idx];
if (addr)
{
if (addr->IsSSU2 () && ((v4 && addr->IsV4 ()) || (!v4 && addr->IsV6 ())))
{
for (auto& intro: addr->ssu->introducers)
if (intro.iTag == introducer.iTag) return false; // already presented
addr->ssu->introducers.push_back (introducer);
SetReachableTransports (GetReachableTransports () | ((addr->IsV4 () ? eSSU2V4 : eSSU2V6)));
return true;
}
for (auto& intro: addr->ssu->introducers)
if (intro.iTag == introducer.iTag) return false; // already presented
addr->ssu->introducers.push_back (introducer);
SetReachableTransports (GetReachableTransports () | ((addr->IsV4 () ? eSSU2V4 : eSSU2V6)));
return true;
}
return false;
}
bool LocalRouterInfo::RemoveSSU2Introducer (const IdentHash& h, bool v4)
{
for (auto& addr: GetAddresses ())
auto addresses = GetAddresses ();
if (!addresses) return false;
auto addr = (*addresses)[v4 ? eSSU2V4Idx : eSSU2V6Idx];
if (addr)
{
if (addr->IsSSU2 () && ((v4 && addr->IsV4 ()) || (!v4 && addr->IsV6 ())))
{
for (auto it = addr->ssu->introducers.begin (); it != addr->ssu->introducers.end (); ++it)
if (h == it->iKey)
{
addr->ssu->introducers.erase (it);
if (addr->ssu->introducers.empty ())
SetReachableTransports (GetReachableTransports () & ~(addr->IsV4 () ? eSSU2V4 : eSSU2V6));
return true;
}
}
for (auto it = addr->ssu->introducers.begin (); it != addr->ssu->introducers.end (); ++it)
if (h == it->iH)
{
addr->ssu->introducers.erase (it);
if (addr->ssu->introducers.empty ())
SetReachableTransports (GetReachableTransports () & ~(addr->IsV4 () ? eSSU2V4 : eSSU2V6));
return true;
}
}
return false;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2022, The PurpleI2P Project
* Copyright (c) 2013-2023, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -47,14 +47,12 @@ namespace data
const char CAPS_FLAG_V4 = '4';
const char CAPS_FLAG_V6 = '6';
const char CAPS_FLAG_SSU_TESTING = 'B';
const char CAPS_FLAG_SSU_INTRODUCER = 'C';
const char CAPS_FLAG_SSU2_TESTING = 'B';
const char CAPS_FLAG_SSU2_INTRODUCER = 'C';
const uint8_t COST_NTCP2_PUBLISHED = 3;
const uint8_t COST_NTCP2_NON_PUBLISHED = 14;
const uint8_t COST_SSU2_DIRECT = 8;
const uint8_t COST_SSU_DIRECT = 9;
const uint8_t COST_SSU_THROUGH_INTRODUCERS = 11;
const uint8_t COST_SSU2_NON_PUBLISHED = 15;
const size_t MAX_RI_BUFFER_SIZE = 3072; // if RouterInfo exceeds 3K we consider it as malformed, might extend later
@@ -62,15 +60,25 @@ namespace data
{
public:
enum SupportedTransportsIdx
{
eNTCP2V4Idx = 0,
eNTCP2V6Idx,
eSSU2V4Idx,
eSSU2V6Idx,
eNTCP2V6MeshIdx,
eNumTransports
};
#define TransportBit(tr) e##tr = (1 << e##tr##Idx)
enum SupportedTransports
{
eNTCP2V4 = 0x01,
eNTCP2V6 = 0x02,
eSSUV4 = 0x04,
eSSUV6 = 0x08,
eNTCP2V6Mesh = 0x10,
eSSU2V4 = 0x20,
eSSU2V6 = 0x40,
TransportBit(NTCP2V4), // 0x01
TransportBit(NTCP2V6), // 0x02
TransportBit(SSU2V4), // 0x04
TransportBit(SSU2V6), // 0x08
TransportBit(NTCP2V6Mesh), // 0x10
eAllTransports = 0xFF
};
typedef uint8_t CompatibleTransports;
@@ -96,20 +104,17 @@ namespace data
enum TransportStyle
{
eTransportUnknown = 0,
eTransportNTCP,
eTransportSSU,
eTransportNTCP2,
eTransportSSU2
};
typedef Tag<32> IntroKey; // should be castable to MacKey and AESKey
struct Introducer
{
Introducer (): iPort (0), iExp (0) {};
boost::asio::ip::address iHost;
int iPort;
IntroKey iKey; // or ih for SSU2
Introducer (): iTag (0), iExp (0), isH (false) {};
IdentHash iH;
uint32_t iTag;
uint32_t iExp;
bool isH; // TODO: remove later
};
struct SSUExt
@@ -146,7 +151,7 @@ namespace data
return !(*this == other);
}
bool IsNTCP2 () const { return transportStyle == eTransportNTCP; };
bool IsNTCP2 () const { return transportStyle == eTransportNTCP2; };
bool IsSSU2 () const { return transportStyle == eTransportSSU2; };
bool IsPublishedNTCP2 () const { return IsNTCP2 () && published; };
bool IsReachableSSU () const { return (bool)ssu && (published || UsesIntroducer ()); };
@@ -167,7 +172,7 @@ namespace data
Buffer (const uint8_t * buf, size_t len);
};
typedef std::vector<std::shared_ptr<Address> > Addresses;
typedef std::array<std::shared_ptr<Address>, eNumTransports> Addresses;
RouterInfo (const std::string& fullPath);
RouterInfo (const RouterInfo& ) = default;
@@ -183,39 +188,32 @@ namespace data
int GetVersion () const { return m_Version; };
virtual void SetProperty (const std::string& key, const std::string& value) {};
virtual void ClearProperties () {};
Addresses& GetAddresses () { return *m_Addresses; }; // should be called for local RI only, otherwise must return shared_ptr
boost::shared_ptr<Addresses> GetAddresses () const; // should be called for local RI only, otherwise must return shared_ptr
std::shared_ptr<const Address> GetNTCP2AddressWithStaticKey (const uint8_t * key) const;
std::shared_ptr<const Address> GetSSU2AddressWithStaticKey (const uint8_t * key, bool isV6) const;
std::shared_ptr<const Address> GetPublishedNTCP2V4Address () const;
std::shared_ptr<const Address> GetPublishedNTCP2V6Address () const;
std::shared_ptr<const Address> GetSSUAddress (bool v4only = true) const;
std::shared_ptr<const Address> GetSSUV6Address () const;
std::shared_ptr<const Address> GetYggdrasilAddress () const;
std::shared_ptr<const Address> GetSSU2V4Address () const;
std::shared_ptr<const Address> GetSSU2V6Address () const;
std::shared_ptr<const Address> GetSSU2Address (bool v4) const;
void AddSSUAddress (const char * host, int port, const uint8_t * key, int mtu = 0);
void AddNTCP2Address (const uint8_t * staticKey, const uint8_t * iv,
const boost::asio::ip::address& host = boost::asio::ip::address(), int port = 0, uint8_t caps = 0);
void AddSSU2Address (const uint8_t * staticKey, const uint8_t * introKey, uint8_t caps = 0); // non published
void AddSSU2Address (const uint8_t * staticKey, const uint8_t * introKey,
const boost::asio::ip::address& host, int port); // published
bool AddIntroducer (const Introducer& introducer);
bool RemoveIntroducer (const boost::asio::ip::udp::endpoint& e);
void SetUnreachableAddressesTransportCaps (uint8_t transports); // bitmask of AddressCaps
void UpdateSupportedTransports ();
bool IsFloodfill () const { return m_Caps & Caps::eFloodfill; };
bool IsReachable () const { return m_Caps & Caps::eReachable; };
bool IsECIES () const { return m_RouterIdentity->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD; };
bool IsSSU (bool v4only = true) const;
bool IsSSUV6 () const { return m_SupportedTransports & eSSUV6; };
bool IsNTCP2 (bool v4only = true) const;
bool IsNTCP2V6 () const { return m_SupportedTransports & eNTCP2V6; };
bool IsSSU2V4 () const { return m_SupportedTransports & eSSU2V4; };
bool IsSSU2V6 () const { return m_SupportedTransports & eSSU2V6; };
bool IsV6 () const { return m_SupportedTransports & (eSSUV6 | eNTCP2V6 | eSSU2V6); };
bool IsV4 () const { return m_SupportedTransports & (eSSUV4 | eNTCP2V4 | eSSU2V4); };
bool IsV6 () const { return m_SupportedTransports & (eNTCP2V6 | eSSU2V6); };
bool IsV4 () const { return m_SupportedTransports & (eNTCP2V4 | eSSU2V4); };
bool IsMesh () const { return m_SupportedTransports & eNTCP2V6Mesh; };
void EnableV6 ();
void DisableV6 ();
@@ -232,9 +230,7 @@ namespace data
bool IsHighBandwidth () const { return m_Caps & RouterInfo::eHighBandwidth; };
bool IsExtraBandwidth () const { return m_Caps & RouterInfo::eExtraBandwidth; };
bool IsEligibleFloodfill () const;
bool IsPeerTesting (bool v4) const;
bool IsSSU2PeerTesting (bool v4) const;
bool IsIntroducer (bool v4) const;
bool IsSSU2Introducer (bool v4) const;
uint8_t GetCaps () const { return m_Caps; };
@@ -274,7 +270,6 @@ namespace data
void UpdateBuffer (const uint8_t * buf, size_t len);
void SetBufferLen (size_t len) { m_BufferLen = len; };
void RefreshTimestamp ();
const Addresses& GetAddresses () const { return *m_Addresses; };
CompatibleTransports GetReachableTransports () const { return m_ReachableTransports; };
void SetReachableTransports (CompatibleTransports transports) { m_ReachableTransports = transports; };
@@ -290,6 +285,8 @@ namespace data
template<typename Filter>
std::shared_ptr<const Address> GetAddress (Filter filter) const;
virtual std::shared_ptr<Buffer> NewBuffer () const;
virtual std::shared_ptr<Address> NewAddress () const;
virtual boost::shared_ptr<Addresses> NewAddresses () const;
private:
@@ -311,6 +308,7 @@ namespace data
public:
LocalRouterInfo () = default;
LocalRouterInfo (const std::string& fullPath): RouterInfo (fullPath) {};
void CreateBuffer (const PrivateKeys& privateKeys);
void UpdateCaps (uint8_t caps);
@@ -328,6 +326,8 @@ namespace data
void UpdateCapsProperty ();
void WriteString (const std::string& str, std::ostream& s) const;
std::shared_ptr<Buffer> NewBuffer () const override;
std::shared_ptr<Address> NewAddress () const override;
boost::shared_ptr<Addresses> NewAddresses () const override;
private:

View File

@@ -1,996 +0,0 @@
/*
* Copyright (c) 2013-2022, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
* See full license text in LICENSE file at top of project tree
*/
#include <string.h>
#include "Log.h"
#include "Timestamp.h"
#include "RouterContext.h"
#include "NetDb.hpp"
#include "Config.h"
#include "util.h"
#include "SSU.h"
#if defined(__linux__) && !defined(_NETINET_IN_H)
#include <linux/in6.h>
#endif
#ifdef _WIN32
#include <boost/winapi/error_codes.hpp>
#endif
namespace i2p
{
namespace transport
{
SSUServer::SSUServer (int port):
m_IsRunning(false), m_Thread (nullptr),
m_ReceiversThread (nullptr), m_ReceiversThreadV6 (nullptr), m_Work (m_Service),
m_ReceiversWork (m_ReceiversService), m_ReceiversWorkV6 (m_ReceiversServiceV6),
m_Endpoint (boost::asio::ip::udp::v4 (), port), m_EndpointV6 (boost::asio::ip::udp::v6 (), port),
m_Socket (m_ReceiversService), m_SocketV6 (m_ReceiversServiceV6),
m_IntroducersUpdateTimer (m_Service), m_IntroducersUpdateTimerV6 (m_Service),
m_PeerTestsCleanupTimer (m_Service), m_TerminationTimer (m_Service), m_TerminationTimerV6 (m_Service),
m_IsSyncClockFromPeers (true)
{
}
SSUServer::~SSUServer ()
{
}
void SSUServer::OpenSocket ()
{
try
{
m_Socket.open (boost::asio::ip::udp::v4());
m_Socket.set_option (boost::asio::socket_base::receive_buffer_size (SSU_SOCKET_RECEIVE_BUFFER_SIZE));
m_Socket.set_option (boost::asio::socket_base::send_buffer_size (SSU_SOCKET_SEND_BUFFER_SIZE));
m_Socket.bind (m_Endpoint);
LogPrint (eLogInfo, "SSU: Start listening v4 port ", m_Endpoint.port());
}
catch ( std::exception & ex )
{
LogPrint (eLogError, "SSU: Failed to bind to v4 port ", m_Endpoint.port(), ": ", ex.what());
ThrowFatal ("Unable to start IPv4 SSU transport at port ", m_Endpoint.port(), ": ", ex.what ());
}
}
void SSUServer::OpenSocketV6 ()
{
try
{
m_SocketV6.open (boost::asio::ip::udp::v6());
m_SocketV6.set_option (boost::asio::ip::v6_only (true));
m_SocketV6.set_option (boost::asio::socket_base::receive_buffer_size (SSU_SOCKET_RECEIVE_BUFFER_SIZE));
m_SocketV6.set_option (boost::asio::socket_base::send_buffer_size (SSU_SOCKET_SEND_BUFFER_SIZE));
#if defined(__linux__) && !defined(_NETINET_IN_H)
if (m_EndpointV6.address() == boost::asio::ip::address().from_string("::")) // only if not binded to address
{
// Set preference to use public IPv6 address -- tested on linux, not works on windows, and not tested on others
#if (BOOST_VERSION >= 105500)
typedef boost::asio::detail::socket_option::integer<BOOST_ASIO_OS_DEF(IPPROTO_IPV6), IPV6_ADDR_PREFERENCES> ipv6PreferAddr;
#else
typedef boost::asio::detail::socket_option::integer<IPPROTO_IPV6, IPV6_ADDR_PREFERENCES> ipv6PreferAddr;
#endif
m_SocketV6.set_option (ipv6PreferAddr(IPV6_PREFER_SRC_PUBLIC | IPV6_PREFER_SRC_HOME | IPV6_PREFER_SRC_NONCGA));
}
#endif
m_SocketV6.bind (m_EndpointV6);
LogPrint (eLogInfo, "SSU: Start listening v6 port ", m_EndpointV6.port());
}
catch ( std::exception & ex )
{
LogPrint (eLogError, "SSU: Failed to bind to v6 port ", m_EndpointV6.port(), ": ", ex.what());
ThrowFatal ("Unable to start IPv6 SSU transport at port ", m_Endpoint.port(), ": ", ex.what ());
}
}
void SSUServer::Start ()
{
i2p::config::GetOption("nettime.frompeers", m_IsSyncClockFromPeers);
m_IsRunning = true;
m_Thread = new std::thread (std::bind (&SSUServer::Run, this));
if (context.SupportsV4 ())
{
OpenSocket ();
m_ReceiversThread = new std::thread (std::bind (&SSUServer::RunReceivers, this));
m_ReceiversService.post (std::bind (&SSUServer::Receive, this));
ScheduleTermination ();
ScheduleIntroducersUpdateTimer (); // wait for 30 seconds and decide if we need introducers
}
if (context.SupportsV6 ())
{
OpenSocketV6 ();
m_ReceiversThreadV6 = new std::thread (std::bind (&SSUServer::RunReceiversV6, this));
m_ReceiversServiceV6.post (std::bind (&SSUServer::ReceiveV6, this));
ScheduleTerminationV6 ();
ScheduleIntroducersUpdateTimerV6 (); // wait for 30 seconds and decide if we need introducers
}
SchedulePeerTestsCleanupTimer ();
}
void SSUServer::Stop ()
{
DeleteAllSessions ();
m_IsRunning = false;
m_TerminationTimer.cancel ();
m_TerminationTimerV6.cancel ();
m_IntroducersUpdateTimer.cancel ();
m_IntroducersUpdateTimerV6.cancel ();
m_Service.stop ();
m_Socket.close ();
m_SocketV6.close ();
m_ReceiversService.stop ();
m_ReceiversServiceV6.stop ();
if (m_ReceiversThread)
{
m_ReceiversThread->join ();
delete m_ReceiversThread;
m_ReceiversThread = nullptr;
}
if (m_ReceiversThreadV6)
{
m_ReceiversThreadV6->join ();
delete m_ReceiversThreadV6;
m_ReceiversThreadV6 = nullptr;
}
if (m_Thread)
{
m_Thread->join ();
delete m_Thread;
m_Thread = nullptr;
}
}
void SSUServer::Run ()
{
i2p::util::SetThreadName("SSU");
while (m_IsRunning)
{
try
{
m_Service.run ();
}
catch (std::exception& ex)
{
LogPrint (eLogError, "SSU: Server runtime exception: ", ex.what ());
}
}
}
void SSUServer::RunReceivers ()
{
i2p::util::SetThreadName("SSUv4");
while (m_IsRunning)
{
try
{
m_ReceiversService.run ();
}
catch (std::exception& ex)
{
LogPrint (eLogError, "SSU: Receivers runtime exception: ", ex.what ());
if (m_IsRunning)
{
// restart socket
m_Socket.close ();
OpenSocket ();
Receive ();
}
}
}
}
void SSUServer::RunReceiversV6 ()
{
i2p::util::SetThreadName("SSUv6");
while (m_IsRunning)
{
try
{
m_ReceiversServiceV6.run ();
}
catch (std::exception& ex)
{
LogPrint (eLogError, "SSU: v6 receivers runtime exception: ", ex.what ());
if (m_IsRunning)
{
m_SocketV6.close ();
OpenSocketV6 ();
ReceiveV6 ();
}
}
}
}
void SSUServer::SetLocalAddress (const boost::asio::ip::address& localAddress)
{
if (localAddress.is_v6 ())
m_EndpointV6.address (localAddress);
else if (localAddress.is_v4 ())
m_Endpoint.address (localAddress);
}
void SSUServer::AddRelay (uint32_t tag, std::shared_ptr<SSUSession> relay)
{
m_Relays.emplace (tag, relay);
}
void SSUServer::RemoveRelay (uint32_t tag)
{
m_Relays.erase (tag);
}
std::shared_ptr<SSUSession> SSUServer::FindRelaySession (uint32_t tag)
{
auto it = m_Relays.find (tag);
if (it != m_Relays.end ())
{
if (it->second->GetState () == eSessionStateEstablished)
return it->second;
else
m_Relays.erase (it);
}
return nullptr;
}
void SSUServer::Send (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& to)
{
boost::system::error_code ec;
if (to.protocol () == boost::asio::ip::udp::v4())
m_Socket.send_to (boost::asio::buffer (buf, len), to, 0, ec);
else
m_SocketV6.send_to (boost::asio::buffer (buf, len), to, 0, ec);
if (ec)
{
LogPrint (eLogError, "SSU: Send exception: ", ec.message (), " while trying to send data to ", to.address (), ":", to.port (), " (length: ", len, ")");
}
}
void SSUServer::Receive ()
{
SSUPacket * packet = m_PacketsPool.AcquireMt ();
m_Socket.async_receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V4), packet->from,
std::bind (&SSUServer::HandleReceivedFrom, this, std::placeholders::_1, std::placeholders::_2, packet));
}
void SSUServer::ReceiveV6 ()
{
SSUPacket * packet = m_PacketsPool.AcquireMt ();
m_SocketV6.async_receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V6), packet->from,
std::bind (&SSUServer::HandleReceivedFromV6, this, std::placeholders::_1, std::placeholders::_2, packet));
}
void SSUServer::HandleReceivedFrom (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet)
{
if (!ecode
|| ecode == boost::asio::error::connection_refused
|| ecode == boost::asio::error::connection_reset
|| ecode == boost::asio::error::network_unreachable
|| ecode == boost::asio::error::host_unreachable
#ifdef _WIN32 // windows can throw WinAPI error, which is not handled by ASIO
|| ecode.value() == boost::winapi::ERROR_CONNECTION_REFUSED_
|| ecode.value() == boost::winapi::ERROR_NETWORK_UNREACHABLE_
|| ecode.value() == boost::winapi::ERROR_HOST_UNREACHABLE_
#endif
)
// just try continue reading when received ICMP response otherwise socket can crash,
// but better to find out which host were sent it and mark that router as unreachable
{
packet->len = bytes_transferred;
std::vector<SSUPacket *> packets;
packets.push_back (packet);
boost::system::error_code ec;
size_t moreBytes = m_Socket.available(ec);
if (!ec)
{
while (moreBytes && packets.size () < 25)
{
packet = m_PacketsPool.AcquireMt ();
packet->len = m_Socket.receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V4), packet->from, 0, ec);
if (!ec)
{
packets.push_back (packet);
moreBytes = m_Socket.available(ec);
if (ec) break;
}
else
{
LogPrint (eLogError, "SSU: receive_from error: code ", ec.value(), ": ", ec.message ());
m_PacketsPool.ReleaseMt (packet);
break;
}
}
}
m_Service.post (std::bind (&SSUServer::HandleReceivedPackets, this, packets, &m_Sessions));
Receive ();
}
else
{
m_PacketsPool.ReleaseMt (packet);
if (ecode != boost::asio::error::operation_aborted)
{
LogPrint (eLogError, "SSU: Receive error: code ", ecode.value(), ": ", ecode.message ());
m_Socket.close ();
OpenSocket ();
Receive ();
}
}
}
void SSUServer::HandleReceivedFromV6 (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet)
{
if (!ecode
|| ecode == boost::asio::error::connection_refused
|| ecode == boost::asio::error::connection_reset
|| ecode == boost::asio::error::network_unreachable
|| ecode == boost::asio::error::host_unreachable
#ifdef _WIN32 // windows can throw WinAPI error, which is not handled by ASIO
|| ecode.value() == boost::winapi::ERROR_CONNECTION_REFUSED_
|| ecode.value() == boost::winapi::ERROR_NETWORK_UNREACHABLE_
|| ecode.value() == boost::winapi::ERROR_HOST_UNREACHABLE_
#endif
)
// just try continue reading when received ICMP response otherwise socket can crash,
// but better to find out which host were sent it and mark that router as unreachable
{
packet->len = bytes_transferred;
std::vector<SSUPacket *> packets;
packets.push_back (packet);
boost::system::error_code ec;
size_t moreBytes = m_SocketV6.available (ec);
if (!ec)
{
while (moreBytes && packets.size () < 25)
{
packet = m_PacketsPool.AcquireMt ();
packet->len = m_SocketV6.receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V6), packet->from, 0, ec);
if (!ec)
{
packets.push_back (packet);
moreBytes = m_SocketV6.available(ec);
if (ec) break;
}
else
{
LogPrint (eLogError, "SSU: v6 receive_from error: code ", ec.value(), ": ", ec.message ());
m_PacketsPool.ReleaseMt (packet);;
break;
}
}
}
m_Service.post (std::bind (&SSUServer::HandleReceivedPackets, this, packets, &m_SessionsV6));
ReceiveV6 ();
}
else
{
m_PacketsPool.ReleaseMt (packet);
if (ecode != boost::asio::error::operation_aborted)
{
LogPrint (eLogError, "SSU: v6 receive error: code ", ecode.value(), ": ", ecode.message ());
m_SocketV6.close ();
OpenSocketV6 ();
ReceiveV6 ();
}
}
}
void SSUServer::HandleReceivedPackets (std::vector<SSUPacket *> packets,
std::map<boost::asio::ip::udp::endpoint, std::shared_ptr<SSUSession> > * sessions)
{
if (!m_IsRunning) return;
std::shared_ptr<SSUSession> session;
for (auto& packet: packets)
{
try
{
if (!session || session->GetRemoteEndpoint () != packet->from) // we received packet for other session than previous
{
if (session)
{
session->FlushData ();
session = nullptr;
}
auto it = sessions->find (packet->from);
if (it != sessions->end ())
session = it->second;
if (!session && packet->len > 0)
{
session = std::make_shared<SSUSession> (*this, packet->from);
session->WaitForConnect ();
(*sessions)[packet->from] = session;
LogPrint (eLogDebug, "SSU: New session from ", packet->from.address ().to_string (), ":", packet->from.port (), " created");
}
}
if (session)
session->ProcessNextMessage (packet->buf, packet->len, packet->from);
}
catch (std::exception& ex)
{
LogPrint (eLogError, "SSU: HandleReceivedPackets ", ex.what ());
if (session) session->FlushData ();
session = nullptr;
}
}
m_PacketsPool.ReleaseMt (packets);
if (session) session->FlushData ();
}
std::shared_ptr<SSUSession> SSUServer::FindSession (const boost::asio::ip::udp::endpoint& e) const
{
auto& sessions = e.address ().is_v6 () ? m_SessionsV6 : m_Sessions;
auto it = sessions.find (e);
if (it != sessions.end ())
return it->second;
else
return nullptr;
}
bool SSUServer::CreateSession (std::shared_ptr<const i2p::data::RouterInfo> router, bool peerTest, bool v4only)
{
auto address = router->GetSSUAddress (v4only || !context.SupportsV6 ());
if (address)
return CreateSession (router, address, peerTest);
else
LogPrint (eLogWarning, "SSU: Router ", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), " doesn't have SSU address");
return false;
}
bool SSUServer::CreateSession (std::shared_ptr<const i2p::data::RouterInfo> router,
std::shared_ptr<const i2p::data::RouterInfo::Address> address, bool peerTest)
{
if (router && address)
{
if (address->UsesIntroducer ())
m_Service.post (std::bind (&SSUServer::CreateSessionThroughIntroducer, this, router, address, peerTest)); // always V4 thread
else
{
if (address->host.is_unspecified () || !address->port) return false;
boost::asio::ip::udp::endpoint remoteEndpoint (address->host, address->port);
m_Service.post (std::bind (&SSUServer::CreateDirectSession, this, router, remoteEndpoint, peerTest));
}
}
else
return false;
return true;
}
void SSUServer::CreateDirectSession (std::shared_ptr<const i2p::data::RouterInfo> router, boost::asio::ip::udp::endpoint remoteEndpoint, bool peerTest)
{
auto& sessions = remoteEndpoint.address ().is_v6 () ? m_SessionsV6 : m_Sessions;
auto it = sessions.find (remoteEndpoint);
if (it != sessions.end ())
{
auto session = it->second;
if (peerTest && session->GetState () == eSessionStateEstablished)
session->SendPeerTest ();
}
else
{
// otherwise create new session
auto session = std::make_shared<SSUSession> (*this, remoteEndpoint, router, peerTest);
sessions[remoteEndpoint] = session;
// connect
LogPrint (eLogDebug, "SSU: Creating new session to [", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), "] ",
remoteEndpoint.address ().to_string (), ":", remoteEndpoint.port ());
session->Connect ();
}
}
void SSUServer::CreateSessionThroughIntroducer (std::shared_ptr<const i2p::data::RouterInfo> router,
std::shared_ptr<const i2p::data::RouterInfo::Address> address, bool peerTest)
{
if (router && address && address->UsesIntroducer ())
{
if (address->IsV4 () && !i2p::context.SupportsV4 ()) return;
if (address->IsV6 () && !i2p::context.SupportsV6 ()) return;
if (!address->host.is_unspecified () && address->port)
{
// we rarely come here
auto& sessions = address->host.is_v6 () ? m_SessionsV6 : m_Sessions;
boost::asio::ip::udp::endpoint remoteEndpoint (address->host, address->port);
auto it = sessions.find (remoteEndpoint);
// check if session is presented already
if (it != sessions.end ())
{
auto session = it->second;
if (peerTest && session->GetState () == eSessionStateEstablished)
session->SendPeerTest ();
return;
}
}
// create new session
int numIntroducers = address->ssu->introducers.size ();
if (numIntroducers > 0)
{
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
std::shared_ptr<SSUSession> introducerSession;
const i2p::data::RouterInfo::Introducer * introducer = nullptr;
// we might have a session to introducer already
auto offset = rand ();
for (int i = 0; i < numIntroducers; i++)
{
auto intr = &(address->ssu->introducers[(offset + i)%numIntroducers]);
if (!intr->iPort) continue; // skip invalid introducer
if (intr->iExp > 0 && ts > intr->iExp) continue; // skip expired introducer
boost::asio::ip::udp::endpoint ep (intr->iHost, intr->iPort);
if (ep.address ().is_v4 () && address->IsV4 ()) // ipv4
{
if (!introducer) introducer = intr;
auto it = m_Sessions.find (ep);
if (it != m_Sessions.end ())
{
introducerSession = it->second;
break;
}
}
if (ep.address ().is_v6 () && address->IsV6 ()) // ipv6
{
if (!introducer) introducer = intr;
auto it = m_SessionsV6.find (ep);
if (it != m_SessionsV6.end ())
{
introducerSession = it->second;
break;
}
}
}
if (!introducer)
{
LogPrint (eLogWarning, "SSU: Can't connect to unreachable router and no compatibe non-expired introducers presented");
return;
}
if (introducerSession) // session found
LogPrint (eLogWarning, "SSU: Session to introducer already exists");
else // create new
{
LogPrint (eLogDebug, "SSU: Creating new session to introducer ", introducer->iHost);
boost::asio::ip::udp::endpoint introducerEndpoint (introducer->iHost, introducer->iPort);
introducerSession = std::make_shared<SSUSession> (*this, introducerEndpoint, router);
if (introducerEndpoint.address ().is_v4 ())
m_Sessions[introducerEndpoint] = introducerSession;
else if (introducerEndpoint.address ().is_v6 ())
m_SessionsV6[introducerEndpoint] = introducerSession;
}
if (!address->host.is_unspecified () && address->port)
{
// create session
boost::asio::ip::udp::endpoint remoteEndpoint (address->host, address->port);
auto session = std::make_shared<SSUSession> (*this, remoteEndpoint, router, peerTest);
if (address->host.is_v4 ())
m_Sessions[remoteEndpoint] = session;
else if (address->host.is_v6 ())
m_SessionsV6[remoteEndpoint] = session;
// introduce
LogPrint (eLogInfo, "SSU: Introduce new session to [", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()),
"] through introducer ", introducer->iHost, ":", introducer->iPort);
session->WaitForIntroduction ();
if ((address->host.is_v4 () && i2p::context.GetStatus () == eRouterStatusFirewalled) ||
(address->host.is_v6 () && i2p::context.GetStatusV6 () == eRouterStatusFirewalled))
{
uint8_t buf[1];
Send (buf, 0, remoteEndpoint); // send HolePunch
}
}
introducerSession->Introduce (*introducer, router);
}
else
LogPrint (eLogWarning, "SSU: Can't connect to unreachable router and no introducers present");
}
}
void SSUServer::DeleteSession (std::shared_ptr<SSUSession> session)
{
if (session)
{
session->Close ();
auto& ep = session->GetRemoteEndpoint ();
if (ep.address ().is_v6 ())
m_SessionsV6.erase (ep);
else
m_Sessions.erase (ep);
}
}
void SSUServer::DeleteAllSessions ()
{
for (auto& it: m_Sessions)
it.second->Close ();
m_Sessions.clear ();
for (auto& it: m_SessionsV6)
it.second->Close ();
m_SessionsV6.clear ();
}
template<typename Filter>
std::shared_ptr<SSUSession> SSUServer::GetRandomV4Session (Filter filter) // v4 only
{
std::vector<std::shared_ptr<SSUSession> > filteredSessions;
for (const auto& s :m_Sessions)
if (filter (s.second)) filteredSessions.push_back (s.second);
if (filteredSessions.size () > 0)
{
auto ind = rand () % filteredSessions.size ();
return filteredSessions[ind];
}
return nullptr;
}
std::shared_ptr<SSUSession> SSUServer::GetRandomEstablishedV4Session (std::shared_ptr<const SSUSession> excluded) // v4 only
{
return GetRandomV4Session (
[excluded](std::shared_ptr<SSUSession> session)->bool
{
return session->GetState () == eSessionStateEstablished && session != excluded;
}
);
}
template<typename Filter>
std::shared_ptr<SSUSession> SSUServer::GetRandomV6Session (Filter filter) // v6 only
{
std::vector<std::shared_ptr<SSUSession> > filteredSessions;
for (const auto& s :m_SessionsV6)
if (filter (s.second)) filteredSessions.push_back (s.second);
if (filteredSessions.size () > 0)
{
auto ind = rand () % filteredSessions.size ();
return filteredSessions[ind];
}
return nullptr;
}
std::shared_ptr<SSUSession> SSUServer::GetRandomEstablishedV6Session (std::shared_ptr<const SSUSession> excluded) // v6 only
{
return GetRandomV6Session (
[excluded](std::shared_ptr<SSUSession> session)->bool
{
return session->GetState () == eSessionStateEstablished && session != excluded;
}
);
}
std::list<std::shared_ptr<SSUSession> > SSUServer::FindIntroducers (int maxNumIntroducers,
bool v4, std::set<i2p::data::IdentHash>& excluded)
{
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
std::list<std::shared_ptr<SSUSession> > ret;
const auto& sessions = v4 ? m_Sessions : m_SessionsV6;
for (const auto& s : sessions)
{
if (s.second->GetRelayTag () && s.second->GetState () == eSessionStateEstablished &&
ts < s.second->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_EXPIRATION)
ret.push_back (s.second);
else if (s.second->GetRemoteIdentity ())
excluded.insert (s.second->GetRemoteIdentity ()->GetIdentHash ());
}
if ((int)ret.size () > maxNumIntroducers)
{
// shink ret randomly
int sz = ret.size () - maxNumIntroducers;
for (int i = 0; i < sz; i++)
{
auto ind = rand () % ret.size ();
auto it = ret.begin ();
std::advance (it, ind);
ret.erase (it);
}
}
return ret;
}
void SSUServer::RescheduleIntroducersUpdateTimer ()
{
m_IntroducersUpdateTimer.cancel ();
m_IntroducersUpdateTimer.expires_from_now (boost::posix_time::seconds(SSU_KEEP_ALIVE_INTERVAL/2));
m_IntroducersUpdateTimer.async_wait (std::bind (&SSUServer::HandleIntroducersUpdateTimer,
this, std::placeholders::_1, true));
}
void SSUServer::ScheduleIntroducersUpdateTimer ()
{
m_IntroducersUpdateTimer.expires_from_now (boost::posix_time::seconds(SSU_KEEP_ALIVE_INTERVAL));
m_IntroducersUpdateTimer.async_wait (std::bind (&SSUServer::HandleIntroducersUpdateTimer,
this, std::placeholders::_1, true));
}
void SSUServer::RescheduleIntroducersUpdateTimerV6 ()
{
m_IntroducersUpdateTimerV6.cancel ();
m_IntroducersUpdateTimerV6.expires_from_now (boost::posix_time::seconds(SSU_KEEP_ALIVE_INTERVAL/2));
m_IntroducersUpdateTimerV6.async_wait (std::bind (&SSUServer::HandleIntroducersUpdateTimer,
this, std::placeholders::_1, false));
}
void SSUServer::ScheduleIntroducersUpdateTimerV6 ()
{
m_IntroducersUpdateTimerV6.expires_from_now (boost::posix_time::seconds(SSU_KEEP_ALIVE_INTERVAL));
m_IntroducersUpdateTimerV6.async_wait (std::bind (&SSUServer::HandleIntroducersUpdateTimer,
this, std::placeholders::_1, false));
}
void SSUServer::HandleIntroducersUpdateTimer (const boost::system::error_code& ecode, bool v4)
{
if (ecode != boost::asio::error::operation_aborted)
{
// timeout expired
if (v4)
{
if (i2p::context.GetStatus () == eRouterStatusTesting)
{
// we still don't know if we need introducers
ScheduleIntroducersUpdateTimer ();
return;
}
if (i2p::context.GetStatus () != eRouterStatusFirewalled)
{
// we don't need introducers
m_Introducers.clear ();
return;
}
// we are firewalled
if (!i2p::context.IsUnreachable ()) i2p::context.SetUnreachable (true, false); // v4
}
else
{
if (i2p::context.GetStatusV6 () == eRouterStatusTesting)
{
// we still don't know if we need introducers
ScheduleIntroducersUpdateTimerV6 ();
return;
}
if (i2p::context.GetStatusV6 () != eRouterStatusFirewalled)
{
// we don't need introducers
m_IntroducersV6.clear ();
return;
}
// we are firewalled
auto addr = i2p::context.GetRouterInfo ().GetSSUV6Address ();
if (addr && addr->ssu && addr->ssu->introducers.empty ())
i2p::context.SetUnreachable (false, true); // v6
}
std::list<boost::asio::ip::udp::endpoint> newList;
size_t numIntroducers = 0;
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
std::set<i2p::data::IdentHash> excluded;
auto& introducers = v4 ? m_Introducers : m_IntroducersV6;
for (const auto& it : introducers)
{
auto session = FindSession (it);
if (session)
{
if (ts < session->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_EXPIRATION)
session->SendKeepAlive ();
if (ts < session->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_DURATION)
{
newList.push_back (it);
numIntroducers++;
if (session->GetRemoteIdentity ())
excluded.insert (session->GetRemoteIdentity ()->GetIdentHash ());
}
else
session = nullptr;
}
if (!session)
i2p::context.RemoveIntroducer (it);
}
if (numIntroducers < SSU_MAX_NUM_INTRODUCERS)
{
// create new
auto sessions = FindIntroducers (SSU_MAX_NUM_INTRODUCERS, v4, excluded); // try to find if duplicates
if (sessions.empty () && !introducers.empty ())
{
// bump creation time for previous introducers if no new sessions found
LogPrint (eLogDebug, "SSU: No new introducers found. Trying to reuse existing");
for (const auto& it : introducers)
{
auto session = FindSession (it);
if (session)
session->SetCreationTime (session->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_DURATION);
}
// try again
excluded.clear ();
sessions = FindIntroducers (SSU_MAX_NUM_INTRODUCERS, v4, excluded);
}
for (const auto& it1: sessions)
{
const auto& ep = it1->GetRemoteEndpoint ();
i2p::data::RouterInfo::Introducer introducer;
introducer.iHost = ep.address ();
introducer.iPort = ep.port ();
introducer.iTag = it1->GetRelayTag ();
introducer.iKey = it1->GetIntroKey ();
introducer.iExp = it1->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_EXPIRATION;
if (i2p::context.AddIntroducer (introducer))
{
newList.push_back (ep);
if (newList.size () >= SSU_MAX_NUM_INTRODUCERS) break;
}
if (it1->GetRemoteIdentity ())
excluded.insert (it1->GetRemoteIdentity ()->GetIdentHash ());
}
}
introducers = newList;
if (introducers.size () < SSU_MAX_NUM_INTRODUCERS)
{
for (auto i = introducers.size (); i < SSU_MAX_NUM_INTRODUCERS; i++)
{
auto introducer = i2p::data::netdb.GetRandomIntroducer (v4, excluded);
if (introducer)
{
auto address = v4 ? introducer->GetSSUAddress (true) : introducer->GetSSUV6Address ();
if (address && !address->host.is_unspecified () && address->port)
{
boost::asio::ip::udp::endpoint ep (address->host, address->port);
if (std::find (introducers.begin (), introducers.end (), ep) == introducers.end ()) // not connected yet
{
CreateDirectSession (introducer, ep, false);
excluded.insert (introducer->GetIdentHash ());
}
}
}
else
{
LogPrint (eLogDebug, "SSU: Can't find more introducers");
break;
}
}
}
if (v4)
ScheduleIntroducersUpdateTimer ();
else
ScheduleIntroducersUpdateTimerV6 ();
}
}
void SSUServer::NewPeerTest (uint32_t nonce, PeerTestParticipant role, std::shared_ptr<SSUSession> session)
{
m_PeerTests[nonce] = { i2p::util::GetMillisecondsSinceEpoch (), role, session };
}
PeerTestParticipant SSUServer::GetPeerTestParticipant (uint32_t nonce)
{
auto it = m_PeerTests.find (nonce);
if (it != m_PeerTests.end ())
return it->second.role;
else
return ePeerTestParticipantUnknown;
}
std::shared_ptr<SSUSession> SSUServer::GetPeerTestSession (uint32_t nonce)
{
auto it = m_PeerTests.find (nonce);
if (it != m_PeerTests.end ())
return it->second.session;
else
return nullptr;
}
void SSUServer::UpdatePeerTest (uint32_t nonce, PeerTestParticipant role)
{
auto it = m_PeerTests.find (nonce);
if (it != m_PeerTests.end ())
it->second.role = role;
}
void SSUServer::RemovePeerTest (uint32_t nonce)
{
m_PeerTests.erase (nonce);
}
void SSUServer::SchedulePeerTestsCleanupTimer ()
{
m_PeerTestsCleanupTimer.expires_from_now (boost::posix_time::seconds(SSU_PEER_TEST_TIMEOUT));
m_PeerTestsCleanupTimer.async_wait (std::bind (&SSUServer::HandlePeerTestsCleanupTimer,
this, std::placeholders::_1));
}
void SSUServer::HandlePeerTestsCleanupTimer (const boost::system::error_code& ecode)
{
if (ecode != boost::asio::error::operation_aborted)
{
int numDeleted = 0;
uint64_t ts = i2p::util::GetMillisecondsSinceEpoch ();
for (auto it = m_PeerTests.begin (); it != m_PeerTests.end ();)
{
if (ts > it->second.creationTime + SSU_PEER_TEST_TIMEOUT*1000LL)
{
numDeleted++;
it = m_PeerTests.erase (it);
}
else
++it;
}
if (numDeleted > 0)
LogPrint (eLogDebug, "SSU: ", numDeleted, " peer tests have been expired");
// some cleaups. TODO: use separate timer
m_FragmentsPool.CleanUp ();
m_IncompleteMessagesPool.CleanUp ();
m_SentMessagesPool.CleanUp ();
SchedulePeerTestsCleanupTimer ();
}
}
void SSUServer::ScheduleTermination ()
{
uint64_t timeout = SSU_TERMINATION_CHECK_TIMEOUT + (rand () % SSU_TERMINATION_CHECK_TIMEOUT)/5;
m_TerminationTimer.expires_from_now (boost::posix_time::seconds(timeout));
m_TerminationTimer.async_wait (std::bind (&SSUServer::HandleTerminationTimer,
this, std::placeholders::_1));
}
void SSUServer::HandleTerminationTimer (const boost::system::error_code& ecode)
{
if (ecode != boost::asio::error::operation_aborted)
{
auto ts = i2p::util::GetSecondsSinceEpoch ();
for (auto& it: m_Sessions)
if (it.second->IsTerminationTimeoutExpired (ts))
{
auto session = it.second;
if (it.first != session->GetRemoteEndpoint ())
LogPrint (eLogWarning, "SSU: Remote endpoint ", session->GetRemoteEndpoint (), " doesn't match key ", it.first, " adjusted");
m_Service.post ([session]
{
LogPrint (eLogWarning, "SSU: No activity with ", session->GetRemoteEndpoint (), " for ", session->GetTerminationTimeout (), " seconds");
session->Failed ();
});
}
else
it.second->CleanUp (ts);
ScheduleTermination ();
}
}
void SSUServer::ScheduleTerminationV6 ()
{
uint64_t timeout = SSU_TERMINATION_CHECK_TIMEOUT + (rand () % SSU_TERMINATION_CHECK_TIMEOUT)/5;
m_TerminationTimerV6.expires_from_now (boost::posix_time::seconds(timeout));
m_TerminationTimerV6.async_wait (std::bind (&SSUServer::HandleTerminationTimerV6,
this, std::placeholders::_1));
}
void SSUServer::HandleTerminationTimerV6 (const boost::system::error_code& ecode)
{
if (ecode != boost::asio::error::operation_aborted)
{
auto ts = i2p::util::GetSecondsSinceEpoch ();
for (auto& it: m_SessionsV6)
if (it.second->IsTerminationTimeoutExpired (ts))
{
auto session = it.second;
if (it.first != session->GetRemoteEndpoint ())
LogPrint (eLogWarning, "SSU: Remote endpoint ", session->GetRemoteEndpoint (), " doesn't match key ", it.first);
m_Service.post ([session]
{
LogPrint (eLogWarning, "SSU: No activity with ", session->GetRemoteEndpoint (), " for ", session->GetTerminationTimeout (), " seconds");
session->Failed ();
});
}
else
it.second->CleanUp (ts);
ScheduleTerminationV6 ();
}
}
}
}

View File

@@ -1,159 +0,0 @@
/*
* Copyright (c) 2013-2022, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
* See full license text in LICENSE file at top of project tree
*/
#ifndef SSU_H__
#define SSU_H__
#include <inttypes.h>
#include <string.h>
#include <map>
#include <list>
#include <set>
#include <thread>
#include <mutex>
#include <boost/asio.hpp>
#include "Crypto.h"
#include "util.h"
#include "I2PEndian.h"
#include "Identity.h"
#include "RouterInfo.h"
#include "I2NPProtocol.h"
#include "SSUSession.h"
namespace i2p
{
namespace transport
{
const int SSU_KEEP_ALIVE_INTERVAL = 30; // 30 seconds
const int SSU_PEER_TEST_TIMEOUT = 60; // 60 seconds
const int SSU_TO_INTRODUCER_SESSION_DURATION = 3600; // 1 hour
const int SSU_TO_INTRODUCER_SESSION_EXPIRATION = 4800; // 80 minutes
const int SSU_TERMINATION_CHECK_TIMEOUT = 30; // 30 seconds
const size_t SSU_MAX_NUM_INTRODUCERS = 3;
const size_t SSU_SOCKET_RECEIVE_BUFFER_SIZE = 0x1FFFF; // 128K
const size_t SSU_SOCKET_SEND_BUFFER_SIZE = 0x1FFFF; // 128K
struct SSUPacket
{
i2p::crypto::AESAlignedBuffer<SSU_MTU_V6 + 18> buf; // max MTU + iv + size
boost::asio::ip::udp::endpoint from;
size_t len;
};
class SSUServer
{
public:
SSUServer (int port);
~SSUServer ();
void Start ();
void Stop ();
bool CreateSession (std::shared_ptr<const i2p::data::RouterInfo> router, bool peerTest = false, bool v4only = false);
bool CreateSession (std::shared_ptr<const i2p::data::RouterInfo> router,
std::shared_ptr<const i2p::data::RouterInfo::Address> address, bool peerTest = false);
void CreateDirectSession (std::shared_ptr<const i2p::data::RouterInfo> router, boost::asio::ip::udp::endpoint remoteEndpoint, bool peerTest);
std::shared_ptr<SSUSession> FindSession (const boost::asio::ip::udp::endpoint& e) const;
std::shared_ptr<SSUSession> GetRandomEstablishedV4Session (std::shared_ptr<const SSUSession> excluded);
std::shared_ptr<SSUSession> GetRandomEstablishedV6Session (std::shared_ptr<const SSUSession> excluded);
void DeleteSession (std::shared_ptr<SSUSession> session);
void DeleteAllSessions ();
boost::asio::io_service& GetService () { return m_Service; };
i2p::util::MemoryPool<Fragment>& GetFragmentsPool () { return m_FragmentsPool; };
i2p::util::MemoryPool<IncompleteMessage>& GetIncompleteMessagesPool () { return m_IncompleteMessagesPool; };
i2p::util::MemoryPool<SentMessage>& GetSentMessagesPool () { return m_SentMessagesPool; };
uint16_t GetPort () const { return m_Endpoint.port (); };
bool IsSyncClockFromPeers () const { return m_IsSyncClockFromPeers; };
void SetLocalAddress (const boost::asio::ip::address& localAddress);
void Send (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& to);
void AddRelay (uint32_t tag, std::shared_ptr<SSUSession> relay);
void RemoveRelay (uint32_t tag);
std::shared_ptr<SSUSession> FindRelaySession (uint32_t tag);
void RescheduleIntroducersUpdateTimer ();
void RescheduleIntroducersUpdateTimerV6 ();
void NewPeerTest (uint32_t nonce, PeerTestParticipant role, std::shared_ptr<SSUSession> session = nullptr);
PeerTestParticipant GetPeerTestParticipant (uint32_t nonce);
std::shared_ptr<SSUSession> GetPeerTestSession (uint32_t nonce);
void UpdatePeerTest (uint32_t nonce, PeerTestParticipant role);
void RemovePeerTest (uint32_t nonce);
private:
void OpenSocket ();
void OpenSocketV6 ();
void Run ();
void RunReceivers ();
void RunReceiversV6 ();
void Receive ();
void ReceiveV6 ();
void HandleReceivedFrom (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet);
void HandleReceivedFromV6 (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet);
void HandleReceivedPackets (std::vector<SSUPacket *> packets,
std::map<boost::asio::ip::udp::endpoint, std::shared_ptr<SSUSession> >* sessions);
void CreateSessionThroughIntroducer (std::shared_ptr<const i2p::data::RouterInfo> router,
std::shared_ptr<const i2p::data::RouterInfo::Address> address, bool peerTest = false);
template<typename Filter>
std::shared_ptr<SSUSession> GetRandomV4Session (Filter filter);
template<typename Filter>
std::shared_ptr<SSUSession> GetRandomV6Session (Filter filter);
std::list<std::shared_ptr<SSUSession> > FindIntroducers (int maxNumIntroducers, bool v4, std::set<i2p::data::IdentHash>& excluded);
void ScheduleIntroducersUpdateTimer ();
void ScheduleIntroducersUpdateTimerV6 ();
void HandleIntroducersUpdateTimer (const boost::system::error_code& ecode, bool v4);
void SchedulePeerTestsCleanupTimer ();
void HandlePeerTestsCleanupTimer (const boost::system::error_code& ecode);
// timer
void ScheduleTermination ();
void HandleTerminationTimer (const boost::system::error_code& ecode);
void ScheduleTerminationV6 ();
void HandleTerminationTimerV6 (const boost::system::error_code& ecode);
private:
struct PeerTest
{
uint64_t creationTime;
PeerTestParticipant role;
std::shared_ptr<SSUSession> session; // for Bob to Alice
};
volatile bool m_IsRunning;
std::thread * m_Thread, * m_ReceiversThread, * m_ReceiversThreadV6;
boost::asio::io_service m_Service, m_ReceiversService, m_ReceiversServiceV6;
boost::asio::io_service::work m_Work, m_ReceiversWork, m_ReceiversWorkV6;
boost::asio::ip::udp::endpoint m_Endpoint, m_EndpointV6;
boost::asio::ip::udp::socket m_Socket, m_SocketV6;
boost::asio::deadline_timer m_IntroducersUpdateTimer, m_IntroducersUpdateTimerV6,
m_PeerTestsCleanupTimer, m_TerminationTimer, m_TerminationTimerV6;
bool m_IsSyncClockFromPeers;
std::list<boost::asio::ip::udp::endpoint> m_Introducers, m_IntroducersV6; // introducers we are connected to
std::map<boost::asio::ip::udp::endpoint, std::shared_ptr<SSUSession> > m_Sessions, m_SessionsV6;
std::map<uint32_t, std::shared_ptr<SSUSession> > m_Relays; // we are introducer
std::map<uint32_t, PeerTest> m_PeerTests; // nonce -> creation time in milliseconds
i2p::util::MemoryPool<Fragment> m_FragmentsPool;
i2p::util::MemoryPool<IncompleteMessage> m_IncompleteMessagesPool;
i2p::util::MemoryPool<SentMessage> m_SentMessagesPool;
i2p::util::MemoryPoolMt<SSUPacket> m_PacketsPool;
public:
// for HTTP only
const decltype(m_Sessions)& GetSessions () const { return m_Sessions; };
const decltype(m_SessionsV6)& GetSessionsV6 () const { return m_SessionsV6; };
};
}
}
#endif

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022, The PurpleI2P Project
* Copyright (c) 2022-2023, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -22,7 +22,7 @@ namespace transport
RunnableServiceWithWork ("SSU2"), m_ReceiveService ("SSU2r"),
m_SocketV4 (m_ReceiveService.GetService ()), m_SocketV6 (m_ReceiveService.GetService ()),
m_AddressV4 (boost::asio::ip::address_v4()), m_AddressV6 (boost::asio::ip::address_v6()),
m_TerminationTimer (GetService ()), m_ResendTimer (GetService ()),
m_TerminationTimer (GetService ()), m_CleanupTimer (GetService ()), m_ResendTimer (GetService ()),
m_IntroducersUpdateTimer (GetService ()), m_IntroducersUpdateTimerV6 (GetService ()),
m_IsPublished (true), m_IsSyncClockFromPeers (true), m_IsThroughProxy (false)
{
@@ -36,8 +36,9 @@ namespace transport
i2p::config::GetOption ("ssu2.published", m_IsPublished);
i2p::config::GetOption("nettime.frompeers", m_IsSyncClockFromPeers);
bool found = false;
auto& addresses = i2p::context.GetRouterInfo ().GetAddresses ();
for (const auto& address: addresses)
auto addresses = i2p::context.GetRouterInfo ().GetAddresses ();
if (!addresses) return;
for (const auto& address: *addresses)
{
if (!address) continue;
if (address->transportStyle == i2p::data::RouterInfo::eTransportSSU2)
@@ -46,21 +47,21 @@ namespace transport
{
found = true;
if (address->IsV6 ())
{
{
uint16_t mtu; i2p::config::GetOption ("ssu2.mtu6", mtu);
if (!mtu || mtu > SSU2_MAX_PACKET_SIZE - SOCKS5_UDP_IPV6_REQUEST_HEADER_SIZE)
mtu = SSU2_MAX_PACKET_SIZE - SOCKS5_UDP_IPV6_REQUEST_HEADER_SIZE;
i2p::context.SetMTU (mtu, false);
}
else
{
}
else
{
uint16_t mtu; i2p::config::GetOption ("ssu2.mtu4", mtu);
if (!mtu || mtu > SSU2_MAX_PACKET_SIZE - SOCKS5_UDP_IPV4_REQUEST_HEADER_SIZE)
mtu = SSU2_MAX_PACKET_SIZE - SOCKS5_UDP_IPV4_REQUEST_HEADER_SIZE;
i2p::context.SetMTU (mtu, true);
}
}
continue; // we don't need port for proxy
}
}
auto port = address->port;
if (!port)
{
@@ -68,9 +69,8 @@ namespace transport
if (ssu2Port) port = ssu2Port;
else
{
bool ssu; i2p::config::GetOption("ssu", ssu);
uint16_t p; i2p::config::GetOption ("port", p);
if (p) port = ssu ? (p + 1) : p;
if (p) port = p;
}
}
if (port)
@@ -103,12 +103,13 @@ namespace transport
}
}
if (found)
{
{
if (m_IsThroughProxy)
ConnectToProxy ();
m_ReceiveService.Start ();
}
}
ScheduleTermination ();
ScheduleCleanup ();
ScheduleResend (false);
}
}
@@ -118,6 +119,7 @@ namespace transport
if (IsRunning ())
{
m_TerminationTimer.cancel ();
m_CleanupTimer.cancel ();
m_ResendTimer.cancel ();
m_IntroducersUpdateTimer.cancel ();
m_IntroducersUpdateTimerV6.cancel ();
@@ -136,11 +138,11 @@ namespace transport
m_SocketV6.close ();
if (m_UDPAssociateSocket)
{
{
m_UDPAssociateSocket->close ();
m_UDPAssociateSocket.reset (nullptr);
}
}
StopIOService ();
m_Sessions.clear ();
@@ -168,12 +170,12 @@ namespace transport
m_AddressV6 = localAddress;
uint16_t mtu; i2p::config::GetOption ("ssu2.mtu6", mtu);
if (!mtu)
{
{
int maxMTU = i2p::util::net::GetMaxMTU (localAddress.to_v6 ());
mtu = i2p::util::net::GetMTU (localAddress);
if (mtu > maxMTU) mtu = maxMTU;
}
else
}
else
if (mtu > (int)SSU2_MAX_PACKET_SIZE) mtu = SSU2_MAX_PACKET_SIZE;
if (mtu < (int)SSU2_MIN_PACKET_SIZE) mtu = SSU2_MIN_PACKET_SIZE;
i2p::context.SetMTU (mtu, false);
@@ -236,7 +238,19 @@ namespace transport
void SSU2Server::HandleReceivedFrom (const boost::system::error_code& ecode, size_t bytes_transferred,
Packet * packet, boost::asio::ip::udp::socket& socket)
{
if (!ecode)
if (!ecode
|| ecode == boost::asio::error::connection_refused
|| ecode == boost::asio::error::connection_reset
|| ecode == boost::asio::error::network_unreachable
|| ecode == boost::asio::error::host_unreachable
#ifdef _WIN32 // windows can throw WinAPI error, which is not handled by ASIO
|| ecode.value() == boost::winapi::ERROR_CONNECTION_REFUSED_
|| ecode.value() == boost::winapi::ERROR_NETWORK_UNREACHABLE_
|| ecode.value() == boost::winapi::ERROR_HOST_UNREACHABLE_
#endif
)
// just try continue reading when received ICMP response otherwise socket can crash,
// but better to find out which host were sent it and mark that router as unreachable
{
i2p::transport::transports.UpdateReceivedBytes (bytes_transferred);
packet->len = bytes_transferred;
@@ -285,12 +299,12 @@ namespace transport
ConnectToProxy ();
}
else
{
{
auto ep = socket.local_endpoint ();
socket.close ();
OpenSocket (ep);
Receive (socket);
}
}
}
}
}
@@ -301,7 +315,7 @@ namespace transport
{
if (m_IsThroughProxy)
ProcessNextPacketFromProxy (packet->buf, packet->len);
else
else
ProcessNextPacket (packet->buf, packet->len, packet->from);
m_PacketsPool.ReleaseMt (packet);
if (m_LastSession && m_LastSession->GetState () != eSSU2SessionStateTerminated)
@@ -314,7 +328,7 @@ namespace transport
if (m_IsThroughProxy)
for (auto& packet: packets)
ProcessNextPacketFromProxy (packet->buf, packet->len);
else
else
for (auto& packet: packets)
ProcessNextPacket (packet->buf, packet->len, packet->from);
m_PacketsPool.ReleaseMt (packets);
@@ -369,6 +383,7 @@ namespace transport
bool SSU2Server::AddPendingOutgoingSession (std::shared_ptr<SSU2Session> session)
{
if (!session) return false;
std::unique_lock<std::mutex> l(m_PendingOutgoingSessionsMutex);
return m_PendingOutgoingSessions.emplace (session->GetRemoteEndpoint (), session).second;
}
@@ -382,6 +397,7 @@ namespace transport
std::shared_ptr<SSU2Session> SSU2Server::FindPendingOutgoingSession (const boost::asio::ip::udp::endpoint& ep) const
{
std::unique_lock<std::mutex> l(m_PendingOutgoingSessionsMutex);
auto it = m_PendingOutgoingSessions.find (ep);
if (it != m_PendingOutgoingSessions.end ())
return it->second;
@@ -390,6 +406,7 @@ namespace transport
void SSU2Server::RemovePendingOutgoingSession (const boost::asio::ip::udp::endpoint& ep)
{
std::unique_lock<std::mutex> l(m_PendingOutgoingSessionsMutex);
m_PendingOutgoingSessions.erase (ep);
}
@@ -409,7 +426,7 @@ namespace transport
return it->second;
it++;
}
// not found, try from begining
// not found, try from beginning
it = m_Sessions.begin ();
while (it != m_Sessions.end () && ind)
{
@@ -443,7 +460,7 @@ namespace transport
}
return nullptr;
}
void SSU2Server::ProcessNextPacket (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint)
{
if (len < 24) return;
@@ -493,9 +510,10 @@ namespace transport
break;
case eSSU2SessionStateClosing:
m_LastSession->ProcessData (buf, len, senderEndpoint); // we might receive termintaion block
if (m_LastSession && m_LastSession->GetState () != eSSU2SessionStateTerminated)
if (m_LastSession && m_LastSession->GetState () == eSSU2SessionStateClosing)
m_LastSession->RequestTermination (eSSU2TerminationReasonIdleTimeout); // send termination again
break;
case eSSU2SessionStateClosingConfirmed:
case eSSU2SessionStateTerminated:
m_LastSession = nullptr;
break;
@@ -511,20 +529,25 @@ namespace transport
{
if (it1->second->GetState () == eSSU2SessionStateSessionRequestSent &&
it1->second->ProcessSessionCreated (buf, len))
{
std::unique_lock<std::mutex> l(m_PendingOutgoingSessionsMutex);
m_PendingOutgoingSessions.erase (it1); // we are done with that endpoint
}
else
it1->second->ProcessRetry (buf, len);
}
else
else if (!i2p::util::net::IsInReservedRange(senderEndpoint.address ()) && senderEndpoint.port ())
{
// assume new incoming session
auto session = std::make_shared<SSU2Session> (*this);
session->SetRemoteEndpoint (senderEndpoint);
session->ProcessFirstIncomingMessage (connID, buf, len);
}
else
LogPrint (eLogError, "SSU2: Incoming packet received from invalid endpoint ", senderEndpoint);
}
}
void SSU2Server::Send (const uint8_t * header, size_t headerLen, const uint8_t * payload, size_t payloadLen,
const boost::asio::ip::udp::endpoint& to)
{
@@ -532,7 +555,7 @@ namespace transport
{
SendThroughProxy (header, headerLen, nullptr, 0, payload, payloadLen, to);
return;
}
}
std::vector<boost::asio::const_buffer> bufs
{
boost::asio::buffer (header, headerLen),
@@ -642,7 +665,7 @@ namespace transport
// try to find existing session first
for (auto& it: address->ssu->introducers)
{
auto it1 = m_SessionsByRouterHash.find (it.iKey);
auto it1 = m_SessionsByRouterHash.find (it.iH);
if (it1 != m_SessionsByRouterHash.end ())
{
it1->second->Introduce (session, it.iTag);
@@ -655,17 +678,17 @@ namespace transport
uint32_t relayTag = 0;
if (!address->ssu->introducers.empty ())
{
std::vector<int> indicies;
for (int i = 0; i < (int)address->ssu->introducers.size (); i++) indicies.push_back(i);
if (indicies.size () > 1)
std::shuffle (indicies.begin(), indicies.end(), std::mt19937(std::random_device()()));
std::vector<int> indices;
for (int i = 0; i < (int)address->ssu->introducers.size (); i++) indices.push_back(i);
if (indices.size () > 1)
std::shuffle (indices.begin(), indices.end(), std::mt19937(std::random_device()()));
for (auto i: indicies)
for (auto i: indices)
{
const auto& introducer = address->ssu->introducers[indicies[i]];
const auto& introducer = address->ssu->introducers[indices[i]];
if (introducer.iTag && ts < introducer.iExp)
{
r = i2p::data::netdb.FindRouter (introducer.iKey);
r = i2p::data::netdb.FindRouter (introducer.iH);
if (r && r->IsReachableFrom (i2p::context.GetRouterInfo ()))
{
relayTag = introducer.iTag;
@@ -714,7 +737,7 @@ namespace transport
// introducers not found, try to request them
for (auto& it: address->ssu->introducers)
if (it.iTag && ts < it.iExp)
i2p::data::netdb.RequestDestination (it.iKey);
i2p::data::netdb.RequestDestination (it.iH);
}
}
@@ -755,6 +778,7 @@ namespace transport
if (it->second->IsTerminationTimeoutExpired (ts))
{
//it->second->Terminate ();
std::unique_lock<std::mutex> l(m_PendingOutgoingSessionsMutex);
it = m_PendingOutgoingSessions.erase (it);
}
else
@@ -785,6 +809,22 @@ namespace transport
it++;
}
ScheduleTermination ();
}
}
void SSU2Server::ScheduleCleanup ()
{
m_CleanupTimer.expires_from_now (boost::posix_time::seconds(SSU2_CLEANUP_INTERVAL));
m_CleanupTimer.async_wait (std::bind (&SSU2Server::HandleCleanupTimer,
this, std::placeholders::_1));
}
void SSU2Server::HandleCleanupTimer (const boost::system::error_code& ecode)
{
if (ecode != boost::asio::error::operation_aborted)
{
auto ts = i2p::util::GetSecondsSinceEpoch ();
for (auto it = m_Relays.begin (); it != m_Relays.begin ();)
{
if (it->second && it->second->GetState () == eSSU2SessionStateTerminated)
@@ -808,13 +848,15 @@ namespace transport
else
it++;
}
m_PacketsPool.CleanUpMt ();
m_SentPacketsPool.CleanUp ();
ScheduleTermination ();
}
}
m_IncompleteMessagesPool.CleanUp ();
m_FragmentsPool.CleanUp ();
ScheduleCleanup ();
}
}
void SSU2Server::ScheduleResend (bool more)
{
m_ResendTimer.expires_from_now (boost::posix_time::milliseconds (more ? SSU2_RESEND_CHECK_MORE_TIMEOUT :
@@ -845,13 +887,17 @@ namespace transport
m_OutgoingTokens[ep] = {token, exp};
}
uint64_t SSU2Server::FindOutgoingToken (const boost::asio::ip::udp::endpoint& ep) const
uint64_t SSU2Server::FindOutgoingToken (const boost::asio::ip::udp::endpoint& ep)
{
auto it = m_OutgoingTokens.find (ep);
if (it != m_OutgoingTokens.end ())
{
if (i2p::util::GetSecondsSinceEpoch () + SSU2_TOKEN_EXPIRATION_THRESHOLD > it->second.second)
return 0; // token expired
{
// token expired
m_OutgoingTokens.erase (it);
return 0;
}
return it->second.first;
}
return 0;
@@ -859,12 +905,18 @@ namespace transport
uint64_t SSU2Server::GetIncomingToken (const boost::asio::ip::udp::endpoint& ep)
{
auto ts = i2p::util::GetSecondsSinceEpoch ();
auto it = m_IncomingTokens.find (ep);
if (it != m_IncomingTokens.end ())
return it->second.first;
{
if (ts + SSU2_TOKEN_EXPIRATION_THRESHOLD <= it->second.second)
return it->second.first;
else // token expired
m_IncomingTokens.erase (it);
}
uint64_t token;
RAND_bytes ((uint8_t *)&token, 8);
m_IncomingTokens.emplace (ep, std::make_pair (token, i2p::util::GetSecondsSinceEpoch () + SSU2_TOKEN_EXPIRATION_TIMEOUT));
m_IncomingTokens.emplace (ep, std::make_pair (token, ts + SSU2_TOKEN_EXPIRATION_TIMEOUT));
return token;
}
@@ -962,7 +1014,8 @@ namespace transport
{
i2p::data::RouterInfo::Introducer introducer;
introducer.iTag = it->GetRelayTag ();
introducer.iKey = it->GetRemoteIdentity ()->GetIdentHash ();
introducer.iH = it->GetRemoteIdentity ()->GetIdentHash ();
introducer.isH = true;
introducer.iExp = it->GetCreationTime () + SSU2_TO_INTRODUCER_SESSION_EXPIRATION;
excluded.insert (it->GetRemoteIdentity ()->GetIdentHash ());
if (i2p::context.AddSSU2Introducer (introducer, v4))
@@ -1068,7 +1121,7 @@ namespace transport
// we are firewalled
auto addr = i2p::context.GetRouterInfo ().GetSSU2V4Address ();
if (addr && addr->ssu && addr->ssu->introducers.empty ())
i2p::context.SetUnreachableSSU2 (true, false); // v4
i2p::context.SetUnreachable (true, false); // v4
UpdateIntroducers (true);
ScheduleIntroducersUpdateTimer ();
@@ -1091,7 +1144,7 @@ namespace transport
// we are firewalled
auto addr = i2p::context.GetRouterInfo ().GetSSU2V6Address ();
if (addr && addr->ssu && addr->ssu->introducers.empty ())
i2p::context.SetUnreachableSSU2 (false, true); // v6
i2p::context.SetUnreachable (false, true); // v6
UpdateIntroducers (false);
ScheduleIntroducersUpdateTimerV6 ();
@@ -1106,23 +1159,23 @@ namespace transport
size_t requestHeaderSize = 0;
memset (m_UDPRequestHeader, 0, 3);
if (to.address ().is_v6 ())
{
{
m_UDPRequestHeader[3] = SOCKS5_ATYP_IPV6;
memcpy (m_UDPRequestHeader + 4, to.address ().to_v6().to_bytes().data(), 16);
requestHeaderSize = SOCKS5_UDP_IPV6_REQUEST_HEADER_SIZE;
}
}
else
{
{
m_UDPRequestHeader[3] = SOCKS5_ATYP_IPV4;
memcpy (m_UDPRequestHeader + 4, to.address ().to_v4().to_bytes().data(), 4);
requestHeaderSize = SOCKS5_UDP_IPV4_REQUEST_HEADER_SIZE;
}
}
htobe16buf (m_UDPRequestHeader + requestHeaderSize - 2, to.port ());
std::vector<boost::asio::const_buffer> bufs;
bufs.push_back (boost::asio::buffer (m_UDPRequestHeader, requestHeaderSize));
bufs.push_back (boost::asio::buffer (header, headerLen));
if (headerX) bufs.push_back (boost::asio::buffer (headerX, headerXLen));
if (headerX) bufs.push_back (boost::asio::buffer (headerX, headerXLen));
bufs.push_back (boost::asio::buffer (payload, payloadLen));
boost::system::error_code ec;
@@ -1131,7 +1184,7 @@ namespace transport
i2p::transport::transports.UpdateSentBytes (headerLen + payloadLen);
else
LogPrint (eLogError, "SSU2: Send exception: ", ec.message (), " to ", to);
}
}
void SSU2Server::ProcessNextPacketFromProxy (uint8_t * buf, size_t len)
{
@@ -1139,11 +1192,11 @@ namespace transport
{
LogPrint (eLogWarning, "SSU2: Proxy packet fragmentation is not supported");
return;
}
}
size_t offset = 0;
boost::asio::ip::udp::endpoint ep;
switch (buf[3]) // ATYP
{
{
case SOCKS5_ATYP_IPV4:
{
offset = SOCKS5_UDP_IPV4_REQUEST_HEADER_SIZE;
@@ -1153,7 +1206,7 @@ namespace transport
uint16_t port = bufbe16toh (buf + 8);
ep = boost::asio::ip::udp::endpoint (boost::asio::ip::address_v4 (bytes), port);
break;
}
}
case SOCKS5_ATYP_IPV6:
{
offset = SOCKS5_UDP_IPV6_REQUEST_HEADER_SIZE;
@@ -1163,23 +1216,23 @@ namespace transport
uint16_t port = bufbe16toh (buf + 20);
ep = boost::asio::ip::udp::endpoint (boost::asio::ip::address_v6 (bytes), port);
break;
}
}
default:
{
LogPrint (eLogWarning, "SSU2: Unknown ATYP ", (int)buf[3], " from proxy relay");
return;
}
}
}
}
ProcessNextPacket (buf + offset, len - offset, ep);
}
}
void SSU2Server::ConnectToProxy ()
{
if (!m_ProxyEndpoint) return;
m_UDPAssociateSocket.reset (new boost::asio::ip::tcp::socket (m_ReceiveService.GetService ()));
m_UDPAssociateSocket->async_connect (*m_ProxyEndpoint,
m_UDPAssociateSocket->async_connect (*m_ProxyEndpoint,
[this] (const boost::system::error_code& ecode)
{
{
if (ecode)
{
LogPrint (eLogError, "SSU2: Can't connect to proxy ", *m_ProxyEndpoint, " ", ecode.message ());
@@ -1189,12 +1242,12 @@ namespace transport
else
HandshakeWithProxy ();
});
}
}
void SSU2Server::HandshakeWithProxy ()
{
if (!m_UDPAssociateSocket) return;
m_UDPRequestHeader[0] = SOCKS5_VER;
m_UDPRequestHeader[0] = SOCKS5_VER;
m_UDPRequestHeader[1] = 1; // 1 method
m_UDPRequestHeader[2] = 0; // no authentication
boost::asio::async_write (*m_UDPAssociateSocket, boost::asio::buffer (m_UDPRequestHeader, 3), boost::asio::transfer_all(),
@@ -1204,13 +1257,13 @@ namespace transport
if (ecode)
{
LogPrint(eLogError, "SSU2: Proxy write error ", ecode.message());
m_UDPAssociateSocket.reset (nullptr);
m_UDPAssociateSocket.reset (nullptr);
ReconnectToProxy ();
}
}
else
ReadHandshakeWithProxyReply ();
});
}
}
void SSU2Server::ReadHandshakeWithProxyReply ()
{
@@ -1224,7 +1277,7 @@ namespace transport
LogPrint(eLogError, "SSU2: Proxy read error ", ecode.message());
m_UDPAssociateSocket.reset (nullptr);
ReconnectToProxy ();
}
}
else
{
if (m_UDPRequestHeader[0] == SOCKS5_VER && !m_UDPRequestHeader[1])
@@ -1233,16 +1286,16 @@ namespace transport
{
LogPrint(eLogError, "SSU2: Invalid proxy reply");
m_UDPAssociateSocket.reset (nullptr);
}
}
});
}
}
}
});
}
void SSU2Server::SendUDPAssociateRequest ()
{
if (!m_UDPAssociateSocket) return;
m_UDPRequestHeader[0] = SOCKS5_VER;
m_UDPRequestHeader[1] = SOCKS5_CMD_UDP_ASSOCIATE;
m_UDPRequestHeader[0] = SOCKS5_VER;
m_UDPRequestHeader[1] = SOCKS5_CMD_UDP_ASSOCIATE;
m_UDPRequestHeader[2] = 0; // RSV
m_UDPRequestHeader[3] = SOCKS5_ATYP_IPV4; // TODO: implement ipv6 proxy
memset (m_UDPRequestHeader + 4, 0, 6); // address and port all zeros
@@ -1253,13 +1306,13 @@ namespace transport
if (ecode)
{
LogPrint(eLogError, "SSU2: Proxy write error ", ecode.message());
m_UDPAssociateSocket.reset (nullptr);
m_UDPAssociateSocket.reset (nullptr);
ReconnectToProxy ();
}
}
else
ReadUDPAssociateReply ();
});
}
}
void SSU2Server::ReadUDPAssociateReply ()
{
@@ -1273,11 +1326,11 @@ namespace transport
LogPrint(eLogError, "SSU2: Proxy read error ", ecode.message());
m_UDPAssociateSocket.reset (nullptr);
ReconnectToProxy ();
}
}
else
{
if (m_UDPRequestHeader[0] == SOCKS5_VER && !m_UDPRequestHeader[1])
{
{
if (m_UDPRequestHeader[3] == SOCKS5_ATYP_IPV4)
{
boost::asio::ip::address_v4::bytes_type bytes;
@@ -1287,21 +1340,21 @@ namespace transport
m_SocketV4.open (boost::asio::ip::udp::v4 ());
Receive (m_SocketV4);
ReadUDPAssociateSocket ();
}
}
else
{
LogPrint(eLogError, "SSU2: Proxy UDP associate unsupported ATYP ", (int)m_UDPRequestHeader[3]);
m_UDPAssociateSocket.reset (nullptr);
}
}
}
}
else
{
LogPrint(eLogError, "SSU2: Proxy UDP associate error ", (int)m_UDPRequestHeader[1]);
m_UDPAssociateSocket.reset (nullptr);
}
}
});
}
}
}
});
}
void SSU2Server::ReadUDPAssociateSocket ()
{
@@ -1317,18 +1370,18 @@ namespace transport
m_ProxyRelayEndpoint.reset (nullptr);
m_SocketV4.close ();
ConnectToProxy (); // try to reconnect immediately
}
}
else
ReadUDPAssociateSocket ();
});
}
});
}
void SSU2Server::ReconnectToProxy ()
{
LogPrint(eLogInfo, "SSU2: Reconnect to proxy after ", SSU2_PROXY_CONNECT_RETRY_TIMEOUT, " seconds");
if (m_ProxyConnectRetryTimer)
m_ProxyConnectRetryTimer->cancel ();
else
else
m_ProxyConnectRetryTimer.reset (new boost::asio::deadline_timer (m_ReceiveService.GetService ()));
m_ProxyConnectRetryTimer->expires_from_now (boost::posix_time::seconds (SSU2_PROXY_CONNECT_RETRY_TIMEOUT));
m_ProxyConnectRetryTimer->async_wait (
@@ -1341,9 +1394,9 @@ namespace transport
LogPrint(eLogInfo, "SSU2: Reconnecting to proxy");
ConnectToProxy ();
}
});
}
});
}
bool SSU2Server::SetProxy (const std::string& address, uint16_t port)
{
boost::system::error_code ecode;
@@ -1352,14 +1405,14 @@ namespace transport
{
m_IsThroughProxy = true;
m_ProxyEndpoint.reset (new boost::asio::ip::tcp::endpoint (addr, port));
}
}
else
{
if (ecode)
LogPrint (eLogError, "SSU2: Invalid proxy address ", address, " ", ecode.message());
return false;
}
}
return true;
}
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022, The PurpleI2P Project
* Copyright (c) 2022-2023, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -10,6 +10,7 @@
#define SSU2_H__
#include <unordered_map>
#include <mutex>
#include "util.h"
#include "SSU2Session.h"
@@ -17,7 +18,8 @@ namespace i2p
{
namespace transport
{
const int SSU2_TERMINATION_CHECK_TIMEOUT = 30; // in seconds
const int SSU2_TERMINATION_CHECK_TIMEOUT = 25; // in seconds
const int SSU2_CLEANUP_INTERVAL = 72; // in seconds
const int SSU2_RESEND_CHECK_TIMEOUT = 400; // in milliseconds
const int SSU2_RESEND_CHECK_TIMEOUT_VARIANCE = 100; // in milliseconds
const int SSU2_RESEND_CHECK_MORE_TIMEOUT = 10; // in milliseconds
@@ -29,7 +31,7 @@ namespace transport
const int SSU2_TO_INTRODUCER_SESSION_EXPIRATION = 4800; // 80 minutes
const int SSU2_KEEP_ALIVE_INTERVAL = 30; // in seconds
const int SSU2_PROXY_CONNECT_RETRY_TIMEOUT = 30; // in seconds
class SSU2Server: private i2p::util::RunnableServiceWithWork
{
struct Packet
@@ -88,7 +90,7 @@ namespace transport
bool StartPeerTest (std::shared_ptr<const i2p::data::RouterInfo> router, bool v4);
void UpdateOutgoingToken (const boost::asio::ip::udp::endpoint& ep, uint64_t token, uint32_t exp);
uint64_t FindOutgoingToken (const boost::asio::ip::udp::endpoint& ep) const;
uint64_t FindOutgoingToken (const boost::asio::ip::udp::endpoint& ep);
uint64_t GetIncomingToken (const boost::asio::ip::udp::endpoint& ep);
std::pair<uint64_t, uint32_t> NewIncomingToken (const boost::asio::ip::udp::endpoint& ep);
@@ -96,7 +98,9 @@ namespace transport
void RescheduleIntroducersUpdateTimerV6 ();
i2p::util::MemoryPool<SSU2SentPacket>& GetSentPacketsPool () { return m_SentPacketsPool; };
i2p::util::MemoryPool<SSU2IncompleteMessage>& GetIncompleteMessagesPool () { return m_IncompleteMessagesPool; };
i2p::util::MemoryPool<SSU2IncompleteMessage::Fragment>& GetFragmentsPool () { return m_FragmentsPool; };
private:
boost::asio::ip::udp::socket& OpenSocket (const boost::asio::ip::udp::endpoint& localEndpoint);
@@ -110,6 +114,9 @@ namespace transport
void ScheduleTermination ();
void HandleTerminationTimer (const boost::system::error_code& ecode);
void ScheduleCleanup ();
void HandleCleanupTimer (const boost::system::error_code& ecode);
void ScheduleResend (bool more);
void HandleResendTimer (const boost::system::error_code& ecode);
@@ -131,7 +138,7 @@ namespace transport
void SendUDPAssociateRequest ();
void ReadUDPAssociateReply ();
void ReadUDPAssociateSocket (); // handle if closed by peer
private:
ReceiveService m_ReceiveService;
@@ -140,12 +147,15 @@ namespace transport
std::unordered_map<uint64_t, std::shared_ptr<SSU2Session> > m_Sessions;
std::unordered_map<i2p::data::IdentHash, std::shared_ptr<SSU2Session> > m_SessionsByRouterHash;
std::map<boost::asio::ip::udp::endpoint, std::shared_ptr<SSU2Session> > m_PendingOutgoingSessions;
mutable std::mutex m_PendingOutgoingSessionsMutex;
std::map<boost::asio::ip::udp::endpoint, std::pair<uint64_t, uint32_t> > m_IncomingTokens, m_OutgoingTokens; // remote endpoint -> (token, expires in seconds)
std::map<uint32_t, std::shared_ptr<SSU2Session> > m_Relays; // we are introducer, relay tag -> session
std::list<i2p::data::IdentHash> m_Introducers, m_IntroducersV6; // introducers we are connected to
i2p::util::MemoryPoolMt<Packet> m_PacketsPool;
i2p::util::MemoryPool<SSU2SentPacket> m_SentPacketsPool;
boost::asio::deadline_timer m_TerminationTimer, m_ResendTimer,
i2p::util::MemoryPool<SSU2IncompleteMessage> m_IncompleteMessagesPool;
i2p::util::MemoryPool<SSU2IncompleteMessage::Fragment> m_FragmentsPool;
boost::asio::deadline_timer m_TerminationTimer, m_CleanupTimer, m_ResendTimer,
m_IntroducersUpdateTimer, m_IntroducersUpdateTimerV6;
std::shared_ptr<SSU2Session> m_LastSession;
bool m_IsPublished; // if we maintain introducers
@@ -158,7 +168,7 @@ namespace transport
std::unique_ptr<boost::asio::ip::tcp::socket> m_UDPAssociateSocket;
std::unique_ptr<boost::asio::ip::udp::endpoint> m_ProxyRelayEndpoint;
std::unique_ptr<boost::asio::deadline_timer> m_ProxyConnectRetryTimer;
public:
// for HTTP/I2PControl

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022, The PurpleI2P Project
* Copyright (c) 2022-2023, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -32,7 +32,52 @@ namespace transport
nextFragmentNum++;
}
bool SSU2IncompleteMessage::ConcatOutOfSequenceFragments ()
{
bool isLast = false;
while (outOfSequenceFragments)
{
if (outOfSequenceFragments->fragmentNum == nextFragmentNum)
{
AttachNextFragment (outOfSequenceFragments->buf, outOfSequenceFragments->len);
isLast = outOfSequenceFragments->isLast;
if (isLast)
outOfSequenceFragments = nullptr;
else
outOfSequenceFragments = outOfSequenceFragments->next;
}
else
break;
}
return isLast;
}
void SSU2IncompleteMessage::AddOutOfSequenceFragment (std::shared_ptr<SSU2IncompleteMessage::Fragment> fragment)
{
if (!fragment || !fragment->fragmentNum) return; // fragment 0 not allowed
if (fragment->fragmentNum < nextFragmentNum) return; // already processed
if (!outOfSequenceFragments)
outOfSequenceFragments = fragment;
else
{
auto frag = outOfSequenceFragments;
std::shared_ptr<Fragment> prev;
do
{
if (fragment->fragmentNum < frag->fragmentNum) break; // found
if (fragment->fragmentNum == frag->fragmentNum) return; // duplicate
prev = frag; frag = frag->next;
}
while (frag);
fragment->next = frag;
if (prev)
prev->next = fragment;
else
outOfSequenceFragments = fragment;
}
lastFragmentInsertTime = i2p::util::GetSecondsSinceEpoch ();
}
SSU2Session::SSU2Session (SSU2Server& server, std::shared_ptr<const i2p::data::RouterInfo> in_RemoteRouter,
std::shared_ptr<const i2p::data::RouterInfo::Address> addr):
TransportSession (in_RemoteRouter, SSU2_CONNECT_TIMEOUT),
@@ -94,7 +139,10 @@ namespace transport
if (!ecode)
{
// timeout expired
LogPrint (eLogWarning, "SSU2: Session with ", m_RemoteEndpoint, " was not established after ", SSU2_CONNECT_TIMEOUT, " seconds");
if (m_State == eSSU2SessionStateIntroduced) // WaitForIntroducer
LogPrint (eLogWarning, "SSU2: Session was not introduced after ", SSU2_CONNECT_TIMEOUT, " seconds");
else
LogPrint (eLogWarning, "SSU2: Session with ", m_RemoteEndpoint, " was not established after ", SSU2_CONNECT_TIMEOUT, " seconds");
Terminate ();
}
}
@@ -103,7 +151,7 @@ namespace transport
{
// we are Alice
if (!session || !relayTag) return false;
// find local adddress to introduce
// find local address to introduce
auto localAddress = session->FindLocalAddress ();
if (!localAddress) return false;
// create nonce
@@ -213,10 +261,12 @@ namespace transport
m_SessionConfirmedFragment.reset (nullptr);
m_PathChallenge.reset (nullptr);
m_SendQueue.clear ();
m_SendQueueSize = 0;
m_SentPackets.clear ();
m_IncompleteMessages.clear ();
m_RelaySessions.clear ();
m_PeerTests.clear ();
m_ReceivedI2NPMsgIDs.clear ();
m_Server.RemoveSession (m_SourceConnID);
transports.PeerDisconnected (shared_from_this ());
LogPrint (eLogDebug, "SSU2: Session terminated");
@@ -287,7 +337,7 @@ namespace transport
{
if (m_State == eSSU2SessionStateTerminated) return;
for (auto it: msgs)
m_SendQueue.push_back (it);
m_SendQueue.push_back (std::move (it));
SendQueue ();
if (m_SendQueue.size () > 0) // windows is full
@@ -301,6 +351,7 @@ namespace transport
RequestTermination (eSSU2TerminationReasonTimeout);
}
}
m_SendQueueSize = m_SendQueue.size ();
}
bool SSU2Session::SendQueue ()
@@ -460,6 +511,7 @@ namespace transport
LogPrint (eLogInfo, "SSU2: Packet was not Acked after ", it->second->numResends, " attempts. Terminate session");
m_SentPackets.clear ();
m_SendQueue.clear ();
m_SendQueueSize = 0;
RequestTermination (eSSU2TerminationReasonTimeout);
return resentPackets.size ();
}
@@ -520,6 +572,11 @@ namespace transport
case eSSU2PeerTest:
{
// TODO: remove later
if (len < 32)
{
LogPrint (eLogWarning, "SSU2: PeerTest message too short ", len);
break;
}
const uint8_t nonce[12] = {0};
uint64_t headerX[2];
i2p::crypto::ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, (uint8_t *)headerX);
@@ -562,7 +619,7 @@ namespace transport
// payload
payload[0] = eSSU2BlkDateTime;
htobe16buf (payload + 1, 4);
htobe32buf (payload + 3, ts/1000);
htobe32buf (payload + 3, (ts + 500)/1000);
size_t payloadSize = 7;
if (GetRouterStatus () == eRouterStatusFirewalled && m_Address->IsIntroducer ())
{
@@ -603,6 +660,11 @@ namespace transport
void SSU2Session::ProcessSessionRequest (Header& header, uint8_t * buf, size_t len)
{
// we are Bob
if (len < 88)
{
LogPrint (eLogWarning, "SSU2: SessionRequest message too short ", len);
return;
}
const uint8_t nonce[12] = {0};
uint8_t headerX[48];
i2p::crypto::ChaCha20 (buf + 16, 48, i2p::context.GetSSU2IntroKey (), nonce, headerX);
@@ -671,7 +733,7 @@ namespace transport
size_t maxPayloadSize = m_MaxPayloadSize - 48;
payload[0] = eSSU2BlkDateTime;
htobe16buf (payload + 1, 4);
htobe32buf (payload + 3, ts/1000);
htobe32buf (payload + 3, (ts + 500)/1000);
size_t payloadSize = 7;
payloadSize += CreateAddressBlock (payload + payloadSize, maxPayloadSize - payloadSize, m_RemoteEndpoint);
if (m_RelayTag)
@@ -723,6 +785,11 @@ namespace transport
if (header.h.type != eSSU2SessionCreated)
// this situation is valid, because it might be Retry with different encryption
return false;
if (len < 80)
{
LogPrint (eLogWarning, "SSU2: SessionCreated message too short ", len);
return false;
}
const uint8_t nonce[12] = {0};
uint8_t headerX[48];
i2p::crypto::ChaCha20 (buf + 16, 48, kh2, nonce, headerX);
@@ -860,7 +927,13 @@ namespace transport
// fragmented
if (numFragments > 2)
{
LogPrint (eLogError, "SSU2: Too many fragments ", numFragments, " in SessionConfirmed");
LogPrint (eLogError, "SSU2: Too many fragments ", (int)numFragments, " in SessionConfirmed from ", m_RemoteEndpoint);
return false;
}
if (len < 32)
{
LogPrint (eLogWarning, "SSU2: SessionConfirmed fragment too short ", len);
if (m_SessionConfirmedFragment) m_SessionConfirmedFragment.reset (nullptr);
return false;
}
if (!(header.h.flags[0] & 0xF0))
@@ -910,6 +983,12 @@ namespace transport
len = m_SessionConfirmedFragment->payloadSize + 16;
}
}
if (len < 80)
{
LogPrint (eLogWarning, "SSU2: SessionConfirmed message too short ", len);
if (m_SessionConfirmedFragment) m_SessionConfirmedFragment.reset (nullptr);
return false;
}
// KDF for Session Confirmed part 1
m_NoiseState->MixHash (header.buf, 16); // h = SHA256(h || header)
// decrypt part1
@@ -1016,7 +1095,7 @@ namespace transport
// payload
payload[0] = eSSU2BlkDateTime;
htobe16buf (payload + 1, 4);
htobe32buf (payload + 3, i2p::util::GetSecondsSinceEpoch ());
htobe32buf (payload + 3, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000);
size_t payloadSize = 7;
payloadSize += CreatePaddingBlock (payload + payloadSize, 25 - payloadSize, 1);
// encrypt
@@ -1087,7 +1166,7 @@ namespace transport
// payload
payload[0] = eSSU2BlkDateTime;
htobe16buf (payload + 1, 4);
htobe32buf (payload + 3, i2p::util::GetSecondsSinceEpoch ());
htobe32buf (payload + 3, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000);
size_t payloadSize = 7;
payloadSize += CreateAddressBlock (payload + payloadSize, 56 - payloadSize, m_RemoteEndpoint);
if (m_TerminationReason != eSSU2TerminationReasonNormalClose)
@@ -1118,6 +1197,11 @@ namespace transport
LogPrint (eLogWarning, "SSU2: Unexpected message type ", (int)header.h.type, " instead ", (int)eSSU2Retry);
return false;
}
if (len < 48)
{
LogPrint (eLogWarning, "SSU2: Retry message too short ", len);
return false;
}
uint8_t nonce[12] = {0};
uint64_t headerX[2]; // sourceConnID, token
i2p::crypto::ChaCha20 (buf + 16, 16, m_Address->i, nonce, (uint8_t *)headerX);
@@ -1170,7 +1254,7 @@ namespace transport
// payload
payload[0] = eSSU2BlkDateTime;
htobe16buf (payload + 1, 4);
htobe32buf (payload + 3, i2p::util::GetSecondsSinceEpoch ());
htobe32buf (payload + 3, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000);
size_t payloadSize = 7;
payloadSize += CreateAddressBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize, ep);
payloadSize += CreateRelayResponseBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize,
@@ -1202,6 +1286,11 @@ namespace transport
LogPrint (eLogWarning, "SSU2: Unexpected message type ", (int)header.h.type, " instead ", (int)eSSU2HolePunch);
return false;
}
if (len < 48)
{
LogPrint (eLogWarning, "SSU2: HolePunch message too short ", len);
return false;
}
uint8_t nonce[12] = {0};
uint64_t headerX[2]; // sourceConnID, token
i2p::crypto::ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, (uint8_t *)headerX);
@@ -1241,7 +1330,7 @@ namespace transport
// payload
payload[0] = eSSU2BlkDateTime;
htobe16buf (payload + 1, 4);
htobe32buf (payload + 3, i2p::util::GetSecondsSinceEpoch ());
htobe32buf (payload + 3, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000);
size_t payloadSize = 7;
if (msg == 6 || msg == 7)
payloadSize += CreateAddressBlock (payload + payloadSize, m_MaxPayloadSize - payloadSize, m_RemoteEndpoint);
@@ -1273,6 +1362,11 @@ namespace transport
LogPrint (eLogWarning, "SSU2: Unexpected message type ", (int)header.h.type, " instead ", (int)eSSU2PeerTest);
return false;
}
if (len < 48)
{
LogPrint (eLogWarning, "SSU2: PeerTest message too short ", len);
return false;
}
uint8_t nonce[12] = {0};
uint64_t headerX[2]; // sourceConnID, token
i2p::crypto::ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, (uint8_t *)headerX);
@@ -1340,6 +1434,11 @@ namespace transport
m_RemoteEndpoint = from;
SendPathChallenge ();
}
if (len < 32)
{
LogPrint (eLogWarning, "SSU2: Data message too short ", len);
return;
}
uint8_t payload[SSU2_MAX_PACKET_SIZE];
size_t payloadSize = len - 32;
uint32_t packetNum = be32toh (header.h.packetNum);
@@ -1397,7 +1496,7 @@ namespace transport
nextMsg->len = nextMsg->offset + size + 7; // 7 more bytes for full I2NP header
memcpy (nextMsg->GetNTCP2Header (), buf + offset, size);
nextMsg->FromNTCP2 (); // SSU2 has the same format as NTCP2
m_Handler.PutNextMessage (std::move (nextMsg));
HandleI2NPMsg (std::move (nextMsg));
m_IsDataReceived = true;
break;
}
@@ -1412,12 +1511,19 @@ namespace transport
m_IsDataReceived = true;
break;
case eSSU2BlkTermination:
LogPrint (eLogDebug, "SSU2: Termination reason=", (int)buf[11]);
if (IsEstablished () && buf[11] != eSSU2TerminationReasonTerminationReceived)
{
uint8_t rsn = buf[11]; // reason
LogPrint (eLogDebug, "SSU2: Termination reason=", (int)rsn);
if (IsEstablished () && rsn != eSSU2TerminationReasonTerminationReceived)
RequestTermination (eSSU2TerminationReasonTerminationReceived);
else
else if (m_State != eSSU2SessionStateTerminated)
{
if (m_State == eSSU2SessionStateClosing && rsn == eSSU2TerminationReasonTerminationReceived)
m_State = eSSU2SessionStateClosingConfirmed;
Done ();
break;
}
break;
}
case eSSU2BlkRelayRequest:
LogPrint (eLogDebug, "SSU2: RelayRequest");
HandleRelayRequest (buf + offset, size);
@@ -1573,7 +1679,7 @@ namespace transport
if (ts > it1->second->sendTime)
{
auto rtt = ts - it1->second->sendTime;
m_RTT = (m_RTT*m_SendPacketNum + rtt)/(m_SendPacketNum + 1);
m_RTT = std::round ((m_RTT*m_SendPacketNum + rtt)/(m_SendPacketNum + 1.0));
m_RTO = m_RTT*SSU2_kAPPA;
if (m_RTO < SSU2_MIN_RTO) m_RTO = SSU2_MIN_RTO;
if (m_RTO > SSU2_MAX_RTO) m_RTO = SSU2_MAX_RTO;
@@ -1604,15 +1710,20 @@ namespace transport
bool isV4 = ep.address ().is_v4 ();
if (ep.port () != m_Server.GetPort (isV4))
{
LogPrint (eLogInfo, "SSU2: Our port ", ep.port (), " received from ", m_RemoteEndpoint, " is different from ", m_Server.GetPort (isV4));
if (isV4)
{
if (i2p::context.GetStatus () == eRouterStatusTesting)
i2p::context.SetError (eRouterErrorSymmetricNAT);
else if (m_State == eSSU2SessionStatePeerTest)
i2p::context.SetError (eRouterErrorFullConeNAT);
}
else
{
if (i2p::context.GetStatusV6 () == eRouterStatusTesting)
i2p::context.SetErrorV6 (eRouterErrorSymmetricNAT);
else if (m_State == eSSU2SessionStatePeerTest)
i2p::context.SetErrorV6 (eRouterErrorFullConeNAT);
}
}
else
@@ -1620,11 +1731,23 @@ namespace transport
if (isV4)
{
if (i2p::context.GetError () == eRouterErrorSymmetricNAT)
{
if (m_State == eSSU2SessionStatePeerTest)
i2p::context.SetStatus (eRouterStatusOK);
i2p::context.SetError (eRouterErrorNone);
}
else if (i2p::context.GetError () == eRouterErrorFullConeNAT)
i2p::context.SetError (eRouterErrorNone);
}
else
{
if (i2p::context.GetErrorV6 () == eRouterErrorSymmetricNAT)
{
if (m_State == eSSU2SessionStatePeerTest)
i2p::context.SetStatusV6 (eRouterStatusOK);
i2p::context.SetErrorV6 (eRouterErrorNone);
}
else if (i2p::context.GetErrorV6 () == eRouterErrorFullConeNAT)
i2p::context.SetErrorV6 (eRouterErrorNone);
}
}
@@ -1649,17 +1772,17 @@ namespace transport
}
else
{
m = std::make_shared<SSU2IncompleteMessage>();
m = m_Server.GetIncompleteMessagesPool ().AcquireShared ();
m_IncompleteMessages.emplace (msgID, m);
}
m->msg = msg;
m->nextFragmentNum = 1;
m->lastFragmentInsertTime = i2p::util::GetSecondsSinceEpoch ();
if (found && ConcatOutOfSequenceFragments (m))
if (found && m->ConcatOutOfSequenceFragments ())
{
// we have all follow-on fragments already
m->msg->FromNTCP2 ();
m_Handler.PutNextMessage (std::move (m->msg));
HandleI2NPMsg (std::move (m->msg));
m_IncompleteMessages.erase (it);
}
}
@@ -1668,11 +1791,17 @@ namespace transport
{
if (len < 5) return;
uint8_t fragmentNum = buf[0] >> 1;
if (!fragmentNum || fragmentNum >= SSU2_MAX_NUM_FRAGMENTS)
{
LogPrint (eLogWarning, "SSU2: Invalid follow-on fragment num ", fragmentNum);
return;
}
bool isLast = buf[0] & 0x01;
uint32_t msgID; memcpy (&msgID, buf + 1, 4);
auto it = m_IncompleteMessages.find (msgID);
if (it != m_IncompleteMessages.end ())
{
if (fragmentNum < it->second->nextFragmentNum) return; // duplicate
if (it->second->nextFragmentNum == fragmentNum && fragmentNum < SSU2_MAX_NUM_FRAGMENTS &&
it->second->msg)
{
@@ -1681,14 +1810,14 @@ namespace transport
if (isLast)
{
it->second->msg->FromNTCP2 ();
m_Handler.PutNextMessage (std::move (it->second->msg));
HandleI2NPMsg (std::move (it->second->msg));
m_IncompleteMessages.erase (it);
}
else
{
if (ConcatOutOfSequenceFragments (it->second))
if (it->second->ConcatOutOfSequenceFragments ())
{
m_Handler.PutNextMessage (std::move (it->second->msg));
HandleI2NPMsg (std::move (it->second->msg));
m_IncompleteMessages.erase (it);
}
else
@@ -1700,38 +1829,17 @@ namespace transport
else
{
// follow-on fragment before first fragment
auto msg = std::make_shared<SSU2IncompleteMessage> ();
auto msg = m_Server.GetIncompleteMessagesPool ().AcquireShared ();
msg->nextFragmentNum = 0;
it = m_IncompleteMessages.emplace (msgID, msg).first;
}
// insert out of sequence fragment
if (fragmentNum >= SSU2_MAX_NUM_FRAGMENTS)
{
LogPrint (eLogWarning, "SSU2: Fragment number ", fragmentNum, " exceeds ", SSU2_MAX_NUM_FRAGMENTS);
return;
}
auto fragment = std::make_shared<SSU2IncompleteMessage::Fragment> ();
auto fragment = m_Server.GetFragmentsPool ().AcquireShared ();
memcpy (fragment->buf, buf + 5, len -5);
fragment->len = len - 5;
fragment->fragmentNum = fragmentNum;
fragment->isLast = isLast;
it->second->outOfSequenceFragments.emplace (fragmentNum, fragment);
it->second->lastFragmentInsertTime = i2p::util::GetSecondsSinceEpoch ();
}
bool SSU2Session::ConcatOutOfSequenceFragments (std::shared_ptr<SSU2IncompleteMessage> m)
{
if (!m) return false;
bool isLast = false;
for (auto it = m->outOfSequenceFragments.begin (); it != m->outOfSequenceFragments.end ();)
if (it->first == m->nextFragmentNum)
{
m->AttachNextFragment (it->second->buf, it->second->len);
isLast = it->second->isLast;
it = m->outOfSequenceFragments.erase (it);
}
else
break;
return isLast;
it->second->AddOutOfSequenceFragment (fragment);
}
void SSU2Session::HandleRelayRequest (const uint8_t * buf, size_t len)
@@ -1769,7 +1877,7 @@ namespace transport
session->SendData (payload, payloadSize);
}
void SSU2Session::HandleRelayIntro (const uint8_t * buf, size_t len)
void SSU2Session::HandleRelayIntro (const uint8_t * buf, size_t len, int attempts)
{
// we are Charlie
SSU2RelayResponseCode code = eSSU2RelayResponseCodeAccept;
@@ -1824,9 +1932,22 @@ namespace transport
code = eSSU2RelayResponseCodeCharlieSignatureFailure;
}
}
else if (!attempts)
{
// RouterInfo might come in the next packet, try again
auto vec = std::make_shared<std::vector<uint8_t> >(len);
memcpy (vec->data (), buf, len);
auto s = shared_from_this ();
m_Server.GetService ().post ([s, vec, attempts]()
{
LogPrint (eLogDebug, "SSU2: RelayIntro attempt ", attempts + 1);
s->HandleRelayIntro (vec->data (), vec->size (), attempts + 1);
});
return;
}
else
{
LogPrint (eLogError, "SSU2: RelayIntro unknown router to introduce");
LogPrint (eLogWarning, "SSU2: RelayIntro unknown router to introduce");
code = eSSU2RelayResponseCodeCharlieAliceIsUnknown;
}
// send relay response to Bob
@@ -2176,6 +2297,22 @@ namespace transport
}
}
void SSU2Session::HandleI2NPMsg (std::shared_ptr<I2NPMessage>&& msg)
{
if (!msg) return;
int32_t msgID = msg->GetMsgID ();
if (!msg->IsExpired ())
{
// m_LastActivityTimestamp is updated in ProcessData before
if (m_ReceivedI2NPMsgIDs.emplace (msgID, (uint32_t)m_LastActivityTimestamp).second)
m_Handler.PutNextMessage (std::move (msg));
else
LogPrint (eLogDebug, "SSU2: Message ", msgID, " already received");
}
else
LogPrint (eLogDebug, "SSU2: Message ", msgID, " expired");
}
bool SSU2Session::ExtractEndpoint (const uint8_t * buf, size_t size, boost::asio::ip::udp::endpoint& ep)
{
if (size < 2) return false;
@@ -2266,9 +2403,9 @@ namespace transport
if (m_Address)
{
if (m_Address->IsV4 ())
i2p::context.SetStatusSSU2 (status);
i2p::context.SetStatus (status);
else if (m_Address->IsV6 ())
i2p::context.SetStatusV6SSU2 (status);
i2p::context.SetStatusV6 (status);
}
}
@@ -2338,7 +2475,7 @@ namespace transport
d.quot = maxNumRanges;
d.rem = 0;
}
// Acks only ragnes for acnt
// Acks only ranges for acnt
for (int i = 0; i < d.quot; i++)
{
buf[8 + numRanges*2] = 0; buf[8 + numRanges*2 + 1] = 255; // NACKs 0, Acks 255
@@ -2387,7 +2524,7 @@ namespace transport
}
if (numRanges < maxNumRanges && it == m_OutOfSequencePackets.rend ())
{
// add range between out-of-seqence and received
// add range between out-of-sequence and received
int nacks = *m_OutOfSequencePackets.begin () - m_ReceivePacketNum - 1;
if (nacks > 0)
{
@@ -2410,14 +2547,9 @@ namespace transport
size_t paddingSize = rand () & 0x0F; // 0 - 15
if (paddingSize + 3 > len) paddingSize = len - 3;
else if (paddingSize + 3 < minSize) paddingSize = minSize - 3;
if (paddingSize)
{
buf[0] = eSSU2BlkPadding;
htobe16buf (buf + 1, paddingSize);
memset (buf + 3, 0, paddingSize);
}
else
return 0;
buf[0] = eSSU2BlkPadding;
htobe16buf (buf + 1, paddingSize);
memset (buf + 3, 0, paddingSize);
return paddingSize + 3;
}
@@ -2703,6 +2835,20 @@ namespace transport
else
++it;
}
if (m_ReceivedI2NPMsgIDs.size () > SSU2_MAX_NUM_RECEIVED_I2NP_MSGIDS || ts > m_LastActivityTimestamp + SSU2_DECAY_INTERVAL)
// decay
m_ReceivedI2NPMsgIDs.clear ();
else
{
// delete old received msgIDs
for (auto it = m_ReceivedI2NPMsgIDs.begin (); it != m_ReceivedI2NPMsgIDs.end ();)
{
if (ts > it->second + SSU2_RECEIVED_I2NP_MSGIDS_CLEANUP_TIMEOUT)
it = m_ReceivedI2NPMsgIDs.erase (it);
else
++it;
}
}
if (!m_OutOfSequencePackets.empty ())
{
if (m_OutOfSequencePackets.size () > 2*SSU2_MAX_NUM_ACK_RANGES ||
@@ -2754,6 +2900,8 @@ namespace transport
void SSU2Session::FlushData ()
{
bool sent = SendQueue (); // if we have something to send
if (sent)
m_SendQueueSize = m_SendQueue.size ();
if (m_IsDataReceived)
{
if (!sent) SendQuickAck ();

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022, The PurpleI2P Project
* Copyright (c) 2022-2023, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -35,10 +35,13 @@ namespace transport
const int SSU2_PEER_TEST_EXPIRATION_TIMEOUT = 60; // 60 seconds
const size_t SSU2_MAX_PACKET_SIZE = 1500;
const size_t SSU2_MIN_PACKET_SIZE = 1280;
const int SSU2_HANDSHAKE_RESEND_INTERVAL = 1000; // in millseconds
const int SSU2_HANDSHAKE_RESEND_INTERVAL = 1000; // in milliseconds
const int SSU2_RESEND_INTERVAL = 300; // in milliseconds
const int SSU2_MAX_NUM_RESENDS = 5;
const int SSU2_INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT = 30; // in seconds
const int SSU2_MAX_NUM_RECEIVED_I2NP_MSGIDS = 5000; // how many msgID we store for duplicates check
const int SSU2_RECEIVED_I2NP_MSGIDS_CLEANUP_TIMEOUT = 10; // in seconds
const int SSU2_DECAY_INTERVAL = 20; // in seconds
const size_t SSU2_MIN_WINDOW_SIZE = 16; // in packets
const size_t SSU2_MAX_WINDOW_SIZE = 256; // in packets
const size_t SSU2_MIN_RTO = 100; // in milliseconds
@@ -47,7 +50,7 @@ namespace transport
const size_t SSU2_MAX_OUTGOING_QUEUE_SIZE = 500; // in messages
const int SSU2_MAX_NUM_ACK_RANGES = 32; // to send
const uint8_t SSU2_MAX_NUM_FRAGMENTS = 64;
// flags
const uint8_t SSU2_FLAG_IMMEDIATE_ACK_REQUESTED = 0x01;
@@ -100,6 +103,7 @@ namespace transport
eSSU2SessionStateSessionConfirmedSent,
eSSU2SessionStateEstablished,
eSSU2SessionStateClosing,
eSSU2SessionStateClosingConfirmed,
eSSU2SessionStateTerminated,
eSSU2SessionStateFailed,
eSSU2SessionStateIntroduced,
@@ -167,15 +171,19 @@ namespace transport
{
uint8_t buf[SSU2_MAX_PACKET_SIZE];
size_t len;
int fragmentNum;
bool isLast;
std::shared_ptr<Fragment> next;
};
std::shared_ptr<I2NPMessage> msg;
int nextFragmentNum;
uint32_t lastFragmentInsertTime; // in seconds
std::map<int, std::shared_ptr<Fragment> > outOfSequenceFragments;
std::shared_ptr<Fragment> outOfSequenceFragments; // #1 and more
void AttachNextFragment (const uint8_t * fragment, size_t fragmentSize);
bool ConcatOutOfSequenceFragments (); // true if message complete
void AddOutOfSequenceFragment (std::shared_ptr<Fragment> fragment);
};
struct SSU2SentPacket
@@ -244,7 +252,7 @@ namespace transport
void SendI2NPMessages (const std::vector<std::shared_ptr<I2NPMessage> >& msgs) override;
uint32_t GetRelayTag () const override { return m_RelayTag; };
size_t Resend (uint64_t ts); // return number or resent packets
bool IsEstablished () const { return m_State == eSSU2SessionStateEstablished; };
bool IsEstablished () const override { return m_State == eSSU2SessionStateEstablished; };
uint64_t GetConnID () const { return m_SourceConnID; };
SSU2SessionState GetState () const { return m_State; };
void SetState (SSU2SessionState state) { m_State = state; };
@@ -302,12 +310,12 @@ namespace transport
bool UpdateReceivePacketNum (uint32_t packetNum); // for Ack, returns false if duplicate
void HandleFirstFragment (const uint8_t * buf, size_t len);
void HandleFollowOnFragment (const uint8_t * buf, size_t len);
bool ConcatOutOfSequenceFragments (std::shared_ptr<SSU2IncompleteMessage> m); // true if message complete
void HandleRelayRequest (const uint8_t * buf, size_t len);
void HandleRelayIntro (const uint8_t * buf, size_t len);
void HandleRelayIntro (const uint8_t * buf, size_t len, int attempts = 0);
void HandleRelayResponse (const uint8_t * buf, size_t len);
void HandlePeerTest (const uint8_t * buf, size_t len);
void HandleI2NPMsg (std::shared_ptr<I2NPMessage>&& msg);
size_t CreateAddressBlock (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& ep);
size_t CreateRouterInfoBlock (uint8_t * buf, size_t len, std::shared_ptr<const i2p::data::RouterInfo> r);
size_t CreateAckBlock (uint8_t * buf, size_t len);
@@ -337,7 +345,7 @@ namespace transport
uint32_t m_SendPacketNum, m_ReceivePacketNum;
std::set<uint32_t> m_OutOfSequencePackets; // packet nums > receive packet num
std::map<uint32_t, std::shared_ptr<SSU2SentPacket> > m_SentPackets; // packetNum -> packet
std::map<uint32_t, std::shared_ptr<SSU2IncompleteMessage> > m_IncompleteMessages; // I2NP
std::unordered_map<uint32_t, std::shared_ptr<SSU2IncompleteMessage> > m_IncompleteMessages; // msgID -> I2NP
std::map<uint32_t, std::pair <std::shared_ptr<SSU2Session>, uint64_t > > m_RelaySessions; // nonce->(Alice, timestamp) for Bob or nonce->(Charlie, timestamp) for Alice
std::map<uint32_t, std::pair <std::shared_ptr<SSU2Session>, uint64_t > > m_PeerTests; // same as for relay sessions
std::list<std::shared_ptr<I2NPMessage> > m_SendQueue;
@@ -350,6 +358,7 @@ namespace transport
SSU2TerminationReason m_TerminationReason;
size_t m_MaxPayloadSize;
std::unique_ptr<i2p::data::IdentHash> m_PathChallenge;
std::unordered_map<uint32_t, uint32_t> m_ReceivedI2NPMsgIDs; // msgID -> timestamp in seconds
};
inline uint64_t CreateHeaderMask (const uint8_t * kh, const uint8_t * nonce)

View File

@@ -1,516 +0,0 @@
/*
* Copyright (c) 2013-2022, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
* See full license text in LICENSE file at top of project tree
*/
#include <stdlib.h>
#include "Log.h"
#include "Timestamp.h"
#include "NetDb.hpp"
#include "SSU.h"
#include "SSUData.h"
namespace i2p
{
namespace transport
{
void IncompleteMessage::AttachNextFragment (const uint8_t * fragment, size_t fragmentSize)
{
if (msg->len + fragmentSize > msg->maxLen)
{
LogPrint (eLogWarning, "SSU: I2NP message size ", msg->maxLen, " is not enough");
auto newMsg = NewI2NPMessage ();
*newMsg = *msg;
msg = newMsg;
}
if (msg->Concat (fragment, fragmentSize) < fragmentSize)
LogPrint (eLogError, "SSU: I2NP buffer overflow ", msg->maxLen);
nextFragmentNum++;
}
SSUData::SSUData (SSUSession& session):
m_Session (session), m_ResendTimer (session.GetService ()),
m_MaxPacketSize (session.IsV6 () ? SSU_V6_MAX_PACKET_SIZE : SSU_V4_MAX_PACKET_SIZE),
m_PacketSize (m_MaxPacketSize), m_LastMessageReceivedTime (0)
{
}
SSUData::~SSUData ()
{
}
void SSUData::Start ()
{
}
void SSUData::Stop ()
{
m_ResendTimer.cancel ();
m_IncompleteMessages.clear ();
m_SentMessages.clear ();
m_ReceivedMessages.clear ();
}
void SSUData::AdjustPacketSize (std::shared_ptr<const i2p::data::RouterInfo> remoteRouter)
{
if (!remoteRouter) return;
auto ssuAddress = remoteRouter->GetSSUAddress ();
if (ssuAddress && ssuAddress->ssu->mtu)
{
if (m_Session.IsV6 ())
m_PacketSize = ssuAddress->ssu->mtu - IPV6_HEADER_SIZE - UDP_HEADER_SIZE;
else
m_PacketSize = ssuAddress->ssu->mtu - IPV4_HEADER_SIZE - UDP_HEADER_SIZE;
if (m_PacketSize > 0)
{
// make sure packet size multiple of 16
m_PacketSize >>= 4;
m_PacketSize <<= 4;
if (m_PacketSize > m_MaxPacketSize) m_PacketSize = m_MaxPacketSize;
LogPrint (eLogDebug, "SSU: MTU=", ssuAddress->ssu->mtu, " packet size=", m_PacketSize);
}
else
{
LogPrint (eLogWarning, "SSU: Unexpected MTU ", ssuAddress->ssu->mtu);
m_PacketSize = m_MaxPacketSize;
}
}
}
void SSUData::UpdatePacketSize (const i2p::data::IdentHash& remoteIdent)
{
auto routerInfo = i2p::data::netdb.FindRouter (remoteIdent);
if (routerInfo)
AdjustPacketSize (routerInfo);
}
void SSUData::ProcessSentMessageAck (uint32_t msgID)
{
auto it = m_SentMessages.find (msgID);
if (it != m_SentMessages.end ())
{
m_SentMessages.erase (it);
if (m_SentMessages.empty ())
m_ResendTimer.cancel ();
}
}
void SSUData::ProcessAcks (uint8_t *& buf, uint8_t flag)
{
if (flag & DATA_FLAG_EXPLICIT_ACKS_INCLUDED)
{
// explicit ACKs
uint8_t numAcks =*buf;
buf++;
for (int i = 0; i < numAcks; i++)
ProcessSentMessageAck (bufbe32toh (buf+i*4));
buf += numAcks*4;
}
if (flag & DATA_FLAG_ACK_BITFIELDS_INCLUDED)
{
// explicit ACK bitfields
uint8_t numBitfields =*buf;
buf++;
for (int i = 0; i < numBitfields; i++)
{
uint32_t msgID = bufbe32toh (buf);
buf += 4; // msgID
auto it = m_SentMessages.find (msgID);
// process individual Ack bitfields
bool isNonLast = false;
int fragment = 0;
do
{
uint8_t bitfield = *buf;
isNonLast = bitfield & 0x80;
bitfield &= 0x7F; // clear MSB
if (bitfield && it != m_SentMessages.end ())
{
int numSentFragments = it->second->fragments.size ();
// process bits
uint8_t mask = 0x01;
for (int j = 0; j < 7; j++)
{
if (bitfield & mask)
{
if (fragment < numSentFragments)
it->second->fragments[fragment] = nullptr;
}
fragment++;
mask <<= 1;
}
}
buf++;
}
while (isNonLast);
}
}
}
void SSUData::ProcessFragments (uint8_t * buf)
{
uint8_t numFragments = *buf; // number of fragments
buf++;
for (int i = 0; i < numFragments; i++)
{
uint32_t msgID = bufbe32toh (buf); // message ID
buf += 4;
uint8_t frag[4] = {0};
memcpy (frag + 1, buf, 3);
buf += 3;
uint32_t fragmentInfo = bufbe32toh (frag); // fragment info
uint16_t fragmentSize = fragmentInfo & 0x3FFF; // bits 0 - 13
bool isLast = fragmentInfo & 0x010000; // bit 16
uint8_t fragmentNum = fragmentInfo >> 17; // bits 23 - 17
if (fragmentSize >= SSU_V4_MAX_PACKET_SIZE)
{
LogPrint (eLogError, "SSU: Fragment size ", fragmentSize, " exceeds max SSU packet size");
return;
}
// find message with msgID
auto it = m_IncompleteMessages.find (msgID);
if (it == m_IncompleteMessages.end ())
{
// create new message
auto msg = NewI2NPShortMessage ();
msg->len -= I2NP_SHORT_HEADER_SIZE;
it = m_IncompleteMessages.insert (std::make_pair (msgID,
m_Session.GetServer ().GetIncompleteMessagesPool ().AcquireShared (std::move (msg)))).first;
}
auto& incompleteMessage = it->second;
// mark fragment as received
if (fragmentNum < 64)
incompleteMessage->receivedFragmentsBits |= (uint64_t(0x01) << fragmentNum);
else
LogPrint (eLogWarning, "SSU: Fragment number ", fragmentNum, " exceeds 64");
// handle current fragment
if (fragmentNum == incompleteMessage->nextFragmentNum)
{
// expected fragment
incompleteMessage->AttachNextFragment (buf, fragmentSize);
if (!isLast && !incompleteMessage->savedFragments.empty ())
{
// try saved fragments
for (auto it1 = incompleteMessage->savedFragments.begin (); it1 != incompleteMessage->savedFragments.end ();)
{
auto& savedFragment = *it1;
if (savedFragment->fragmentNum == incompleteMessage->nextFragmentNum)
{
incompleteMessage->AttachNextFragment (savedFragment->buf, savedFragment->len);
isLast = savedFragment->isLast;
incompleteMessage->savedFragments.erase (it1++);
}
else
break;
}
if (isLast)
LogPrint (eLogDebug, "SSU: Message ", msgID, " complete");
}
}
else
{
if (fragmentNum < incompleteMessage->nextFragmentNum)
// duplicate fragment
LogPrint (eLogWarning, "SSU: Duplicate fragment ", (int)fragmentNum, " of message ", msgID, ", ignored");
else
{
// missing fragment
LogPrint (eLogWarning, "SSU: Missing fragments from ", (int)incompleteMessage->nextFragmentNum, " to ", fragmentNum - 1, " of message ", msgID);
auto savedFragment = m_Session.GetServer ().GetFragmentsPool ().AcquireShared (fragmentNum, buf, fragmentSize, isLast);
if (incompleteMessage->savedFragments.insert (savedFragment).second)
incompleteMessage->lastFragmentInsertTime = i2p::util::GetSecondsSinceEpoch ();
else
LogPrint (eLogWarning, "SSU: Fragment ", (int)fragmentNum, " of message ", msgID, " already saved");
}
isLast = false;
}
if (isLast)
{
// delete incomplete message
auto msg = incompleteMessage->msg;
incompleteMessage->msg = nullptr;
m_IncompleteMessages.erase (msgID);
// process message
SendMsgAck (msgID);
msg->FromSSU (msgID);
if (m_Session.GetState () == eSessionStateEstablished)
{
if (!m_ReceivedMessages.count (msgID))
{
m_LastMessageReceivedTime = i2p::util::GetSecondsSinceEpoch ();
m_ReceivedMessages.emplace (msgID, m_LastMessageReceivedTime);
if (!msg->IsExpired ())
{
m_Handler.PutNextMessage (std::move (msg));
}
else
LogPrint (eLogDebug, "SSU: message expired");
}
else
LogPrint (eLogWarning, "SSU: Message ", msgID, " already received");
}
else
{
// we expect DeliveryStatus
if (msg->GetTypeID () == eI2NPDeliveryStatus)
{
LogPrint (eLogDebug, "SSU: session established");
m_Session.Established ();
}
else
LogPrint (eLogError, "SSU: unexpected message ", (int)msg->GetTypeID ());
}
}
else
SendFragmentAck (msgID, incompleteMessage->receivedFragmentsBits);
buf += fragmentSize;
}
}
void SSUData::FlushReceivedMessage ()
{
m_Handler.Flush ();
}
void SSUData::ProcessMessage (uint8_t * buf, size_t len)
{
//uint8_t * start = buf;
uint8_t flag = *buf;
buf++;
LogPrint (eLogDebug, "SSU: Process data, flags=", (int)flag, ", len=", len);
// process acks if presented
if (flag & (DATA_FLAG_ACK_BITFIELDS_INCLUDED | DATA_FLAG_EXPLICIT_ACKS_INCLUDED))
ProcessAcks (buf, flag);
// extended data if presented
if (flag & DATA_FLAG_EXTENDED_DATA_INCLUDED)
{
uint8_t extendedDataSize = *buf;
buf++; // size
LogPrint (eLogDebug, "SSU: extended data of ", extendedDataSize, " bytes present");
buf += extendedDataSize;
}
// process data
ProcessFragments (buf);
}
void SSUData::Send (std::shared_ptr<i2p::I2NPMessage> msg)
{
uint32_t msgID = msg->ToSSU ();
if (m_SentMessages.find (msgID) != m_SentMessages.end())
{
LogPrint (eLogWarning, "SSU: message ", msgID, " already sent");
return;
}
if (m_SentMessages.empty ()) // schedule resend at first message only
ScheduleResend ();
auto ret = m_SentMessages.emplace (msgID, m_Session.GetServer ().GetSentMessagesPool ().AcquireShared ());
auto& sentMessage = ret.first->second;
if (ret.second)
{
sentMessage->nextResendTime = i2p::util::GetSecondsSinceEpoch () + RESEND_INTERVAL;
sentMessage->numResends = 0;
}
auto& fragments = sentMessage->fragments;
size_t payloadSize = m_PacketSize - sizeof (SSUHeader) - 9; // 9 = flag + #frg(1) + messageID(4) + frag info (3)
size_t len = msg->GetLength ();
uint8_t * msgBuf = msg->GetSSUHeader ();
uint32_t fragmentNum = 0;
while (len > 0 && fragmentNum <= 127)
{
auto fragment = m_Session.GetServer ().GetFragmentsPool ().AcquireShared ();
fragment->fragmentNum = fragmentNum;
uint8_t * payload = fragment->buf + sizeof (SSUHeader);
*payload = DATA_FLAG_WANT_REPLY; // for compatibility
payload++;
*payload = 1; // always 1 message fragment per message
payload++;
htobe32buf (payload, msgID);
payload += 4;
bool isLast = (len <= payloadSize) || fragmentNum == 127; // 127 fragments max
size_t size = isLast ? len : payloadSize;
uint32_t fragmentInfo = (fragmentNum << 17);
if (isLast)
fragmentInfo |= 0x010000;
fragmentInfo |= size;
fragmentInfo = htobe32 (fragmentInfo);
memcpy (payload, (uint8_t *)(&fragmentInfo) + 1, 3);
payload += 3;
memcpy (payload, msgBuf, size);
size += payload - fragment->buf;
uint8_t rem = size & 0x0F;
if (rem) // make sure 16 bytes boundary
{
auto padding = 16 - rem;
memset (fragment->buf + size, 0, padding);
size += padding;
}
fragment->len = size;
fragments.push_back (fragment);
// encrypt message with session key
uint8_t buf[SSU_V4_MAX_PACKET_SIZE + 18];
m_Session.FillHeaderAndEncrypt (PAYLOAD_TYPE_DATA, fragment->buf, size, buf);
try
{
m_Session.Send (buf, size);
}
catch (boost::system::system_error& ec)
{
LogPrint (eLogWarning, "SSU: Can't send data fragment ", ec.what ());
}
if (!isLast)
{
len -= payloadSize;
msgBuf += payloadSize;
}
else
len = 0;
fragmentNum++;
}
}
void SSUData::SendMsgAck (uint32_t msgID)
{
uint8_t buf[48 + 18] = {0}; // actual length is 44 = 37 + 7 but pad it to multiple of 16
uint8_t * payload = buf + sizeof (SSUHeader);
*payload = DATA_FLAG_EXPLICIT_ACKS_INCLUDED; // flag
payload++;
*payload = 1; // number of ACKs
payload++;
htobe32buf (payload, msgID); // msgID
payload += 4;
*payload = 0; // number of fragments
// encrypt message with session key
m_Session.FillHeaderAndEncrypt (PAYLOAD_TYPE_DATA, buf, 48);
m_Session.Send (buf, 48);
}
void SSUData::SendFragmentAck (uint32_t msgID, uint64_t bits)
{
if (!bits) return;
uint8_t buf[64 + 18] = {0};
uint8_t * payload = buf + sizeof (SSUHeader);
*payload = DATA_FLAG_ACK_BITFIELDS_INCLUDED; // flag
payload++;
*payload = 1; // number of ACK bitfields
payload++;
// one ack
*(uint32_t *)(payload) = htobe32 (msgID); // msgID
payload += 4;
size_t len = 0;
while (bits)
{
*payload = (bits & 0x7F); // next 7 bits
bits >>= 7;
if (bits) *payload &= 0x80; // 0x80 means non-last
payload++; len++;
}
*payload = 0; // number of fragments
len = (len <= 4) ? 48 : 64; // 48 = 37 + 7 + 4
// encrypt message with session key
m_Session.FillHeaderAndEncrypt (PAYLOAD_TYPE_DATA, buf, len);
m_Session.Send (buf, len);
}
void SSUData::ScheduleResend()
{
m_ResendTimer.cancel ();
m_ResendTimer.expires_from_now (boost::posix_time::seconds(RESEND_INTERVAL));
auto s = m_Session.shared_from_this();
m_ResendTimer.async_wait ([s](const boost::system::error_code& ecode)
{ s->m_Data.HandleResendTimer (ecode); });
}
void SSUData::HandleResendTimer (const boost::system::error_code& ecode)
{
if (ecode != boost::asio::error::operation_aborted)
{
uint8_t buf[SSU_V4_MAX_PACKET_SIZE + 18];
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
int numResent = 0;
for (auto it = m_SentMessages.begin (); it != m_SentMessages.end ();)
{
if (ts >= it->second->nextResendTime)
{
if (it->second->numResends < MAX_NUM_RESENDS)
{
for (auto& f: it->second->fragments)
if (f)
{
try
{
m_Session.FillHeaderAndEncrypt (PAYLOAD_TYPE_DATA, f->buf, f->len, buf);
m_Session.Send (buf, f->len); // resend
numResent++;
}
catch (boost::system::system_error& ec)
{
LogPrint (eLogWarning, "SSU: Can't resend message ", it->first, " data fragment: ", ec.what ());
}
}
it->second->numResends++;
it->second->nextResendTime += it->second->numResends*RESEND_INTERVAL;
++it;
}
else
{
LogPrint (eLogInfo, "SSU: message ", it->first, " has not been ACKed after ", MAX_NUM_RESENDS, " attempts, deleted");
it = m_SentMessages.erase (it);
}
}
else
++it;
}
if (m_SentMessages.empty ()) return; // nothing to resend
if (numResent < MAX_OUTGOING_WINDOW_SIZE)
ScheduleResend ();
else
{
LogPrint (eLogError, "SSU: resend window exceeds max size. Session terminated");
m_Session.Close ();
}
}
}
void SSUData::CleanUp (uint64_t ts)
{
for (auto it = m_IncompleteMessages.begin (); it != m_IncompleteMessages.end ();)
{
if (ts > it->second->lastFragmentInsertTime + INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT)
{
LogPrint (eLogWarning, "SSU: message ", it->first, " was not completed in ", INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT, " seconds, deleted");
it = m_IncompleteMessages.erase (it);
}
else
++it;
}
if (m_ReceivedMessages.size () > MAX_NUM_RECEIVED_MESSAGES || ts > m_LastMessageReceivedTime + DECAY_INTERVAL)
// decay
m_ReceivedMessages.clear ();
else
{
// delete old received messages
for (auto it = m_ReceivedMessages.begin (); it != m_ReceivedMessages.end ();)
{
if (ts > it->second + RECEIVED_MESSAGES_CLEANUP_TIMEOUT)
it = m_ReceivedMessages.erase (it);
else
++it;
}
}
}
}
}

View File

@@ -1,131 +0,0 @@
/*
* Copyright (c) 2013-2022, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
* See full license text in LICENSE file at top of project tree
*/
#ifndef SSU_DATA_H__
#define SSU_DATA_H__
#include <inttypes.h>
#include <string.h>
#include <vector>
#include <map>
#include <unordered_map>
#include <memory>
#include <boost/asio.hpp>
#include "I2NPProtocol.h"
#include "Identity.h"
#include "RouterInfo.h"
#include "TransportSession.h"
namespace i2p
{
namespace transport
{
const size_t SSU_MTU_V4 = 1484;
const size_t SSU_MTU_V6 = 1488;
const size_t SSU_V4_MAX_PACKET_SIZE = SSU_MTU_V4 - IPV4_HEADER_SIZE - UDP_HEADER_SIZE; // 1456
const size_t SSU_V6_MAX_PACKET_SIZE = SSU_MTU_V6 - IPV6_HEADER_SIZE - UDP_HEADER_SIZE; // 1440
const int RESEND_INTERVAL = 3; // in seconds
const int MAX_NUM_RESENDS = 5;
const int DECAY_INTERVAL = 20; // in seconds
const int INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT = 30; // in seconds
const int RECEIVED_MESSAGES_CLEANUP_TIMEOUT = 40; // in seconds
const unsigned int MAX_NUM_RECEIVED_MESSAGES = 1000; // how many msgID we store for duplicates check
const int MAX_OUTGOING_WINDOW_SIZE = 200; // how many unacked message we can store
// data flags
const uint8_t DATA_FLAG_EXTENDED_DATA_INCLUDED = 0x02;
const uint8_t DATA_FLAG_WANT_REPLY = 0x04;
const uint8_t DATA_FLAG_REQUEST_PREVIOUS_ACKS = 0x08;
const uint8_t DATA_FLAG_EXPLICIT_CONGESTION_NOTIFICATION = 0x10;
const uint8_t DATA_FLAG_ACK_BITFIELDS_INCLUDED = 0x40;
const uint8_t DATA_FLAG_EXPLICIT_ACKS_INCLUDED = 0x80;
struct Fragment
{
int fragmentNum;
size_t len;
bool isLast;
uint8_t buf[SSU_V4_MAX_PACKET_SIZE + 18]; // use biggest
Fragment () = default;
Fragment (int n, const uint8_t * b, int l, bool last):
fragmentNum (n), len (l), isLast (last) { memcpy (buf, b, len); };
};
struct FragmentCmp
{
bool operator() (const std::shared_ptr<Fragment>& f1, const std::shared_ptr<Fragment>& f2) const
{
return f1->fragmentNum < f2->fragmentNum;
};
};
struct IncompleteMessage
{
std::shared_ptr<I2NPMessage> msg;
int nextFragmentNum;
uint32_t lastFragmentInsertTime; // in seconds
uint64_t receivedFragmentsBits;
std::set<std::shared_ptr<Fragment>, FragmentCmp> savedFragments;
IncompleteMessage (std::shared_ptr<I2NPMessage>&& m): msg (m), nextFragmentNum (0),
lastFragmentInsertTime (0), receivedFragmentsBits (0) {};
void AttachNextFragment (const uint8_t * fragment, size_t fragmentSize);
};
struct SentMessage
{
std::vector<std::shared_ptr<Fragment> > fragments;
uint32_t nextResendTime; // in seconds
int numResends;
};
class SSUSession;
class SSUData
{
public:
SSUData (SSUSession& session);
~SSUData ();
void Start ();
void Stop ();
void CleanUp (uint64_t ts);
void ProcessMessage (uint8_t * buf, size_t len);
void FlushReceivedMessage ();
void Send (std::shared_ptr<i2p::I2NPMessage> msg);
void AdjustPacketSize (std::shared_ptr<const i2p::data::RouterInfo> remoteRouter);
void UpdatePacketSize (const i2p::data::IdentHash& remoteIdent);
private:
void SendMsgAck (uint32_t msgID);
void SendFragmentAck (uint32_t msgID, uint64_t bits);
void ProcessAcks (uint8_t *& buf, uint8_t flag);
void ProcessFragments (uint8_t * buf);
void ProcessSentMessageAck (uint32_t msgID);
void ScheduleResend ();
void HandleResendTimer (const boost::system::error_code& ecode);
private:
SSUSession& m_Session;
std::map<uint32_t, std::shared_ptr<IncompleteMessage> > m_IncompleteMessages;
std::map<uint32_t, std::shared_ptr<SentMessage> > m_SentMessages;
std::unordered_map<uint32_t, uint64_t> m_ReceivedMessages; // msgID -> timestamp in seconds
boost::asio::deadline_timer m_ResendTimer;
int m_MaxPacketSize, m_PacketSize;
i2p::I2NPMessagesHandler m_Handler;
uint32_t m_LastMessageReceivedTime; // in second
};
}
}
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -1,177 +0,0 @@
/*
* Copyright (c) 2013-2022, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
* See full license text in LICENSE file at top of project tree
*/
#ifndef SSU_SESSION_H__
#define SSU_SESSION_H__
#include <inttypes.h>
#include <set>
#include <memory>
#include "Crypto.h"
#include "I2NPProtocol.h"
#include "TransportSession.h"
#include "SSUData.h"
namespace i2p
{
namespace transport
{
const uint8_t SSU_HEADER_EXTENDED_OPTIONS_INCLUDED = 0x04;
struct SSUHeader
{
uint8_t mac[16];
uint8_t iv[16];
uint8_t flag;
uint8_t time[4];
uint8_t GetPayloadType () const { return flag >> 4; };
bool IsExtendedOptions () const { return flag & SSU_HEADER_EXTENDED_OPTIONS_INCLUDED; };
};
const int SSU_CONNECT_TIMEOUT = 5; // 5 seconds
const int SSU_TERMINATION_TIMEOUT = 330; // 5.5 minutes
const int SSU_CLOCK_SKEW = 60; // in seconds
const int SSU_CLOCK_THRESHOLD = 15; // in seconds, if more we should adjust
const size_t SSU_MAX_I2NP_MESSAGE_SIZE = 32768;
// payload types (4 bits)
const uint8_t PAYLOAD_TYPE_SESSION_REQUEST = 0;
const uint8_t PAYLOAD_TYPE_SESSION_CREATED = 1;
const uint8_t PAYLOAD_TYPE_SESSION_CONFIRMED = 2;
const uint8_t PAYLOAD_TYPE_RELAY_REQUEST = 3;
const uint8_t PAYLOAD_TYPE_RELAY_RESPONSE = 4;
const uint8_t PAYLOAD_TYPE_RELAY_INTRO = 5;
const uint8_t PAYLOAD_TYPE_DATA = 6;
const uint8_t PAYLOAD_TYPE_PEER_TEST = 7;
const uint8_t PAYLOAD_TYPE_SESSION_DESTROYED = 8;
// extended options
const uint16_t EXTENDED_OPTIONS_FLAG_REQUEST_RELAY_TAG = 0x0001;
enum SessionState
{
eSessionStateUnknown,
eSessionStateIntroduced,
eSessionStateEstablished,
eSessionStateClosed,
eSessionStateFailed
};
enum PeerTestParticipant
{
ePeerTestParticipantUnknown = 0,
ePeerTestParticipantAlice1,
ePeerTestParticipantAlice2,
ePeerTestParticipantBob,
ePeerTestParticipantCharlie
};
class SSUServer;
class SSUSession: public TransportSession, public std::enable_shared_from_this<SSUSession>
{
public:
SSUSession (SSUServer& server, boost::asio::ip::udp::endpoint& remoteEndpoint,
std::shared_ptr<const i2p::data::RouterInfo> router = nullptr, bool peerTest = false);
void ProcessNextMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint);
~SSUSession ();
void Connect ();
void WaitForConnect ();
void Introduce (const i2p::data::RouterInfo::Introducer& introducer,
std::shared_ptr<const i2p::data::RouterInfo> to); // Alice to Charlie
void WaitForIntroduction ();
void Close ();
void Done ();
void Failed ();
const boost::asio::ip::udp::endpoint& GetRemoteEndpoint () { return m_RemoteEndpoint; };
SSUServer& GetServer () { return m_Server; };
bool IsV6 () const { return m_RemoteEndpoint.address ().is_v6 (); };
void SendI2NPMessages (const std::vector<std::shared_ptr<I2NPMessage> >& msgs);
void SendPeerTest (); // Alice
SessionState GetState () const { return m_State; };
size_t GetNumSentBytes () const { return m_NumSentBytes; };
size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; };
void SendKeepAlive ();
uint32_t GetRelayTag () const { return m_RelayTag; };
const i2p::data::RouterInfo::IntroKey& GetIntroKey () const { return m_IntroKey; };
void FlushData ();
void CleanUp (uint64_t ts);
private:
boost::asio::io_service& GetService ();
void CreateAESandMacKey (const uint8_t * pubKey);
size_t GetSSUHeaderSize (const uint8_t * buf) const;
void PostI2NPMessages (std::vector<std::shared_ptr<I2NPMessage> > msgs);
void ProcessMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); // call for established session
void ProcessSessionRequest (const uint8_t * buf, size_t len);
void SendSessionRequest ();
void SendRelayRequest (const i2p::data::RouterInfo::Introducer& introducer, uint32_t nonce);
void ProcessSessionCreated (uint8_t * buf, size_t len);
void SendSessionCreated (const uint8_t * x, bool sendRelayTag = true);
void ProcessSessionConfirmed (const uint8_t * buf, size_t len);
void SendSessionConfirmed (const uint8_t * y, const uint8_t * ourAddress, size_t ourAddressLen);
void ProcessRelayRequest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& from);
void SendRelayResponse (uint32_t nonce, const boost::asio::ip::udp::endpoint& from,
const uint8_t * introKey, const boost::asio::ip::udp::endpoint& to);
void SendRelayIntro (std::shared_ptr<SSUSession> session, const boost::asio::ip::udp::endpoint& from);
void ProcessRelayResponse (const uint8_t * buf, size_t len);
void ProcessRelayIntro (const uint8_t * buf, size_t len);
void Established ();
void ScheduleConnectTimer ();
void HandleConnectTimer (const boost::system::error_code& ecode);
void ProcessPeerTest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint);
void SendPeerTest (uint32_t nonce, const boost::asio::ip::address& address, uint16_t port, const uint8_t * introKey, bool toAddress = true, bool sendAddress = true);
void ProcessData (uint8_t * buf, size_t len);
void SendSessionDestroyed ();
void Send (uint8_t type, const uint8_t * payload, size_t len); // with session key
void Send (const uint8_t * buf, size_t size);
void FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len, const i2p::crypto::AESKey& aesKey,
const uint8_t * iv, const i2p::crypto::MACKey& macKey, uint8_t flag = 0);
void FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len); // with session key
void FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * in, size_t len, uint8_t * out); // with session key
void Decrypt (uint8_t * buf, size_t len, const i2p::crypto::AESKey& aesKey);
void DecryptSessionKey (uint8_t * buf, size_t len);
bool Validate (uint8_t * buf, size_t len, const i2p::crypto::MACKey& macKey);
void Reset ();
static size_t ExtractIPAddressAndPort (const uint8_t * buf, size_t len, boost::asio::ip::address& ip, uint16_t& port); // returns actual buf size
private:
friend class SSUData; // TODO: change in later
SSUServer& m_Server;
const boost::asio::ip::udp::endpoint m_RemoteEndpoint;
boost::asio::deadline_timer m_ConnectTimer;
bool m_IsPeerTest;
SessionState m_State;
bool m_IsSessionKey;
uint32_t m_RelayTag; // received from peer
uint32_t m_SentRelayTag; // sent by us
i2p::crypto::CBCEncryption m_SessionKeyEncryption;
i2p::crypto::CBCDecryption m_SessionKeyDecryption;
i2p::crypto::AESKey m_SessionKey;
i2p::crypto::MACKey m_MacKey;
i2p::data::RouterInfo::IntroKey m_IntroKey;
SSUData m_Data;
bool m_IsDataReceived;
std::unique_ptr<SignedData> m_SignedData; // we need it for SessionConfirmed only
std::map<uint32_t, std::pair <std::shared_ptr<const i2p::data::RouterInfo>, uint64_t > > m_RelayRequests; // nonce->(Charlie, timestamp)
std::shared_ptr<i2p::crypto::DHKeys> m_DHKeysPair; // X - for client and Y - for server
};
}
}
#endif

View File

@@ -435,7 +435,7 @@ namespace stream
LogPrint(eLogError, "Streaming: Packet ", seqn, "sent from the future, sendTime=", sentPacket->sendTime);
rtt = 1;
}
m_RTT = (m_RTT*seqn + rtt)/(seqn + 1);
m_RTT = std::round ((m_RTT*seqn + rtt)/(seqn + 1.0));
m_RTO = m_RTT*1.5; // TODO: implement it better
LogPrint (eLogDebug, "Streaming: Packet ", seqn, " acknowledged rtt=", rtt, " sentTime=", sentPacket->sendTime);
m_SentPackets.erase (it++);
@@ -478,11 +478,11 @@ namespace stream
{
if (!len) return 0;
size_t ret = 0;
volatile bool done = false;
std::condition_variable newDataReceived;
std::mutex newDataReceivedMutex;
std::unique_lock<std::mutex> l(newDataReceivedMutex);
AsyncReceive (boost::asio::buffer (buf, len),
[&ret, &newDataReceived, &newDataReceivedMutex](const boost::system::error_code& ecode, std::size_t bytes_transferred)
[&ret, &done, &newDataReceived, &newDataReceivedMutex](const boost::system::error_code& ecode, std::size_t bytes_transferred)
{
if (ecode == boost::asio::error::timed_out)
ret = 0;
@@ -490,13 +490,32 @@ namespace stream
ret = bytes_transferred;
std::unique_lock<std::mutex> l(newDataReceivedMutex);
newDataReceived.notify_all ();
done = true;
},
timeout);
if (newDataReceived.wait_for (l, std::chrono::seconds (timeout)) == std::cv_status::timeout)
ret = 0;
if (!done)
{ std::unique_lock<std::mutex> l(newDataReceivedMutex);
if (!done && newDataReceived.wait_for (l, std::chrono::seconds (timeout)) == std::cv_status::timeout)
ret = 0;
}
if (!done)
{
// make sure that AsycReceive complete
auto s = shared_from_this();
m_Service.post ([s]()
{
s->m_ReceiveTimer.cancel ();
});
int i = 0;
while (!done && i < 100) // 1 sec
{
std::this_thread::sleep_for (std::chrono::milliseconds(10));
i++;
}
}
return ret;
}
}
size_t Stream::Send (const uint8_t * buf, size_t len)
{
AsyncSend (buf, len, nullptr);
@@ -1377,11 +1396,11 @@ namespace stream
});
if (timeout)
streamAccept.wait_for (l, std::chrono::seconds (timeout));
else
else
streamAccept.wait (l);
return stream;
}
}
void StreamingDestination::HandlePendingIncomingTimer (const boost::system::error_code& ecode)
{
if (ecode != boost::asio::error::operation_aborted)

View File

@@ -186,7 +186,7 @@ namespace stream
void AsyncReceive (const Buffer& buffer, ReceiveHandler handler, int timeout = 0);
size_t ReadSome (uint8_t * buf, size_t len) { return ConcatenatePackets (buf, len); };
size_t Receive (uint8_t * buf, size_t len, int timeout);
void AsyncClose() { m_Service.post(std::bind(&Stream::Close, shared_from_this())); };
/** only call close from destination thread, use Stream::AsyncClose for other threads */
@@ -280,7 +280,7 @@ namespace stream
void AcceptOnce (const Acceptor& acceptor);
void AcceptOnceAcceptor (std::shared_ptr<Stream> stream, Acceptor acceptor, Acceptor prev);
std::shared_ptr<Stream> AcceptStream (int timeout = 0); // sync
std::shared_ptr<i2p::client::ClientDestination> GetOwner () const { return m_Owner; };
void SetOwner (std::shared_ptr<i2p::client::ClientDestination> owner) { m_Owner = owner; };
uint16_t GetLocalPort () const { return m_LocalPort; };

View File

@@ -25,7 +25,7 @@ namespace util
uint32_t GetHoursSinceEpoch ();
void GetCurrentDate (char * date); // returns date as YYYYMMDD string, 9 bytes
void GetDateString (uint64_t timestamp, char * date); // timestap is seconds since epoch, returns date as YYYYMMDD string, 9 bytes
void GetDateString (uint64_t timestamp, char * date); // timestamp is seconds since epoch, returns date as YYYYMMDD string, 9 bytes
void AdjustTimeOffset (int64_t offset); // in seconds from current
class NTPTimeSync

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2021, The PurpleI2P Project
* Copyright (c) 2013-2022, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -20,16 +20,21 @@ namespace i2p
namespace tunnel
{
TransitTunnel::TransitTunnel (uint32_t receiveTunnelID,
const uint8_t * nextIdent, uint32_t nextTunnelID,
const uint8_t * layerKey,const uint8_t * ivKey):
TunnelBase (receiveTunnelID, nextTunnelID, nextIdent)
const i2p::data::IdentHash& nextIdent, uint32_t nextTunnelID,
const i2p::crypto::AESKey& layerKey, const i2p::crypto::AESKey& ivKey):
TunnelBase (receiveTunnelID, nextTunnelID, nextIdent),
m_LayerKey (layerKey), m_IVKey (ivKey)
{
m_Encryption.SetKeys (layerKey, ivKey);
}
void TransitTunnel::EncryptTunnelMsg (std::shared_ptr<const I2NPMessage> in, std::shared_ptr<I2NPMessage> out)
{
m_Encryption.Encrypt (in->GetPayload () + 4, out->GetPayload () + 4);
if (!m_Encryption)
{
m_Encryption.reset (new i2p::crypto::TunnelEncryption);
m_Encryption->SetKeys (m_LayerKey, m_IVKey);
}
m_Encryption->Encrypt (in->GetPayload () + 4, out->GetPayload () + 4);
i2p::transport::transports.UpdateTotalTransitTransmittedBytes (TUNNEL_DATA_MSG_SIZE);
}
@@ -94,8 +99,8 @@ namespace tunnel
}
std::shared_ptr<TransitTunnel> CreateTransitTunnel (uint32_t receiveTunnelID,
const uint8_t * nextIdent, uint32_t nextTunnelID,
const uint8_t * layerKey,const uint8_t * ivKey,
const i2p::data::IdentHash& nextIdent, uint32_t nextTunnelID,
const i2p::crypto::AESKey& layerKey, const i2p::crypto::AESKey& ivKey,
bool isGateway, bool isEndpoint)
{
if (isEndpoint)

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2021, The PurpleI2P Project
* Copyright (c) 2013-2022, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -28,8 +28,8 @@ namespace tunnel
public:
TransitTunnel (uint32_t receiveTunnelID,
const uint8_t * nextIdent, uint32_t nextTunnelID,
const uint8_t * layerKey,const uint8_t * ivKey);
const i2p::data::IdentHash& nextIdent, uint32_t nextTunnelID,
const i2p::crypto::AESKey& layerKey, const i2p::crypto::AESKey& ivKey);
virtual size_t GetNumTransmittedBytes () const { return 0; };
@@ -39,7 +39,8 @@ namespace tunnel
void EncryptTunnelMsg (std::shared_ptr<const I2NPMessage> in, std::shared_ptr<I2NPMessage> out);
private:
i2p::crypto::TunnelEncryption m_Encryption;
i2p::crypto::AESKey m_LayerKey, m_IVKey;
std::unique_ptr<i2p::crypto::TunnelEncryption> m_Encryption;
};
class TransitTunnelParticipant: public TransitTunnel
@@ -47,8 +48,8 @@ namespace tunnel
public:
TransitTunnelParticipant (uint32_t receiveTunnelID,
const uint8_t * nextIdent, uint32_t nextTunnelID,
const uint8_t * layerKey,const uint8_t * ivKey):
const i2p::data::IdentHash& nextIdent, uint32_t nextTunnelID,
const i2p::crypto::AESKey& layerKey, const i2p::crypto::AESKey& ivKey):
TransitTunnel (receiveTunnelID, nextIdent, nextTunnelID,
layerKey, ivKey), m_NumTransmittedBytes (0) {};
~TransitTunnelParticipant ();
@@ -68,8 +69,8 @@ namespace tunnel
public:
TransitTunnelGateway (uint32_t receiveTunnelID,
const uint8_t * nextIdent, uint32_t nextTunnelID,
const uint8_t * layerKey,const uint8_t * ivKey):
const i2p::data::IdentHash& nextIdent, uint32_t nextTunnelID,
const i2p::crypto::AESKey& layerKey, const i2p::crypto::AESKey& ivKey):
TransitTunnel (receiveTunnelID, nextIdent, nextTunnelID,
layerKey, ivKey), m_Gateway(this) {};
@@ -88,8 +89,8 @@ namespace tunnel
public:
TransitTunnelEndpoint (uint32_t receiveTunnelID,
const uint8_t * nextIdent, uint32_t nextTunnelID,
const uint8_t * layerKey,const uint8_t * ivKey):
const i2p::data::IdentHash& nextIdent, uint32_t nextTunnelID,
const i2p::crypto::AESKey& layerKey, const i2p::crypto::AESKey& ivKey):
TransitTunnel (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey),
m_Endpoint (false) {}; // transit endpoint is always outbound
@@ -104,8 +105,8 @@ namespace tunnel
};
std::shared_ptr<TransitTunnel> CreateTransitTunnel (uint32_t receiveTunnelID,
const uint8_t * nextIdent, uint32_t nextTunnelID,
const uint8_t * layerKey,const uint8_t * ivKey,
const i2p::data::IdentHash& nextIdent, uint32_t nextTunnelID,
const i2p::crypto::AESKey& layerKey, const i2p::crypto::AESKey& ivKey,
bool isGateway, bool isEndpoint);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2022, The PurpleI2P Project
* Copyright (c) 2013-2023, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -74,7 +74,8 @@ namespace transport
public:
TransportSession (std::shared_ptr<const i2p::data::RouterInfo> router, int terminationTimeout):
m_NumSentBytes (0), m_NumReceivedBytes (0), m_IsOutgoing (router), m_TerminationTimeout (terminationTimeout),
m_NumSentBytes (0), m_NumReceivedBytes (0), m_SendQueueSize (0),
m_IsOutgoing (router), m_TerminationTimeout (terminationTimeout),
m_LastActivityTimestamp (i2p::util::GetSecondsSinceEpoch ())
{
if (router)
@@ -100,6 +101,7 @@ namespace transport
size_t GetNumSentBytes () const { return m_NumSentBytes; };
size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; };
size_t GetSendQueueSize () const { return m_SendQueueSize; };
bool IsOutgoing () const { return m_IsOutgoing; };
int GetTerminationTimeout () const { return m_TerminationTimeout; };
@@ -113,12 +115,13 @@ namespace transport
virtual uint32_t GetRelayTag () const { return 0; };
virtual void SendLocalRouterInfo (bool update = false) { SendI2NPMessages ({ CreateDatabaseStoreMsg () }); };
virtual void SendI2NPMessages (const std::vector<std::shared_ptr<I2NPMessage> >& msgs) = 0;
virtual bool IsEstablished () const = 0;
protected:
std::shared_ptr<const i2p::data::IdentityEx> m_RemoteIdentity;
mutable std::mutex m_RemoteIdentityMutex;
size_t m_NumSentBytes, m_NumReceivedBytes;
size_t m_NumSentBytes, m_NumReceivedBytes, m_SendQueueSize;
bool m_IsOutgoing;
int m_TerminationTimeout;
uint64_t m_LastActivityTimestamp;

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2022, The PurpleI2P Project
* Copyright (c) 2013-2023, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -136,12 +136,14 @@ namespace transport
Transports::Transports ():
m_IsOnline (true), m_IsRunning (false), m_IsNAT (true), m_CheckReserved(true), m_Thread (nullptr),
m_Service (nullptr), m_Work (nullptr), m_PeerCleanupTimer (nullptr), m_PeerTestTimer (nullptr),
m_SSUServer (nullptr), m_SSU2Server (nullptr), m_NTCP2Server (nullptr),
m_UpdateBandwidthTimer (nullptr), m_SSU2Server (nullptr), m_NTCP2Server (nullptr),
m_X25519KeysPairSupplier (15), // 15 pre-generated keys
m_TotalSentBytes(0), m_TotalReceivedBytes(0), m_TotalTransitTransmittedBytes (0),
m_InBandwidth (0), m_OutBandwidth (0), m_TransitBandwidth(0),
m_LastInBandwidthUpdateBytes (0), m_LastOutBandwidthUpdateBytes (0),
m_LastTransitBandwidthUpdateBytes (0), m_LastBandwidthUpdateTime (0)
m_TotalSentBytes (0), m_TotalReceivedBytes (0), m_TotalTransitTransmittedBytes (0),
m_InBandwidth (0), m_OutBandwidth (0), m_TransitBandwidth (0),
m_LastInBandwidthUpdateBytes (0), m_LastOutBandwidthUpdateBytes (0), m_LastTransitBandwidthUpdateBytes (0),
m_InBandwidth15s (0), m_OutBandwidth15s (0), m_TransitBandwidth15s (0),
m_LastInBandwidth15sUpdateBytes (0), m_LastOutBandwidth15sUpdateBytes (0), m_LastTransitBandwidth15sUpdateBytes (0),
m_LastBandwidth15sUpdateTime (0)
{
}
@@ -152,12 +154,13 @@ namespace transport
{
delete m_PeerCleanupTimer; m_PeerCleanupTimer = nullptr;
delete m_PeerTestTimer; m_PeerTestTimer = nullptr;
delete m_UpdateBandwidthTimer; m_UpdateBandwidthTimer = nullptr;
delete m_Work; m_Work = nullptr;
delete m_Service; m_Service = nullptr;
}
}
void Transports::Start (bool enableNTCP2, bool enableSSU, bool enableSSU2)
void Transports::Start (bool enableNTCP2, bool enableSSU2)
{
if (!m_Service)
{
@@ -165,6 +168,7 @@ namespace transport
m_Work = new boost::asio::io_service::work (*m_Service);
m_PeerCleanupTimer = new boost::asio::deadline_timer (*m_Service);
m_PeerTestTimer = new boost::asio::deadline_timer (*m_Service);
m_UpdateBandwidthTimer = new boost::asio::deadline_timer (*m_Service);
}
bool ipv4; i2p::config::GetOption("ipv4", ipv4);
@@ -205,25 +209,9 @@ namespace transport
m_NTCP2Server = new NTCP2Server ();
}
// create SSU server
int ssuPort = 0;
if (enableSSU)
{
auto& addresses = context.GetRouterInfo ().GetAddresses ();
for (const auto& address: addresses)
{
if (!address) continue;
if (address->transportStyle == RouterInfo::eTransportSSU)
{
ssuPort = address->port;
m_SSUServer = new SSUServer (address->port);
break;
}
}
}
// create SSU2 server
if (enableSSU2)
{
if (enableSSU2)
{
m_SSU2Server = new SSU2Server ();
std::string ssu2proxy; i2p::config::GetOption("ssu2.proxy", ssu2proxy);
if (!ssu2proxy.empty())
@@ -231,18 +219,18 @@ namespace transport
if (proxyurl.parse (ssu2proxy) && proxyurl.schema == "socks")
{
if (m_SSU2Server->SetProxy (proxyurl.host, proxyurl.port))
{
{
i2p::context.SetStatus (eRouterStatusProxy);
if (ipv6)
i2p::context.SetStatusV6 (eRouterStatusProxy);
}
}
else
LogPrint(eLogError, "Transports: Can't set SSU2 proxy ", ssu2proxy);
}
}
else
LogPrint(eLogError, "Transports: Invalid SSU2 proxy URL ", ssu2proxy);
}
}
}
}
// bind to interfaces
if (ipv4)
@@ -255,7 +243,6 @@ namespace transport
if (!ec)
{
if (m_NTCP2Server) m_NTCP2Server->SetLocalAddress (addr);
if (m_SSUServer) m_SSUServer->SetLocalAddress (addr);
if (m_SSU2Server) m_SSU2Server->SetLocalAddress (addr);
}
}
@@ -264,12 +251,12 @@ namespace transport
{
uint16_t mtu; i2p::config::GetOption ("ssu2.mtu4", mtu);
if (mtu)
{
{
if (mtu < (int)SSU2_MIN_PACKET_SIZE) mtu = SSU2_MIN_PACKET_SIZE;
if (mtu > (int)SSU2_MAX_PACKET_SIZE) mtu = SSU2_MAX_PACKET_SIZE;
i2p::context.SetMTU (mtu, true);
}
}
}
}
}
if (ipv6)
@@ -282,7 +269,6 @@ namespace transport
if (!ec)
{
if (m_NTCP2Server) m_NTCP2Server->SetLocalAddress (addr);
if (m_SSUServer) m_SSUServer->SetLocalAddress (addr);
if (m_SSU2Server) m_SSU2Server->SetLocalAddress (addr);
}
}
@@ -291,12 +277,12 @@ namespace transport
{
uint16_t mtu; i2p::config::GetOption ("ssu2.mtu6", mtu);
if (mtu)
{
{
if (mtu < (int)SSU2_MIN_PACKET_SIZE) mtu = SSU2_MIN_PACKET_SIZE;
if (mtu > (int)SSU2_MAX_PACKET_SIZE) mtu = SSU2_MAX_PACKET_SIZE;
i2p::context.SetMTU (mtu, false);
}
}
}
}
}
bool ygg; i2p::config::GetOption("meshnets.yggdrasil", ygg);
@@ -315,26 +301,14 @@ namespace transport
// start servers
if (m_NTCP2Server) m_NTCP2Server->Start ();
if (m_SSU2Server) m_SSU2Server->Start ();
if (m_SSUServer)
{
LogPrint (eLogInfo, "Transports: Start listening UDP port ", ssuPort);
try
{
m_SSUServer->Start ();
}
catch (std::exception& ex )
{
LogPrint(eLogError, "Transports: Failed to bind to UDP port", ssuPort);
m_SSUServer->Stop ();
delete m_SSUServer;
m_SSUServer = nullptr;
}
}
if (m_SSUServer || m_SSU2Server) DetectExternalIP ();
if (m_SSU2Server) DetectExternalIP ();
m_PeerCleanupTimer->expires_from_now (boost::posix_time::seconds(5*SESSION_CREATION_TIMEOUT));
m_PeerCleanupTimer->expires_from_now (boost::posix_time::seconds(5 * SESSION_CREATION_TIMEOUT));
m_PeerCleanupTimer->async_wait (std::bind (&Transports::HandlePeerCleanupTimer, this, std::placeholders::_1));
m_UpdateBandwidthTimer->expires_from_now (boost::posix_time::seconds(1));
m_UpdateBandwidthTimer->async_wait (std::bind (&Transports::HandleUpdateBandwidthTimer, this, std::placeholders::_1));
if (m_IsNAT)
{
m_PeerTestTimer->expires_from_now (boost::posix_time::minutes(PEER_TEST_INTERVAL));
@@ -347,12 +321,6 @@ namespace transport
if (m_PeerCleanupTimer) m_PeerCleanupTimer->cancel ();
if (m_PeerTestTimer) m_PeerTestTimer->cancel ();
m_Peers.clear ();
if (m_SSUServer)
{
m_SSUServer->Stop ();
delete m_SSUServer;
m_SSUServer = nullptr;
}
if (m_SSU2Server)
{
@@ -396,29 +364,44 @@ namespace transport
}
}
void Transports::UpdateBandwidth ()
void Transports::HandleUpdateBandwidthTimer (const boost::system::error_code& ecode)
{
uint64_t ts = i2p::util::GetMillisecondsSinceEpoch ();
if (m_LastBandwidthUpdateTime > 0)
if (ecode != boost::asio::error::operation_aborted)
{
auto delta = ts - m_LastBandwidthUpdateTime;
if (delta > 0)
uint64_t ts = i2p::util::GetMillisecondsSinceEpoch ();
// updated every second
m_InBandwidth = m_TotalReceivedBytes - m_LastInBandwidthUpdateBytes;
m_OutBandwidth = m_TotalSentBytes - m_LastOutBandwidthUpdateBytes;
m_TransitBandwidth = m_TotalTransitTransmittedBytes - m_LastTransitBandwidthUpdateBytes;
m_LastInBandwidthUpdateBytes = m_TotalReceivedBytes;
m_LastOutBandwidthUpdateBytes = m_TotalSentBytes;
m_LastTransitBandwidthUpdateBytes = m_TotalTransitTransmittedBytes;
// updated every 15 seconds
auto delta = ts - m_LastBandwidth15sUpdateTime;
if (delta > 15 * 1000)
{
m_InBandwidth = (m_TotalReceivedBytes - m_LastInBandwidthUpdateBytes)*1000/delta; // per second
m_OutBandwidth = (m_TotalSentBytes - m_LastOutBandwidthUpdateBytes)*1000/delta; // per second
m_TransitBandwidth = (m_TotalTransitTransmittedBytes - m_LastTransitBandwidthUpdateBytes)*1000/delta;
m_InBandwidth15s = (m_TotalReceivedBytes - m_LastInBandwidth15sUpdateBytes) * 1000 / delta;
m_OutBandwidth15s = (m_TotalSentBytes - m_LastOutBandwidth15sUpdateBytes) * 1000 / delta;
m_TransitBandwidth15s = (m_TotalTransitTransmittedBytes - m_LastTransitBandwidth15sUpdateBytes) * 1000 / delta;
m_LastBandwidth15sUpdateTime = ts;
m_LastInBandwidth15sUpdateBytes = m_TotalReceivedBytes;
m_LastOutBandwidth15sUpdateBytes = m_TotalSentBytes;
m_LastTransitBandwidth15sUpdateBytes = m_TotalTransitTransmittedBytes;
}
m_UpdateBandwidthTimer->expires_from_now (boost::posix_time::seconds(1));
m_UpdateBandwidthTimer->async_wait (std::bind (&Transports::HandleUpdateBandwidthTimer, this, std::placeholders::_1));
}
m_LastBandwidthUpdateTime = ts;
m_LastInBandwidthUpdateBytes = m_TotalReceivedBytes;
m_LastOutBandwidthUpdateBytes = m_TotalSentBytes;
m_LastTransitBandwidthUpdateBytes = m_TotalTransitTransmittedBytes;
}
bool Transports::IsBandwidthExceeded () const
{
auto limit = i2p::context.GetBandwidthLimit() * 1024; // convert to bytes
auto bw = std::max (m_InBandwidth, m_OutBandwidth);
auto bw = std::max (m_InBandwidth15s, m_OutBandwidth15s);
return bw > limit;
}
@@ -538,21 +521,6 @@ namespace transport
}
break;
}
case i2p::data::RouterInfo::eSSUV4:
case i2p::data::RouterInfo::eSSUV6:
{
if (!m_SSUServer) continue;
std::shared_ptr<const RouterInfo::Address> address = (tr == i2p::data::RouterInfo::eSSUV6) ?
peer.router->GetSSUV6Address () : peer.router->GetSSUAddress (true);
if (address && m_CheckReserved && i2p::util::net::IsInReservedRange(address->host))
address = nullptr;
if (address && address->IsReachableSSU ())
{
if (m_SSUServer->CreateSession (peer.router, address))
return true;
}
break;
}
case i2p::data::RouterInfo::eNTCP2V6Mesh:
{
if (!m_NTCP2Server) continue;
@@ -595,9 +563,7 @@ namespace transport
i2p::data::RouterInfo::eNTCP2V4,
i2p::data::RouterInfo::eSSU2V6,
i2p::data::RouterInfo::eSSU2V4,
i2p::data::RouterInfo::eNTCP2V6Mesh,
i2p::data::RouterInfo::eSSUV6,
i2p::data::RouterInfo::eSSUV4
i2p::data::RouterInfo::eNTCP2V6Mesh
},
ssu2Priority =
{
@@ -605,9 +571,7 @@ namespace transport
i2p::data::RouterInfo::eSSU2V4,
i2p::data::RouterInfo::eNTCP2V6,
i2p::data::RouterInfo::eNTCP2V4,
i2p::data::RouterInfo::eNTCP2V6Mesh,
i2p::data::RouterInfo::eSSUV6,
i2p::data::RouterInfo::eSSUV4
i2p::data::RouterInfo::eNTCP2V6Mesh
};
if (!peer.router) return;
auto compatibleTransports = context.GetRouterInfo ().GetCompatibleTransports (false) &
@@ -654,7 +618,7 @@ namespace transport
i2p::context.SetStatus (eRouterStatusOK);
return;
}
if (m_SSUServer || m_SSU2Server)
if (m_SSU2Server)
PeerTest ();
else
LogPrint (eLogWarning, "Transports: Can't detect external IP. SSU or SSU2 is not available");
@@ -662,103 +626,44 @@ namespace transport
void Transports::PeerTest (bool ipv4, bool ipv6)
{
if (RoutesRestricted() || (!m_SSUServer && !m_SSU2Server)) return;
if (RoutesRestricted() || !m_SSU2Server || m_SSU2Server->UsesProxy ()) return;
if (ipv4 && i2p::context.SupportsV4 ())
{
LogPrint (eLogInfo, "Transports: Started peer test IPv4");
std::set<i2p::data::IdentHash> excluded;
excluded.insert (i2p::context.GetIdentHash ()); // don't pick own router
if (m_SSUServer)
for (int i = 0; i < 5; i++)
{
bool statusChanged = false;
for (int i = 0; i < 5; i++)
auto router = i2p::data::netdb.GetRandomSSU2PeerTestRouter (true, excluded); // v4
if (router)
{
auto router = i2p::data::netdb.GetRandomPeerTestRouter (true, excluded); // v4
if (router)
{
auto addr = router->GetSSUAddress (true); // ipv4
if (addr && !i2p::util::net::IsInReservedRange(addr->host))
{
if (!statusChanged)
{
statusChanged = true;
i2p::context.SetStatus (eRouterStatusTesting); // first time only
}
m_SSUServer->CreateSession (router, addr, true); // peer test v4
}
excluded.insert (router->GetIdentHash ());
}
}
if (!statusChanged)
LogPrint (eLogWarning, "Transports: Can't find routers for peer test IPv4");
}
// SSU2
if (m_SSU2Server && !m_SSU2Server->UsesProxy ())
{
excluded.clear ();
excluded.insert (i2p::context.GetIdentHash ());
int numTests = m_SSUServer ? 3 : 5;
for (int i = 0; i < numTests; i++)
{
auto router = i2p::data::netdb.GetRandomSSU2PeerTestRouter (true, excluded); // v4
if (router)
{
if (i2p::context.GetStatus () != eRouterStatusTesting)
i2p::context.SetStatusSSU2 (eRouterStatusTesting);
m_SSU2Server->StartPeerTest (router, true);
excluded.insert (router->GetIdentHash ());
}
if (i2p::context.GetStatus () != eRouterStatusTesting)
i2p::context.SetStatus (eRouterStatusTesting);
m_SSU2Server->StartPeerTest (router, true);
excluded.insert (router->GetIdentHash ());
}
}
if (excluded.size () <= 1)
LogPrint (eLogWarning, "Transports: Can't find routers for peer test IPv4");
}
if (ipv6 && i2p::context.SupportsV6 ())
{
LogPrint (eLogInfo, "Transports: Started peer test IPv6");
std::set<i2p::data::IdentHash> excluded;
excluded.insert (i2p::context.GetIdentHash ()); // don't pick own router
if (m_SSUServer)
for (int i = 0; i < 5; i++)
{
bool statusChanged = false;
for (int i = 0; i < 5; i++)
auto router = i2p::data::netdb.GetRandomSSU2PeerTestRouter (false, excluded); // v6
if (router)
{
auto router = i2p::data::netdb.GetRandomPeerTestRouter (false, excluded); // v6
if (router)
{
auto addr = router->GetSSUV6Address ();
if (addr && !i2p::util::net::IsInReservedRange(addr->host))
{
if (!statusChanged)
{
statusChanged = true;
i2p::context.SetStatusV6 (eRouterStatusTesting); // first time only
}
m_SSUServer->CreateSession (router, addr, true); // peer test v6
}
excluded.insert (router->GetIdentHash ());
}
}
if (!statusChanged)
LogPrint (eLogWarning, "Transports: Can't find routers for peer test IPv6");
}
// SSU2
if (m_SSU2Server && !m_SSU2Server->UsesProxy ())
{
excluded.clear ();
excluded.insert (i2p::context.GetIdentHash ());
int numTests = m_SSUServer ? 3 : 5;
for (int i = 0; i < numTests; i++)
{
auto router = i2p::data::netdb.GetRandomSSU2PeerTestRouter (false, excluded); // v6
if (router)
{
if (i2p::context.GetStatusV6 () != eRouterStatusTesting)
i2p::context.SetStatusV6SSU2 (eRouterStatusTesting);
m_SSU2Server->StartPeerTest (router, false);
excluded.insert (router->GetIdentHash ());
}
if (i2p::context.GetStatusV6 () != eRouterStatusTesting)
i2p::context.SetStatusV6 (eRouterStatusTesting);
m_SSU2Server->StartPeerTest (router, false);
excluded.insert (router->GetIdentHash ());
}
}
if (excluded.size () <= 1)
LogPrint (eLogWarning, "Transports: Can't find routers for peer test IPv6");
}
}
@@ -811,7 +716,8 @@ namespace transport
session->SendI2NPMessages ({ CreateDatabaseStoreMsg () }); // send DatabaseStore
auto ts = i2p::util::GetSecondsSinceEpoch ();
std::unique_lock<std::mutex> l(m_PeersMutex);
m_Peers.insert (std::make_pair (ident, Peer{ nullptr, ts }));
auto it = m_Peers.insert (std::make_pair (ident, Peer{ nullptr, ts })).first;
it->second.sessions.push_back (session);
}
});
}
@@ -860,7 +766,12 @@ namespace transport
auto ts = i2p::util::GetSecondsSinceEpoch ();
for (auto it = m_Peers.begin (); it != m_Peers.end (); )
{
if (it->second.sessions.empty () && ts > it->second.creationTime + SESSION_CREATION_TIMEOUT)
it->second.sessions.remove_if (
[](std::shared_ptr<TransportSession> session)->bool
{
return !session || !session->IsEstablished ();
});
if (it->second.sessions.empty () && ts > it->second.creationTime + SESSION_CREATION_TIMEOUT)
{
LogPrint (eLogWarning, "Transports: Session to peer ", it->first.ToBase64 (), " has not been created in ", SESSION_CREATION_TIMEOUT, " seconds");
auto profile = i2p::data::GetRouterProfile(it->first);
@@ -884,13 +795,12 @@ namespace transport
++it;
}
}
UpdateBandwidth (); // TODO: use separate timer(s) for it
bool ipv4Testing = i2p::context.GetStatus () == eRouterStatusTesting;
bool ipv6Testing = i2p::context.GetStatusV6 () == eRouterStatusTesting;
// if still testing, repeat peer test
if (ipv4Testing || ipv6Testing)
PeerTest (ipv4Testing, ipv6Testing);
m_PeerCleanupTimer->expires_from_now (boost::posix_time::seconds(3*SESSION_CREATION_TIMEOUT));
m_PeerCleanupTimer->expires_from_now (boost::posix_time::seconds(3 * SESSION_CREATION_TIMEOUT));
m_PeerCleanupTimer->async_wait (std::bind (&Transports::HandlePeerCleanupTimer, this, std::placeholders::_1));
}
}
@@ -913,7 +823,9 @@ namespace transport
std::unique_lock<std::mutex> l(m_PeersMutex);
auto it = m_Peers.begin ();
std::advance (it, rand () % m_Peers.size ());
if (it == m_Peers.end () || it->second.router) return nullptr; // not connected
if (it == m_Peers.end () || it->second.router || it->second.sessions.empty () ||
it->second.sessions.front ()->GetSendQueueSize () > PEER_ROUTER_INFO_OVERLOAD_QUEUE_SIZE)
return nullptr; // not connected or overloaded
ident = it->first;
}
return i2p::data::netdb.FindRouter (ident);
@@ -1075,7 +987,6 @@ namespace transport
i2p::context.SetSupportsV4 (ipv4);
i2p::context.SetSupportsMesh (ygg, yggaddr);
i2p::context.RemoveNTCPAddress (!ipv6); // TODO: remove later
bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2);
if (ntcp2)
{
@@ -1108,15 +1019,13 @@ namespace transport
if (!ipv4 && !ipv6)
i2p::context.SetStatus (eRouterStatusMesh);
}
bool ssu; i2p::config::GetOption("ssu", ssu);
if (!ssu) i2p::context.RemoveSSUAddress (); // TODO: remove later
bool ssu2; i2p::config::GetOption("ssu2.enabled", ssu2);
if (ssu2 && i2p::config::IsDefault ("ssu2.enabled") && !ipv4 && !ipv6)
ssu2 = false; // don't enable ssu2 for yggdrasil only router
if (ssu2)
{
uint16_t ssu2port; i2p::config::GetOption("ssu2.port", ssu2port);
if (!ssu2port && port) ssu2port = ssu ? (port + 1) : port;
if (!ssu2port && port) ssu2port = port;
bool published; i2p::config::GetOption("ssu2.published", published);
if (published)
i2p::context.PublishSSU2Address (ssu2port, true, ipv4, ipv6); // publish

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2022, The PurpleI2P Project
* Copyright (c) 2013-2023, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -21,7 +21,6 @@
#include <atomic>
#include <boost/asio.hpp>
#include "TransportSession.h"
#include "SSU.h"
#include "SSU2.h"
#include "NTCP2.h"
#include "RouterInfo.h"
@@ -64,6 +63,7 @@ namespace transport
const int PEER_ROUTER_INFO_UPDATE_INTERVAL = 31*60; // in seconds
const int PEER_ROUTER_INFO_UPDATE_INTERVAL_VARIANCE = 7*60; // in seconds
const size_t PEER_ROUTER_INFO_OVERLOAD_QUEUE_SIZE = 25;
struct Peer
{
int numAttempts;
@@ -96,10 +96,9 @@ namespace transport
Transports ();
~Transports ();
void Start (bool enableNTCP2=true, bool enableSSU=true, bool enableSSU2=false);
void Start (bool enableNTCP2=true, bool enableSSU2=true);
void Stop ();
bool IsBoundSSU() const { return m_SSUServer != nullptr; }
bool IsBoundSSU2() const { return m_SSU2Server != nullptr; }
bool IsBoundNTCP2() const { return m_NTCP2Server != nullptr; }
@@ -126,6 +125,9 @@ namespace transport
uint32_t GetInBandwidth () const { return m_InBandwidth; };
uint32_t GetOutBandwidth () const { return m_OutBandwidth; };
uint32_t GetTransitBandwidth () const { return m_TransitBandwidth; };
uint32_t GetInBandwidth15s () const { return m_InBandwidth15s; };
uint32_t GetOutBandwidth15s () const { return m_OutBandwidth15s; };
uint32_t GetTransitBandwidth15s () const { return m_TransitBandwidth15s; };
bool IsBandwidthExceeded () const;
bool IsTransitBandwidthExceeded () const;
size_t GetNumPeers () const { return m_Peers.size (); };
@@ -157,8 +159,8 @@ namespace transport
void SetPriority (Peer& peer) const;
void HandlePeerCleanupTimer (const boost::system::error_code& ecode);
void HandlePeerTestTimer (const boost::system::error_code& ecode);
void HandleUpdateBandwidthTimer (const boost::system::error_code& ecode);
void UpdateBandwidth ();
void DetectExternalIP ();
private:
@@ -168,9 +170,8 @@ namespace transport
std::thread * m_Thread;
boost::asio::io_service * m_Service;
boost::asio::io_service::work * m_Work;
boost::asio::deadline_timer * m_PeerCleanupTimer, * m_PeerTestTimer;
boost::asio::deadline_timer * m_PeerCleanupTimer, * m_PeerTestTimer, * m_UpdateBandwidthTimer;
SSUServer * m_SSUServer;
SSU2Server * m_SSU2Server;
NTCP2Server * m_NTCP2Server;
mutable std::mutex m_PeersMutex;
@@ -179,9 +180,15 @@ namespace transport
X25519KeysPairSupplier m_X25519KeysPairSupplier;
std::atomic<uint64_t> m_TotalSentBytes, m_TotalReceivedBytes, m_TotalTransitTransmittedBytes;
uint32_t m_InBandwidth, m_OutBandwidth, m_TransitBandwidth; // bytes per second
// Bandwidth per second
uint32_t m_InBandwidth, m_OutBandwidth, m_TransitBandwidth;
uint64_t m_LastInBandwidthUpdateBytes, m_LastOutBandwidthUpdateBytes, m_LastTransitBandwidthUpdateBytes;
uint64_t m_LastBandwidthUpdateTime;
// Bandwidth every 15 seconds
uint32_t m_InBandwidth15s, m_OutBandwidth15s, m_TransitBandwidth15s;
uint64_t m_LastInBandwidth15sUpdateBytes, m_LastOutBandwidth15sUpdateBytes, m_LastTransitBandwidth15sUpdateBytes;
uint64_t m_LastBandwidth15sUpdateTime;
/** which router families to trust for first hops */
std::vector<i2p::data::FamilyID> m_TrustedFamilies;
@@ -196,7 +203,6 @@ namespace transport
public:
// for HTTP only
const SSUServer * GetSSUServer () const { return m_SSUServer; };
const NTCP2Server * GetNTCP2Server () const { return m_NTCP2Server; };
const SSU2Server * GetSSU2Server () const { return m_SSU2Server; };
const decltype(m_Peers)& GetPeers () const { return m_Peers; };

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2022, The PurpleI2P Project
* Copyright (c) 2013-2023, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -332,8 +332,7 @@ namespace tunnel
Tunnels tunnels;
Tunnels::Tunnels (): m_IsRunning (false), m_Thread (nullptr),
m_NumSuccesiveTunnelCreations (0), m_NumFailedTunnelCreations (0)
{
m_TunnelCreationSuccessRate (TCSR_START_VALUE), m_TunnelCreationAttemptsNum(0) {
}
Tunnels::~Tunnels ()
@@ -433,12 +432,16 @@ namespace tunnel
}
}
void Tunnels::AddTransitTunnel (std::shared_ptr<TransitTunnel> tunnel)
bool Tunnels::AddTransitTunnel (std::shared_ptr<TransitTunnel> tunnel)
{
if (m_Tunnels.emplace (tunnel->GetTunnelID (), tunnel).second)
m_TransitTunnels.push_back (tunnel);
else
{
LogPrint (eLogError, "Tunnel: Tunnel with id ", tunnel->GetTunnelID (), " already exists");
return false;
}
return true;
}
void Tunnels::Start ()
@@ -472,6 +475,7 @@ namespace tunnel
auto msg = m_Queue.GetNextWithTimeout (1000); // 1 sec
if (msg)
{
int numMsgs = 0;
uint32_t prevTunnelID = 0, tunnelID = 0;
std::shared_ptr<TunnelBase> prevTunnel;
do
@@ -515,11 +519,12 @@ namespace tunnel
LogPrint (eLogWarning, "Tunnel: Unexpected message type ", (int) typeID);
}
msg = m_Queue.Get ();
msg = (numMsgs <= MAX_TUNNEL_MSGS_BATCH_SIZE) ? m_Queue.Get () : nullptr;
if (msg)
{
prevTunnelID = tunnelID;
prevTunnel = tunnel;
numMsgs++;
}
else if (tunnel)
tunnel->FlushTunnelDataMsgs ();
@@ -530,17 +535,17 @@ namespace tunnel
if (i2p::transport::transports.IsOnline())
{
uint64_t ts = i2p::util::GetSecondsSinceEpoch ();
if (ts - lastTs >= 15) // manage tunnels every 15 seconds
if (ts - lastTs >= TUNNEL_MANAGE_INTERVAL) // manage tunnels every 15 seconds
{
ManageTunnels ();
lastTs = ts;
}
if (ts - lastPoolsTs >= 5) // manage pools every 5 seconds
if (ts - lastPoolsTs >= TUNNEL_POOLS_MANAGE_INTERVAL) // manage pools every 5 seconds
{
ManageTunnelPools (ts);
lastPoolsTs = ts;
}
if (ts - lastMemoryPoolTs >= 120) // manage memory pool every 2 minutes
if (ts - lastMemoryPoolTs >= TUNNEL_MEMORY_POOL_MANAGE_INTERVAL) // manage memory pool every 2 minutes
{
m_I2NPTunnelEndpointMessagesMemoryPool.CleanUpMt ();
m_I2NPTunnelMessagesMemoryPool.CleanUpMt ();
@@ -628,7 +633,7 @@ namespace tunnel
}
// delete
it = pendingTunnels.erase (it);
m_NumFailedTunnelCreations++;
FailedTunnelCreation();
}
else
++it;
@@ -636,7 +641,7 @@ namespace tunnel
case eTunnelStateBuildFailed:
LogPrint (eLogDebug, "Tunnel: Pending build request ", it->first, " failed, deleted");
it = pendingTunnels.erase (it);
m_NumFailedTunnelCreations++;
FailedTunnelCreation();
break;
case eTunnelStateBuildReplyReceived:
// intermediate state, will be either established of build failed
@@ -645,7 +650,7 @@ namespace tunnel
default:
// success
it = pendingTunnels.erase (it);
m_NumSuccesiveTunnelCreations++;
SuccesiveTunnelCreation();
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2022, The PurpleI2P Project
* Copyright (c) 2013-2023, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -40,10 +40,17 @@ namespace tunnel
const int STANDARD_NUM_RECORDS = 4; // in VariableTunnelBuild message
const int MAX_NUM_RECORDS = 8;
const int HIGH_LATENCY_PER_HOP = 250; // in milliseconds
const int MAX_TUNNEL_MSGS_BATCH_SIZE = 100; // handle messages without interrupt
const int TUNNEL_MANAGE_INTERVAL = 15; // in seconds
const int TUNNEL_POOLS_MANAGE_INTERVAL = 5; // in seconds
const int TUNNEL_MEMORY_POOL_MANAGE_INTERVAL = 120; // in seconds
const size_t I2NP_TUNNEL_MESSAGE_SIZE = TUNNEL_DATA_MSG_SIZE + I2NP_HEADER_SIZE + 34; // reserved for alignment and NTCP 16 + 6 + 12
const size_t I2NP_TUNNEL_ENPOINT_MESSAGE_SIZE = 2*TUNNEL_DATA_MSG_SIZE + I2NP_HEADER_SIZE + TUNNEL_GATEWAY_HEADER_SIZE + 28; // reserved for alignment and NTCP 16 + 6 + 6
const double TCSR_SMOOTHING_CONSTANT = 0.0005; // smoothing constant in exponentially weighted moving average
const double TCSR_START_VALUE = 0.1; // start value of tunnel creation success rate
enum TunnelState
{
eTunnelStatePending,
@@ -206,7 +213,7 @@ namespace tunnel
std::shared_ptr<TunnelPool> GetExploratoryPool () const { return m_ExploratoryPool; };
std::shared_ptr<TunnelBase> GetTunnel (uint32_t tunnelID);
int GetTransitTunnelsExpirationTimeout ();
void AddTransitTunnel (std::shared_ptr<TransitTunnel> tunnel);
bool AddTransitTunnel (std::shared_ptr<TransitTunnel> tunnel);
void AddOutboundTunnel (std::shared_ptr<OutboundTunnel> newTunnel);
void AddInboundTunnel (std::shared_ptr<InboundTunnel> newTunnel);
std::shared_ptr<InboundTunnel> CreateInboundTunnel (std::shared_ptr<TunnelConfig> config, std::shared_ptr<TunnelPool> pool, std::shared_ptr<OutboundTunnel> outboundTunnel);
@@ -263,8 +270,19 @@ namespace tunnel
i2p::util::MemoryPoolMt<I2NPMessageBuffer<I2NP_TUNNEL_ENPOINT_MESSAGE_SIZE> > m_I2NPTunnelEndpointMessagesMemoryPool;
i2p::util::MemoryPoolMt<I2NPMessageBuffer<I2NP_TUNNEL_MESSAGE_SIZE> > m_I2NPTunnelMessagesMemoryPool;
// some stats
int m_NumSuccesiveTunnelCreations, m_NumFailedTunnelCreations;
// Calculating of tunnel creation success rate
// A modified version of the EWMA algorithm, where alpha is increased at the beginning to accelerate similarity
void SuccesiveTunnelCreation() {
double alpha = TCSR_SMOOTHING_CONSTANT + (1 - TCSR_SMOOTHING_CONSTANT)/++m_TunnelCreationAttemptsNum;
m_TunnelCreationSuccessRate = alpha * 1 + (1 - alpha) * m_TunnelCreationSuccessRate;
};
void FailedTunnelCreation() {
double alpha = TCSR_SMOOTHING_CONSTANT + (1 - TCSR_SMOOTHING_CONSTANT)/++m_TunnelCreationAttemptsNum;
m_TunnelCreationSuccessRate = alpha * 0 + (1 - alpha) * m_TunnelCreationSuccessRate;
};
double m_TunnelCreationSuccessRate;
int m_TunnelCreationAttemptsNum;
public:
@@ -278,11 +296,7 @@ namespace tunnel
size_t CountOutboundTunnels() const;
int GetQueueSize () { return m_Queue.GetSize (); };
int GetTunnelCreationSuccessRate () const // in percents
{
int totalNum = m_NumSuccesiveTunnelCreations + m_NumFailedTunnelCreations;
return totalNum ? m_NumSuccesiveTunnelCreations*100/totalNum : 0;
}
int GetTunnelCreationSuccessRate () const { return std::round(m_TunnelCreationSuccessRate * 100); } // in percents
};
extern Tunnels tunnels;

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2020, The PurpleI2P Project
* Copyright (c) 2013-2022, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -41,7 +41,7 @@ namespace tunnel
{
public:
TunnelBase (uint32_t tunnelID, uint32_t nextTunnelID, i2p::data::IdentHash nextIdent):
TunnelBase (uint32_t tunnelID, uint32_t nextTunnelID, const i2p::data::IdentHash& nextIdent):
m_TunnelID (tunnelID), m_NextTunnelID (nextTunnelID), m_NextIdent (nextIdent),
m_CreationTime (i2p::util::GetSecondsSinceEpoch ()) {};
virtual ~TunnelBase () {};

View File

@@ -76,12 +76,12 @@ namespace tunnel
if (m_NumInboundHops > size)
{
m_NumInboundHops = size;
LogPrint (eLogInfo, "Tunnels: Inbound tunnel length has beed adjusted to ", size, " for explicit peers");
LogPrint (eLogInfo, "Tunnels: Inbound tunnel length has been adjusted to ", size, " for explicit peers");
}
if (m_NumOutboundHops > size)
{
m_NumOutboundHops = size;
LogPrint (eLogInfo, "Tunnels: Outbound tunnel length has beed adjusted to ", size, " for explicit peers");
LogPrint (eLogInfo, "Tunnels: Outbound tunnel length has been adjusted to ", size, " for explicit peers");
}
m_NumInboundTunnels = 1;
m_NumOutboundTunnels = 1;
@@ -282,8 +282,13 @@ namespace tunnel
for (const auto& it : m_OutboundTunnels)
if (it->IsEstablished ()) num++;
}
for (int i = num; i < m_NumOutboundTunnels; i++)
CreateOutboundTunnel ();
num = m_NumOutboundTunnels - num;
if (num > 0)
{
if (num > TUNNEL_POOL_MAX_NUM_BUILD_REQUESTS) num = TUNNEL_POOL_MAX_NUM_BUILD_REQUESTS;
for (int i = 0; i < num; i++)
CreateOutboundTunnel ();
}
num = 0;
{
@@ -300,8 +305,13 @@ namespace tunnel
if (num >= m_NumInboundTunnels) break;
}
}
for (int i = num; i < m_NumInboundTunnels; i++)
CreateInboundTunnel ();
num = m_NumInboundTunnels - num;
if (num > 0)
{
if (num > TUNNEL_POOL_MAX_NUM_BUILD_REQUESTS) num = TUNNEL_POOL_MAX_NUM_BUILD_REQUESTS;
for (int i = 0; i < num; i++)
CreateInboundTunnel ();
}
if (num < m_NumInboundTunnels && m_NumInboundHops <= 0 && m_LocalDestination) // zero hops IB
m_LocalDestination->SetLeaseSetUpdated (); // update LeaseSet immediately

View File

@@ -30,6 +30,7 @@ namespace tunnel
const int TUNNEL_POOL_MANAGE_INTERVAL = 10; // in seconds
const int TUNNEL_POOL_MAX_INBOUND_TUNNELS_QUANTITY = 16;
const int TUNNEL_POOL_MAX_OUTBOUND_TUNNELS_QUANTITY = 16;
const int TUNNEL_POOL_MAX_NUM_BUILD_REQUESTS = 2;
class Tunnel;
class InboundTunnel;

Some files were not shown because too many files have changed in this diff Show More