Compare commits

...

286 Commits

Author SHA1 Message Date
orignal 0b97b4294c don't request relay tag for every session if we have enough introducers 14 hours ago
orignal 4178ac8eac select newest introducers to publish 17 hours ago
orignal 6a590bf970 use std::mt19937 instead rand() 1 day ago
orignal 2f847d62bb fixed typo 2 days ago
orignal df6d48dbae request only not found routers from introducers 2 days ago
orignal bacce7dc60 fixed introducer's index 3 days ago
orignal b3314380cc don't use expired introducer even if we a session with it 3 days ago
orignal d4eea61b82 use mt19937 instead rand 4 days ago
orignal a1995c13cd flood to 2 next day closest floodfills before UTC midnight 5 days ago
orignal bc8adf1433 move unsent I2NP messages to new session if replaced 1 week ago
orignal a1322d4667 move unsent I2NP messages to new session if replaced 1 week ago
orignal 4100249313 removed bootstrap from floodfill. Removed requested destinations mutex 1 week ago
orignal acbd3f897b fixed race condition between local buffer creation and sending it through the transports 2 weeks ago
orignal 7dc5a04b8d update timestamp for non-reachable router 2 weeks ago
orignal 03635f4444 publish through tunnels in case of restricted routes 2 weeks ago
orignal 0fae04f96a update local RouterInfo timestamp by timer even in hidden mode 2 weeks ago
orignal bb531a878d request newly discovered routers with random intervals after exploratory 2 weeks ago
orignal 0f7db8e418 list of request callbacks 2 weeks ago
orignal 9a724b2af9 separate timer for netdb requests cleanup 2 weeks ago
orignal f4ea6138e8 removed non longer used mutex 2 weeks ago
orignal e74272781f moved exploratory to netdb requests thread 3 weeks ago
orignal b75e418879 request destination in netdb requests thread 3 weeks ago
orignal 927123188c handle onDrop for request message in nedb requests thread 3 weeks ago
orignal c00eb8cf44 handle requests completions in netdb requests thread 3 weeks ago
orignal 265bb8b779 handle DatabaseSearchReply in netdb requests thread 3 weeks ago
orignal e3be409945 moved netdb requests to separate thread 3 weeks ago
orignal d8707ceb57
Merge pull request #2066 from EKCKABATOP54/fix
Fixed checking the bandwidth flag in the config
3 weeks ago
orignal 39e16824b9 reset routing path if duplicated SYN received 3 weeks ago
orignal 285e693a4e fixed deadlock 3 weeks ago
orignal 940628bf36 update LeaseSet if inbond tunnel failed 3 weeks ago
orignal b5994e058a increment num attempts if no reply tunnel specified 4 weeks ago
orignal 22dabfd79e use unordered_map for excluded routers. don't request to self 4 weeks ago
orignal 0e41c3fa36 resend more interval variance 4 weeks ago
orignal 124698854f skip resent recently sessions during resend 4 weeks ago
EKCKABATOP54 f223e668ce Fixed checking the bandwidth flag in the config 4 weeks ago
orignal f5b823a712 common code for sending netdb lookup 4 weeks ago
r4sas 4163542125
[gha] short commit hashes for deb packages
Signed-off-by: r4sas <r4sas@i2pmail.org>
4 weeks ago
r4sas 6921c8391e
[FS] boost 1.85.0 support
Signed-off-by: r4sas <r4sas@i2pmail.org>
4 weeks ago
r4sas 47a2020472
[i18n] update translations
Signed-off-by: r4sas <r4sas@i2pmail.org>
4 weeks ago
orignal 9d38facf3b 2.52.0 4 weeks ago
r4sas a1be1aa9ec
[gha] cache packages on winxp build
Signed-off-by: r4sas <r4sas@i2pmail.org>
4 weeks ago
r4sas c4bbe2bb4a
[win32] fix warning in NetState
Signed-off-by: r4sas <r4sas@i2pmail.org>
1 month ago
r4sas 601695dede [gha] winxp: rebuild boost
Signed-off-by: r4sas <r4sas@i2pmail.org>
1 month ago
r4sas cdc81e19a0 [gha] update winxp build
Signed-off-by: r4sas <r4sas@i2pmail.org>
1 month ago
orignal b98b3a87b0 fixed race codition between RouterInfo's buffer persist and update 1 month ago
orignal 425ef2cfe5 persist routers in separate thread 1 month ago
orignal c454685605 exclude addresses with wrong keys 1 month ago
orignal c98926abf2 use mt19937 instead rand() 1 month ago
orignal 7aacc97351 initialize requests before reseeds 1 month ago
orignal c49e17ad40 use timestamp to reset rng 1 month ago
orignal 296b721929 shared_ptr for NetdbRequests 1 month ago
orignal d0cf385f4b memory pool for requested destination. Reduced request attaempts to 5 1 month ago
orignal b91f5a7430 fixed race condition on stop 1 month ago
orignal 4d8431907d use std::sample for exploratory selection if C++17 1 month ago
orignal b0cf5130a2 don't try another flloofill if request already exists 1 month ago
orignal 396aa6944d cache complete request for a while and not send the same request again 1 month ago
orignal 8a20d3219b don't build exploratory selection on each request 1 month ago
orignal 13a746162a reduce exploratory selection size 1 month ago
orignal 7e5370fbe5 check for excluded routers during exploratory 1 month ago
orignal ec59308fad return up to 16 hash in expolatory reply 1 month ago
orignal 5ed76b997c log initial destination request 1 month ago
orignal 535fbdb4c9 don't check exporatory too often 1 month ago
orignal 034332a0ef changed minimal exploratory interval to 55 seconds. added variance 1 month ago
orignal a1eac6f28e drop unsolicited database serach replies 1 month ago
orignal ba22a940f1 30 seconds timeout for exploratory requests 1 month ago
orignal 8439f6dc57 don't reply with close than us only floodfills for lookup 1 month ago
orignal a21bec0ed8 check if router if real only if tunnel build rate is low and router's profile is presented when handle exploratory request 1 month ago
orignal 5adbc2c3fe don't stop lookup if number of attempts < 3 1 month ago
orignal c515f49903 removed dependency from boost::thread 1 month ago
orignal 845b14f581 removed dependency from boost::thread 1 month ago
orignal cdfdfc9e24 don't manage netdb is transports are not running 1 month ago
orignal 62d279e1b0 don't return routers with 'f' cap to exploratory request 1 month ago
orignal a1fcd8af39 don't accept too old RouterInfo 1 month ago
orignal 8c6c954ea2 enable previously disabled floodfill 1 month ago
orignal 77bb7432bc insert previously ecluded floodfill back when connected 1 month ago
orignal 720ffa8a31 move traverse thrugh profiles files to deleting thread 1 month ago
orignal c1c69258c3 Merge branch 'openssl' of https://github.com/PurpleI2P/i2pd into openssl 1 month ago
orignal 733a4a2869 moved save/delete profile disk oprations to separate threads 1 month ago
r4sas ca3ac8c11d
[gha] update actions
Signed-off-by: r4sas <r4sas@i2pmail.org>
1 month ago
r4sas d4e3991257
[gha] fix osx build
Signed-off-by: r4sas <r4sas@i2pmail.org>
1 month ago
orignal 648a884a18 use copy of excluded peers 1 month ago
orignal 8fe989050e use share_ptr to store peers 1 month ago
orignal bb6212ccc1 fixed typo 1 month ago
orignal 5f39f65540 mutex for request's excluded peers 1 month ago
orignal 146b3f52c0 check if pool is ready before sending next request 1 month ago
orignal cc75ccd070 don't manage requests if exploratory pool is not ready. use monotonic timer and milliseconds 1 month ago
orignal 0ddc514221 don't send next request if requested destination is over 1 month ago
orignal d3b699d7cd
Merge pull request #2059 from Vort/manage_interval
manage netDb requests more frequently
2 months ago
Vort 6592fab41c manage netDb requests more frequently 2 months ago
orignal 02895d4cf5 respond with confirmied router to exploratory lookup 2 months ago
orignal 8b7941c4ce fixed typo 2 months ago
orignal de673464d1 don't try to connect directy by SSU2 to unnconfirmed router if direct NTCP2 is presented 2 months ago
orignal 6ce2c30522 build client tunnels through confimed routers only if low rate 2 months ago
orignal c5a1e8cac8 give preference to direct connection 2 months ago
orignal f67c38d8d2 fixed typo 2 months ago
orignal 1f1a3270f7 fixed IsPublished for ipv6 2 months ago
orignal ffee29272f avoid two firewalled routers in the row 2 months ago
orignal 2d2469c23d
Merge pull request #2052 from nonlin-lin-chaos-order-etc-etal/openssl
Add fcntl to actually lock pidfile on Android
2 months ago
nonlin-lin-chaos-order-etc-etal 8e80a8b06f Add fcntl to actually lock pidfile on Android 2 months ago
orignal 26fac94d05 delete unused varibale 2 months ago
orignal 9a30068ae5 don't compare OBEP hash twice to check if it's a fresh tunnel 2 months ago
orignal 04bccedd9b
Merge pull request #2048 from Vort/stream_resends
Reset stream RTO if outbound tunnel was changed
2 months ago
Vort b2e21a4f12 increase maximum stream resend attempts to 9 2 months ago
Vort 57e46ba0cf reset stream RTO if outbound tunnel was changed 2 months ago
Vort df3dc1f574 change tunnels during stream resend attempts 5 and 6 2 months ago
r4sas 1b5f67e185
[FS] misc: bump year
Signed-off-by: r4sas <r4sas@i2pmail.org>
2 months ago
r4sas 89064b6fb4
2.51.0
Signed-off-by: r4sas <r4sas@i2pmail.org>
2 months ago
orignal c49dd712de 2.51.0 2 months ago
orignal 4f1cb74f75 request choking delay if too many NACKs. Drop window size to 1 if choking delay received 2 months ago
orignal 75df8d3c7b drop ack Through to last packet if Number of NACKs exceeds 255 2 months ago
orignal 85be76b01a check if LeaseSet was submitted recently. Fixed typo 2 months ago
orignal 835c480269
Merge pull request #2047 from Vort/ssu2_lag
select maximum SSU2 queue size depending on RTT value
2 months ago
Vort ac9d92c681 select maximum SSU2 queue size depending on RTT value 3 months ago
r4sas a30d1972e5
[win] update status code
Signed-off-by: r4sas <r4sas@i2pmail.org>
3 months ago
orignal 0c2330bf14
Merge pull request #2045 from Vort/rtt_sample
streaming improvements
3 months ago
Vort a703d31893 don't double initial RTO 3 months ago
Vort 4f8f3a386f restart stream resend timer after updating initial RTO 3 months ago
Vort 83f0b9c041 extract single RTT sample from stream ACK 3 months ago
Vort cf77be0eeb add lower limit for stream RTO 3 months ago
orignal b2aa34baa6 use C++17 for newer versions of clang for BSD 3 months ago
orignal 4def0b6ea5 use C++17 for newer versions of clang for BSD 3 months ago
orignal 25592a00b6 use C++17 for newer versions of clang for BSD 3 months ago
orignal edaf162f9c
Merge pull request #2044 from Vort/stream_resend_rtt
exclude resent stream packets from RTT calculations
3 months ago
Vort e7ff15c573 exclude resent stream packets from RTT calculations 3 months ago
orignal 161ff3579b don't delete new session with same router hash from sessions-by-hash table 3 months ago
orignal f2085ecc8d fixed warning 3 months ago
orignal 0c5dee69ba
Merge pull request #2043 from Vort/rtt_tune
tune RTT calculations
3 months ago
Vort d74033dd2b tune RTT calculations 3 months ago
orignal 5412e29ff5
Merge pull request #2042 from Vort/ssu2_spikes
lower SSU2 resend traffic spikes
3 months ago
Vort 0236769134 lower SSU2 resend traffic spikes 3 months ago
orignal 530a078535 don't request temination if session was not established 3 months ago
orignal fbca27fe73
Merge pull request #2041 from Vort/i2np_typo
fix typo in message expiration check
3 months ago
Vort 08cc256c54 fix typo in message expiration check 3 months ago
orignal 6432963294
Merge pull request #2037 from vovasty/fs_ios_simulator_fix
fix HashedStorage::Init exceptions in ios simulator.
3 months ago
Vlad Solomenchuk 59beb5e4e4 fix TARGET_OS_SIMULATOR check 3 months ago
orignal 217aa0c882 fixed #2038. don't add comma for missing param 3 months ago
orignal e889dc1508
Merge pull request #2039 from Vort/ssu2_expiration
add expiration for messages in SSU2 send queue
3 months ago
Vort 2d06c0cbe6 add expiration for messages in SSU2 send queue 3 months ago
Vlad Solomenchuk 51446f0324 fix FS::HashedStorage::Init exceptions in ios simulator. 3 months ago
orignal 66d0b7aec4 correct publication verification for encrypted LeaseSet 3 months ago
orignal 92b49fb969 clear excluded floodfills after successive publishing 3 months ago
orignal 17dd5c1285 publish encrypted leaset on floodfill closest to store hash 3 months ago
orignal ce97ec1534
Merge pull request #2036 from Vort/stream_ewma2
changes in stream RTT estimation and window size drop percent
3 months ago
Vort 3ceb64db2e 1. Use EWMA for stream RTT estimation;
2. Drop window size by 10% instead of 50% in case of resend.

Change is based on code by onon.
3 months ago
orignal bb702700f7 don't check session for single tag 3 months ago
orignal ff3fec9a00 remove tag immediately after use 3 months ago
orignal 3873e60cbb try to send database store reply directly to IBGW 3 months ago
orignal 2dbf094433 try to send lookup reply directly to IBGW 3 months ago
orignal e85e96bc35
Merge pull request #2035 from Vort/high_latency_fix
fix high latency threshold
3 months ago
Vort 98543af92b fix high latency threshold 3 months ago
orignal af0d853ccd some cleanup 3 months ago
orignal 20a5e19ea1 don't request banned router 3 months ago
orignal f1058410fb don't request banned router 3 months ago
orignal 6ba42a0912 check if established peer test session has the same address type 3 months ago
orignal 1292ec67c0 check if remote router supports peer test 3 months ago
orignal 2f2f14e3a7 try publishing again after 5 seconds if no tunnels in the pool 3 months ago
orignal edd9dd2c39 try to publish again after 5 seconds if destination is not ready 3 months ago
orignal f8722f17c6 pick peer test session only if Charlie's address supports peer testing 3 months ago
orignal 38cc01e13d check own peer test cap for peer test msg 2 3 months ago
orignal 6ca266ff3b reject peer test msg 2 if peer testing is not supported 3 months ago
orignal b9773c88e4 don't set test failed state to expiring tunnels 3 months ago
orignal 3311fe62bb fixed potential race condition with tunnel tests 3 months ago
orignal a284c85153
Merge pull request #2033 from Vort/ssu2_dynamic_buffer
derive SSU2 socket buffer size from bandwidth limit
3 months ago
Vort e5f75eb61c log would_block error at info level 3 months ago
Vort 89f9bec49a derive SSU2 socket buffer size from bandwidth limit 3 months ago
orignal dbc3952654
Merge pull request #2031 from Vort/buf_size_log
write SSU2 socket buffer sizes to log
3 months ago
Vort f3c052ed0c write SSU2 socket buffer sizes to log 3 months ago
orignal 692f495adc
Merge pull request #2030 from Vort/non_block
enable non-blocking mode for UDP sockets
3 months ago
Vort aa1de7fe94 enable non-blocking mode for UDP sockets 3 months ago
orignal ca45fe73e9 never delete conneted router from netdb 3 months ago
orignal a8af683643 renamed steady to monotonic 3 months ago
orignal b86c83a068 encrypt tunnel tests for ElGamal-only destinations 3 months ago
orignal 6656ef3c8d correct clock for non-encrypted tunnel tests 3 months ago
orignal 6898d04a1d send tunnel test mesaage only if encrypted 3 months ago
orignal 3215125950
Merge pull request #2029 from Vort/tunnel_test_msg
add tunnel test message
3 months ago
Vort 3d03732555 add tunnel test message 3 months ago
Vort d6d440ba8a allow 0ms latency for tunnel 3 months ago
orignal 821a76a7c5 flush IBGW if tunnel build request OBEP is on the same router 3 months ago
r4sas 34154596f2
[gha] build only when related to app and runner itself files are changed, disable temporary msvc
Signed-off-by: r4sas <r4sas@i2pmail.org>
4 months ago
orignal a1dce017f4 allow tunnel endpoint to send tunnel build reply to itself 4 months ago
orignal 78af34237e reduce session termination timeout 4 months ago
orignal 8874ea8033 fixed typo 4 months ago
orignal 6bd1ee36f7 fixed typo 4 months ago
orignal f07c4bd1dd drop transit tunnel if next ident is ours 4 months ago
orignal 8524a67895 fail last tunnel if tunnel quantity is 1 4 months ago
orignal 43d880752e Merge branch 'openssl' of https://github.com/PurpleI2P/i2pd into openssl 4 months ago
orignal d4246edb82 don't fail last tunnel 4 months ago
orignal ee8449fa05
Merge pull request #2024 from Vort/congestion_zero_check
add zero check to congestion level calculations
4 months ago
Vort 5415598f60 add zero check to congestion level calculations 4 months ago
orignal 7d73c304b5 Extend transit tunnels limit to 4 bytes. Bump default value to 10K 4 months ago
orignal d25206abce encrypted tunnel test messages 4 months ago
r4sas 5d7c6fb0b3
[gha] msvc: copy openssl libraries to fix build
Signed-off-by: r4sas <r4sas@i2pmail.org>
4 months ago
orignal 36a060d50f Consider 'M' routers as low bandwidth 4 months ago
orignal 577ed56af0 store HTTP and SOCKS proxy as pointer to I2PService 4 months ago
orignal 695dc96a83 common ServiceAcceptor for all stream protocols 4 months ago
orignal e5251bf3c3
Merge pull request #2022 from Vort/bw_time_jumps3
skip bandwidth updates in case of time going backwards
4 months ago
R4SAS 2692aef53d
[gha] update windows msvc action 4 months ago
Vort d524105727 skip bandwidth updates in case of time going backwards 4 months ago
orignal 26463c50fc
Merge pull request #2020 from Vort/select_next_hop2
make more attempts to select not bad hop
4 months ago
Vort b092e712ec make more attempts to select not bad hop 4 months ago
orignal cb8fbb0135
Merge pull request #2018 from Vort/congestion_consts
add constants for congestion levels
4 months ago
Vort 19e23b34da add constants for congestion levels 4 months ago
orignal a4a3f8e96b support upstream proxy through local sockets 4 months ago
orignal f2b720617c
Merge pull request #2016 from Vort/medium_congestion2
implement medium congestion indication
4 months ago
Vort d677d67676 implement medium congestion indication 4 months ago
orignal 7e3157b162 don't process packet in terminated stream 4 months ago
orignal 2b6a95cbee don't check session for symmetric key tagset. re-create tags hash if too many used tags 4 months ago
orignal 900153765a move router's tags cleanup to router's thread 4 months ago
orignal 441e847de8 don't try to decrypt dulpicate message 4 months ago
orignal 6439e227f6 consider test failed state as established. Delete failed tunnels sooner 4 months ago
orignal def404b61a skip failed and expiring tunnels for peer tests 4 months ago
orignal d8be5b8ce1 fixed warning 4 months ago
orignal dddbca6ffb common rng for random shuffle 4 months ago
orignal 56619caa71 random shuffle of tunnels for peer test pairs 4 months ago
orignal 0e502c49b5 show correct tunnel status. restore tunnel if delivery status or data for inbound tunnel received 4 months ago
orignal 710b27688b generic SocketsPipe for different socket types 4 months ago
orignal a9ad6fc31e renamed TCPIPPipe to SocketsPipe 4 months ago
orignal 967627e58a read correct reply length and handle reply codes 4 months ago
orignal 7691a5b4a9 use common SOCK5 code for reseed 4 months ago
orignal d9b6262a6e removed unused field 4 months ago
orignal 075f80aea2 use SOCK5 proxy for upstream 4 months ago
orignal b07530a8a1 don't print error message if operation cancelled 4 months ago
orignal 0ae7931a6f replaced SOCKS4 outproxy by SOCKS5 4 months ago
orignal 158160f5c0 common code for SOCKS5 proxy connectivity 4 months ago
orignal 4cb2ad48be
Merge pull request #2014 from Vort/log_fixes
Logging fixes
4 months ago
Vort adba3987f8 logging fixes 4 months ago
orignal 36dbc15bca keep SSU2 socket open even if failed to bind 4 months ago
orignal d96803a290 always request through tunnels in case of restricted routes 4 months ago
orignal 592d6ae4f4 check log level before calculating base32 or base64 of ident 4 months ago
orignal 7dd9a7a0af added CheckLogLevel 4 months ago
orignal 1b23aa2d7b increase request timeout 4 months ago
orignal f980277552 don't flood failed router 4 months ago
orignal 586695673b correct log message for next netdb request 4 months ago
orignal c158bbe90a send frame when it exceeds 16K 4 months ago
orignal c01fd3299f handle drop of destination publish msg 4 months ago
orignal f64b136f5a remove prestium reseeds 4 months ago
orignal 37e67cbcaa
Merge pull request #2013 from WaxySteelWorm/openssl
reseed.stormycloud.org reseed added
4 months ago
orignal be815804e6 expire transit tunnel is not sent further 4 months ago
orignal ce35637866 handle drop of tunnel build message 4 months ago
StormyCloudInc de2b0f6e09
Update Config.cpp 4 months ago
StormyCloudInc 7b776666a3
Add files via upload 4 months ago
orignal 47578b69c6 handle drop of tunnel test message 4 months ago
orignal 8f28cee32f drop earlier if delayed queue is semi-full 4 months ago
orignal 96cf6ca531 drop earlier if outgoing queue is semi-full 4 months ago
orignal 83cb3a1820 reduce router unreachable interval 4 months ago
orignal ffdd5935e9 Handle drop of own RouterInfo publishing message 4 months ago
orignal 2e9f2d4a3b Drop for LeaseSet request 4 months ago
orignal 0ca782ed71 drop unsent messages if session disconnects 4 months ago
orignal e40b656ecf Drop for tunnel and encrypted messages 4 months ago
orignal 85f5f5b91e fixed potential deadlock 4 months ago
orignal f008478505 handle I2NP messages drops 4 months ago
orignal d926a31064 fixed warning 4 months ago
orignal 822cb35efe limit send Ack timeout by Ack delay interval 4 months ago
orignal faaa8115d9
Merge pull request #2011 from Vort/net_status
log changes of network status
4 months ago
Vort 81015a5228 log changes of network status 4 months ago
orignal a3246cd9dc doen't send Ack packet too often if missing packets 4 months ago
orignal 530c353b00 don't send Ack with NACK immediately but after 2 milliseconds 4 months ago
orignal 75c2cb751f lock mutex before deleting RouterInfo's buffer 5 months ago
orignal 67c4d4bcaa fixed VS build error 5 months ago
orignal 25e82105b2
Merge pull request #2010 from wekoq/udp-tunnels-dest
Add support for multiple udp server tunnels on one destionation
5 months ago
weko 4b167fdbaf Update copyright year 5 months ago
weko 5b93558bd0 Add support for multiple udp server tunnels on one destionation 5 months ago
orignal 70639f1139 don't adjust clock if offsets came from same router 5 months ago
orignal c5a1806528 fixed possible deadlock 5 months ago
orignal a2249f0a82 identify server tunnel session but from ant to ports 5 months ago
orignal 9f217f8a11 don't send expired I2NP messages 5 months ago
orignal 5e19e361e7 check max frame sizebefore sending 5 months ago
orignal 0b47f65b06 don't remove another NTCP2 session with same address 5 months ago
orignal 140146e433 limit mininal received packet size to 40 bytes 5 months ago
R4SAS b4484c8e8f
[gha] msvc: switch back to boost 1.81.0 5 months ago
orignal 1e5604ed43 check if peer is connected before trying to connect 5 months ago
orignal 39e378a03d check received data size 5 months ago
orignal 7cfcb12c7b don't create peer for unreachable router 5 months ago
orignal 49f4dc53ad try next floodfill for router request on demand 5 months ago
orignal 4afdca090d support multiple RouterInfo request callbacks 5 months ago
orignal d8f6c4a93d correct encryption and path for follow on lookup request 5 months ago
orignal d724948d03
Merge pull request #2007 from Vort/reservedrange2
handle 'reservedrange' param properly
5 months ago
Vort 34d75b08dd handle 'reservedrange' param properly 5 months ago
orignal ca9782dd0d handle 'reservedrange' param properly 5 months ago
orignal 2ad26dd4c9 fixed race condition in ECIESx25519 tags table 5 months ago
orignal 828facab57
Merge pull request #2003 from rex4539/typos
Fix typos
5 months ago
orignal cd087568b5 reply with CANT_REACH_PEER if connect to outselves 5 months ago
R4SAS dfe8b25e5e
[gha] MSVC: remove deletion of installation files 5 months ago
R4SAS 1e9bcd6b8b
[gha] MSVC: Switch to direct Boost and OpenSSL installation 5 months ago
orignal 8bc58daa5a fixed #2004. Check supported crypto 5 months ago
Dimitris Apostolou 577c71b930
Fix typos 5 months ago

@ -1,6 +1,24 @@
name: Build Debian packages
on: [push, pull_request]
on:
push:
branches:
- '*'
paths:
- .github/workflows/build-deb.yml
- contrib/**
- daemon/**
- debian/**
- i18n/**
- libi2pd/**
- libi2pd_client/**
- Makefile
- Makefile.linux
tags:
- '*'
pull_request:
branches:
- '*'
jobs:
build:
@ -14,26 +32,30 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Commit Hash
id: commit
uses: prompt/actions-commit-hash@v3.0.0
- name: Build package
uses: jtdor/build-deb-action@v1
with:
docker-image: debian:${{ matrix.dist }}-slim
buildpackage-opts: --build=binary --no-sign
before-build-hook: debchange --controlmaint --local "+${{ github.sha }}~${{ matrix.dist }}" -b --distribution ${{ matrix.dist }} "CI build"
before-build-hook: debchange --controlmaint --local "+${{ steps.commit.outputs.short }}~${{ matrix.dist }}" -b --distribution ${{ matrix.dist }} "CI build"
extra-build-deps: devscripts git
- name: Upload package
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: i2pd_${{ matrix.dist }}
path: debian/artifacts/i2pd_*.deb
- name: Upload debugging symbols
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: i2pd-dbgsym_${{ matrix.dist }}
path: debian/artifacts/i2pd-dbgsym_*.deb

@ -1,6 +1,24 @@
name: Build on FreeBSD
on: [push, pull_request]
on:
push:
branches:
- '*'
paths:
- .github/workflows/build-freebsd.yml
- build/CMakeLists.txt
- build/cmake_modules/**
- daemon/**
- i18n/**
- libi2pd/**
- libi2pd_client/**
- Makefile
- Makefile.bsd
tags:
- '*'
pull_request:
branches:
- '*'
jobs:
build:
@ -9,7 +27,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Test in FreeBSD
id: test
@ -26,7 +44,7 @@ jobs:
gmake -j2
- name: Upload artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: i2pd-freebsd
path: build/i2pd

@ -1,6 +1,22 @@
name: Build on OSX
on: [push, pull_request]
on:
push:
branches:
- '*'
paths:
- .github/workflows/build-osx.yml
- daemon/**
- i18n/**
- libi2pd/**
- libi2pd_client/**
- Makefile
- Makefile.homebrew
tags:
- '*'
pull_request:
branches:
- '*'
jobs:
build:
@ -14,13 +30,16 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: install packages
- name: Install required formulae
run: |
find /usr/local/bin -lname '*/Library/Frameworks/Python.framework/*' -delete
brew update
brew install boost miniupnpc openssl@1.1
- name: build application
- name: List installed formulae
run: brew list
- name: Build application
run: make HOMEBREW=1 USE_UPNP=${{ matrix.with_upnp }} PREFIX=$GITHUB_WORKSPACE/output -j3

@ -1,52 +0,0 @@
name: Build on Windows with MSVC
on: [push, pull_request]
jobs:
build:
name: Build
runs-on: windows-latest
strategy:
fail-fast: false
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Build and install zlib
run: |
powershell -Command "(Invoke-WebRequest -Uri https://raw.githubusercontent.com/r4sas/zlib.install/master/install.bat -OutFile install_zlib.bat)"
powershell -Command "(Get-Content install_zlib.bat) | Set-Content install_zlib.bat" # fixing line endings
set BUILD_TYPE=Debug
./install_zlib.bat
set BUILD_TYPE=Release
./install_zlib.bat
del install_zlib.bat
- name: Install Boost
uses: crazy-max/ghaction-chocolatey@v2
with:
args: install boost-msvc-14.3 --version=1.81.0
- name: Install OpenSSL
uses: crazy-max/ghaction-chocolatey@v2
with:
args: install openssl
- name: Configure
working-directory: build
run: cmake -DWITH_STATIC=ON .
- name: Build
working-directory: build
run: cmake --build . --config Debug -- -m
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: i2pd-msvc
path: build/Debug/i2pd.*

@ -0,0 +1,80 @@
name: Build on Windows with MSVC
on:
push:
branches:
- '*'
paths:
- .github/workflows/build-windows-msvc.yml
- build/CMakeLists.txt
- build/cmake_modules/**
- daemon/**
- i18n/**
- libi2pd/**
- libi2pd_client/**
- Win32/**
tags:
- '*'
pull_request:
branches:
- '*'
jobs:
build:
name: Build
runs-on: windows-latest
env:
boost_path: ${{ github.workspace }}\boost_1_83_0
openssl_path: ${{ github.workspace }}\openssl_3_2_1
strategy:
fail-fast: false
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Build and install zlib
run: |
powershell -Command "(Invoke-WebRequest -Uri https://raw.githubusercontent.com/r4sas/zlib.install/master/install.bat -OutFile install_zlib.bat)"
powershell -Command "(Get-Content install_zlib.bat) | Set-Content install_zlib.bat" # fixing line endings
set BUILD_TYPE=Debug
./install_zlib.bat
set BUILD_TYPE=Release
./install_zlib.bat
del install_zlib.bat
- name: Install Boost
run: |
powershell -Command "(Start-BitsTransfer -Source https://sourceforge.net/projects/boost/files/boost-binaries/1.83.0/boost_1_83_0-msvc-14.3-64.exe/download -Destination boost_1_83_0-msvc-14.3-64.exe)"
./boost_1_83_0-msvc-14.3-64.exe /DIR="${{env.boost_path}}" /VERYSILENT /SUPPRESSMSGBOXES /SP-
- name: Install OpenSSL
run: |
powershell -Command "(Start-BitsTransfer -Source https://slproweb.com/download/Win64OpenSSL-3_2_1.exe -Destination Win64OpenSSL-3_2_1.exe)"
./Win64OpenSSL-3_2_1.exe /DIR="${{env.openssl_path}}" /TASKS="copytobin" /VERYSILENT /SUPPRESSMSGBOXES /NORESTART /SP-
- name: Make copy of the OpenSSL libraries for CMake
run: |
dir ${{ github.workspace }}
dir ${{env.openssl_path}}\lib\VC
dir ${{env.openssl_path}}\lib\VC\x64\
dir ${{env.openssl_path}}\lib\VC\x64\MTd\
xcopy /s /y "${{env.openssl_path}}\lib\VC\x64\MTd" "${{env.openssl_path}}\lib"
- name: Configure
working-directory: build
run: cmake -DBoost_ROOT="${{env.boost_path}}" -DOPENSSL_ROOT_DIR="${{env.openssl_path}}" -DWITH_STATIC=ON .
- name: Build
working-directory: build
run: cmake --build . --config Debug -- -m
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: i2pd-msvc
path: build/Debug/i2pd.*

@ -1,6 +1,25 @@
name: Build on Windows
on: [push, pull_request]
on:
push:
branches:
- '*'
paths:
- .github/workflows/build-windows.yml
- build/CMakeLists.txt
- build/cmake_modules/**
- daemon/**
- i18n/**
- libi2pd/**
- libi2pd_client/**
- Win32/**
- Makefile
- Makefile.mingw
tags:
- '*'
pull_request:
branches:
- '*'
defaults:
run:
@ -23,7 +42,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: 0
@ -44,7 +63,7 @@ jobs:
make USE_UPNP=yes DEBUG=no USE_GIT_VERSION=yes -j3
- name: Upload artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: i2pd-${{ matrix.arch_short }}.exe
path: i2pd.exe
@ -65,7 +84,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: 0
@ -83,7 +102,7 @@ jobs:
cmake --build . -- -j3
- name: Upload artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: i2pd-cmake-${{ matrix.arch_short }}.exe
path: build/i2pd.exe
@ -97,7 +116,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: 0
@ -106,34 +125,117 @@ jobs:
with:
msystem: MINGW32
install: base-devel git mingw-w64-i686-gcc mingw-w64-i686-boost mingw-w64-i686-openssl mingw-w64-i686-miniupnpc
cache: true
update: true
- name: Build WinXP-capable CRT packages
- name: Clone MinGW packages repository
run: git clone https://github.com/msys2/MINGW-packages
# headers
- name: Get headers package version
id: version-headers
run: |
echo "version=$(pacman -Si mingw-w64-i686-headers-git | grep -Po '^Version\s*: \K.+')" >> $GITHUB_OUTPUT
- name: Cache headers package
uses: actions/cache@v4
id: cache-headers
with:
path: MINGW-packages/mingw-w64-headers-git/*.zst
key: winxp-headers-${{ steps.version-headers.outputs.version }}
- name: Build WinXP-capable headers package
if: steps.cache-headers.outputs.cache-hit != 'true'
run: |
git clone https://github.com/msys2/MINGW-packages
pushd MINGW-packages
pushd mingw-w64-headers-git
cd MINGW-packages/mingw-w64-headers-git
sed -i 's/0x601/0x501/' PKGBUILD
MINGW_ARCH=mingw32 makepkg-mingw -sCLf --noconfirm
pacman --noconfirm -U mingw-w64-i686-headers-git-*-any.pkg.tar.zst
popd
pushd mingw-w64-crt-git
MINGW_ARCH=mingw32 makepkg-mingw -sCLf --noconfirm
pacman --noconfirm -U mingw-w64-i686-crt-git-*-any.pkg.tar.zst
popd
pushd mingw-w64-winpthreads-git
MINGW_ARCH=mingw32 makepkg-mingw -sCLf --noconfirm
pacman --noconfirm -U mingw-w64-i686-libwinpthread-git-*-any.pkg.tar.zst mingw-w64-i686-winpthreads-git-*-any.pkg.tar.zst
popd
popd
MINGW_ARCH=mingw32 makepkg-mingw -sCLf --noconfirm --nocheck
- name: Install headers package
run: pacman --noconfirm -U MINGW-packages/mingw-w64-headers-git/mingw-w64-i686-*-any.pkg.tar.zst
# CRT
- name: Get crt package version
id: version-crt
run: |
echo "version=$(pacman -Si mingw-w64-i686-crt-git | grep -Po '^Version\s*: \K.+')" >> $GITHUB_OUTPUT
- name: Cache crt package
uses: actions/cache@v4
id: cache-crt
with:
path: MINGW-packages/mingw-w64-crt-git/*.zst
key: winxp-crt-${{ steps.version-crt.outputs.version }}
- name: Build WinXP-capable crt package
if: steps.cache-crt.outputs.cache-hit != 'true'
run: |
cd MINGW-packages/mingw-w64-crt-git
MINGW_ARCH=mingw32 makepkg-mingw -sCLf --noconfirm --nocheck
- name: Install crt package
run: pacman --noconfirm -U MINGW-packages/mingw-w64-crt-git/mingw-w64-i686-*-any.pkg.tar.zst
# winpthreads
- name: Get winpthreads package version
id: version-winpthreads
run: |
echo "version=$(pacman -Si mingw-w64-i686-winpthreads-git | grep -Po '^Version\s*: \K.+')" >> $GITHUB_OUTPUT
- name: Cache winpthreads package
uses: actions/cache@v4
id: cache-winpthreads
with:
path: MINGW-packages/mingw-w64-winpthreads-git/*.zst
key: winxp-winpthreads-${{ steps.version-winpthreads.outputs.version }}
- name: Build WinXP-capable winpthreads package
if: steps.cache-winpthreads.outputs.cache-hit != 'true'
run: |
cd MINGW-packages/mingw-w64-winpthreads-git
MINGW_ARCH=mingw32 makepkg-mingw -sCLf --noconfirm --nocheck
- name: Install winpthreads package
run: pacman --noconfirm -U MINGW-packages/mingw-w64-winpthreads-git/mingw-w64-i686-*-any.pkg.tar.zst
# OpenSSL
- name: Get openssl package version
id: version-openssl
run: |
echo "version=$(pacman -Si mingw-w64-i686-openssl | grep -Po '^Version\s*: \K.+')" >> $GITHUB_OUTPUT
- name: Cache openssl package
uses: actions/cache@v4
id: cache-openssl
with:
path: MINGW-packages/mingw-w64-openssl/*.zst
key: winxp-openssl-${{ steps.version-openssl.outputs.version }}
- name: Build WinXP-capable openssl package
if: steps.cache-openssl.outputs.cache-hit != 'true'
run: |
cd MINGW-packages/mingw-w64-openssl
gpg --recv-keys D894E2CE8B3D79F5
MINGW_ARCH=mingw32 makepkg-mingw -sCLf --noconfirm --nocheck
- name: Install openssl package
run: pacman --noconfirm -U MINGW-packages/mingw-w64-openssl/mingw-w64-i686-*-any.pkg.tar.zst
# Boost
- name: Get boost package version
id: version-boost
run: |
echo "version=$(pacman -Si mingw-w64-i686-boost | grep -Po '^Version\s*: \K.+')" >> $GITHUB_OUTPUT
- name: Cache boost package
uses: actions/cache@v4
id: cache-boost
with:
path: MINGW-packages/mingw-w64-boost/*.zst
key: winxp-winpthreads-${{ steps.version-boost.outputs.version }}
- name: Build WinXP-capable boost package
if: steps.cache-boost.outputs.cache-hit != 'true'
run: |
cd MINGW-packages/mingw-w64-boost
MINGW_ARCH=mingw32 makepkg-mingw -sCLf --noconfirm --nocheck
- name: Install boost package
run: pacman --noconfirm -U MINGW-packages/mingw-w64-boost/mingw-w64-i686-*-any.pkg.tar.zst
# Building i2pd
- name: Build application
run: |
mkdir -p obj/Win32 obj/libi2pd obj/libi2pd_client obj/daemon
make USE_UPNP=yes DEBUG=no USE_GIT_VERSION=yes USE_WINXP_FLAGS=yes -j3
- name: Upload artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: i2pd-xp.exe
path: i2pd.exe

@ -1,6 +1,24 @@
name: Build on Ubuntu
on: [push, pull_request]
on:
push:
branches:
- '*'
paths:
- .github/workflows/build.yml
- build/CMakeLists.txt
- build/cmake_modules/**
- daemon/**
- i18n/**
- libi2pd/**
- libi2pd_client/**
- Makefile
- Makefile.linux
tags:
- '*'
pull_request:
branches:
- '*'
jobs:
build-make:
@ -14,7 +32,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: install packages
run: |
@ -35,7 +53,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: install packages
run: |

@ -37,29 +37,29 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v3
- name: Login to DockerHub
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GitHub Container registry
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build container for ${{ matrix.archname }}
uses: docker/build-push-action@v3
uses: docker/build-push-action@v5
with:
context: ./contrib/docker
file: ./contrib/docker/Dockerfile
@ -82,22 +82,22 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v3
- name: Login to DockerHub
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GitHub Container registry
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}

@ -1,6 +1,92 @@
# for this file format description,
# see https://github.com/olivierlacan/keep-a-changelog
## [2.52.0] - 2024-05-12
### Added
- Separate threads for persisting RouterInfos and profiles to disk
- Give preference to address with direct connection
- Exclude addresses with incorrect static or intro key
- Avoid two firewalled routers in the row in tunnel
- Drop unsolicited database search replies
### Changed
- Increase number of hashes to 16 in exploratory lookup reply
- Reduce number of a RouterInfo lookup attempts to 5
- Reset stream RTO if outbound tunnel was changed
- Insert previously excluded floodfill back when successfully connected
- Increase maximum stream resend attempts to 9
- Reply to exploratory lookups with only confirmed routers if low tunnel build rate
- Don't accept too old RouterInfo
- Build client tunnels through confirmed routers only if low tunnel build rate
- Manage netDb requests more frequently
- Don't reply with closer than us only floodfills for lookup
### Fixed
- Crash on router lookup if exploratory pool is not ready
- Race condition in excluded peers for next lookup
- Excessive number of lookups for same destination
- Race condition with transport peers during shutdown
- Corrupted RouterInfo files
## [2.51.0] - 2024-04-06
### Added
- Non-blocking mode for UDP sockets
- Set SSU2 socket buffer size based on bandwidth limit
- Encrypted tunnel tests
- Support for multiple UDP server tunnels on one destination
- Publish medium congestion indication
- Local domain sockets for SOCKS proxy upstream
- Tunnel status "declined" in web console
- SAM error reply "Incompatible crypto" if remote destination has incompatible crypto
- Reduce amount of traffic by handling local message drops
- Keep SSU2 socket open even if it fails to bind
- Lower SSU2 resend traffic spikes
- Expiration for messages in SSU2 send queue
- Use EWMA for stream RTT estimation
- Request choking delay if too many NACKs in stream
- Allow 0ms latency for tunnel
- Randomize tunnels selection for tests
### Changed
- Upstream SOCKS proxy from SOCKS4 to SOCKS5
- Transit tunnels limit to 4 bytes. Default value to 10K
- Reply CANT_REACH_PEER if connect to ourselves in SAM
- Don't send already expired I2NP messages
- Use monotonic timer to measure tunnel test latency
- Standard NTCP2 frame doesn't exceed 16K
- Always send request through tunnels in case of restricted routes
- Don't delete connected routers from NetDb
- Send lookup reply directly to reply tunnel gateway if possible
- Reduce unreachable router ban interval to 8 minutes
- Don't request banned routers / don't try to connect to unreachable router
- Consider 'M' routers as low bandwidth
- Limit minimal received SSU2 packet size to 40 bytes
- Bob picks peer test session only if Charlie's address supports peer testing
- Reject peer test msg 2 if peer testing is not supported
- Don't request termination if SSU2 session was not established
- Set maximum SSU2 queue size depending on RTT value
- New streaming RTT calculation algorithm
- Don't double initial RTO for streams when changing tunnels
- Restore failed tunnel if test or data for inbound tunnel received
- Don't fail last remaining tunnel in pool
- Publish LeasetSet again if local destination was not ready or no tunnels
- Make more attempts to pick high bandwidth hop for client tunnel
- Reduced SSU2 session termination timeout to 165 seconds
- Reseeds list
### Fixed
- ECIESx25519 symmetric key tagset early expiration
- Encrypted LeaseSet lookup
- Outbound tunnel build fails if it's endpoint is the same as reply tunnel gateway
- I2PControl RouterManager returns invalid JSON when unknown params are passed
- Mix of data between different UDP sessions on the same server
- TARGET_OS_SIMULATOR check
- Handling of "reservedrange" param
- New NTCP2 session gets teminated upon termination of old one
- New SSU2 session gets teminated upon termination of old one
- Peer test to non-supporting router
- Streaming ackThrough off 1 if number of NACKs exceeds 255
- Race condition in ECIESx25519 tags table
- Good tunnel becomes failed
- Crash when packet comes to terminated stream
- Stream hangs during LeaseSet update
## [2.50.2] - 2024-01-06
###Fixed
- Crash with OpenSSL 3.2.0

@ -6,7 +6,12 @@ CXXFLAGS ?= ${CXX_DEBUG} -Wall -Wextra -Wno-unused-parameter -pedantic -Wno-misl
## (e.g. -fstack-protector-strong -Wformat -Werror=format-security), we do not want to remove
## -std=c++11. If you want to remove this variable please do so in a way that allows setting
## custom FLAGS to work at build-time.
NEEDED_CXXFLAGS = -std=c++11
CXXVER := $(shell $(CXX) -dumpversion)
ifeq (${CXXVER}, "4.2.1") # older clang always returned 4.2.1
NEEDED_CXXFLAGS = -std=c++11
else # newer versions support C++17
NEEDED_CXXFLAGS = -std=c++17
endif
DEFINES = -D_GLIBCXX_USE_NANOSLEEP=1
INCFLAGS = -I/usr/include/ -I/usr/local/include/
LDFLAGS = ${LD_DEBUG} -Wl,-rpath,/usr/local/lib -L/usr/local/lib

@ -1,41 +1,40 @@
# root directory holding homebrew
BREWROOT = /usr/local
BREWROOT = /opt/homebrew
BOOSTROOT = ${BREWROOT}/opt/boost
SSLROOT = ${BREWROOT}/opt/openssl@1.1
UPNPROOT = ${BREWROOT}/opt/miniupnpc
CXXFLAGS = ${CXX_DEBUG} -Wall -std=c++11 -DMAC_OSX -Wno-overloaded-virtual
INCFLAGS = -I${SSLROOT}/include -I${BOOSTROOT}/include
LDFLAGS = ${LD_DEBUG}
ifndef TRAVIS
CXX = clang++
endif
CXXFLAGS ?= ${CXX_DEBUG} -Wall -Wno-overloaded-virtual
NEEDED_CXXFLAGS ?= -std=c++11
INCFLAGS ?= -I${SSLROOT}/include -I${BOOSTROOT}/include
LDFLAGS ?= ${LD_DEBUG}
DEFINES += -DMAC_OSX
ifeq ($(USE_STATIC),yes)
LDLIBS = -lz ${SSLROOT}/lib/libcrypto.a ${SSLROOT}/lib/libssl.a ${BOOSTROOT}/lib/libboost_system.a ${BOOSTROOT}/lib/libboost_date_time.a ${BOOSTROOT}/lib/libboost_filesystem.a ${BOOSTROOT}/lib/libboost_program_options.a -lpthread
LDLIBS = -lz ${SSLROOT}/lib/libcrypto.a ${SSLROOT}/lib/libssl.a ${BOOSTROOT}/lib/libboost_system.a ${BOOSTROOT}/lib/libboost_date_time.a ${BOOSTROOT}/lib/libboost_filesystem.a ${BOOSTROOT}/lib/libboost_program_options.a
ifeq ($(USE_UPNP),yes)
LDLIBS += ${UPNPROOT}/lib/libminiupnpc.a
endif
LDLIBS += -lpthread -ldl
else
LDFLAGS += -L${SSLROOT}/lib -L${BOOSTROOT}/lib
LDLIBS = -lz -lcrypto -lssl -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread
ifeq ($(USE_UPNP),yes)
LDFLAGS += -L${UPNPROOT}/lib
LDLIBS += -lminiupnpc
endif
endif
ifeq ($(USE_UPNP),yes)
LDFLAGS += -ldl
CXXFLAGS += -DUSE_UPNP
DEFINES += -DUSE_UPNP
INCFLAGS += -I${UPNPROOT}/include
ifeq ($(USE_STATIC),yes)
LDLIBS += ${UPNPROOT}/lib/libminiupnpc.a
else
LDFLAGS += -L${UPNPROOT}/lib
LDLIBS += -lminiupnpc
endif
endif
# OSX Notes
# http://www.hutsby.net/2011/08/macs-with-aes-ni.html
# Seems like all recent Mac's have AES-NI, after firmware upgrade 2.2
# Found no good way to detect it from command line. TODO: Might be some osx sysinfo magic
ifeq ($(USE_AESNI),yes)
CXXFLAGS += -D__AES__ -maes
ifneq (, $(findstring i386, $(SYS))$(findstring i686, $(SYS))$(findstring x86_64, $(SYS))) # only x86-based CPU supports that
NEEDED_CXXFLAGS += -maes
DEFINES += -D__AES__
endif
endif
install: all

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2022, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -145,7 +145,7 @@ namespace win32
s << bytes << " Bytes\n";
}
static void ShowNetworkStatus (std::stringstream& s, RouterStatus status, bool testing)
static void ShowNetworkStatus (std::stringstream& s, RouterStatus status, bool testing, RouterError error)
{
switch (status)
{
@ -158,18 +158,24 @@ namespace win32
};
if (testing)
s << " (Test)";
if (i2p::context.GetError () != eRouterErrorNone)
if (error != eRouterErrorNone)
{
switch (i2p::context.GetError ())
switch (error)
{
case eRouterErrorClockSkew:
s << " - Clock skew";
s << " - " << tr("Clock skew");
break;
case eRouterErrorOffline:
s << " - Offline";
s << " - " << tr("Offline");
break;
case eRouterErrorSymmetricNAT:
s << " - Symmetric NAT";
s << " - " << tr("Symmetric NAT");
break;
case eRouterErrorFullConeNAT:
s << " - " << tr("Full cone NAT");
break;
case eRouterErrorNoDescriptors:
s << " - " << tr("No Descriptors");
break;
default: ;
}
@ -180,11 +186,11 @@ namespace win32
{
s << "\n";
s << "Status: ";
ShowNetworkStatus (s, i2p::context.GetStatus (), i2p::context.GetTesting ());
ShowNetworkStatus (s, i2p::context.GetStatus (), i2p::context.GetTesting(), i2p::context.GetError ());
if (i2p::context.SupportsV6 ())
{
s << " / ";
ShowNetworkStatus (s, i2p::context.GetStatusV6 (), i2p::context.GetTestingV6 ());
ShowNetworkStatus (s, i2p::context.GetStatusV6 (), i2p::context.GetTestingV6(), i2p::context.GetErrorV6 ());
}
s << "; ";
s << "Success Rate: " << i2p::tunnel::tunnels.GetTunnelCreationSuccessRate() << "%\n";

@ -73,16 +73,24 @@ void UnSubscribeFromEvents()
}
if (pNetEvent)
{
pNetEvent->Release();
}
if (pCPContainer)
{
pCPContainer->Release();
}
if (pNetworkListManager)
{
pNetworkListManager->Release();
}
if (pUnknown)
{
pUnknown->Release();
}
CoUninitialize();
}

@ -15,10 +15,11 @@
#include "Log.h"
#include "Transports.h"
class CNetworkListManagerEvent : public INetworkListManagerEvents
class CNetworkListManagerEvent final : public INetworkListManagerEvents
{
public:
CNetworkListManagerEvent() : m_ref(1) { }
~CNetworkListManagerEvent() { }
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject)
{

@ -0,0 +1,34 @@
-----BEGIN CERTIFICATE-----
MIIF1zCCA7+gAwIBAgIRAMDqFR09Xuj8ZUu+oetSvAEwDQYJKoZIhvcNAQELBQAw
dTELMAkGA1UEBhMCWFgxCzAJBgNVBAcTAlhYMQswCQYDVQQJEwJYWDEeMBwGA1UE
ChMVSTJQIEFub255bW91cyBOZXR3b3JrMQwwCgYDVQQLEwNJMlAxHjAcBgNVBAMM
FWFkbWluQHN0b3JteWNsb3VkLm9yZzAeFw0yNDAxMjUxNDE1MzBaFw0zNDAxMjUx
NDE1MzBaMHUxCzAJBgNVBAYTAlhYMQswCQYDVQQHEwJYWDELMAkGA1UECRMCWFgx
HjAcBgNVBAoTFUkyUCBBbm9ueW1vdXMgTmV0d29yazEMMAoGA1UECxMDSTJQMR4w
HAYDVQQDDBVhZG1pbkBzdG9ybXljbG91ZC5vcmcwggIiMA0GCSqGSIb3DQEBAQUA
A4ICDwAwggIKAoICAQDbGX+GikPzQXr9zvkrhfO9g0l49KHLNQhUKYqd6T+PfnGo
Fm0d3ZZVVQZ045vWgroOXDGGZZWxUIlb2inRaR2DF1TxN3pPYt59RgY9ZQ9+TL7o
isY91krCRygY8EcAmHIjlfZQ9dBVcL7CfyT0MYZA5Efee9+NDHSewTfQP9T2faIE
83Fcyd93a2mIHYjKUbJnojng/wgsy8srbsEuuTok4MIQmDj+B5nz+za2FgI0/ydh
srlMt4aGJF4/DIem9z9d0zBCOkwrmtFIzjNF1mOSA8ES4m5YnKA/y9rZlRidLPGu
prbXhPVnqHeOnHMz2QCw1wbVo504kl0bMqyEz2tVWsO9ep7iZoQs2xkFAEaegYNT
QLUpwVGlyuq3wXXwopFRffOSimGSazICwWI6j+K0pOtgefNJaWrqKYvtkj1SbK2L
LBNUIENz6VnB7KPRckuX6zxC8PpOiBK9BcftfO+xAz/wC6qq3riBPw30KKSym0nC
Zp5KciDn4Phtw9PGq8Bkl8SyWl0jtFnfTB1tzJkisf2qKcNHaFTEe2JW763YLbh/
AU+8X8evFu40qLgvOgKoyy5DLy6i8zetX+3t9K0Fxt9+Vzzq6lm5V/RS8iIPPn+M
q1/3Z5kD0KQBG9h/Gl8BH+lB71ZxPAOZ3SMu8DJZcxBLVmDWqQPCr5CKnoz0swID
AQABo2IwYDAOBgNVHQ8BAf8EBAMCAoQwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsG
AQUFBwMBMA8GA1UdEwEB/wQFMAMBAf8wHgYDVR0OBBcEFWFkbWluQHN0b3JteWNs
b3VkLm9yZzANBgkqhkiG9w0BAQsFAAOCAgEARWOJ69vTHMneSXYscha+4Ytjg0RM
faewJNEGj8qy/Qvh9si2bWYNPRK6BlbHFS7pRYBLAnhaeLBGVv1CCR6GUMMe74zQ
UuMeAoWU6qMDmB3GfYoZJh8sIxpwHqyJeTdeccRbZ4sX4F6u3IHPXYiU/AgbYqH7
pYXQg2lCjXZYaDFAlEf5SlYUDOhhXe5kR8Edhlrsu32/JzA1DQK0JjxKCBp+DQmA
ltdOpQtAg03fHP4ssdj7VvjIDl28iIlATwBvHrdNm7T0tYWn6TWhvxbRqvfTxfaH
MvxnPdIJwNP4/9TyQkwjwHb1h+ucho3CnxI/AxspdOvT1ElMhP6Ce6rcS9pk11Rl
x0ChsqpWwDg7KYpg0qZFSKCTBp4zBq9xoMJ6BQcgMfyl736WbsCzFTEyfifp8beg
NxUa/Qk7w7cuSPGyMIKNOmOR7FLlFbtocy8sXVsUQdqnp/edelufdNe39U9uNtY6
yoXI9//Tc6NgOwy2Oyia0slZ5qHRkB7e4USXMRzJ3p4q9eCVKjAJs81Utp7O2U+9
vhbhwWP8CAnNTT1E5WS6EKtfrdqF7wjkV+noPGLDGmrXi01J1fSMAjMfVO+7/LOL
UN+G4ybKWnEhhOO27yidN8Xx6UrCS23DBlPPQAeA74dTsTExiOxf1o1EXzcQiMyO
LAj3/Ojbi1xkWhI=
-----END CERTIFICATE-----

@ -1,33 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIFyDCCA7CgAwIBAgIRAO8lBnTo+hlvglQwug2jHZkwDQYJKoZIhvcNAQELBQAw
cDELMAkGA1UEBhMCWFgxCzAJBgNVBAcTAlhYMQswCQYDVQQJEwJYWDEeMBwGA1UE
ChMVSTJQIEFub255bW91cyBOZXR3b3JrMQwwCgYDVQQLEwNJMlAxGTAXBgNVBAMM
EG51bGxAaTJwbWFpbC5vcmcwHhcNMjMwOTIxMjIzMTM2WhcNMzMwOTIxMjIzMTM2
WjBwMQswCQYDVQQGEwJYWDELMAkGA1UEBxMCWFgxCzAJBgNVBAkTAlhYMR4wHAYD
VQQKExVJMlAgQW5vbnltb3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEZMBcGA1UE
AwwQbnVsbEBpMnBtYWlsLm9yZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
ggIBAMMpAvaHwzuZZ6qelRU4jcgpuAIZFH++F1Te4b1t02pRfnQ0Eeh04VC1JxO0
XjUr1/iszEyvrI4+AdxaobDyRFPylkOLtfec4d2ciDc1cupj6y2vyYhMVN31rrvE
ve7sKoTHJ5Dx+UPGOVZZsSsmK9TXIU23W2bo7k2VnjVBXdWZyNE4twfTYCosDnYA
1HIEaIUFVv+COqw2pktxkMmfUAlnDLeVSfsAzEr37K+x0Xk5hO8m6GWQx0NRjjYp
gyEcFhWAJjAYaF3gUVR9rVVky1OeFhZgxE/KzVrW7uc84ZCMKITEPwT0qqIpsTJp
486YXzuPSc+ef78cKSQf5992l7imySJ24I/5H73HkovGAFGZdwvl6V6Ta5YqO7RR
gVDOL1EIVUnMCqFBCE6RmyZqXBVrv4Cacdc6lZ4fj42SRtWZfe6rNCpJzTRtbOyW
DBmYpK6q/jddfqI1sX0PXIn9U+Rod5Z4uz82PAjhamqyr5fpAnoQxKppBvQ3tNfn
KQhmP73Hdpvl24pRyQLBIRUL86i7TPBBn7n3XZlQfXP7lp8+KJYLkL2/zCVDrwLX
kC9hRIxCU9bZbXlkRE2R/PrK53LZecjk2KcgINA4ZlguNgze/Qj8BXelUF4izbpV
bTSvniTM46AECvjDcICAOky9Ku4RnmUJxQVf3ahDEuso7/N7AgMBAAGjXTBbMA4G
A1UdDwEB/wQEAwIChDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwDwYD
VR0TAQH/BAUwAwEB/zAZBgNVHQ4EEgQQbnVsbEBpMnBtYWlsLm9yZzANBgkqhkiG
9w0BAQsFAAOCAgEAEUfYJTdDH7uCojnpF0Gs2tXxPJ22UhdqEsXfqR7KhhmmApss
q5kiiPIYoy5T/4IM7NVyeeJAMYwQsdJjwZ4QyxLBb9EqMS2krREcPZNRfFzBr2Wj
EBhJEYTnbIn4docwJWyXsJVG0CqFXPF1qGd0Sc2u87yj2xZNTnloWKAEQAO7DE39
gWfDH6slM/3h3WD3Mjuk7JoYSYmBfvvm2hkBbC6lzD7XY7rdSmIUwJ050e9UrJaV
La51dd5r4q8d1cHrVUwLiACAaXJ15AEqUDLHQcvKvyfhkabwRy+v0wsodSMgSMEH
xA+kGhkIW7yV7o2exYOYypHCca3IA+pimMpEseNNrHSwbHOMfauiN7jiZLEPg6D6
a8XwK7qmMYUq7j6QWuIqI81o29WZRf4LZ0GFoVce+e5VxkVKSItKcJoedIAp1ML8
NhFwd9s/nqWidu/StscEEbGzz6ZuDXwshERXC0QR8HjHEPi4U8220juf4cxUahxK
heEU91l7VksSZYRUN98h28vovGcukLcnVoLj5H/+Z4r/BgxMrOUJKetxf8fU7FjO
j1U6XV36tGi+IOwYQb9D5fTVafC3hHkuUIjlOdUGYadse98ILhn9kaNtqkBtk/EU
vK+McnrEv7tcKrbvYEop/KaUayhjFiL+wGWnpxt7gLhIiavnIeUyD7acltw=
-----END CERTIFICATE-----

@ -1,34 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIF7zCCA9egAwIBAgIRANVB/+wEuXS0Ttoh5teJt90wDQYJKoZIhvcNAQELBQAw
fTELMAkGA1UEBhMCWFgxCzAJBgNVBAcTAlhYMQswCQYDVQQJEwJYWDEeMBwGA1UE
ChMVSTJQIEFub255bW91cyBOZXR3b3JrMQwwCgYDVQQLEwNJMlAxJjAkBgNVBAMM
HXJlaGVhdGVkYnVyZ2VyQHByb3Rvbm1haWwuY29tMB4XDTIzMDkyMTE4MDAyOVoX
DTMzMDkyMTE4MDAyOVowfTELMAkGA1UEBhMCWFgxCzAJBgNVBAcTAlhYMQswCQYD
VQQJEwJYWDEeMBwGA1UEChMVSTJQIEFub255bW91cyBOZXR3b3JrMQwwCgYDVQQL
EwNJMlAxJjAkBgNVBAMMHXJlaGVhdGVkYnVyZ2VyQHByb3Rvbm1haWwuY29tMIIC
IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAuNwmiIY3MLSBS5sL5PXRDVK6
MoSNw4qx0o8nDHvVBxNtzgc0/qjYvsuUggY0tZbPpxhML6GHd4qo7Z3Ip1x0MxhI
Ao5MJaflaEdm4+HeMy0IE3aU73KRUwp+nF3cUHZdlps+9mtYs4oncVEWkFQwGsgt
4yrLtXf6PmPWfFH28ffeaev90e+hdhQpTvr54Ewx6NTaMQr8mkhXL2utvPpjnPM5
UAhOeJCMgfhLzgS4rahG0O8CQMtH5gKZ+6zjoSRatnjj0j1mBO7+e1TL5O7dVS9k
P83tmkIDDl4tXBzXr9aXQMJstbM2CEvinVcCsR74GjPcg4iB0Ift71Dx7oGKI06t
3bSvll0GZm2mFhIba/4q6f4oAJ2aeq6ejt1Kcm8g5cxtwrRZnXv5JXHZqba3y8J5
zWaRHzhc9tyEqRBRkc6c7xMdZQ31iJ6TlxUT8vAJ1N7OnX87oHrCjwyikpyOen4r
Uvv1Ge054XPTeoHz+Jyt34t71ty1W13uPHpuvtPVR9MfgGrxd4Z9+LWvAjmMbFsZ
lC3Ll+94nUk+O0puU6KisuCGP4hCtdEtebkIqT8zo8LicLAYUMjX7KwnS7681Cu1
sY2mB2oZAytN9Zy42oOoNeY5x39kxfwuut/2E1kxKX75O0bwfIXr611abCKc3bbz
euMrIsaB/2VFp9nAah8CAwEAAaNqMGgwDgYDVR0PAQH/BAQDAgKEMB0GA1UdJQQW
MBQGCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCYGA1UdDgQf
BB1yZWhlYXRlZGJ1cmdlckBwcm90b25tYWlsLmNvbTANBgkqhkiG9w0BAQsFAAOC
AgEATuHi2Yz52OK7e+sKVdHu2KrSLCGm98BG1UIMHFi3WRBTOFyp+lZ519bJ1rFj
tmP9E1a+k/vlbc7FbV4PcV6HJYfGEv/ImtJsEnrzbhrQphC1zMFv7q6JCTUbAzl6
ySlJ++mVxQ6AzPNH3TQgL1wPKuLh76/Y4053fg+NI3PmzzhkTUheVDkg0/a9ENSf
xMnCa3fIm869735qHk67QlikFvAfWwc4zT1Ncwodh8G4+oX0GFzIl+OZaM1GTMuD
UCcFKoqwtjyLCr22xNk8CfyiExPJXQG1HzEvDcxyoxQtnh9occR9PgqXySz26/NM
XDyM+l4utLMGBcVY4x9fksRiaWEfxiygYOxY9zDl6clh6S10b3CLut4UMiS1RTtE
Mjx2BZN3p0nxpT2leJdGxtBPGrvxuiCOEmTbOMLc3DQtppXO97B3dVMtJ5Ee8Y6p
Tq/8eiHI6eQXat6dgFT5X16vzF7w7XO7fAxuqk4Kx1D1aTVyikdo+Fcdg44dWOjq
NZu8VcCzZij/Dfjlce6t6h8D+wvDD8AkiivaDljpvbNDx/QQlQXFgH98TZA8Rnvr
QcyNNATfz+1yQUiyO6Lrjaw64OJwXYX/llgnDC+qQpP6kqZabi2TsG0EVPukVvr9
0HyAUu4lnXtTIDq2yPNenegCloqDL1ZQdaYd2XIItnfZdTY=
-----END CERTIFICATE-----

@ -1,7 +1,7 @@
%define git_hash %(git rev-parse HEAD | cut -c -7)
Name: i2pd-git
Version: 2.50.2
Version: 2.52.0
Release: git%{git_hash}%{?dist}
Summary: I2P router written in C++
Conflicts: i2pd
@ -144,6 +144,12 @@ getent passwd i2pd >/dev/null || \
%changelog
* Sun May 12 2024 orignal <orignal@i2pmail.org> - 2.52.0
- update to 2.52.0
* Sat Apr 06 2024 orignal <orignal@i2pmail.org> - 2.51.0
- update to 2.51.0
* Sat Jan 06 2024 orignal <orignal@i2pmail.org> - 2.50.2
- update to 2.50.2

@ -1,5 +1,5 @@
Name: i2pd
Version: 2.50.2
Version: 2.52.0
Release: 1%{?dist}
Summary: I2P router written in C++
Conflicts: i2pd-git
@ -142,6 +142,12 @@ getent passwd i2pd >/dev/null || \
%changelog
* Sun May 12 2024 orignal <orignal@i2pmail.org> - 2.52.0
- update to 2.52.0
* Sat Apr 06 2024 orignal <orignal@i2pmail.org> - 2.51.0
- update to 2.51.0
* Sat Jan 06 2024 orignal <orignal@i2pmail.org> - 2.50.2
- update to 2.50.2

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2023, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -160,6 +160,10 @@ namespace util
int netID; i2p::config::GetOption("netid", netID);
i2p::context.SetNetID (netID);
bool checkReserved; i2p::config::GetOption("reservedrange", checkReserved);
i2p::transport::transports.SetCheckReserved(checkReserved);
i2p::context.Init ();
i2p::transport::InitTransports ();
@ -175,7 +179,7 @@ namespace util
bool transit; i2p::config::GetOption("notransit", transit);
i2p::context.SetAcceptsTunnels (!transit);
uint16_t transitTunnels; i2p::config::GetOption("limits.transittunnels", transitTunnels);
uint32_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
i2p::tunnel::tunnels.SetMaxNumTransitTunnels (transitTunnels);
@ -184,7 +188,7 @@ namespace util
std::string bandwidth; i2p::config::GetOption("bandwidth", bandwidth);
if (bandwidth.length () > 0)
{
if (bandwidth[0] >= 'K' && bandwidth[0] <= 'X')
if (bandwidth.length () == 1 && ((bandwidth[0] >= 'K' && bandwidth[0] <= 'P') || bandwidth[0] == 'X' ))
{
i2p::context.SetBandwidth (bandwidth[0]);
LogPrint(eLogInfo, "Daemon: Bandwidth set to ", i2p::context.GetBandwidthLimit (), "KBps");
@ -298,12 +302,10 @@ namespace util
bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2);
bool ssu2; i2p::config::GetOption("ssu2.enabled", ssu2);
bool checkInReserved; i2p::config::GetOption("reservedrange", checkInReserved);
LogPrint(eLogInfo, "Daemon: Starting Transports");
if(!ssu2) LogPrint(eLogInfo, "Daemon: SSU2 disabled");
if(!ntcp2) LogPrint(eLogInfo, "Daemon: NTCP2 disabled");
i2p::transport::transports.SetCheckReserved(checkInReserved);
i2p::transport::transports.Start(ntcp2, ssu2);
if (i2p::transport::transports.IsBoundSSU2() || i2p::transport::transports.IsBoundNTCP2())
LogPrint(eLogInfo, "Daemon: Transports started");

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2023, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -133,23 +133,19 @@ namespace http {
static void ShowTunnelDetails (std::stringstream& s, enum i2p::tunnel::TunnelState eState, bool explr, int bytes)
{
std::string state, stateText;
switch (eState) {
switch (eState)
{
case i2p::tunnel::eTunnelStateBuildReplyReceived :
case i2p::tunnel::eTunnelStatePending : state = "building"; break;
case i2p::tunnel::eTunnelStateBuildFailed :
case i2p::tunnel::eTunnelStateTestFailed :
case i2p::tunnel::eTunnelStateBuildFailed : state = "failed"; stateText = "declined"; break;
case i2p::tunnel::eTunnelStateTestFailed : state = "failed"; stateText = "test failed"; break;
case i2p::tunnel::eTunnelStateFailed : state = "failed"; break;
case i2p::tunnel::eTunnelStateExpiring : state = "expiring"; break;
case i2p::tunnel::eTunnelStateEstablished : state = "established"; break;
default: state = "unknown"; break;
}
if (state == "building") stateText = tr("building");
else if (state == "failed") stateText = tr("failed");
else if (state == "expiring") stateText = tr("expiring");
else if (state == "established") stateText = tr("established");
else stateText = tr("unknown");
if (stateText.empty ()) stateText = tr(state);
s << "<span class=\"tunnel " << state << "\"> " << stateText << ((explr) ? " (" + tr("exploratory") + ")" : "") << "</span>, ";
ShowTraffic(s, bytes);
s << "\r\n";
@ -776,7 +772,7 @@ namespace http {
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 = i2p::tunnel::tunnels.GetMaxNumTransitTunnels ();
uint32_t maxTunnels = i2p::tunnel::tunnels.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";

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2023, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -25,7 +25,7 @@ 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;
const int TRANSIT_TUNNELS_LIMIT = 1000000;
class HTTPConnection: public std::enable_shared_from_this<HTTPConnection>
{

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2022, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -338,10 +338,11 @@ namespace client
{
for (auto it = params.begin (); it != params.end (); it++)
{
if (it != params.begin ()) results << ",";
LogPrint (eLogDebug, "I2PControl: RouterManager request: ", it->first);
auto it1 = m_RouterManagerHandlers.find (it->first);
if (it1 != m_RouterManagerHandlers.end ()) {
if (it1 != m_RouterManagerHandlers.end ())
{
if (it != params.begin ()) results << ",";
(this->*(it1->second))(results);
} else
LogPrint (eLogError, "I2PControl: RouterManager unknown request: ", it->first);

@ -1,10 +1,15 @@
/*
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
* See full license text in LICENSE file at top of project tree
*/
#ifdef USE_UPNP
#include <string>
#include <thread>
#include <boost/thread/thread.hpp>
#include <boost/asio.hpp>
#include "Log.h"
#include "RouterContext.h"
@ -166,11 +171,11 @@ namespace transport
if (address && !address->host.is_v6 () && address->port)
TryPortMapping (address);
}
m_Timer.expires_from_now (boost::posix_time::minutes(20)); // every 20 minutes
m_Timer.expires_from_now (boost::posix_time::minutes(UPNP_PORT_FORWARDING_INTERVAL)); // every 20 minutes
m_Timer.async_wait ([this](const boost::system::error_code& ecode)
{
if (ecode != boost::asio::error::operation_aborted)
PortMapping ();
PortMapping ();
});
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2020, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -28,7 +28,8 @@ namespace i2p
namespace transport
{
const int UPNP_RESPONSE_TIMEOUT = 2000; // in milliseconds
const int UPNP_PORT_FORWARDING_INTERVAL = 20; // in minutes
enum
{
UPNP_IGD_NONE = 0,

@ -162,12 +162,21 @@ namespace i2p
#ifndef ANDROID
if (lockf(pidFH, F_TLOCK, 0) != 0)
#else
struct flock fl;
fl.l_len = 0;
fl.l_type = F_WRLCK;
fl.l_whence = SEEK_SET;
fl.l_start = 0;
if (fcntl(pidFH, F_SETLK, &fl) != 0)
#endif
{
LogPrint(eLogError, "Daemon: Could not lock pid file ", pidfile, ": ", strerror(errno));
std::cerr << "i2pd: Could not lock pid file " << pidfile << ": " << strerror(errno) << std::endl;
return false;
}
#endif
char pid[10];
sprintf(pid, "%d\n", getpid());
ftruncate(pidFH, 0);

16
debian/changelog vendored

@ -1,8 +1,20 @@
i2pd (2.50.2) unstable; urgency=medium
i2pd (2.52.0-1) unstable; urgency=medium
* updated to version 2.52.0
-- orignal <orignal@i2pmail.org> Sun, 12 May 2024 16:00:00 +0000
i2pd (2.51.0-1) unstable; urgency=medium
* updated to version 2.51.0/0.9.62
-- orignal <orignal@i2pmail.org> Sat, 06 Apr 2024 16:00:00 +0000
i2pd (2.50.2-1) unstable; urgency=medium
* updated to version 2.50.2/0.9.61
-- orignal <orignal@i2pmail.org> Sat, 06 Jan 2024 16:00:00 +0000
-- orignal <orignal@i2pmail.org> Sat, 06 Jan 2024 16:00:00 +0000
i2pd (2.50.1-1) unstable; urgency=medium

@ -1,5 +1,5 @@
/*
* Copyright (c) 2022-2023, The PurpleI2P Project
* Copyright (c) 2022-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -69,6 +69,7 @@ namespace chinese // language namespace
{"Stopping in", "距停止还有:"},
{"Family", "家族"},
{"Tunnel creation success rate", "隧道创建成功率"},
{"Total tunnel creation success rate", "当前隧道创建成功率"},
{"Received", "已接收"},
{"%.2f KiB/s", "%.2f KiB/s"},
{"Sent", "已发送"},
@ -95,6 +96,7 @@ namespace chinese // language namespace
{"Address", "地址"},
{"Type", "类型"},
{"EncType", "加密类型"},
{"Expire LeaseSet", "到期租约集"},
{"Inbound tunnels", "入站隧道"},
{"%dms", "%dms"},
{"Outbound tunnels", "出站隧道"},
@ -151,6 +153,8 @@ namespace chinese // language namespace
{"StreamID can't be null", "StreamID 不能为空"},
{"Return to destination page", "返回目标页面"},
{"You will be redirected in %d seconds", "您将在%d秒内被重定向"},
{"LeaseSet expiration time updated", "租约集到期时间已更新"},
{"LeaseSet is not found or already expired", "租约集未找到或已过期"},
{"Transit tunnels count must not exceed %d", "中转隧道数量限制为 %d"},
{"Back to commands list", "返回命令列表"},
{"Register at reg.i2p", "在 reg.i2p 注册域名"},

@ -1,5 +1,5 @@
/*
* Copyright (c) 2022-2023, The PurpleI2P Project
* Copyright (c) 2022-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -36,18 +36,18 @@ namespace czech // language namespace
{"%.2f GiB", "%.2f GiB"},
{"building", "vytváří se"},
{"failed", "selhalo"},
{"expiring", "končící"},
{"expiring", "vyprší platnost"},
{"established", "vytvořeno"},
{"unknown", "neznámý"},
{"exploratory", "průzkumné"},
{"Purple I2P Webconsole", "Purple I2P Webkonsole"},
{"<b>i2pd</b> webconsole", "<b>i2pd</b> webkonsole"},
{"Purple I2P Webconsole", "Purple I2P webová konzole"},
{"<b>i2pd</b> webconsole", "<b>i2pd</b> webová konzole"},
{"Main page", "Hlavní stránka"},
{"Router commands", "Router příkazy"},
{"Local Destinations", "Lokální destinace"},
{"LeaseSets", "LeaseSety"},
{"Local Destinations", "Místní cíle"},
{"LeaseSets", "Sety pronájmu"},
{"Tunnels", "Tunely"},
{"Transit Tunnels", "Transitní tunely"},
{"Transit Tunnels", "Tranzitní tunely"},
{"Transports", "Transporty"},
{"I2P tunnels", "I2P tunely"},
{"SAM sessions", "SAM relace"},
@ -61,18 +61,21 @@ namespace czech // language namespace
{"Clock skew", "Časová nesrovnalost"},
{"Offline", "Offline"},
{"Symmetric NAT", "Symetrický NAT"},
{"Full cone NAT", "Full cone NAT"},
{"No Descriptors", "Žádné popisovače"},
{"Uptime", "Doba provozu"},
{"Network status", "Status sítě"},
{"Network status v6", "Status sítě v6"},
{"Network status", "Stav sítě"},
{"Network status v6", "Stav sítě v6"},
{"Stopping in", "Zastavuji za"},
{"Family", "Rodina"},
{"Tunnel creation success rate", "Úspěšnost vytváření tunelů"},
{"Total tunnel creation success rate", "Celková míra úspěšnosti 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."},
{"Data path", "Cesta k datovým souborům"},
{"Hidden content. Press on text to see.", "Skrytý obsah. Pro zobrazení klikněte sem."},
{"Router Ident", "Routerová Identita"},
{"Router Family", "Rodina routerů"},
{"Router Caps", "Omezení Routerů"},
@ -93,6 +96,7 @@ namespace czech // language namespace
{"Address", "Adresa"},
{"Type", "Typ"},
{"EncType", "EncType"},
{"Expire LeaseSet", "Zrušit platnost setu pronájmu"},
{"Inbound tunnels", "Příchozí tunely"},
{"%dms", "%dms"},
{"Outbound tunnels", "Odchozí tunely"},
@ -103,21 +107,24 @@ namespace czech // language namespace
{"Amount", "Množství"},
{"Incoming Tags", "Příchozí štítky"},
{"Tags sessions", "Relace štítků"},
{"Status", "Status"},
{"Local Destination", "Lokální Destinace"},
{"Status", "Stav"},
{"Local Destination", "Místní cíl"},
{"Streams", "Toky"},
{"Close stream", "Uzavřít tok"},
{"Such destination is not found", "Takováto destinace nebyla nalezena"},
{"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"},
{"Non Expired Leases", "Pronájmy, kterým nevypršela platnost"},
{"Gateway", "Brána"},
{"TunnelID", "ID tunelu"},
{"EndDate", "Datum ukončení"},
{"floodfill mode is disabled", "režim floodfill je vypnut"},
{"Queue size", "Velikost fronty"},
{"Run peer test", "Spustit peer test"},
{"Reload tunnels configuration", "Znovu načíst nastavení tunelů"},
{"Decline transit tunnels", "Odmítnout tranzitní tunely"},
{"Accept transit tunnels", "Přijmout tranzitní tunely"},
{"Cancel graceful shutdown", "Zrušit hladké vypnutí"},
@ -145,14 +152,17 @@ namespace czech // language namespace
{"Destination not found", "Destinace nenalezena"},
{"StreamID can't be null", "StreamID nemůže být null"},
{"Return to destination page", "Zpět na stránku destinací"},
{"Back to commands list", "Zpět na list příkazů"},
{"You will be redirected in %d seconds", "Budete přesměrováni za %d sekund"},
{"LeaseSet expiration time updated", "Aktualizován čas vypršení platnosti setu pronájmu"},
{"LeaseSet is not found or already expired", "Set pronájmu není k nalezení nebo již vypršela jeho platnost"},
{"Transit tunnels count must not exceed %d", "Počet tranzitních tunelů nesmí překročit %d"},
{"Back to commands list", "Zpět na seznam 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"},
@ -162,6 +172,15 @@ namespace czech // language namespace
{"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", "Addresshelper není podporován"},
{"Host %s is <font color=red>already in router's addressbook</font>. <b>Be careful: source of this URL may be harmful!</b> Click here to update record: <a href=\"%s%s%s&update=true\">Continue</a>.", "Hostitel %s je <font color=red>již v adresáři routeru</font>. <b>Buďte opatrní: zdroj této URL může být škodlivý!</b> Klikněte zde pro aktualizaci záznamu: <a href=\"%s%s%s&update=true\">Pokračovat</a>."},
{"Addresshelper forced update rejected", "Addresshelperem vynucená aktualizace zamítnuta"},
{"To add host <b>%s</b> in router's addressbook, click here: <a href=\"%s%s%s\">Continue</a>.", "Pro přidání hostitele <b>%s</b> do adresáře routeru, klikněte zde: <a href=\"%s%s%s\">Pokračovat</a>."},
{"Addresshelper request", "Požadavek Addresshelperu"},
{"Host %s added to router's addressbook from helper. Click here to proceed: <a href=\"%s\">Continue</a>.", "Hostitel %s přidán do adresáře routeru od pomocníka. Klikněte zde pro pokračování: <a href=\"%s\">Pokračovat</a>."},
{"Addresshelper adding", "Addresshelper přidávání"},
{"Host %s is <font color=red>already in router's addressbook</font>. Click here to update record: <a href=\"%s%s%s&update=true\">Continue</a>.", "Hostitel %s je <font color=red>již v adresáři routeru</font>. Klikněte zde pro aktualizaci záznamu: <a href=\"%s%s%s&update=true\">Pokračovat</a>."},
{"Addresshelper update", "Addresshelper aktualizace"},
{"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í"},

@ -1,5 +1,5 @@
/*
* Copyright (c) 2022-2023, The PurpleI2P Project
* Copyright (c) 2022-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -58,7 +58,7 @@ namespace french // language namespace
{"Unknown", "Inconnu"},
{"Proxy", "Proxy"},
{"Mesh", "Maillé"},
{"Clock skew", "Horloge décalée"},
{"Clock skew", "Décalage de l'horloge"},
{"Offline", "Hors ligne"},
{"Symmetric NAT", "NAT symétrique"},
{"Full cone NAT", "NAT à cône complet"},
@ -68,8 +68,8 @@ namespace french // language namespace
{"Network status v6", "État du réseau v6"},
{"Stopping in", "Arrêt dans"},
{"Family", "Famille"},
{"Tunnel creation success rate", "Taux de succès de création de tunnels"},
{"Total tunnel creation success rate", "Taux de réussite de création de tunnel"},
{"Tunnel creation success rate", "Taux de création de tunnel réussie"},
{"Total tunnel creation success rate", "Taux total de création de tunnel réussie"},
{"Received", "Reçu"},
{"%.2f KiB/s", "%.2f Kio/s"},
{"Sent", "Envoyé"},

@ -1,5 +1,5 @@
/*
* Copyright (c) 2023, The PurpleI2P Project
* Copyright (c) 2023-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -31,22 +31,186 @@ namespace polish // language namespace
static std::map<std::string, std::string> strings
{
{"%.2f KiB", "%.2f KiB"},
{"%.2f MiB", "%.2f MiB"},
{"%.2f GiB", "%.2f GiB"},
{"building", "Kompilowanie"},
{"failed", "nieudane"},
{"expiring", "wygasający"},
{"established", "ustanowiony"},
{"unknown", "nieznany"},
{"exploratory", "eksploracyjny"},
{"Purple I2P Webconsole", "Konsola webowa Purple I2P"},
{"<b>i2pd</b> webconsole", "<b>i2pd</b> konsola webowa"},
{"Main page", "Strona główna"},
{"Router commands", "Komendy routera"},
{"Local Destinations", "Lokalne miejsca docelowe"},
{"LeaseSets", "ZestawyNajmu"},
{"Tunnels", "Tunele"},
{"Transit Tunnels", "Tunele Tranzytu"},
{"Transports", "Transportery"},
{"I2P tunnels", "Tunele I2P"},
{"SAM sessions", "Sesje SAM"},
{"ERROR", "BŁĄD"},
{"OK", "Ok"},
{"Testing", "Testowanie"},
{"Firewalled", "Za zaporą sieciową"},
{"Unknown", "Nieznany"},
{"Proxy", "Proxy"},
{"Mesh", "Sieć"},
{"Clock skew", "Przesunięcie czasu"},
{"Offline", "Offline"},
{"Symmetric NAT", "Symetryczny NAT"},
{"Full cone NAT", "Pełny stożek NAT"},
{"No Descriptors", "Brak deskryptorów"},
{"Uptime", "Czas pracy"},
{"Network status", "Stan sieci"},
{"Network status v6", "Stan sieci v6"},
{"Stopping in", "Zatrzymywanie za"},
{"Family", "Rodzina"},
{"Tunnel creation success rate", "Wskaźnik sukcesu tworzenia tunelu"},
{"Total tunnel creation success rate", "Całkowity wskaźnik sukcesu tworzenia tunelu"},
{"Received", "Odebrano"},
{"%.2f KiB/s", "%.2f KiB/s"},
{"Sent", "Wysłane"},
{"Transit", "Tranzyt"},
{"Data path", "Ścieżka do danych"},
{"Hidden content. Press on text to see.", "Ukryta zawartość. Naciśnij tekst, aby zobaczyć."},
{"Router Ident", "Identyfikator routera"},
{"Router Family", "Rodzina routera"},
{"Router Caps", "Możliwości routera"},
{"Version", "Wersja"},
{"Our external address", "Nasz zewnętrzny adres"},
{"supported", "wspierane"},
{"Routers", "Routery"},
{"Floodfills", "Floodfille"},
{"Client Tunnels", "Tunele Klienta"},
{"Services", "Usługi"},
{"Enabled", "Aktywny"},
{"Disabled", "Wyłączony"},
{"Encrypted B33 address", "Zaszyfrowany adres B33"},
{"Address registration line", "Linia rejestracji adresu"},
{"Domain", "Domena"},
{"Generate", "Generuj"},
{"<b>Note:</b> result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", "<b>Uwaga:</b> wynik string może być używany tylko do rejestracji domen 2LD (przykład.i2p). Do rejestracji subdomen należy użyć narzędzi i2pd."},
{"Address", "Adres"},
{"Type", "Typ"},
{"EncType", "TypEnkrypcji"},
{"Expire LeaseSet", "Wygaśnij LeaseSet"},
{"Inbound tunnels", "Tunele przychodzące"},
{"%dms", "%dms"},
{"Outbound tunnels", "Tunele wychodzące"},
{"Tags", "Tagi"},
{"Incoming", "Przychodzące"},
{"Outgoing", "Wychodzące"},
{"Destination", "Miejsce docelowe"},
{"Amount", "Ilość"},
{"Incoming Tags", "Przychodzące tagi"},
{"Tags sessions", "Sesje tagów"},
{"Status", "Status"},
{"Local Destination", "Lokalne miejsce docelowe"},
{"Streams", "Strumienie"},
{"Close stream", "Zamknij strumień"},
{"Such destination is not found", "Nie znaleziono takiego miejsca docelowego"},
{"I2CP session not found", "Sesja I2CP nie została znaleziona"},
{"I2CP is not enabled", "I2CP nie jest włączone"},
{"Invalid", "Niepoprawny"},
{"Store type", "Rodzaj przechowywania"},
{"Expires", "Wygasa za"},
{"Non Expired Leases", "Leasingi niewygasłe"},
{"Gateway", "Brama"},
{"TunnelID", "IDTunelu"},
{"EndDate", "DataZakończenia"},
{"floodfill mode is disabled", "tryb floodfill jest wyłączony"},
{"Queue size", "Wielkość kolejki"},
{"Run peer test", "Wykonaj test peer"},
{"Reload tunnels configuration", "Załaduj ponownie konfigurację tuneli"},
{"Decline transit tunnels", "Odrzuć tunele tranzytowe"},
{"Accept transit tunnels", "Akceptuj tunele tranzytowe"},
{"Cancel graceful shutdown", "Anuluj łagodne wyłączenie"},
{"Start graceful shutdown", "Rozpocznij łagodne wyłączenie"},
{"Force shutdown", "Wymuś wyłączenie"},
{"Reload external CSS styles", "Odśwież zewnętrzne style CSS"},
{"<b>Note:</b> any action done here are not persistent and not changes your config files.", "<b>Uwaga:</b> każda akcja wykonana tutaj nie jest trwała i nie zmienia Twoich plików konfiguracyjnych."},
{"Logging level", "Poziom logowania"},
{"Transit tunnels limit", "Limit tuneli tranzytowych"},
{"Change", "Zmień"},
{"Change language", "Zmień język"},
{"no transit tunnels currently built", "brak obecnie zbudowanych tuneli tranzytowych"},
{"SAM disabled", "SAM wyłączony"},
{"no sessions currently running", "brak aktualnie uruchomionych sesji"},
{"SAM session not found", "Sesja SAM nie została znaleziona"},
{"SAM Session", "Sesja SAM"},
{"Server Tunnels", "Tunele Serwera"},
{"Client Forwards", "Przekierowania Klienta"},
{"Server Forwards", "Przekierowania Serwera"},
{"Unknown page", "Nieznana strona"},
{"Invalid token", "Nieprawidłowy token"},
{"SUCCESS", "SUKCES"},
{"Stream closed", "Strumień zamknięty"},
{"Stream not found or already was closed", "Strumień nie został znaleziony lub został już zamknięty"},
{"Destination not found", "Nie znaleziono punktu docelowego"},
{"StreamID can't be null", "StreamID nie może być null"},
{"Return to destination page", "Wróć do strony miejsca docelowego"},
{"You will be redirected in %d seconds", "Zostaniesz prekierowany za %d sekund"},
{"LeaseSet expiration time updated", "Zaktualizowano czas wygaśnięcia LeaseSet"},
{"LeaseSet is not found or already expired", "LeaseSet nie został znaleziony lub już wygasł"},
{"Transit tunnels count must not exceed %d", "Liczba tuneli tranzytowych nie może przekraczać %d"},
{"Back to commands list", "Powrót do listy poleceń"},
{"Register at reg.i2p", "Zarejestruj się na reg.i2p"},
{"Description", "Opis"},
{"A bit information about service on domain", "Trochę informacji o usłudze w domenie"},
{"Submit", "Zatwierdź"},
{"Domain can't end with .b32.i2p", "Domena nie może kończyć się na .b32.i2p"},
{"Domain must end with .i2p", "Domena musi kończyć się na .i2p"},
{"Unknown command", "Nieznana komenda"},
{"Command accepted", "Polecenie zaakceptowane"},
{"Proxy error", "Błąd serwera proxy"},
{"Proxy info", "Informacje o proxy"},
{"Proxy error: Host not found", "Błąd proxy: Nie znaleziono hosta"},
{"Remote host not found in router's addressbook", "Nie znaleziono zdalnego hosta w książce adresowej routera"},
{"You may try to find this host on jump services below", "Możesz znaleźć tego hosta na poniższych usługach skoku"},
{"Invalid request", "Nieprawidłowe żądanie"},
{"Proxy unable to parse your request", "Serwer proxy nie może przetworzyć Twojego żądania"},
{"Addresshelper is not supported", "Adresshelper nie jest obsługiwany"},
{"Host %s is <font color=red>already in router's addressbook</font>. <b>Be careful: source of this URL may be harmful!</b> Click here to update record: <a href=\"%s%s%s&update=true\">Continue</a>.", "Host %s <font color=red>jest już w książce adresowej routera</font>. <b>Uważaj: źródło tego adresu URL może być szkodliwe!</b> Kliknij tutaj, aby zaktualizować rekord: <a href=\"%s%s%s&update=true\">Kontynuuj</a>."},
{"Addresshelper forced update rejected", "Wymuszona aktualizacja Addreshelper odrzucona"},
{"To add host <b>%s</b> in router's addressbook, click here: <a href=\"%s%s%s\">Continue</a>.", "Aby dodać host <b>%s</b> w książce adresowej routera, kliknij tutaj: <a href=\"%s%s%s\">Kontynuuj</a>."},
{"Addresshelper request", "Prośba Addresshelper"},
{"Host %s added to router's addressbook from helper. Click here to proceed: <a href=\"%s\">Continue</a>.", "Host %s dodany do książki adresowej routera od pomocnika. Kliknij tutaj, aby kontynuować: <a href=\"%s\">Kontynuuj</a>."},
{"Addresshelper adding", "Dodawanie Addresshelper"},
{"Host %s is <font color=red>already in router's addressbook</font>. Click here to update record: <a href=\"%s%s%s&update=true\">Continue</a>.", "Host %s jest <font color=red>już w książce adresowej routera</font>. Kliknij tutaj, aby zaktualizować rekord: <a href=\"%s%s%s&update=true\">Kontynuuj</a>."},
{"Addresshelper update", "Aktualizacja Adresshelper"},
{"Invalid request URI", "Nieprawidłowe URI żądania"},
{"Can't detect destination host from request", "Nie można wykryć hosta docelowego z żądania"},
{"Outproxy failure", "Błąd proxy wyjściowego"},
{"Bad outproxy settings", "Błędne ustawienia proxy wyjściowych"},
{"Host %s is not inside I2P network, but outproxy is not enabled", "Host %s nie jest wewnątrz sieci I2P, a proxy wyjściowe nie jest włączone"},
{"Unknown outproxy URL", "Nieznany adres URL proxy wyjściowego"},
{"Cannot resolve upstream proxy", "Nie można rozwiązać serwera proxy upstream"},
{"Hostname is too long", "Nazwa hosta jest zbyt długa"},
{"Cannot connect to upstream SOCKS proxy", "Nie można połączyć się z proxy SOCKS upstream"},
{"Cannot negotiate with SOCKS proxy", "Nie można negocjować z proxy SOCKS"},
{"CONNECT error", "Błąd POŁĄCZENIE"},
{"Failed to connect", "Nie udało się połączyć"},
{"SOCKS proxy error", "Błąd proxy SOCKS"},
{"Failed to send request to upstream", "Nie udało się wysłać żądania do upstream"},
{"No reply from SOCKS proxy", "Brak odpowiedzi od serwera proxy SOCKS"},
{"Cannot connect", "Nie można się połączyć"},
{"HTTP out proxy not implemented", "Serwer wyjściowy proxy HTTP nie został zaimplementowany"},
{"Cannot connect to upstream HTTP proxy", "Nie można połączyć się z proxy HTTP upstream"},
{"Host is down", "Host jest niedostępny"},
{"Can't create connection to requested host, it may be down. Please try again later.", "Nie można utworzyć połączenia z żądanym hostem, może być wyłączony. Spróbuj ponownie później."},
{"", ""},
};
static std::map<std::string, std::vector<std::string>> plurals
{
{"", {"", "", ""}},
{"%d days", {"%d dzień", "%d dni", "%d dni", "%d dni"}},
{"%d hours", {"%d godzina", "%d godziny", "%d godzin", "%d godzin"}},
{"%d minutes", {"%d minuta", "%d minuty", "%d minut", "%d minut"}},
{"%d seconds", {"%d sekunda", "%d sekundy", "%d sekund", "%d sekund"}},
{"", {"", "", "", ""}},
};
std::shared_ptr<const i2p::i18n::Locale> GetLocale()

@ -1,5 +1,5 @@
/*
* Copyright (c) 2023, The PurpleI2P Project
* Copyright (c) 2023-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -58,7 +58,7 @@ namespace portuguese // language namespace
{"Unknown", "Desconhecido"},
{"Proxy", "Proxy"},
{"Mesh", "Malha"},
{"Clock skew", "Defasagem do Relógio"},
{"Clock skew", "Desvio de Relógio"},
{"Offline", "Desligado"},
{"Symmetric NAT", "NAT Simétrico"},
{"Full cone NAT", "Full cone NAT"},
@ -74,7 +74,7 @@ namespace portuguese // language namespace
{"%.2f KiB/s", "%.2f KiB/s"},
{"Sent", "Enviado"},
{"Transit", "Trânsito"},
{"Data path", "Diretório dos dados"},
{"Data path", "Diretório de dados"},
{"Hidden content. Press on text to see.", "Conteúdo oculto. Clique no texto para revelar."},
{"Router Ident", "Identidade do Roteador"},
{"Router Family", "Família do Roteador"},
@ -106,9 +106,9 @@ namespace portuguese // language namespace
{"Destination", "Destinos"},
{"Amount", "Quantidade"},
{"Incoming Tags", "Etiquetas de Entrada"},
{"Tags sessions", "Sessões de etiquetas"},
{"Tags sessions", "Sessões de Etiquetas"},
{"Status", "Estado"},
{"Local Destination", "Destinos Locais"},
{"Local Destination", "Destino Local"},
{"Streams", "Fluxos"},
{"Close stream", "Fechar fluxo"},
{"Such destination is not found", "Tal destino não foi encontrado"},
@ -148,7 +148,7 @@ namespace portuguese // language namespace
{"Invalid token", "Token Inválido"},
{"SUCCESS", "SUCESSO"},
{"Stream closed", "Fluxo fechado"},
{"Stream not found or already was closed", "Fluxo não encontrado ou já encerrado"},
{"Stream not found or already was closed", "Fluxo não encontrado ou já fechado"},
{"Destination not found", "Destino não encontrado"},
{"StreamID can't be null", "StreamID não pode ser nulo"},
{"Return to destination page", "Retornar para à página de destino"},
@ -157,7 +157,7 @@ namespace portuguese // language namespace
{"LeaseSet is not found or already expired", "LeaseSet não foi encontrado ou já expirou"},
{"Transit tunnels count must not exceed %d", "A contagem de túneis de trânsito não deve exceder %d"},
{"Back to commands list", "Voltar para a lista de comandos"},
{"Register at reg.i2p", "Registrar na reg.i2p"},
{"Register at reg.i2p", "Registrar em reg.i2p"},
{"Description", "Descrição"},
{"A bit information about service on domain", "Algumas informações sobre o serviço no domínio"},
{"Submit", "Enviar"},
@ -169,22 +169,22 @@ namespace portuguese // language namespace
{"Proxy info", "Informações do proxy"},
{"Proxy error: Host not found", "Erro no proxy: Host não encontrado"},
{"Remote host not found in router's addressbook", "O host remoto não foi encontrado no livro de endereços do roteador"},
{"You may try to find this host on jump services below", "Você pode tentar encontrar este host nos jump services abaixo"},
{"You may try to find this host on jump services below", "Você pode tentar encontrar este host nos serviços de jump abaixo"},
{"Invalid request", "Requisição inválida"},
{"Proxy unable to parse your request", "O proxy foi incapaz de processar a sua requisição"},
{"Addresshelper is not supported", "O Auxiliar de Endereços não é suportado"},
{"Host %s is <font color=red>already in router's addressbook</font>. <b>Be careful: source of this URL may be harmful!</b> Click here to update record: <a href=\"%s%s%s&update=true\">Continue</a>.", "O host %s já <font color=red>está no catálogo de endereços do roteador</font>. <b>Cuidado: a fonte desta URL pode ser perigosa!</b> Clique aqui para atualizar o registro: <a href=\"%s%s%s&update=true\">Continuar</a>."},
{"Addresshelper forced update rejected", "A atualização forçada do Auxiliar de Endereços foi rejeitada"},
{"To add host <b>%s</b> in router's addressbook, click here: <a href=\"%s%s%s\">Continue</a>.", "Para adicionar o host <b> %s </b> ao catálogo de endereços do roteador, clique aqui: <a href='%s%s%s'>Continuar </a>."},
{"Addresshelper request", "Requisição do Auxiliar de Endereços"},
{"Host %s added to router's addressbook from helper. Click here to proceed: <a href=\"%s\">Continue</a>.", "O host %s foi adicionado ao catálogo de endereços do roteador por um auxiliar. Clique aqui para proceder: <a href='%s'> Continuar </a>."},
{"Addresshelper request", "Requisição ao Auxiliar de Endereços"},
{"Host %s added to router's addressbook from helper. Click here to proceed: <a href=\"%s\">Continue</a>.", "O host %s foi adicionado ao catálogo de endereços do roteador por um auxiliar. Clique aqui para prosseguir: <a href='%s'> Continuar </a>."},
{"Addresshelper adding", "Auxiliar de Endereço adicionando"},
{"Host %s is <font color=red>already in router's addressbook</font>. Click here to update record: <a href=\"%s%s%s&update=true\">Continue</a>.", "O host %s já <font color=red>está no catálogo de endereços do roteador </font>. Clique aqui para atualizar o registro: <a href=\"%s%s%s&update=true\">Continuar</a>."},
{"Addresshelper update", "Atualização do Auxiliar de Endereços"},
{"Invalid request URI", "A URI de requisição é inválida"},
{"Can't detect destination host from request", "Incapaz de detectar o host de destino da requisição"},
{"Outproxy failure", "Falha no outproxy"},
{"Bad outproxy settings", "Configurações ruins de outproxy"},
{"Bad outproxy settings", "Má configurações do outproxy"},
{"Host %s is not inside I2P network, but outproxy is not enabled", "O host %s não está dentro da rede I2P, mas o outproxy não está ativado"},
{"Unknown outproxy URL", "URL de outproxy desconhecida"},
{"Cannot resolve upstream proxy", "Não é possível resolver o proxy de entrada"},

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2023, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -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(5000), "Maximum active transit tunnels (default:5000)")
("limits.transittunnels", value<uint32_t>()->default_value(10000), "Maximum active transit tunnels (default:10000)")
("limits.zombies", value<double>()->default_value(0), "Minimum percentage of successfully created tunnels under which tunnel cleanup is paused (default [%]: 0.00)")
("limits.ntcpsoft", value<uint16_t>()->default_value(0), "Ignored")
("limits.ntcphard", value<uint16_t>()->default_value(0), "Ignored")
@ -205,7 +205,7 @@ namespace config {
reseed.add_options()
("reseed.verify", value<bool>()->default_value(false), "Verify .su3 signature")
("reseed.threshold", value<uint16_t>()->default_value(25), "Minimum number of known routers before requesting reseed")
("reseed.floodfill", value<std::string>()->default_value(""), "Path to router info of floodfill to reseed from")
("reseed.floodfill", value<std::string>()->default_value(""), "Ignored. Always empty")
("reseed.file", value<std::string>()->default_value(""), "Path to local .su3 file or HTTPS URL to reseed from")
("reseed.zipfile", value<std::string>()->default_value(""), "Path to local .zip file to reseed from")
("reseed.proxy", value<std::string>()->default_value(""), "url for reseed proxy, supports http/socks")
@ -222,8 +222,7 @@ namespace config {
"https://www2.mk16.de/,"
"https://i2p.ghativega.in/,"
"https://i2p.novg.net/,"
"https://reseed.is.prestium.org/,"
"https://reseed.us.prestium.org/"
"https://reseed.stormycloud.org/"
), "Reseed URLs, separated by comma")
("reseed.yggurls", value<std::string>()->default_value(
"http://[324:71e:281a:9ed3::ace]:7070/,"

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2023, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -19,7 +19,7 @@ namespace i2p
namespace datagram
{
DatagramDestination::DatagramDestination (std::shared_ptr<i2p::client::ClientDestination> owner, bool gzip):
m_Owner (owner), m_Receiver (nullptr), m_RawReceiver (nullptr), m_Gzip (gzip)
m_Owner (owner), m_DefaultReceiver (nullptr), m_DefaultRawReceiver (nullptr), m_Gzip (gzip)
{
if (m_Gzip)
m_Deflator.reset (new i2p::data::GzipDeflator);
@ -119,19 +119,79 @@ namespace datagram
void DatagramDestination::HandleRawDatagram (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len)
{
if (m_RawReceiver)
m_RawReceiver (fromPort, toPort, buf, len);
auto r = FindRawReceiver(toPort);
if (r)
r (fromPort, toPort, buf, len);
else
LogPrint (eLogWarning, "DatagramDestination: no receiver for raw datagram");
}
void DatagramDestination::SetReceiver (const Receiver& receiver, uint16_t port)
{
std::lock_guard<std::mutex> lock(m_ReceiversMutex);
m_ReceiversByPorts[port] = receiver;
if (!m_DefaultReceiver) {
m_DefaultReceiver = receiver;
m_DefaultReceiverPort = port;
}
}
void DatagramDestination::ResetReceiver (uint16_t port)
{
std::lock_guard<std::mutex> lock(m_ReceiversMutex);
m_ReceiversByPorts.erase (port);
if (m_DefaultReceiverPort == port) {
m_DefaultReceiver = nullptr;
m_DefaultReceiverPort = 0;
}
}
void DatagramDestination::SetRawReceiver (const RawReceiver& receiver, uint16_t port)
{
std::lock_guard<std::mutex> lock(m_RawReceiversMutex);
m_RawReceiversByPorts[port] = receiver;
if (!m_DefaultRawReceiver) {
m_DefaultRawReceiver = receiver;
m_DefaultRawReceiverPort = port;
}
}
void DatagramDestination::ResetRawReceiver (uint16_t port)
{
std::lock_guard<std::mutex> lock(m_RawReceiversMutex);
m_RawReceiversByPorts.erase (port);
if (m_DefaultRawReceiverPort == port) {
m_DefaultRawReceiver = nullptr;
m_DefaultRawReceiverPort = 0;
}
}
DatagramDestination::Receiver DatagramDestination::FindReceiver(uint16_t port)
{
std::lock_guard<std::mutex> lock(m_ReceiversMutex);
Receiver r = m_Receiver;
Receiver r = nullptr;
auto itr = m_ReceiversByPorts.find(port);
if (itr != m_ReceiversByPorts.end())
r = itr->second;
else {
r = m_DefaultReceiver;
}
return r;
}
DatagramDestination::RawReceiver DatagramDestination::FindRawReceiver(uint16_t port)
{
std::lock_guard<std::mutex> lock(m_RawReceiversMutex);
RawReceiver r = nullptr;
auto itr = m_RawReceiversByPorts.find(port);
if (itr != m_RawReceiversByPorts.end())
r = itr->second;
else {
r = m_DefaultRawReceiver;
}
return r;
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2022, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -126,14 +126,12 @@ namespace datagram
void HandleDataMessagePayload (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len, bool isRaw = false);
void SetReceiver (const Receiver& receiver) { m_Receiver = receiver; };
void ResetReceiver () { m_Receiver = nullptr; };
void SetReceiver (const Receiver& receiver, uint16_t port) { std::lock_guard<std::mutex> lock(m_ReceiversMutex); m_ReceiversByPorts[port] = receiver; };
void ResetReceiver (uint16_t port) { std::lock_guard<std::mutex> lock(m_ReceiversMutex); m_ReceiversByPorts.erase (port); };
void SetReceiver (const Receiver& receiver, uint16_t port);
void ResetReceiver (uint16_t port);
void SetRawReceiver (const RawReceiver& receiver) { m_RawReceiver = receiver; };
void ResetRawReceiver () { m_RawReceiver = nullptr; };
void SetRawReceiver (const RawReceiver& receiver, uint16_t port);
void ResetRawReceiver (uint16_t port);
std::shared_ptr<DatagramSession::Info> GetInfoForRemote(const i2p::data::IdentHash & remote);
@ -150,20 +148,26 @@ namespace datagram
void HandleDatagram (uint16_t fromPort, uint16_t toPort, uint8_t *const& buf, size_t len);
void HandleRawDatagram (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len);
/** find a receiver by port, if none by port is found try default receiever, otherwise returns nullptr */
Receiver FindReceiver(uint16_t port);
RawReceiver FindRawReceiver(uint16_t port);
private:
std::shared_ptr<i2p::client::ClientDestination> m_Owner;
Receiver m_Receiver; // default
RawReceiver m_RawReceiver; // default
bool m_Gzip; // gzip compression of data messages
std::mutex m_SessionsMutex;
std::map<i2p::data::IdentHash, DatagramSession_ptr > m_Sessions;
Receiver m_DefaultReceiver;
RawReceiver m_DefaultRawReceiver;
uint16_t m_DefaultReceiverPort;
uint16_t m_DefaultRawReceiverPort;
std::mutex m_ReceiversMutex;
std::map<uint16_t, Receiver> m_ReceiversByPorts;
std::mutex m_RawReceiversMutex;
std::unordered_map<uint16_t, Receiver> m_ReceiversByPorts;
std::unordered_map<uint16_t, RawReceiver> m_RawReceiversByPorts;
bool m_Gzip; // gzip compression of data messages
i2p::data::GzipInflator m_Inflator;
std::unique_ptr<i2p::data::GzipDeflator> m_Deflator;
std::vector<uint8_t> m_From, m_Signature;

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2023, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -367,9 +367,12 @@ namespace client
HandleDataMessage (payload, len);
break;
case eI2NPDeliveryStatus:
// we assume tunnel tests non-encrypted
HandleDeliveryStatusMessage (bufbe32toh (payload + DELIVERY_STATUS_MSGID_OFFSET));
break;
case eI2NPTunnelTest:
if (m_Pool)
m_Pool->ProcessTunnelTest (bufbe32toh (payload + TUNNEL_TEST_MSGID_OFFSET), bufbe64toh (payload + TUNNEL_TEST_TIMESTAMP_OFFSET));
break;
case eI2NPDatabaseStore:
HandleDatabaseStoreMessage (payload, len);
break;
@ -407,6 +410,7 @@ namespace client
}
i2p::data::IdentHash key (buf + DATABASE_STORE_KEY_OFFSET);
std::shared_ptr<i2p::data::LeaseSet> leaseSet;
std::shared_ptr<LeaseSetRequest> request;
switch (buf[DATABASE_STORE_TYPE_OFFSET])
{
case i2p::data::NETDB_STORE_TYPE_LEASESET: // 1
@ -462,34 +466,59 @@ namespace client
case i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2: // 5
{
auto it2 = m_LeaseSetRequests.find (key);
if (it2 != m_LeaseSetRequests.end () && it2->second->requestedBlindedKey)
{
auto ls2 = std::make_shared<i2p::data::LeaseSet2> (buf + offset, len - offset,
it2->second->requestedBlindedKey, m_LeaseSetPrivKey ? ((const uint8_t *)*m_LeaseSetPrivKey) : nullptr , GetPreferredCryptoType ());
if (ls2->IsValid () && !ls2->IsExpired ())
if (it2 != m_LeaseSetRequests.end ())
{
request = it2->second;
m_LeaseSetRequests.erase (it2);
if (request->requestedBlindedKey)
{
leaseSet = ls2;
std::lock_guard<std::mutex> lock(m_RemoteLeaseSetsMutex);
m_RemoteLeaseSets[ls2->GetIdentHash ()] = ls2; // ident is not key
m_RemoteLeaseSets[key] = ls2; // also store as key for next lookup
auto ls2 = std::make_shared<i2p::data::LeaseSet2> (buf + offset, len - offset,
request->requestedBlindedKey, m_LeaseSetPrivKey ? ((const uint8_t *)*m_LeaseSetPrivKey) : nullptr , GetPreferredCryptoType ());
if (ls2->IsValid () && !ls2->IsExpired ())
{
leaseSet = ls2;
std::lock_guard<std::mutex> lock(m_RemoteLeaseSetsMutex);
m_RemoteLeaseSets[ls2->GetIdentHash ()] = ls2; // ident is not key
m_RemoteLeaseSets[key] = ls2; // also store as key for next lookup
}
else
LogPrint (eLogError, "Destination: New remote encrypted LeaseSet2 failed");
}
else
LogPrint (eLogError, "Destination: New remote encrypted LeaseSet2 failed");
{
// publishing verification doesn't have requestedBlindedKey
auto localLeaseSet = GetLeaseSetMt ();
if (localLeaseSet->GetStoreHash () == key)
{
auto ls = std::make_shared<i2p::data::LeaseSet2> (i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2,
localLeaseSet->GetBuffer (), localLeaseSet->GetBufferLen (), false);
leaseSet = ls;
}
else
LogPrint (eLogWarning, "Destination: Encrypted LeaseSet2 received for request without blinded key");
}
}
else
LogPrint (eLogInfo, "Destination: Couldn't find request for encrypted LeaseSet2");
LogPrint (eLogWarning, "Destination: Couldn't find request for encrypted LeaseSet2");
break;
}
default:
LogPrint (eLogError, "Destination: Unexpected client's DatabaseStore type ", buf[DATABASE_STORE_TYPE_OFFSET], ", dropped");
}
auto it1 = m_LeaseSetRequests.find (key);
if (it1 != m_LeaseSetRequests.end ())
if (!request)
{
auto it1 = m_LeaseSetRequests.find (key);
if (it1 != m_LeaseSetRequests.end ())
{
request = it1->second;
m_LeaseSetRequests.erase (it1);
}
}
if (request)
{
it1->second->requestTimeoutTimer.cancel ();
if (it1->second) it1->second->Complete (leaseSet);
m_LeaseSetRequests.erase (it1);
request->requestTimeoutTimer.cancel ();
request->Complete (leaseSet);
}
}
@ -502,38 +531,43 @@ namespace client
if (it != m_LeaseSetRequests.end ())
{
auto request = it->second;
bool found = false;
if (request->excluded.size () < MAX_NUM_FLOODFILLS_PER_REQUEST)
for (int i = 0; i < num; i++)
{
for (int i = 0; i < num; i++)
i2p::data::IdentHash peerHash (buf + 33 + i*32);
if (!request->excluded.count (peerHash) && !i2p::data::netdb.FindRouter (peerHash))
{
i2p::data::IdentHash peerHash (buf + 33 + i*32);
if (!request->excluded.count (peerHash) && !i2p::data::netdb.FindRouter (peerHash))
{
LogPrint (eLogInfo, "Destination: Found new floodfill, request it");
i2p::data::netdb.RequestDestination (peerHash, nullptr, false); // through exploratory
}
LogPrint (eLogInfo, "Destination: Found new floodfill, request it");
i2p::data::netdb.RequestDestination (peerHash, nullptr, false); // through exploratory
}
auto floodfill = i2p::data::netdb.GetClosestFloodfill (key, request->excluded);
if (floodfill)
{
LogPrint (eLogInfo, "Destination: Requesting ", key.ToBase64 (), " at ", floodfill->GetIdentHash ().ToBase64 ());
if (SendLeaseSetRequest (key, floodfill, request))
found = true;
}
}
if (!found)
{
LogPrint (eLogInfo, "Destination: ", key.ToBase64 (), " was not found on ", MAX_NUM_FLOODFILLS_PER_REQUEST, " floodfills");
request->Complete (nullptr);
m_LeaseSetRequests.erase (key);
}
SendNextLeaseSetRequest (key, request);
}
else
LogPrint (eLogWarning, "Destination: Request for ", key.ToBase64 (), " not found");
}
void LeaseSetDestination::SendNextLeaseSetRequest (const i2p::data::IdentHash& key,
std::shared_ptr<LeaseSetRequest> request)
{
bool found = false;
if (request->excluded.size () < MAX_NUM_FLOODFILLS_PER_REQUEST)
{
auto floodfill = i2p::data::netdb.GetClosestFloodfill (key, request->excluded);
if (floodfill)
{
LogPrint (eLogInfo, "Destination: Requesting ", key.ToBase64 (), " at ", floodfill->GetIdentHash ().ToBase64 ());
if (SendLeaseSetRequest (key, floodfill, request))
found = true;
}
}
if (!found)
{
LogPrint (eLogInfo, "Destination: ", key.ToBase64 (), " was not found on ", MAX_NUM_FLOODFILLS_PER_REQUEST, " floodfills");
request->Complete (nullptr);
m_LeaseSetRequests.erase (key);
}
}
void LeaseSetDestination::HandleDeliveryStatusMessage (uint32_t msgID)
{
if (msgID == m_PublishReplyToken)
@ -578,12 +612,7 @@ namespace client
shared_from_this (), std::placeholders::_1));
return;
}
if (!m_Pool->GetInboundTunnels ().size () || !m_Pool->GetOutboundTunnels ().size ())
{
LogPrint (eLogError, "Destination: Can't publish LeaseSet. Destination is not ready");
return;
}
auto floodfill = i2p::data::netdb.GetClosestFloodfill (leaseSet->GetIdentHash (), m_ExcludedFloodfills);
auto floodfill = i2p::data::netdb.GetClosestFloodfill (leaseSet->GetStoreHash (), m_ExcludedFloodfills);
if (!floodfill)
{
LogPrint (eLogError, "Destination: Can't publish LeaseSet, no more floodfills found");
@ -594,26 +623,39 @@ namespace client
auto inbound = m_Pool->GetNextInboundTunnel (nullptr, floodfill->GetCompatibleTransports (true));
if (!outbound || !inbound)
{
LogPrint (eLogInfo, "Destination: No compatible tunnels with ", floodfill->GetIdentHash ().ToBase64 (), ". Trying another floodfill");
m_ExcludedFloodfills.insert (floodfill->GetIdentHash ());
floodfill = i2p::data::netdb.GetClosestFloodfill (leaseSet->GetIdentHash (), m_ExcludedFloodfills);
if (floodfill)
{
outbound = m_Pool->GetNextOutboundTunnel (nullptr, floodfill->GetCompatibleTransports (false));
if (outbound)
if (!m_Pool->GetInboundTunnels ().empty () && !m_Pool->GetOutboundTunnels ().empty ())
{
LogPrint (eLogInfo, "Destination: No compatible tunnels with ", floodfill->GetIdentHash ().ToBase64 (), ". Trying another floodfill");
m_ExcludedFloodfills.insert (floodfill->GetIdentHash ());
floodfill = i2p::data::netdb.GetClosestFloodfill (leaseSet->GetStoreHash (), m_ExcludedFloodfills);
if (floodfill)
{
inbound = m_Pool->GetNextInboundTunnel (nullptr, floodfill->GetCompatibleTransports (true));
if (!inbound)
LogPrint (eLogError, "Destination: Can't publish LeaseSet. No inbound tunnels");
outbound = m_Pool->GetNextOutboundTunnel (nullptr, floodfill->GetCompatibleTransports (false));
if (outbound)
{
inbound = m_Pool->GetNextInboundTunnel (nullptr, floodfill->GetCompatibleTransports (true));
if (!inbound)
LogPrint (eLogError, "Destination: Can't publish LeaseSet. No inbound tunnels");
}
else
LogPrint (eLogError, "Destination: Can't publish LeaseSet. No outbound tunnels");
}
else
LogPrint (eLogError, "Destination: Can't publish LeaseSet. No outbound tunnels");
}
LogPrint (eLogError, "Destination: Can't publish LeaseSet, no more floodfills found");
}
else
LogPrint (eLogError, "Destination: Can't publish LeaseSet, no more floodfills found");
LogPrint (eLogDebug, "Destination: No tunnels in pool");
if (!floodfill || !outbound || !inbound)
{
// we can't publish now
m_ExcludedFloodfills.clear ();
m_PublishReplyToken = 1; // dummy non-zero value
// try again after a while
LogPrint (eLogInfo, "Destination: Can't publish LeasetSet because destination is not ready. Try publishing again after ", PUBLISH_CONFIRMATION_TIMEOUT, " seconds");
m_PublishConfirmationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_CONFIRMATION_TIMEOUT));
m_PublishConfirmationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishConfirmationTimer,
shared_from_this (), std::placeholders::_1));
return;
}
}
@ -621,6 +663,15 @@ namespace client
LogPrint (eLogDebug, "Destination: Publish LeaseSet of ", GetIdentHash ().ToBase32 ());
RAND_bytes ((uint8_t *)&m_PublishReplyToken, 4);
auto msg = WrapMessageForRouter (floodfill, i2p::CreateDatabaseStoreMsg (leaseSet, m_PublishReplyToken, inbound));
auto s = shared_from_this ();
msg->onDrop = [s]()
{
s->GetService ().post([s]()
{
s->m_PublishConfirmationTimer.cancel ();
s->HandlePublishConfirmationTimer (boost::system::error_code());
});
};
m_PublishConfirmationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_CONFIRMATION_TIMEOUT));
m_PublishConfirmationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishConfirmationTimer,
shared_from_this (), std::placeholders::_1));
@ -637,7 +688,7 @@ namespace client
m_PublishReplyToken = 0;
if (GetIdentity ()->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ELGAMAL)
{
LogPrint (eLogWarning, "Destination: Publish confirmation was not received in ", PUBLISH_CONFIRMATION_TIMEOUT, " seconds, will try again");
LogPrint (eLogWarning, "Destination: Publish confirmation was not received in ", PUBLISH_CONFIRMATION_TIMEOUT, " seconds or failed. will try again");
Publish ();
}
else
@ -750,7 +801,7 @@ namespace client
void LeaseSetDestination::RequestLeaseSet (const i2p::data::IdentHash& dest, RequestComplete requestComplete, std::shared_ptr<const i2p::data::BlindedPublicKey> requestedBlindedKey)
{
std::set<i2p::data::IdentHash> excluded;
std::unordered_set<i2p::data::IdentHash> excluded;
auto floodfill = i2p::data::netdb.GetClosestFloodfill (dest, excluded);
if (floodfill)
{
@ -822,8 +873,17 @@ namespace client
AddECIESx25519Key (replyKey, replyTag);
else
AddSessionKey (replyKey, replyTag);
auto msg = WrapMessageForRouter (nextFloodfill, CreateLeaseSetDatabaseLookupMsg (dest,
request->excluded, request->replyTunnel, replyKey, replyTag, isECIES));
auto msg = WrapMessageForRouter (nextFloodfill,
CreateLeaseSetDatabaseLookupMsg (dest, request->excluded, request->replyTunnel, replyKey, replyTag, isECIES));
auto s = shared_from_this ();
msg->onDrop = [s, dest, request]()
{
s->GetService ().post([s, dest, request]()
{
s->SendNextLeaseSetRequest (dest, request);
});
};
request->outboundTunnel->SendTunnelDataMsgs (
{
i2p::tunnel::TunnelMessageBlock

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2023, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -15,7 +15,7 @@
#include <memory>
#include <map>
#include <unordered_map>
#include <set>
#include <unordered_set>
#include <string>
#include <functional>
#include <boost/asio.hpp>
@ -97,7 +97,7 @@ namespace client
struct LeaseSetRequest
{
LeaseSetRequest (boost::asio::io_service& service): requestTime (0), requestTimeoutTimer (service) {};
std::set<i2p::data::IdentHash> excluded;
std::unordered_set<i2p::data::IdentHash> excluded;
uint64_t requestTime;
boost::asio::deadline_timer requestTimeoutTimer;
std::list<RequestComplete> requestComplete;
@ -176,6 +176,7 @@ namespace client
void RequestLeaseSet (const i2p::data::IdentHash& dest, RequestComplete requestComplete, std::shared_ptr<const i2p::data::BlindedPublicKey> requestedBlindedKey = nullptr);
bool SendLeaseSetRequest (const i2p::data::IdentHash& dest, std::shared_ptr<const i2p::data::RouterInfo> nextFloodfill, std::shared_ptr<LeaseSetRequest> request);
void SendNextLeaseSetRequest (const i2p::data::IdentHash& key, std::shared_ptr<LeaseSetRequest> request);
void HandleRequestTimoutTimer (const boost::system::error_code& ecode, const i2p::data::IdentHash& dest);
void HandleCleanupTimer (const boost::system::error_code& ecode);
void CleanupRemoteLeaseSets ();
@ -194,7 +195,7 @@ namespace client
bool m_IsPublic;
uint32_t m_PublishReplyToken;
uint64_t m_LastSubmissionTime; // in seconds
std::set<i2p::data::IdentHash> m_ExcludedFloodfills; // for publishing
std::unordered_set<i2p::data::IdentHash> m_ExcludedFloodfills; // for publishing
boost::asio::deadline_timer m_PublishConfirmationTimer, m_PublishVerificationTimer,
m_PublishDelayTimer, m_CleanupTimer;

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2023, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -117,6 +117,12 @@ namespace garlic
return session->HandleNextMessage (buf, len, shared_from_this (), index);
}
bool ReceiveRatchetTagSet::IsSessionTerminated () const
{
return !m_Session || m_Session->IsTerminated ();
}
SymmetricKeyTagSet::SymmetricKeyTagSet (GarlicDestination * destination, const uint8_t * key):
ReceiveRatchetTagSet (nullptr), m_Destination (destination)
{
@ -857,7 +863,7 @@ namespace garlic
payloadLen += msg->GetPayloadLength () + 13;
if (m_Destination) payloadLen += 32;
}
if (GetLeaseSetUpdateStatus () == eLeaseSetSubmitted && ts > GetLeaseSetSubmissionTime () + LEASET_CONFIRMATION_TIMEOUT)
if (GetLeaseSetUpdateStatus () == eLeaseSetSubmitted && ts > GetLeaseSetSubmissionTime () + LEASESET_CONFIRMATION_TIMEOUT)
{
// resubmit non-confirmed LeaseSet
SetLeaseSetUpdateStatus (eLeaseSetUpdated);
@ -1148,7 +1154,7 @@ namespace garlic
return len;
}
std::shared_ptr<I2NPMessage> WrapECIESX25519Message (std::shared_ptr<const I2NPMessage> msg, const uint8_t * key, uint64_t tag)
std::shared_ptr<I2NPMessage> WrapECIESX25519Message (std::shared_ptr<I2NPMessage> msg, const uint8_t * key, uint64_t tag)
{
auto m = NewI2NPMessage ((msg ? msg->GetPayloadLength () : 0) + 128);
m->Align (12); // in order to get buf aligned to 16 (12 + 4)
@ -1168,10 +1174,16 @@ namespace garlic
htobe32buf (m->GetPayload (), offset);
m->len += offset + 4;
m->FillI2NPMessageHeader (eI2NPGarlic);
if (msg->onDrop)
{
// move onDrop to the wrapping I2NP messages
m->onDrop = msg->onDrop;
msg->onDrop = nullptr;
}
return m;
}
std::shared_ptr<I2NPMessage> WrapECIESX25519MessageForRouter (std::shared_ptr<const I2NPMessage> msg, const uint8_t * routerPublicKey)
std::shared_ptr<I2NPMessage> WrapECIESX25519MessageForRouter (std::shared_ptr<I2NPMessage> msg, const uint8_t * routerPublicKey)
{
// Noise_N, we are Alice, routerPublicKey is Bob's
i2p::crypto::NoiseSymmetricState noiseState;
@ -1205,6 +1217,12 @@ namespace garlic
htobe32buf (m->GetPayload (), offset);
m->len += offset + 4;
m->FillI2NPMessageHeader (eI2NPGarlic);
if (msg->onDrop)
{
// move onDrop to the wrapping I2NP messages
m->onDrop = msg->onDrop;
msg->onDrop = nullptr;
}
return m;
}
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2021, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -86,7 +86,8 @@ namespace garlic
virtual bool IsIndexExpired (int index) const;
virtual bool HandleNextMessage (uint8_t * buf, size_t len, int index);
virtual bool IsSessionTerminated () const;
private:
int m_TrimBehindIndex = 0;
@ -101,9 +102,10 @@ namespace garlic
SymmetricKeyTagSet (GarlicDestination * destination, const uint8_t * key);
bool IsIndexExpired (int index) const { return false; };
bool HandleNextMessage (uint8_t * buf, size_t len, int index);
bool IsIndexExpired (int index) const override { return false; };
bool HandleNextMessage (uint8_t * buf, size_t len, int index) override;
bool IsSessionTerminated () const override { return false; }
private:
GarlicDestination * m_Destination;
@ -245,8 +247,8 @@ namespace garlic
i2p::crypto::NoiseSymmetricState m_CurrentNoiseState;
};
std::shared_ptr<I2NPMessage> WrapECIESX25519Message (std::shared_ptr<const I2NPMessage> msg, const uint8_t * key, uint64_t tag);
std::shared_ptr<I2NPMessage> WrapECIESX25519MessageForRouter (std::shared_ptr<const I2NPMessage> msg, const uint8_t * routerPublicKey);
std::shared_ptr<I2NPMessage> WrapECIESX25519Message (std::shared_ptr<I2NPMessage> msg, const uint8_t * key, uint64_t tag);
std::shared_ptr<I2NPMessage> WrapECIESX25519MessageForRouter (std::shared_ptr<I2NPMessage> msg, const uint8_t * routerPublicKey);
}
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2022, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -9,6 +9,11 @@
#include <algorithm>
#include <boost/filesystem.hpp>
#if defined(MAC_OSX)
#include <boost/system/system_error.hpp>
#include <TargetConditionals.h>
#endif
#ifdef _WIN32
#include <shlobj.h>
#include <windows.h>
@ -49,7 +54,11 @@ namespace fs {
const std::string GetUTF8DataDir () {
#ifdef _WIN32
#if (BOOST_VERSION >= 108500)
boost::filesystem::path path (dataDir);
#else
boost::filesystem::wpath path (dataDir);
#endif
auto loc = boost::filesystem::path::imbue(std::locale( std::locale(), new std::codecvt_utf8_utf16<wchar_t>() ) ); // convert path to UTF-8
auto dataDirUTF8 = path.string();
boost::filesystem::path::imbue(loc); // Return locale settings back
@ -82,7 +91,11 @@ namespace fs {
}
else
{
#if (BOOST_VERSION >= 108500)
dataDir = boost::filesystem::path(commonAppData).string() + "\\" + appName;
#else
dataDir = boost::filesystem::wpath(commonAppData).string() + "\\" + appName;
#endif
}
#else
dataDir = "/var/lib/" + appName;
@ -107,7 +120,11 @@ namespace fs {
}
else
{
#if (BOOST_VERSION >= 108500)
auto execPath = boost::filesystem::path(localAppData).parent_path();
#else
auto execPath = boost::filesystem::wpath(localAppData).parent_path();
#endif
// if config file exists in .exe's folder use it
if(boost::filesystem::exists(execPath/"i2pd.conf")) // TODO: magic string
@ -126,7 +143,11 @@ namespace fs {
}
else
{
#if (BOOST_VERSION >= 108500)
dataDir = boost::filesystem::path(localAppData).string() + "\\" + appName;
#else
dataDir = boost::filesystem::wpath(localAppData).string() + "\\" + appName;
#endif
}
}
}
@ -251,8 +272,22 @@ namespace fs {
auto p = root + i2p::fs::dirSep + prefix1 + chars[i];
if (boost::filesystem::exists(p))
continue;
#if TARGET_OS_SIMULATOR
// ios simulator fs says it is case sensitive, but it is not
boost::system::error_code ec;
if (boost::filesystem::create_directory(p, ec))
continue;
switch (ec.value()) {
case boost::system::errc::file_exists:
case boost::system::errc::success:
continue;
default:
throw boost::system::system_error( ec, __func__ );
}
#else
if (boost::filesystem::create_directory(p))
continue; /* ^ throws exception on failure */
#endif
return false;
}
return true;

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2023, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -80,7 +80,7 @@ namespace garlic
void GarlicRoutingSession::CleanupUnconfirmedLeaseSet (uint64_t ts)
{
if (m_LeaseSetUpdateMsgID && ts*1000LL > m_LeaseSetSubmissionTime + LEASET_CONFIRMATION_TIMEOUT)
if (m_LeaseSetUpdateMsgID && ts*1000LL > m_LeaseSetSubmissionTime + LEASESET_CONFIRMATION_TIMEOUT)
{
if (GetOwner ())
GetOwner ()->RemoveDeliveryStatusSession (m_LeaseSetUpdateMsgID);
@ -232,7 +232,7 @@ namespace garlic
if (GetOwner ())
{
// resubmit non-confirmed LeaseSet
if (GetLeaseSetUpdateStatus () == eLeaseSetSubmitted && ts > GetLeaseSetSubmissionTime () + LEASET_CONFIRMATION_TIMEOUT)
if (GetLeaseSetUpdateStatus () == eLeaseSetSubmitted && ts > GetLeaseSetSubmissionTime () + LEASESET_CONFIRMATION_TIMEOUT)
{
SetLeaseSetUpdateStatus (eLeaseSetUpdated);
SetSharedRoutingPath (nullptr); // invalidate path since leaseset was not confirmed
@ -887,8 +887,7 @@ namespace garlic
}
else
{
auto session = it->second.tagset->GetSession ();
if (!session || session->IsTerminated())
if (it->second.tagset->IsSessionTerminated ())
{
it = m_ECIESx25519Tags.erase (it);
numExpiredTags++;

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2022, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -50,7 +50,7 @@ namespace garlic
const int INCOMING_TAGS_EXPIRATION_TIMEOUT = 960; // 16 minutes
const int OUTGOING_TAGS_EXPIRATION_TIMEOUT = 720; // 12 minutes
const int OUTGOING_TAGS_CONFIRMATION_TIMEOUT = 10; // 10 seconds
const int LEASET_CONFIRMATION_TIMEOUT = 4000; // in milliseconds
const int LEASESET_CONFIRMATION_TIMEOUT = 4000; // in milliseconds
const int ROUTING_PATH_EXPIRATION_TIMEOUT = 30; // 30 seconds
const int ROUTING_PATH_MAX_NUM_TIMES_USED = 100; // how many times might be used
@ -221,7 +221,7 @@ namespace garlic
struct ECIESX25519AEADRatchetIndexTagset
{
int index;
ReceiveRatchetTagSetPtr tagset;
ReceiveRatchetTagSetPtr tagset; // null if used
};
class GarlicDestination: public i2p::data::LocalDestination

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2023, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -72,11 +72,15 @@ namespace i2p
SetExpiration (i2p::util::GetMillisecondsSinceEpoch () + I2NP_MESSAGE_EXPIRATION_TIMEOUT);
}
bool I2NPMessage::IsExpired () const
bool I2NPMessage::IsExpired (uint64_t ts) const
{
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
auto exp = GetExpiration ();
return (ts > exp + I2NP_MESSAGE_CLOCK_SKEW) || (ts < exp - 3*I2NP_MESSAGE_CLOCK_SKEW); // check if expired or too far in future
}
bool I2NPMessage::IsExpired () const
{
return IsExpired (i2p::util::GetMillisecondsSinceEpoch ());
}
std::shared_ptr<I2NPMessage> CreateI2NPMessage (I2NPMessageType msgType, const uint8_t * buf, size_t len, uint32_t replyMsgID)
@ -111,6 +115,17 @@ namespace i2p
return newMsg;
}
std::shared_ptr<I2NPMessage> CreateTunnelTestMsg (uint32_t msgID)
{
auto m = NewI2NPShortMessage ();
uint8_t * buf = m->GetPayload ();
htobe32buf (buf + TUNNEL_TEST_MSGID_OFFSET, msgID);
htobe64buf (buf + TUNNEL_TEST_TIMESTAMP_OFFSET, i2p::util::GetMonotonicMicroseconds ());
m->len += TUNNEL_TEST_SIZE;
m->FillI2NPMessageHeader (eI2NPTunnelTest);
return m;
}
std::shared_ptr<I2NPMessage> CreateDeliveryStatusMsg (uint32_t msgID)
{
auto m = NewI2NPShortMessage ();
@ -132,7 +147,7 @@ namespace i2p
}
std::shared_ptr<I2NPMessage> CreateRouterInfoDatabaseLookupMsg (const uint8_t * key, const uint8_t * from,
uint32_t replyTunnelID, bool exploratory, std::set<i2p::data::IdentHash> * excludedPeers)
uint32_t replyTunnelID, bool exploratory, std::unordered_set<i2p::data::IdentHash> * excludedPeers)
{
int cnt = excludedPeers ? excludedPeers->size () : 0;
auto m = cnt > 7 ? NewI2NPMessage () : NewI2NPShortMessage ();
@ -177,7 +192,7 @@ namespace i2p
}
std::shared_ptr<I2NPMessage> CreateLeaseSetDatabaseLookupMsg (const i2p::data::IdentHash& dest,
const std::set<i2p::data::IdentHash>& excludedFloodfills,
const std::unordered_set<i2p::data::IdentHash>& excludedFloodfills,
std::shared_ptr<const i2p::tunnel::InboundTunnel> replyTunnel, const uint8_t * replyKey,
const uint8_t * replyTag, bool replyECIES)
{
@ -369,10 +384,20 @@ namespace i2p
if (!memcmp (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)i2p::context.GetRouterInfo ().GetIdentHash (), 16))
{
LogPrint (eLogDebug, "I2NP: Build request record ", i, " is ours");
if (!i2p::context.DecryptTunnelBuildRecord (record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, clearText)) return false;
if (!i2p::context.DecryptTunnelBuildRecord (record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, clearText))
{
LogPrint (eLogWarning, "I2NP: Failed to decrypt tunnel build record");
return false;
}
if (!memcmp ((const uint8_t *)i2p::context.GetIdentHash (), clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, 32) && // if next ident is now ours
!(clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG)) // and not endpoint
{
LogPrint (eLogWarning, "I2NP: Next ident is ours in tunnel build record");
return false;
}
uint8_t retCode = 0;
// replace record to reply
if (i2p::context.AcceptsTunnels () && !i2p::context.IsHighCongestion ())
if (i2p::context.AcceptsTunnels () && i2p::context.GetCongestionLevel (false) < CONGESTION_LEVEL_FULL)
{
auto transitTunnel = i2p::tunnel::CreateTransitTunnel (
bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET),
@ -577,16 +602,24 @@ namespace i2p
memcpy (ivKey, noiseState.m_CK + 32, 32);
}
else
{
if (!memcmp ((const uint8_t *)i2p::context.GetIdentHash (), clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, 32)) // if next ident is now ours
{
LogPrint (eLogWarning, "I2NP: Next ident is ours in short request record");
return;
}
memcpy (ivKey, noiseState.m_CK , 32);
}
// check if we accept this tunnel
std::shared_ptr<i2p::tunnel::TransitTunnel> transitTunnel;
uint8_t retCode = 0;
if (!i2p::context.AcceptsTunnels () || i2p::context.IsHighCongestion ())
if (!i2p::context.AcceptsTunnels () || i2p::context.GetCongestionLevel (false) >= CONGESTION_LEVEL_FULL)
retCode = 30;
if (!retCode)
{
// create new transit tunnel
auto transitTunnel = i2p::tunnel::CreateTransitTunnel (
transitTunnel = i2p::tunnel::CreateTransitTunnel (
bufbe32toh (clearText + SHORT_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET),
clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET,
bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET),
@ -620,11 +653,22 @@ namespace i2p
reply += SHORT_TUNNEL_BUILD_RECORD_SIZE;
}
// send reply
auto onDrop = [transitTunnel]()
{
if (transitTunnel)
{
auto t = transitTunnel->GetCreationTime ();
if (t > i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT)
// make transit tunnel expired
transitTunnel->SetCreationTime (t - i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT);
}
};
if (isEndpoint)
{
auto replyMsg = NewI2NPShortMessage ();
replyMsg->Concat (buf, len);
replyMsg->FillI2NPMessageHeader (eI2NPShortTunnelBuildReply, bufbe32toh (clearText + SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET));
if (transitTunnel) replyMsg->onDrop = onDrop;
if (memcmp ((const uint8_t *)i2p::context.GetIdentHash (),
clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, 32)) // reply IBGW is not local?
{
@ -642,15 +686,21 @@ namespace i2p
uint32_t tunnelID = bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET);
auto tunnel = i2p::tunnel::tunnels.GetTunnel (tunnelID);
if (tunnel)
{
tunnel->SendTunnelDataMsg (replyMsg);
tunnel->FlushTunnelDataMsgs ();
}
else
LogPrint (eLogWarning, "I2NP: Tunnel ", tunnelID, " not found for short tunnel build reply");
}
}
else
transports.SendMessage (clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET,
CreateI2NPMessage (eI2NPShortTunnelBuild, buf, len,
bufbe32toh (clearText + SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET)));
{
auto msg = CreateI2NPMessage (eI2NPShortTunnelBuild, buf, len,
bufbe32toh (clearText + SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET));
if (transitTunnel) msg->onDrop = onDrop;
transports.SendMessage (clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, msg);
}
return;
}
record += SHORT_TUNNEL_BUILD_RECORD_SIZE;
@ -710,7 +760,11 @@ namespace i2p
return msg;
}
else
return CreateTunnelGatewayMsg (tunnelID, msg->GetBuffer (), msg->GetLength ());
{
auto newMsg = CreateTunnelGatewayMsg (tunnelID, msg->GetBuffer (), msg->GetLength ());
if (msg->onDrop) newMsg->onDrop = msg->onDrop;
return newMsg;
}
}
std::shared_ptr<I2NPMessage> CreateTunnelGatewayMsg (uint32_t tunnelID, I2NPMessageType msgType,
@ -808,12 +862,14 @@ namespace i2p
break;
}
case eI2NPDatabaseStore:
case eI2NPDatabaseSearchReply:
// forward to netDb if came directly or through exploratory tunnel as response to our request
if (!msg->from || !msg->from->GetTunnelPool () || msg->from->GetTunnelPool ()->IsExploratory ())
i2p::data::netdb.PostI2NPMsg (msg);
break;
case eI2NPDatabaseSearchReply:
if (!msg->from || !msg->from->GetTunnelPool () || msg->from->GetTunnelPool ()->IsExploratory ())
i2p::data::netdb.PostDatabaseSearchReplyMsg (msg);
break;
case eI2NPDatabaseLookup:
// forward to netDb if floodfill and came directly
if (!msg->from && i2p::context.IsFloodfill ())
@ -827,6 +883,10 @@ namespace i2p
i2p::context.ProcessDeliveryStatusMessage (msg);
break;
}
case eI2NPTunnelTest:
if (msg->from && msg->from->GetTunnelPool ())
msg->from->GetTunnelPool ()->ProcessTunnelTest (msg);
break;
case eI2NPVariableTunnelBuild:
case eI2NPTunnelBuild:
case eI2NPShortTunnelBuild:

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2023, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -11,8 +11,9 @@
#include <inttypes.h>
#include <string.h>
#include <set>
#include <unordered_set>
#include <memory>
#include <functional>
#include "Crypto.h"
#include "I2PEndian.h"
#include "Identity.h"
@ -47,6 +48,11 @@ namespace i2p
const size_t DELIVERY_STATUS_TIMESTAMP_OFFSET = DELIVERY_STATUS_MSGID_OFFSET + 4;
const size_t DELIVERY_STATUS_SIZE = DELIVERY_STATUS_TIMESTAMP_OFFSET + 8;
// TunnelTest
const size_t TUNNEL_TEST_MSGID_OFFSET = 0;
const size_t TUNNEL_TEST_TIMESTAMP_OFFSET = TUNNEL_TEST_MSGID_OFFSET + 4;
const size_t TUNNEL_TEST_SIZE = TUNNEL_TEST_TIMESTAMP_OFFSET + 8;
// DatabaseStore
const size_t DATABASE_STORE_KEY_OFFSET = 0;
const size_t DATABASE_STORE_TYPE_OFFSET = DATABASE_STORE_KEY_OFFSET + 32;
@ -115,7 +121,8 @@ namespace i2p
eI2NPVariableTunnelBuild = 23,
eI2NPVariableTunnelBuildReply = 24,
eI2NPShortTunnelBuild = 25,
eI2NPShortTunnelBuildReply = 26
eI2NPShortTunnelBuildReply = 26,
eI2NPTunnelTest = 231
};
const uint8_t TUNNEL_BUILD_RECORD_GATEWAY_FLAG = 0x80;
@ -138,9 +145,16 @@ namespace tunnel
class TunnelPool;
}
const int CONGESTION_LEVEL_MEDIUM = 70;
const int CONGESTION_LEVEL_HIGH = 90;
const int CONGESTION_LEVEL_FULL = 100;
const size_t I2NP_MAX_MESSAGE_SIZE = 62708;
const size_t I2NP_MAX_SHORT_MESSAGE_SIZE = 4096;
const size_t I2NP_MAX_MEDIUM_MESSAGE_SIZE = 16384;
const unsigned int I2NP_MESSAGE_LOCAL_EXPIRATION_TIMEOUT_FACTOR = 3; // multiples of RTT
const unsigned int I2NP_MESSAGE_LOCAL_EXPIRATION_TIMEOUT_MIN = 200000; // in microseconds
const unsigned int I2NP_MESSAGE_LOCAL_EXPIRATION_TIMEOUT_MAX = 2000000; // in microseconds
const unsigned int I2NP_MESSAGE_EXPIRATION_TIMEOUT = 8000; // in milliseconds (as initial RTT)
const unsigned int I2NP_MESSAGE_CLOCK_SKEW = 60*1000; // 1 minute in milliseconds
@ -149,9 +163,11 @@ namespace tunnel
uint8_t * buf;
size_t len, offset, maxLen;
std::shared_ptr<i2p::tunnel::InboundTunnel> from;
std::function<void ()> onDrop;
uint64_t enqueueTime; // monotonic microseconds
I2NPMessage (): buf (nullptr),len (I2NP_HEADER_SIZE + 2),
offset(2), maxLen (0), from (nullptr) {}; // reserve 2 bytes for NTCP header
I2NPMessage (): buf (nullptr), len (I2NP_HEADER_SIZE + 2),
offset(2), maxLen (0), from (nullptr), enqueueTime (0) {}; // reserve 2 bytes for NTCP header
// header accessors
uint8_t * GetHeader () { return GetBuffer (); };
@ -161,7 +177,9 @@ namespace tunnel
void SetMsgID (uint32_t msgID) { htobe32buf (GetHeader () + I2NP_HEADER_MSGID_OFFSET, msgID); };
uint32_t GetMsgID () const { return bufbe32toh (GetHeader () + I2NP_HEADER_MSGID_OFFSET); };
void SetExpiration (uint64_t expiration) { htobe64buf (GetHeader () + I2NP_HEADER_EXPIRATION_OFFSET, expiration); };
void SetEnqueueTime (uint64_t mts) { enqueueTime = mts; };
uint64_t GetExpiration () const { return bufbe64toh (GetHeader () + I2NP_HEADER_EXPIRATION_OFFSET); };
uint64_t GetEnqueueTime () const { return enqueueTime; };
void SetSize (uint16_t size) { htobe16buf (GetHeader () + I2NP_HEADER_SIZE_OFFSET, size); };
uint16_t GetSize () const { return bufbe16toh (GetHeader () + I2NP_HEADER_SIZE_OFFSET); };
void UpdateSize () { SetSize (GetPayloadLength ()); };
@ -241,7 +259,6 @@ namespace tunnel
SetSize (len - offset - I2NP_HEADER_SIZE);
SetChks (0);
}
void ToNTCP2 ()
{
uint8_t * ntcp2 = GetNTCP2Header ();
@ -252,6 +269,9 @@ namespace tunnel
void FillI2NPMessageHeader (I2NPMessageType msgType, uint32_t replyMsgID = 0, bool checksum = true);
void RenewI2NPMessageHeader ();
bool IsExpired () const;
bool IsExpired (uint64_t ts) const; // in milliseconds
void Drop () { if (onDrop) { onDrop (); onDrop = nullptr; }; }
};
template<int sz>
@ -271,11 +291,12 @@ namespace tunnel
std::shared_ptr<I2NPMessage> CreateI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr<i2p::tunnel::InboundTunnel> from = nullptr);
std::shared_ptr<I2NPMessage> CopyI2NPMessage (std::shared_ptr<I2NPMessage> msg);
std::shared_ptr<I2NPMessage> CreateTunnelTestMsg (uint32_t msgID);
std::shared_ptr<I2NPMessage> CreateDeliveryStatusMsg (uint32_t msgID);
std::shared_ptr<I2NPMessage> CreateRouterInfoDatabaseLookupMsg (const uint8_t * key, const uint8_t * from,
uint32_t replyTunnelID, bool exploratory = false, std::set<i2p::data::IdentHash> * excludedPeers = nullptr);
uint32_t replyTunnelID, bool exploratory = false, std::unordered_set<i2p::data::IdentHash> * excludedPeers = nullptr);
std::shared_ptr<I2NPMessage> CreateLeaseSetDatabaseLookupMsg (const i2p::data::IdentHash& dest,
const std::set<i2p::data::IdentHash>& excludedFloodfills,
const std::unordered_set<i2p::data::IdentHash>& excludedFloodfills,
std::shared_ptr<const i2p::tunnel::InboundTunnel> replyTunnel,
const uint8_t * replyKey, const uint8_t * replyTag, bool replyECIES = false);
std::shared_ptr<I2NPMessage> CreateDatabaseSearchReply (const i2p::data::IdentHash& ident, std::vector<i2p::data::IdentHash> routers);

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2023, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -790,11 +790,14 @@ namespace data
return keys;
}
IdentHash CreateRoutingKey (const IdentHash& ident)
IdentHash CreateRoutingKey (const IdentHash& ident, bool nextDay)
{
uint8_t buf[41]; // ident + yyyymmdd
memcpy (buf, (const uint8_t *)ident, 32);
i2p::util::GetCurrentDate ((char *)(buf + 32));
if (nextDay)
i2p::util::GetNextDayDate ((char *)(buf + 32));
else
i2p::util::GetCurrentDate ((char *)(buf + 32));
IdentHash key;
SHA256(buf, 40, key);
return key;

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2023, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -206,7 +206,7 @@ namespace data
bool operator< (const XORMetric& other) const { return memcmp (metric, other.metric, 32) < 0; };
};
IdentHash CreateRoutingKey (const IdentHash& ident);
IdentHash CreateRoutingKey (const IdentHash& ident, bool nextDay = false);
XORMetric operator^(const IdentHash& key1, const IdentHash& key2);
// destination for delivery instructions

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2020, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -87,8 +87,8 @@ namespace log {
Log ();
~Log ();
LogType GetLogType () { return m_Destination; };
LogLevel GetLogLevel () { return m_MinLevel; };
LogType GetLogType () const { return m_Destination; };
LogLevel GetLogLevel () const { return m_MinLevel; };
void Start ();
void Stop ();
@ -160,6 +160,11 @@ namespace log {
} // log
} // i2p
inline bool CheckLogLevel (LogLevel level) noexcept
{
return level <= i2p::log::Logger().GetLogLevel ();
}
/** internal usage only -- folding args array to single string */
template<typename TValue>
void LogPrint (std::stringstream& s, TValue&& arg) noexcept
@ -185,9 +190,7 @@ void LogPrint (std::stringstream& s, TValue&& arg, TArgs&&... args) noexcept
template<typename... TArgs>
void LogPrint (LogLevel level, TArgs&&... args) noexcept
{
i2p::log::Log &log = i2p::log::Logger();
if (level > log.GetLogLevel ())
return;
if (!CheckLogLevel (level)) return;
// fold message to single string
std::stringstream ss;
@ -200,7 +203,7 @@ void LogPrint (LogLevel level, TArgs&&... args) noexcept
auto msg = std::make_shared<i2p::log::LogMsg>(level, std::time(nullptr), std::move(ss).str());
msg->tid = std::this_thread::get_id();
log.Append(msg);
i2p::log::Logger().Append(msg);
}
/**

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2023, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -19,9 +19,10 @@
#include "RouterContext.h"
#include "Transports.h"
#include "NetDb.hpp"
#include "NTCP2.h"
#include "HTTP.h"
#include "util.h"
#include "Socks5.h"
#include "NTCP2.h"
#if defined(__linux__) && !defined(_NETINET_IN_H)
#include <linux/in6.h>
@ -128,7 +129,8 @@ namespace transport
options[1] = 2; // ver
htobe16buf (options + 2, paddingLength); // padLen
// m3p2Len
auto bufLen = i2p::context.GetRouterInfo ().GetBufferLen ();
auto riBuffer = i2p::context.CopyRouterInfoBuffer ();
auto bufLen = riBuffer->GetBufferLen ();
m3p2Len = bufLen + 4 + 16; // (RI header + RI + MAC for now) TODO: implement options
htobe16buf (options + 4, m3p2Len);
// fill m3p2 payload (RouterInfo block)
@ -137,7 +139,7 @@ namespace transport
m3p2[0] = eNTCP2BlkRouterInfo; // block
htobe16buf (m3p2 + 1, bufLen + 1); // flag + RI
m3p2[3] = 0; // flag
memcpy (m3p2 + 4, i2p::context.GetRouterInfo ().GetBuffer (), bufLen); // TODO: own RI should be protected by mutex
memcpy (m3p2 + 4, riBuffer->data (), bufLen); // TODO: eliminate extra copy
// 2 bytes reserved
htobe32buf (options + 8, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000); // tsA, rounded to seconds
// 4 bytes reserved
@ -373,13 +375,15 @@ namespace transport
m_Socket.close ();
transports.PeerDisconnected (shared_from_this ());
m_Server.RemoveNTCP2Session (shared_from_this ());
for (auto& it: m_SendQueue)
it->Drop ();
m_SendQueue.clear ();
SetSendQueueSize (0);
auto remoteIdentity = GetRemoteIdentity ();
if (remoteIdentity)
{
LogPrint (eLogDebug, "NTCP2: Session with ", GetRemoteEndpoint (),
" (", i2p::data::GetIdentHashAbbreviation (GetRemoteIdentity ()->GetIdentHash ()), ") terminated");
" (", i2p::data::GetIdentHashAbbreviation (remoteIdentity->GetIdentHash ()), ") terminated");
}
else
{
@ -408,6 +412,7 @@ namespace transport
m_IsEstablished = true;
m_Establisher.reset (nullptr);
SetTerminationTimeout (NTCP2_TERMINATION_TIMEOUT);
SendQueue ();
transports.PeerConnected (shared_from_this ());
}
@ -833,14 +838,19 @@ namespace transport
CreateNextReceivedBuffer (m_NextReceivedLen);
boost::system::error_code ec;
size_t moreBytes = m_Socket.available(ec);
if (!ec && moreBytes >= m_NextReceivedLen)
{
// read and process message immediately if available
moreBytes = boost::asio::read (m_Socket, boost::asio::buffer(m_NextReceivedBuffer, m_NextReceivedLen), boost::asio::transfer_all (), ec);
HandleReceived (ec, moreBytes);
}
if (!ec)
{
if (moreBytes >= m_NextReceivedLen)
{
// read and process message immediately if available
moreBytes = boost::asio::read (m_Socket, boost::asio::buffer(m_NextReceivedBuffer, m_NextReceivedLen), boost::asio::transfer_all (), ec);
HandleReceived (ec, moreBytes);
}
else
Receive ();
}
else
Receive ();
LogPrint (eLogWarning, "NTCP2: Socket error: ", ec.message ());
}
else
{
@ -866,8 +876,8 @@ namespace transport
{
if (ecode)
{
if (ecode != boost::asio::error::operation_aborted)
LogPrint (eLogWarning, "NTCP2: Receive read error: ", ecode.message ());
if (ecode != boost::asio::error::operation_aborted)
LogPrint (eLogWarning, "NTCP2: Receive read error: ", ecode.message ());
Terminate ();
}
else
@ -1046,6 +1056,11 @@ namespace transport
macBuf = m_NextSendBuffer + paddingLen;
totalLen += paddingLen;
}
if (totalLen > NTCP2_UNENCRYPTED_FRAME_MAX_SIZE)
{
LogPrint (eLogError, "NTCP2: Frame to send is too long ", totalLen);
return;
}
uint8_t nonce[12];
CreateNonce (m_SendSequenceNumber, nonce); m_SendSequenceNumber++;
i2p::crypto::AEADChaCha20Poly1305Encrypt (encryptBufs, m_SendKey, nonce, macBuf); // encrypt buffers
@ -1070,6 +1085,12 @@ namespace transport
delete[] m_NextSendBuffer; m_NextSendBuffer = nullptr;
return;
}
if (payloadLen > NTCP2_UNENCRYPTED_FRAME_MAX_SIZE)
{
LogPrint (eLogError, "NTCP2: Buffer to send is too long ", payloadLen);
delete[] m_NextSendBuffer; m_NextSendBuffer = nullptr;
return;
}
// encrypt
uint8_t nonce[12];
CreateNonce (m_SendSequenceNumber, nonce); m_SendSequenceNumber++;
@ -1113,23 +1134,34 @@ namespace transport
void NTCP2Session::SendQueue ()
{
if (!m_SendQueue.empty ())
if (!m_SendQueue.empty () && m_IsEstablished)
{
std::vector<std::shared_ptr<I2NPMessage> > msgs;
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
size_t s = 0;
while (!m_SendQueue.empty ())
{
auto msg = m_SendQueue.front ();
if (!msg || msg->IsExpired (ts))
{
// drop null or expired message
if (msg) msg->Drop ();
m_SendQueue.pop_front ();
continue;
}
size_t len = msg->GetNTCP2Length ();
if (s + len + 3 <= NTCP2_UNENCRYPTED_FRAME_MAX_SIZE) // 3 bytes block header
{
msgs.push_back (msg);
s += (len + 3);
m_SendQueue.pop_front ();
if (s >= NTCP2_SEND_AFTER_FRAME_SIZE)
break; // send frame right a way
}
else if (len + 3 > NTCP2_UNENCRYPTED_FRAME_MAX_SIZE)
{
LogPrint (eLogError, "NTCP2: I2NP message of size ", len, " can't be sent. Dropped");
msg->Drop ();
m_SendQueue.pop_front ();
}
else
@ -1139,13 +1171,33 @@ namespace transport
}
}
void NTCP2Session::MoveSendQueue (std::shared_ptr<NTCP2Session> other)
{
if (!other || m_SendQueue.empty ()) return;
std::vector<std::shared_ptr<I2NPMessage> > msgs;
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
for (auto it: m_SendQueue)
if (!it->IsExpired (ts))
msgs.push_back (it);
else
it->Drop ();
m_SendQueue.clear ();
if (!msgs.empty ())
other->PostI2NPMessages (msgs);
}
size_t NTCP2Session::CreatePaddingBlock (size_t msgLen, uint8_t * buf, size_t len)
{
if (len < 3) return 0;
len -= 3;
if (msgLen < 256) msgLen = 256; // for short message padding should not be always zero
size_t paddingSize = (msgLen*NTCP2_MAX_PADDING_RATIO)/100;
if (msgLen + paddingSize + 3 > NTCP2_UNENCRYPTED_FRAME_MAX_SIZE) paddingSize = NTCP2_UNENCRYPTED_FRAME_MAX_SIZE - msgLen -3;
if (msgLen + paddingSize + 3 > NTCP2_UNENCRYPTED_FRAME_MAX_SIZE)
{
int l = (int)NTCP2_UNENCRYPTED_FRAME_MAX_SIZE - msgLen -3;
if (l <= 0) return 0;
paddingSize = l;
}
if (paddingSize > len) paddingSize = len;
if (paddingSize)
{
@ -1154,7 +1206,7 @@ namespace transport
RAND_bytes ((uint8_t *)m_PaddingSizes, sizeof (m_PaddingSizes));
m_NextPaddingSize = 0;
}
paddingSize = m_PaddingSizes[m_NextPaddingSize++] % paddingSize;
paddingSize = m_PaddingSizes[m_NextPaddingSize++] % (paddingSize + 1);
}
buf[0] = eNTCP2BlkPadding; // blk
htobe16buf (buf + 1, paddingSize); // size
@ -1165,7 +1217,8 @@ namespace transport
void NTCP2Session::SendRouterInfo ()
{
if (!IsEstablished ()) return;
auto riLen = i2p::context.GetRouterInfo ().GetBufferLen ();
auto riBuffer = i2p::context.CopyRouterInfoBuffer ();
auto riLen = riBuffer->GetBufferLen ();
size_t payloadLen = riLen + 3 + 1 + 7; // 3 bytes block header + 1 byte RI flag + 7 bytes DateTime
m_NextSendBuffer = new uint8_t[payloadLen + 16 + 2 + 64]; // up to 64 bytes padding
// DateTime block
@ -1176,7 +1229,7 @@ namespace transport
m_NextSendBuffer[9] = eNTCP2BlkRouterInfo;
htobe16buf (m_NextSendBuffer + 10, riLen + 1); // size
m_NextSendBuffer[12] = 0; // flag
memcpy (m_NextSendBuffer + 13, i2p::context.GetRouterInfo ().GetBuffer (), riLen);
memcpy (m_NextSendBuffer + 13, riBuffer->data (), riLen); // TODO: eliminate extra copy
// padding block
auto paddingSize = CreatePaddingBlock (payloadLen, m_NextSendBuffer + 2 + payloadLen, 64);
payloadLen += paddingSize;
@ -1219,9 +1272,14 @@ namespace transport
void NTCP2Session::PostI2NPMessages (std::vector<std::shared_ptr<I2NPMessage> > msgs)
{
if (m_IsTerminated) return;
bool isSemiFull = m_SendQueue.size () > NTCP2_MAX_OUTGOING_QUEUE_SIZE/2;
for (auto it: msgs)
m_SendQueue.push_back (std::move (it));
if (!m_IsSending)
if (isSemiFull && it->onDrop)
it->Drop (); // drop earlier because we can handle it
else
m_SendQueue.push_back (std::move (it));
if (!m_IsSending && m_IsEstablished)
SendQueue ();
else if (m_SendQueue.size () > NTCP2_MAX_OUTGOING_QUEUE_SIZE)
{
@ -1234,7 +1292,7 @@ namespace transport
void NTCP2Session::SendLocalRouterInfo (bool update)
{
if (update || !IsOutgoing ()) // we send it in SessionConfirmed for ougoing session
if (update || !IsOutgoing ()) // we send it in SessionConfirmed for outgoing session
m_Server.GetService ().post (std::bind (&NTCP2Session::SendRouterInfo, shared_from_this ()));
}
@ -1384,6 +1442,7 @@ namespace transport
{
// replace by new session
auto s = it->second;
s->MoveSendQueue (session);
m_NTCP2Sessions.erase (it);
s->Terminate ();
}
@ -1400,7 +1459,11 @@ namespace transport
void NTCP2Server::RemoveNTCP2Session (std::shared_ptr<NTCP2Session> session)
{
if (session && session->GetRemoteIdentity ())
m_NTCP2Sessions.erase (session->GetRemoteIdentity ()->GetIdentHash ());
{
auto it = m_NTCP2Sessions.find (session->GetRemoteIdentity ()->GetIdentHash ());
if (it != m_NTCP2Sessions.end () && it->second == session)
m_NTCP2Sessions.erase (it);
}
}
std::shared_ptr<NTCP2Session> NTCP2Server::FindNTCP2Session (const i2p::data::IdentHash& ident)
@ -1490,7 +1553,7 @@ namespace transport
if (!ec)
{
LogPrint (eLogDebug, "NTCP2: Connected from ", ep);
if (!i2p::util::net::IsInReservedRange(ep.address ()))
if (!i2p::transport::transports.IsInReservedRange(ep.address ()))
{
if (m_PendingIncomingSessions.emplace (ep.address (), conn).second)
{
@ -1537,7 +1600,7 @@ namespace transport
if (!ec)
{
LogPrint (eLogDebug, "NTCP2: Connected from ", ep);
if (!i2p::util::net::IsInReservedRange(ep.address ()) ||
if (!i2p::transport::transports.IsInReservedRange(ep.address ()) ||
i2p::util::net::IsYggdrasilAddress (ep.address ()))
{
if (m_PendingIncomingSessions.emplace (ep.address (), conn).second)
@ -1685,47 +1748,18 @@ namespace transport
case eSocksProxy:
{
// TODO: support username/password auth etc
static const uint8_t buff[3] = {SOCKS5_VER, 0x01, 0x00};
boost::asio::async_write(conn->GetSocket(), boost::asio::buffer(buff, 3), boost::asio::transfer_all(),
[] (const boost::system::error_code & ec, std::size_t transferred)
{
(void) transferred;
if(ec)
{
LogPrint(eLogWarning, "NTCP2: SOCKS5 write error ", ec.message());
}
});
auto readbuff = std::make_shared<std::vector<uint8_t> >(2);
boost::asio::async_read(conn->GetSocket(), boost::asio::buffer(readbuff->data (), 2),
[this, readbuff, timer, conn](const boost::system::error_code & ec, std::size_t transferred)
{
if(ec)
{
LogPrint(eLogError, "NTCP2: SOCKS5 read error ", ec.message());
timer->cancel();
conn->Terminate();
return;
}
else if(transferred == 2)
{
if((*readbuff)[1] == 0x00)
{
AfterSocksHandshake(conn, timer);
return;
}
else if ((*readbuff)[1] == 0xff)
{
LogPrint(eLogError, "NTCP2: SOCKS5 proxy rejected authentication");
timer->cancel();
conn->Terminate();
return;
}
LogPrint(eLogError, "NTCP2:", (int)(*readbuff)[1]);
}
LogPrint(eLogError, "NTCP2: SOCKS5 server gave invalid response");
Socks5Handshake (conn->GetSocket(), conn->GetRemoteEndpoint (),
[conn, timer](const boost::system::error_code& ec)
{
timer->cancel();
conn->Terminate();
});
if (!ec)
conn->ClientLogin();
else
{
LogPrint(eLogError, "NTCP2: SOCKS proxy handshake error ", ec.message());
conn->Terminate();
}
});
break;
}
case eHTTPProxy:
@ -1793,71 +1827,6 @@ namespace transport
}
}
void NTCP2Server::AfterSocksHandshake(std::shared_ptr<NTCP2Session> conn, std::shared_ptr<boost::asio::deadline_timer> timer)
{
// build request
size_t sz = 6; // header + port
auto buff = std::make_shared<std::vector<int8_t> >(256);
auto readbuff = std::make_shared<std::vector<int8_t> >(256);
(*buff)[0] = SOCKS5_VER;
(*buff)[1] = SOCKS5_CMD_CONNECT;
(*buff)[2] = 0x00;
auto& ep = conn->GetRemoteEndpoint ();
if(ep.address ().is_v4 ())
{
(*buff)[3] = SOCKS5_ATYP_IPV4;
auto addrbytes = ep.address ().to_v4().to_bytes();
sz += 4;
memcpy(buff->data () + 4, addrbytes.data(), 4);
}
else if (ep.address ().is_v6 ())
{
(*buff)[3] = SOCKS5_ATYP_IPV6;
auto addrbytes = ep.address ().to_v6().to_bytes();
sz += 16;
memcpy(buff->data () + 4, addrbytes.data(), 16);
}
else
{
// We mustn't really fall here because all connections are made to IP addresses
LogPrint(eLogError, "NTCP2: Tried to connect to unexpected address via proxy");
return;
}
htobe16buf(buff->data () + sz - 2, ep.port ());
boost::asio::async_write(conn->GetSocket(), boost::asio::buffer(buff->data (), sz), boost::asio::transfer_all(),
[buff](const boost::system::error_code & ec, std::size_t written)
{
if(ec)
{
LogPrint(eLogError, "NTCP2: Failed to write handshake to socks proxy ", ec.message());
return;
}
});
boost::asio::async_read(conn->GetSocket(), boost::asio::buffer(readbuff->data (), SOCKS5_UDP_IPV4_REQUEST_HEADER_SIZE), // read min reply size
boost::asio::transfer_all(),
[timer, conn, readbuff](const boost::system::error_code & e, std::size_t transferred)
{
if (e)
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);
if (moreBytes) // read remaining portion of reply if ipv6 received
boost::asio::read (conn->GetSocket (), boost::asio::buffer(readbuff->data (), moreBytes), boost::asio::transfer_all (), ec);
timer->cancel();
conn->ClientLogin();
return;
}
else
LogPrint(eLogError, "NTCP2: Proxy reply error ", (int)(*readbuff)[1]);
timer->cancel();
conn->Terminate();
});
}
void NTCP2Server::SetLocalAddress (const boost::asio::ip::address& localAddress)
{
auto addr = std::make_shared<boost::asio::ip::tcp::endpoint>(boost::asio::ip::tcp::endpoint(localAddress, 0));

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2022, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -28,6 +28,7 @@ namespace transport
{
const size_t NTCP2_UNENCRYPTED_FRAME_MAX_SIZE = 65519;
const size_t NTCP2_SEND_AFTER_FRAME_SIZE = 16386; // send frame when exceeds this size
const size_t NTCP2_SESSION_REQUEST_MAX_SIZE = 287;
const size_t NTCP2_SESSION_CREATED_MAX_SIZE = 287;
const int NTCP2_MAX_PADDING_RATIO = 6; // in %
@ -150,7 +151,8 @@ namespace transport
void SendLocalRouterInfo (bool update) override; // after handshake or by update
void SendI2NPMessages (const std::vector<std::shared_ptr<I2NPMessage> >& msgs) override;
void MoveSendQueue (std::shared_ptr<NTCP2Session> other);
private:
void Established ();
@ -266,8 +268,7 @@ namespace transport
void HandleConnect (const boost::system::error_code& ecode, std::shared_ptr<NTCP2Session> conn, std::shared_ptr<boost::asio::deadline_timer> timer);
void HandleProxyConnect(const boost::system::error_code& ecode, std::shared_ptr<NTCP2Session> conn, std::shared_ptr<boost::asio::deadline_timer> timer);
void AfterSocksHandshake(std::shared_ptr<NTCP2Session> conn, std::shared_ptr<boost::asio::deadline_timer> timer);
// timer
void ScheduleTermination ();
void HandleTerminationTimer (const boost::system::error_code& ecode);

File diff suppressed because it is too large Load Diff

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2023, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -10,11 +10,12 @@
#define NETDB_H__
// this file is called NetDb.hpp to resolve conflict with libc's netdb.h on case insensitive fs
#include <inttypes.h>
#include <set>
#include <unordered_set>
#include <unordered_map>
#include <string>
#include <thread>
#include <mutex>
#include <future>
#include "Base.h"
#include "Gzip.h"
@ -38,8 +39,10 @@ namespace data
{
const int NETDB_MIN_ROUTERS = 90;
const int NETDB_MIN_FLOODFILLS = 5;
const int NETDB_NUM_FLOODFILLS_THRESHOLD = 1500;
const int NETDB_NUM_FLOODFILLS_THRESHOLD = 1000;
const int NETDB_NUM_ROUTERS_THRESHOLD = 4*NETDB_NUM_FLOODFILLS_THRESHOLD;
const int NETDB_TUNNEL_CREATION_RATE_THRESHOLD = 10; // in %
const int NETDB_CHECK_FOR_EXPIRATION_UPTIME = 600; // 10 minutes, in seconds
const int NETDB_FLOODFILL_EXPIRATION_TIMEOUT = 60 * 60; // 1 hour, in seconds
const int NETDB_MIN_EXPIRATION_TIMEOUT = 90 * 60; // 1.5 hours
const int NETDB_MAX_EXPIRATION_TIMEOUT = 27 * 60 * 60; // 27 hours
@ -48,6 +51,11 @@ namespace data
const int NETDB_MIN_HIGHBANDWIDTH_VERSION = MAKE_VERSION_NUMBER(0, 9, 51); // 0.9.51
const int NETDB_MIN_FLOODFILL_VERSION = MAKE_VERSION_NUMBER(0, 9, 51); // 0.9.51
const int NETDB_MIN_SHORT_TUNNEL_BUILD_VERSION = MAKE_VERSION_NUMBER(0, 9, 51); // 0.9.51
const size_t NETDB_MAX_NUM_SEARCH_REPLY_PEER_HASHES = 16;
const size_t NETDB_MAX_EXPLORATORY_SELECTION_SIZE = 500;
const int NETDB_EXPLORATORY_SELECTION_UPDATE_INTERVAL = 82; // in seconds. for floodfill
const int NETDB_NEXT_DAY_ROUTER_INFO_THRESHOLD = 45; // in minutes
const int NETDB_NEXT_DAY_LEASESET_THRESHOLD = 10; // in minutes
/** function for visiting a leaseset stored in a floodfill */
typedef std::function<void(const IdentHash, std::shared_ptr<LeaseSet>)> LeaseSetVisitor;
@ -77,27 +85,22 @@ namespace data
std::shared_ptr<RouterProfile> FindRouterProfile (const IdentHash& ident) const;
void RequestDestination (const IdentHash& destination, RequestedDestination::RequestComplete requestComplete = nullptr, bool direct = true);
void RequestDestinationFrom (const IdentHash& destination, const IdentHash & from, bool exploritory, RequestedDestination::RequestComplete requestComplete = nullptr);
void HandleDatabaseStoreMsg (std::shared_ptr<const I2NPMessage> msg);
void HandleDatabaseSearchReplyMsg (std::shared_ptr<const I2NPMessage> msg);
void HandleDatabaseLookupMsg (std::shared_ptr<const I2NPMessage> msg);
void HandleNTCP2RouterInfoMsg (std::shared_ptr<const I2NPMessage> m);
std::shared_ptr<const RouterInfo> GetRandomRouter () const;
std::shared_ptr<const RouterInfo> GetRandomRouter (std::shared_ptr<const RouterInfo> compatibleWith, bool reverse, bool endpoint) const;
std::shared_ptr<const RouterInfo> GetHighBandwidthRandomRouter (std::shared_ptr<const RouterInfo> compatibleWith, bool reverse, bool endpoint) const;
std::shared_ptr<const RouterInfo> GetRandomSSU2PeerTestRouter (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) const;
std::shared_ptr<const RouterInfo> GetRandomSSU2PeerTestRouter (bool v4, const std::unordered_set<IdentHash>& excluded) const;
std::shared_ptr<const RouterInfo> GetRandomSSU2Introducer (bool v4, const std::unordered_set<IdentHash>& excluded) const;
std::shared_ptr<const RouterInfo> GetClosestFloodfill (const IdentHash& destination, const std::unordered_set<IdentHash>& excluded, bool nextDay = false) const;
std::vector<IdentHash> GetClosestFloodfills (const IdentHash& destination, size_t num,
std::set<IdentHash>& excluded, bool closeThanUsOnly = false) const;
std::shared_ptr<const RouterInfo> GetClosestNonFloodfill (const IdentHash& destination, const std::set<IdentHash>& excluded) const;
std::unordered_set<IdentHash>& excluded, bool closeThanUsOnly = false) const;
std::vector<IdentHash> GetExploratoryNonFloodfill (const IdentHash& destination, size_t num, const std::unordered_set<IdentHash>& excluded);
std::shared_ptr<const RouterInfo> GetRandomRouterInFamily (FamilyID fam) const;
void SetUnreachable (const IdentHash& ident, bool unreachable);
void ExcludeReachableTransports (const IdentHash& ident, RouterInfo::CompatibleTransports transports);
void PostI2NPMsg (std::shared_ptr<const I2NPMessage> msg);
void PostDatabaseSearchReplyMsg (std::shared_ptr<const I2NPMessage> msg); // to NetdbReq thread
void Reseed ();
Families& GetFamilies () { return m_Families; };
@ -117,7 +120,11 @@ namespace data
size_t VisitRandomRouterInfos(RouterInfoFilter f, RouterInfoVisitor v, size_t n);
void ClearRouterInfos () { m_RouterInfos.clear (); };
std::shared_ptr<RouterInfo::Buffer> NewRouterInfoBuffer () { return m_RouterInfoBuffersPool.AcquireSharedMt (); };
template<typename... TArgs>
std::shared_ptr<RouterInfo::Buffer> NewRouterInfoBuffer (TArgs&&... args)
{
return m_RouterInfoBuffersPool.AcquireSharedMt (std::forward<TArgs>(args)...);
}
bool PopulateRouterInfoBuffer (std::shared_ptr<RouterInfo> r);
std::shared_ptr<RouterInfo::Address> NewRouterInfoAddress () { return m_RouterInfoAddressesPool.AcquireSharedMt (); };
boost::shared_ptr<RouterInfo::Addresses> NewRouterInfoAddresses ()
@ -131,16 +138,15 @@ namespace data
std::shared_ptr<IdentityEx> NewIdentity (const uint8_t * buf, size_t len) { return m_IdentitiesPool.AcquireSharedMt (buf, len); };
std::shared_ptr<RouterProfile> NewRouterProfile () { return m_RouterProfilesPool.AcquireSharedMt (); };
uint32_t GetPublishReplyToken () const { return m_PublishReplyToken; };
private:
void Load ();
bool LoadRouterInfo (const std::string& path, uint64_t ts);
void SaveUpdated ();
void Run (); // exploratory thread
void Explore (int numDestinations);
void Flood (const IdentHash& ident, std::shared_ptr<I2NPMessage> floodMsg);
void PersistRouters (std::list<std::pair<std::string, std::shared_ptr<RouterInfo::Buffer> > >&& update,
std::list<std::string>&& remove);
void Run ();
void Flood (const IdentHash& ident, std::shared_ptr<I2NPMessage> floodMsg, bool andNextDay = false);
void ManageRouterInfos ();
void ManageLeaseSets ();
void ManageRequests ();
@ -153,6 +159,10 @@ namespace data
template<typename Filter>
std::shared_ptr<const RouterInfo> GetRandomRouter (Filter filter) const;
void HandleDatabaseStoreMsg (std::shared_ptr<const I2NPMessage> msg);
void HandleDatabaseLookupMsg (std::shared_ptr<const I2NPMessage> msg);
void HandleNTCP2RouterInfoMsg (std::shared_ptr<const I2NPMessage> m);
private:
mutable std::mutex m_LeaseSetsMutex;
@ -171,16 +181,13 @@ namespace data
Families m_Families;
i2p::fs::HashedStorage m_Storage;
friend class NetDbRequests;
NetDbRequests m_Requests;
std::shared_ptr<NetDbRequests> m_Requests;
bool m_PersistProfiles;
std::future<void> m_SavingProfiles, m_DeletingProfiles, m_PersistingRouters;
/** router info we are bootstrapping from or nullptr if we are not currently doing that*/
std::shared_ptr<RouterInfo> m_FloodfillBootstrap;
std::set<IdentHash> m_PublishExcluded;
uint32_t m_PublishReplyToken = 0;
std::vector<std::shared_ptr<const RouterInfo> > m_ExploratorySelection;
uint64_t m_LastExploratorySelectionUpdateTime; // in monotonic seconds
i2p::util::MemoryPoolMt<RouterInfo::Buffer> m_RouterInfoBuffersPool;
i2p::util::MemoryPoolMt<RouterInfo::Address> m_RouterInfoAddressesPool;

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2023, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -10,12 +10,28 @@
#include "I2NPProtocol.h"
#include "Transports.h"
#include "NetDb.hpp"
#include "ECIESX25519AEADRatchetSession.h"
#include "RouterContext.h"
#include "Timestamp.h"
#include "NetDbRequests.h"
namespace i2p
{
namespace data
{
RequestedDestination::RequestedDestination (const IdentHash& destination, bool isExploratory, bool direct):
m_Destination (destination), m_IsExploratory (isExploratory), m_IsDirect (direct), m_IsActive (true),
m_CreationTime (i2p::util::GetSecondsSinceEpoch ()), m_LastRequestTime (0), m_NumAttempts (0)
{
if (i2p::context.IsFloodfill ())
m_ExcludedPeers.insert (i2p::context.GetIdentHash ()); // exclude self if floodfill
}
RequestedDestination::~RequestedDestination ()
{
InvokeRequestComplete (nullptr);
}
std::shared_ptr<I2NPMessage> RequestedDestination::CreateRequestMessage (std::shared_ptr<const RouterInfo> router,
std::shared_ptr<const i2p::tunnel::InboundTunnel> replyTunnel)
{
@ -28,7 +44,8 @@ namespace data
msg = i2p::CreateRouterInfoDatabaseLookupMsg(m_Destination, i2p::context.GetIdentHash(), 0, m_IsExploratory, &m_ExcludedPeers);
if(router)
m_ExcludedPeers.insert (router->GetIdentHash ());
m_CreationTime = i2p::util::GetSecondsSinceEpoch ();
m_LastRequestTime = i2p::util::GetSecondsSinceEpoch ();
m_NumAttempts++;
return msg;
}
@ -37,80 +54,154 @@ namespace data
auto msg = i2p::CreateRouterInfoDatabaseLookupMsg (m_Destination,
i2p::context.GetRouterInfo ().GetIdentHash () , 0, false, &m_ExcludedPeers);
m_ExcludedPeers.insert (floodfill);
m_CreationTime = i2p::util::GetSecondsSinceEpoch ();
m_NumAttempts++;
m_LastRequestTime = i2p::util::GetSecondsSinceEpoch ();
return msg;
}
bool RequestedDestination::IsExcluded (const IdentHash& ident) const
{
return m_ExcludedPeers.count (ident);
}
void RequestedDestination::ClearExcludedPeers ()
{
m_ExcludedPeers.clear ();
}
void RequestedDestination::InvokeRequestComplete (std::shared_ptr<RouterInfo> r)
{
if (!m_RequestComplete.empty ())
{
for (auto it: m_RequestComplete)
if (it != nullptr) it (r);
m_RequestComplete.clear ();
}
}
void RequestedDestination::Success (std::shared_ptr<RouterInfo> r)
{
if (m_RequestComplete)
{
m_RequestComplete (r);
m_RequestComplete = nullptr;
}
if (m_IsActive)
{
m_IsActive = false;
InvokeRequestComplete (r);
}
}
void RequestedDestination::Fail ()
{
if (m_RequestComplete)
{
m_RequestComplete (nullptr);
m_RequestComplete = nullptr;
}
if (m_IsActive)
{
m_IsActive = false;
InvokeRequestComplete (nullptr);
}
}
NetDbRequests::NetDbRequests ():
RunnableServiceWithWork ("NetDbReq"),
m_ManageRequestsTimer (GetIOService ()), m_ExploratoryTimer (GetIOService ()),
m_CleanupTimer (GetIOService ()), m_DiscoveredRoutersTimer (GetIOService ()),
m_Rng(i2p::util::GetMonotonicMicroseconds () % 1000000LL)
{
}
NetDbRequests::~NetDbRequests ()
{
Stop ();
}
void NetDbRequests::Start ()
{
if (!IsRunning ())
{
StartIOService ();
ScheduleManageRequests ();
ScheduleCleanup ();
if (!i2p::context.IsHidden ())
ScheduleExploratory (EXPLORATORY_REQUEST_INTERVAL);
}
}
void NetDbRequests::Stop ()
{
m_RequestedDestinations.clear ();
if (IsRunning ())
{
m_ManageRequestsTimer.cancel ();
m_ExploratoryTimer.cancel ();
m_CleanupTimer.cancel ();
StopIOService ();
m_RequestedDestinations.clear ();
m_RequestedDestinationsPool.CleanUpMt ();
}
}
std::shared_ptr<RequestedDestination> NetDbRequests::CreateRequest (const IdentHash& destination, bool isExploratory, RequestedDestination::RequestComplete requestComplete)
void NetDbRequests::ScheduleCleanup ()
{
// request RouterInfo directly
auto dest = std::make_shared<RequestedDestination> (destination, isExploratory);
dest->SetRequestComplete (requestComplete);
m_CleanupTimer.expires_from_now (boost::posix_time::seconds(REQUESTED_DESTINATIONS_POOL_CLEANUP_INTERVAL));
m_CleanupTimer.async_wait (std::bind (&NetDbRequests::HandleCleanupTimer,
this, std::placeholders::_1));
}
void NetDbRequests::HandleCleanupTimer (const boost::system::error_code& ecode)
{
if (ecode != boost::asio::error::operation_aborted)
{
std::unique_lock<std::mutex> l(m_RequestedDestinationsMutex);
if (!m_RequestedDestinations.insert (std::make_pair (destination, dest)).second) // not inserted
return nullptr;
}
m_RequestedDestinationsPool.CleanUpMt ();
ScheduleCleanup ();
}
}
std::shared_ptr<RequestedDestination> NetDbRequests::CreateRequest (const IdentHash& destination,
bool isExploratory, bool direct, RequestedDestination::RequestComplete requestComplete)
{
// request RouterInfo directly
auto dest = m_RequestedDestinationsPool.AcquireSharedMt (destination, isExploratory, direct);
if (requestComplete)
dest->AddRequestComplete (requestComplete);
auto ret = m_RequestedDestinations.emplace (destination, dest);
if (!ret.second) // not inserted
{
dest->ResetRequestComplete (); // don't call requestComplete in destructor
dest = ret.first->second; // existing one
if (requestComplete)
{
if (dest->IsActive ())
dest->AddRequestComplete (requestComplete);
else
requestComplete (nullptr);
}
return nullptr;
}
return dest;
}
void NetDbRequests::RequestComplete (const IdentHash& ident, std::shared_ptr<RouterInfo> r)
{
std::shared_ptr<RequestedDestination> request;
{
std::unique_lock<std::mutex> l(m_RequestedDestinationsMutex);
auto it = m_RequestedDestinations.find (ident);
if (it != m_RequestedDestinations.end ())
{
request = it->second;
m_RequestedDestinations.erase (it);
}
}
if (request)
{
if (r)
request->Success (r);
else
request->Fail ();
}
GetIOService ().post ([this, ident, r]()
{
std::shared_ptr<RequestedDestination> request;
auto it = m_RequestedDestinations.find (ident);
if (it != m_RequestedDestinations.end ())
{
request = it->second;
if (request->IsExploratory ())
m_RequestedDestinations.erase (it);
// otherwise cache for a while
}
if (request)
{
if (r)
request->Success (r);
else
request->Fail ();
}
});
}
std::shared_ptr<RequestedDestination> NetDbRequests::FindRequest (const IdentHash& ident) const
{
std::unique_lock<std::mutex> l(m_RequestedDestinationsMutex);
auto it = m_RequestedDestinations.find (ident);
if (it != m_RequestedDestinations.end ())
return it->second;
@ -120,49 +211,345 @@ namespace data
void NetDbRequests::ManageRequests ()
{
uint64_t ts = i2p::util::GetSecondsSinceEpoch ();
std::unique_lock<std::mutex> l(m_RequestedDestinationsMutex);
for (auto it = m_RequestedDestinations.begin (); it != m_RequestedDestinations.end ();)
{
auto& dest = it->second;
bool done = false;
if (ts < dest->GetCreationTime () + 60) // request is worthless after 1 minute
{
if (ts > dest->GetCreationTime () + 5) // no response for 5 seconds
{
auto count = dest->GetExcludedPeers ().size ();
if (!dest->IsExploratory () && count < 7)
if (dest->IsActive () || ts < dest->GetCreationTime () + REQUEST_CACHE_TIME)
{
if (!dest->IsExploratory ())
{
// regular request
bool done = false;
if (ts < dest->GetCreationTime () + MAX_REQUEST_TIME)
{
if (ts > dest->GetLastRequestTime () + MIN_REQUEST_TIME) // try next floodfill if no response after min interval
done = !SendNextRequest (dest);
}
else // request is expired
done = true;
if (done)
dest->Fail ();
it++;
}
else
{
// exploratory
if (ts >= dest->GetCreationTime () + MAX_EXPLORATORY_REQUEST_TIME)
{
auto pool = i2p::tunnel::tunnels.GetExploratoryPool ();
dest->Fail ();
it = m_RequestedDestinations.erase (it); // delete expired exploratory request right a way
}
else
it++;
}
}
else
it = m_RequestedDestinations.erase (it);
}
}
bool NetDbRequests::SendNextRequest (std::shared_ptr<RequestedDestination> dest)
{
if (!dest || !dest->IsActive ()) return false;
bool ret = true;
auto count = dest->GetNumAttempts ();
if (!dest->IsExploratory () && count < MAX_NUM_REQUEST_ATTEMPTS)
{
auto nextFloodfill = netdb.GetClosestFloodfill (dest->GetDestination (), dest->GetExcludedPeers ());
if (nextFloodfill)
{
bool direct = dest->IsDirect ();
if (direct && !nextFloodfill->IsReachableFrom (i2p::context.GetRouterInfo ()) &&
!i2p::transport::transports.IsConnected (nextFloodfill->GetIdentHash ()))
direct = false; // floodfill can't be reached directly
auto s = shared_from_this ();
auto onDrop = [s, dest]()
{
if (dest->IsActive ())
{
s->GetIOService ().post ([s, dest]()
{
if (dest->IsActive ()) s->SendNextRequest (dest);
});
}
};
if (direct)
{
if (CheckLogLevel (eLogDebug))
LogPrint (eLogDebug, "NetDbReq: Try ", dest->GetDestination ().ToBase64 (), " at ", count, " floodfill ", nextFloodfill->GetIdentHash ().ToBase64 (), " directly");
auto msg = dest->CreateRequestMessage (nextFloodfill->GetIdentHash ());
msg->onDrop = onDrop;
i2p::transport::transports.SendMessage (nextFloodfill->GetIdentHash (), msg);
}
else
{
auto pool = i2p::tunnel::tunnels.GetExploratoryPool ();
if (pool)
{
auto outbound = pool->GetNextOutboundTunnel ();
auto inbound = pool->GetNextInboundTunnel ();
auto nextFloodfill = netdb.GetClosestFloodfill (dest->GetDestination (), dest->GetExcludedPeers ());
if (nextFloodfill && outbound && inbound)
{
if (CheckLogLevel (eLogDebug))
LogPrint (eLogDebug, "NetDbReq: Try ", dest->GetDestination ().ToBase64 (), " at ", count, " floodfill ", nextFloodfill->GetIdentHash ().ToBase64 (), " through tunnels");
auto msg = dest->CreateRequestMessage (nextFloodfill, inbound);
msg->onDrop = onDrop;
outbound->SendTunnelDataMsgTo (nextFloodfill->GetIdentHash (), 0,
dest->CreateRequestMessage (nextFloodfill, inbound));
i2p::garlic::WrapECIESX25519MessageForRouter (msg, nextFloodfill->GetIdentity ()->GetEncryptionPublicKey ()));
}
else
{
done = true;
ret = false;
if (!inbound) LogPrint (eLogWarning, "NetDbReq: No inbound tunnels");
if (!outbound) LogPrint (eLogWarning, "NetDbReq: No outbound tunnels");
if (!nextFloodfill) LogPrint (eLogWarning, "NetDbReq: No more floodfills");
}
}
}
else
{
if (!dest->IsExploratory ())
LogPrint (eLogWarning, "NetDbReq: ", dest->GetDestination ().ToBase64 (), " not found after 7 attempts");
done = true;
}
}
ret = false;
LogPrint (eLogWarning, "NetDbReq: Exploratory pool is not ready");
}
}
}
else // delete obsolete request
done = true;
else
{
ret = false;
LogPrint (eLogWarning, "NetDbReq: No more floodfills for ", dest->GetDestination ().ToBase64 (), " after ", count, "attempts");
}
}
else
{
if (!dest->IsExploratory ())
LogPrint (eLogWarning, "NetDbReq: ", dest->GetDestination ().ToBase64 (), " not found after ", MAX_NUM_REQUEST_ATTEMPTS," attempts");
ret = false;
}
return ret;
}
if (done)
it = m_RequestedDestinations.erase (it);
void NetDbRequests::ScheduleManageRequests ()
{
m_ManageRequestsTimer.expires_from_now (boost::posix_time::seconds(MANAGE_REQUESTS_INTERVAL));
m_ManageRequestsTimer.async_wait (std::bind (&NetDbRequests::HandleManageRequestsTimer,
this, std::placeholders::_1));
}
void NetDbRequests::HandleManageRequestsTimer (const boost::system::error_code& ecode)
{
if (ecode != boost::asio::error::operation_aborted)
{
if (i2p::tunnel::tunnels.GetExploratoryPool ()) // expolratory pool is ready?
ManageRequests ();
ScheduleManageRequests ();
}
}
void NetDbRequests::PostDatabaseSearchReplyMsg (std::shared_ptr<const I2NPMessage> msg)
{
GetIOService ().post ([this, msg]()
{
HandleDatabaseSearchReplyMsg (msg);
});
}
void NetDbRequests::HandleDatabaseSearchReplyMsg (std::shared_ptr<const I2NPMessage> msg)
{
const uint8_t * buf = msg->GetPayload ();
char key[48];
int l = i2p::data::ByteStreamToBase64 (buf, 32, key, 48);
key[l] = 0;
size_t num = buf[32]; // num
LogPrint (eLogDebug, "NetDbReq: DatabaseSearchReply for ", key, " num=", num);
IdentHash ident (buf);
bool isExploratory = false;
auto dest = FindRequest (ident);
if (dest && dest->IsActive ())
{
isExploratory = dest->IsExploratory ();
if (!isExploratory && (num > 0 || dest->GetNumAttempts () < 3)) // before 3-rd attempt might be just bad luck
{
// try to send next requests
if (!SendNextRequest (dest))
RequestComplete (ident, nullptr);
}
else
// no more requests for destination possible. delete it
RequestComplete (ident, nullptr);
}
else /*if (!m_FloodfillBootstrap)*/
{
LogPrint (eLogInfo, "NetDbReq: Unsolicited or late database search reply for ", key);
return;
}
// try responses
if (num > NETDB_MAX_NUM_SEARCH_REPLY_PEER_HASHES)
{
LogPrint (eLogWarning, "NetDbReq: Too many peer hashes ", num, " in database search reply, Reduced to ", NETDB_MAX_NUM_SEARCH_REPLY_PEER_HASHES);
num = NETDB_MAX_NUM_SEARCH_REPLY_PEER_HASHES;
}
if (isExploratory && !m_DiscoveredRouterHashes.empty ())
{
// request outstanding routers
for (auto it: m_DiscoveredRouterHashes)
RequestRouter (it);
m_DiscoveredRouterHashes.clear ();
m_DiscoveredRoutersTimer.cancel ();
}
for (size_t i = 0; i < num; i++)
{
IdentHash router (buf + 33 + i*32);
if (CheckLogLevel (eLogDebug))
LogPrint (eLogDebug, "NetDbReq: ", i, ": ", router.ToBase64 ());
if (isExploratory)
// postpone request
m_DiscoveredRouterHashes.push_back (router);
else
// send request right a way
RequestRouter (router);
}
if (isExploratory && !m_DiscoveredRouterHashes.empty ())
ScheduleDiscoveredRoutersRequest ();
}
void NetDbRequests::RequestRouter (const IdentHash& router)
{
auto r = netdb.FindRouter (router);
if (!r || i2p::util::GetMillisecondsSinceEpoch () > r->GetTimestamp () + 3600*1000LL)
{
// router with ident not found or too old (1 hour)
LogPrint (eLogDebug, "NetDbReq: Found new/outdated router. Requesting RouterInfo...");
if (!IsRouterBanned (router))
RequestDestination (router, nullptr, true);
else
LogPrint (eLogDebug, "NetDbReq: Router ", router.ToBase64 (), " is banned. Skipped");
}
else
LogPrint (eLogDebug, "NetDbReq: [:|||:]");
}
void NetDbRequests::PostRequestDestination (const IdentHash& destination,
const RequestedDestination::RequestComplete& requestComplete, bool direct)
{
GetIOService ().post ([this, destination, requestComplete, direct]()
{
RequestDestination (destination, requestComplete, direct);
});
}
void NetDbRequests::RequestDestination (const IdentHash& destination, const RequestedDestination::RequestComplete& requestComplete, bool direct)
{
auto dest = CreateRequest (destination, false, direct, requestComplete); // non-exploratory
if (dest)
{
if (!SendNextRequest (dest))
RequestComplete (destination, nullptr);
}
else
LogPrint (eLogWarning, "NetDbReq: Destination ", destination.ToBase64(), " is requested already or cached");
}
void NetDbRequests::Explore (int numDestinations)
{
// new requests
auto exploratoryPool = i2p::tunnel::tunnels.GetExploratoryPool ();
auto outbound = exploratoryPool ? exploratoryPool->GetNextOutboundTunnel () : nullptr;
auto inbound = exploratoryPool ? exploratoryPool->GetNextInboundTunnel () : nullptr;
bool throughTunnels = outbound && inbound;
uint8_t randomHash[32];
std::vector<i2p::tunnel::TunnelMessageBlock> msgs;
LogPrint (eLogInfo, "NetDbReq: Exploring new ", numDestinations, " routers ...");
for (int i = 0; i < numDestinations; i++)
{
RAND_bytes (randomHash, 32);
auto dest = CreateRequest (randomHash, true, !throughTunnels); // exploratory
if (!dest)
{
LogPrint (eLogWarning, "NetDbReq: Exploratory destination is requested already");
return;
}
auto floodfill = netdb.GetClosestFloodfill (randomHash, dest->GetExcludedPeers ());
if (floodfill)
{
if (i2p::transport::transports.IsConnected (floodfill->GetIdentHash ()))
throughTunnels = false;
if (throughTunnels)
{
msgs.push_back (i2p::tunnel::TunnelMessageBlock
{
i2p::tunnel::eDeliveryTypeRouter,
floodfill->GetIdentHash (), 0,
CreateDatabaseStoreMsg () // tell floodfill about us
});
msgs.push_back (i2p::tunnel::TunnelMessageBlock
{
i2p::tunnel::eDeliveryTypeRouter,
floodfill->GetIdentHash (), 0,
dest->CreateRequestMessage (floodfill, inbound) // explore
});
}
else
i2p::transport::transports.SendMessage (floodfill->GetIdentHash (), dest->CreateRequestMessage (floodfill->GetIdentHash ()));
}
else
++it;
RequestComplete (randomHash, nullptr);
}
if (throughTunnels && msgs.size () > 0)
outbound->SendTunnelDataMsgs (msgs);
}
void NetDbRequests::ScheduleExploratory (uint64_t interval)
{
m_ExploratoryTimer.expires_from_now (boost::posix_time::seconds(interval));
m_ExploratoryTimer.async_wait (std::bind (&NetDbRequests::HandleExploratoryTimer,
this, std::placeholders::_1));
}
void NetDbRequests::HandleExploratoryTimer (const boost::system::error_code& ecode)
{
if (ecode != boost::asio::error::operation_aborted)
{
auto numRouters = netdb.GetNumRouters ();
auto nextExploratoryInterval = numRouters < 2500 ? (EXPLORATORY_REQUEST_INTERVAL + m_Rng () % EXPLORATORY_REQUEST_INTERVAL)/2 :
EXPLORATORY_REQUEST_INTERVAL + m_Rng () % EXPLORATORY_REQUEST_INTERVAL_VARIANCE;
if (numRouters)
{
if (i2p::transport::transports.IsOnline () && i2p::transport::transports.IsRunning ())
{
// explore only if online
numRouters = 800/numRouters;
if (numRouters < 1) numRouters = 1;
if (numRouters > 9) numRouters = 9;
Explore (numRouters);
}
}
else
LogPrint (eLogError, "NetDbReq: No known routers, reseed seems to be totally failed");
ScheduleExploratory (nextExploratoryInterval);
}
}
void NetDbRequests::ScheduleDiscoveredRoutersRequest ()
{
m_DiscoveredRoutersTimer.expires_from_now (boost::posix_time::milliseconds(
DISCOVERED_REQUEST_INTERVAL + m_Rng () % DISCOVERED_REQUEST_INTERVAL_VARIANCE));
m_DiscoveredRoutersTimer.async_wait (std::bind (&NetDbRequests::HandleDiscoveredRoutersTimer,
this, std::placeholders::_1));
}
void NetDbRequests::HandleDiscoveredRoutersTimer (const boost::system::error_code& ecode)
{
if (ecode != boost::asio::error::operation_aborted)
{
if (!m_DiscoveredRouterHashes.empty ())
{
RequestRouter (m_DiscoveredRouterHashes.front ());
m_DiscoveredRouterHashes.pop_front ();
if (!m_DiscoveredRouterHashes.empty ()) // more hashes to request
ScheduleDiscoveredRoutersRequest ();
}
}
}
}
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2020, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -9,66 +9,118 @@
#ifndef NETDB_REQUESTS_H__
#define NETDB_REQUESTS_H__
#include <inttypes.h>
#include <memory>
#include <set>
#include <map>
#include <random>
#include <unordered_set>
#include <unordered_map>
#include <list>
#include "Identity.h"
#include "RouterInfo.h"
#include "util.h"
namespace i2p
{
namespace data
{
const int MAX_NUM_REQUEST_ATTEMPTS = 5;
const uint64_t MANAGE_REQUESTS_INTERVAL = 1; // in seconds
const uint64_t MIN_REQUEST_TIME = 5; // in seconds
const uint64_t MAX_REQUEST_TIME = MAX_NUM_REQUEST_ATTEMPTS * (MIN_REQUEST_TIME + MANAGE_REQUESTS_INTERVAL);
const uint64_t EXPLORATORY_REQUEST_INTERVAL = 55; // in seconds
const uint64_t EXPLORATORY_REQUEST_INTERVAL_VARIANCE = 170; // in seconds
const uint64_t DISCOVERED_REQUEST_INTERVAL = 360; // in milliseconds
const uint64_t DISCOVERED_REQUEST_INTERVAL_VARIANCE = 540; // in milliseconds
const uint64_t MAX_EXPLORATORY_REQUEST_TIME = 30; // in seconds
const uint64_t REQUEST_CACHE_TIME = MAX_REQUEST_TIME + 40; // in seconds
const uint64_t REQUESTED_DESTINATIONS_POOL_CLEANUP_INTERVAL = 191; // in seconds
class RequestedDestination
{
public:
typedef std::function<void (std::shared_ptr<RouterInfo>)> RequestComplete;
RequestedDestination (const IdentHash& destination, bool isExploratory = false):
m_Destination (destination), m_IsExploratory (isExploratory), m_CreationTime (0) {};
~RequestedDestination () { if (m_RequestComplete) m_RequestComplete (nullptr); };
RequestedDestination (const IdentHash& destination, bool isExploratory = false, bool direct = true);
~RequestedDestination ();
const IdentHash& GetDestination () const { return m_Destination; };
int GetNumExcludedPeers () const { return m_ExcludedPeers.size (); };
const std::set<IdentHash>& GetExcludedPeers () { return m_ExcludedPeers; };
const std::unordered_set<IdentHash>& GetExcludedPeers () const { return m_ExcludedPeers; };
int GetNumAttempts () const { return m_NumAttempts; };
void ClearExcludedPeers ();
bool IsExploratory () const { return m_IsExploratory; };
bool IsExcluded (const IdentHash& ident) const { return m_ExcludedPeers.count (ident); };
bool IsDirect () const { return m_IsDirect; };
bool IsActive () const { return m_IsActive; };
bool IsExcluded (const IdentHash& ident) const;
uint64_t GetCreationTime () const { return m_CreationTime; };
uint64_t GetLastRequestTime () const { return m_LastRequestTime; };
std::shared_ptr<I2NPMessage> CreateRequestMessage (std::shared_ptr<const RouterInfo>, std::shared_ptr<const i2p::tunnel::InboundTunnel> replyTunnel);
std::shared_ptr<I2NPMessage> CreateRequestMessage (const IdentHash& floodfill);
void SetRequestComplete (const RequestComplete& requestComplete) { m_RequestComplete = requestComplete; };
bool IsRequestComplete () const { return m_RequestComplete != nullptr; };
void AddRequestComplete (const RequestComplete& requestComplete) { m_RequestComplete.push_back (requestComplete); };
void ResetRequestComplete () { m_RequestComplete.clear (); };
void Success (std::shared_ptr<RouterInfo> r);
void Fail ();
private:
void InvokeRequestComplete (std::shared_ptr<RouterInfo> r);
private:
IdentHash m_Destination;
bool m_IsExploratory;
std::set<IdentHash> m_ExcludedPeers;
uint64_t m_CreationTime;
RequestComplete m_RequestComplete;
bool m_IsExploratory, m_IsDirect, m_IsActive;
std::unordered_set<IdentHash> m_ExcludedPeers;
uint64_t m_CreationTime, m_LastRequestTime; // in seconds
std::list<RequestComplete> m_RequestComplete;
int m_NumAttempts;
};
class NetDbRequests
class NetDbRequests: public std::enable_shared_from_this<NetDbRequests>,
private i2p::util::RunnableServiceWithWork
{
public:
NetDbRequests ();
~NetDbRequests ();
void Start ();
void Stop ();
std::shared_ptr<RequestedDestination> CreateRequest (const IdentHash& destination, bool isExploratory, RequestedDestination::RequestComplete requestComplete = nullptr);
void RequestComplete (const IdentHash& ident, std::shared_ptr<RouterInfo> r);
void PostDatabaseSearchReplyMsg (std::shared_ptr<const I2NPMessage> msg);
void PostRequestDestination (const IdentHash& destination, const RequestedDestination::RequestComplete& requestComplete, bool direct);
private:
std::shared_ptr<RequestedDestination> CreateRequest (const IdentHash& destination, bool isExploratory,
bool direct = false, RequestedDestination::RequestComplete requestComplete = nullptr);
std::shared_ptr<RequestedDestination> FindRequest (const IdentHash& ident) const;
bool SendNextRequest (std::shared_ptr<RequestedDestination> dest);
void HandleDatabaseSearchReplyMsg (std::shared_ptr<const I2NPMessage> msg);
void RequestRouter (const IdentHash& router);
void RequestDestination (const IdentHash& destination, const RequestedDestination::RequestComplete& requestComplete, bool direct);
void Explore (int numDestinations);
void ManageRequests ();
// timer
void ScheduleManageRequests ();
void HandleManageRequestsTimer (const boost::system::error_code& ecode);
void ScheduleExploratory (uint64_t interval);
void HandleExploratoryTimer (const boost::system::error_code& ecode);
void ScheduleCleanup ();
void HandleCleanupTimer (const boost::system::error_code& ecode);
void ScheduleDiscoveredRoutersRequest ();
void HandleDiscoveredRoutersTimer (const boost::system::error_code& ecode);
private:
mutable std::mutex m_RequestedDestinationsMutex;
std::map<IdentHash, std::shared_ptr<RequestedDestination> > m_RequestedDestinations;
std::unordered_map<IdentHash, std::shared_ptr<RequestedDestination> > m_RequestedDestinations;
std::list<IdentHash> m_DiscoveredRouterHashes;
i2p::util::MemoryPoolMt<RequestedDestination> m_RequestedDestinationsPool;
boost::asio::deadline_timer m_ManageRequestsTimer, m_ExploratoryTimer,
m_CleanupTimer, m_DiscoveredRoutersTimer;
std::mt19937 m_Rng;
};
}
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2023, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -245,13 +245,28 @@ namespace data
return profile;
}
bool IsRouterBanned (const IdentHash& identHash)
{
std::unique_lock<std::mutex> l(g_ProfilesMutex);
auto it = g_Profiles.find (identHash);
if (it != g_Profiles.end ())
return it->second->IsUnreachable ();
return false;
}
void InitProfilesStorage ()
{
g_ProfilesStorage.SetPlace(i2p::fs::GetDataDir());
g_ProfilesStorage.Init(i2p::data::GetBase64SubstitutionTable(), 64);
}
void PersistProfiles ()
static void SaveProfilesToDisk (std::list<std::pair<i2p::data::IdentHash, std::shared_ptr<RouterProfile> > >&& profiles)
{
for (auto& it: profiles)
if (it.second) it.second->Save (it.first);
}
std::future<void> PersistProfiles ()
{
auto ts = GetTime ();
std::list<std::pair<i2p::data::IdentHash, std::shared_ptr<RouterProfile> > > tmp;
@ -269,8 +284,9 @@ namespace data
it++;
}
}
for (auto& it: tmp)
if (it.second) it.second->Save (it.first);
if (!tmp.empty ())
return std::async (std::launch::async, SaveProfilesToDisk, std::move (tmp));
return std::future<void>();
}
void SaveProfiles ()
@ -278,8 +294,7 @@ namespace data
std::unordered_map<i2p::data::IdentHash, std::shared_ptr<RouterProfile> > tmp;
{
std::unique_lock<std::mutex> l(g_ProfilesMutex);
tmp = g_Profiles;
g_Profiles.clear ();
std::swap (tmp, g_Profiles);
}
auto ts = GetTime ();
for (auto& it: tmp)
@ -287,7 +302,29 @@ namespace data
it.second->Save (it.first);
}
void DeleteObsoleteProfiles ()
static void DeleteFilesFromDisk ()
{
std::vector<std::string> files;
g_ProfilesStorage.Traverse(files);
struct stat st;
std::time_t now = std::time(nullptr);
for (const auto& path: files)
{
if (stat(path.c_str(), &st) != 0)
{
LogPrint(eLogWarning, "Profiling: Can't stat(): ", path);
continue;
}
if (now - st.st_mtime >= PEER_PROFILE_EXPIRATION_TIMEOUT*3600)
{
LogPrint(eLogDebug, "Profiling: Removing expired peer profile: ", path);
i2p::fs::Remove(path);
}
}
}
std::future<void> DeleteObsoleteProfiles ()
{
{
auto ts = GetTime ();
@ -301,21 +338,7 @@ namespace data
}
}
struct stat st;
std::time_t now = std::time(nullptr);
std::vector<std::string> files;
g_ProfilesStorage.Traverse(files);
for (const auto& path: files) {
if (stat(path.c_str(), &st) != 0) {
LogPrint(eLogWarning, "Profiling: Can't stat(): ", path);
continue;
}
if (now - st.st_mtime >= PEER_PROFILE_EXPIRATION_TIMEOUT*3600) {
LogPrint(eLogDebug, "Profiling: Removing expired peer profile: ", path);
i2p::fs::Remove(path);
}
}
return std::async (std::launch::async, DeleteFilesFromDisk);
}
}
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2023, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -10,6 +10,7 @@
#define PROFILING_H__
#include <memory>
#include <future>
#include <boost/date_time/posix_time/posix_time.hpp>
#include "Identity.h"
@ -31,11 +32,13 @@ namespace data
const char PEER_PROFILE_USAGE_CONNECTED[] = "connected";
const int PEER_PROFILE_EXPIRATION_TIMEOUT = 36; // in hours (1.5 days)
const int PEER_PROFILE_AUTOCLEAN_TIMEOUT = 3 * 3600; // in seconds (3 hours)
const int PEER_PROFILE_AUTOCLEAN_VARIANCE = 3600; // in seconds (1 hour)
const int PEER_PROFILE_AUTOCLEAN_TIMEOUT = 1500; // in seconds (25 minutes)
const int PEER_PROFILE_AUTOCLEAN_VARIANCE = 900; // in seconds (15 minutes)
const int PEER_PROFILE_OBSOLETE_PROFILES_CLEAN_TIMEOUT = 5400; // in seconds (1.5 hours)
const int PEER_PROFILE_OBSOLETE_PROFILES_CLEAN_VARIANCE = 2400; // in seconds (40 minutes)
const int PEER_PROFILE_DECLINED_RECENTLY_INTERVAL = 150; // in seconds (2.5 minutes)
const int PEER_PROFILE_PERSIST_INTERVAL = 3300; // in seconds (55 minutes)
const int PEER_PROFILE_UNREACHABLE_INTERVAL = 2*3600; // on seconds (2 hours)
const int PEER_PROFILE_UNREACHABLE_INTERVAL = 480; // in seconds (8 minutes)
const int PEER_PROFILE_USEFUL_THRESHOLD = 3;
class RouterProfile
@ -87,10 +90,11 @@ namespace data
};
std::shared_ptr<RouterProfile> GetRouterProfile (const IdentHash& identHash);
bool IsRouterBanned (const IdentHash& identHash); // check only existing profiles
void InitProfilesStorage ();
void DeleteObsoleteProfiles ();
std::future<void> DeleteObsoleteProfiles ();
void SaveProfiles ();
void PersistProfiles ();
std::future<void> PersistProfiles ();
}
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2023, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -26,6 +26,7 @@
#include "HTTP.h"
#include "util.h"
#include "Config.h"
#include "Socks5.h"
namespace i2p
{
@ -615,62 +616,21 @@ namespace data
{
// assume socks if not http, is checked before this for other types
// TODO: support username/password auth etc
uint8_t hs_writebuf[3] = {0x05, 0x01, 0x00};
uint8_t hs_readbuf[2];
boost::asio::write(sock, boost::asio::buffer(hs_writebuf, 3), boost::asio::transfer_all(), ecode);
if(ecode)
bool success = false;
i2p::transport::Socks5Handshake (sock, std::make_pair(url.host, url.port),
[&success](const boost::system::error_code& ec)
{
if (!ec)
success = true;
else
LogPrint (eLogError, "Reseed: SOCKS handshake failed: ", ec.message());
});
service.run (); // execute all async operations
if (!success)
{
sock.close();
LogPrint(eLogError, "Reseed: SOCKS handshake write failed: ", ecode.message());
return "";
}
boost::asio::read(sock, boost::asio::buffer(hs_readbuf, 2), ecode);
if(ecode)
{
sock.close();
LogPrint(eLogError, "Reseed: SOCKS handshake read failed: ", ecode.message());
return "";
}
size_t sz = 0;
uint8_t buf[256];
buf[0] = 0x05;
buf[1] = 0x01;
buf[2] = 0x00;
buf[3] = 0x03;
sz += 4;
size_t hostsz = url.host.size();
if(1 + 2 + hostsz + sz > sizeof(buf))
{
sock.close();
LogPrint(eLogError, "Reseed: SOCKS handshake failed, hostname too big: ", url.host);
return "";
}
buf[4] = (uint8_t) hostsz;
memcpy(buf+5, url.host.c_str(), hostsz);
sz += hostsz + 1;
htobe16buf(buf+sz, url.port);
sz += 2;
boost::asio::write(sock, boost::asio::buffer(buf, sz), boost::asio::transfer_all(), ecode);
if(ecode)
{
sock.close();
LogPrint(eLogError, "Reseed: SOCKS handshake failed writing: ", ecode.message());
return "";
}
boost::asio::read(sock, boost::asio::buffer(buf, 10), ecode);
if(ecode)
{
sock.close();
LogPrint(eLogError, "Reseed: SOCKS handshake failed reading: ", ecode.message());
return "";
}
if(buf[1] != 0x00)
{
sock.close();
LogPrint(eLogError, "Reseed: SOCKS handshake bad reply code: ", std::to_string(buf[1]));
return "";
}
}
}
}
}
@ -687,18 +647,16 @@ namespace data
while (it != end)
{
boost::asio::ip::tcp::endpoint ep = *it;
if (
(
!i2p::util::net::IsInReservedRange(ep.address ()) && (
(ep.address ().is_v4 () && i2p::context.SupportsV4 ()) ||
(ep.address ().is_v6 () && i2p::context.SupportsV6 ())
)
) ||
(
i2p::util::net::IsYggdrasilAddress (ep.address ()) &&
i2p::context.SupportsMesh ()
)
)
bool supported = false;
if (!ep.address ().is_unspecified ())
{
if (ep.address ().is_v4 ())
supported = i2p::context.SupportsV4 ();
else if (ep.address ().is_v6 ())
supported = i2p::util::net::IsYggdrasilAddress (ep.address ()) ?
i2p::context.SupportsMesh () : i2p::context.SupportsV6 ();
}
if (supported)
{
s.lowest_layer().connect (ep, ecode);
if (!ecode)

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2023, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -40,7 +40,7 @@ namespace i2p
void RouterContext::Init ()
{
srand (i2p::util::GetMillisecondsSinceEpoch () % 1000);
m_StartupTime = std::chrono::steady_clock::now();
m_StartupTime = i2p::util::GetMonotonicSeconds ();
if (!Load ())
CreateNewRouter ();
@ -57,13 +57,12 @@ namespace i2p
{
m_Service.reset (new RouterService);
m_Service->Start ();
if (!m_IsHiddenMode)
{
m_PublishTimer.reset (new boost::asio::deadline_timer (m_Service->GetService ()));
ScheduleInitialPublish ();
m_CongestionUpdateTimer.reset (new boost::asio::deadline_timer (m_Service->GetService ()));
ScheduleCongestionUpdate ();
}
m_PublishTimer.reset (new boost::asio::deadline_timer (m_Service->GetService ()));
ScheduleInitialPublish ();
m_CongestionUpdateTimer.reset (new boost::asio::deadline_timer (m_Service->GetService ()));
ScheduleCongestionUpdate ();
m_CleanupTimer.reset (new boost::asio::deadline_timer (m_Service->GetService ()));
ScheduleCleanupTimer ();
}
}
@ -78,7 +77,13 @@ namespace i2p
m_Service->Stop ();
}
}
std::shared_ptr<i2p::data::RouterInfo::Buffer> RouterContext::CopyRouterInfoBuffer () const
{
std::lock_guard<std::mutex> l(m_RouterInfoMutex);
return m_RouterInfo.CopyBuffer ();
}
void RouterContext::CreateNewRouter ()
{
m_Keys = i2p::data::PrivateKeys::CreateRandomKeys (i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519,
@ -247,7 +252,10 @@ namespace i2p
void RouterContext::UpdateRouterInfo ()
{
m_RouterInfo.CreateBuffer (m_Keys);
{
std::lock_guard<std::mutex> l(m_RouterInfoMutex);
m_RouterInfo.CreateBuffer (m_Keys);
}
m_RouterInfo.SaveToFile (i2p::fs::DataDirPath (ROUTER_INFO));
m_LastUpdateTime = i2p::util::GetSecondsSinceEpoch ();
}
@ -303,6 +311,8 @@ namespace i2p
SetTesting (false);
if (status != m_Status)
{
LogPrint(eLogInfo, "Router: network status v4 changed ",
ROUTER_STATUS_NAMES[m_Status], " -> ", ROUTER_STATUS_NAMES[status]);
m_Status = status;
switch (m_Status)
{
@ -323,6 +333,8 @@ namespace i2p
SetTestingV6 (false);
if (status != m_StatusV6)
{
LogPrint(eLogInfo, "Router: network status v6 changed ",
ROUTER_STATUS_NAMES[m_StatusV6], " -> ", ROUTER_STATUS_NAMES[status]);
m_StatusV6 = status;
switch (m_StatusV6)
{
@ -554,10 +566,10 @@ namespace i2p
{
m_IsFloodfill = floodfill;
if (floodfill)
m_RouterInfo.UpdateCaps (m_RouterInfo.GetCaps () | i2p::data::RouterInfo::eFloodfill);
m_RouterInfo.UpdateFloodfillProperty (true);
else
{
m_RouterInfo.UpdateCaps (m_RouterInfo.GetCaps () & ~i2p::data::RouterInfo::eFloodfill);
m_RouterInfo.UpdateFloodfillProperty (false);
// we don't publish number of routers and leaseset for non-floodfill
m_RouterInfo.DeleteProperty (i2p::data::ROUTER_INFO_PROPERTY_LEASESETS);
m_RouterInfo.DeleteProperty (i2p::data::ROUTER_INFO_PROPERTY_ROUTERS);
@ -596,9 +608,9 @@ namespace i2p
{
case i2p::data::CAPS_FLAG_LOW_BANDWIDTH1 : limit = 12; type = low; break;
case i2p::data::CAPS_FLAG_LOW_BANDWIDTH2 : limit = i2p::data::LOW_BANDWIDTH_LIMIT; type = low; break; // 48
case i2p::data::CAPS_FLAG_HIGH_BANDWIDTH1 : limit = 64; type = high; break;
case i2p::data::CAPS_FLAG_HIGH_BANDWIDTH2 : limit = 128; type = high; break;
case i2p::data::CAPS_FLAG_HIGH_BANDWIDTH3 : limit = i2p::data::HIGH_BANDWIDTH_LIMIT; type = high; break; // 256
case i2p::data::CAPS_FLAG_LOW_BANDWIDTH3 : limit = 64; type = low; break;
case i2p::data::CAPS_FLAG_HIGH_BANDWIDTH1 : limit = 128; type = high; break;
case i2p::data::CAPS_FLAG_HIGH_BANDWIDTH2 : limit = i2p::data::HIGH_BANDWIDTH_LIMIT; type = high; break; // 256
case i2p::data::CAPS_FLAG_EXTRA_BANDWIDTH1 : limit = i2p::data::EXTRA_BANDWIDTH_LIMIT; type = extra; break; // 2048
case i2p::data::CAPS_FLAG_EXTRA_BANDWIDTH2 : limit = 1000000; type = unlim; break; // 1Gbyte/s
default:
@ -1131,13 +1143,14 @@ namespace i2p
return i2p::tunnel::tunnels.GetExploratoryPool ();
}
bool RouterContext::IsHighCongestion () const
int RouterContext::GetCongestionLevel (bool longTerm) const
{
return i2p::tunnel::tunnels.IsTooManyTransitTunnels () ||
i2p::transport::transports.IsBandwidthExceeded () ||
i2p::transport::transports.IsTransitBandwidthExceeded ();
}
return std::max (
i2p::tunnel::tunnels.GetCongestionLevel (),
i2p::transport::transports.GetCongestionLevel (longTerm)
);
}
void RouterContext::HandleI2NPMessage (const uint8_t * buf, size_t len)
{
i2p::HandleI2NPMessage (CreateI2NPMessage (buf, GetI2NPMessageLength (buf, len)));
@ -1145,6 +1158,13 @@ namespace i2p
bool RouterContext::HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID)
{
if (typeID == eI2NPTunnelTest)
{
// try tunnel test
auto pool = GetTunnelPool ();
if (pool && pool->ProcessTunnelTest (bufbe32toh (payload + TUNNEL_TEST_MSGID_OFFSET), bufbe64toh (payload + TUNNEL_TEST_TIMESTAMP_OFFSET)))
return true;
}
auto msg = CreateI2NPMessage (typeID, payload, len, msgID);
if (!msg) return false;
i2p::HandleI2NPMessage (msg);
@ -1199,21 +1219,30 @@ namespace i2p
else
i2p::garlic::GarlicDestination::ProcessDeliveryStatusMessage (msg);
}
void RouterContext::CleanupDestination ()
void RouterContext::SubmitECIESx25519Key (const uint8_t * key, uint64_t tag)
{
if (m_Service)
m_Service->GetService ().post ([this]()
{
this->i2p::garlic::GarlicDestination::CleanupExpiredTags ();
});
{
struct
{
uint8_t k[32];
uint64_t t;
} data;
memcpy (data.k, key, 32);
data.t = tag;
m_Service->GetService ().post ([this,data](void)
{
AddECIESx25519Key (data.k, data.t);
});
}
else
LogPrint (eLogError, "Router: service is NULL");
}
}
uint32_t RouterContext::GetUptime () const
{
return std::chrono::duration_cast<std::chrono::seconds> (std::chrono::steady_clock::now() - m_StartupTime).count ();
return i2p::util::GetMonotonicSeconds () - m_StartupTime;
}
bool RouterContext::Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const
@ -1303,7 +1332,10 @@ namespace i2p
if (m_RouterInfo.IsReachableBy (i2p::data::RouterInfo::eAllTransports))
HandlePublishTimer (ecode);
else
{
UpdateTimestamp (i2p::util::GetSecondsSinceEpoch ());
ScheduleInitialPublish ();
}
}
}
@ -1325,16 +1357,21 @@ namespace i2p
{
if (ecode != boost::asio::error::operation_aborted)
{
m_PublishExcluded.clear ();
m_PublishReplyToken = 0;
if (IsFloodfill ())
UpdateTimestamp (i2p::util::GetSecondsSinceEpoch ());
if (!m_IsHiddenMode)
{
UpdateStats (); // for floodfill
m_PublishExcluded.insert (i2p::context.GetIdentHash ()); // don't publish to ourselves
m_PublishExcluded.clear ();
m_PublishReplyToken = 0;
if (IsFloodfill ())
{
UpdateStats (); // for floodfill
m_PublishExcluded.insert (i2p::context.GetIdentHash ()); // don't publish to ourselves
}
Publish ();
SchedulePublishResend ();
}
UpdateTimestamp (i2p::util::GetSecondsSinceEpoch ());
Publish ();
SchedulePublishResend ();
else
SchedulePublish ();
}
}
@ -1354,10 +1391,20 @@ namespace i2p
uint32_t replyToken;
RAND_bytes ((uint8_t *)&replyToken, 4);
LogPrint (eLogInfo, "Router: Publishing our RouterInfo to ", i2p::data::GetIdentHashAbbreviation(floodfill->GetIdentHash ()), ". reply token=", replyToken);
if (floodfill->IsReachableFrom (i2p::context.GetRouterInfo ()) || // are we able to connect?
i2p::transport::transports.IsConnected (floodfill->GetIdentHash ())) // already connected ?
auto onDrop = [this]()
{
if (m_Service)
m_Service->GetService ().post ([this]() { HandlePublishResendTimer (boost::system::error_code ()); });
};
if (i2p::transport::transports.IsConnected (floodfill->GetIdentHash ()) || // already connected
(floodfill->IsReachableFrom (i2p::context.GetRouterInfo ()) && // are we able to connect
!i2p::transport::transports.RoutesRestricted ())) // and routes not restricted
{
// send directly
i2p::transport::transports.SendMessage (floodfill->GetIdentHash (), CreateDatabaseStoreMsg (i2p::context.GetSharedRouterInfo (), replyToken));
auto msg = CreateDatabaseStoreMsg (i2p::context.GetSharedRouterInfo (), replyToken);
msg->onDrop = onDrop;
i2p::transport::transports.SendMessage (floodfill->GetIdentHash (), msg);
}
else
{
// otherwise through exploratory
@ -1368,6 +1415,7 @@ namespace i2p
{
// encrypt for floodfill
auto msg = CreateDatabaseStoreMsg (i2p::context.GetSharedRouterInfo (), replyToken, inbound);
msg->onDrop = onDrop;
outbound->SendTunnelDataMsgTo (floodfill->GetIdentHash (), 0,
i2p::garlic::WrapECIESX25519MessageForRouter (msg, floodfill->GetIdentity ()->GetEncryptionPublicKey ()));
}
@ -1422,13 +1470,41 @@ namespace i2p
if (ecode != boost::asio::error::operation_aborted)
{
auto c = i2p::data::RouterInfo::eLowCongestion;
if (!AcceptsTunnels ())
if (!AcceptsTunnels () || !m_ShareRatio)
c = i2p::data::RouterInfo::eRejectAll;
else if (IsHighCongestion ())
c = i2p::data::RouterInfo::eHighCongestion;
else
{
int congestionLevel = GetCongestionLevel (true);
if (congestionLevel > CONGESTION_LEVEL_HIGH)
c = i2p::data::RouterInfo::eHighCongestion;
else if (congestionLevel > CONGESTION_LEVEL_MEDIUM)
c = i2p::data::RouterInfo::eMediumCongestion;
}
if (m_RouterInfo.UpdateCongestion (c))
UpdateRouterInfo ();
ScheduleCongestionUpdate ();
}
}
void RouterContext::ScheduleCleanupTimer ()
{
if (m_CleanupTimer)
{
m_CleanupTimer->cancel ();
m_CleanupTimer->expires_from_now (boost::posix_time::minutes(ROUTER_INFO_CLEANUP_INTERVAL));
m_CleanupTimer->async_wait (std::bind (&RouterContext::HandleCleanupTimer,
this, std::placeholders::_1));
}
else
LogPrint (eLogError, "Router: Cleanup timer is NULL");
}
void RouterContext::HandleCleanupTimer (const boost::system::error_code& ecode)
{
if (ecode != boost::asio::error::operation_aborted)
{
CleanupExpiredTags ();
ScheduleCleanupTimer ();
}
}
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2023, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -12,8 +12,7 @@
#include <inttypes.h>
#include <string>
#include <memory>
#include <chrono>
#include <set>
#include <unordered_set>
#include <boost/asio.hpp>
#include "Identity.h"
#include "RouterInfo.h"
@ -38,6 +37,7 @@ namespace garlic
const int ROUTER_INFO_CONFIRMATION_TIMEOUT = 5; // in seconds
const int ROUTER_INFO_MAX_PUBLISH_EXCLUDED_FLOODFILLS = 15;
const int ROUTER_INFO_CONGESTION_UPDATE_INTERVAL = 12*60; // in seconds
const int ROUTER_INFO_CLEANUP_INTERVAL = 5; // in minutes
enum RouterStatus
{
@ -48,6 +48,15 @@ namespace garlic
eRouterStatusMesh = 4
};
const char* const ROUTER_STATUS_NAMES[] =
{
"OK", // 0
"Firewalled", // 1
"Unknown", // 2
"Proxy", // 3
"Mesh" // 4
};
enum RouterError
{
eRouterErrorNone = 0,
@ -105,7 +114,8 @@ namespace garlic
return std::shared_ptr<i2p::garlic::GarlicDestination> (this,
[](i2p::garlic::GarlicDestination *) {});
}
std::shared_ptr<i2p::data::RouterInfo::Buffer> CopyRouterInfoBuffer () const;
const uint8_t * GetNTCP2StaticPublicKey () const { return m_NTCP2Keys ? m_NTCP2Keys->staticPublicKey : nullptr; };
const uint8_t * GetNTCP2StaticPrivateKey () const { return m_NTCP2Keys ? m_NTCP2Keys->staticPrivateKey : nullptr; };
const uint8_t * GetNTCP2IV () const { return m_NTCP2Keys ? m_NTCP2Keys->iv : nullptr; };
@ -136,6 +146,7 @@ namespace garlic
void SetNetID (int netID) { m_NetID = netID; };
bool DecryptTunnelBuildRecord (const uint8_t * encrypted, uint8_t * data);
bool DecryptTunnelShortRequestRecord (const uint8_t * encrypted, uint8_t * data);
void SubmitECIESx25519Key (const uint8_t * key, uint64_t tag);
void UpdatePort (int port); // called from Daemon
void UpdateAddress (const boost::asio::ip::address& host); // called from SSU2 or Daemon
@ -156,7 +167,7 @@ namespace garlic
void SetShareRatio (int percents); // 0 - 100
bool AcceptsTunnels () const { return m_AcceptsTunnels; };
void SetAcceptsTunnels (bool acceptsTunnels) { m_AcceptsTunnels = acceptsTunnels; };
bool IsHighCongestion () const;
int GetCongestionLevel (bool longTerm) const;
bool SupportsV6 () const { return m_RouterInfo.IsV6 (); };
bool SupportsV4 () const { return m_RouterInfo.IsV4 (); };
bool SupportsMesh () const { return m_RouterInfo.IsMesh (); };
@ -171,7 +182,6 @@ namespace garlic
void UpdateNTCP2V6Address (const boost::asio::ip::address& host); // called from Daemon. TODO: remove
void UpdateStats ();
void UpdateTimestamp (uint64_t ts); // in seconds, called from NetDb before publishing
void CleanupDestination (); // garlic destination
// implements LocalDestination
std::shared_ptr<const i2p::data::IdentityEx> GetIdentity () const { return m_Keys.GetPublic (); };
@ -220,6 +230,8 @@ namespace garlic
void HandlePublishResendTimer (const boost::system::error_code& ecode);
void ScheduleCongestionUpdate ();
void HandleCongestionUpdateTimer (const boost::system::error_code& ecode);
void ScheduleCleanupTimer ();
void HandleCleanupTimer (const boost::system::error_code& ecode);
private:
@ -229,7 +241,7 @@ namespace garlic
std::shared_ptr<i2p::garlic::RouterIncomingRatchetSession> m_ECIESSession;
uint64_t m_LastUpdateTime; // in seconds
bool m_AcceptsTunnels, m_IsFloodfill;
std::chrono::time_point<std::chrono::steady_clock> m_StartupTime;
uint64_t m_StartupTime; // monotonic seconds
uint64_t m_BandwidthLimit; // allowed bandwidth
int m_ShareRatio;
RouterStatus m_Status, m_StatusV6;
@ -243,10 +255,11 @@ namespace garlic
i2p::crypto::NoiseSymmetricState m_InitialNoiseState, m_CurrentNoiseState;
// publish
std::unique_ptr<RouterService> m_Service;
std::unique_ptr<boost::asio::deadline_timer> m_PublishTimer, m_CongestionUpdateTimer;
std::set<i2p::data::IdentHash> m_PublishExcluded;
std::unique_ptr<boost::asio::deadline_timer> m_PublishTimer, m_CongestionUpdateTimer, m_CleanupTimer;
std::unordered_set<i2p::data::IdentHash> m_PublishExcluded;
uint32_t m_PublishReplyToken;
bool m_IsHiddenMode; // not publish
mutable std::mutex m_RouterInfoMutex;
};
extern RouterContext context;

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2023, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -21,6 +21,7 @@
#include "Base.h"
#include "Timestamp.h"
#include "Log.h"
#include "Transports.h"
#include "NetDb.hpp"
#include "RouterContext.h"
#include "RouterInfo.h"
@ -33,6 +34,7 @@ namespace data
{
if (len > size ()) len = size ();
memcpy (data (), buf, len);
m_BufferLen = len;
}
RouterInfo::RouterInfo (): m_Buffer (nullptr)
@ -41,8 +43,8 @@ namespace data
}
RouterInfo::RouterInfo (const std::string& fullPath):
m_FamilyID (0), m_IsUpdated (false), m_IsUnreachable (false),
m_SupportedTransports (0),m_ReachableTransports (0),
m_FamilyID (0), m_IsUpdated (false), m_IsUnreachable (false), m_IsFloodfill (false),
m_SupportedTransports (0),m_ReachableTransports (0), m_PublishedTransports (0),
m_Caps (0), m_Version (0), m_Congestion (eLowCongestion)
{
m_Addresses = boost::make_shared<Addresses>(); // create empty list
@ -51,15 +53,15 @@ namespace data
}
RouterInfo::RouterInfo (std::shared_ptr<Buffer>&& buf, size_t len):
m_FamilyID (0), m_IsUpdated (true), m_IsUnreachable (false),
m_SupportedTransports (0), m_ReachableTransports (0),
m_FamilyID (0), m_IsUpdated (true), m_IsUnreachable (false), m_IsFloodfill (false),
m_SupportedTransports (0), m_ReachableTransports (0), m_PublishedTransports (0),
m_Caps (0), m_Version (0), m_Congestion (eLowCongestion)
{
if (len <= MAX_RI_BUFFER_SIZE)
{
m_Addresses = boost::make_shared<Addresses>(); // create empty list
m_Buffer = buf;
m_BufferLen = len;
if (m_Buffer) m_Buffer->SetBufferLen (len);
ReadFromBuffer (true);
}
else
@ -95,7 +97,8 @@ namespace data
m_IsUnreachable = false;
m_SupportedTransports = 0;
m_ReachableTransports = 0;
m_Caps = 0;
m_PublishedTransports = 0;
m_Caps = 0; m_IsFloodfill = false;
// don't clean up m_Addresses, it will be replaced in ReadFromStream
ClearProperties ();
// skip identity
@ -127,16 +130,17 @@ namespace data
if (s.is_open ())
{
s.seekg (0,std::ios::end);
m_BufferLen = s.tellg ();
if (m_BufferLen < 40 || m_BufferLen > MAX_RI_BUFFER_SIZE)
size_t bufferLen = s.tellg ();
if (bufferLen < 40 || bufferLen > MAX_RI_BUFFER_SIZE)
{
LogPrint(eLogError, "RouterInfo: File", fullPath, " is malformed");
LogPrint(eLogError, "RouterInfo: File ", fullPath, " is malformed");
return false;
}
s.seekg(0, std::ios::beg);
if (!m_Buffer)
m_Buffer = NewBuffer ();
s.read((char *)m_Buffer->data (), m_BufferLen);
s.read((char *)m_Buffer->data (), bufferLen);
m_Buffer->SetBufferLen (bufferLen);
}
else
{
@ -161,11 +165,12 @@ namespace data
m_IsUnreachable = true;
return;
}
m_RouterIdentity = NewIdentity (m_Buffer->data (), m_BufferLen);
size_t bufferLen = m_Buffer->GetBufferLen ();
m_RouterIdentity = NewIdentity (m_Buffer->data (), bufferLen);
size_t identityLen = m_RouterIdentity->GetFullLen ();
if (identityLen >= m_BufferLen)
if (identityLen >= bufferLen)
{
LogPrint (eLogError, "RouterInfo: Identity length ", identityLen, " exceeds buffer size ", m_BufferLen);
LogPrint (eLogError, "RouterInfo: Identity length ", identityLen, " exceeds buffer size ", bufferLen);
m_IsUnreachable = true;
return;
}
@ -179,7 +184,7 @@ namespace data
return;
}
// verify signature
int l = m_BufferLen - m_RouterIdentity->GetSignatureLen ();
int l = bufferLen - m_RouterIdentity->GetSignatureLen ();
if (l < 0 || !m_RouterIdentity->Verify ((uint8_t *)m_Buffer->data (), l, (uint8_t *)m_Buffer->data () + l))
{
LogPrint (eLogError, "RouterInfo: Signature verification failed");
@ -189,7 +194,7 @@ namespace data
}
// parse RI
std::stringstream str;
str.write ((const char *)m_Buffer->data () + identityLen, m_BufferLen - identityLen);
str.write ((const char *)m_Buffer->data () + identityLen, bufferLen - identityLen);
ReadFromStream (str);
if (!str)
{
@ -215,7 +220,7 @@ namespace data
uint8_t cost; // ignore
s.read ((char *)&cost, sizeof (cost));
s.read ((char *)&address->date, sizeof (address->date));
bool isHost = false, isStaticKey = false, isV2 = false;
bool isHost = false, isStaticKey = false, isV2 = false, isIntroKey = false;
char transportStyle[6];
ReadString (transportStyle, 6, s);
if (!strncmp (transportStyle, "NTCP", 4)) // NTCP or NTCP2
@ -253,7 +258,7 @@ namespace data
address->host = boost::asio::ip::address::from_string (value, ecode);
if (!ecode && !address->host.is_unspecified ())
{
if (!i2p::util::net::IsInReservedRange (address->host) ||
if (!i2p::transport::transports.IsInReservedRange (address->host) ||
i2p::util::net::IsYggdrasilAddress (address->host))
isHost = true;
else
@ -292,26 +297,38 @@ namespace data
address->caps = ExtractAddressCaps (value);
else if (!strcmp (key, "s")) // ntcp2 or ssu2 static key
{
Base64ToByteStream (value, strlen (value), address->s, 32);
if (!(address->s[31] & 0x80)) // check if x25519 public key
isStaticKey = true;
if (Base64ToByteStream (value, strlen (value), address->s, 32) == 32 &&
!(address->s[31] & 0x80)) // check if x25519 public key
isStaticKey = true;
else
address->transportStyle = eTransportUnknown; // invalid address
}
else if (!strcmp (key, "i")) // ntcp2 iv or ssu2 intro
{
if (address->IsNTCP2 ())
{
Base64ToByteStream (value, strlen (value), address->i, 16);
address->published = true; // presence of "i" means "published" NTCP2
if (Base64ToByteStream (value, strlen (value), address->i, 16) == 16)
address->published = true; // presence of "i" means "published" NTCP2
else
address->transportStyle = eTransportUnknown; // invalid address
}
else if (address->IsSSU2 ())
Base64ToByteStream (value, strlen (value), address->i, 32);
{
if (Base64ToByteStream (value, strlen (value), address->i, 32) == 32)
isIntroKey = true;
else
address->transportStyle = eTransportUnknown; // invalid address
}
}
else if (!strcmp (key, "v"))
{
if (!strcmp (value, "2"))
isV2 = true;
else
{
LogPrint (eLogWarning, "RouterInfo: Unexpected value ", value, " for v");
address->transportStyle = eTransportUnknown; // invalid address
}
}
else if (key[0] == 'i')
{
@ -374,7 +391,7 @@ namespace data
supportedTransports |= (i2p::util::net::IsYggdrasilAddress (address->host) ? eNTCP2V6Mesh : eNTCP2V6);
else
supportedTransports |= eNTCP2V4;
m_ReachableTransports |= supportedTransports;
m_PublishedTransports |= supportedTransports;
}
else
{
@ -389,17 +406,17 @@ namespace data
}
}
}
else if (address->transportStyle == eTransportSSU2 && isV2 && isStaticKey)
else if (address->transportStyle == eTransportSSU2 && isV2 && isStaticKey && isIntroKey)
{
if (address->IsV4 ()) supportedTransports |= eSSU2V4;
if (address->IsV6 ()) supportedTransports |= eSSU2V6;
if (isHost && address->port)
{
if (address->host.is_v4 ()) m_ReachableTransports |= eSSU2V4;
if (address->host.is_v6 ()) m_ReachableTransports |= eSSU2V6;
if (address->host.is_v4 ()) m_PublishedTransports |= eSSU2V4;
if (address->host.is_v6 ()) m_PublishedTransports |= eSSU2V6;
address->published = true;
}
if (address->ssu && !address->ssu->introducers.empty ())
else if (address->ssu && !address->ssu->introducers.empty ())
{
// exclude invalid introducers
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
@ -419,6 +436,7 @@ namespace data
m_SupportedTransports |= supportedTransports;
}
}
m_ReachableTransports |= m_PublishedTransports;
// update addresses
#if (BOOST_VERSION >= 105300)
boost::atomic_store (&m_Addresses, addresses);
@ -448,7 +466,10 @@ namespace data
// extract caps
if (!strcmp (key, "caps"))
{
ExtractCaps (value);
m_IsFloodfill = IsDeclaredFloodfill ();
}
// extract version
else if (!strcmp (key, ROUTER_INFO_PROPERTY_VERSION))
{
@ -515,7 +536,6 @@ namespace data
break;
case CAPS_FLAG_HIGH_BANDWIDTH1:
case CAPS_FLAG_HIGH_BANDWIDTH2:
case CAPS_FLAG_HIGH_BANDWIDTH3:
m_Caps |= Caps::eHighBandwidth;
break;
case CAPS_FLAG_EXTRA_BANDWIDTH1:
@ -608,22 +628,28 @@ namespace data
return m_Buffer->data ();
}
bool RouterInfo::SaveToFile (const std::string& fullPath)
bool RouterInfo::SaveToFile (const std::string& fullPath, std::shared_ptr<Buffer> buf)
{
if (m_IsUnreachable) return false; // don't save bad router
if (!m_Buffer)
{
LogPrint (eLogWarning, "RouterInfo: Can't save, m_Buffer == NULL");
return false;
}
if (!buf) return false;
std::ofstream f (fullPath, std::ofstream::binary | std::ofstream::out);
if (!f.is_open ())
{
LogPrint (eLogError, "RouterInfo: Can't save to ", fullPath);
return false;
}
f.write ((char *)m_Buffer->data (), m_BufferLen);
f.write ((char *)buf->data (), buf->GetBufferLen ());
return true;
}
bool RouterInfo::SaveToFile (const std::string& fullPath)
{
if (m_IsUnreachable) return false; // don't save bad router
if (!m_Buffer)
{
LogPrint (eLogWarning, "RouterInfo: Can't save, m_Buffer == NULL");
return false;
}
return SaveToFile (fullPath, m_Buffer);
}
size_t RouterInfo::ReadString (char * str, size_t len, std::istream& s) const
@ -995,14 +1021,14 @@ namespace data
bool RouterInfo::IsPublished (bool v4) const
{
if (m_Caps & (eUnreachable | eHidden)) return false; // if router sets U or H we assume that all addreses are not published
auto addr = GetAddresses ();
if (v4)
return ((*addr)[eNTCP2V4Idx] && ((*addr)[eNTCP2V4Idx])->published) ||
((*addr)[eSSU2V4Idx] && ((*addr)[eSSU2V4Idx])->published);
else
return ((*addr)[eNTCP2V6Idx] && ((*addr)[eNTCP2V6Idx])->published) ||
((*addr)[eSSU2V6Idx] && ((*addr)[eSSU2V6Idx])->published);
if (m_Caps & (eUnreachable | eHidden)) return false; // if router sets U or H we assume that all addresses are not published
return m_PublishedTransports & (v4 ? (eNTCP2V4 | eSSU2V4) : (eNTCP2V6 | eSSU2V6));
}
bool RouterInfo::IsNAT2NATOnly (const RouterInfo& other) const
{
return !(m_PublishedTransports & other.m_SupportedTransports) &&
!(other.m_PublishedTransports & m_SupportedTransports);
}
bool RouterInfo::IsSSU2PeerTesting (bool v4) const
@ -1091,9 +1117,15 @@ namespace data
m_Buffer = NewBuffer ();
if (len > m_Buffer->size ()) len = m_Buffer->size ();
memcpy (m_Buffer->data (), buf, len);
m_BufferLen = len;
m_Buffer->SetBufferLen (len);
}
std::shared_ptr<RouterInfo::Buffer> RouterInfo::CopyBuffer () const
{
if (!m_Buffer) return nullptr;
return netdb.NewRouterInfoBuffer (*m_Buffer);
}
std::shared_ptr<RouterInfo::Buffer> RouterInfo::NewBuffer () const
{
return netdb.NewRouterInfoBuffer ();
@ -1177,7 +1209,7 @@ namespace data
CAPS_FLAG_EXTRA_BANDWIDTH2 : // 'X'
CAPS_FLAG_EXTRA_BANDWIDTH1; // 'P'
else
caps += CAPS_FLAG_HIGH_BANDWIDTH3; // 'O'
caps += CAPS_FLAG_HIGH_BANDWIDTH2; // 'O'
caps += CAPS_FLAG_FLOODFILL; // floodfill
}
else
@ -1185,7 +1217,7 @@ namespace data
if (c & eExtraBandwidth)
caps += (c & eHighBandwidth) ? CAPS_FLAG_EXTRA_BANDWIDTH2 /* 'X' */ : CAPS_FLAG_EXTRA_BANDWIDTH1; /*'P' */
else
caps += (c & eHighBandwidth) ? CAPS_FLAG_HIGH_BANDWIDTH3 /* 'O' */: CAPS_FLAG_LOW_BANDWIDTH2 /* 'L' */; // bandwidth
caps += (c & eHighBandwidth) ? CAPS_FLAG_HIGH_BANDWIDTH2 /* 'O' */: CAPS_FLAG_LOW_BANDWIDTH2 /* 'L' */; // bandwidth
}
if (c & eHidden) caps += CAPS_FLAG_HIDDEN; // hidden
if (c & eReachable) caps += CAPS_FLAG_REACHABLE; // reachable
@ -1425,6 +1457,20 @@ namespace data
return "";
}
void LocalRouterInfo::UpdateFloodfillProperty (bool floodfill)
{
if (floodfill)
{
UpdateCaps (GetCaps () | i2p::data::RouterInfo::eFloodfill);
SetFloodfill ();
}
else
{
UpdateCaps (GetCaps () & ~i2p::data::RouterInfo::eFloodfill);
ResetFloodfill ();
}
}
void LocalRouterInfo::WriteString (const std::string& str, std::ostream& s) const
{
uint8_t len = str.size ();

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2023, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -39,9 +39,9 @@ namespace data
/* bandwidth flags */
const char CAPS_FLAG_LOW_BANDWIDTH1 = 'K'; /* < 12 KBps */
const char CAPS_FLAG_LOW_BANDWIDTH2 = 'L'; /* 12-48 KBps */
const char CAPS_FLAG_HIGH_BANDWIDTH1 = 'M'; /* 48-64 KBps */
const char CAPS_FLAG_HIGH_BANDWIDTH2 = 'N'; /* 64-128 KBps */
const char CAPS_FLAG_HIGH_BANDWIDTH3 = 'O'; /* 128-256 KBps */
const char CAPS_FLAG_LOW_BANDWIDTH3 = 'M'; /* 48-64 KBps */
const char CAPS_FLAG_HIGH_BANDWIDTH1 = 'N'; /* 64-128 KBps */
const char CAPS_FLAG_HIGH_BANDWIDTH2 = 'O'; /* 128-256 KBps */
const char CAPS_FLAG_EXTRA_BANDWIDTH1 = 'P'; /* 256-2048 KBps */
const char CAPS_FLAG_EXTRA_BANDWIDTH2 = 'X'; /* > 2048 KBps */
// bandwidth limits in kBps
@ -188,6 +188,14 @@ namespace data
Buffer () = default;
Buffer (const uint8_t * buf, size_t len);
Buffer (const Buffer& other): Buffer (other.data (), other.m_BufferLen) {};
size_t GetBufferLen () const { return m_BufferLen; };
void SetBufferLen (size_t len) { m_BufferLen = len; };
private:
size_t m_BufferLen = 0;
};
typedef std::array<std::shared_ptr<Address>, eNumTransports> Addresses;
@ -227,8 +235,9 @@ namespace data
void SetUnreachableAddressesTransportCaps (uint8_t transports); // bitmask of AddressCaps
void UpdateSupportedTransports ();
void UpdateIntroducers (uint64_t ts); // ts in seconds
bool IsFloodfill () const { return m_Caps & Caps::eFloodfill; };
void ResetFlooldFill () { m_Caps &= ~Caps::eFloodfill; };
bool IsFloodfill () const { return m_IsFloodfill; };
void SetFloodfill () { m_IsFloodfill = true; };
void ResetFloodfill () { m_IsFloodfill = false; };
bool IsECIES () const { return m_RouterIdentity->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD; };
bool IsNTCP2 (bool v4only = true) const;
bool IsNTCP2V6 () const { return m_SupportedTransports & eNTCP2V6; };
@ -247,12 +256,15 @@ namespace data
bool IsReachableFrom (const RouterInfo& other) const { return m_ReachableTransports & other.m_SupportedTransports; };
bool IsReachableBy (CompatibleTransports transports) const { return m_ReachableTransports & transports; };
CompatibleTransports GetCompatibleTransports (bool incoming) const { return incoming ? m_ReachableTransports : m_SupportedTransports; };
CompatibleTransports GetPublishedTransports () const { return m_PublishedTransports; };
bool HasValidAddresses () const { return m_SupportedTransports; };
bool IsHidden () const { return m_Caps & eHidden; };
bool IsHighBandwidth () const { return m_Caps & RouterInfo::eHighBandwidth; };
bool IsExtraBandwidth () const { return m_Caps & RouterInfo::eExtraBandwidth; };
bool IsEligibleFloodfill () const;
bool IsDeclaredFloodfill () const { return m_Caps & RouterInfo::eFloodfill; };
bool IsPublished (bool v4) const;
bool IsNAT2NATOnly (const RouterInfo& other) const; // only NAT-to-NAT connection is possible
bool IsSSU2PeerTesting (bool v4) const;
bool IsSSU2Introducer (bool v4) const;
bool IsHighCongestion (bool highBandwidth) const;
@ -268,17 +280,21 @@ namespace data
const uint8_t * GetBuffer () const { return m_Buffer ? m_Buffer->data () : nullptr; };
const uint8_t * LoadBuffer (const std::string& fullPath); // load if necessary
size_t GetBufferLen () const { return m_BufferLen; };
size_t GetBufferLen () const { return m_Buffer ? m_Buffer->GetBufferLen () : 0; };
void DeleteBuffer () { m_Buffer = nullptr; };
std::shared_ptr<Buffer> GetSharedBuffer () const { return m_Buffer; };
std::shared_ptr<Buffer> CopyBuffer () const;
bool IsUpdated () const { return m_IsUpdated; };
void SetUpdated (bool updated) { m_IsUpdated = updated; };
bool SaveToFile (const std::string& fullPath);
static bool SaveToFile (const std::string& fullPath, std::shared_ptr<Buffer> buf);
std::shared_ptr<RouterProfile> GetProfile () const;
void DropProfile () { m_Profile = nullptr; };
bool HasProfile () const { return (bool)m_Profile; };
bool Update (const uint8_t * buf, size_t len);
void DeleteBuffer () { m_Buffer = nullptr; };
bool IsNewer (const uint8_t * buf, size_t len) const;
/** return true if we are in a router family and the signature is valid */
@ -295,7 +311,7 @@ namespace data
RouterInfo ();
uint8_t * GetBufferPointer (size_t offset = 0 ) { return m_Buffer->data () + offset; };
void UpdateBuffer (const uint8_t * buf, size_t len);
void SetBufferLen (size_t len) { m_BufferLen = len; };
void SetBufferLen (size_t len) { if (m_Buffer) m_Buffer->SetBufferLen (len); };
void RefreshTimestamp ();
CompatibleTransports GetReachableTransports () const { return m_ReachableTransports; };
void SetReachableTransports (CompatibleTransports transports) { m_ReachableTransports = transports; };
@ -323,11 +339,10 @@ namespace data
FamilyID m_FamilyID;
std::shared_ptr<const IdentityEx> m_RouterIdentity;
std::shared_ptr<Buffer> m_Buffer;
size_t m_BufferLen;
uint64_t m_Timestamp; // in milliseconds
boost::shared_ptr<Addresses> m_Addresses; // TODO: use std::shared_ptr and std::atomic_store for gcc >= 4.9
bool m_IsUpdated, m_IsUnreachable;
CompatibleTransports m_SupportedTransports, m_ReachableTransports;
bool m_IsUpdated, m_IsUnreachable, m_IsFloodfill;
CompatibleTransports m_SupportedTransports, m_ReachableTransports, m_PublishedTransports;
uint8_t m_Caps;
int m_Version;
Congestion m_Congestion;
@ -347,7 +362,8 @@ namespace data
void DeleteProperty (const std::string& key);
std::string GetProperty (const std::string& key) const;
void ClearProperties () override { m_Properties.clear (); };
void UpdateFloodfillProperty (bool floodfill);
bool AddSSU2Introducer (const Introducer& introducer, bool v4);
bool RemoveSSU2Introducer (const IdentHash& h, bool v4);

@ -1,5 +1,5 @@
/*
* Copyright (c) 2022-2023, The PurpleI2P Project
* Copyright (c) 2022-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -25,7 +25,7 @@ namespace transport
m_TerminationTimer (GetService ()), m_CleanupTimer (GetService ()), m_ResendTimer (GetService ()),
m_IntroducersUpdateTimer (GetService ()), m_IntroducersUpdateTimerV6 (GetService ()),
m_IsPublished (true), m_IsSyncClockFromPeers (true), m_PendingTimeOffset (0),
m_IsThroughProxy (false)
m_Rng(i2p::util::GetMonotonicMicroseconds ()%1000000LL), m_IsThroughProxy (false)
{
}
@ -210,27 +210,40 @@ namespace transport
return ep.port ();
}
void SSU2Server::AdjustTimeOffset (int64_t offset)
void SSU2Server::AdjustTimeOffset (int64_t offset, std::shared_ptr<const i2p::data::IdentityEx> from)
{
if (offset)
{
if (m_PendingTimeOffset) // one more
{
if (std::abs (m_PendingTimeOffset - offset) < SSU2_CLOCK_SKEW)
if (m_PendingTimeOffsetFrom && from &&
m_PendingTimeOffsetFrom->GetIdentHash ().GetLL()[0] != from->GetIdentHash ().GetLL()[0]) // from different routers
{
offset = (m_PendingTimeOffset + offset)/2; // average
LogPrint (eLogWarning, "SSU2: Clock adjusted by ", offset, " seconds");
i2p::util::AdjustTimeOffset (offset);
}
if (std::abs (m_PendingTimeOffset - offset) < SSU2_CLOCK_SKEW)
{
offset = (m_PendingTimeOffset + offset)/2; // average
LogPrint (eLogWarning, "SSU2: Clock adjusted by ", offset, " seconds");
i2p::util::AdjustTimeOffset (offset);
}
else
LogPrint (eLogWarning, "SSU2: Time offsets are too different. Clock not adjusted");
m_PendingTimeOffset = 0;
m_PendingTimeOffsetFrom = nullptr;
}
else
LogPrint (eLogWarning, "SSU2: Time offsets are too different. Clock not adjusted");
m_PendingTimeOffset = 0;
LogPrint (eLogWarning, "SSU2: Time offsets from same router. Clock not adjusted");
}
else
{
m_PendingTimeOffset = offset; // first
m_PendingTimeOffsetFrom = from;
}
}
else
{
m_PendingTimeOffset = 0; // reset
m_PendingTimeOffsetFrom = nullptr;
}
}
boost::asio::ip::udp::socket& SSU2Server::OpenSocket (const boost::asio::ip::udp::endpoint& localEndpoint)
@ -243,15 +256,49 @@ namespace transport
socket.open (localEndpoint.protocol ());
if (localEndpoint.address ().is_v6 ())
socket.set_option (boost::asio::ip::v6_only (true));
socket.set_option (boost::asio::socket_base::receive_buffer_size (SSU2_SOCKET_RECEIVE_BUFFER_SIZE));
socket.set_option (boost::asio::socket_base::send_buffer_size (SSU2_SOCKET_SEND_BUFFER_SIZE));
uint64_t bufferSize = i2p::context.GetBandwidthLimit() * 1024 / 5; // max lag = 200ms
bufferSize = std::max(SSU2_SOCKET_MIN_BUFFER_SIZE, std::min(bufferSize, SSU2_SOCKET_MAX_BUFFER_SIZE));
boost::asio::socket_base::receive_buffer_size receiveBufferSizeSet (bufferSize);
boost::asio::socket_base::send_buffer_size sendBufferSizeSet (bufferSize);
socket.set_option (receiveBufferSizeSet);
socket.set_option (sendBufferSizeSet);
boost::asio::socket_base::receive_buffer_size receiveBufferSizeGet;
boost::asio::socket_base::send_buffer_size sendBufferSizeGet;
socket.get_option (receiveBufferSizeGet);
socket.get_option (sendBufferSizeGet);
if (receiveBufferSizeGet.value () != receiveBufferSizeSet.value () ||
sendBufferSizeGet.value () != sendBufferSizeSet.value ())
{
LogPrint (eLogWarning, "SSU2: Socket receive buffer size: requested = ",
receiveBufferSizeSet.value (), ", got = ", receiveBufferSizeGet.value ());
LogPrint (eLogWarning, "SSU2: Socket send buffer size: requested = ",
sendBufferSizeSet.value (), ", got = ", sendBufferSizeGet.value ());
}
else
{
LogPrint (eLogInfo, "SSU2: Socket receive buffer size: ", receiveBufferSizeGet.value ());
LogPrint (eLogInfo, "SSU2: Socket send buffer size: ", sendBufferSizeGet.value ());
}
socket.non_blocking (true);
}
catch (std::exception& ex )
{
LogPrint (eLogCritical, "SSU2: Failed to open socket on ", localEndpoint.address (), ": ", ex.what());
ThrowFatal ("Unable to start SSU2 transport on ", localEndpoint.address (), ": ", ex.what ());
return socket;
}
try
{
socket.bind (localEndpoint);
LogPrint (eLogInfo, "SSU2: Start listening on ", localEndpoint);
}
catch (std::exception& ex )
{
LogPrint (eLogCritical, "SSU2: Failed to bind to ", localEndpoint, ": ", ex.what());
ThrowFatal ("Unable to start SSU2 transport on ", localEndpoint, ": ", ex.what ());
LogPrint (eLogWarning, "SSU2: Failed to bind to ", localEndpoint, ": ", ex.what(), ". Actual endpoint is ", socket.local_endpoint ());
// we can continue without binding being firewalled
}
return socket;
}
@ -283,8 +330,15 @@ namespace transport
// but better to find out which host were sent it and mark that router as unreachable
{
i2p::transport::transports.UpdateReceivedBytes (bytes_transferred);
if (bytes_transferred < SSU2_MIN_RECEIVED_PACKET_SIZE)
{
// drop too short packets
m_PacketsPool.ReleaseMt (packet);
Receive (socket);
return;
}
packet->len = bytes_transferred;
boost::system::error_code ec;
size_t moreBytes = socket.available (ec);
if (!ec && moreBytes)
@ -382,7 +436,11 @@ namespace transport
{
auto ident = it->second->GetRemoteIdentity ();
if (ident)
m_SessionsByRouterHash.erase (ident->GetIdentHash ());
{
auto it1 = m_SessionsByRouterHash.find (ident->GetIdentHash ());
if (it1 != m_SessionsByRouterHash.end () && it->second == it1->second)
m_SessionsByRouterHash.erase (it1);
}
if (m_LastSession == it->second)
m_LastSession = nullptr;
m_Sessions.erase (it);
@ -397,10 +455,12 @@ namespace transport
if (ident)
{
auto ret = m_SessionsByRouterHash.emplace (ident->GetIdentHash (), session);
if (!ret.second)
if (!ret.second && ret.first->second != session)
{
// session already exists
LogPrint (eLogWarning, "SSU2: Session to ", ident->GetIdentHash ().ToBase64 (), " already exists");
// move unsent msgs to new session
ret.first->second->MoveSendQueue (session);
// terminate existing
GetService ().post (std::bind (&SSU2Session::RequestTermination, ret.first->second, eSSU2TerminationReasonReplacedByNewSession));
// update session
@ -440,7 +500,7 @@ namespace transport
m_PendingOutgoingSessions.erase (ep);
}
std::shared_ptr<SSU2Session> SSU2Server::GetRandomSession (
std::shared_ptr<SSU2Session> SSU2Server::GetRandomPeerTestSession (
i2p::data::RouterInfo::CompatibleTransports remoteTransports, const i2p::data::IdentHash& excluded) const
{
if (m_Sessions.empty ()) return nullptr;
@ -451,7 +511,7 @@ namespace transport
std::advance (it, ind);
while (it != m_Sessions.end ())
{
if ((it->second->GetRemoteTransports () & remoteTransports) &&
if ((it->second->GetRemotePeerTestTransports () & remoteTransports) &&
it->second->GetRemoteIdentity ()->GetIdentHash () != excluded)
return it->second;
it++;
@ -460,7 +520,7 @@ namespace transport
it = m_Sessions.begin ();
while (it != m_Sessions.end () && ind)
{
if ((it->second->GetRemoteTransports () & remoteTransports) &&
if ((it->second->GetRemotePeerTestTransports () & remoteTransports) &&
it->second->GetRemoteIdentity ()->GetIdentHash () != excluded)
return it->second;
it++; ind--;
@ -566,7 +626,7 @@ namespace transport
else
it1->second->ProcessRetry (buf, len);
}
else if (!i2p::util::net::IsInReservedRange(senderEndpoint.address ()) && senderEndpoint.port ())
else if (!i2p::transport::transports.IsInReservedRange(senderEndpoint.address ()) && senderEndpoint.port ())
{
// assume new incoming session
auto session = std::make_shared<SSU2Session> (*this);
@ -608,7 +668,10 @@ namespace transport
if (!ec)
i2p::transport::transports.UpdateSentBytes (headerLen + payloadLen);
else
LogPrint (eLogError, "SSU2: Send exception: ", ec.message (), " to ", to);
{
LogPrint (ec == boost::asio::error::would_block ? eLogInfo : eLogError,
"SSU2: Send exception: ", ec.message (), " to ", to);
}
}
void SSU2Server::Send (const uint8_t * header, size_t headerLen, const uint8_t * headerX, size_t headerXLen,
@ -642,7 +705,10 @@ namespace transport
if (!ec)
i2p::transport::transports.UpdateSentBytes (headerLen + headerXLen + payloadLen);
else
LogPrint (eLogError, "SSU2: Send exception: ", ec.message (), " to ", to);
{
LogPrint (ec == boost::asio::error::would_block ? eLogInfo : eLogError,
"SSU2: Send exception: ", ec.message (), " to ", to);
}
}
bool SSU2Server::CreateSession (std::shared_ptr<const i2p::data::RouterInfo> router,
@ -666,7 +732,7 @@ namespace transport
bool isValidEndpoint = !address->host.is_unspecified () && address->port;
if (isValidEndpoint)
{
if (i2p::util::net::IsInReservedRange(address->host)) return false;
if (i2p::transport::transports.IsInReservedRange(address->host)) return false;
auto s = FindPendingOutgoingSession (boost::asio::ip::udp::endpoint (address->host, address->port));
if (s)
{
@ -709,39 +775,48 @@ namespace transport
auto address = session->GetAddress ();
if (!address) return;
session->WaitForIntroduction ();
auto ts = i2p::util::GetSecondsSinceEpoch ();
std::vector<int> indices; int i = 0;
// try to find existing session first
for (auto& it: address->ssu->introducers)
{
auto it1 = m_SessionsByRouterHash.find (it.iH);
if (it1 != m_SessionsByRouterHash.end ())
if (it.iTag && ts < it.iExp)
{
it1->second->Introduce (session, it.iTag);
return;
}
auto it1 = m_SessionsByRouterHash.find (it.iH);
if (it1 != m_SessionsByRouterHash.end ())
{
it1->second->Introduce (session, it.iTag);
return;
}
else
indices.push_back(i);
}
i++;
}
// we have to start a new session to an introducer
auto ts = i2p::util::GetSecondsSinceEpoch ();
std::vector<i2p::data::IdentHash> newRouters;
std::shared_ptr<i2p::data::RouterInfo> r;
uint32_t relayTag = 0;
if (!address->ssu->introducers.empty ())
if (!indices.empty ())
{
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()()));
std::shuffle (indices.begin(), indices.end(), m_Rng);
for (auto i: indices)
for (auto ind: indices)
{
const auto& introducer = address->ssu->introducers[indices[i]];
if (introducer.iTag && ts < introducer.iExp)
{
r = i2p::data::netdb.FindRouter (introducer.iH);
if (r && r->IsReachableFrom (i2p::context.GetRouterInfo ()))
const auto& introducer = address->ssu->introducers[ind];
// introducer is not expired, because in indices
r = i2p::data::netdb.FindRouter (introducer.iH);
if (r)
{
if (r->IsReachableFrom (i2p::context.GetRouterInfo ()))
{
relayTag = introducer.iTag;
if (relayTag) break;
}
}
}
else if (!i2p::data::IsRouterBanned (introducer.iH))
newRouters.push_back (introducer.iH);
}
}
if (r)
@ -753,7 +828,7 @@ namespace transport
if (addr)
{
bool isValidEndpoint = !addr->host.is_unspecified () && addr->port &&
!i2p::util::net::IsInReservedRange(addr->host);
!i2p::transport::transports.IsInReservedRange(addr->host);
if (isValidEndpoint)
{
auto s = FindPendingOutgoingSession (boost::asio::ip::udp::endpoint (addr->host, addr->port));
@ -782,9 +857,8 @@ namespace transport
else
{
// introducers not found, try to request them
for (auto& it: address->ssu->introducers)
if (it.iTag && ts < it.iExp)
i2p::data::netdb.RequestDestination (it.iH);
for (auto& it: newRouters)
i2p::data::netdb.RequestDestination (it);
}
}
@ -796,8 +870,11 @@ namespace transport
auto it = m_SessionsByRouterHash.find (router->GetIdentHash ());
if (it != m_SessionsByRouterHash.end ())
{
auto s = it->second;
if (it->second->IsEstablished ())
auto remoteAddr = it->second->GetAddress ();
if (!remoteAddr || !remoteAddr->IsPeerTesting () ||
(v4 && !remoteAddr->IsV4 ()) || (!v4 && !remoteAddr->IsV6 ())) return false;
auto s = it->second;
if (s->IsEstablished ())
GetService ().post ([s]() { s->SendPeerTest (); });
else
s->SetOnEstablished ([s]() { s->SendPeerTest (); });
@ -906,8 +983,9 @@ namespace transport
void SSU2Server::ScheduleResend (bool more)
{
m_ResendTimer.expires_from_now (boost::posix_time::milliseconds (more ? SSU2_RESEND_CHECK_MORE_TIMEOUT :
(SSU2_RESEND_CHECK_TIMEOUT + rand () % SSU2_RESEND_CHECK_TIMEOUT_VARIANCE)));
m_ResendTimer.expires_from_now (boost::posix_time::milliseconds (more ?
(SSU2_RESEND_CHECK_MORE_TIMEOUT + m_Rng () % SSU2_RESEND_CHECK_MORE_TIMEOUT_VARIANCE):
(SSU2_RESEND_CHECK_TIMEOUT + m_Rng () % SSU2_RESEND_CHECK_TIMEOUT_VARIANCE)));
m_ResendTimer.async_wait (std::bind (&SSU2Server::HandleResendTimer,
this, std::placeholders::_1));
}
@ -920,7 +998,8 @@ namespace transport
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
for (auto it: m_Sessions)
{
resentPacketsNum += it.second->Resend (ts);
if (ts >= it.second->GetLastResendTime () + SSU2_RESEND_CHECK_TIMEOUT)
resentPacketsNum += it.second->Resend (ts);
if (resentPacketsNum > SSU2_MAX_RESEND_PACKETS) break;
}
for (auto it: m_PendingOutgoingSessions)
@ -977,30 +1056,32 @@ namespace transport
return ret;
}
std::list<std::shared_ptr<SSU2Session> > SSU2Server::FindIntroducers (int maxNumIntroducers,
bool v4, const std::set<i2p::data::IdentHash>& excluded) const
std::vector<std::shared_ptr<SSU2Session> > SSU2Server::FindIntroducers (int maxNumIntroducers,
bool v4, const std::unordered_set<i2p::data::IdentHash>& excluded) const
{
std::list<std::shared_ptr<SSU2Session> > ret;
std::vector<std::shared_ptr<SSU2Session> > ret;
if (maxNumIntroducers <= 0) return ret;
auto newer = [](const std::shared_ptr<SSU2Session>& s1, const std::shared_ptr<SSU2Session>& s2) -> bool
{
auto t1 = s1->GetCreationTime (), t2 = s2->GetCreationTime ();
return (t1 != t2) ? (t1 > t2) : (s1->GetConnID () > s2->GetConnID ());
};
std::set<std::shared_ptr<SSU2Session>, decltype (newer)> introducers(newer);
for (const auto& s : m_Sessions)
{
if (s.second->IsEstablished () && (s.second->GetRelayTag () && s.second->IsOutgoing ()) &&
!excluded.count (s.second->GetRemoteIdentity ()->GetIdentHash ()) &&
((v4 && (s.second->GetRemoteTransports () & i2p::data::RouterInfo::eSSU2V4)) ||
(!v4 && (s.second->GetRemoteTransports () & i2p::data::RouterInfo::eSSU2V6))))
ret.push_back (s.second);
introducers.insert (s.second);
}
if ((int)ret.size () > maxNumIntroducers)
int i = 0;
for (auto it: introducers)
{
// 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);
}
}
ret.push_back (it);
i++;
if (i >= maxNumIntroducers) break;
}
return ret;
}
@ -1009,7 +1090,7 @@ namespace transport
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
std::list<i2p::data::IdentHash> newList, impliedList;
auto& introducers = v4 ? m_Introducers : m_IntroducersV6;
std::set<i2p::data::IdentHash> excluded;
std::unordered_set<i2p::data::IdentHash> excluded;
for (const auto& it : introducers)
{
std::shared_ptr<SSU2Session> session;
@ -1113,7 +1194,7 @@ namespace transport
if (m_IsPublished)
{
m_IntroducersUpdateTimer.expires_from_now (boost::posix_time::seconds(
SSU2_KEEP_ALIVE_INTERVAL + rand () % SSU2_KEEP_ALIVE_INTERVAL_VARIANCE));
SSU2_KEEP_ALIVE_INTERVAL + m_Rng () % SSU2_KEEP_ALIVE_INTERVAL_VARIANCE));
m_IntroducersUpdateTimer.async_wait (std::bind (&SSU2Server::HandleIntroducersUpdateTimer,
this, std::placeholders::_1, true));
}
@ -1127,7 +1208,7 @@ namespace transport
i2p::context.ClearSSU2Introducers (true);
m_Introducers.clear ();
m_IntroducersUpdateTimer.expires_from_now (boost::posix_time::seconds(
(SSU2_KEEP_ALIVE_INTERVAL + rand () % SSU2_KEEP_ALIVE_INTERVAL_VARIANCE)/2));
(SSU2_KEEP_ALIVE_INTERVAL + m_Rng () % SSU2_KEEP_ALIVE_INTERVAL_VARIANCE)/2));
m_IntroducersUpdateTimer.async_wait (std::bind (&SSU2Server::HandleIntroducersUpdateTimer,
this, std::placeholders::_1, true));
}
@ -1138,7 +1219,7 @@ namespace transport
if (m_IsPublished)
{
m_IntroducersUpdateTimerV6.expires_from_now (boost::posix_time::seconds(
SSU2_KEEP_ALIVE_INTERVAL + rand () % SSU2_KEEP_ALIVE_INTERVAL_VARIANCE));
SSU2_KEEP_ALIVE_INTERVAL + m_Rng () % SSU2_KEEP_ALIVE_INTERVAL_VARIANCE));
m_IntroducersUpdateTimerV6.async_wait (std::bind (&SSU2Server::HandleIntroducersUpdateTimer,
this, std::placeholders::_1, false));
}
@ -1152,7 +1233,7 @@ namespace transport
i2p::context.ClearSSU2Introducers (false);
m_IntroducersV6.clear ();
m_IntroducersUpdateTimerV6.expires_from_now (boost::posix_time::seconds(
(SSU2_KEEP_ALIVE_INTERVAL + rand () % SSU2_KEEP_ALIVE_INTERVAL_VARIANCE)/2));
(SSU2_KEEP_ALIVE_INTERVAL + m_Rng () % SSU2_KEEP_ALIVE_INTERVAL_VARIANCE)/2));
m_IntroducersUpdateTimerV6.async_wait (std::bind (&SSU2Server::HandleIntroducersUpdateTimer,
this, std::placeholders::_1, false));
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2022-2023, The PurpleI2P Project
* Copyright (c) 2022-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -10,9 +10,13 @@
#define SSU2_H__
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include <mutex>
#include <random>
#include "util.h"
#include "SSU2Session.h"
#include "Socks5.h"
namespace i2p
{
@ -20,13 +24,15 @@ namespace transport
{
const int SSU2_TERMINATION_CHECK_TIMEOUT = 25; // in seconds
const int SSU2_CLEANUP_INTERVAL = 72; // in seconds
const int SSU2_RESEND_CHECK_TIMEOUT = 400; // in milliseconds
const int SSU2_RESEND_CHECK_TIMEOUT_VARIANCE = 100; // in milliseconds
const int SSU2_RESEND_CHECK_MORE_TIMEOUT = 10; // in milliseconds
const int SSU2_RESEND_CHECK_TIMEOUT = 40; // in milliseconds
const int SSU2_RESEND_CHECK_TIMEOUT_VARIANCE = 10; // in milliseconds
const int SSU2_RESEND_CHECK_MORE_TIMEOUT = 4; // in milliseconds
const int SSU2_RESEND_CHECK_MORE_TIMEOUT_VARIANCE = 9; // in milliseconds
const size_t SSU2_MAX_RESEND_PACKETS = 128; // packets to resend at the time
const size_t SSU2_SOCKET_RECEIVE_BUFFER_SIZE = 0x1FFFF; // 128K
const size_t SSU2_SOCKET_SEND_BUFFER_SIZE = 0x1FFFF; // 128K
const uint64_t SSU2_SOCKET_MIN_BUFFER_SIZE = 128 * 1024;
const uint64_t SSU2_SOCKET_MAX_BUFFER_SIZE = 4 * 1024 * 1024;
const size_t SSU2_MAX_NUM_INTRODUCERS = 3;
const size_t SSU2_MIN_RECEIVED_PACKET_SIZE = 40; // 16 byte short header + 8 byte minimum payload + 16 byte MAC
const int SSU2_TO_INTRODUCER_SESSION_DURATION = 3600; // 1 hour
const int SSU2_TO_INTRODUCER_SESSION_EXPIRATION = 4800; // 80 minutes
const int SSU2_KEEP_ALIVE_INTERVAL = 15; // in seconds
@ -65,8 +71,10 @@ namespace transport
bool UsesProxy () const { return m_IsThroughProxy; };
bool IsSupported (const boost::asio::ip::address& addr) const;
uint16_t GetPort (bool v4) const;
std::mt19937& GetRng () { return m_Rng; }
bool IsMaxNumIntroducers (bool v4) const { return (v4 ? m_Introducers.size () : m_IntroducersV6.size ()) >= SSU2_MAX_NUM_INTRODUCERS; }
bool IsSyncClockFromPeers () const { return m_IsSyncClockFromPeers; };
void AdjustTimeOffset (int64_t offset);
void AdjustTimeOffset (int64_t offset, std::shared_ptr<const i2p::data::IdentityEx> from);
void AddSession (std::shared_ptr<SSU2Session> session);
void RemoveSession (uint64_t connID);
@ -75,7 +83,7 @@ namespace transport
void RemovePendingOutgoingSession (const boost::asio::ip::udp::endpoint& ep);
std::shared_ptr<SSU2Session> FindSession (const i2p::data::IdentHash& ident) const;
std::shared_ptr<SSU2Session> FindPendingOutgoingSession (const boost::asio::ip::udp::endpoint& ep) const;
std::shared_ptr<SSU2Session> GetRandomSession (i2p::data::RouterInfo::CompatibleTransports remoteTransports,
std::shared_ptr<SSU2Session> GetRandomPeerTestSession (i2p::data::RouterInfo::CompatibleTransports remoteTransports,
const i2p::data::IdentHash& excluded) const;
void AddRelay (uint32_t tag, std::shared_ptr<SSU2Session> relay);
@ -123,8 +131,8 @@ namespace transport
void HandleResendTimer (const boost::system::error_code& ecode);
void ConnectThroughIntroducer (std::shared_ptr<SSU2Session> session);
std::list<std::shared_ptr<SSU2Session> > FindIntroducers (int maxNumIntroducers,
bool v4, const std::set<i2p::data::IdentHash>& excluded) const;
std::vector<std::shared_ptr<SSU2Session> > FindIntroducers (int maxNumIntroducers,
bool v4, const std::unordered_set<i2p::data::IdentHash>& excluded) const;
void UpdateIntroducers (bool v4);
void ScheduleIntroducersUpdateTimer ();
void HandleIntroducersUpdateTimer (const boost::system::error_code& ecode, bool v4);
@ -163,6 +171,8 @@ namespace transport
bool m_IsPublished; // if we maintain introducers
bool m_IsSyncClockFromPeers;
int64_t m_PendingTimeOffset; // during peer test
std::shared_ptr<const i2p::data::IdentityEx> m_PendingTimeOffsetFrom;
std::mt19937 m_Rng;
// proxy
bool m_IsThroughProxy;

@ -81,13 +81,17 @@ namespace transport
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),
m_Server (server), m_Address (addr), m_RemoteTransports (0),
m_Server (server), m_Address (addr), m_RemoteTransports (0), m_RemotePeerTestTransports (0),
m_DestConnID (0), m_SourceConnID (0), m_State (eSSU2SessionStateUnknown),
m_SendPacketNum (0), m_ReceivePacketNum (0), m_LastDatetimeSentPacketNum (0),
m_IsDataReceived (false), m_WindowSize (SSU2_MIN_WINDOW_SIZE),
m_RTT (SSU2_RESEND_INTERVAL), m_RTO (SSU2_RESEND_INTERVAL*SSU2_kAPPA), m_RelayTag (0),
m_ConnectTimer (server.GetService ()), m_TerminationReason (eSSU2TerminationReasonNormalClose),
m_MaxPayloadSize (SSU2_MIN_PACKET_SIZE - IPV6_HEADER_SIZE - UDP_HEADER_SIZE - 32) // min size
m_IsDataReceived (false), m_RTT (SSU2_UNKNOWN_RTT),
m_MsgLocalExpirationTimeout (I2NP_MESSAGE_LOCAL_EXPIRATION_TIMEOUT_MAX),
m_MsgLocalSemiExpirationTimeout (I2NP_MESSAGE_LOCAL_EXPIRATION_TIMEOUT_MAX / 2),
m_WindowSize (SSU2_MIN_WINDOW_SIZE),
m_RTO (SSU2_INITIAL_RTO), m_RelayTag (0),m_ConnectTimer (server.GetService ()),
m_TerminationReason (eSSU2TerminationReasonNormalClose),
m_MaxPayloadSize (SSU2_MIN_PACKET_SIZE - IPV6_HEADER_SIZE - UDP_HEADER_SIZE - 32), // min size
m_LastResendTime (0)
{
m_NoiseState.reset (new i2p::crypto::NoiseSymmetricState);
if (in_RemoteRouter && m_Address)
@ -96,6 +100,8 @@ namespace transport
InitNoiseXKState1 (*m_NoiseState, m_Address->s);
m_RemoteEndpoint = boost::asio::ip::udp::endpoint (m_Address->host, m_Address->port);
m_RemoteTransports = in_RemoteRouter->GetCompatibleTransports (false);
if (in_RemoteRouter->IsSSU2PeerTesting (true)) m_RemotePeerTestTransports |= i2p::data::RouterInfo::eSSU2V4;
if (in_RemoteRouter->IsSSU2PeerTesting (false)) m_RemotePeerTestTransports |= i2p::data::RouterInfo::eSSU2V6;
RAND_bytes ((uint8_t *)&m_DestConnID, 8);
RAND_bytes ((uint8_t *)&m_SourceConnID, 8);
}
@ -262,6 +268,8 @@ namespace transport
m_SentHandshakePacket.reset (nullptr);
m_SessionConfirmedFragment.reset (nullptr);
m_PathChallenge.reset (nullptr);
for (auto& it: m_SendQueue)
it->Drop ();
m_SendQueue.clear ();
SetSendQueueSize (0);
m_SentPackets.clear ();
@ -275,7 +283,7 @@ namespace transport
if (remoteIdentity)
{
LogPrint (eLogDebug, "SSU2: Session with ", GetRemoteEndpoint (),
" (", i2p::data::GetIdentHashAbbreviation (GetRemoteIdentity ()->GetIdentHash ()), ") terminated");
" (", i2p::data::GetIdentHashAbbreviation (remoteIdentity->GetIdentHash ()), ") terminated");
}
else
{
@ -290,8 +298,10 @@ namespace transport
{
m_TerminationReason = reason;
SendTermination ();
m_State = eSSU2SessionStateClosing;
}
m_State = eSSU2SessionStateClosing;
else
Done ();
}
void SSU2Session::Established ()
@ -303,6 +313,7 @@ namespace transport
m_SentHandshakePacket.reset (nullptr);
m_ConnectTimer.cancel ();
SetTerminationTimeout (SSU2_TERMINATION_TIMEOUT);
SendQueue ();
transports.PeerConnected (shared_from_this ());
if (m_OnEstablished)
{
@ -327,7 +338,7 @@ namespace transport
{
if (!s->IsEstablished ()) return;
uint8_t payload[SSU2_MAX_PACKET_SIZE];
size_t payloadSize = s->CreateRouterInfoBlock (payload, s->m_MaxPayloadSize - 32, i2p::context.GetSharedRouterInfo ());
size_t payloadSize = s->CreateRouterInfoBlock (payload, s->m_MaxPayloadSize - 32, i2p::context.CopyRouterInfoBuffer ());
if (payloadSize)
{
if (payloadSize < s->m_MaxPayloadSize)
@ -349,29 +360,59 @@ namespace transport
void SSU2Session::PostI2NPMessages (std::vector<std::shared_ptr<I2NPMessage> > msgs)
{
if (m_State == eSSU2SessionStateTerminated) return;
uint64_t mts = i2p::util::GetMonotonicMicroseconds ();
bool isSemiFull = false;
if (m_SendQueue.size ())
{
int64_t queueLag = (int64_t)mts - (int64_t)m_SendQueue.front ()->GetEnqueueTime ();
isSemiFull = queueLag > m_MsgLocalSemiExpirationTimeout;
if (isSemiFull)
{
LogPrint (eLogWarning, "SSU2: Outgoing messages queue to ",
i2p::data::GetIdentHashAbbreviation (GetRemoteIdentity ()->GetIdentHash ()),
" is semi-full (size = ", m_SendQueue.size (), ", lag = ", queueLag / 1000, ", rtt = ", (int)m_RTT, ")");
}
}
for (auto it: msgs)
m_SendQueue.push_back (std::move (it));
SendQueue ();
if (m_SendQueue.size () > 0) // windows is full
{
if (m_SendQueue.size () <= SSU2_MAX_OUTGOING_QUEUE_SIZE)
Resend (i2p::util::GetMillisecondsSinceEpoch ());
if (isSemiFull && it->onDrop)
it->Drop (); // drop earlier because we can handle it
else
{
LogPrint (eLogWarning, "SSU2: Outgoing messages queue size to ",
GetIdentHashBase64(), " exceeds ", SSU2_MAX_OUTGOING_QUEUE_SIZE);
RequestTermination (eSSU2TerminationReasonTimeout);
it->SetEnqueueTime (mts);
m_SendQueue.push_back (std::move (it));
}
}
if (IsEstablished ())
{
SendQueue ();
if (m_SendQueue.size () > 0) // windows is full
Resend (i2p::util::GetMillisecondsSinceEpoch ());
}
SetSendQueueSize (m_SendQueue.size ());
}
void SSU2Session::MoveSendQueue (std::shared_ptr<SSU2Session> other)
{
if (!other || m_SendQueue.empty ()) return;
std::vector<std::shared_ptr<I2NPMessage> > msgs;
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
for (auto it: m_SendQueue)
if (!it->IsExpired (ts))
msgs.push_back (it);
else
it->Drop ();
m_SendQueue.clear ();
if (!msgs.empty ())
other->PostI2NPMessages (msgs);
}
bool SSU2Session::SendQueue ()
{
if (!m_SendQueue.empty () && m_SentPackets.size () <= m_WindowSize)
if (!m_SendQueue.empty () && m_SentPackets.size () <= m_WindowSize && IsEstablished ())
{
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
uint64_t mts = i2p::util::GetMonotonicMicroseconds ();
auto packet = m_Server.GetSentPacketsPool ().AcquireShared ();
size_t ackBlockSize = CreateAckBlock (packet->payload, m_MaxPayloadSize);
bool ackBlockSent = false;
@ -379,8 +420,10 @@ namespace transport
while (!m_SendQueue.empty () && m_SentPackets.size () <= m_WindowSize)
{
auto msg = m_SendQueue.front ();
if (!msg)
if (!msg || msg->IsExpired (ts) || msg->GetEnqueueTime() + m_MsgLocalExpirationTimeout < mts)
{
// drop null or expired message
if (msg) msg->Drop ();
m_SendQueue.pop_front ();
continue;
}
@ -473,7 +516,7 @@ namespace transport
else
extraSize -= packet->payloadSize;
}
size_t offset = extraSize > 0 ? (rand () % extraSize) : 0;
size_t offset = extraSize > 0 ? (m_Server.GetRng ()() % extraSize) : 0;
if (offset + packet->payloadSize >= m_MaxPayloadSize) offset = 0;
auto size = CreateFirstFragmentBlock (packet->payload + packet->payloadSize, m_MaxPayloadSize - offset - packet->payloadSize, msg);
if (!size) return false;
@ -485,7 +528,7 @@ namespace transport
uint8_t fragmentNum = 0;
while (msg->offset < msg->len)
{
offset = extraSize > 0 ? (rand () % extraSize) : 0;
offset = extraSize > 0 ? (m_Server.GetRng ()() % extraSize) : 0;
packet = m_Server.GetSentPacketsPool ().AcquireShared ();
packet->payloadSize = CreateFollowOnFragmentBlock (packet->payload, m_MaxPayloadSize - offset, msg, fragmentNum, msgID);
extraSize -= offset;
@ -517,7 +560,7 @@ namespace transport
if (m_SentPackets.empty ()) return 0;
std::map<uint32_t, std::shared_ptr<SSU2SentPacket> > resentPackets;
for (auto it = m_SentPackets.begin (); it != m_SentPackets.end (); )
if (ts >= it->second->sendTime + it->second->numResends*m_RTO)
if (ts >= it->second->sendTime + (it->second->numResends + 1) * m_RTO)
{
if (it->second->numResends > SSU2_MAX_NUM_RESENDS)
{
@ -541,6 +584,7 @@ namespace transport
it++;
if (!resentPackets.empty ())
{
m_LastResendTime = ts;
#if (__cplusplus >= 201703L) // C++ 17 or higher
m_SentPackets.merge (resentPackets);
#else
@ -636,10 +680,14 @@ namespace transport
size_t payloadSize = 7;
if (GetRouterStatus () == eRouterStatusFirewalled && m_Address->IsIntroducer ())
{
// relay tag request
payload[payloadSize] = eSSU2BlkRelayTagRequest;
memset (payload + payloadSize + 1, 0, 2); // size = 0
payloadSize += 3;
if (!m_Server.IsMaxNumIntroducers (m_RemoteEndpoint.address ().is_v4 ()) ||
m_Server.GetRng ()() & 0x01) // request tag with probability 1/2 if we have enough introducers
{
// relay tag request
payload[payloadSize] = eSSU2BlkRelayTagRequest;
memset (payload + payloadSize + 1, 0, 2); // size = 0
payloadSize += 3;
}
}
payloadSize += CreatePaddingBlock (payload + payloadSize, 40 - payloadSize, 1);
// KDF for session request
@ -857,12 +905,12 @@ namespace transport
// payload
size_t maxPayloadSize = m_MaxPayloadSize - 48; // for part 2, 48 is part1
uint8_t * payload = m_SentHandshakePacket->payload;
size_t payloadSize = CreateRouterInfoBlock (payload, maxPayloadSize, i2p::context.GetSharedRouterInfo ());
size_t payloadSize = CreateRouterInfoBlock (payload, maxPayloadSize, i2p::context.CopyRouterInfoBuffer ());
if (!payloadSize)
{
// split by two fragments
maxPayloadSize += m_MaxPayloadSize;
payloadSize = CreateRouterInfoBlock (payload, maxPayloadSize, i2p::context.GetSharedRouterInfo ());
payloadSize = CreateRouterInfoBlock (payload, maxPayloadSize, i2p::context.CopyRouterInfoBuffer ());
header.h.flags[0] = 0x02; // frag 0, total fragments 2
// TODO: check if we need more fragments
}
@ -890,7 +938,7 @@ namespace transport
{
if (payloadSize > m_MaxPayloadSize - 48)
{
payloadSize = m_MaxPayloadSize - 48 - (rand () % 16);
payloadSize = m_MaxPayloadSize - 48 - (m_Server.GetRng ()() % 16);
if (m_SentHandshakePacket->payloadSize - payloadSize < 24)
payloadSize -= 24;
}
@ -1102,6 +1150,10 @@ namespace transport
AdjustMaxPayloadSize ();
m_Server.AddSessionByRouterHash (shared_from_this ()); // we know remote router now
m_RemoteTransports = ri->GetCompatibleTransports (false);
m_RemotePeerTestTransports = 0;
if (ri->IsSSU2PeerTesting (true)) m_RemotePeerTestTransports |= i2p::data::RouterInfo::eSSU2V4;
if (ri->IsSSU2PeerTesting (false)) m_RemotePeerTestTransports |= i2p::data::RouterInfo::eSSU2V6;
// handle other blocks
HandlePayload (decryptedPayload.data () + riSize + 3, decryptedPayload.size () - riSize - 3);
Established ();
@ -1471,7 +1523,7 @@ namespace transport
ResendHandshakePacket (); // assume we receive
return;
}
if (from != m_RemoteEndpoint && !i2p::util::net::IsInReservedRange (from.address ()))
if (from != m_RemoteEndpoint && !i2p::transport::transports.IsInReservedRange (from.address ()))
{
LogPrint (eLogInfo, "SSU2: Remote endpoint update ", m_RemoteEndpoint, "->", from);
m_RemoteEndpoint = from;
@ -1670,10 +1722,10 @@ namespace transport
if (std::abs (offset) > SSU2_CLOCK_THRESHOLD)
{
LogPrint (eLogWarning, "SSU2: Time offset ", offset, " from ", m_RemoteEndpoint);
m_Server.AdjustTimeOffset (-offset);
m_Server.AdjustTimeOffset (-offset, GetRemoteIdentity ());
}
else
m_Server.AdjustTimeOffset (0);
m_Server.AdjustTimeOffset (0, nullptr);
}
else if (std::abs (offset) > SSU2_CLOCK_SKEW)
{
@ -1729,8 +1781,15 @@ namespace transport
if (ts > it1->second->sendTime)
{
auto rtt = ts - it1->second->sendTime;
m_RTT = std::round ((m_RTT*m_SendPacketNum + rtt)/(m_SendPacketNum + 1.0));
if (m_RTT != SSU2_UNKNOWN_RTT)
m_RTT = SSU2_RTT_EWMA_ALPHA * rtt + (1.0 - SSU2_RTT_EWMA_ALPHA) * m_RTT;
else
m_RTT = rtt;
m_RTO = m_RTT*SSU2_kAPPA;
m_MsgLocalExpirationTimeout = std::max (I2NP_MESSAGE_LOCAL_EXPIRATION_TIMEOUT_MIN,
std::min (I2NP_MESSAGE_LOCAL_EXPIRATION_TIMEOUT_MAX,
(unsigned int)(m_RTT * 1000 * I2NP_MESSAGE_LOCAL_EXPIRATION_TIMEOUT_FACTOR)));
m_MsgLocalSemiExpirationTimeout = m_MsgLocalExpirationTimeout / 2;
if (m_RTO < SSU2_MIN_RTO) m_RTO = SSU2_MIN_RTO;
if (m_RTO > SSU2_MAX_RTO) m_RTO = SSU2_MAX_RTO;
}
@ -1753,7 +1812,7 @@ namespace transport
if (ExtractEndpoint (buf, len, ep))
{
LogPrint (eLogInfo, "SSU2: Our external address is ", ep);
if (!i2p::util::net::IsInReservedRange (ep.address ()))
if (!i2p::transport::transports.IsInReservedRange (ep.address ()))
{
i2p::context.UpdateAddress (ep.address ());
// check our port
@ -2101,7 +2160,7 @@ namespace transport
{
case 1: // Bob from Alice
{
auto session = m_Server.GetRandomSession ((buf[12] == 6) ? i2p::data::RouterInfo::eSSU2V4 : i2p::data::RouterInfo::eSSU2V6,
auto session = m_Server.GetRandomPeerTestSession ((buf[12] == 6) ? i2p::data::RouterInfo::eSSU2V4 : i2p::data::RouterInfo::eSSU2V6,
GetRemoteIdentity ()->GetIdentHash ());
if (session) // session with Charlie
{
@ -2172,7 +2231,8 @@ namespace transport
std::shared_ptr<const i2p::data::RouterInfo::Address> addr;
if (ExtractEndpoint (buf + offset + 10, asz, ep))
addr = r->GetSSU2Address (ep.address ().is_v4 ());
if (addr && m_Server.IsSupported (ep.address ()))
if (addr && m_Server.IsSupported (ep.address ()) &&
i2p::context.GetRouterInfo ().IsSSU2PeerTesting (ep.address ().is_v4 ()))
{
// send msg 5 to Alice
auto session = std::make_shared<SSU2Session> (m_Server, r, addr);
@ -2272,7 +2332,7 @@ namespace transport
if (GetTestingState ())
{
SetTestingState (false);
if (GetRouterStatus () != eRouterStatusFirewalled)
if (GetRouterStatus () != eRouterStatusFirewalled && addr->IsPeerTesting ())
{
SetRouterStatus (eRouterStatusFirewalled);
if (m_Address->IsV4 ())
@ -2484,7 +2544,7 @@ namespace transport
i2p::context.SetTestingV6 (testing);
}
if (!testing)
m_Server.AdjustTimeOffset (0); // reset time offset when testing is over
m_Server.AdjustTimeOffset (0, nullptr); // reset time offset when testing is over
}
size_t SSU2Session::CreateAddressBlock (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& ep)
@ -2499,27 +2559,34 @@ namespace transport
size_t SSU2Session::CreateRouterInfoBlock (uint8_t * buf, size_t len, std::shared_ptr<const i2p::data::RouterInfo> r)
{
if (!r || !r->GetBuffer () || len < 5) return 0;
if (!r || len < 5) return 0;
return CreateRouterInfoBlock (buf, len, r->GetSharedBuffer ());
}
size_t SSU2Session::CreateRouterInfoBlock (uint8_t * buf, size_t len, std::shared_ptr<const i2p::data::RouterInfo::Buffer> riBuffer)
{
if (!riBuffer || len < 5) return 0;
buf[0] = eSSU2BlkRouterInfo;
size_t size = r->GetBufferLen ();
size_t size = riBuffer->GetBufferLen ();
if (size + 5 < len)
{
memcpy (buf + 5, r->GetBuffer (), size);
memcpy (buf + 5, riBuffer->data (), size);
buf[3] = 0; // flag
}
else
{
i2p::data::GzipDeflator deflator;
deflator.SetCompressionLevel (9);
size = deflator.Deflate (r->GetBuffer (), r->GetBufferLen (), buf + 5, len - 5);
size = deflator.Deflate (riBuffer->data (), riBuffer->GetBufferLen (), buf + 5, len - 5);
if (!size) return 0; // doesn't fit
buf[3] = SSU2_ROUTER_INFO_FLAG_GZIP; // flag
}
htobe16buf (buf + 1, size + 2); // size
buf[4] = 1; // frag
return size + 5;
}
}
size_t SSU2Session::CreateAckBlock (uint8_t * buf, size_t len)
{
if (len < 8) return 0;
@ -2632,7 +2699,7 @@ namespace transport
size_t SSU2Session::CreatePaddingBlock (uint8_t * buf, size_t len, size_t minSize)
{
if (len < 3 || len < minSize) return 0;
size_t paddingSize = rand () & 0x0F; // 0 - 15
size_t paddingSize = m_Server.GetRng ()() & 0x0F; // 0 - 15
if (paddingSize + 3 > len) paddingSize = len - 3;
else if (paddingSize + 3 < minSize) paddingSize = minSize - 3;
buf[0] = eSSU2BlkPadding;
@ -2918,7 +2985,7 @@ namespace transport
{
uint8_t payload[SSU2_MAX_PACKET_SIZE];
payload[0] = eSSU2BlkPathChallenge;
size_t len = rand () % (m_MaxPayloadSize - 3);
size_t len = m_Server.GetRng ()() % (m_MaxPayloadSize - 3);
htobe16buf (payload + 1, len);
if (len > 0)
{

@ -1,5 +1,5 @@
/*
* Copyright (c) 2022-2023, The PurpleI2P Project
* Copyright (c) 2022-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -25,7 +25,7 @@ namespace i2p
namespace transport
{
const int SSU2_CONNECT_TIMEOUT = 5; // 5 seconds
const int SSU2_TERMINATION_TIMEOUT = 330; // 5.5 minutes
const int SSU2_TERMINATION_TIMEOUT = 165; // in seconds
const int SSU2_CLOCK_SKEW = 60; // in seconds
const int SSU2_CLOCK_THRESHOLD = 15; // in seconds, if more we should adjust
const int SSU2_TOKEN_EXPIRATION_TIMEOUT = 9; // for Retry message, in seconds
@ -36,7 +36,6 @@ namespace transport
const size_t SSU2_MAX_PACKET_SIZE = 1500;
const size_t SSU2_MIN_PACKET_SIZE = 1280;
const int SSU2_HANDSHAKE_RESEND_INTERVAL = 1000; // in milliseconds
const int SSU2_RESEND_INTERVAL = 300; // in milliseconds
const int SSU2_MAX_NUM_RESENDS = 5;
const int SSU2_INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT = 30; // in seconds
const int SSU2_MAX_NUM_RECEIVED_I2NP_MSGIDS = 5000; // how many msgID we store for duplicates check
@ -45,9 +44,11 @@ namespace transport
const size_t SSU2_MIN_WINDOW_SIZE = 16; // in packets
const size_t SSU2_MAX_WINDOW_SIZE = 256; // in packets
const size_t SSU2_MIN_RTO = 100; // in milliseconds
const size_t SSU2_INITIAL_RTO = 540; // in milliseconds
const size_t SSU2_MAX_RTO = 2500; // in milliseconds
const double SSU2_UNKNOWN_RTT = -1;
const double SSU2_RTT_EWMA_ALPHA = 0.125;
const float SSU2_kAPPA = 1.8;
const size_t SSU2_MAX_OUTGOING_QUEUE_SIZE = 500; // in messages
const int SSU2_MAX_NUM_ACNT = 255; // acnt, acks or nacks
const int SSU2_MAX_NUM_ACK_PACKETS = 511; // ackthrough + acnt + 1 range
const int SSU2_MAX_NUM_ACK_RANGES = 32; // to send
@ -238,6 +239,7 @@ namespace transport
void SetRemoteEndpoint (const boost::asio::ip::udp::endpoint& ep) { m_RemoteEndpoint = ep; };
const boost::asio::ip::udp::endpoint& GetRemoteEndpoint () const { return m_RemoteEndpoint; };
i2p::data::RouterInfo::CompatibleTransports GetRemoteTransports () const { return m_RemoteTransports; };
i2p::data::RouterInfo::CompatibleTransports GetRemotePeerTestTransports () const { return m_RemotePeerTestTransports; };
std::shared_ptr<const i2p::data::RouterInfo::Address> GetAddress () const { return m_Address; };
void SetOnEstablished (OnEstablished e) { m_OnEstablished = e; };
OnEstablished GetOnEstablished () const { return m_OnEstablished; };
@ -253,8 +255,10 @@ namespace transport
void Done () override;
void SendLocalRouterInfo (bool update) override;
void SendI2NPMessages (const std::vector<std::shared_ptr<I2NPMessage> >& msgs) override;
void MoveSendQueue (std::shared_ptr<SSU2Session> other);
uint32_t GetRelayTag () const override { return m_RelayTag; };
size_t Resend (uint64_t ts); // return number or resent packets
size_t Resend (uint64_t ts); // return number of resent packets
uint64_t GetLastResendTime () const { return m_LastResendTime; };
bool IsEstablished () const override { return m_State == eSSU2SessionStateEstablished; };
uint64_t GetConnID () const { return m_SourceConnID; };
SSU2SessionState GetState () const { return m_State; };
@ -323,6 +327,7 @@ namespace transport
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 CreateRouterInfoBlock (uint8_t * buf, size_t len, std::shared_ptr<const i2p::data::RouterInfo::Buffer> riBuffer);
size_t CreateAckBlock (uint8_t * buf, size_t len);
size_t CreatePaddingBlock (uint8_t * buf, size_t len, size_t minSize = 0);
size_t CreateI2NPBlock (uint8_t * buf, size_t len, std::shared_ptr<I2NPMessage>&& msg);
@ -343,7 +348,7 @@ namespace transport
std::unique_ptr<HandshakePacket> m_SentHandshakePacket; // SessionRequest, SessionCreated or SessionConfirmed
std::shared_ptr<const i2p::data::RouterInfo::Address> m_Address;
boost::asio::ip::udp::endpoint m_RemoteEndpoint;
i2p::data::RouterInfo::CompatibleTransports m_RemoteTransports; // for peer tests
i2p::data::RouterInfo::CompatibleTransports m_RemoteTransports, m_RemotePeerTestTransports;
uint64_t m_DestConnID, m_SourceConnID;
SSU2SessionState m_State;
uint8_t m_KeyDataSend[64], m_KeyDataReceive[64];
@ -356,7 +361,10 @@ namespace transport
std::list<std::shared_ptr<I2NPMessage> > m_SendQueue;
i2p::I2NPMessagesHandler m_Handler;
bool m_IsDataReceived;
size_t m_WindowSize, m_RTT, m_RTO;
double m_RTT;
int m_MsgLocalExpirationTimeout;
int m_MsgLocalSemiExpirationTimeout;
size_t m_WindowSize, m_RTO;
uint32_t m_RelayTag; // between Bob and Charlie
OnEstablished m_OnEstablished; // callback from Established
boost::asio::deadline_timer m_ConnectTimer;
@ -364,6 +372,7 @@ namespace transport
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
uint64_t m_LastResendTime; // in milliseconds
};
inline uint64_t CreateHeaderMask (const uint8_t * kh, const uint8_t * nonce)

@ -0,0 +1,210 @@
/*
* Copyright (c) 2024, 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 SOCKS5_H__
#define SOCKS5_H__
#include <string>
#include <memory>
#include <boost/asio.hpp>
#include "I2PEndian.h"
namespace i2p
{
namespace transport
{
// SOCKS5 constants
const uint8_t SOCKS5_VER = 0x05;
const uint8_t SOCKS5_CMD_CONNECT = 0x01;
const uint8_t SOCKS5_CMD_UDP_ASSOCIATE = 0x03;
const uint8_t SOCKS5_ATYP_IPV4 = 0x01;
const uint8_t SOCKS5_ATYP_IPV6 = 0x04;
const uint8_t SOCKS5_ATYP_NAME = 0x03;
const size_t SOCKS5_UDP_IPV4_REQUEST_HEADER_SIZE = 10;
const size_t SOCKS5_UDP_IPV6_REQUEST_HEADER_SIZE = 22;
const uint8_t SOCKS5_REPLY_SUCCESS = 0x00;
const uint8_t SOCKS5_REPLY_SERVER_FAILURE = 0x01;
const uint8_t SOCKS5_REPLY_CONNECTION_NOT_ALLOWED = 0x02;
const uint8_t SOCKS5_REPLY_NETWORK_UNREACHABLE = 0x03;
const uint8_t SOCKS5_REPLY_HOST_UNREACHABLE = 0x04;
const uint8_t SOCKS5_REPLY_CONNECTION_REFUSED = 0x05;
const uint8_t SOCKS5_REPLY_TTL_EXPIRED = 0x06;
const uint8_t SOCKS5_REPLY_COMMAND_NOT_SUPPORTED = 0x07;
const uint8_t SOCKS5_REPLY_ADDRESS_TYPE_NOT_SUPPORTED = 0x08;
// SOCKS5 handshake
template<typename Socket, typename Handler>
void Socks5ReadReply (Socket& s, Handler handler)
{
auto readbuff = std::make_shared<std::vector<int8_t> >(258); // max possible
boost::asio::async_read(s, boost::asio::buffer(readbuff->data (), 5), boost::asio::transfer_all(), // read 4 bytes of header + first byte of address
[readbuff, &s, handler](const boost::system::error_code& ec, std::size_t transferred)
{
if (!ec)
{
if ((*readbuff)[1] == SOCKS5_REPLY_SUCCESS)
{
size_t len = 0;
switch ((*readbuff)[3]) // ATYP
{
case SOCKS5_ATYP_IPV4: len = 3; break; // address length 4 bytes
case SOCKS5_ATYP_IPV6: len = 15; break; // address length 16 bytes
case SOCKS5_ATYP_NAME: len += (*readbuff)[4]; break; // first byte of address is length
default: ;
}
if (len)
{
len += 2; // port
boost::asio::async_read(s, boost::asio::buffer(readbuff->data (), len), boost::asio::transfer_all(),
[readbuff, handler](const boost::system::error_code& ec, std::size_t transferred)
{
if (!ec)
handler (boost::system::error_code ()); // success
else
handler (boost::asio::error::make_error_code (boost::asio::error::connection_aborted));
});
}
else
handler (boost::asio::error::make_error_code (boost::asio::error::fault)); // unknown address type
}
else
switch ((*readbuff)[1]) // REP
{
case SOCKS5_REPLY_SERVER_FAILURE:
handler (boost::asio::error::make_error_code (boost::asio::error::access_denied ));
break;
case SOCKS5_REPLY_CONNECTION_NOT_ALLOWED:
handler (boost::asio::error::make_error_code (boost::asio::error::no_permission));
break;
case SOCKS5_REPLY_HOST_UNREACHABLE:
handler (boost::asio::error::make_error_code (boost::asio::error::host_unreachable));
break;
case SOCKS5_REPLY_NETWORK_UNREACHABLE:
handler (boost::asio::error::make_error_code (boost::asio::error::network_unreachable));
break;
case SOCKS5_REPLY_CONNECTION_REFUSED:
handler (boost::asio::error::make_error_code (boost::asio::error::connection_refused));
break;
case SOCKS5_REPLY_TTL_EXPIRED:
handler (boost::asio::error::make_error_code (boost::asio::error::timed_out));
break;
case SOCKS5_REPLY_COMMAND_NOT_SUPPORTED:
handler (boost::asio::error::make_error_code (boost::asio::error::operation_not_supported));
break;
case SOCKS5_REPLY_ADDRESS_TYPE_NOT_SUPPORTED:
handler (boost::asio::error::make_error_code (boost::asio::error::no_protocol_option));
break;
default:
handler (boost::asio::error::make_error_code (boost::asio::error::connection_aborted));
}
}
else
handler (ec);
});
}
template<typename Socket, typename Handler>
void Socks5Connect (Socket& s, Handler handler, std::shared_ptr<std::vector<uint8_t> > buff, uint16_t port)
{
if (buff && buff->size () >= 6)
{
(*buff)[0] = SOCKS5_VER;
(*buff)[1] = SOCKS5_CMD_CONNECT;
(*buff)[2] = 0x00;
htobe16buf(buff->data () + buff->size () - 2, port);
boost::asio::async_write(s, boost::asio::buffer(*buff), boost::asio::transfer_all(),
[buff, &s, handler](const boost::system::error_code& ec, std::size_t transferred)
{
(void) transferred;
if (!ec)
Socks5ReadReply (s, handler);
else
handler (ec);
});
}
else
handler (boost::asio::error::make_error_code (boost::asio::error::no_buffer_space));
}
template<typename Socket, typename Handler>
void Socks5Connect (Socket& s, const boost::asio::ip::tcp::endpoint& ep, Handler handler)
{
std::shared_ptr<std::vector<uint8_t> > buff;
if(ep.address ().is_v4 ())
{
buff = std::make_shared<std::vector<uint8_t> >(10);
(*buff)[3] = SOCKS5_ATYP_IPV4;
auto addrbytes = ep.address ().to_v4().to_bytes();
memcpy(buff->data () + 4, addrbytes.data(), 4);
}
else if (ep.address ().is_v6 ())
{
buff = std::make_shared<std::vector<uint8_t> >(22);
(*buff)[3] = SOCKS5_ATYP_IPV6;
auto addrbytes = ep.address ().to_v6().to_bytes();
memcpy(buff->data () + 4, addrbytes.data(), 16);
}
if (buff)
Socks5Connect (s, handler, buff, ep.port ());
else
handler (boost::asio::error::make_error_code (boost::asio::error::fault));
}
template<typename Socket, typename Handler>
void Socks5Connect (Socket& s, const std::pair<std::string, uint16_t>& ep, Handler handler)
{
auto& addr = ep.first;
if (addr.length () <= 255)
{
auto buff = std::make_shared<std::vector<uint8_t> >(addr.length () + 7);
(*buff)[3] = SOCKS5_ATYP_NAME;
(*buff)[4] = addr.length ();
memcpy (buff->data () + 5, addr.c_str (), addr.length ());
Socks5Connect (s, handler, buff, ep.second);
}
else
handler (boost::asio::error::make_error_code (boost::asio::error::name_too_long));
}
template<typename Socket, typename Endpoint, typename Handler>
void Socks5Handshake (Socket& s, Endpoint ep, Handler handler)
{
static const uint8_t methodSelection[3] = { SOCKS5_VER, 0x01, 0x00 }; // 1 method, no auth
boost::asio::async_write(s, boost::asio::buffer(methodSelection, 3), boost::asio::transfer_all(),
[&s, ep, handler] (const boost::system::error_code& ec, std::size_t transferred)
{
(void) transferred;
if (!ec)
{
auto readbuff = std::make_shared<std::vector<uint8_t> >(2);
boost::asio::async_read(s, boost::asio::buffer(*readbuff), boost::asio::transfer_all(),
[&s, ep, handler, readbuff] (const boost::system::error_code& ec, std::size_t transferred)
{
if (!ec)
{
if (transferred == 2 && (*readbuff)[1] == 0x00) // no auth
Socks5Connect (s, ep, handler);
else
handler (boost::asio::error::make_error_code (boost::asio::error::invalid_argument));
}
else
handler (ec);
});
}
else
handler (ec);
});
}
}
}
#endif

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2023, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -68,11 +68,12 @@ namespace stream
Stream::Stream (boost::asio::io_service& service, StreamingDestination& local,
std::shared_ptr<const i2p::data::LeaseSet> remote, int port): m_Service (service),
m_SendStreamID (0), m_SequenceNumber (0), m_LastReceivedSequenceNumber (-1),
m_SendStreamID (0), m_SequenceNumber (0),
m_TunnelsChangeSequenceNumber (0), m_LastReceivedSequenceNumber (-1),
m_Status (eStreamStatusNew), m_IsAckSendScheduled (false), m_LocalDestination (local),
m_RemoteLeaseSet (remote), m_ReceiveTimer (m_Service), m_ResendTimer (m_Service),
m_AckSendTimer (m_Service), m_NumSentBytes (0), m_NumReceivedBytes (0), m_Port (port),
m_WindowSize (MIN_WINDOW_SIZE), m_RTT (INITIAL_RTT), m_RTO (INITIAL_RTO),
m_RTT (INITIAL_RTT), m_WindowSize (MIN_WINDOW_SIZE), m_RTO (INITIAL_RTO),
m_AckDelay (local.GetOwner ()->GetStreamingAckDelay ()),
m_LastWindowSizeIncreaseTime (0), m_NumResendAttempts (0), m_MTU (STREAMING_MTU)
{
@ -81,11 +82,12 @@ namespace stream
}
Stream::Stream (boost::asio::io_service& service, StreamingDestination& local):
m_Service (service), m_SendStreamID (0), m_SequenceNumber (0), m_LastReceivedSequenceNumber (-1),
m_Service (service), m_SendStreamID (0), m_SequenceNumber (0),
m_TunnelsChangeSequenceNumber (0), m_LastReceivedSequenceNumber (-1),
m_Status (eStreamStatusNew), m_IsAckSendScheduled (false), m_LocalDestination (local),
m_ReceiveTimer (m_Service), m_ResendTimer (m_Service), m_AckSendTimer (m_Service),
m_NumSentBytes (0), m_NumReceivedBytes (0), m_Port (0), m_WindowSize (MIN_WINDOW_SIZE),
m_RTT (INITIAL_RTT), m_RTO (INITIAL_RTO), m_AckDelay (local.GetOwner ()->GetStreamingAckDelay ()),
m_NumSentBytes (0), m_NumReceivedBytes (0), m_Port (0), m_RTT (INITIAL_RTT),
m_WindowSize (MIN_WINDOW_SIZE), m_RTO (INITIAL_RTO), m_AckDelay (local.GetOwner ()->GetStreamingAckDelay ()),
m_LastWindowSizeIncreaseTime (0), m_NumResendAttempts (0), m_MTU (STREAMING_MTU)
{
RAND_bytes ((uint8_t *)&m_RecvStreamID, 4);
@ -129,6 +131,11 @@ namespace stream
void Stream::HandleNextPacket (Packet * packet)
{
if (m_Status == eStreamStatusTerminated)
{
m_LocalDestination.DeletePacket (packet);
return;
}
m_NumReceivedBytes += packet->GetLength ();
if (!m_SendStreamID)
{
@ -159,7 +166,8 @@ namespace stream
{
// we have received next in sequence message
ProcessPacket (packet);
if (m_Status == eStreamStatusTerminated) return;
// we should also try stored messages if any
for (auto it = m_SavedPackets.begin (); it != m_SavedPackets.end ();)
{
@ -169,6 +177,7 @@ namespace stream
m_SavedPackets.erase (it++);
ProcessPacket (savedPacket);
if (m_Status == eStreamStatusTerminated) return;
}
else
break;
@ -179,13 +188,9 @@ namespace stream
{
if (!m_IsAckSendScheduled)
{
m_IsAckSendScheduled = true;
auto ackTimeout = m_RTT/10;
if (ackTimeout > m_AckDelay) ackTimeout = m_AckDelay;
else if (ackTimeout < MIN_SEND_ACK_TIMEOUT) ackTimeout = MIN_SEND_ACK_TIMEOUT;
m_AckSendTimer.expires_from_now (boost::posix_time::milliseconds(ackTimeout));
m_AckSendTimer.async_wait (std::bind (&Stream::HandleAckSendTimer,
shared_from_this (), std::placeholders::_1));
ScheduleAck (ackTimeout);
}
}
else if (packet->IsSYN ())
@ -208,22 +213,17 @@ namespace stream
SavePacket (packet);
if (m_LastReceivedSequenceNumber >= 0)
{
// send NACKs for missing messages ASAP
if (m_IsAckSendScheduled)
{
m_IsAckSendScheduled = false;
m_AckSendTimer.cancel ();
}
SendQuickAck ();
}
if (!m_IsAckSendScheduled)
{
// send NACKs for missing messages
int ackTimeout = MIN_SEND_ACK_TIMEOUT*m_SavedPackets.size ();
if (ackTimeout > m_AckDelay) ackTimeout = m_AckDelay;
ScheduleAck (ackTimeout);
}
}
else
{
// wait for SYN
m_IsAckSendScheduled = true;
m_AckSendTimer.expires_from_now (boost::posix_time::milliseconds(SYN_TIMEOUT));
m_AckSendTimer.async_wait (std::bind (&Stream::HandleAckSendTimer,
shared_from_this (), std::placeholders::_1));
}
ScheduleAck (SYN_TIMEOUT);
}
}
}
@ -289,6 +289,8 @@ namespace stream
m_AckSendTimer.async_wait (std::bind (&Stream::HandleAckSendTimer,
shared_from_this (), std::placeholders::_1));
}
if (delayRequested >= DELAY_CHOKING)
m_WindowSize = 1;
}
optionData += 2;
}
@ -406,6 +408,8 @@ namespace stream
LogPrint (eLogError, "Streaming: Unexpected ackThrough=", ackThrough, " > seqn=", m_SequenceNumber);
return;
}
int rttSample = INT_MAX;
bool firstRttSample = false;
int nackCount = packet->GetNACKCount ();
for (auto it = m_SentPackets.begin (); it != m_SentPackets.end ();)
{
@ -429,38 +433,51 @@ namespace stream
}
}
auto sentPacket = *it;
uint64_t rtt = ts - sentPacket->sendTime;
if(ts < sentPacket->sendTime)
int64_t rtt = (int64_t)ts - (int64_t)sentPacket->sendTime;
if (rtt < 0)
LogPrint (eLogError, "Streaming: Packet ", seqn, "sent from the future, sendTime=", sentPacket->sendTime);
if (!seqn)
{
LogPrint(eLogError, "Streaming: Packet ", seqn, "sent from the future, sendTime=", sentPacket->sendTime);
rtt = 1;
firstRttSample = true;
rttSample = rtt < 0 ? 1 : rtt;
}
m_RTT = std::round ((m_RTT*seqn + rtt)/(seqn + 1.0));
m_RTO = m_RTT*1.5; // TODO: implement it better
else if (!sentPacket->resent && seqn > m_TunnelsChangeSequenceNumber && rtt >= 0)
rttSample = std::min (rttSample, (int)rtt);
LogPrint (eLogDebug, "Streaming: Packet ", seqn, " acknowledged rtt=", rtt, " sentTime=", sentPacket->sendTime);
m_SentPackets.erase (it++);
m_LocalDestination.DeletePacket (sentPacket);
acknowledged = true;
if (m_WindowSize < WINDOW_SIZE)
m_WindowSize++; // slow start
else
{
// linear growth
if (ts > m_LastWindowSizeIncreaseTime + m_RTT)
{
m_WindowSize++;
if (m_WindowSize > MAX_WINDOW_SIZE) m_WindowSize = MAX_WINDOW_SIZE;
m_LastWindowSizeIncreaseTime = ts;
}
}
if (!seqn && m_RoutingSession) // first message confirmed
m_RoutingSession->SetSharedRoutingPath (
std::make_shared<i2p::garlic::GarlicRoutingPath> (
i2p::garlic::GarlicRoutingPath{m_CurrentOutboundTunnel, m_CurrentRemoteLease, m_RTT, 0, 0}));
}
else
break;
}
if (rttSample != INT_MAX)
{
if (firstRttSample)
m_RTT = rttSample;
else
m_RTT = RTT_EWMA_ALPHA * rttSample + (1.0 - RTT_EWMA_ALPHA) * m_RTT;
bool wasInitial = m_RTO == INITIAL_RTO;
m_RTO = std::max (MIN_RTO, (int)(m_RTT * 1.5)); // TODO: implement it better
if (wasInitial)
ScheduleResend ();
}
if (acknowledged && m_WindowSize >= WINDOW_SIZE)
{
// linear growth
if (ts > m_LastWindowSizeIncreaseTime + m_RTT)
{
m_WindowSize++;
if (m_WindowSize > MAX_WINDOW_SIZE) m_WindowSize = MAX_WINDOW_SIZE;
m_LastWindowSizeIncreaseTime = ts;
}
}
if (firstRttSample && m_RoutingSession)
m_RoutingSession->SetSharedRoutingPath (
std::make_shared<i2p::garlic::GarlicRoutingPath> (
i2p::garlic::GarlicRoutingPath{m_CurrentOutboundTunnel, m_CurrentRemoteLease, (int)m_RTT, 0, 0}));
if (m_SentPackets.empty ())
m_ResendTimer.cancel ();
if (acknowledged)
@ -676,6 +693,7 @@ namespace stream
htobe32buf (packet + size, lastReceivedSeqn);
size += 4; // ack Through
uint8_t numNacks = 0;
bool choking = false;
if (lastReceivedSeqn > m_LastReceivedSequenceNumber)
{
// fill NACKs
@ -687,7 +705,8 @@ namespace stream
if (numNacks + (seqn - nextSeqn) >= 256)
{
LogPrint (eLogError, "Streaming: Number of NACKs exceeds 256. seqn=", seqn, " nextSeqn=", nextSeqn);
htobe32buf (packet + 12, nextSeqn); // change ack Through
htobe32buf (packet + 12, nextSeqn - 1); // change ack Through back
choking = true;
break;
}
for (uint32_t i = nextSeqn; i < seqn; i++)
@ -709,10 +728,17 @@ namespace stream
size++; // NACK count
}
packet[size] = 0;
size++; // resend delay
htobuf16 (packet + size, 0); // no flags set
size++; // resend delay
htobuf16 (packet + size, choking ? PACKET_FLAG_DELAY_REQUESTED : 0); // no flags set or delay
size += 2; // flags
htobuf16 (packet + size, 0); // no options
if (choking)
{
htobuf16 (packet + size, 2); // 2 bytes delay interval
htobuf16 (packet + size + 2, DELAY_CHOKING); // set choking interval
size += 2;
}
else
htobuf16 (packet + size, 0); // no options
size += 2; // options size
p.len = size;
@ -880,7 +906,7 @@ namespace stream
m_CurrentOutboundTunnel = routingPath->outboundTunnel;
m_CurrentRemoteLease = routingPath->remoteLease;
m_RTT = routingPath->rtt;
m_RTO = m_RTT*1.5; // TODO: implement it better
m_RTO = std::max (MIN_RTO, (int)(m_RTT * 1.5)); // TODO: implement it better
}
}
@ -890,20 +916,27 @@ namespace stream
UpdateCurrentRemoteLease (true);
if (m_CurrentRemoteLease && ts < m_CurrentRemoteLease->endDate + i2p::data::LEASE_ENDDATE_THRESHOLD)
{
bool freshTunnel = false;
if (!m_CurrentOutboundTunnel)
{
auto leaseRouter = i2p::data::netdb.FindRouter (m_CurrentRemoteLease->tunnelGateway);
m_CurrentOutboundTunnel = m_LocalDestination.GetOwner ()->GetTunnelPool ()->GetNextOutboundTunnel (nullptr,
leaseRouter ? leaseRouter->GetCompatibleTransports (false) : (i2p::data::RouterInfo::CompatibleTransports)i2p::data::RouterInfo::eAllTransports);
freshTunnel = true;
}
else if (!m_CurrentOutboundTunnel->IsEstablished ())
m_CurrentOutboundTunnel = m_LocalDestination.GetOwner ()->GetTunnelPool ()->GetNewOutboundTunnel (m_CurrentOutboundTunnel);
std::tie(m_CurrentOutboundTunnel, freshTunnel) = m_LocalDestination.GetOwner ()->GetTunnelPool ()->GetNewOutboundTunnel (m_CurrentOutboundTunnel);
if (!m_CurrentOutboundTunnel)
{
LogPrint (eLogError, "Streaming: No outbound tunnels in the pool, sSID=", m_SendStreamID);
m_CurrentRemoteLease = nullptr;
return;
}
if (freshTunnel)
{
m_RTO = INITIAL_RTO;
m_TunnelsChangeSequenceNumber = m_SequenceNumber; // should be determined more precisely
}
std::vector<i2p::tunnel::TunnelMessageBlock> msgs;
for (const auto& it: packets)
@ -935,10 +968,10 @@ namespace stream
if (m_RoutingSession->IsLeaseSetNonConfirmed ())
{
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
if (ts > m_RoutingSession->GetLeaseSetSubmissionTime () + i2p::garlic::LEASET_CONFIRMATION_TIMEOUT)
if (ts > m_RoutingSession->GetLeaseSetSubmissionTime () + i2p::garlic::LEASESET_CONFIRMATION_TIMEOUT)
{
// LeaseSet was not confirmed, should try other tunnels
LogPrint (eLogWarning, "Streaming: LeaseSet was not confirmed in ", i2p::garlic::LEASET_CONFIRMATION_TIMEOUT, " milliseconds. Trying to resubmit");
LogPrint (eLogWarning, "Streaming: LeaseSet was not confirmed in ", i2p::garlic::LEASESET_CONFIRMATION_TIMEOUT, " milliseconds. Trying to resubmit");
m_RoutingSession->SetSharedRoutingPath (nullptr);
m_CurrentOutboundTunnel = nullptr;
m_CurrentRemoteLease = nullptr;
@ -988,6 +1021,7 @@ namespace stream
{
if (ts >= it->sendTime + m_RTO)
{
it->resent = true;
it->sendTime = ts;
packets.push_back (it);
}
@ -997,31 +1031,31 @@ namespace stream
if (packets.size () > 0)
{
m_NumResendAttempts++;
m_RTO *= 2;
switch (m_NumResendAttempts)
if (m_NumResendAttempts == 1 && m_RTO != INITIAL_RTO)
{
case 1: // congesion avoidance
m_WindowSize >>= 1; // /2
if (m_WindowSize < MIN_WINDOW_SIZE) m_WindowSize = MIN_WINDOW_SIZE;
break;
case 2:
m_RTO = INITIAL_RTO; // drop RTO to initial upon tunnels pair change first time
#if (__cplusplus >= 201703L) // C++ 17 or higher
[[fallthrough]];
#endif
// no break here
case 4:
if (m_RoutingSession) m_RoutingSession->SetSharedRoutingPath (nullptr);
UpdateCurrentRemoteLease (); // pick another lease
LogPrint (eLogWarning, "Streaming: Another remote lease has been selected for stream with rSID=", m_RecvStreamID, ", sSID=", m_SendStreamID);
break;
case 3:
// congestion avoidance
m_RTO *= 2;
m_WindowSize -= (m_WindowSize + WINDOW_SIZE_DROP_FRACTION) / WINDOW_SIZE_DROP_FRACTION; // adjustment >= 1
if (m_WindowSize < MIN_WINDOW_SIZE) m_WindowSize = MIN_WINDOW_SIZE;
}
else
{
m_TunnelsChangeSequenceNumber = m_SequenceNumber;
m_RTO = INITIAL_RTO; // drop RTO to initial upon tunnels pair change
if (m_RoutingSession) m_RoutingSession->SetSharedRoutingPath (nullptr);
if (m_NumResendAttempts & 1)
{
// pick another outbound tunnel
if (m_RoutingSession) m_RoutingSession->SetSharedRoutingPath (nullptr);
m_CurrentOutboundTunnel = m_LocalDestination.GetOwner ()->GetTunnelPool ()->GetNextOutboundTunnel (m_CurrentOutboundTunnel);
LogPrint (eLogWarning, "Streaming: Another outbound tunnel has been selected for stream with sSID=", m_SendStreamID);
break;
default: ;
LogPrint (eLogWarning, "Streaming: Resend #", m_NumResendAttempts,
", another outbound tunnel has been selected for stream with sSID=", m_SendStreamID);
}
else
{
UpdateCurrentRemoteLease (); // pick another lease
LogPrint (eLogWarning, "Streaming: Resend #", m_NumResendAttempts,
", another remote lease has been selected for stream with rSID=", m_RecvStreamID, ", sSID=", m_SendStreamID);
}
}
SendPackets (packets);
}
@ -1029,6 +1063,17 @@ namespace stream
}
}
void Stream::ScheduleAck (int timeout)
{
if (m_IsAckSendScheduled)
m_AckSendTimer.cancel ();
m_IsAckSendScheduled = true;
if (timeout < MIN_SEND_ACK_TIMEOUT) timeout = MIN_SEND_ACK_TIMEOUT;
m_AckSendTimer.expires_from_now (boost::posix_time::milliseconds(timeout));
m_AckSendTimer.async_wait (std::bind (&Stream::HandleAckSendTimer,
shared_from_this (), std::placeholders::_1));
}
void Stream::HandleAckSendTimer (const boost::system::error_code& ecode)
{
if (m_IsAckSendScheduled)
@ -1044,9 +1089,13 @@ namespace stream
{
if (m_RoutingSession && m_RoutingSession->IsLeaseSetNonConfirmed ())
{
// seems something went wrong and we should re-select tunnels
m_CurrentOutboundTunnel = nullptr;
m_CurrentRemoteLease = nullptr;
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
if (ts > m_RoutingSession->GetLeaseSetSubmissionTime () + i2p::garlic::LEASESET_CONFIRMATION_TIMEOUT)
{
// seems something went wrong and we should re-select tunnels
m_CurrentOutboundTunnel = nullptr;
m_CurrentRemoteLease = nullptr;
}
}
SendQuickAck ();
}
@ -1135,6 +1184,16 @@ namespace stream
}
}
void Stream::ResetRoutingPath ()
{
m_CurrentOutboundTunnel = nullptr;
m_CurrentRemoteLease = nullptr;
m_RTT = INITIAL_RTT;
m_RTO = INITIAL_RTO;
if (m_RoutingSession)
m_RoutingSession->SetSharedRoutingPath (nullptr); // TODO: count failures
}
StreamingDestination::StreamingDestination (std::shared_ptr<i2p::client::ClientDestination> owner, uint16_t localPort, bool gzip):
m_Owner (owner), m_LocalPort (localPort), m_Gzip (gzip),
m_PendingIncomingTimer (m_Owner->GetService ())
@ -1215,6 +1274,7 @@ namespace stream
{
// already pending
LogPrint(eLogWarning, "Streaming: Incoming streaming with rSID=", receiveStreamID, " already exists");
it1->second->ResetRoutingPath (); // Ack was not delivered, changing path
DeletePacket (packet); // drop it, because previous should be connected
return;
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2023, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -52,10 +52,13 @@ namespace stream
const size_t STREAMING_MTU_RATCHETS = 1812;
const size_t MAX_PACKET_SIZE = 4096;
const size_t COMPRESSION_THRESHOLD_SIZE = 66;
const int MAX_NUM_RESEND_ATTEMPTS = 6;
const int MAX_NUM_RESEND_ATTEMPTS = 9;
const int WINDOW_SIZE = 6; // in messages
const int MIN_WINDOW_SIZE = 1;
const int MAX_WINDOW_SIZE = 128;
const int WINDOW_SIZE_DROP_FRACTION = 10; // 1/10
const double RTT_EWMA_ALPHA = 0.125;
const int MIN_RTO = 20; // in milliseconds
const int INITIAL_RTT = 8000; // in milliseconds
const int INITIAL_RTO = 9000; // in milliseconds
const int MIN_SEND_ACK_TIMEOUT = 2; // in milliseconds
@ -63,14 +66,16 @@ namespace stream
const size_t MAX_PENDING_INCOMING_BACKLOG = 128;
const int PENDING_INCOMING_TIMEOUT = 10; // in seconds
const int MAX_RECEIVE_TIMEOUT = 20; // in seconds
const uint16_t DELAY_CHOKING = 60000; // in milliseconds
struct Packet
{
size_t len, offset;
uint8_t buf[MAX_PACKET_SIZE];
uint64_t sendTime;
bool resent;
Packet (): len (0), offset (0), sendTime (0) {};
Packet (): len (0), offset (0), sendTime (0), resent (false) {};
uint8_t * GetBuffer () { return buf + offset; };
size_t GetLength () const { return len - offset; };
@ -175,6 +180,7 @@ namespace stream
bool IsEstablished () const { return m_SendStreamID; };
StreamStatus GetStatus () const { return m_Status; };
StreamingDestination& GetLocalDestination () { return m_LocalDestination; };
void ResetRoutingPath ();
void HandleNextPacket (Packet * packet);
void HandlePing (Packet * packet);
@ -227,12 +233,14 @@ namespace stream
void ScheduleResend ();
void HandleResendTimer (const boost::system::error_code& ecode);
void ScheduleAck (int timeout);
void HandleAckSendTimer (const boost::system::error_code& ecode);
private:
boost::asio::io_service& m_Service;
uint32_t m_SendStreamID, m_RecvStreamID, m_SequenceNumber;
uint32_t m_TunnelsChangeSequenceNumber;
int32_t m_LastReceivedSequenceNumber;
StreamStatus m_Status;
bool m_IsAckSendScheduled;
@ -251,7 +259,8 @@ namespace stream
uint16_t m_Port;
SendBufferQueue m_SendBuffer;
int m_WindowSize, m_RTT, m_RTO, m_AckDelay;
double m_RTT;
int m_WindowSize, m_RTO, m_AckDelay;
uint64_t m_LastWindowSizeIncreaseTime;
int m_NumResendAttempts;
size_t m_MTU;

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2022, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -232,11 +232,34 @@ namespace util
return GetLocalHoursSinceEpoch () + g_TimeOffset/3600;
}
uint64_t GetMonotonicMicroseconds()
{
return std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now().time_since_epoch()).count();
}
uint64_t GetMonotonicMilliseconds()
{
return std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now().time_since_epoch()).count();
}
uint64_t GetMonotonicSeconds ()
{
return std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::steady_clock::now().time_since_epoch()).count();
}
void GetCurrentDate (char * date)
{
GetDateString (GetSecondsSinceEpoch (), date);
}
void GetNextDayDate (char * date)
{
GetDateString (GetSecondsSinceEpoch () + 24*60*60, date);
}
void GetDateString (uint64_t timestamp, char * date)
{
using clock = std::chrono::system_clock;

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2022, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -24,7 +24,12 @@ namespace util
uint32_t GetMinutesSinceEpoch ();
uint32_t GetHoursSinceEpoch ();
void GetCurrentDate (char * date); // returns date as YYYYMMDD string, 9 bytes
uint64_t GetMonotonicMicroseconds ();
uint64_t GetMonotonicMilliseconds ();
uint64_t GetMonotonicSeconds ();
void GetCurrentDate (char * date); // returns UTC date as YYYYMMDD string, 9 bytes
void GetNextDayDate (char * date); // returns next UTC day 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

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2023, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -187,15 +187,6 @@ namespace transport
uint64_t m_LastActivityTimestamp, m_LastBandwidthUpdateTimestamp;
uint32_t m_InBandwidth, m_OutBandwidth;
};
// SOCKS5 proxy
const uint8_t SOCKS5_VER = 0x05;
const uint8_t SOCKS5_CMD_CONNECT = 0x01;
const uint8_t SOCKS5_CMD_UDP_ASSOCIATE = 0x03;
const uint8_t SOCKS5_ATYP_IPV4 = 0x01;
const uint8_t SOCKS5_ATYP_IPV6 = 0x04;
const size_t SOCKS5_UDP_IPV4_REQUEST_HEADER_SIZE = 10;
const size_t SOCKS5_UDP_IPV6_REQUEST_HEADER_SIZE = 22;
}
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2023, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -140,10 +140,8 @@ namespace transport
m_X25519KeysPairSupplier (15), // 15 pre-generated keys
m_TotalSentBytes (0), m_TotalReceivedBytes (0), m_TotalTransitTransmittedBytes (0),
m_InBandwidth (0), m_OutBandwidth (0), m_TransitBandwidth (0),
m_LastInBandwidthUpdateBytes (0), m_LastOutBandwidthUpdateBytes (0), m_LastTransitBandwidthUpdateBytes (0),
m_InBandwidth15s (0), m_OutBandwidth15s (0), m_TransitBandwidth15s (0),
m_LastInBandwidth15sUpdateBytes (0), m_LastOutBandwidth15sUpdateBytes (0), m_LastTransitBandwidth15sUpdateBytes (0),
m_LastBandwidth15sUpdateTime (0)
m_InBandwidth5m (0), m_OutBandwidth5m (0), m_TransitBandwidth5m (0)
{
}
@ -306,6 +304,16 @@ namespace transport
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));
uint64_t ts = i2p::util::GetMillisecondsSinceEpoch();
for (int i = 0; i < TRAFFIC_SAMPLE_COUNT; i++)
{
m_TrafficSamples[i].Timestamp = ts - (TRAFFIC_SAMPLE_COUNT - i - 1) * 1000;
m_TrafficSamples[i].TotalReceivedBytes = 0;
m_TrafficSamples[i].TotalSentBytes = 0;
m_TrafficSamples[i].TotalTransitTransmittedBytes = 0;
}
m_TrafficSamplePtr = TRAFFIC_SAMPLE_COUNT - 1;
m_UpdateBandwidthTimer->expires_from_now (boost::posix_time::seconds(1));
m_UpdateBandwidthTimer->async_wait (std::bind (&Transports::HandleUpdateBandwidthTimer, this, std::placeholders::_1));
@ -320,7 +328,6 @@ namespace transport
{
if (m_PeerCleanupTimer) m_PeerCleanupTimer->cancel ();
if (m_PeerTestTimer) m_PeerTestTimer->cancel ();
m_Peers.clear ();
if (m_SSU2Server)
{
@ -345,6 +352,7 @@ namespace transport
delete m_Thread;
m_Thread = nullptr;
}
m_Peers.clear ();
}
void Transports::Run ()
@ -364,51 +372,67 @@ namespace transport
}
}
void Transports::UpdateBandwidthValues(int interval, uint32_t& in, uint32_t& out, uint32_t& transit)
{
TrafficSample& sample1 = m_TrafficSamples[m_TrafficSamplePtr];
TrafficSample& sample2 = m_TrafficSamples[(TRAFFIC_SAMPLE_COUNT + m_TrafficSamplePtr - interval) % TRAFFIC_SAMPLE_COUNT];
auto delta = (int64_t)sample1.Timestamp - (int64_t)sample2.Timestamp;
if (delta <= 0)
{
LogPrint (eLogError, "Transports: Backward clock jump detected, got ", delta, " instead of ", interval * 1000);
return;
}
in = (sample1.TotalReceivedBytes - sample2.TotalReceivedBytes) * 1000 / delta;
out = (sample1.TotalSentBytes - sample2.TotalSentBytes) * 1000 / delta;
transit = (sample1.TotalTransitTransmittedBytes - sample2.TotalTransitTransmittedBytes) * 1000 / delta;
}
void Transports::HandleUpdateBandwidthTimer (const boost::system::error_code& ecode)
{
if (ecode != boost::asio::error::operation_aborted)
{
uint64_t ts = i2p::util::GetMillisecondsSinceEpoch ();
m_TrafficSamplePtr++;
if (m_TrafficSamplePtr == TRAFFIC_SAMPLE_COUNT)
m_TrafficSamplePtr = 0;
// updated every second
m_InBandwidth = m_TotalReceivedBytes - m_LastInBandwidthUpdateBytes;
m_OutBandwidth = m_TotalSentBytes - m_LastOutBandwidthUpdateBytes;
m_TransitBandwidth = m_TotalTransitTransmittedBytes - m_LastTransitBandwidthUpdateBytes;
TrafficSample& sample = m_TrafficSamples[m_TrafficSamplePtr];
sample.Timestamp = i2p::util::GetMillisecondsSinceEpoch();
sample.TotalReceivedBytes = m_TotalReceivedBytes;
sample.TotalSentBytes = m_TotalSentBytes;
sample.TotalTransitTransmittedBytes = m_TotalTransitTransmittedBytes;
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_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;
}
UpdateBandwidthValues (1, m_InBandwidth, m_OutBandwidth, m_TransitBandwidth);
UpdateBandwidthValues (15, m_InBandwidth15s, m_OutBandwidth15s, m_TransitBandwidth15s);
UpdateBandwidthValues (300, m_InBandwidth5m, m_OutBandwidth5m, m_TransitBandwidth5m);
m_UpdateBandwidthTimer->expires_from_now (boost::posix_time::seconds(1));
m_UpdateBandwidthTimer->async_wait (std::bind (&Transports::HandleUpdateBandwidthTimer, this, std::placeholders::_1));
}
}
bool Transports::IsBandwidthExceeded () const
int Transports::GetCongestionLevel (bool longTerm) const
{
auto limit = i2p::context.GetBandwidthLimit() * 1024; // convert to bytes
auto bw = std::max (m_InBandwidth15s, m_OutBandwidth15s);
return bw > limit;
}
auto bwLimit = i2p::context.GetBandwidthLimit () * 1024; // convert to bytes
auto tbwLimit = i2p::context.GetTransitBandwidthLimit () * 1024; // convert to bytes
bool Transports::IsTransitBandwidthExceeded () const
{
auto limit = i2p::context.GetTransitBandwidthLimit() * 1024; // convert to bytes
return m_TransitBandwidth > limit;
if (tbwLimit == 0 || bwLimit == 0)
return CONGESTION_LEVEL_FULL;
uint32_t bw;
uint32_t tbw;
if (longTerm)
{
bw = std::max (m_InBandwidth5m, m_OutBandwidth5m);
tbw = m_TransitBandwidth5m;
}
else
{
bw = std::max (m_InBandwidth15s, m_OutBandwidth15s);
tbw = m_TransitBandwidth;
}
auto bwCongestionLevel = CONGESTION_LEVEL_FULL * bw / bwLimit;
auto tbwCongestionLevel = CONGESTION_LEVEL_FULL * tbw / tbwLimit;
return std::max (bwCongestionLevel, tbwCongestionLevel);
}
void Transports::SendMessage (const i2p::data::IdentHash& ident, std::shared_ptr<i2p::I2NPMessage> msg)
@ -433,9 +457,13 @@ namespace transport
return;
}
if(RoutesRestricted() && !IsRestrictedPeer(ident)) return;
std::shared_ptr<Peer> peer;
auto it = m_Peers.find (ident);
if (it == m_Peers.end ())
{
// check if not banned
if (i2p::data::IsRouterBanned (ident)) return; // don't create peer to unreachable router
// try to connect
bool connected = false;
try
{
@ -443,10 +471,12 @@ namespace transport
if (r && (r->IsUnreachable () || !r->IsReachableFrom (i2p::context.GetRouterInfo ()))) return; // router found but non-reachable
{
auto ts = i2p::util::GetSecondsSinceEpoch ();
peer = std::make_shared<Peer>(r, ts);
std::unique_lock<std::mutex> l(m_PeersMutex);
it = m_Peers.insert (std::pair<i2p::data::IdentHash, Peer>(ident, {r, ts})).first;
peer = m_Peers.emplace (ident, peer).first->second;
}
connected = ConnectToPeer (ident, it->second);
if (peer)
connected = ConnectToPeer (ident, peer);
}
catch (std::exception& ex)
{
@ -454,49 +484,55 @@ namespace transport
}
if (!connected) return;
}
if (!it->second.sessions.empty ())
it->second.sessions.front ()->SendI2NPMessages (msgs);
else
peer = it->second;
if (!peer) return;
if (peer->IsConnected ())
peer->sessions.front ()->SendI2NPMessages (msgs);
else
{
auto sz = it->second.delayedMessages.size ();
auto sz = peer->delayedMessages.size ();
if (sz < MAX_NUM_DELAYED_MESSAGES)
{
if (sz < CHECK_PROFILE_NUM_DELAYED_MESSAGES && sz + msgs.size () >= CHECK_PROFILE_NUM_DELAYED_MESSAGES)
{
auto profile = i2p::data::GetRouterProfile (ident);
if (profile && profile->IsUnreachable ())
if (i2p::data::IsRouterBanned (ident))
{
LogPrint (eLogWarning, "Transports: Peer profile for ", ident.ToBase64 (), " reports unreachable. Dropped");
LogPrint (eLogWarning, "Transports: Router ", ident.ToBase64 (), " is banned. Peer dropped");
std::unique_lock<std::mutex> l(m_PeersMutex);
m_Peers.erase (it);
m_Peers.erase (ident);
return;
}
}
for (auto& it1: msgs)
it->second.delayedMessages.push_back (it1);
if (sz > MAX_NUM_DELAYED_MESSAGES/2 && it1->onDrop)
it1->Drop (); // drop earlier because we can handle it
else
peer->delayedMessages.push_back (it1);
}
else
{
LogPrint (eLogWarning, "Transports: Delayed messages queue size to ",
ident.ToBase64 (), " exceeds ", MAX_NUM_DELAYED_MESSAGES);
std::unique_lock<std::mutex> l(m_PeersMutex);
m_Peers.erase (it);
m_Peers.erase (ident);
}
}
}
bool Transports::ConnectToPeer (const i2p::data::IdentHash& ident, Peer& peer)
bool Transports::ConnectToPeer (const i2p::data::IdentHash& ident, std::shared_ptr<Peer> peer)
{
if (!peer.router) // reconnect
peer.SetRouter (netdb.FindRouter (ident)); // try to get new one from netdb
if (peer.router) // we have RI already
if (!peer->router) // reconnect
peer->SetRouter (netdb.FindRouter (ident)); // try to get new one from netdb
if (peer->router) // we have RI already
{
if (peer.priority.empty ())
if (peer->priority.empty ())
SetPriority (peer);
while (peer.numAttempts < (int)peer.priority.size ())
while (peer->numAttempts < (int)peer->priority.size ())
{
auto tr = peer.priority[peer.numAttempts];
peer.numAttempts++;
auto tr = peer->priority[peer->numAttempts];
peer->numAttempts++;
switch (tr)
{
case i2p::data::RouterInfo::eNTCP2V4:
@ -504,12 +540,12 @@ namespace transport
{
if (!m_NTCP2Server) continue;
std::shared_ptr<const RouterInfo::Address> address = (tr == i2p::data::RouterInfo::eNTCP2V6) ?
peer.router->GetPublishedNTCP2V6Address () : peer.router->GetPublishedNTCP2V4Address ();
if (address && m_CheckReserved && i2p::util::net::IsInReservedRange(address->host))
peer->router->GetPublishedNTCP2V6Address () : peer->router->GetPublishedNTCP2V4Address ();
if (address && IsInReservedRange(address->host))
address = nullptr;
if (address)
{
auto s = std::make_shared<NTCP2Session> (*m_NTCP2Server, peer.router, address);
auto s = std::make_shared<NTCP2Session> (*m_NTCP2Server, peer->router, address);
if( m_NTCP2Server->UsingProxy())
m_NTCP2Server->ConnectWithProxy(s);
else
@ -523,12 +559,12 @@ namespace transport
{
if (!m_SSU2Server) continue;
std::shared_ptr<const RouterInfo::Address> address = (tr == i2p::data::RouterInfo::eSSU2V6) ?
peer.router->GetSSU2V6Address () : peer.router->GetSSU2V4Address ();
if (address && m_CheckReserved && i2p::util::net::IsInReservedRange(address->host))
peer->router->GetSSU2V6Address () : peer->router->GetSSU2V4Address ();
if (address && IsInReservedRange(address->host))
address = nullptr;
if (address && address->IsReachableSSU ())
{
if (m_SSU2Server->CreateSession (peer.router, address))
if (m_SSU2Server->CreateSession (peer->router, address))
return true;
}
break;
@ -536,10 +572,10 @@ namespace transport
case i2p::data::RouterInfo::eNTCP2V6Mesh:
{
if (!m_NTCP2Server) continue;
auto address = peer.router->GetYggdrasilAddress ();
auto address = peer->router->GetYggdrasilAddress ();
if (address)
{
auto s = std::make_shared<NTCP2Session> (*m_NTCP2Server, peer.router, address);
auto s = std::make_shared<NTCP2Session> (*m_NTCP2Server, peer->router, address);
m_NTCP2Server->Connect (s);
return true;
}
@ -551,9 +587,17 @@ namespace transport
}
LogPrint (eLogInfo, "Transports: No compatible addresses available");
if (peer.router->IsReachableFrom (i2p::context.GetRouterInfo ()))
if (peer->router->IsReachableFrom (i2p::context.GetRouterInfo ()))
i2p::data::netdb.SetUnreachable (ident, true); // we are here because all connection attempts failed but router claimed them
peer.Done ();
peer->Done ();
std::unique_lock<std::mutex> l(m_PeersMutex);
m_Peers.erase (ident);
return false;
}
else if (i2p::data::IsRouterBanned (ident))
{
LogPrint (eLogWarning, "Transports: Router ", ident.ToBase64 (), " is banned. Peer dropped");
peer->Done ();
std::unique_lock<std::mutex> l(m_PeersMutex);
m_Peers.erase (ident);
return false;
@ -567,7 +611,7 @@ namespace transport
return true;
}
void Transports::SetPriority (Peer& peer) const
void Transports::SetPriority (std::shared_ptr<Peer> peer) const
{
static const std::vector<i2p::data::RouterInfo::SupportedTransports>
ntcp2Priority =
@ -586,16 +630,36 @@ namespace transport
i2p::data::RouterInfo::eNTCP2V4,
i2p::data::RouterInfo::eNTCP2V6Mesh
};
if (!peer.router) return;
if (!peer || !peer->router) return;
auto compatibleTransports = context.GetRouterInfo ().GetCompatibleTransports (false) &
peer.router->GetCompatibleTransports (true);
peer.numAttempts = 0;
peer.priority.clear ();
bool ssu2 = peer.router->GetProfile ()->IsReal () ? (rand () & 1) : false; // try NTCP2 if router is not confirmed real
peer->router->GetCompatibleTransports (true);
auto directTransports = compatibleTransports & peer->router->GetPublishedTransports ();
peer->numAttempts = 0;
peer->priority.clear ();
bool isReal = peer->router->GetProfile ()->IsReal ();
bool ssu2 = isReal ? (rand () & 1) : false; // try NTCP2 if router is not confirmed real
const auto& priority = ssu2 ? ssu2Priority : ntcp2Priority;
for (auto transport: priority)
if (transport & compatibleTransports)
peer.priority.push_back (transport);
if (directTransports)
{
// direct connections have higher priority
if (!isReal && (directTransports & (i2p::data::RouterInfo::eNTCP2V4 | i2p::data::RouterInfo::eNTCP2V6)))
{
// Non-confirmed router and a NTCP2 direct connection is presented
compatibleTransports &= ~directTransports; // exclude SSU2 direct connections
directTransports &= ~(i2p::data::RouterInfo::eSSU2V4 | i2p::data::RouterInfo::eSSU2V6);
}
for (auto transport: priority)
if (transport & directTransports)
peer->priority.push_back (transport);
compatibleTransports &= ~directTransports;
}
if (compatibleTransports)
{
// then remaining
for (auto transport: priority)
if (transport & compatibleTransports)
peer->priority.push_back (transport);
}
}
void Transports::RequestComplete (std::shared_ptr<const i2p::data::RouterInfo> r, const i2p::data::IdentHash& ident)
@ -611,8 +675,9 @@ namespace transport
if (r)
{
LogPrint (eLogDebug, "Transports: RouterInfo for ", ident.ToBase64 (), " found, trying to connect");
it->second.SetRouter (r);
ConnectToPeer (ident, it->second);
it->second->SetRouter (r);
if (!it->second->IsConnected ())
ConnectToPeer (ident, it->second);
}
else
{
@ -643,7 +708,7 @@ namespace transport
if (ipv4 && i2p::context.SupportsV4 ())
{
LogPrint (eLogInfo, "Transports: Started peer test IPv4");
std::set<i2p::data::IdentHash> excluded;
std::unordered_set<i2p::data::IdentHash> excluded;
excluded.insert (i2p::context.GetIdentHash ()); // don't pick own router
int testDelay = 0;
for (int i = 0; i < 5; i++)
@ -681,7 +746,7 @@ namespace transport
if (ipv6 && i2p::context.SupportsV6 ())
{
LogPrint (eLogInfo, "Transports: Started peer test IPv6");
std::set<i2p::data::IdentHash> excluded;
std::unordered_set<i2p::data::IdentHash> excluded;
excluded.insert (i2p::context.GetIdentHash ()); // don't pick own router
int testDelay = 0;
for (int i = 0; i < 5; i++)
@ -738,31 +803,32 @@ namespace transport
auto it = m_Peers.find (ident);
if (it != m_Peers.end ())
{
if (it->second.numAttempts > 1)
auto peer = it->second;
if (peer->numAttempts > 1)
{
// exclude failed transports
i2p::data::RouterInfo::CompatibleTransports transports = 0;
int numExcluded = it->second.numAttempts - 1;
if (numExcluded > (int)it->second.priority.size ()) numExcluded = it->second.priority.size ();
int numExcluded = peer->numAttempts - 1;
if (numExcluded > (int)peer->priority.size ()) numExcluded = peer->priority.size ();
for (int i = 0; i < numExcluded; i++)
transports |= it->second.priority[i];
transports |= peer->priority[i];
i2p::data::netdb.ExcludeReachableTransports (ident, transports);
}
if (it->second.router && it->second.numAttempts)
if (peer->router && peer->numAttempts)
{
auto transport = it->second.priority[it->second.numAttempts-1];
auto transport = peer->priority[peer->numAttempts-1];
if (transport == i2p::data::RouterInfo::eNTCP2V4 ||
transport == i2p::data::RouterInfo::eNTCP2V6 || transport == i2p::data::RouterInfo::eNTCP2V6Mesh)
it->second.router->GetProfile ()->Connected (); // outgoing NTCP2 connection if always real
peer->router->GetProfile ()->Connected (); // outgoing NTCP2 connection if always real
i2p::data::netdb.SetUnreachable (ident, false); // clear unreachable
}
it->second.numAttempts = 0;
it->second.router = nullptr; // we don't need RouterInfo after successive connect
peer->numAttempts = 0;
peer->router = nullptr; // we don't need RouterInfo after successive connect
bool sendDatabaseStore = true;
if (it->second.delayedMessages.size () > 0)
if (it->second->delayedMessages.size () > 0)
{
// check if first message is our DatabaseStore (publishing)
auto firstMsg = it->second.delayedMessages[0];
auto firstMsg = peer->delayedMessages[0];
if (firstMsg && firstMsg->GetTypeID () == eI2NPDatabaseStore &&
i2p::data::IdentHash(firstMsg->GetPayload () + DATABASE_STORE_KEY_OFFSET) == i2p::context.GetIdentHash ())
sendDatabaseStore = false; // we have it in the list already
@ -771,9 +837,9 @@ namespace transport
session->SendLocalRouterInfo ();
else
session->SetTerminationTimeout (10); // most likely it's publishing, no follow-up messages expected, set timeout to 10 seconds
it->second.sessions.push_back (session);
session->SendI2NPMessages (it->second.delayedMessages);
it->second.delayedMessages.clear ();
peer->sessions.push_back (session);
session->SendI2NPMessages (peer->delayedMessages);
peer->delayedMessages.clear ();
}
else // incoming connection or peer test
{
@ -788,10 +854,11 @@ namespace transport
auto r = i2p::data::netdb.FindRouter (ident); // router should be in netdb after SessionConfirmed
if (r) r->GetProfile ()->Connected ();
auto ts = i2p::util::GetSecondsSinceEpoch ();
auto peer = std::make_shared<Peer>(r, ts);
peer->sessions.push_back (session);
peer->router = nullptr;
std::unique_lock<std::mutex> l(m_PeersMutex);
auto it = m_Peers.insert (std::make_pair (ident, Peer{ r, ts })).first;
it->second.sessions.push_back (session);
it->second.router = nullptr;
m_Peers.emplace (ident, peer);
}
});
}
@ -806,15 +873,16 @@ namespace transport
auto it = m_Peers.find (ident);
if (it != m_Peers.end ())
{
auto before = it->second.sessions.size ();
it->second.sessions.remove (session);
if (it->second.sessions.empty ())
auto peer = it->second;
bool wasConnected = peer->IsConnected ();
peer->sessions.remove (session);
if (!peer->IsConnected ())
{
if (it->second.delayedMessages.size () > 0)
if (peer->delayedMessages.size () > 0)
{
if (before > 0) // we had an active session before
it->second.numAttempts = 0; // start over
ConnectToPeer (ident, it->second);
if (wasConnected) // we had an active session before
peer->numAttempts = 0; // start over
ConnectToPeer (ident, peer);
}
else
{
@ -840,12 +908,12 @@ namespace transport
auto ts = i2p::util::GetSecondsSinceEpoch ();
for (auto it = m_Peers.begin (); it != m_Peers.end (); )
{
it->second.sessions.remove_if (
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)
if (!it->second->IsConnected () && 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");
/* if (!it->second.router)
@ -859,12 +927,12 @@ namespace transport
}
else
{
if (ts > it->second.nextRouterInfoUpdateTime)
if (ts > it->second->nextRouterInfoUpdateTime)
{
auto session = it->second.sessions.front ();
auto session = it->second->sessions.front ();
if (session)
session->SendLocalRouterInfo (true);
it->second.nextRouterInfoUpdateTime = ts + PEER_ROUTER_INFO_UPDATE_INTERVAL +
it->second->nextRouterInfoUpdateTime = ts + PEER_ROUTER_INFO_UPDATE_INTERVAL +
rand () % PEER_ROUTER_INFO_UPDATE_INTERVAL_VARIANCE;
}
++it;
@ -984,13 +1052,13 @@ namespace transport
std::shared_ptr<const i2p::data::RouterInfo> Transports::GetRandomPeer (bool isHighBandwidth) const
{
return GetRandomPeer (
[isHighBandwidth](const Peer& peer)->bool
[isHighBandwidth](std::shared_ptr<const Peer> peer)->bool
{
// connected, not overloaded and not slow
return !peer.router && !peer.sessions.empty () && peer.isReachable &&
peer.sessions.front ()->GetSendQueueSize () <= PEER_ROUTER_INFO_OVERLOAD_QUEUE_SIZE &&
!peer.sessions.front ()->IsSlow () && !peer.sessions.front ()->IsBandwidthExceeded (peer.isHighBandwidth) &&
(!isHighBandwidth || peer.isHighBandwidth);
return !peer->router && peer->IsConnected () && peer->isReachable &&
peer->sessions.front ()->GetSendQueueSize () <= PEER_ROUTER_INFO_OVERLOAD_QUEUE_SIZE &&
!peer->sessions.front ()->IsSlow () && !peer->sessions.front ()->IsBandwidthExceeded (peer->isHighBandwidth) &&
(!isHighBandwidth || peer->isHighBandwidth);
});
}
@ -1007,18 +1075,25 @@ namespace transport
}
}
void Transports::RestrictRoutesToRouters(std::set<i2p::data::IdentHash> routers)
void Transports::RestrictRoutesToRouters(const std::set<i2p::data::IdentHash>& routers)
{
std::unique_lock<std::mutex> lock(m_TrustedRoutersMutex);
std::lock_guard<std::mutex> lock(m_TrustedRoutersMutex);
m_TrustedRouters.clear();
for (const auto & ri : routers )
m_TrustedRouters.push_back(ri);
}
bool Transports::RoutesRestricted() const {
std::unique_lock<std::mutex> famlock(m_FamilyMutex);
std::unique_lock<std::mutex> routerslock(m_TrustedRoutersMutex);
return m_TrustedFamilies.size() > 0 || m_TrustedRouters.size() > 0;
bool Transports::RoutesRestricted() const
{
{
std::lock_guard<std::mutex> routerslock(m_TrustedRoutersMutex);
if (!m_TrustedRouters.empty ()) return true;
}
{
std::lock_guard<std::mutex> famlock(m_FamilyMutex);
if (!m_TrustedFamilies.empty ()) return true;
}
return false;
}
/** XXX: if routes are not restricted this dies */
@ -1042,7 +1117,7 @@ namespace transport
return i2p::data::netdb.GetRandomRouterInFamily(fam);
}
{
std::unique_lock<std::mutex> l(m_TrustedRoutersMutex);
std::lock_guard<std::mutex> l(m_TrustedRoutersMutex);
auto sz = m_TrustedRouters.size();
if (sz)
{
@ -1059,12 +1134,12 @@ namespace transport
bool Transports::IsRestrictedPeer(const i2p::data::IdentHash & ih) const
{
{
std::unique_lock<std::mutex> l(m_TrustedRoutersMutex);
std::lock_guard<std::mutex> l(m_TrustedRoutersMutex);
for (const auto & r : m_TrustedRouters )
if ( r == ih ) return true;
}
{
std::unique_lock<std::mutex> l(m_FamilyMutex);
std::lock_guard<std::mutex> l(m_FamilyMutex);
auto ri = i2p::data::netdb.FindRouter(ih);
for (const auto & fam : m_TrustedFamilies)
if(ri->IsFamily(fam)) return true;
@ -1084,6 +1159,11 @@ namespace transport
}
}
bool Transports::IsInReservedRange (const boost::asio::ip::address& host) const
{
return IsCheckReserved () && i2p::util::net::IsInReservedRange (host);
}
void InitAddressFromIface ()
{
bool ipv6; i2p::config::GetOption("ipv6", ipv6);
@ -1195,7 +1275,6 @@ namespace transport
else
i2p::context.PublishSSU2Address (ssu2port, false, ipv4, ipv6); // unpublish
}
}
}
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2023, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -85,11 +85,14 @@ namespace transport
isReachable = (bool)router->GetCompatibleTransports (true);
}
}
void Done ()
{
for (auto& it: sessions)
it->Done ();
// drop not sent delayed messages
for (auto& it: delayedMessages)
it->Drop ();
}
void SetRouter (std::shared_ptr<const i2p::data::RouterInfo> r)
@ -101,6 +104,8 @@ namespace transport
isReachable = (bool)router->GetCompatibleTransports (true);
}
}
bool IsConnected () const { return !sessions.empty (); }
};
const uint64_t SESSION_CREATION_TIMEOUT = 15; // in seconds
@ -109,6 +114,17 @@ namespace transport
const int PEER_TEST_DELAY_INTERVAL_VARIANCE = 30; // in milliseconds
const int MAX_NUM_DELAYED_MESSAGES = 150;
const int CHECK_PROFILE_NUM_DELAYED_MESSAGES = 15; // check profile after
const int TRAFFIC_SAMPLE_COUNT = 301; // seconds
struct TrafficSample
{
uint64_t Timestamp;
uint64_t TotalReceivedBytes;
uint64_t TotalSentBytes;
uint64_t TotalTransitTransmittedBytes;
};
class Transports
{
public:
@ -118,6 +134,7 @@ namespace transport
void Start (bool enableNTCP2=true, bool enableSSU2=true);
void Stop ();
bool IsRunning () const { return m_IsRunning; }
bool IsBoundSSU2() const { return m_SSU2Server != nullptr; }
bool IsBoundNTCP2() const { return m_NTCP2Server != nullptr; }
@ -148,8 +165,7 @@ namespace transport
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;
int GetCongestionLevel (bool longTerm) const;
size_t GetNumPeers () const { return m_Peers.size (); };
std::shared_ptr<const i2p::data::RouterInfo> GetRandomPeer (bool isHighBandwidth) const;
@ -160,14 +176,15 @@ namespace transport
/** restrict routes to use only these router families for first hops */
void RestrictRoutesToFamilies(const std::set<std::string>& families);
/** restrict routes to use only these routers for first hops */
void RestrictRoutesToRouters(std::set<i2p::data::IdentHash> routers);
void RestrictRoutesToRouters(const std::set<i2p::data::IdentHash>& routers);
bool IsRestrictedPeer(const i2p::data::IdentHash & ident) const;
void PeerTest (bool ipv4 = true, bool ipv6 = true);
void SetCheckReserved (bool check) { m_CheckReserved = check; };
bool IsCheckReserved () { return m_CheckReserved; };
bool IsCheckReserved () const { return m_CheckReserved; };
bool IsInReservedRange (const boost::asio::ip::address& host) const;
private:
@ -175,11 +192,12 @@ namespace transport
void RequestComplete (std::shared_ptr<const i2p::data::RouterInfo> r, const i2p::data::IdentHash& ident);
void HandleRequestComplete (std::shared_ptr<const i2p::data::RouterInfo> r, i2p::data::IdentHash ident);
void PostMessages (i2p::data::IdentHash ident, std::vector<std::shared_ptr<i2p::I2NPMessage> > msgs);
bool ConnectToPeer (const i2p::data::IdentHash& ident, Peer& peer);
void SetPriority (Peer& peer) const;
bool ConnectToPeer (const i2p::data::IdentHash& ident, std::shared_ptr<Peer> peer);
void SetPriority (std::shared_ptr<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 UpdateBandwidthValues (int interval, uint32_t& in, uint32_t& out, uint32_t& transit);
void DetectExternalIP ();
@ -198,20 +216,21 @@ namespace transport
SSU2Server * m_SSU2Server;
NTCP2Server * m_NTCP2Server;
mutable std::mutex m_PeersMutex;
std::unordered_map<i2p::data::IdentHash, Peer> m_Peers;
std::unordered_map<i2p::data::IdentHash, std::shared_ptr<Peer> > m_Peers;
X25519KeysPairSupplier m_X25519KeysPairSupplier;
std::atomic<uint64_t> m_TotalSentBytes, m_TotalReceivedBytes, m_TotalTransitTransmittedBytes;
TrafficSample m_TrafficSamples[TRAFFIC_SAMPLE_COUNT];
int m_TrafficSamplePtr;
// Bandwidth per second
uint32_t m_InBandwidth, m_OutBandwidth, m_TransitBandwidth;
uint64_t m_LastInBandwidthUpdateBytes, m_LastOutBandwidthUpdateBytes, m_LastTransitBandwidthUpdateBytes;
// Bandwidth every 15 seconds
// Bandwidth during last 15 seconds
uint32_t m_InBandwidth15s, m_OutBandwidth15s, m_TransitBandwidth15s;
uint64_t m_LastInBandwidth15sUpdateBytes, m_LastOutBandwidth15sUpdateBytes, m_LastTransitBandwidth15sUpdateBytes;
uint64_t m_LastBandwidth15sUpdateTime;
// Bandwidth during last 5 minutes
uint32_t m_InBandwidth5m, m_OutBandwidth5m, m_TransitBandwidth5m;
/** which router families to trust for first hops */
std::vector<i2p::data::FamilyID> m_TrustedFamilies;

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2023, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -33,7 +33,7 @@ namespace tunnel
TunnelBase (config->GetTunnelID (), config->GetNextTunnelID (), config->GetNextIdentHash ()),
m_Config (config), m_IsShortBuildMessage (false), m_Pool (nullptr),
m_State (eTunnelStatePending), m_FarEndTransports (i2p::data::RouterInfo::eAllTransports),
m_IsRecreated (false), m_Latency (0)
m_IsRecreated (false), m_Latency (UNKNOWN_LATENCY)
{
}
@ -52,7 +52,7 @@ namespace tunnel
// shuffle records
std::vector<int> recordIndicies;
for (int i = 0; i < numRecords; i++) recordIndicies.push_back(i);
std::shuffle (recordIndicies.begin(), recordIndicies.end(), std::mt19937(std::random_device()()));
std::shuffle (recordIndicies.begin(), recordIndicies.end(), m_Pool ? m_Pool->GetRng () : std::mt19937(std::random_device()()));
// create real records
uint8_t * records = msg->GetPayload () + 1;
@ -90,7 +90,13 @@ namespace tunnel
hop = hop->prev;
}
msg->FillI2NPMessageHeader (m_Config->IsShort () ? eI2NPShortTunnelBuild : eI2NPVariableTunnelBuild);
auto s = shared_from_this ();
msg->onDrop = [s]()
{
LogPrint (eLogInfo, "I2NP: Tunnel ", s->GetTunnelID (), " request was not sent");
s->SetState (i2p::tunnel::eTunnelStateBuildFailed);
};
// send message
if (outboundTunnel)
{
@ -116,7 +122,7 @@ namespace tunnel
if (m_Pool && m_Pool->GetLocalDestination ())
m_Pool->GetLocalDestination ()->SubmitECIESx25519Key (key, tag);
else
i2p::context.AddECIESx25519Key (key, tag);
i2p::context.SubmitECIESx25519Key (key, tag);
}
i2p::transport::transports.SendMessage (GetNextIdentHash (), msg);
}
@ -192,10 +198,10 @@ namespace tunnel
return established;
}
bool Tunnel::LatencyFitsRange(uint64_t lower, uint64_t upper) const
bool Tunnel::LatencyFitsRange(int lowerbound, int upperbound) const
{
auto latency = GetMeanLatency();
return latency >= lower && latency <= upper;
return latency >= lowerbound && latency <= upperbound;
}
void Tunnel::EncryptTunnelMsg (std::shared_ptr<const I2NPMessage> in, std::shared_ptr<I2NPMessage> out)
@ -244,9 +250,9 @@ namespace tunnel
void InboundTunnel::HandleTunnelDataMsg (std::shared_ptr<I2NPMessage>&& msg)
{
if (IsFailed ()) SetState (eTunnelStateEstablished); // incoming messages means a tunnel is alive
if (GetState () != eTunnelStateExpiring) SetState (eTunnelStateEstablished); // incoming messages means a tunnel is alive
EncryptTunnelMsg (msg, msg);
msg->from = shared_from_this ();
msg->from = GetSharedFromThis ();
m_Endpoint.HandleDecryptedTunnelDataMsg (msg);
}
@ -261,7 +267,7 @@ namespace tunnel
if (msg)
{
m_NumReceivedBytes += msg->GetLength ();
msg->from = shared_from_this ();
msg->from = GetSharedFromThis ();
HandleI2NPMessage (msg);
}
}
@ -586,17 +592,6 @@ namespace tunnel
auto typeID = msg->GetTypeID ();
LogPrint (eLogDebug, "Tunnel: Gateway of ", (int) len, " bytes for tunnel ", tunnel->GetTunnelID (), ", msg type ", (int)typeID);
if (typeID == eI2NPDatabaseSearchReply)
// DatabaseSearchReply with new routers
i2p::data::netdb.PostI2NPMsg (CopyI2NPMessage (msg));
else if (IsRouterInfoMsg (msg))
{
// transit DatabaseStore might contain new/updated RI
auto m = CopyI2NPMessage (msg);
if (bufbe32toh (m->GetPayload () + DATABASE_STORE_REPLY_TOKEN_OFFSET))
memset (m->GetPayload () + DATABASE_STORE_REPLY_TOKEN_OFFSET, 0xFF, 4); // fake replyToken meaning no reply
i2p::data::netdb.PostI2NPMsg (m);
}
tunnel->SendTunnelDataMsg (msg);
}
@ -673,9 +668,10 @@ namespace tunnel
for (auto it = m_OutboundTunnels.begin (); it != m_OutboundTunnels.end ();)
{
auto tunnel = *it;
if (ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT)
if (tunnel->IsFailed () || ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT ||
ts + TUNNEL_EXPIRATION_TIMEOUT < tunnel->GetCreationTime ())
{
LogPrint (eLogDebug, "Tunnel: Tunnel with id ", tunnel->GetTunnelID (), " expired");
LogPrint (eLogDebug, "Tunnel: Tunnel with id ", tunnel->GetTunnelID (), " expired or failed");
auto pool = tunnel->GetTunnelPool ();
if (pool)
pool->TunnelExpired (tunnel);
@ -724,10 +720,10 @@ namespace tunnel
for (auto it = m_InboundTunnels.begin (); it != m_InboundTunnels.end ();)
{
auto tunnel = *it;
if (ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT ||
if (tunnel->IsFailed () || ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT ||
ts + TUNNEL_EXPIRATION_TIMEOUT < tunnel->GetCreationTime ())
{
LogPrint (eLogDebug, "Tunnel: Tunnel with id ", tunnel->GetTunnelID (), " expired");
LogPrint (eLogDebug, "Tunnel: Tunnel with id ", tunnel->GetTunnelID (), " expired or failed");
auto pool = tunnel->GetTunnelPool ();
if (pool)
pool->TunnelExpired (tunnel);
@ -979,7 +975,7 @@ namespace tunnel
return m_OutboundTunnels.size();
}
void Tunnels::SetMaxNumTransitTunnels (uint16_t maxNumTransitTunnels)
void Tunnels::SetMaxNumTransitTunnels (uint32_t maxNumTransitTunnels)
{
if (maxNumTransitTunnels > 0 && m_MaxNumTransitTunnels != maxNumTransitTunnels)
{

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2023, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -39,7 +39,8 @@ namespace tunnel
const int TUNNEL_CREATION_TIMEOUT = 30; // 30 seconds
const int STANDARD_NUM_RECORDS = 4; // in VariableTunnelBuild message
const int MAX_NUM_RECORDS = 8;
const int HIGH_LATENCY_PER_HOP = 250; // in milliseconds
const int UNKNOWN_LATENCY = -1;
const int HIGH_LATENCY_PER_HOP = 250000; // in microseconds
const int MAX_TUNNEL_MSGS_BATCH_SIZE = 100; // handle messages without interrupt
const uint16_t DEFAULT_MAX_NUM_TRANSIT_TUNNELS = 5000;
const int TUNNEL_MANAGE_INTERVAL = 15; // in seconds
@ -65,7 +66,8 @@ namespace tunnel
class OutboundTunnel;
class InboundTunnel;
class Tunnel: public TunnelBase
class Tunnel: public TunnelBase,
public std::enable_shared_from_this<Tunnel>
{
struct TunnelHop
{
@ -90,7 +92,7 @@ namespace tunnel
i2p::data::RouterInfo::CompatibleTransports GetFarEndTransports () const { return m_FarEndTransports; };
TunnelState GetState () const { return m_State; };
void SetState (TunnelState state);
bool IsEstablished () const { return m_State == eTunnelStateEstablished; };
bool IsEstablished () const { return m_State == eTunnelStateEstablished || m_State == eTunnelStateTestFailed; };
bool IsFailed () const { return m_State == eTunnelStateFailed; };
bool IsRecreated () const { return m_IsRecreated; };
void SetRecreated (bool recreated) { m_IsRecreated = recreated; };
@ -107,14 +109,14 @@ namespace tunnel
void EncryptTunnelMsg (std::shared_ptr<const I2NPMessage> in, std::shared_ptr<I2NPMessage> out) override;
/** @brief add latency sample */
void AddLatencySample(const uint64_t ms) { m_Latency = (m_Latency + ms) >> 1; }
void AddLatencySample(const int us) { m_Latency = LatencyIsKnown() ? (m_Latency + us) >> 1 : us; }
/** @brief get this tunnel's estimated latency */
uint64_t GetMeanLatency() const { return m_Latency; }
int GetMeanLatency() const { return (m_Latency + 500) / 1000; }
/** @brief return true if this tunnel's latency fits in range [lowerbound, upperbound] */
bool LatencyFitsRange(uint64_t lowerbound, uint64_t upperbound) const;
bool LatencyFitsRange(int lowerbound, int upperbound) const;
bool LatencyIsKnown() const { return m_Latency > 0; }
bool IsSlow () const { return LatencyIsKnown() && (int)m_Latency > HIGH_LATENCY_PER_HOP*GetNumHops (); }
bool LatencyIsKnown() const { return m_Latency != UNKNOWN_LATENCY; }
bool IsSlow () const { return LatencyIsKnown() && m_Latency > HIGH_LATENCY_PER_HOP*GetNumHops (); }
/** visit all hops we currently store */
void VisitTunnelHops(TunnelHopVisitor v);
@ -128,7 +130,7 @@ namespace tunnel
TunnelState m_State;
i2p::data::RouterInfo::CompatibleTransports m_FarEndTransports;
bool m_IsRecreated; // if tunnel is replaced by new, or new tunnel requested to replace
uint64_t m_Latency; // in milliseconds
int m_Latency; // in microseconds
};
class OutboundTunnel: public Tunnel
@ -155,7 +157,7 @@ namespace tunnel
i2p::data::IdentHash m_EndpointIdentHash;
};
class InboundTunnel: public Tunnel, public std::enable_shared_from_this<InboundTunnel>
class InboundTunnel: public Tunnel
{
public:
@ -167,6 +169,13 @@ namespace tunnel
// override TunnelBase
void Cleanup () override { m_Endpoint.Cleanup (); };
protected:
std::shared_ptr<InboundTunnel> GetSharedFromThis ()
{
return std::static_pointer_cast<InboundTunnel>(shared_from_this ());
}
private:
TunnelEndpoint m_Endpoint;
@ -230,9 +239,9 @@ namespace tunnel
std::shared_ptr<I2NPMessage> NewI2NPTunnelMessage (bool endpoint);
void SetMaxNumTransitTunnels (uint16_t maxNumTransitTunnels);
uint16_t GetMaxNumTransitTunnels () const { return m_MaxNumTransitTunnels; };
bool IsTooManyTransitTunnels () const { return m_TransitTunnels.size () >= m_MaxNumTransitTunnels; };
void SetMaxNumTransitTunnels (uint32_t maxNumTransitTunnels);
uint32_t GetMaxNumTransitTunnels () const { return m_MaxNumTransitTunnels; };
int GetCongestionLevel() const { return m_MaxNumTransitTunnels ? CONGESTION_LEVEL_FULL * m_TransitTunnels.size() / m_MaxNumTransitTunnels : CONGESTION_LEVEL_FULL; }
private:
@ -292,7 +301,7 @@ namespace tunnel
i2p::util::Queue<std::shared_ptr<I2NPMessage> > m_Queue;
i2p::util::MemoryPoolMt<I2NPMessageBuffer<I2NP_TUNNEL_ENPOINT_MESSAGE_SIZE> > m_I2NPTunnelEndpointMessagesMemoryPool;
i2p::util::MemoryPoolMt<I2NPMessageBuffer<I2NP_TUNNEL_MESSAGE_SIZE> > m_I2NPTunnelMessagesMemoryPool;
uint16_t m_MaxNumTransitTunnels;
uint32_t m_MaxNumTransitTunnels;
// count of tunnels for total TCSR algorithm
int m_TotalNumSuccesiveTunnelCreations, m_TotalNumFailedTunnelCreations;
double m_TunnelCreationSuccessRate;

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2021, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -35,6 +35,13 @@ namespace tunnel
if (!m_CurrentTunnelDataMsg)
{
CreateCurrentTunnelDataMessage ();
if (block.data && block.data->onDrop)
{
// onDrop is called for the first fragment in tunnel message
// that's usually true for short TBMs or lookups
m_CurrentTunnelDataMsg->onDrop = block.data->onDrop;
block.data->onDrop = nullptr;
}
messageCreated = true;
}
@ -155,7 +162,6 @@ namespace tunnel
void TunnelGatewayBuffer::CreateCurrentTunnelDataMessage ()
{
m_CurrentTunnelDataMsg = nullptr;
m_CurrentTunnelDataMsg = NewI2NPTunnelMessage (true); // tunnel endpoint is at least of two tunnel messages size
// we reserve space for padding
m_CurrentTunnelDataMsg->offset += TUNNEL_DATA_MSG_SIZE + I2NP_HEADER_SIZE;
@ -223,6 +229,7 @@ namespace tunnel
m_Tunnel->EncryptTunnelMsg (tunnelMsg, newMsg);
htobe32buf (newMsg->GetPayload (), m_Tunnel->GetNextTunnelID ());
newMsg->FillI2NPMessageHeader (eI2NPTunnelData);
if (tunnelMsg->onDrop) newMsg->onDrop = tunnelMsg->onDrop;
newTunnelMsgs.push_back (newMsg);
m_NumSentBytes += TUNNEL_DATA_MSG_SIZE;
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2023, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -7,13 +7,13 @@
*/
#include <algorithm>
#include <random>
#include "I2PEndian.h"
#include "Crypto.h"
#include "Tunnel.h"
#include "NetDb.hpp"
#include "Timestamp.h"
#include "Garlic.h"
#include "ECIESX25519AEADRatchetSession.h"
#include "Transports.h"
#include "Log.h"
#include "Tunnel.h"
@ -45,7 +45,8 @@ namespace tunnel
m_NumInboundHops (numInboundHops), m_NumOutboundHops (numOutboundHops),
m_NumInboundTunnels (numInboundTunnels), m_NumOutboundTunnels (numOutboundTunnels),
m_InboundVariance (inboundVariance), m_OutboundVariance (outboundVariance),
m_IsActive (true), m_CustomPeerSelector(nullptr)
m_IsActive (true), m_CustomPeerSelector(nullptr),
m_Rng(i2p::util::GetMonotonicMicroseconds ()%1000000LL)
{
if (m_NumInboundTunnels > TUNNEL_POOL_MAX_INBOUND_TUNNELS_QUANTITY)
m_NumInboundTunnels = TUNNEL_POOL_MAX_INBOUND_TUNNELS_QUANTITY;
@ -59,7 +60,7 @@ namespace tunnel
m_InboundVariance = (m_NumInboundHops < STANDARD_NUM_RECORDS) ? STANDARD_NUM_RECORDS - m_NumInboundHops : 0;
if (m_OutboundVariance > 0 && m_NumOutboundHops + m_OutboundVariance > STANDARD_NUM_RECORDS)
m_OutboundVariance = (m_NumOutboundHops < STANDARD_NUM_RECORDS) ? STANDARD_NUM_RECORDS - m_NumOutboundHops : 0;
m_NextManageTime = i2p::util::GetSecondsSinceEpoch () + rand () % TUNNEL_POOL_MANAGE_INTERVAL;
m_NextManageTime = i2p::util::GetSecondsSinceEpoch () + m_Rng () % TUNNEL_POOL_MANAGE_INTERVAL;
}
TunnelPool::~TunnelPool ()
@ -102,7 +103,10 @@ namespace tunnel
it->SetTunnelPool (nullptr);
m_OutboundTunnels.clear ();
}
m_Tests.clear ();
{
std::unique_lock<std::mutex> l(m_TestsMutex);
m_Tests.clear ();
}
}
bool TunnelPool::Reconfigure(int inHops, int outHops, int inQuant, int outQuant)
@ -145,8 +149,11 @@ namespace tunnel
if (expiredTunnel)
{
expiredTunnel->SetTunnelPool (nullptr);
for (auto& it: m_Tests)
if (it.second.second == expiredTunnel) it.second.second = nullptr;
{
std::unique_lock<std::mutex> l(m_TestsMutex);
for (auto& it: m_Tests)
if (it.second.second == expiredTunnel) it.second.second = nullptr;
}
std::unique_lock<std::mutex> l(m_InboundTunnelsMutex);
m_InboundTunnels.erase (expiredTunnel);
@ -167,8 +174,11 @@ namespace tunnel
if (expiredTunnel)
{
expiredTunnel->SetTunnelPool (nullptr);
for (auto& it: m_Tests)
if (it.second.first == expiredTunnel) it.second.first = nullptr;
{
std::unique_lock<std::mutex> l(m_TestsMutex);
for (auto& it: m_Tests)
if (it.second.first == expiredTunnel) it.second.first = nullptr;
}
std::unique_lock<std::mutex> l(m_OutboundTunnelsMutex);
m_OutboundTunnels.erase (expiredTunnel);
@ -201,14 +211,14 @@ namespace tunnel
}
std::shared_ptr<OutboundTunnel> TunnelPool::GetNextOutboundTunnel (std::shared_ptr<OutboundTunnel> excluded,
i2p::data::RouterInfo::CompatibleTransports compatible) const
i2p::data::RouterInfo::CompatibleTransports compatible)
{
std::unique_lock<std::mutex> l(m_OutboundTunnelsMutex);
return GetNextTunnel (m_OutboundTunnels, excluded, compatible);
}
std::shared_ptr<InboundTunnel> TunnelPool::GetNextInboundTunnel (std::shared_ptr<InboundTunnel> excluded,
i2p::data::RouterInfo::CompatibleTransports compatible) const
i2p::data::RouterInfo::CompatibleTransports compatible)
{
std::unique_lock<std::mutex> l(m_InboundTunnelsMutex);
return GetNextTunnel (m_InboundTunnels, excluded, compatible);
@ -216,10 +226,10 @@ namespace tunnel
template<class TTunnels>
typename TTunnels::value_type TunnelPool::GetNextTunnel (TTunnels& tunnels,
typename TTunnels::value_type excluded, i2p::data::RouterInfo::CompatibleTransports compatible) const
typename TTunnels::value_type excluded, i2p::data::RouterInfo::CompatibleTransports compatible)
{
if (tunnels.empty ()) return nullptr;
uint32_t ind = rand () % (tunnels.size ()/2 + 1), i = 0;
uint32_t ind = m_Rng () % (tunnels.size ()/2 + 1), i = 0;
bool skipped = false;
typename TTunnels::value_type tunnel = nullptr;
for (const auto& it: tunnels)
@ -239,7 +249,7 @@ namespace tunnel
}
if (!tunnel && skipped)
{
ind = rand () % (tunnels.size ()/2 + 1), i = 0;
ind = m_Rng () % (tunnels.size ()/2 + 1), i = 0;
for (const auto& it: tunnels)
{
if (it->IsEstablished () && it != excluded)
@ -254,10 +264,11 @@ namespace tunnel
return tunnel;
}
std::shared_ptr<OutboundTunnel> TunnelPool::GetNewOutboundTunnel (std::shared_ptr<OutboundTunnel> old) const
std::pair<std::shared_ptr<OutboundTunnel>, bool> TunnelPool::GetNewOutboundTunnel (std::shared_ptr<OutboundTunnel> old)
{
if (old && old->IsEstablished ()) return old;
if (old && old->IsEstablished ()) return std::make_pair(old, false);
std::shared_ptr<OutboundTunnel> tunnel;
bool freshTunnel = false;
if (old)
{
std::unique_lock<std::mutex> l(m_OutboundTunnelsMutex);
@ -270,8 +281,11 @@ namespace tunnel
}
if (!tunnel)
{
tunnel = GetNextOutboundTunnel ();
return tunnel;
freshTunnel = true;
}
return std::make_pair(tunnel, freshTunnel);
}
void TunnelPool::CreateTunnels ()
@ -301,7 +315,7 @@ namespace tunnel
{
for (auto it: m_OutboundTunnels)
{
// try to create inbound tunnel through the same path as succesive outbound
// try to create inbound tunnel through the same path as successive outbound
CreatePairedInboundTunnel (it);
num++;
if (num >= m_NumInboundTunnels) break;
@ -337,9 +351,12 @@ namespace tunnel
{
it.second.first->SetState (eTunnelStateFailed);
std::unique_lock<std::mutex> l(m_OutboundTunnelsMutex);
m_OutboundTunnels.erase (it.second.first);
if (m_OutboundTunnels.size () > 1 || m_NumOutboundTunnels <= 1) // don't fail last tunnel
m_OutboundTunnels.erase (it.second.first);
else
it.second.first->SetState (eTunnelStateTestFailed);
}
else
else if (it.second.first->GetState () != eTunnelStateExpiring)
it.second.first->SetState (eTunnelStateTestFailed);
}
if (it.second.second)
@ -348,48 +365,97 @@ namespace tunnel
{
it.second.second->SetState (eTunnelStateFailed);
{
std::unique_lock<std::mutex> l(m_InboundTunnelsMutex);
m_InboundTunnels.erase (it.second.second);
bool failed = false;
{
std::unique_lock<std::mutex> l(m_InboundTunnelsMutex);
if (m_InboundTunnels.size () > 1 || m_NumInboundTunnels <= 1) // don't fail last tunnel
{
m_InboundTunnels.erase (it.second.second);
failed = true;
}
else
it.second.second->SetState (eTunnelStateTestFailed);
}
if (failed && m_LocalDestination)
m_LocalDestination->SetLeaseSetUpdated ();
}
if (m_LocalDestination)
m_LocalDestination->SetLeaseSetUpdated ();
}
else
else if (it.second.second->GetState () != eTunnelStateExpiring)
it.second.second->SetState (eTunnelStateTestFailed);
}
}
// new tests
std::unique_lock<std::mutex> l1(m_OutboundTunnelsMutex);
auto it1 = m_OutboundTunnels.begin ();
std::unique_lock<std::mutex> l2(m_InboundTunnelsMutex);
auto it2 = m_InboundTunnels.begin ();
while (it1 != m_OutboundTunnels.end () && it2 != m_InboundTunnels.end ())
{
bool failed = false;
if ((*it1)->IsFailed ())
if (!m_LocalDestination) return;
std::vector<std::pair<std::shared_ptr<OutboundTunnel>, std::shared_ptr<InboundTunnel> > > newTests;
std::vector<std::shared_ptr<OutboundTunnel> > outboundTunnels;
{
std::unique_lock<std::mutex> l(m_OutboundTunnelsMutex);
for (auto& it: m_OutboundTunnels)
if (it->IsEstablished ())
outboundTunnels.push_back (it);
}
std::shuffle (outboundTunnels.begin(), outboundTunnels.end(), m_Rng);
std::vector<std::shared_ptr<InboundTunnel> > inboundTunnels;
{
std::unique_lock<std::mutex> l(m_InboundTunnelsMutex);
for (auto& it: m_InboundTunnels)
if (it->IsEstablished ())
inboundTunnels.push_back (it);
}
std::shuffle (inboundTunnels.begin(), inboundTunnels.end(), m_Rng);
auto it1 = outboundTunnels.begin ();
auto it2 = inboundTunnels.begin ();
while (it1 != outboundTunnels.end () && it2 != inboundTunnels.end ())
{
newTests.push_back(std::make_pair (*it1, *it2));
++it1; ++it2;
}
bool isECIES = m_LocalDestination->SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD);
for (auto& it: newTests)
{
uint32_t msgID;
RAND_bytes ((uint8_t *)&msgID, 4);
{
failed = true;
++it1;
std::unique_lock<std::mutex> l(m_TestsMutex);
m_Tests[msgID] = it;
}
if ((*it2)->IsFailed ())
auto msg = CreateTunnelTestMsg (msgID);
auto outbound = it.first;
auto s = shared_from_this ();
msg->onDrop = [msgID, outbound, s]()
{
// if test msg dropped locally it's outbound tunnel to blame
outbound->SetState (eTunnelStateFailed);
{
std::unique_lock<std::mutex> l(s->m_TestsMutex);
s->m_Tests.erase (msgID);
}
{
std::unique_lock<std::mutex> l(s->m_OutboundTunnelsMutex);
s->m_OutboundTunnels.erase (outbound);
}
};
// encrypt
if (isECIES)
{
failed = true;
++it2;
uint8_t key[32]; RAND_bytes (key, 32);
uint64_t tag; RAND_bytes ((uint8_t *)&tag, 8);
m_LocalDestination->SubmitECIESx25519Key (key, tag);
msg = i2p::garlic::WrapECIESX25519Message (msg, key, tag);
}
if (!failed)
else
{
uint32_t msgID;
RAND_bytes ((uint8_t *)&msgID, 4);
{
std::unique_lock<std::mutex> l(m_TestsMutex);
m_Tests[msgID] = std::make_pair (*it1, *it2);
}
(*it1)->SendTunnelDataMsgTo ((*it2)->GetNextIdentHash (), (*it2)->GetNextTunnelID (),
CreateDeliveryStatusMsg (msgID));
++it1; ++it2;
}
}
uint8_t key[32], tag[32];
RAND_bytes (key, 32); RAND_bytes (tag, 32);
m_LocalDestination->SubmitSessionKey (key, tag);
i2p::garlic::ElGamalAESSession garlic (key, tag);
msg = garlic.WrapSingleMessage (msg);
}
outbound->SendTunnelDataMsgTo (it.second->GetNextIdentHash (), it.second->GetNextTunnelID (), msg);
}
}
void TunnelPool::ManageTunnels (uint64_t ts)
@ -398,7 +464,7 @@ namespace tunnel
{
CreateTunnels ();
TestTunnels ();
m_NextManageTime = ts + TUNNEL_POOL_MANAGE_INTERVAL + (rand () % TUNNEL_POOL_MANAGE_INTERVAL)/2;
m_NextManageTime = ts + TUNNEL_POOL_MANAGE_INTERVAL + (m_Rng () % TUNNEL_POOL_MANAGE_INTERVAL)/2;
}
}
@ -411,12 +477,25 @@ namespace tunnel
}
void TunnelPool::ProcessDeliveryStatus (std::shared_ptr<I2NPMessage> msg)
{
if (m_LocalDestination)
m_LocalDestination->ProcessDeliveryStatusMessage (msg);
else
LogPrint (eLogWarning, "Tunnels: Local destination doesn't exist, dropped");
}
void TunnelPool::ProcessTunnelTest (std::shared_ptr<I2NPMessage> msg)
{
const uint8_t * buf = msg->GetPayload ();
uint32_t msgID = bufbe32toh (buf);
buf += 4;
uint64_t timestamp = bufbe64toh (buf);
ProcessTunnelTest (msgID, timestamp);
}
bool TunnelPool::ProcessTunnelTest (uint32_t msgID, uint64_t timestamp)
{
decltype(m_Tests)::mapped_type test;
bool found = false;
{
@ -431,42 +510,37 @@ namespace tunnel
}
if (found)
{
uint64_t dlt = i2p::util::GetMillisecondsSinceEpoch () - timestamp;
LogPrint (eLogDebug, "Tunnels: Test of ", msgID, " successful. ", dlt, " milliseconds");
int dlt = (uint64_t)i2p::util::GetMonotonicMicroseconds () - (int64_t)timestamp;
LogPrint (eLogDebug, "Tunnels: Test of ", msgID, " successful. ", dlt, " microseconds");
if (dlt < 0) dlt = 0; // should not happen
int numHops = 0;
if (test.first) numHops += test.first->GetNumHops ();
if (test.second) numHops += test.second->GetNumHops ();
// restore from test failed state if any
if (test.first)
{
if (test.first->GetState () == eTunnelStateTestFailed)
if (test.first->GetState () != eTunnelStateExpiring)
test.first->SetState (eTunnelStateEstablished);
// update latency
uint64_t latency = 0;
int latency = 0;
if (numHops) latency = dlt*test.first->GetNumHops ()/numHops;
if (!latency) latency = dlt/2;
test.first->AddLatencySample(latency);
test.first->AddLatencySample (latency);
}
if (test.second)
{
if (test.second->GetState () == eTunnelStateTestFailed)
if (test.second->GetState () != eTunnelStateExpiring)
test.second->SetState (eTunnelStateEstablished);
// update latency
uint64_t latency = 0;
int latency = 0;
if (numHops) latency = dlt*test.second->GetNumHops ()/numHops;
if (!latency) latency = dlt/2;
test.second->AddLatencySample(latency);
test.second->AddLatencySample (latency);
}
}
else
{
if (m_LocalDestination)
m_LocalDestination->ProcessDeliveryStatusMessage (msg);
else
LogPrint (eLogWarning, "Tunnels: Local destination doesn't exist, dropped");
}
}
return found;
}
bool TunnelPool::IsExploratory () const
{
return i2p::tunnel::tunnels.GetExploratoryPool () == shared_from_this ();
@ -475,11 +549,23 @@ namespace tunnel
std::shared_ptr<const i2p::data::RouterInfo> TunnelPool::SelectNextHop (std::shared_ptr<const i2p::data::RouterInfo> prevHop,
bool reverse, bool endpoint) const
{
auto hop = IsExploratory () ? i2p::data::netdb.GetRandomRouter (prevHop, reverse, endpoint):
i2p::data::netdb.GetHighBandwidthRandomRouter (prevHop, reverse, endpoint);
if (!hop || hop->GetProfile ()->IsBad ())
hop = i2p::data::netdb.GetRandomRouter (prevHop, reverse, endpoint);
bool tryHighBandwidth = !IsExploratory ();
std::shared_ptr<const i2p::data::RouterInfo> hop;
for (int i = 0; i < TUNNEL_POOL_MAX_HOP_SELECTION_ATTEMPTS; i++)
{
hop = tryHighBandwidth ?
i2p::data::netdb.GetHighBandwidthRandomRouter (prevHop, reverse, endpoint) :
i2p::data::netdb.GetRandomRouter (prevHop, reverse, endpoint);
if (hop)
{
if (!hop->GetProfile ()->IsBad ())
break;
}
else if (tryHighBandwidth)
tryHighBandwidth = false;
else
return nullptr;
}
return hop;
}
@ -541,7 +627,7 @@ namespace tunnel
numHops = m_NumInboundHops;
if (m_InboundVariance)
{
int offset = rand () % (std::abs (m_InboundVariance) + 1);
int offset = m_Rng () % (std::abs (m_InboundVariance) + 1);
if (m_InboundVariance < 0) offset = -offset;
numHops += offset;
}
@ -551,7 +637,7 @@ namespace tunnel
numHops = m_NumOutboundHops;
if (m_OutboundVariance)
{
int offset = rand () % (std::abs (m_OutboundVariance) + 1);
int offset = m_Rng () % (std::abs (m_OutboundVariance) + 1);
if (m_OutboundVariance < 0) offset = -offset;
numHops += offset;
}
@ -771,7 +857,7 @@ namespace tunnel
{
std::shared_ptr<InboundTunnel> tun = nullptr;
std::unique_lock<std::mutex> lock(m_InboundTunnelsMutex);
uint64_t min = 1000000;
int min = 1000000;
for (const auto & itr : m_InboundTunnels) {
if(!itr->LatencyIsKnown()) continue;
auto l = itr->GetMeanLatency();
@ -787,7 +873,7 @@ namespace tunnel
{
std::shared_ptr<OutboundTunnel> tun = nullptr;
std::unique_lock<std::mutex> lock(m_OutboundTunnelsMutex);
uint64_t min = 1000000;
int min = 1000000;
for (const auto & itr : m_OutboundTunnels) {
if(!itr->LatencyIsKnown()) continue;
auto l = itr->GetMeanLatency();

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2023, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -15,6 +15,7 @@
#include <utility>
#include <mutex>
#include <memory>
#include <random>
#include "Identity.h"
#include "LeaseSet.h"
#include "RouterInfo.h"
@ -30,7 +31,8 @@ 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;
const int TUNNEL_POOL_MAX_NUM_BUILD_REQUESTS = 3;
const int TUNNEL_POOL_MAX_HOP_SELECTION_ATTEMPTS = 3;
class Tunnel;
class InboundTunnel;
@ -76,13 +78,15 @@ namespace tunnel
void RecreateOutboundTunnel (std::shared_ptr<OutboundTunnel> tunnel);
std::vector<std::shared_ptr<InboundTunnel> > GetInboundTunnels (int num) const;
std::shared_ptr<OutboundTunnel> GetNextOutboundTunnel (std::shared_ptr<OutboundTunnel> excluded = nullptr,
i2p::data::RouterInfo::CompatibleTransports compatible = i2p::data::RouterInfo::eAllTransports) const;
i2p::data::RouterInfo::CompatibleTransports compatible = i2p::data::RouterInfo::eAllTransports);
std::shared_ptr<InboundTunnel> GetNextInboundTunnel (std::shared_ptr<InboundTunnel> excluded = nullptr,
i2p::data::RouterInfo::CompatibleTransports compatible = i2p::data::RouterInfo::eAllTransports) const;
std::shared_ptr<OutboundTunnel> GetNewOutboundTunnel (std::shared_ptr<OutboundTunnel> old) const;
i2p::data::RouterInfo::CompatibleTransports compatible = i2p::data::RouterInfo::eAllTransports);
std::pair<std::shared_ptr<OutboundTunnel>, bool> GetNewOutboundTunnel (std::shared_ptr<OutboundTunnel> old);
void ManageTunnels (uint64_t ts);
void ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg);
void ProcessDeliveryStatus (std::shared_ptr<I2NPMessage> msg);
void ProcessTunnelTest (std::shared_ptr<I2NPMessage> msg);
bool ProcessTunnelTest (uint32_t msgID, uint64_t timestamp);
bool IsExploratory () const;
bool IsActive () const { return m_IsActive; };
@ -102,7 +106,7 @@ namespace tunnel
bool HasCustomPeerSelector();
/** @brief make this tunnel pool yield tunnels that fit latency range [min, max] */
void RequireLatency(uint64_t min, uint64_t max) { m_MinLatency = min; m_MaxLatency = max; }
void RequireLatency(int min, int max) { m_MinLatency = min; m_MaxLatency = max; }
/** @brief return true if this tunnel pool has a latency requirement */
bool HasLatencyRequirement() const { return m_MinLatency > 0 && m_MaxLatency > 0; }
@ -115,6 +119,8 @@ namespace tunnel
std::shared_ptr<const i2p::data::RouterInfo> SelectNextHop (std::shared_ptr<const i2p::data::RouterInfo> prevHop, bool reverse, bool endpoint) const;
bool StandardSelectPeers(Path & path, int numHops, bool inbound, SelectHopFunc nextHop);
std::mt19937& GetRng () { return m_Rng; }
private:
void TestTunnels ();
@ -123,7 +129,7 @@ namespace tunnel
void CreatePairedInboundTunnel (std::shared_ptr<OutboundTunnel> outboundTunnel);
template<class TTunnels>
typename TTunnels::value_type GetNextTunnel (TTunnels& tunnels,
typename TTunnels::value_type excluded, i2p::data::RouterInfo::CompatibleTransports compatible) const;
typename TTunnels::value_type excluded, i2p::data::RouterInfo::CompatibleTransports compatible);
bool SelectPeers (Path& path, bool isInbound);
bool SelectExplicitPeers (Path& path, bool isInbound);
bool ValidatePeers (std::vector<std::shared_ptr<const i2p::data::IdentityEx> >& peers) const;
@ -145,9 +151,11 @@ namespace tunnel
std::mutex m_CustomPeerSelectorMutex;
ITunnelPeerSelector * m_CustomPeerSelector;
uint64_t m_MinLatency = 0; // if > 0 this tunnel pool will try building tunnels with minimum latency by ms
uint64_t m_MaxLatency = 0; // if > 0 this tunnel pool will try building tunnels with maximum latency by ms
int m_MinLatency = 0; // if > 0 this tunnel pool will try building tunnels with minimum latency by ms
int m_MaxLatency = 0; // if > 0 this tunnel pool will try building tunnels with maximum latency by ms
std::mt19937 m_Rng;
public:
// for HTTP only

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2023, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -44,6 +44,9 @@ namespace api
int netID; i2p::config::GetOption("netid", netID);
i2p::context.SetNetID (netID);
bool checkReserved; i2p::config::GetOption("reservedrange", checkReserved);
i2p::transport::transports.SetCheckReserved(checkReserved);
i2p::context.Init ();
}

@ -18,8 +18,8 @@
#define MAKE_VERSION_NUMBER(a,b,c) ((a*100+b)*100+c)
#define I2PD_VERSION_MAJOR 2
#define I2PD_VERSION_MINOR 50
#define I2PD_VERSION_MICRO 2
#define I2PD_VERSION_MINOR 52
#define I2PD_VERSION_MICRO 0
#define I2PD_VERSION_PATCH 0
#ifdef GITVER
#define I2PD_VERSION XSTRINGIZE(GITVER)
@ -33,7 +33,7 @@
#define I2P_VERSION_MAJOR 0
#define I2P_VERSION_MINOR 9
#define I2P_VERSION_MICRO 61
#define I2P_VERSION_MICRO 62
#define I2P_VERSION_PATCH 0
#define I2P_VERSION MAKE_VERSION(I2P_VERSION_MAJOR, I2P_VERSION_MINOR, I2P_VERSION_MICRO)
#define I2P_VERSION_NUMBER MAKE_VERSION_NUMBER(I2P_VERSION_MAJOR, I2P_VERSION_MINOR, I2P_VERSION_MICRO)

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2023, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -16,6 +16,7 @@
#include "Identity.h"
#include "util.h"
#include "ClientContext.h"
#include "HTTPProxy.h"
#include "SOCKS.h"
#include "MatchedDestination.h"
@ -775,7 +776,7 @@ namespace client
address = "127.0.0.1";
}
auto localAddress = boost::asio::ip::address::from_string(address);
auto serverTunnel = std::make_shared<I2PUDPServerTunnel>(name, localDestination, localAddress, endpoint, port, gzip);
auto serverTunnel = std::make_shared<I2PUDPServerTunnel>(name, localDestination, localAddress, endpoint, inPort, gzip);
if(!isUniqueLocal)
{
LogPrint(eLogInfo, "Clients: Disabling loopback address mapping");

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2022, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -15,8 +15,6 @@
#include <boost/asio.hpp>
#include "Destination.h"
#include "I2PService.h"
#include "HTTPProxy.h"
#include "SOCKS.h"
#include "I2PTunnel.h"
#include "UDPTunnel.h"
#include "SAM.h"
@ -141,8 +139,7 @@ namespace client
AddressBook m_AddressBook;
i2p::proxy::HTTPProxy * m_HttpProxy;
i2p::proxy::SOCKSProxy * m_SocksProxy;
I2PService * m_HttpProxy, * m_SocksProxy;
std::map<boost::asio::ip::tcp::endpoint, std::shared_ptr<I2PService> > m_ClientTunnels; // local endpoint -> tunnel
std::map<std::pair<i2p::data::IdentHash, int>, std::shared_ptr<I2PServerTunnel> > m_ServerTunnels; // <destination,port> -> tunnel
@ -167,8 +164,8 @@ namespace client
const decltype(m_ServerTunnels)& GetServerTunnels () const { return m_ServerTunnels; };
const decltype(m_ClientForwards)& GetClientForwards () const { return m_ClientForwards; }
const decltype(m_ServerForwards)& GetServerForwards () const { return m_ServerForwards; }
const i2p::proxy::HTTPProxy * GetHttpProxy () const { return m_HttpProxy; }
const i2p::proxy::SOCKSProxy * GetSocksProxy () const { return m_SocksProxy; }
const I2PService * GetHttpProxy () const { return m_HttpProxy; }
const I2PService * GetSocksProxy () const { return m_SocksProxy; }
};
extern ClientContext context;

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2023, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -29,6 +29,7 @@
#include "Config.h"
#include "HTTP.h"
#include "I18N.h"
#include "Socks5.h"
namespace i2p {
namespace proxy {
@ -93,9 +94,6 @@ namespace proxy {
void HTTPConnect(const std::string & host, uint16_t port);
void HandleHTTPConnectStreamRequestComplete(std::shared_ptr<i2p::stream::Stream> stream);
void HandleSocksProxySendHandshake(const boost::system::error_code & ec, std::size_t bytes_transfered);
void HandleSocksProxyReply(const boost::system::error_code & ec, std::size_t bytes_transfered);
typedef std::function<void(boost::asio::ip::tcp::endpoint)> ProxyResolvedHandler;
void HandleUpstreamProxyResolved(const boost::system::error_code & ecode, boost::asio::ip::tcp::resolver::iterator itr, ProxyResolvedHandler handler);
@ -113,7 +111,6 @@ namespace proxy {
bool m_Addresshelper;
i2p::http::URL m_ProxyURL;
i2p::http::URL m_RequestURL;
uint8_t m_socks_buf[255+8]; // for socks request/response
int m_req_len;
i2p::http::URL m_ClientRequestURL;
i2p::http::HTTPReq m_ClientRequest;
@ -612,49 +609,36 @@ namespace proxy {
void HTTPReqHandler::HandleUpstreamSocksProxyConnect(const boost::system::error_code & ec)
{
if(!ec) {
if(m_RequestURL.host.size() > 255) {
if(!ec)
{
if(m_RequestURL.host.size() > 255)
{
GenericProxyError(tr("Hostname is too long"), m_RequestURL.host);
return;
}
uint16_t port = m_RequestURL.port;
if(!port) port = 80;
LogPrint(eLogDebug, "HTTPProxy: Connected to SOCKS upstream");
std::string host = m_RequestURL.host;
std::size_t reqsize = 0;
m_socks_buf[0] = '\x04';
m_socks_buf[1] = 1;
htobe16buf(m_socks_buf+2, port);
m_socks_buf[4] = 0;
m_socks_buf[5] = 0;
m_socks_buf[6] = 0;
m_socks_buf[7] = 1;
// user id
m_socks_buf[8] = 'i';
m_socks_buf[9] = '2';
m_socks_buf[10] = 'p';
m_socks_buf[11] = 'd';
m_socks_buf[12] = 0;
reqsize += 13;
memcpy(m_socks_buf+ reqsize, host.c_str(), host.size());
reqsize += host.size();
m_socks_buf[++reqsize] = 0;
boost::asio::async_write(*m_proxysock, boost::asio::buffer(m_socks_buf, reqsize), boost::asio::transfer_all(), std::bind(&HTTPReqHandler::HandleSocksProxySendHandshake, this, std::placeholders::_1, std::placeholders::_2));
} else GenericProxyError(tr("Cannot connect to upstream SOCKS proxy"), ec.message());
}
void HTTPReqHandler::HandleSocksProxySendHandshake(const boost::system::error_code & ec, std::size_t bytes_transferred)
{
LogPrint(eLogDebug, "HTTPProxy: Upstream SOCKS handshake sent");
if(ec) GenericProxyError(tr("Cannot negotiate with SOCKS proxy"), ec.message());
else m_proxysock->async_read_some(boost::asio::buffer(m_socks_buf, 8), std::bind(&HTTPReqHandler::HandleSocksProxyReply, this, std::placeholders::_1, std::placeholders::_2));
auto s = shared_from_this ();
i2p::transport::Socks5Handshake (*m_proxysock, std::make_pair(host, port),
[s](const boost::system::error_code& ec)
{
if (!ec)
s->SocksProxySuccess();
else
s->GenericProxyError(tr("SOCKS proxy error"), ec.message ());
});
}
else
GenericProxyError(tr("Cannot connect to upstream SOCKS proxy"), ec.message());
}
void HTTPReqHandler::HandoverToUpstreamProxy()
{
LogPrint(eLogDebug, "HTTPProxy: Handover to SOCKS proxy");
auto connection = std::make_shared<i2p::client::TCPIPPipe>(GetOwner(), m_proxysock, m_sock);
auto connection = CreateSocketsPipe (GetOwner(), m_proxysock, m_sock);
m_sock = nullptr;
m_proxysock = nullptr;
GetOwner()->AddHandler(connection);
@ -714,24 +698,6 @@ namespace proxy {
}
}
void HTTPReqHandler::HandleSocksProxyReply(const boost::system::error_code & ec, std::size_t bytes_transferred)
{
if(!ec)
{
if(m_socks_buf[1] == 90) {
// success
SocksProxySuccess();
} else {
std::stringstream ss;
ss << "error code: ";
ss << (int) m_socks_buf[1];
std::string msg = ss.str();
GenericProxyError(tr("SOCKS proxy error"), msg);
}
}
else GenericProxyError(tr("No reply from SOCKS proxy"), ec.message());
}
void HTTPReqHandler::HandleUpstreamHTTPProxyConnect(const boost::system::error_code & ec)
{
if(!ec) {

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2023, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -147,196 +147,5 @@ namespace client
m_LocalDestination->CreateStream (streamRequestComplete, address->blindedPublicKey, port);
}
}
TCPIPPipe::TCPIPPipe(I2PService * owner, std::shared_ptr<boost::asio::ip::tcp::socket> upstream, std::shared_ptr<boost::asio::ip::tcp::socket> downstream) : I2PServiceHandler(owner), m_up(upstream), m_down(downstream)
{
boost::asio::socket_base::receive_buffer_size option(TCP_IP_PIPE_BUFFER_SIZE);
upstream->set_option(option);
downstream->set_option(option);
}
TCPIPPipe::~TCPIPPipe()
{
Terminate();
}
void TCPIPPipe::Start()
{
AsyncReceiveUpstream();
AsyncReceiveDownstream();
}
void TCPIPPipe::Terminate()
{
if(Kill()) return;
if (m_up)
{
if (m_up->is_open())
m_up->close();
m_up = nullptr;
}
if (m_down)
{
if (m_down->is_open())
m_down->close();
m_down = nullptr;
}
Done(shared_from_this());
}
void TCPIPPipe::AsyncReceiveUpstream()
{
if (m_up)
{
m_up->async_read_some(boost::asio::buffer(m_upstream_to_down_buf, TCP_IP_PIPE_BUFFER_SIZE),
std::bind(&TCPIPPipe::HandleUpstreamReceived, shared_from_this(),
std::placeholders::_1, std::placeholders::_2));
}
else
LogPrint(eLogError, "TCPIPPipe: Upstream receive: No socket");
}
void TCPIPPipe::AsyncReceiveDownstream()
{
if (m_down) {
m_down->async_read_some(boost::asio::buffer(m_downstream_to_up_buf, TCP_IP_PIPE_BUFFER_SIZE),
std::bind(&TCPIPPipe::HandleDownstreamReceived, shared_from_this(),
std::placeholders::_1, std::placeholders::_2));
}
else
LogPrint(eLogError, "TCPIPPipe: Downstream receive: No socket");
}
void TCPIPPipe::UpstreamWrite(size_t len)
{
if (m_up)
{
LogPrint(eLogDebug, "TCPIPPipe: Upstream: ", (int) len, " bytes written");
boost::asio::async_write(*m_up, boost::asio::buffer(m_upstream_buf, len),
boost::asio::transfer_all(),
std::bind(&TCPIPPipe::HandleUpstreamWrite,
shared_from_this(),
std::placeholders::_1));
}
else
LogPrint(eLogError, "TCPIPPipe: Upstream write: no socket");
}
void TCPIPPipe::DownstreamWrite(size_t len)
{
if (m_down)
{
LogPrint(eLogDebug, "TCPIPPipe: Downstream: ", (int) len, " bytes written");
boost::asio::async_write(*m_down, boost::asio::buffer(m_downstream_buf, len),
boost::asio::transfer_all(),
std::bind(&TCPIPPipe::HandleDownstreamWrite,
shared_from_this(),
std::placeholders::_1));
}
else
LogPrint(eLogError, "TCPIPPipe: Downstream write: No socket");
}
void TCPIPPipe::HandleDownstreamReceived(const boost::system::error_code & ecode, std::size_t bytes_transfered)
{
LogPrint(eLogDebug, "TCPIPPipe: Downstream: ", (int) bytes_transfered, " bytes received");
if (ecode)
{
LogPrint(eLogError, "TCPIPPipe: Downstream read error:" , ecode.message());
if (ecode != boost::asio::error::operation_aborted)
Terminate();
} else {
if (bytes_transfered > 0 )
memcpy(m_upstream_buf, m_downstream_to_up_buf, bytes_transfered);
UpstreamWrite(bytes_transfered);
}
}
void TCPIPPipe::HandleDownstreamWrite(const boost::system::error_code & ecode) {
if (ecode)
{
LogPrint(eLogError, "TCPIPPipe: Downstream write error:" , ecode.message());
if (ecode != boost::asio::error::operation_aborted)
Terminate();
}
else
AsyncReceiveUpstream();
}
void TCPIPPipe::HandleUpstreamWrite(const boost::system::error_code & ecode) {
if (ecode)
{
LogPrint(eLogError, "TCPIPPipe: Upstream write error:" , ecode.message());
if (ecode != boost::asio::error::operation_aborted)
Terminate();
}
else
AsyncReceiveDownstream();
}
void TCPIPPipe::HandleUpstreamReceived(const boost::system::error_code & ecode, std::size_t bytes_transfered)
{
LogPrint(eLogDebug, "TCPIPPipe: Upstream ", (int)bytes_transfered, " bytes received");
if (ecode)
{
LogPrint(eLogError, "TCPIPPipe: Upstream read error:" , ecode.message());
if (ecode != boost::asio::error::operation_aborted)
Terminate();
} else {
if (bytes_transfered > 0 )
memcpy(m_downstream_buf, m_upstream_to_down_buf, bytes_transfered);
DownstreamWrite(bytes_transfered);
}
}
void TCPIPAcceptor::Start ()
{
m_Acceptor.reset (new boost::asio::ip::tcp::acceptor (GetService (), m_LocalEndpoint));
// update the local end point in case port has been set zero and got updated now
m_LocalEndpoint = m_Acceptor->local_endpoint();
m_Acceptor->listen ();
Accept ();
}
void TCPIPAcceptor::Stop ()
{
if (m_Acceptor)
{
m_Acceptor->close();
m_Acceptor.reset (nullptr);
}
m_Timer.cancel ();
ClearHandlers();
}
void TCPIPAcceptor::Accept ()
{
auto newSocket = std::make_shared<boost::asio::ip::tcp::socket> (GetService ());
m_Acceptor->async_accept (*newSocket, std::bind (&TCPIPAcceptor::HandleAccept, this,
std::placeholders::_1, newSocket));
}
void TCPIPAcceptor::HandleAccept (const boost::system::error_code& ecode, std::shared_ptr<boost::asio::ip::tcp::socket> socket)
{
if (!ecode)
{
LogPrint(eLogDebug, "I2PService: ", GetName(), " accepted");
auto handler = CreateHandler(socket);
if (handler)
{
AddHandler(handler);
handler->Handle();
}
else
socket->close();
Accept();
}
else
{
if (ecode != boost::asio::error::operation_aborted)
LogPrint (eLogError, "I2PService: ", GetName(), " closing socket on accept because: ", ecode.message ());
}
}
}
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2023, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -99,6 +99,7 @@ namespace client
virtual ~I2PServiceHandler() { }
//If you override this make sure you call it from the children
virtual void Handle() {}; //Start handling the socket
virtual void Start () {};
void Terminate () { Kill (); };
@ -119,72 +120,171 @@ namespace client
std::atomic<bool> m_Dead; //To avoid cleaning up multiple times
};
const size_t TCP_IP_PIPE_BUFFER_SIZE = 8192 * 8;
const size_t SOCKETS_PIPE_BUFFER_SIZE = 8192 * 8;
// bidirectional pipe for 2 tcp/ip sockets
class TCPIPPipe: public I2PServiceHandler, public std::enable_shared_from_this<TCPIPPipe>
// bidirectional pipe for 2 stream sockets
template<typename SocketUpstream, typename SocketDownstream>
class SocketsPipe: public I2PServiceHandler,
public std::enable_shared_from_this<SocketsPipe<SocketUpstream, SocketDownstream> >
{
public:
TCPIPPipe(I2PService * owner, std::shared_ptr<boost::asio::ip::tcp::socket> upstream, std::shared_ptr<boost::asio::ip::tcp::socket> downstream);
~TCPIPPipe();
void Start();
protected:
SocketsPipe(I2PService * owner, std::shared_ptr<SocketUpstream> upstream, std::shared_ptr<SocketDownstream> downstream):
I2PServiceHandler(owner), m_up(upstream), m_down(downstream)
{
boost::asio::socket_base::receive_buffer_size option(SOCKETS_PIPE_BUFFER_SIZE);
upstream->set_option(option);
downstream->set_option(option);
}
~SocketsPipe() { Terminate(); }
void Start() override
{
Transfer (m_up, m_down, m_upstream_to_down_buf, SOCKETS_PIPE_BUFFER_SIZE); // receive from upstream
Transfer (m_down, m_up, m_downstream_to_up_buf, SOCKETS_PIPE_BUFFER_SIZE); // receive from upstream
}
void Terminate();
void AsyncReceiveUpstream();
void AsyncReceiveDownstream();
void HandleUpstreamReceived(const boost::system::error_code & ecode, std::size_t bytes_transferred);
void HandleDownstreamReceived(const boost::system::error_code & ecode, std::size_t bytes_transferred);
void HandleUpstreamWrite(const boost::system::error_code & ecode);
void HandleDownstreamWrite(const boost::system::error_code & ecode);
void UpstreamWrite(size_t len);
void DownstreamWrite(size_t len);
private:
void Terminate()
{
if(Kill()) return;
if (m_up)
{
if (m_up->is_open())
m_up->close();
m_up = nullptr;
}
if (m_down)
{
if (m_down->is_open())
m_down->close();
m_down = nullptr;
}
Done(SocketsPipe<SocketUpstream, SocketDownstream>::shared_from_this());
}
template<typename From, typename To>
void Transfer (std::shared_ptr<From> from, std::shared_ptr<To> to, uint8_t * buf, size_t len)
{
if (!from || !to || !buf) return;
auto s = SocketsPipe<SocketUpstream, SocketDownstream>::shared_from_this ();
from->async_read_some(boost::asio::buffer(buf, len),
[from, to, s, buf, len](const boost::system::error_code& ecode, std::size_t transferred)
{
if (ecode == boost::asio::error::operation_aborted) return;
if (!ecode)
{
boost::asio::async_write(*to, boost::asio::buffer(buf, transferred), boost::asio::transfer_all(),
[from, to, s, buf, len](const boost::system::error_code& ecode, std::size_t transferred)
{
(void) transferred;
if (ecode == boost::asio::error::operation_aborted) return;
if (!ecode)
s->Transfer (from, to, buf, len);
else
{
LogPrint(eLogWarning, "SocketsPipe: Write error:" , ecode.message());
s->Terminate();
}
});
}
else
{
LogPrint(eLogWarning, "SocketsPipe: Read error:" , ecode.message());
s->Terminate();
}
});
}
private:
uint8_t m_upstream_to_down_buf[TCP_IP_PIPE_BUFFER_SIZE], m_downstream_to_up_buf[TCP_IP_PIPE_BUFFER_SIZE];
uint8_t m_upstream_buf[TCP_IP_PIPE_BUFFER_SIZE], m_downstream_buf[TCP_IP_PIPE_BUFFER_SIZE];
std::shared_ptr<boost::asio::ip::tcp::socket> m_up, m_down;
uint8_t m_upstream_to_down_buf[SOCKETS_PIPE_BUFFER_SIZE], m_downstream_to_up_buf[SOCKETS_PIPE_BUFFER_SIZE];
std::shared_ptr<SocketUpstream> m_up;
std::shared_ptr<SocketDownstream> m_down;
};
/* TODO: support IPv6 too */
//This is a service that listens for connections on the IP network and interacts with I2P
class TCPIPAcceptor: public I2PService
template<typename SocketUpstream, typename SocketDownstream>
std::shared_ptr<I2PServiceHandler> CreateSocketsPipe (I2PService * owner, std::shared_ptr<SocketUpstream> upstream, std::shared_ptr<SocketDownstream> downstream)
{
return std::make_shared<SocketsPipe<SocketUpstream, SocketDownstream> >(owner, upstream, downstream);
}
//This is a service that listens for connections on the IP network or local socket and interacts with I2P
template<typename Protocol>
class ServiceAcceptor: public I2PService
{
public:
TCPIPAcceptor (const std::string& address, uint16_t port, std::shared_ptr<ClientDestination> localDestination = nullptr) :
I2PService(localDestination),
m_LocalEndpoint (boost::asio::ip::address::from_string(address), port),
m_Timer (GetService ()) {}
TCPIPAcceptor (const std::string& address, uint16_t port, i2p::data::SigningKeyType kt) :
I2PService(kt),
m_LocalEndpoint (boost::asio::ip::address::from_string(address), port),
m_Timer (GetService ()) {}
virtual ~TCPIPAcceptor () { TCPIPAcceptor::Stop(); }
//If you override this make sure you call it from the children
void Start ();
//If you override this make sure you call it from the children
void Stop ();
const boost::asio::ip::tcp::endpoint& GetLocalEndpoint () const { return m_LocalEndpoint; };
ServiceAcceptor (const typename Protocol::endpoint& localEndpoint, std::shared_ptr<ClientDestination> localDestination = nullptr) :
I2PService(localDestination), m_LocalEndpoint (localEndpoint) {}
virtual ~ServiceAcceptor () { Stop(); }
void Start () override
{
m_Acceptor.reset (new typename Protocol::acceptor (GetService (), m_LocalEndpoint));
// update the local end point in case port has been set zero and got updated now
m_LocalEndpoint = m_Acceptor->local_endpoint();
m_Acceptor->listen ();
Accept ();
}
void Stop () override
{
if (m_Acceptor)
{
m_Acceptor->close();
m_Acceptor.reset (nullptr);
}
ClearHandlers();
}
const typename Protocol::endpoint& GetLocalEndpoint () const { return m_LocalEndpoint; };
virtual const char* GetName() { return "Generic TCP/IP accepting daemon"; }
const char* GetName() override { return "Generic TCP/IP accepting daemon"; }
protected:
virtual std::shared_ptr<I2PServiceHandler> CreateHandler(std::shared_ptr<boost::asio::ip::tcp::socket> socket) = 0;
virtual std::shared_ptr<I2PServiceHandler> CreateHandler(std::shared_ptr<typename Protocol::socket> socket) = 0;
private:
void Accept();
void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr<boost::asio::ip::tcp::socket> socket);
boost::asio::ip::tcp::endpoint m_LocalEndpoint;
std::unique_ptr<boost::asio::ip::tcp::acceptor> m_Acceptor;
boost::asio::deadline_timer m_Timer;
void Accept()
{
auto newSocket = std::make_shared<typename Protocol::socket> (GetService ());
m_Acceptor->async_accept (*newSocket,
[newSocket, this](const boost::system::error_code& ecode)
{
if (ecode == boost::asio::error::operation_aborted) return;
if (!ecode)
{
LogPrint(eLogDebug, "ServiceAcceptor: ", GetName(), " accepted");
auto handler = CreateHandler(newSocket);
if (handler)
{
AddHandler(handler);
handler->Handle();
}
else
newSocket->close();
Accept();
}
else
LogPrint (eLogError, "ServiceAcceptor: ", GetName(), " closing socket on accept because: ", ecode.message ());
});
}
private:
typename Protocol::endpoint m_LocalEndpoint;
std::unique_ptr<typename Protocol::acceptor> m_Acceptor;
};
class TCPIPAcceptor: public ServiceAcceptor<boost::asio::ip::tcp>
{
public:
TCPIPAcceptor (const std::string& address, uint16_t port, std::shared_ptr<ClientDestination> localDestination = nullptr) :
ServiceAcceptor (boost::asio::ip::tcp::endpoint (boost::asio::ip::address::from_string(address), port), localDestination) {}
};
}
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2023, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -415,12 +415,17 @@ namespace client
{
session->UDPEndpoint = forward;
auto dest = session->GetLocalDestination ()->CreateDatagramDestination ();
auto port = std::stoi(params[SAM_PARAM_PORT]);
if (type == eSAMSessionTypeDatagram)
dest->SetReceiver (std::bind (&SAMSocket::HandleI2PDatagramReceive, shared_from_this (),
std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5));
std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5),
port
);
else // raw
dest->SetRawReceiver (std::bind (&SAMSocket::HandleI2PRawDatagramReceive, shared_from_this (),
std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4));
std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4),
port
);
}
if (session->GetLocalDestination ()->IsReady ())
@ -523,15 +528,20 @@ namespace client
{
if (addr->IsIdentHash ())
{
auto leaseSet = session->GetLocalDestination ()->FindLeaseSet(addr->identHash);
if (leaseSet)
Connect(leaseSet, session);
else
if (session->GetLocalDestination ()->GetIdentHash () != addr->identHash)
{
session->GetLocalDestination ()->RequestDestination(addr->identHash,
std::bind(&SAMSocket::HandleConnectLeaseSetRequestComplete,
shared_from_this(), std::placeholders::_1));
auto leaseSet = session->GetLocalDestination ()->FindLeaseSet(addr->identHash);
if (leaseSet)
Connect(leaseSet, session);
else
{
session->GetLocalDestination ()->RequestDestination(addr->identHash,
std::bind(&SAMSocket::HandleConnectLeaseSetRequestComplete,
shared_from_this(), std::placeholders::_1));
}
}
else
SendStreamCantReachPeer ("Can't connect to myself");
}
else // B33
session->GetLocalDestination ()->RequestDestinationWithEncryptedLeaseSet (addr->blindedPublicKey,
@ -550,17 +560,22 @@ namespace client
if (!session) session = m_Owner.FindSession(m_ID);
if (session)
{
m_SocketType = eSAMSocketTypeStream;
m_Stream = session->GetLocalDestination ()->CreateStream (remote);
if (m_Stream)
if (session->GetLocalDestination ()->SupportsEncryptionType (remote->GetEncryptionType ()))
{
m_Stream->Send ((uint8_t *)m_Buffer, m_BufferOffset); // connect and send
m_BufferOffset = 0;
I2PReceive ();
SendMessageReply (SAM_STREAM_STATUS_OK, strlen(SAM_STREAM_STATUS_OK), false);
m_SocketType = eSAMSocketTypeStream;
m_Stream = session->GetLocalDestination ()->CreateStream (remote);
if (m_Stream)
{
m_Stream->Send ((uint8_t *)m_Buffer, m_BufferOffset); // connect and send
m_BufferOffset = 0;
I2PReceive ();
SendMessageReply (SAM_STREAM_STATUS_OK, strlen(SAM_STREAM_STATUS_OK), false);
}
else
SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, strlen(SAM_STREAM_STATUS_INVALID_ID), true);
}
else
SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, strlen(SAM_STREAM_STATUS_INVALID_ID), true);
SendStreamCantReachPeer ("Incompatible crypto");
}
else
SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, strlen(SAM_STREAM_STATUS_INVALID_ID), true);
@ -573,7 +588,7 @@ namespace client
else
{
LogPrint (eLogError, "SAM: Destination to connect not found");
SendMessageReply (SAM_STREAM_STATUS_CANT_REACH_PEER, strlen(SAM_STREAM_STATUS_CANT_REACH_PEER), true);
SendStreamCantReachPeer ("LeaseSet not found");
}
}
@ -602,27 +617,27 @@ namespace client
session->GetLocalDestination ()->AcceptOnce (std::bind (&SAMSocket::HandleI2PAccept, shared_from_this (), std::placeholders::_1));
}
else
{
{
auto ts = i2p::util::GetSecondsSinceEpoch ();
while (!session->acceptQueue.empty () && session->acceptQueue.front ().second + SAM_SESSION_MAX_ACCEPT_INTERVAL > ts)
{
{
auto socket = session->acceptQueue.front ().first;
session->acceptQueue.pop_front ();
if (socket)
m_Owner.GetService ().post (std::bind(&SAMSocket::TerminateClose, socket));
}
}
if (session->acceptQueue.size () < SAM_SESSION_MAX_ACCEPT_QUEUE_SIZE)
{
// already accepting, queue up
SendMessageReply (SAM_STREAM_STATUS_OK, strlen(SAM_STREAM_STATUS_OK), false);
session->acceptQueue.push_back (std::make_pair(shared_from_this(), ts));
}
else
{
}
else
{
LogPrint (eLogInfo, "SAM: Session ", m_ID, " accept queue is full ", session->acceptQueue.size ());
SendStreamI2PError ("Already accepting");
}
}
}
}
}
else
SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, strlen(SAM_STREAM_STATUS_INVALID_ID), true);
@ -857,28 +872,33 @@ namespace client
SendSessionI2PError ("Wrong session type");
}
void SAMSocket::SendSessionI2PError(const std::string & msg)
void SAMSocket::SendReplyWithMessage (const char * reply, const std::string & msg)
{
LogPrint (eLogError, "SAM: Session I2P error: ", msg);
#ifdef _MSC_VER
size_t len = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_SESSION_STATUS_I2P_ERROR, msg.c_str());
size_t len = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, reply, msg.c_str());
#else
size_t len = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_SESSION_STATUS_I2P_ERROR, msg.c_str());
size_t len = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, reply, msg.c_str());
#endif
SendMessageReply (m_Buffer, len, true);
}
void SAMSocket::SendSessionI2PError(const std::string & msg)
{
LogPrint (eLogError, "SAM: Session I2P error: ", msg);
SendReplyWithMessage (SAM_SESSION_STATUS_I2P_ERROR, msg);
}
void SAMSocket::SendStreamI2PError(const std::string & msg)
{
LogPrint (eLogError, "SAM: Stream I2P error: ", msg);
#ifdef _MSC_VER
size_t len = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_STREAM_STATUS_I2P_ERROR, msg.c_str());
#else
size_t len = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_STREAM_STATUS_I2P_ERROR, msg.c_str());
#endif
SendMessageReply (m_Buffer, len, true);
SendReplyWithMessage (SAM_STREAM_STATUS_I2P_ERROR, msg);
}
void SAMSocket::SendStreamCantReachPeer(const std::string & msg)
{
SendReplyWithMessage (SAM_STREAM_STATUS_CANT_REACH_PEER, msg);
}
void SAMSocket::HandleNamingLookupLeaseSetRequestComplete (std::shared_ptr<i2p::data::LeaseSet> leaseSet, std::string name)
{
if (leaseSet)
@ -1078,14 +1098,14 @@ namespace client
// pending acceptors
auto ts = i2p::util::GetSecondsSinceEpoch ();
while (!session->acceptQueue.empty () && session->acceptQueue.front ().second + SAM_SESSION_MAX_ACCEPT_INTERVAL > ts)
{
{
auto socket = session->acceptQueue.front ().first;
session->acceptQueue.pop_front ();
if (socket)
m_Owner.GetService ().post (std::bind(&SAMSocket::TerminateClose, socket));
}
}
if (!session->acceptQueue.empty ())
{
{
auto socket = session->acceptQueue.front ().first;
session->acceptQueue.pop_front ();
if (socket && socket->GetSocketType () == eSAMSocketTypeAcceptor)
@ -1093,7 +1113,7 @@ namespace client
socket->m_IsAccepting = true;
session->GetLocalDestination ()->AcceptOnce (std::bind (&SAMSocket::HandleI2PAccept, socket, std::placeholders::_1));
}
}
}
}
if (!m_IsSilent)
{

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2023, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -51,7 +51,7 @@ namespace client
const char SAM_STREAM_STATUS_OK[] = "STREAM STATUS RESULT=OK\n";
const char SAM_STREAM_STATUS_INVALID_ID[] = "STREAM STATUS RESULT=INVALID_ID\n";
const char SAM_STREAM_STATUS_INVALID_KEY[] = "STREAM STATUS RESULT=INVALID_KEY\n";
const char SAM_STREAM_STATUS_CANT_REACH_PEER[] = "STREAM STATUS RESULT=CANT_REACH_PEER\n";
const char SAM_STREAM_STATUS_CANT_REACH_PEER[] = "STREAM STATUS RESULT=CANT_REACH_PEER MESSAGE=\"%s\"\n";
const char SAM_STREAM_STATUS_I2P_ERROR[] = "STREAM STATUS RESULT=I2P_ERROR MESSAGE=\"%s\"\n";
const char SAM_STREAM_ACCEPT[] = "STREAM ACCEPT";
const char SAM_STREAM_FORWARD[] = "STREAM FORWARD";
@ -144,8 +144,10 @@ namespace client
void ProcessNamingLookup (char * buf, size_t len);
void ProcessSessionAdd (char * buf, size_t len);
void ProcessSessionRemove (char * buf, size_t len);
void SendReplyWithMessage (const char * reply, const std::string & msg);
void SendSessionI2PError(const std::string & msg);
void SendStreamI2PError(const std::string & msg);
void SendStreamCantReachPeer(const std::string & msg);
size_t ProcessDatagramSend (char * buf, size_t len, const char * data); // from SAM 1.0
void ExtractParams (char * buf, std::map<std::string, std::string>& params);

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2023, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -19,6 +19,7 @@
#include "I2PTunnel.h"
#include "I2PService.h"
#include "util.h"
#include "Socks5.h"
namespace i2p
{
@ -27,10 +28,6 @@ namespace proxy
static const size_t socks_buffer_size = 8192;
static const size_t max_socks_hostname_size = 255; // Limit for socks5 and bad idea to traverse
//static const size_t SOCKS_FORWARDER_BUFFER_SIZE = 8192;
static const size_t SOCKS_UPSTREAM_SOCKS4A_REPLY_SIZE = 8;
struct SOCKSDnsAddress
{
uint8_t size;
@ -132,7 +129,6 @@ namespace proxy
boost::asio::const_buffers_1 GenerateSOCKS5SelectAuth(authMethods method);
boost::asio::const_buffers_1 GenerateSOCKS4Response(errTypes error, uint32_t ip, uint16_t port);
boost::asio::const_buffers_1 GenerateSOCKS5Response(errTypes error, addrTypes type, const address &addr, uint16_t port);
boost::asio::const_buffers_1 GenerateUpstreamRequest();
bool Socks5ChooseAuth();
void Socks5UserPasswdResponse ();
void SocksRequestFailed(errTypes error);
@ -143,12 +139,11 @@ namespace proxy
void HandleStreamRequestComplete (std::shared_ptr<i2p::stream::Stream> stream);
void ForwardSOCKS();
void SocksUpstreamSuccess();
template<typename Socket>
void SocksUpstreamSuccess(std::shared_ptr<Socket>& upstreamSock);
void AsyncUpstreamSockRead();
void SendUpstreamRequest();
void HandleUpstreamData(uint8_t * buff, std::size_t len);
void HandleUpstreamSockSend(const boost::system::error_code & ecode, std::size_t bytes_transfered);
void HandleUpstreamSockRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered);
template<typename Socket>
void SendUpstreamRequest(std::shared_ptr<Socket>& upstreamSock);
void HandleUpstreamConnected(const boost::system::error_code & ecode,
boost::asio::ip::tcp::resolver::iterator itr);
void HandleUpstreamResolved(const boost::system::error_code & ecode,
@ -157,13 +152,13 @@ namespace proxy
boost::asio::ip::tcp::resolver m_proxy_resolver;
uint8_t m_sock_buff[socks_buffer_size];
std::shared_ptr<boost::asio::ip::tcp::socket> m_sock, m_upstreamSock;
#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS)
std::shared_ptr<boost::asio::local::stream_protocol::socket> m_upstreamLocalSock;
#endif
std::shared_ptr<i2p::stream::Stream> m_stream;
uint8_t *m_remaining_data; //Data left to be sent
uint8_t *m_remaining_upstream_data; //upstream data left to be forwarded
uint8_t m_response[7+max_socks_hostname_size];
uint8_t m_upstream_response[SOCKS_UPSTREAM_SOCKS4A_REPLY_SIZE];
uint8_t m_upstream_request[14+max_socks_hostname_size];
std::size_t m_upstream_response_len;
address m_address; //Address
std::size_t m_remaining_data_len; //Size of the data left to be sent
uint32_t m_4aip; //Used in 4a requests
@ -222,6 +217,14 @@ namespace proxy
m_upstreamSock->close();
m_upstreamSock = nullptr;
}
#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS)
if (m_upstreamLocalSock)
{
LogPrint(eLogDebug, "SOCKS: Closing upstream local socket");
m_upstreamLocalSock->close();
m_upstreamLocalSock = nullptr;
}
#endif
if (m_stream)
{
LogPrint(eLogDebug, "SOCKS: Closing stream");
@ -280,37 +283,6 @@ namespace proxy
return boost::asio::const_buffers_1(m_response, size);
}
boost::asio::const_buffers_1 SOCKSHandler::GenerateUpstreamRequest()
{
size_t upstreamRequestSize = 0;
// TODO: negotiate with upstream
// SOCKS 4a
m_upstream_request[0] = '\x04'; //version
m_upstream_request[1] = m_cmd;
htobe16buf(m_upstream_request + 2, m_port);
m_upstream_request[4] = 0;
m_upstream_request[5] = 0;
m_upstream_request[6] = 0;
m_upstream_request[7] = 1;
// user id
m_upstream_request[8] = 'i';
m_upstream_request[9] = '2';
m_upstream_request[10] = 'p';
m_upstream_request[11] = 'd';
m_upstream_request[12] = 0;
upstreamRequestSize += 13;
if (m_address.dns.size <= max_socks_hostname_size - ( upstreamRequestSize + 1) ) {
// bounds check okay
memcpy(m_upstream_request + upstreamRequestSize, m_address.dns.value, m_address.dns.size);
upstreamRequestSize += m_address.dns.size;
// null terminate
m_upstream_request[++upstreamRequestSize] = 0;
} else {
LogPrint(eLogError, "SOCKS: BUG!!! m_addr.dns.sizs > max_socks_hostname - ( upstreamRequestSize + 1 ) )");
}
return boost::asio::const_buffers_1(m_upstream_request, upstreamRequestSize);
}
bool SOCKSHandler::Socks5ChooseAuth()
{
m_response[0] = '\x05'; // Version
@ -718,39 +690,45 @@ namespace proxy
void SOCKSHandler::ForwardSOCKS()
{
LogPrint(eLogInfo, "SOCKS: Forwarding to upstream");
EnterState(UPSTREAM_RESOLVE);
boost::asio::ip::tcp::resolver::query q(m_UpstreamProxyAddress, std::to_string(m_UpstreamProxyPort));
m_proxy_resolver.async_resolve(q, std::bind(&SOCKSHandler::HandleUpstreamResolved, shared_from_this(),
std::placeholders::_1, std::placeholders::_2));
}
void SOCKSHandler::AsyncUpstreamSockRead()
{
LogPrint(eLogDebug, "SOCKS: Async upstream sock read");
if (m_upstreamSock) {
m_upstreamSock->async_read_some(boost::asio::buffer(m_upstream_response, SOCKS_UPSTREAM_SOCKS4A_REPLY_SIZE),
std::bind(&SOCKSHandler::HandleUpstreamSockRecv, shared_from_this(), std::placeholders::_1, std::placeholders::_2));
} else {
LogPrint(eLogError, "SOCKS: No upstream socket for read");
SocksRequestFailed(SOCKS5_GEN_FAIL);
}
}
void SOCKSHandler::HandleUpstreamSockRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered)
{
if (ecode) {
if (m_state == UPSTREAM_HANDSHAKE ) {
// we are trying to handshake but it failed
SocksRequestFailed(SOCKS5_NET_UNREACH);
} else {
LogPrint(eLogError, "SOCKS: Bad state when reading from upstream: ", (int) m_state);
}
return;
if (m_UpstreamProxyPort) // TCP
{
EnterState(UPSTREAM_RESOLVE);
boost::asio::ip::tcp::resolver::query q(m_UpstreamProxyAddress, std::to_string(m_UpstreamProxyPort));
m_proxy_resolver.async_resolve(q, std::bind(&SOCKSHandler::HandleUpstreamResolved, shared_from_this(),
std::placeholders::_1, std::placeholders::_2));
}
else if (!m_UpstreamProxyAddress.empty ())// local
{
#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS)
EnterState(UPSTREAM_CONNECT);
m_upstreamLocalSock = std::make_shared<boost::asio::local::stream_protocol::socket>(GetOwner()->GetService());
auto s = shared_from_this ();
m_upstreamLocalSock->async_connect(m_UpstreamProxyAddress,
[s](const boost::system::error_code& ecode)
{
if (ecode)
{
LogPrint(eLogWarning, "SOCKS: Could not connect to local upstream proxy: ", ecode.message());
s->SocksRequestFailed(SOCKS5_NET_UNREACH);
return;
}
LogPrint(eLogInfo, "SOCKS: Connected to local upstream proxy");
s->SendUpstreamRequest(s->m_upstreamLocalSock);
});
#else
LogPrint(eLogError, "SOCKS: Local sockets for upstream proxy not supported");
SocksRequestFailed(SOCKS5_ADDR_UNSUP);
#endif
}
HandleUpstreamData(m_upstream_response, bytes_transfered);
else
{
LogPrint(eLogError, "SOCKS: Incorrect upstream proxy address");
SocksRequestFailed(SOCKS5_ADDR_UNSUP);
}
}
void SOCKSHandler::SocksUpstreamSuccess()
template<typename Socket>
void SOCKSHandler::SocksUpstreamSuccess(std::shared_ptr<Socket>& upstreamSock)
{
LogPrint(eLogInfo, "SOCKS: Upstream success");
boost::asio::const_buffers_1 response(nullptr, 0);
@ -767,55 +745,36 @@ namespace proxy
break;
}
m_sock->send(response);
auto forwarder = std::make_shared<i2p::client::TCPIPPipe>(GetOwner(), m_sock, m_upstreamSock);
m_upstreamSock = nullptr;
auto forwarder = CreateSocketsPipe (GetOwner(), m_sock, upstreamSock);
upstreamSock = nullptr;
m_sock = nullptr;
GetOwner()->AddHandler(forwarder);
forwarder->Start();
Terminate();
}
void SOCKSHandler::HandleUpstreamData(uint8_t * dataptr, std::size_t len)
{
if (m_state == UPSTREAM_HANDSHAKE) {
m_upstream_response_len += len;
// handle handshake data
if (m_upstream_response_len < SOCKS_UPSTREAM_SOCKS4A_REPLY_SIZE) {
// too small, continue reading
AsyncUpstreamSockRead();
} else if (len == SOCKS_UPSTREAM_SOCKS4A_REPLY_SIZE) {
// just right
uint8_t resp = m_upstream_response[1];
if (resp == SOCKS4_OK) {
// we have connected !
SocksUpstreamSuccess();
} else {
// upstream failure
LogPrint(eLogError, "SOCKS: Upstream proxy failure: ", (int) resp);
// TODO: runtime error?
SocksRequestFailed(SOCKS5_GEN_FAIL);
}
} else {
// too big
SocksRequestFailed(SOCKS5_GEN_FAIL);
}
} else {
// invalid state
LogPrint(eLogError, "SOCKS: Invalid state reading from upstream: ", (int) m_state);
}
}
void SOCKSHandler::SendUpstreamRequest()
template<typename Socket>
void SOCKSHandler::SendUpstreamRequest(std::shared_ptr<Socket>& upstreamSock)
{
LogPrint(eLogInfo, "SOCKS: Negotiating with upstream proxy");
EnterState(UPSTREAM_HANDSHAKE);
if (m_upstreamSock) {
boost::asio::write(*m_upstreamSock, GenerateUpstreamRequest());
AsyncUpstreamSockRead();
} else {
if (upstreamSock)
{
auto s = shared_from_this ();
i2p::transport::Socks5Handshake (*upstreamSock, std::make_pair(m_address.dns.ToString (), m_port),
[s, &upstreamSock](const boost::system::error_code& ec)
{
if (!ec)
s->SocksUpstreamSuccess(upstreamSock);
else
{
s->SocksRequestFailed(SOCKS5_NET_UNREACH);
LogPrint(eLogError, "SOCKS: Upstream proxy failure: ", ec.message ());
}
});
}
else
LogPrint(eLogError, "SOCKS: No upstream socket to send handshake to");
}
}
void SOCKSHandler::HandleUpstreamConnected(const boost::system::error_code & ecode, boost::asio::ip::tcp::resolver::iterator itr)
@ -826,7 +785,7 @@ namespace proxy
return;
}
LogPrint(eLogInfo, "SOCKS: Connected to upstream proxy");
SendUpstreamRequest();
SendUpstreamRequest(m_upstreamSock);
}
void SOCKSHandler::HandleUpstreamResolved(const boost::system::error_code & ecode, boost::asio::ip::tcp::resolver::iterator itr)

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2022, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -19,16 +19,22 @@ namespace client
void I2PUDPServerTunnel::HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len)
{
if (!m_LastSession || m_LastSession->Identity.GetLL()[0] != from.GetIdentHash ().GetLL()[0] || fromPort != m_LastSession->RemotePort)
{
std::lock_guard<std::mutex> lock(m_SessionsMutex);
m_LastSession = ObtainUDPSession(from, toPort, fromPort);
}
m_LastSession->IPSocket.send_to(boost::asio::buffer(buf, len), m_RemoteEndpoint);
m_LastSession->LastActivity = i2p::util::GetMillisecondsSinceEpoch();
}
void I2PUDPServerTunnel::HandleRecvFromI2PRaw (uint16_t, uint16_t, const uint8_t * buf, size_t len)
void I2PUDPServerTunnel::HandleRecvFromI2PRaw (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len)
{
if (m_LastSession && (fromPort != m_LastSession->RemotePort || toPort != m_LastSession->LocalPort))
{
std::lock_guard<std::mutex> lock(m_SessionsMutex);
auto it = m_Sessions.find (GetSessionIndex (fromPort, toPort));
if (it != m_Sessions.end ())
m_LastSession = it->second;
else
m_LastSession = nullptr;
}
if (m_LastSession)
{
m_LastSession->IPSocket.send_to(boost::asio::buffer(buf, len), m_RemoteEndpoint);
@ -41,11 +47,12 @@ namespace client
std::lock_guard<std::mutex> lock(m_SessionsMutex);
uint64_t now = i2p::util::GetMillisecondsSinceEpoch();
auto itr = m_Sessions.begin();
while(itr != m_Sessions.end()) {
if(now - (*itr)->LastActivity >= delta )
while(itr != m_Sessions.end())
{
if(now - itr->second->LastActivity >= delta )
itr = m_Sessions.erase(itr);
else
++itr;
itr++;
}
}
@ -66,15 +73,25 @@ namespace client
UDPSessionPtr I2PUDPServerTunnel::ObtainUDPSession(const i2p::data::IdentityEx& from, uint16_t localPort, uint16_t remotePort)
{
auto ih = from.GetIdentHash();
for (auto & s : m_Sessions )
auto idx = GetSessionIndex (remotePort, localPort);
{
if (s->Identity.GetLL()[0] == ih.GetLL()[0] && remotePort == s->RemotePort)
std::lock_guard<std::mutex> lock(m_SessionsMutex);
auto it = m_Sessions.find (idx);
if (it != m_Sessions.end ())
{
/** found existing session */
LogPrint(eLogDebug, "UDPServer: Found session ", s->IPSocket.local_endpoint(), " ", ih.ToBase32());
return s;
if (it->second->Identity.GetLL()[0] == ih.GetLL()[0])
{
LogPrint(eLogDebug, "UDPServer: Found session ", it->second->IPSocket.local_endpoint(), " ", ih.ToBase32());
return it->second;
}
else
{
LogPrint(eLogWarning, "UDPServer: Session with from ", remotePort, " and to ", localPort, " ports already exists. But from differend address. Removed");
m_Sessions.erase (it);
}
}
}
boost::asio::ip::address addr;
/** create new udp session */
if(m_IsUniqueLocal && m_LocalAddress.is_loopback())
@ -84,10 +101,12 @@ namespace client
}
else
addr = m_LocalAddress;
boost::asio::ip::udp::endpoint ep(addr, 0);
m_Sessions.push_back(std::make_shared<UDPSession>(ep, m_LocalDest, m_RemoteEndpoint, ih, localPort, remotePort));
auto & back = m_Sessions.back();
return back;
auto s = std::make_shared<UDPSession>(boost::asio::ip::udp::endpoint(addr, 0),
m_LocalDest, m_RemoteEndpoint, ih, localPort, remotePort);
std::lock_guard<std::mutex> lock(m_SessionsMutex);
m_Sessions.emplace (idx, s);
return s;
}
UDPSession::UDPSession(boost::asio::ip::udp::endpoint localEndpoint,
@ -144,9 +163,9 @@ namespace client
}
I2PUDPServerTunnel::I2PUDPServerTunnel (const std::string & name, std::shared_ptr<i2p::client::ClientDestination> localDestination,
const boost::asio::ip::address& localAddress, const boost::asio::ip::udp::endpoint& forwardTo, uint16_t port, bool gzip) :
const boost::asio::ip::address& localAddress, const boost::asio::ip::udp::endpoint& forwardTo, uint16_t inPort, bool gzip) :
m_IsUniqueLocal (true), m_Name (name), m_LocalAddress (localAddress),
m_RemoteEndpoint (forwardTo), m_LocalDest (localDestination), m_Gzip (gzip)
m_RemoteEndpoint (forwardTo), m_LocalDest (localDestination), m_inPort(inPort), m_Gzip (gzip)
{
}
@ -160,14 +179,23 @@ namespace client
m_LocalDest->Start ();
auto dgram = m_LocalDest->CreateDatagramDestination (m_Gzip);
dgram->SetReceiver (std::bind (&I2PUDPServerTunnel::HandleRecvFromI2P, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5));
dgram->SetRawReceiver (std::bind (&I2PUDPServerTunnel::HandleRecvFromI2PRaw, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4));
dgram->SetReceiver (
std::bind (&I2PUDPServerTunnel::HandleRecvFromI2P, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5),
m_inPort
);
dgram->SetRawReceiver (
std::bind (&I2PUDPServerTunnel::HandleRecvFromI2PRaw, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4),
m_inPort
);
}
void I2PUDPServerTunnel::Stop ()
{
auto dgram = m_LocalDest->GetDatagramDestination ();
if (dgram) dgram->ResetReceiver ();
if (dgram) {
dgram->ResetReceiver (m_inPort);
dgram->ResetRawReceiver (m_inPort);
}
}
std::vector<std::shared_ptr<DatagramSessionInfo> > I2PUDPServerTunnel::GetSessions ()
@ -175,8 +203,9 @@ namespace client
std::vector<std::shared_ptr<DatagramSessionInfo> > sessions;
std::lock_guard<std::mutex> lock (m_SessionsMutex);
for (UDPSessionPtr s: m_Sessions)
for (auto it: m_Sessions)
{
auto s = it.second;
if (!s->m_Destination) continue;
auto info = s->m_Destination->GetInfoForRemote (s->Identity);
if (!info) continue;
@ -220,9 +249,13 @@ namespace client
dgram->SetReceiver (std::bind (&I2PUDPClientTunnel::HandleRecvFromI2P, this,
std::placeholders::_1, std::placeholders::_2,
std::placeholders::_3, std::placeholders::_4,
std::placeholders::_5));
std::placeholders::_5),
RemotePort
);
dgram->SetRawReceiver (std::bind (&I2PUDPClientTunnel::HandleRecvFromI2PRaw, this,
std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4));
std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4),
RemotePort
);
m_LocalDest->Start ();
if (m_ResolveThread == nullptr)
@ -233,7 +266,10 @@ namespace client
void I2PUDPClientTunnel::Stop ()
{
auto dgram = m_LocalDest->GetDatagramDestination ();
if (dgram) dgram->ResetReceiver ();
if (dgram) {
dgram->ResetReceiver (RemotePort);
dgram->ResetRawReceiver (RemotePort);
}
m_cancel_resolve = true;
m_Sessions.clear();

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2022, The PurpleI2P Project
* Copyright (c) 2013-2024, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -72,7 +72,7 @@ namespace client
boost::asio::ip::udp::endpoint LocalEndpoint;
/** client's udp endpoint */
boost::asio::ip::udp::endpoint RemoteEndpoint;
/** how long has this converstation been idle in ms */
/** how long has this conversation been idle in ms */
uint64_t idle;
};
@ -104,6 +104,7 @@ namespace client
void HandleRecvFromI2P (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len);
void HandleRecvFromI2PRaw (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len);
UDPSessionPtr ObtainUDPSession (const i2p::data::IdentityEx& from, uint16_t localPort, uint16_t remotePort);
uint32_t GetSessionIndex (uint16_t fromPort, uint16_t toPort) const { return ((uint32_t)fromPort << 16) + toPort; }
private:
@ -112,9 +113,10 @@ namespace client
boost::asio::ip::address m_LocalAddress;
boost::asio::ip::udp::endpoint m_RemoteEndpoint;
std::mutex m_SessionsMutex;
std::vector<UDPSessionPtr> m_Sessions;
std::unordered_map<uint32_t, UDPSessionPtr> m_Sessions; // (from port, to port)->session
std::shared_ptr<i2p::client::ClientDestination> m_LocalDest;
UDPSessionPtr m_LastSession;
uint16_t m_inPort;
bool m_Gzip;
public:

Loading…
Cancel
Save