Compare commits

...

290 Commits

Author SHA1 Message Date
orignal a1322d4667 move unsent I2NP messages to new session if replaced 5 hours ago
orignal 4100249313 removed bootstrap from floodfill. Removed requested destinations mutex 1 day ago
orignal acbd3f897b fixed race condition between local buffer creation and sending it through the transports 6 days ago
orignal 7dc5a04b8d update timestamp for non-reachable router 6 days ago
orignal 03635f4444 publish through tunnels in case of restricted routes 6 days ago
orignal 0fae04f96a update local RouterInfo timestamp by timer even in hidden mode 7 days ago
orignal bb531a878d request newly discovered routers with random intervals after exploratory 1 week ago
orignal 0f7db8e418 list of request callbacks 1 week ago
orignal 9a724b2af9 separate timer for netdb requests cleanup 1 week ago
orignal f4ea6138e8 removed non longer used mutex 1 week ago
orignal e74272781f moved exploratory to netdb requests thread 1 week ago
orignal b75e418879 request destination in netdb requests thread 1 week ago
orignal 927123188c handle onDrop for request message in nedb requests thread 1 week ago
orignal c00eb8cf44 handle requests completions in netdb requests thread 2 weeks ago
orignal 265bb8b779 handle DatabaseSearchReply in netdb requests thread 2 weeks ago
orignal e3be409945 moved netdb requests to separate thread 2 weeks ago
orignal d8707ceb57
Merge pull request #2066 from EKCKABATOP54/fix
Fixed checking the bandwidth flag in the config
2 weeks ago
orignal 39e16824b9 reset routing path if duplicated SYN received 2 weeks ago
orignal 285e693a4e fixed deadlock 2 weeks ago
orignal 940628bf36 update LeaseSet if inbond tunnel failed 2 weeks ago
orignal b5994e058a increment num attempts if no reply tunnel specified 2 weeks ago
orignal 22dabfd79e use unordered_map for excluded routers. don't request to self 2 weeks ago
orignal 0e41c3fa36 resend more interval variance 2 weeks ago
orignal 124698854f skip resent recently sessions during resend 3 weeks ago
EKCKABATOP54 f223e668ce Fixed checking the bandwidth flag in the config 3 weeks ago
orignal f5b823a712 common code for sending netdb lookup 3 weeks ago
r4sas 4163542125
[gha] short commit hashes for deb packages
Signed-off-by: r4sas <r4sas@i2pmail.org>
3 weeks ago
r4sas 6921c8391e
[FS] boost 1.85.0 support
Signed-off-by: r4sas <r4sas@i2pmail.org>
3 weeks ago
r4sas 47a2020472
[i18n] update translations
Signed-off-by: r4sas <r4sas@i2pmail.org>
3 weeks ago
orignal 9d38facf3b 2.52.0 3 weeks ago
r4sas a1be1aa9ec
[gha] cache packages on winxp build
Signed-off-by: r4sas <r4sas@i2pmail.org>
3 weeks ago
r4sas c4bbe2bb4a
[win32] fix warning in NetState
Signed-off-by: r4sas <r4sas@i2pmail.org>
3 weeks ago
r4sas 601695dede [gha] winxp: rebuild boost
Signed-off-by: r4sas <r4sas@i2pmail.org>
3 weeks ago
r4sas cdc81e19a0 [gha] update winxp build
Signed-off-by: r4sas <r4sas@i2pmail.org>
3 weeks ago
orignal b98b3a87b0 fixed race codition between RouterInfo's buffer persist and update 3 weeks ago
orignal 425ef2cfe5 persist routers in separate thread 3 weeks ago
orignal c454685605 exclude addresses with wrong keys 4 weeks ago
orignal c98926abf2 use mt19937 instead rand() 4 weeks ago
orignal 7aacc97351 initialize requests before reseeds 4 weeks ago
orignal c49e17ad40 use timestamp to reset rng 4 weeks ago
orignal 296b721929 shared_ptr for NetdbRequests 4 weeks ago
orignal d0cf385f4b memory pool for requested destination. Reduced request attaempts to 5 4 weeks ago
orignal b91f5a7430 fixed race condition on stop 4 weeks ago
orignal 4d8431907d use std::sample for exploratory selection if C++17 4 weeks ago
orignal b0cf5130a2 don't try another flloofill if request already exists 4 weeks ago
orignal 396aa6944d cache complete request for a while and not send the same request again 4 weeks ago
orignal 8a20d3219b don't build exploratory selection on each request 4 weeks 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
1 month ago
Vort 6592fab41c manage netDb requests more frequently 1 month ago
orignal 02895d4cf5 respond with confirmied router to exploratory lookup 1 month ago
orignal 8b7941c4ce fixed typo 1 month ago
orignal de673464d1 don't try to connect directy by SSU2 to unnconfirmed router if direct NTCP2 is presented 1 month ago
orignal 6ce2c30522 build client tunnels through confimed routers only if low rate 1 month ago
orignal c5a1e8cac8 give preference to direct connection 1 month ago
orignal f67c38d8d2 fixed typo 1 month 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 2 months ago
r4sas a30d1972e5
[win] update status code
Signed-off-by: r4sas <r4sas@i2pmail.org>
2 months ago
orignal 0c2330bf14
Merge pull request #2045 from Vort/rtt_sample
streaming improvements
2 months ago
Vort a703d31893 don't double initial RTO 2 months ago
Vort 4f8f3a386f restart stream resend timer after updating initial RTO 2 months ago
Vort 83f0b9c041 extract single RTT sample from stream ACK 2 months ago
Vort cf77be0eeb add lower limit for stream RTO 2 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>
3 months ago
orignal a1dce017f4 allow tunnel endpoint to send tunnel build reply to itself 3 months ago
orignal 78af34237e reduce session termination timeout 3 months ago
orignal 8874ea8033 fixed typo 3 months ago
orignal 6bd1ee36f7 fixed typo 3 months ago
orignal f07c4bd1dd drop transit tunnel if next ident is ours 3 months ago
orignal 8524a67895 fail last tunnel if tunnel quantity is 1 3 months ago
orignal 43d880752e Merge branch 'openssl' of https://github.com/PurpleI2P/i2pd into openssl 3 months ago
orignal d4246edb82 don't fail last tunnel 3 months ago
orignal ee8449fa05
Merge pull request #2024 from Vort/congestion_zero_check
add zero check to congestion level calculations
3 months ago
Vort 5415598f60 add zero check to congestion level calculations 3 months ago
orignal 7d73c304b5 Extend transit tunnels limit to 4 bytes. Bump default value to 10K 3 months ago
orignal d25206abce encrypted tunnel test messages 3 months ago
r4sas 5d7c6fb0b3
[gha] msvc: copy openssl libraries to fix build
Signed-off-by: r4sas <r4sas@i2pmail.org>
3 months ago
orignal 36a060d50f Consider 'M' routers as low bandwidth 3 months ago
orignal 577ed56af0 store HTTP and SOCKS proxy as pointer to I2PService 3 months ago
orignal 695dc96a83 common ServiceAcceptor for all stream protocols 3 months ago
orignal e5251bf3c3
Merge pull request #2022 from Vort/bw_time_jumps3
skip bandwidth updates in case of time going backwards
3 months ago
R4SAS 2692aef53d
[gha] update windows msvc action 3 months ago
Vort d524105727 skip bandwidth updates in case of time going backwards 3 months ago
orignal 26463c50fc
Merge pull request #2020 from Vort/select_next_hop2
make more attempts to select not bad hop
3 months ago
Vort b092e712ec make more attempts to select not bad hop 3 months ago
orignal cb8fbb0135
Merge pull request #2018 from Vort/congestion_consts
add constants for congestion levels
3 months ago
Vort 19e23b34da add constants for congestion levels 3 months ago
orignal a4a3f8e96b support upstream proxy through local sockets 3 months ago
orignal f2b720617c
Merge pull request #2016 from Vort/medium_congestion2
implement medium congestion indication
3 months ago
Vort d677d67676 implement medium congestion indication 3 months ago
orignal 7e3157b162 don't process packet in terminated stream 3 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 4 months ago
orignal 67c4d4bcaa fixed VS build error 4 months ago
orignal 25e82105b2
Merge pull request #2010 from wekoq/udp-tunnels-dest
Add support for multiple udp server tunnels on one destionation
4 months ago
weko 4b167fdbaf Update copyright year 4 months ago
weko 5b93558bd0 Add support for multiple udp server tunnels on one destionation 4 months ago
orignal 70639f1139 don't adjust clock if offsets came from same router 4 months ago
orignal c5a1806528 fixed possible deadlock 4 months ago
orignal a2249f0a82 identify server tunnel session but from ant to ports 4 months ago
orignal 9f217f8a11 don't send expired I2NP messages 4 months ago
orignal 5e19e361e7 check max frame sizebefore sending 4 months ago
orignal 0b47f65b06 don't remove another NTCP2 session with same address 4 months ago
orignal 140146e433 limit mininal received packet size to 40 bytes 4 months ago
R4SAS b4484c8e8f
[gha] msvc: switch back to boost 1.81.0 4 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
orignal 3b97feb89f 2.50.2 5 months ago
orignal a8135b8d18 2.50.2 5 months ago
orignal 5cf1961fa4 drop updated routers from future 5 months ago
Dimitris Apostolou 577c71b930
Fix typos 5 months ago
orignal c5cab05a6b reset peding time offset if correct time was received 5 months ago
orignal b855c71891 don't adjust clock if time offsets are too different 5 months ago
orignal 21f41a2b2a correct time offset direction 5 months ago
orignal 8319dd6b25 drop exploratory and leaseset lookups for non-floodfill router 5 months ago
orignal d4c47d90cb adjust time offset after second time discrepancy 5 months ago
orignal 302af823a3 fixed race condition with openssl 3.2.0 5 months ago
r4sas 69ee6112b3
[changelog] fix version
Signed-off-by: r4sas <r4sas@i2pmail.org>
5 months ago
r4sas 816a58f292
2.50.1
Signed-off-by: r4sas <r4sas@i2pmail.org>
5 months ago
orignal 43e130ee34 reinitialize context before each Sign/Verify call to make it working with openssl 3.2 6 months ago
orignal 8ffc1486a4 test-eddsa added 6 months ago
orignal 0e98dd5c70 use fallback EdDSA implementation with openssl 3.2.0 due to regression in EVP_DigestSign (#23075) 6 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,101 @@
# 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
- False positive clock skew detection
## [2.50.1] - 2023-12-23
###Fixed
- Support for new EdDSA usage behavior in OpenSSL 3.2.0
## [2.50.0] - 2023-12-18
### Added
- Support of concurrent ACCEPTs on SAM 3.1

@ -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.0
Version: 2.52.0
Release: git%{git_hash}%{?dist}
Summary: I2P router written in C++
Conflicts: i2pd
@ -144,6 +144,18 @@ 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
* Sat Dec 23 2023 r4sas <r4sas@i2pmail.org> - 2.50.1
- update to 2.50.1
* Mon Dec 18 2023 orignal <orignal@i2pmail.org> - 2.50.0
- update to 2.50.0

@ -1,5 +1,5 @@
Name: i2pd
Version: 2.50.0
Version: 2.52.0
Release: 1%{?dist}
Summary: I2P router written in C++
Conflicts: i2pd-git
@ -142,6 +142,18 @@ 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
* Sat Dec 23 2023 r4sas <r4sas@i2pmail.org> - 2.50.1
- update to 2.50.1
* Mon Dec 18 2023 orignal <orignal@i2pmail.org> - 2.50.0
- update to 2.50.0

@ -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);

24
debian/changelog vendored

@ -1,3 +1,27 @@
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
i2pd (2.50.1-1) unstable; urgency=medium
* updated to version 2.50.1/0.9.61
-- r4sas <r4sas@i2pmail.org> Sat, 23 Dec 2023 18:30:00 +0000
i2pd (2.50.0-1) unstable; urgency=medium
* updated to version 2.50.0/0.9.61

@ -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-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
{
@ -833,14 +837,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 +875,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 +1055,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 +1084,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++;
@ -1116,20 +1136,31 @@ namespace transport
if (!m_SendQueue.empty ())
{
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
@ -1145,7 +1176,12 @@ namespace transport
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 +1190,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 +1201,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 +1213,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,8 +1256,13 @@ 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 (isSemiFull && it->onDrop)
it->Drop (); // drop earlier because we can handle it
else
m_SendQueue.push_back (std::move (it));
if (!m_IsSending)
SendQueue ();
else if (m_SendQueue.size () > NTCP2_MAX_OUTGOING_QUEUE_SIZE)
@ -1234,7 +1276,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 ()));
}
@ -1400,7 +1442,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 +1536,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 +1583,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 +1731,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 +1810,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 %
@ -266,8 +267,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,9 @@ 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
/** function for visiting a leaseset stored in a floodfill */
typedef std::function<void(const IdentHash, std::shared_ptr<LeaseSet>)> LeaseSetVisitor;
@ -77,27 +83,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) 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 +118,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,15 +136,14 @@ 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 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);
void ManageRouterInfos ();
void ManageLeaseSets ();
@ -153,6 +157,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 +179,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
*
@ -11,11 +11,26 @@
#include "Transports.h"
#include "NetDb.hpp"
#include "NetDbRequests.h"
#include "ECIESX25519AEADRatchetSession.h"
#include "RouterContext.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 +43,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 +53,153 @@ 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 ())
{
}
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 +209,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 + rand () % EXPLORATORY_REQUEST_INTERVAL)/2 :
EXPLORATORY_REQUEST_INTERVAL + rand () % 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 + rand () % 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,116 @@
#ifndef NETDB_REQUESTS_H__
#define NETDB_REQUESTS_H__
#include <inttypes.h>
#include <memory>
#include <set>
#include <map>
#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;
};
}
}

@ -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
*
@ -24,7 +24,8 @@ namespace transport
m_AddressV4 (boost::asio::ip::address_v4()), m_AddressV6 (boost::asio::ip::address_v6()),
m_TerminationTimer (GetService ()), m_CleanupTimer (GetService ()), m_ResendTimer (GetService ()),
m_IntroducersUpdateTimer (GetService ()), m_IntroducersUpdateTimerV6 (GetService ()),
m_IsPublished (true), m_IsSyncClockFromPeers (true), m_IsThroughProxy (false)
m_IsPublished (true), m_IsSyncClockFromPeers (true), m_PendingTimeOffset (0),
m_IsThroughProxy (false)
{
}
@ -209,6 +210,42 @@ namespace transport
return ep.port ();
}
void SSU2Server::AdjustTimeOffset (int64_t offset, std::shared_ptr<const i2p::data::IdentityEx> from)
{
if (offset)
{
if (m_PendingTimeOffset) // one more
{
if (m_PendingTimeOffsetFrom && from &&
m_PendingTimeOffsetFrom->GetIdentHash ().GetLL()[0] != from->GetIdentHash ().GetLL()[0]) // from different routers
{
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 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)
{
boost::asio::ip::udp::socket& socket = localEndpoint.address ().is_v6 () ? m_SocketV6 : m_SocketV4;
@ -219,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;
}
@ -259,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)
@ -358,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);
@ -373,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
@ -416,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;
@ -427,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++;
@ -436,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--;
@ -542,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);
@ -584,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,
@ -618,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,
@ -642,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)
{
@ -729,7 +819,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));
@ -772,8 +862,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 (); });
@ -882,7 +975,8 @@ namespace transport
void SSU2Server::ScheduleResend (bool more)
{
m_ResendTimer.expires_from_now (boost::posix_time::milliseconds (more ? SSU2_RESEND_CHECK_MORE_TIMEOUT :
m_ResendTimer.expires_from_now (boost::posix_time::milliseconds (more ?
(SSU2_RESEND_CHECK_MORE_TIMEOUT + rand () % SSU2_RESEND_CHECK_MORE_TIMEOUT_VARIANCE):
(SSU2_RESEND_CHECK_TIMEOUT + rand () % SSU2_RESEND_CHECK_TIMEOUT_VARIANCE)));
m_ResendTimer.async_wait (std::bind (&SSU2Server::HandleResendTimer,
this, std::placeholders::_1));
@ -896,7 +990,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)
@ -954,7 +1049,7 @@ namespace transport
}
std::list<std::shared_ptr<SSU2Session> > SSU2Server::FindIntroducers (int maxNumIntroducers,
bool v4, const std::set<i2p::data::IdentHash>& excluded) const
bool v4, const std::unordered_set<i2p::data::IdentHash>& excluded) const
{
std::list<std::shared_ptr<SSU2Session> > ret;
for (const auto& s : m_Sessions)
@ -985,7 +1080,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;

@ -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,11 @@
#define SSU2_H__
#include <unordered_map>
#include <unordered_set>
#include <mutex>
#include "util.h"
#include "SSU2Session.h"
#include "Socks5.h"
namespace i2p
{
@ -20,13 +22,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
@ -66,6 +70,7 @@ namespace transport
bool IsSupported (const boost::asio::ip::address& addr) const;
uint16_t GetPort (bool v4) const;
bool IsSyncClockFromPeers () const { return m_IsSyncClockFromPeers; };
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);
@ -74,7 +79,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,7 +128,7 @@ namespace transport
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;
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);
@ -161,6 +166,8 @@ namespace transport
std::shared_ptr<SSU2Session> m_LastSession;
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;
// proxy
bool m_IsThroughProxy;

@ -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
*
@ -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,57 @@ 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));
}
}
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 +418,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;
}
@ -517,7 +558,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 +582,7 @@ namespace transport
it++;
if (!resentPackets.empty ())
{
m_LastResendTime = ts;
#if (__cplusplus >= 201703L) // C++ 17 or higher
m_SentPackets.merge (resentPackets);
#else
@ -857,12 +899,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
}
@ -1102,6 +1144,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 +1517,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;
@ -1668,10 +1714,12 @@ namespace transport
if (m_Server.IsSyncClockFromPeers ())
{
if (std::abs (offset) > SSU2_CLOCK_THRESHOLD)
{
LogPrint (eLogWarning, "SSU2: Clock adjusted by ", -offset, " seconds");
i2p::util::AdjustTimeOffset (-offset);
}
{
LogPrint (eLogWarning, "SSU2: Time offset ", offset, " from ", m_RemoteEndpoint);
m_Server.AdjustTimeOffset (-offset, GetRemoteIdentity ());
}
else
m_Server.AdjustTimeOffset (0, nullptr);
}
else if (std::abs (offset) > SSU2_CLOCK_SKEW)
{
@ -1727,8 +1775,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;
}
@ -1751,7 +1806,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
@ -2099,7 +2154,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
{
@ -2170,7 +2225,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);
@ -2270,7 +2326,7 @@ namespace transport
if (GetTestingState ())
{
SetTestingState (false);
if (GetRouterStatus () != eRouterStatusFirewalled)
if (GetRouterStatus () != eRouterStatusFirewalled && addr->IsPeerTesting ())
{
SetRouterStatus (eRouterStatusFirewalled);
if (m_Address->IsV4 ())
@ -2481,6 +2537,8 @@ namespace transport
else if (m_Address->IsV6 ())
i2p::context.SetTestingV6 (testing);
}
if (!testing)
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)
@ -2495,27 +2553,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;

@ -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)

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2021, The PurpleI2P Project
* Copyright (c) 2013-2023, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -15,26 +15,35 @@ namespace i2p
namespace crypto
{
#if OPENSSL_EDDSA
EDDSA25519Verifier::EDDSA25519Verifier ()
EDDSA25519Verifier::EDDSA25519Verifier ():
m_Pkey (nullptr)
{
m_MDCtx = EVP_MD_CTX_create ();
}
EDDSA25519Verifier::~EDDSA25519Verifier ()
{
EVP_MD_CTX_destroy (m_MDCtx);
EVP_PKEY_free (m_Pkey);
}
void EDDSA25519Verifier::SetPublicKey (const uint8_t * signingKey)
{
EVP_PKEY * pkey = EVP_PKEY_new_raw_public_key (EVP_PKEY_ED25519, NULL, signingKey, 32);
EVP_DigestVerifyInit (m_MDCtx, NULL, NULL, NULL, pkey);
EVP_PKEY_free (pkey);
if (m_Pkey) EVP_PKEY_free (m_Pkey);
m_Pkey = EVP_PKEY_new_raw_public_key (EVP_PKEY_ED25519, NULL, signingKey, 32);
}
bool EDDSA25519Verifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const
{
return EVP_DigestVerify (m_MDCtx, signature, 64, buf, len);
if (m_Pkey)
{
EVP_MD_CTX * ctx = EVP_MD_CTX_create ();
EVP_DigestVerifyInit (ctx, NULL, NULL, NULL, m_Pkey);
auto ret = EVP_DigestVerify (ctx, signature, 64, buf, len);
EVP_MD_CTX_destroy (ctx);
return ret;
}
else
LogPrint (eLogError, "EdDSA verification key is not set");
return false;
}
#else
@ -99,41 +108,45 @@ namespace crypto
#if OPENSSL_EDDSA
EDDSA25519Signer::EDDSA25519Signer (const uint8_t * signingPrivateKey, const uint8_t * signingPublicKey):
m_MDCtx (nullptr), m_Fallback (nullptr)
m_Pkey (nullptr), m_Fallback (nullptr)
{
EVP_PKEY * pkey = EVP_PKEY_new_raw_private_key (EVP_PKEY_ED25519, NULL, signingPrivateKey, 32);
m_Pkey = EVP_PKEY_new_raw_private_key (EVP_PKEY_ED25519, NULL, signingPrivateKey, 32);
uint8_t publicKey[EDDSA25519_PUBLIC_KEY_LENGTH];
size_t len = EDDSA25519_PUBLIC_KEY_LENGTH;
EVP_PKEY_get_raw_public_key (pkey, publicKey, &len);
EVP_PKEY_get_raw_public_key (m_Pkey, publicKey, &len);
if (signingPublicKey && memcmp (publicKey, signingPublicKey, EDDSA25519_PUBLIC_KEY_LENGTH))
{
LogPrint (eLogWarning, "EdDSA public key mismatch. Fallback");
m_Fallback = new EDDSA25519SignerCompat (signingPrivateKey, signingPublicKey);
EVP_PKEY_free (m_Pkey);
m_Pkey = nullptr;
}
else
{
m_MDCtx = EVP_MD_CTX_create ();
EVP_DigestSignInit (m_MDCtx, NULL, NULL, NULL, pkey);
}
EVP_PKEY_free (pkey);
}
EDDSA25519Signer::~EDDSA25519Signer ()
{
if (m_Fallback) delete m_Fallback;
EVP_MD_CTX_destroy (m_MDCtx);
if (m_Pkey) EVP_PKEY_free (m_Pkey);
}
void EDDSA25519Signer::Sign (const uint8_t * buf, int len, uint8_t * signature) const
{
if (m_Fallback) return m_Fallback->Sign (buf, len, signature);
else
if (m_Fallback)
return m_Fallback->Sign (buf, len, signature);
else if (m_Pkey)
{
EVP_MD_CTX * ctx = EVP_MD_CTX_create ();
size_t l = 64;
uint8_t sig[64]; // temporary buffer for signature. openssl issue #7232
EVP_DigestSign (m_MDCtx, sig, &l, buf, len);
EVP_DigestSignInit (ctx, NULL, NULL, NULL, m_Pkey);
if (!EVP_DigestSign (ctx, sig, &l, buf, len))
LogPrint (eLogError, "EdDSA signing failed");
memcpy (signature, sig, 64);
EVP_MD_CTX_destroy (ctx);
}
else
LogPrint (eLogError, "EdDSA signing key is not set");
}
#endif
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2021, The PurpleI2P Project
* Copyright (c) 2013-2023, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@ -304,7 +304,7 @@ namespace crypto
private:
#if OPENSSL_EDDSA
EVP_MD_CTX * m_MDCtx;
EVP_PKEY * m_Pkey;
#else
EDDSAPoint m_PublicKey;
uint8_t m_PublicKeyEncoded[EDDSA25519_PUBLIC_KEY_LENGTH];
@ -341,7 +341,7 @@ namespace crypto
private:
EVP_MD_CTX * m_MDCtx;
EVP_PKEY * m_Pkey;
EDDSA25519SignerCompat * m_Fallback;
};
#else

@ -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,6 +232,24 @@ 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);

@ -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,6 +24,10 @@ namespace util
uint32_t GetMinutesSinceEpoch ();
uint32_t GetHoursSinceEpoch ();
uint64_t GetMonotonicMicroseconds ();
uint64_t GetMonotonicMilliseconds ();
uint64_t GetMonotonicSeconds ();
void GetCurrentDate (char * date); // returns date as YYYYMMDD string, 9 bytes
void GetDateString (uint64_t timestamp, char * date); // timestamp is seconds since epoch, returns date as YYYYMMDD string, 9 bytes
void AdjustTimeOffset (int64_t offset); // in seconds from current

@ -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 ();
}

@ -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
*
@ -18,7 +18,7 @@
#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_MINOR 52
#define I2PD_VERSION_MICRO 0
#define I2PD_VERSION_PATCH 0
#ifdef 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:

@ -65,6 +65,10 @@ SET(test-elligator_SRCS
test-elligator.cpp
)
set(test-eddsa_SRCS
test-eddsa.cpp
)
add_executable(test-http-merge_chunked ${test-http-merge_chunked_SRCS})
add_executable(test-http-req ${test-http-req_SRCS})
add_executable(test-http-res ${test-http-res_SRCS})
@ -77,6 +81,7 @@ add_executable(test-x25519 ${test-x25519_SRCS})
add_executable(test-aeadchacha20poly1305 ${test-aeadchacha20poly1305_SRCS})
add_executable(test-blinding ${test-blinding_SRCS})
add_executable(test-elligator ${test-elligator_SRCS})
add_executable(test-eddsa ${test-eddsa_SRCS})
set(LIBS
libi2pd
@ -101,6 +106,7 @@ target_link_libraries(test-x25519 ${LIBS})
target_link_libraries(test-aeadchacha20poly1305 ${LIBS})
target_link_libraries(test-blinding ${LIBS})
target_link_libraries(test-elligator ${LIBS})
target_link_libraries(test-eddsa ${LIBS})
add_test(test-http-merge_chunked ${TEST_PATH}/test-http-merge_chunked)
add_test(test-http-req ${TEST_PATH}/test-http-req)
@ -114,3 +120,4 @@ add_test(test-x25519 ${TEST_PATH}/test-x25519)
add_test(test-aeadchacha20poly1305 ${TEST_PATH}/test-aeadchacha20poly1305)
add_test(test-blinding ${TEST_PATH}/test-blinding)
add_test(test-elligator ${TEST_PATH}/test-elligator)
add_test(test-eddsa ${TEST_PATH}/test-eddsa)

@ -7,7 +7,8 @@ LIBI2PD = ../libi2pd.a
TESTS = \
test-http-merge_chunked test-http-req test-http-res test-http-url test-http-url_decode \
test-gost test-gost-sig test-base-64 test-x25519 test-aeadchacha20poly1305 test-blinding test-elligator
test-gost test-gost-sig test-base-64 test-x25519 test-aeadchacha20poly1305 test-blinding \
test-elligator test-eddsa
ifneq (, $(findstring mingw, $(SYS))$(findstring windows-gnu, $(SYS))$(findstring cygwin, $(SYS)))
CXXFLAGS += -DWIN32_LEAN_AND_MEAN
@ -55,6 +56,9 @@ test-blinding: test-blinding.cpp $(LIBI2PD)
test-elligator: test-elligator.cpp $(LIBI2PD)
$(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS)
test-eddsa: test-eddsa.cpp $(LIBI2PD)
$(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS)
run: $(TESTS)
@for TEST in $(TESTS); do echo Running $$TEST; ./$$TEST ; done

@ -0,0 +1,68 @@
#include <cassert>
#include <inttypes.h>
#include <string.h>
#include "Signature.h"
// TEST 1024 from RFC-8032
int main ()
{
uint8_t key[32], pub[32], msg[1024], sig[64];
BIGNUM * input = BN_new();
BN_hex2bn(&input, "f5e5767cf153319517630f226876b86c8160cc583bc013744c6bf255f5cc0ee5");
BN_bn2bin(input, key);
BN_hex2bn(&input,
"08b8b2b733424243760fe426a4b54908632110a66c2f6591eabd3345e3e4eb98"
"fa6e264bf09efe12ee50f8f54e9f77b1e355f6c50544e23fb1433ddf73be84d8"
"79de7c0046dc4996d9e773f4bc9efe5738829adb26c81b37c93a1b270b20329d"
"658675fc6ea534e0810a4432826bf58c941efb65d57a338bbd2e26640f89ffbc"
"1a858efcb8550ee3a5e1998bd177e93a7363c344fe6b199ee5d02e82d522c4fe"
"ba15452f80288a821a579116ec6dad2b3b310da903401aa62100ab5d1a36553e"
"06203b33890cc9b832f79ef80560ccb9a39ce767967ed628c6ad573cb116dbef"
"efd75499da96bd68a8a97b928a8bbc103b6621fcde2beca1231d206be6cd9ec7"
"aff6f6c94fcd7204ed3455c68c83f4a41da4af2b74ef5c53f1d8ac70bdcb7ed1"
"85ce81bd84359d44254d95629e9855a94a7c1958d1f8ada5d0532ed8a5aa3fb2"
"d17ba70eb6248e594e1a2297acbbb39d502f1a8c6eb6f1ce22b3de1a1f40cc24"
"554119a831a9aad6079cad88425de6bde1a9187ebb6092cf67bf2b13fd65f270"
"88d78b7e883c8759d2c4f5c65adb7553878ad575f9fad878e80a0c9ba63bcbcc"
"2732e69485bbc9c90bfbd62481d9089beccf80cfe2df16a2cf65bd92dd597b07"
"07e0917af48bbb75fed413d238f5555a7a569d80c3414a8d0859dc65a46128ba"
"b27af87a71314f318c782b23ebfe808b82b0ce26401d2e22f04d83d1255dc51a"
"ddd3b75a2b1ae0784504df543af8969be3ea7082ff7fc9888c144da2af58429e"
"c96031dbcad3dad9af0dcbaaaf268cb8fcffead94f3c7ca495e056a9b47acdb7"
"51fb73e666c6c655ade8297297d07ad1ba5e43f1bca32301651339e22904cc8c"
"42f58c30c04aafdb038dda0847dd988dcda6f3bfd15c4b4c4525004aa06eeff8"
"ca61783aacec57fb3d1f92b0fe2fd1a85f6724517b65e614ad6808d6f6ee34df"
"f7310fdc82aebfd904b01e1dc54b2927094b2db68d6f903b68401adebf5a7e08"
"d78ff4ef5d63653a65040cf9bfd4aca7984a74d37145986780fc0b16ac451649"
"de6188a7dbdf191f64b5fc5e2ab47b57f7f7276cd419c17a3ca8e1b939ae49e4"
"88acba6b965610b5480109c8b17b80e1b7b750dfc7598d5d5011fd2dcc5600a3"
"2ef5b52a1ecc820e308aa342721aac0943bf6686b64b2579376504ccc493d97e"
"6aed3fb0f9cd71a43dd497f01f17c0e2cb3797aa2a2f256656168e6c496afc5f"
"b93246f6b1116398a346f1a641f3b041e989f7914f90cc2c7fff357876e506b5"
"0d334ba77c225bc307ba537152f3f1610e4eafe595f6d9d90d11faa933a15ef1"
"369546868a7f3a45a96768d40fd9d03412c091c6315cf4fde7cb68606937380d"
"b2eaaa707b4c4185c32eddcdd306705e4dc1ffc872eeee475a64dfac86aba41c"
"0618983f8741c5ef68d3a101e8a3b8cac60c905c15fc910840b94c00a0b9d0"
);
BN_bn2bin(input, msg);
BN_hex2bn(&input,
"0aab4c900501b3e24d7cdf4663326a3a87df5e4843b2cbdb67cbf6e460fec350"
"aa5371b1508f9f4528ecea23c436d94b5e8fcd4f681e30a6ac00a9704a188a03");
BN_bn2bin(input, sig);
BN_hex2bn(&input,
"278117fc144c72340f67d0f2316e8386ceffbf2b2428c9c51fef7c597f1d426e");
BN_bn2bin(input, pub);
uint8_t s[64];
i2p::crypto::EDDSA25519Signer signer (key);
signer.Sign (msg, 1023, s);
#if OPENSSL_EDDSA
assert(memcmp (s, sig, 64) == 0);
#endif
i2p::crypto::EDDSA25519Verifier verifier;
verifier.SetPublicKey (pub);
assert(verifier.Verify (msg, 1023, s));
}
Loading…
Cancel
Save