Compare commits

..

444 Commits

Author SHA1 Message Date
lcharles123
71334e487b Merge 6e639f0e6a into 5f762845f0 2025-03-01 03:57:48 -10:00
orignal
5f762845f0 move BOB incoming connection logic from I2PTunnelConnection to BOB 2025-02-28 22:20:50 -05:00
orignal
539e7e988e reduce I2PTunnelConnection buffer size 2025-02-27 21:35:14 -05:00
orignal
2a4403f1e0 lazy creation of TunnelEnpoint for transit tunnel 2025-02-27 18:00:24 -05:00
orignal
dcd15cc244 use common constants for babdwidth limits 2025-02-25 14:12:10 -05:00
orignal
9432202fad check PeerTest buffer size 2025-02-24 13:58:10 -05:00
orignal
bf050ac465 fixed typo 2025-02-22 19:01:20 -05:00
orignal
81dae1997d replace boost::lexical_cast by std::from_chars and std::to_string 2025-02-21 20:34:53 -05:00
orignal
7e3d9649de use plain buffer instead stream for SignedData 2025-02-20 22:04:58 -05:00
orignal
9ba016259d use plain buffer instead stream for SignedData 2025-02-20 21:59:24 -05:00
orignal
7791b3952e check RelayRequest, RelayIntro, RelayResponse buffer size. Use assign instead memcpy 2025-02-20 16:53:49 -05:00
orignal
b97f09cc95 const ExtractString and ExtractMapping 2025-02-19 18:51:54 -05:00
orignal
aedf59d11a fixed typo 2025-02-19 13:38:47 -05:00
orignal
d09367d686 always pass RouterInfo param values as string_view 2025-02-19 11:08:47 -05:00
orignal
70f99ccc21 update router caps 2025-02-18 21:49:07 -05:00
orignal
ef72ba3f34 parse RouterInfo from buffer 2025-02-18 21:11:59 -05:00
orignal
251605e2b8 Fix the calculation of the window drop size 2025-02-18 20:22:18 -05:00
orignal
fa2178ca3e set max padding size to 32 bytes 2025-02-17 15:08:22 -05:00
orignal
3d19fa12f6 create new tunnel immediately if last one failed 2025-02-15 15:27:14 -05:00
orignal
48aaecacce check outbound tunnels only for LeaseSet request 2025-02-14 21:53:38 -05:00
orignal
4bb86b6a86 don't request LeaseSet until destination if ready 2025-02-14 18:18:28 -05:00
orignal
0588116489 make token always non-zero 2025-02-14 13:08:22 -05:00
orignal
78a37cc00f changed some log levels 2025-02-12 15:56:22 -05:00
r4sas
fb90b01f6c 2.56.0
Signed-off-by: r4sas <r4sas@i2pmail.org>
2025-02-11 22:15:11 +03:00
orignal
ea55215668 Merge pull request #2154 from rex4539/uninitvar
fix uninitialized variable block.tunnelID
2025-02-11 08:30:28 -05:00
r4sas
58a86fa2dc [build] OpenSSL linking reorder (#2156)
Signed-off-by: r4sas <r4sas@i2pmail.org>
2025-02-10 22:10:06 +00:00
Dimitris Apostolou
8a7c4040b6 fix typo 2025-02-09 14:20:56 +03:00
orignal
ea279ef45a Merge branch 'openssl' of https://github.com/PurpleI2P/i2pd into openssl 2025-02-08 15:51:58 -05:00
orignal
8aa18add4b 2.56.0 2025-02-08 15:51:13 -05:00
orignal
5ff52e6c93 Removed rus.i2p from subscriptions 2025-02-08 07:45:04 -05:00
orignal
ef19a85fc0 use correct OBEP inbound tunnel build 2025-02-03 20:41:51 -05:00
orignal
2ce3145195 Build IRC tunnel through low bandwidth routers 2025-02-02 18:05:46 -05:00
orignal
b8d74dab47 recreate tunnels in random order 2025-02-02 16:49:37 -05:00
orignal
e8f5efd156 peers cleanup time variance 2025-02-01 09:42:44 -05:00
orignal
57aa8b3de8 fixed typo 2025-01-31 12:55:09 -05:00
orignal
972b66f9a5 decline transit tunnel to duplicated router 2025-01-31 11:20:39 -05:00
orignal
eadeea76e7 check congestion and random reject short tunnel build requests 2025-01-30 15:35:04 -05:00
orignal
da7d3c55b0 replaced banana.incognet.io reseed by coconut.incognet.io 2025-01-30 08:21:01 -05:00
Dimitris Apostolou
ff0b6a6a6a fix uninitialized variable block.tunnelID 2025-01-30 13:36:53 +02:00
orignal
60d3e4d963 set ack requested flag after second resend attempt 2025-01-29 19:15:12 -05:00
orignal
adc230acde use m_IsSaving flag for saving RouterInfo 2025-01-29 16:02:31 -05:00
orignal
e4ba07a540 persist local RouterInfo in separate thread using seperate buffer 2025-01-29 13:22:15 -05:00
orignal
93ec5ac5c4 rollback 2025-01-28 21:45:26 -05:00
orignal
774c606b09 don't wait for completion explicitly 2025-01-28 21:30:48 -05:00
orignal
1bff42042d check if saving if complete 2025-01-28 21:22:36 -05:00
orignal
daeb177579 save updated local RouterInfo in separate thread 2025-01-28 20:49:36 -05:00
orignal
5d7a062f1b std::mt19937 for random. Congestion update timer variance 2025-01-28 14:47:22 -05:00
orignal
35f7bd5127 don't delete actively use profile. Last persist time 2025-01-28 14:09:25 -05:00
orignal
d411da451a Merge pull request #2152 from rex4539/typo
fix typo
2025-01-27 22:48:40 -05:00
orignal
45bab06f37 cleanup cached addresses 2025-01-27 18:30:22 -05:00
Dimitris Apostolou
588855c6a7 fix typo 2025-01-27 23:04:23 +02:00
orignal
c3fa0ae8cc cache full addresses in memory when requested or received 2025-01-27 13:24:37 -05:00
orignal
bf85a69a2f min peer test version 0.9.62 2025-01-26 17:49:24 -05:00
orignal
72ff0b9fbb shorter ack request interval 2025-01-25 09:02:18 -05:00
orignal
b9c9988ff4 smaller request timeout if sent directly 2025-01-24 13:56:33 -05:00
orignal
1bb5ad22af use std::mt19937 for random. Peer test interval variance 2025-01-23 19:20:20 -05:00
orignal
4fa5cec0dc fixed termination deadlock if SAM session is active 2025-01-23 14:12:52 -05:00
orignal
1e7254dfaa don't delete router's buffer if an update received or connecting 2025-01-22 13:25:11 -05:00
orignal
ca0818af7e drop buffer upon peer disconnect 2025-01-22 12:00:37 -05:00
orignal
b3d09513b8 fixed race condition 2025-01-21 19:38:07 -05:00
orignal
2857a163e9 check last endpoint only if profile is in memory. postpone profile update when connected 2025-01-21 15:03:25 -05:00
orignal
cba7e5350d drop router's buffer after a while without updates 2025-01-20 18:17:41 -05:00
orignal
29a5effabb use std::mt19937 for random numbers in netdb 2025-01-20 13:27:40 -05:00
orignal
39e07ac265 don't load router profile in NTCP2 or SSU2 thread when check for duplicates 2025-01-20 11:58:33 -05:00
orignal
57986bd348 postpone updating router profile after tunnel build. Check profiles only in memory 2025-01-19 19:16:34 -05:00
orignal
5e301937f2 use pointer to whole struct instead publicKey for buffer 2025-01-19 15:22:46 -05:00
orignal
4edde333ad don't drop router buffer if connected or being updated 2025-01-19 11:47:32 -05:00
orignal
c600b834e3 postpone reading from file and updating router profile 2025-01-18 18:26:16 -05:00
orignal
b6319d78bf don't delete buffer of connected routers 2025-01-16 19:06:33 -05:00
orignal
e4fc2789fe Merge pull request #2149 from rex4539/var
Fix uninitialized variables
2025-01-16 14:21:18 -05:00
Dimitris Apostolou
4c5a1e064d Fix uninitialized variables 2025-01-16 17:54:38 +02:00
orignal
4bb82110ab don't create EVP_CIPHER_CTX for each ChaCha20 2025-01-15 21:13:50 -05:00
orignal
8c555fe592 copy fragment faster 2025-01-14 13:30:47 -05:00
orignal
5f1c599f81 fixed warning 2025-01-13 21:37:40 -05:00
orignal
f2b5606583 store fragments inside m_OutOfSequenceFragments 2025-01-13 20:36:27 -05:00
orignal
08a680b53d use std::string_view instead const std::string& 2025-01-12 18:36:35 -05:00
orignal
634ceceb1c use std::string_view instead const std::string& 2025-01-12 12:23:26 -05:00
orignal
efd8e6e65b use string_view in ExtractString and PutString 2025-01-11 22:34:18 -05:00
orignal
915429bb49 don't drop routing path if no data received 2025-01-10 11:16:07 -05:00
orignal
3e3e0e0a62 shorter ack request interval for datagrams 2025-01-08 20:52:38 -05:00
orignal
c023051fe4 Merge pull request #2147 from Vort/xp_fix2
fix Windows XP build
2025-01-07 16:42:02 -05:00
Vort
0b788de627 fix Windows XP build 2025-01-07 22:15:08 +02:00
orignal
fce4fab071 configurable shared local destination 2025-01-07 13:58:19 -05:00
orignal
3236de0d5a reduce publishing confimation intervals 2025-01-06 19:36:15 -05:00
orignal
18707dd844 don't recalculate and process ranges for every Ack block 2025-01-03 22:04:09 -05:00
orignal
fc16a70f7b use AEADChaCha20Poly1305Encryptor and AEADChaCha20Poly1305Decryptor for test 2025-01-02 18:30:16 -05:00
orignal
619ec5d9c1 fixed AEAD/Chacha20/Poly1305 test 2025-01-02 09:04:57 -05:00
r4sas
1293e122bc [deb] update patch
Signed-off-by: r4sas <r4sas@i2pmail.org>
2024-12-30 21:48:57 +00:00
orignal
24bcc651e0 Fixed typo 2024-12-29 17:44:32 -05:00
orignal
8713974f40 2.55.0 2024-12-29 17:25:54 -05:00
lcharles123
6e639f0e6a Add yggdrasil as weak dependency to i2pd.service 2024-12-26 17:23:42 -03:00
orignal
d48bf33fc5 request time in milliseconds. shorter intervals. interval variance 2024-12-23 17:52:14 -05:00
orignal
0f14f9a302 LeaseSet request timeout in milliseconds 2024-12-23 13:47:38 -05:00
orignal
55708d2a6d reduced LeaseSet lookup timeout 2024-12-22 16:09:58 -05:00
orignal
3bdfa5562b don't send same message twice 2024-12-20 19:42:25 -05:00
orignal
3995448014 fixed possible crash at shutdown 2024-12-19 14:24:26 -05:00
orignal
7497741846 fixed possible crash at shutdown 2024-12-18 14:22:05 -05:00
orignal
36939898fe send tunnel endpoint data to transport session to gateway directly 2024-12-17 20:50:54 -05:00
orignal
b4bcd9914a show next peer and connectivity on transit tunnels page 2024-12-16 19:49:14 -05:00
orignal
833e0a936e fixed build with boost 1.87 2024-12-15 18:27:24 -05:00
orignal
bdc5eaa824 fixed build with boost 1.87 2024-12-15 18:15:21 -05:00
orignal
e76d09e1a1 send tunnel participant data to transport session directly. Implemented TunnelTransportSender 2024-12-15 18:03:31 -05:00
orignal
3264704a23 Handle choked, new RTO and window size calculation 2024-12-14 17:59:51 -05:00
orignal
cec68a2447 rollback 2024-12-11 21:33:16 -05:00
orignal
73ba1afc20 don't create EVP_CIPHER_CTX for each AEAD/Chacha20/Poly1305 message 2024-12-11 18:55:30 -05:00
orignal
dcbe6cfaf2 Update RTO calculation and windows reseting algorithm 2024-12-10 17:49:11 -05:00
orignal
3534b9c499 don't create EVP_CIPHER_CTX for each AEAD/Chacha20/Poly1305 message 2024-12-09 20:59:59 -05:00
orignal
a7021a8283 Merge pull request #2136 from Vort/xp_fix
fix Windows XP build
2024-12-09 08:00:01 -05:00
Vort
946e523554 fix Windows XP build 2024-12-09 02:49:43 +02:00
r4sas
cdd528c51f [gha] disable winxp build
Signed-off-by: r4sas <r4sas@i2pmail.org>
2024-12-08 23:50:16 +03:00
orignal
1a748aebf1 removed depereated section from config 2024-12-08 11:33:30 -05:00
orignal
f23a7f569b pass iv to AES Encrypt/Decrypt directly. aes-test added 2024-12-08 11:08:17 -05:00
orignal
48b62340cc exclude AESNI option 2024-12-07 15:27:23 -05:00
orignal
65da550d19 fix bug with unexpected stream closing 2024-12-07 15:03:18 -05:00
orignal
786da057f2 always use openssl for AES 2024-12-06 20:25:22 -05:00
orignal
097813a6ca Merge pull request #2131 from rex4539/typos
Fix typos
2024-12-06 20:15:25 -05:00
Dimitris Apostolou
226257aa71 Fix typos 2024-12-06 17:11:31 +02:00
orignal
13604ccbb6 Changing the window calculation algorithm, increasing the minimum window size 2024-12-05 22:15:11 -05:00
orignal
e996db03c0 process SessionConfirmed in establisher's thread 2024-12-05 20:57:40 -05:00
orignal
f79a2e81ff calculate data phase keys after verification 2024-12-04 18:36:57 -05:00
orignal
4b1ac7420c Merge branch 'openssl' of https://github.com/PurpleI2P/i2pd into openssl 2024-12-02 19:06:04 -05:00
orignal
e518b92a89 calculate X_I2P_DEST* headers once for series of HTTP requests 2024-12-02 19:05:12 -05:00
R4SAS
1a32ed9088 [gha] winxp: fix option order 2024-12-03 00:44:14 +03:00
R4SAS
b17bbd754a [gha] winxp: forced overwrite files from boost package 2024-12-03 00:40:30 +03:00
orignal
7b0ff2850c close session if x25519 fails 2024-12-01 16:53:08 -05:00
orignal
31ff0ff1cb use weak_ptr for transport session 2024-11-29 21:29:03 -05:00
orignal
fcc70025fd use reference instead naked pointer to tunnel in tunnel gateway 2024-11-29 11:31:13 -05:00
orignal
56145d0f3c bind tunnel gateway to transport session 2024-11-28 21:56:26 -05:00
orignal
8b9f427aa4 handle session handshakes in separate thread 2024-11-27 18:31:58 -05:00
orignal
cc768de8ea iterator through resolver's results 2024-11-25 19:16:42 -05:00
orignal
ffd18baf30 support boost 1.87 2024-11-25 16:00:06 -05:00
orignal
3474538697 replaced boost::asio::io_service by boost::asio:io_context 2024-11-25 10:08:27 -05:00
orignal
5f1b31213f more adequate initial RTT 2024-11-25 08:12:40 -05:00
orignal
6fb3c7c3ba removed dependancy from boost_system for newer compliers 2024-11-23 18:34:33 -05:00
orignal
a248a2a732 Show TBM Queue size 2024-11-20 13:28:01 -05:00
orignal
09ae278306 const GetSize() 2024-11-20 13:27:25 -05:00
orignal
d241e5d5cb handle transit tunnel build messages in separate thread 2024-11-19 19:11:09 -05:00
orignal
b80278421d re-create ECIES session for follow-on packets 2024-11-19 13:00:13 -05:00
orignal
f2596e0187 fixed typo with cleanup timer expiration 2024-11-18 21:34:41 -05:00
orignal
5265dc71e9 drop too old LeaseSet or from future 2024-11-18 15:49:11 -05:00
orignal
a05bb93792 check LeaseSet expiration time 2024-11-18 12:16:05 -05:00
orignal
5d5970bed4 more SYN resend attempts for outgoing stream 2024-11-18 07:59:39 -05:00
orignal
86080b26ae terminate non-established sessions sortly 2024-11-17 22:11:30 -05:00
orignal
5b2d0c579b close stream if SYACK is not acked 2024-11-17 20:51:59 -05:00
orignal
3c4926f377 limit number of incoming ECIES sessions. Don't try to create ECIES session for incoming stream 2024-11-17 18:53:21 -05:00
orignal
391e3b7814 don't schedule send for first SYN reply 2024-11-17 17:29:04 -05:00
orignal
72a39609ed moved all transit tunnels code to TransitTunnels class 2024-11-16 20:56:35 -05:00
orignal
0c5f39ad81 separate class TransitTunnelBuildMsgHandler for tunnel build messages 2024-11-16 16:05:46 -05:00
orignal
3c608ec07c received garlic messages queue for destination 2024-11-13 21:17:58 -05:00
r4sas
ce0461bf86 Destination: cut name for thread name
Signed-off-by: r4sas <r4sas@i2pmail.org>
2024-11-12 21:09:21 +00:00
orignal
ce96f93c80 cleanup tags and ECIES sessions more often 2024-11-11 21:59:19 -05:00
orignal
d88ba768d7 set i2p.streaming.profile=2 for shared local destination 2024-11-11 18:50:53 -05:00
r4sas
574d12298b Destinations: set thread name from tunnel name
Signed-off-by: r4sas <r4sas@i2pmail.org>
2024-11-12 00:36:07 +03:00
orignal
7285caa4f1 if i2p.streaming.maxConcurrentStreams is zeor or negative than unlimited 2024-11-11 14:43:20 -05:00
orignal
2778b092e3 i2p.streaming.maxConcurrentStreams I2CP param 2024-11-11 13:41:27 -05:00
orignal
dbef3fe9d2 temirminate incoming right a way if no remote LeaseSet 2024-11-10 20:15:50 -05:00
orignal
09b7d44dad delete ECIESX25519 session without destination shortly 2024-11-10 19:15:44 -05:00
orignal
a411fff1d9 limit number of incoming streams. don't request LeaseSet for incoming stream 2024-11-10 16:49:44 -05:00
orignal
e574354896 limit received SSU2 packets queue 2024-11-10 11:32:46 -05:00
orignal
0a08383471 check msg size in HandleTunnelBuildResponse 2024-11-10 09:15:23 -05:00
orignal
c5e464a8b5 move tunnel build request/reply code from I2NPProtocol.cpp to Tunnel.cpp 2024-11-09 17:25:43 -05:00
orignal
002d8c7773 removed HidUser's reseed 2024-11-08 15:53:01 -05:00
orignal
32921ead80 move transit tunnel build requests from I2NPProtocol.cpp to TransitTunnel.cpp 2024-11-07 19:00:11 -05:00
orignal
be24a3e336 publish R cap for yggdrasil-only router and U cap for routers through proxy 2024-11-05 19:24:54 -05:00
orignal
d99a7d9b20 allow transit for router behind symmetric NAT or proxy 2024-11-05 15:20:05 -05:00
orignal
2f6bdd1c84 limit last decline time by 1.5 hours. Increased declined recently interval to 5.5 minutes 2024-11-04 18:20:46 -05:00
John
5a4ce66d42 debian/changelog: Add leading space 2024-11-03 21:44:54 +03:00
orignal
76190ea365 don't resend RelayReponse if Alice is older version 2024-11-03 11:25:19 -05:00
orignal
f90386803f Resend RelayResponse if relay tag not found. Send Ack block 2024-11-03 11:03:12 -05:00
orignal
29d77113cc memory pool for x25519 keys 2024-11-02 11:20:23 -04:00
orignal
0d09a8be00 removed own implementation of x25519 2024-11-01 17:53:27 -04:00
orignal
b8d61e04f0 generate x25519 keys more often 2024-11-01 14:46:13 -04:00
orignal
4432c5a2c4 update last activity time after sending peer test or hole punch message 2024-10-31 18:24:02 -04:00
orignal
2419f52af4 fixed potential race condition 2024-10-31 15:01:19 -04:00
orignal
b2a10ac82b don't update found router again in RequestComplete 2024-10-30 22:00:12 -04:00
orignal
0086f8e27a use std::async for address book download 2024-10-29 15:32:06 -04:00
orignal
8a8277edda check for empty URL string 2024-10-29 13:59:21 -04:00
orignal
3f10f6651d use splice if queue is not semi-full 2024-10-29 12:46:14 -04:00
orignal
9bc595a9a2 eliminate extra copy 2024-10-29 08:41:59 -04:00
orignal
f04048717d cleanup messages to send if session was terminated 2024-10-28 21:34:33 -04:00
orignal
361f364966 intermediate queue for transport sessions. use std::list instead std::vector for multiple I2NP messages 2024-10-28 21:15:16 -04:00
orignal
4c90a88b85 eliminate extra copy of I2NP messages list 2024-10-28 21:10:30 -04:00
orignal
23e66671c2 intermediate queue for transport sessions. use std::list instead std::vector for multiple I2NP messages 2024-10-28 20:36:50 -04:00
orignal
ec67f48d85 fixed possible memory leak 2024-10-28 08:46:01 -04:00
orignal
3a229ea65c Merge pull request #2117 from mittwerk/openssl
hardening iterator
2024-10-28 08:39:44 -04:00
orignal
0e8d624d86 move UpdatePacingTime out of loop 2024-10-28 08:38:04 -04:00
mittwerk
8f9874570a hardening iterator 2024-10-28 09:11:09 +02:00
orignal
43939cedf4 random tunnel reject when medium congestion 2024-10-27 22:19:06 -04:00
orignal
4c66608caf random tunnel reject when medium congestion 2024-10-27 21:58:19 -04:00
orignal
ec4fe9a1e6 set congesion cap G if symmetric NAT and ipv4 in only transport 2024-10-27 18:17:28 -04:00
orignal
79e8ccbb5b don't handle PeerTest 1 with same nonce twice 2024-10-27 12:24:22 -04:00
orignal
608056dcd2 don't handle RelayRequest and RelayIntro with same nonce twice 2024-10-27 11:55:10 -04:00
orignal
7461b640e3 reduce CPU usage 2024-10-26 19:26:25 -04:00
orignal
743126b2ad better hole punch expiration intervals 2024-10-26 19:05:08 -04:00
orignal
f611136ea7 resend relay reponnse if remote router >= 0.9.64 2024-10-26 15:30:48 -04:00
orignal
87ae9c4b74 call main thread as i2pd-daemon 2024-10-25 18:40:51 -04:00
orignal
d3630fb2b2 assign name to main thread 2024-10-25 13:25:33 -04:00
orignal
500afe745f use min hole punch interval for connection attempts 2024-10-24 18:49:11 -04:00
orignal
26901e2945 try recently connected SSU2 address if no other transports found 2024-10-23 20:39:00 -04:00
orignal
64bde69967 Merge pull request #2108 from SystemFailureNet/openssl
Please disable LibreSSL workaround when LibreSSL version >= 4.0.0
2024-10-21 21:38:50 -04:00
orignal
ddf30784ec connected recently mutex 2024-10-21 21:22:16 -04:00
orignal
ea14b00d63 save router's endpoint to profile and try to use it next time without requesting introducers 2024-10-21 20:58:09 -04:00
orignal
a24e0eb2dc don't delete unreachable routers if no transports 2024-10-20 16:12:35 -04:00
orignal
0cb677a2c0 don't send peer test msg 6 if remote endpoint is unknown 2024-10-19 09:18:31 -04:00
orignal
e6cbc842bf request new leaseset if all leases are about to expire 2024-10-19 08:45:25 -04:00
orignal
f087654f25 fixed warnings 2024-10-18 20:02:41 -04:00
orignal
10335b90c5 fixed warnings 2024-10-18 19:57:35 -04:00
orignal
8a234f70e6 send a packet to new remote lease in advance if current is about to expire 2024-10-18 15:59:37 -04:00
SystemFailure
f98a310235 Revert LibreSSL workaround when LibreSSL version >= 4.0.0 2024-10-18 13:17:47 +00:00
orignal
1419745a5d recognize symmetric NAT from peer test msg 7 2024-10-17 21:09:37 -04:00
orignal
890fe77b10 update leaseset in destination's thread 2024-10-17 18:15:33 -04:00
orignal
bc9d25ec3b ability post LeaseSet update to destination's thread 2024-10-17 16:20:27 -04:00
orignal
fe71776b6f update LeaseSet if inbound tunnel was restored 2024-10-17 15:44:26 -04:00
orignal
0213f058d1 Send peer test msg 6 with delay if msg 4 was received before msg 5 2024-10-16 21:19:30 -04:00
orignal
0ccf0a6339 use pointer to RouterInfo in SaveUpdated 2024-10-16 17:57:52 -04:00
orignal
e26682f4cb don't try to save invalid router 2024-10-16 15:05:29 -04:00
orignal
8981e406f5 don't delete RouterInfo's buffer right a way 2024-10-16 13:51:48 -04:00
orignal
50d9252ba9 resend HolePunch 3 times or until SessionRequest received 2024-10-16 13:10:21 -04:00
orignal
4f73f60e51 don't create relay response block twice 2024-10-16 12:07:13 -04:00
orignal
d69e957213 rollback 2024-10-16 09:04:35 -04:00
orignal
97fdedfbe3 implement SSU2HolPunchSession 2024-10-16 08:28:25 -04:00
orignal
ec1f41b13c insert multiple packets to the queue using splice 2024-10-15 15:05:18 -04:00
orignal
7104d334fd Do not increase the window size if the speed limit is reached when it is limited 2024-10-14 22:29:55 -04:00
orignal
4e581af3ba plain list of received packets in queue 2024-10-14 21:21:07 -04:00
orignal
48f7131a7d received packets queue 2024-10-14 18:55:41 -04:00
orignal
fbd07a5276 SSU2Session/SSU2OutOfSession split 2024-10-13 19:53:40 -04:00
orignal
8210911bc5 use std::list and splice fr msg queue 2024-10-12 17:51:26 -04:00
orignal
4a5406b803 lock queue's mutex less often 2024-10-11 13:41:37 -04:00
orignal
ab02f722af print non-resolved address to log 2024-10-11 11:27:36 -04:00
orignal
c86e0ec371 lock queue's mutex less often 2024-10-10 20:43:06 -04:00
orignal
ac4c58bbe9 reload cerificate again after re-creation attempt 2024-10-10 15:46:22 -04:00
orignal
23bac4a403 recreate certificate if invalid 2024-10-09 21:40:26 -04:00
orignal
2321a897f5 rollback 2024-10-09 08:48:24 -04:00
orignal
88a5f8b125 use EVP_PKEY for signing 2024-10-08 18:44:46 -04:00
orignal
78847306e9 use EVP_PKEY for family signature verification 2024-10-08 13:44:25 -04:00
orignal
1a6109109a don't sample too small list of eligible introducers 2024-10-06 20:57:35 -04:00
r4sas
905c6debf2 [win32] use boost filesystem for gcc builds
Signed-off-by: r4sas <r4sas@i2pmail.org>
2024-10-06 20:25:55 +03:00
r4sas
d7c4d0ff3e [gha] xp build again
Signed-off-by: r4sas <r4sas@i2pmail.org>
2024-10-06 16:36:52 +03:00
orignal
da3e83138a Merge branch 'openssl' of https://github.com/PurpleI2P/i2pd into openssl 2024-10-06 07:43:57 -04:00
orignal
c6eba73653 Merge branch 'master' of https://github.com/PurpleI2P/i2pd into openssl 2024-10-06 07:43:45 -04:00
orignal
0d224dfc54 2.54.0 2024-10-06 07:42:59 -04:00
r4sas
dc48fb0180 [rpm] try to fix Fedora ELN build
Signed-off-by: r4sas <r4sas@i2pmail.org>
2024-10-06 14:36:30 +03:00
orignal
cc05f9c5d9 2.54.0 2024-10-06 07:34:18 -04:00
r4sas
e4c8cc300d [gha] disable winxp build (it is broken in MSYS2)
Signed-off-by: r4sas <r4sas@i2pmail.org>
2024-10-06 14:28:33 +03:00
orignal
0710f62948 fixed potential race condition 2024-10-04 20:44:58 -04:00
orignal
58245bf121 temporary disable RelayRespond resend from Bob because it might be not acked 2024-10-04 14:06:02 -04:00
orignal
4436c49ccc temporary disable RelayResponse resend through introducer session 2024-10-04 11:30:56 -04:00
orignal
bce9630ff8 try to create new sessions with introducers if existing are about to expire 2024-10-04 08:26:32 -04:00
orignal
7f3a04a72f select random introducer session. don't update creation time 2024-10-03 18:44:09 -04:00
orignal
34f1ba5bd9 don't send invalid local address in RelayRequest 2024-10-02 12:45:12 -04:00
orignal
dc4cd34893 handle immediate ack requsted flag in data message. set it in keep-alive 2024-10-02 08:45:44 -04:00
orignal
1fb45c4b0d don't send HolePunch or PeerTest 5 to unspecified address 2024-10-02 08:27:49 -04:00
orignal
514be6d048 introducer expiration time variance 2024-10-01 21:26:54 -04:00
orignal
8c292727da introducer duration variance 2024-10-01 20:35:46 -04:00
orignal
d5c40bb6be send keep-alive for newly selected introducer session 2024-10-01 18:21:07 -04:00
orignal
eed48c43fd don't change Firewalled status to Unknown if peer test error 2024-10-01 15:29:48 -04:00
orignal
600f36539f don't change ConnIDs of just introduced session. Let Charlie recognize SessionRequest 2024-09-30 22:38:42 -04:00
orignal
e1e530b4a9 never send Ack to HolePunch and PeerTest messages 2024-09-30 18:27:13 -04:00
orignal
98e93468a6 send ack to relay messages 2024-09-30 18:12:42 -04:00
orignal
0f5e8d8424 don't print warning if duplicated nonce or peer test 2024-09-30 14:54:20 -04:00
orignal
d521350588 resend relay response 2024-09-29 22:15:03 -04:00
orignal
237d9474d8 fixed incomplete response 2024-09-29 21:43:47 -04:00
orignal
5466983b36 resend relay messages 2024-09-29 20:57:18 -04:00
orignal
ba41f7107d resend peer test responses 2024-09-29 18:17:49 -04:00
orignal
c2234599cd exclude boost_system from linking because it's headers only now 2024-09-29 17:11:54 -04:00
orignal
6ebb019e15 resend peer test msgs 5 and 6 2024-09-29 15:02:18 -04:00
orignal
15cd4feade move Bob's peer tests from SSU2 session to server 2024-09-28 22:05:25 -04:00
orignal
abbe1fea64 fixed clang build error 2024-09-28 16:20:59 -04:00
orignal
62b811c2c1 use memory pool for SSU2 received packets arrays 2024-09-28 09:49:45 -04:00
orignal
64e4b3871a update introducer's iTag is session to introducer was replaced to new one 2024-09-27 13:32:20 -04:00
orignal
c3a1631319 use weak_ptr for Bob's peer tests and relay tags 2024-09-26 18:38:17 -04:00
orignal
a06cce0aaf eliminate extra copy of vector of SSU2 packets 2024-09-26 15:54:29 -04:00
orignal
75b1c144b4 drop too short follow on SSU2 packets 2024-09-26 08:48:17 -04:00
orignal
32ad4b4858 fixed possible race conditions with m_SessionsByRouterHash 2024-09-25 14:34:52 -04:00
orignal
98669eff4f delete session by hash from table if expired or terminated 2024-09-25 11:13:01 -04:00
orignal
67763248cc add peer test session endpoint to connected recently after msg 6 and 7 2024-09-24 22:06:44 -04:00
orignal
262a803d10 make sure we are done with session before remving it 2024-09-24 20:57:04 -04:00
orignal
0912de5b77 don't connect peer test session. Use weak_ptr for seesions by hash 2024-09-24 20:03:15 -04:00
orignal
edb2ba7107 set address when peer test msg 4 received 2024-09-24 15:20:10 -04:00
orignal
74f0330730 moved SendPeerTest for msgs 5,6,7 to SSU2PeerTestSession 2024-09-24 14:37:27 -04:00
orignal
5cd0248494 set router status to uknown if peer test msg 5 came from recently connected peer 2024-09-23 20:24:33 -04:00
orignal
816771dd00 fixed build for gcc 8-9 2024-09-23 18:18:26 -04:00
orignal
189d7179c0 check if msg 5 was received instead state 2024-09-23 15:34:14 -04:00
orignal
2dfc9003a7 separate and move own peer test to SSU2Server 2024-09-23 14:16:24 -04:00
orignal
9968afc038 check senduseragent for outproxy. Update User-Agent for clearnet 2024-09-22 21:27:09 -04:00
orignal
5073c9637e implement httpproxy.senduseragent 2024-09-22 21:07:44 -04:00
orignal
2c594dc67a moved peer test 5-7 to SSU2PeerTestSession 2024-09-22 20:25:41 -04:00
orignal
11bca5c3cd don't initialize Noise state for peer test sessions 2024-09-22 18:02:12 -04:00
orignal
9d1e526812 separate SSU2PeerTestSession for peer tests msgs 5,6 and 7 2024-09-22 17:22:08 -04:00
orignal
018fa0ec00 added i2p.streaming.maxOutboundSpeed, i2p.streaming.maxInboundSpeed and i2p.streaming.profile to HTTP and SOCKS proxy configs 2024-09-21 19:48:45 -04:00
orignal
f733f0a636 added i2p.streaming.maxOutboundSpeed, i2p.streaming.maxInboundSpeed and i2p.streaming.profile to HTTP and SOCKS proxy configs 2024-09-21 19:04:03 -04:00
orignal
fd2b15fe81 don't drop too old router if low uptime 2024-09-21 17:58:14 -04:00
orignal
c8958d71a2 pick routers with any bandwidth if limited connectivity 2024-09-21 12:59:11 -04:00
orignal
e4962b855f pick first hop based on pool's bandwidth requirements 2024-09-20 10:34:55 -04:00
orignal
9f30499984 implement i2p.streaming.profile to specify high or low bandwidth tunnel pools 2024-09-19 21:16:16 -04:00
orignal
5324197e43 don't change router status if peer test came from recent endpoint 2024-09-19 15:27:04 -04:00
orignal
715e063550 set boost flags before finding 2024-09-19 08:02:37 -04:00
orignal
7ef1fdf634 exclude false position OK peer test if comes from recently connected endpoint 2024-09-18 18:48:18 -04:00
orignal
db19c32381 require minimal boost 1.83 for c++20 2024-09-18 15:42:30 -04:00
orignal
ac1c28cb39 don't send ack in case of lost packet and incoming speed limitation 2024-09-18 15:19:18 -04:00
orignal
2fa4237acd renew connected receintly timestamp, add endpoint to the list if hole punch is being sent 2024-09-18 14:35:59 -04:00
orignal
ae26758170 allocate RouterInfo's buffer from pool 2024-09-18 08:40:22 -04:00
orignal
a723405fb0 check max RouterInfo size 2024-09-18 08:38:29 -04:00
orignal
f20391d460 check if we connected recently to an endpoint before sending peer test 2024-09-17 21:49:23 -04:00
orignal
ca4db7aab2 handle siuatuion if only one lease in remote LeaseSet 2024-09-17 20:56:00 -04:00
orignal
13b2fc3266 drop window size only when lease changes 2024-09-16 19:09:18 -04:00
orignal
d4c1a1c0bb cleanup streaming destination's pools 2024-09-16 13:39:11 -04:00
orignal
d5aca85a35 don't try to generate more ECIES-X25519-AEAD-Ratchet tags if decryption failed 2024-09-12 21:03:46 -04:00
orignal
17d0e59d02 fixed warning 2024-09-12 20:30:26 -04:00
orignal
d20475e3d0 set default i2cp.leaseSetEncType to 0,4 and to 4 for server tunnels 2024-09-12 19:11:13 -04:00
orignal
ebec4d8a5e set default i2cp.leaseSetEncType to 0,4 and to 4 for server tunnels 2024-09-12 18:56:23 -04:00
orignal
cb0801fc16 reduce number of retransmits 2024-09-11 20:54:22 -04:00
R4SAS
a5e9d9c6a3 [gha] winxp build fix 2024-09-11 20:18:05 +03:00
orignal
3d0a1afd64 check if addressbook is enabled 2024-09-11 12:06:55 -04:00
orignal
78ec5b2c6e faster RTT recalculation if bad sample 2024-09-11 11:24:51 -04:00
orignal
272bf7dbc1 terminate NTCP2 session from duplicated router properly 2024-09-10 12:35:36 -04:00
orignal
261acbbd66 recalculate RTT for one way communications 2024-09-10 12:22:42 -04:00
orignal
a65dd218da correct endianess in SendQuickAck 2024-09-10 10:27:26 -04:00
orignal
50d297fa29 check if first packet 2024-09-09 22:37:35 -04:00
orignal
699e17b594 handle plain ack with options 2024-09-09 22:26:03 -04:00
orignal
a91caa6559 Merge branch 'openssl' of https://github.com/PurpleI2P/i2pd into openssl 2024-09-09 18:40:42 -04:00
orignal
07d108bb6f send immediate ack request if no packet being sent 2024-09-09 18:40:06 -04:00
orignal
bcace3fb29 use -std=c++17 2024-09-08 17:53:41 -04:00
orignal
cd648b9b3f use std::atomic<std::shared_ptr<...>> instead boost::shared_ptr if applicable 2024-09-08 16:30:27 -04:00
orignal
ba451eeca5 set congestion cap G immediately if through proxy 2024-09-07 18:01:48 -04:00
orignal
8d1c186665 limited connectivity mode 2024-09-07 16:25:26 -04:00
orignal
d539c9677e don't accept tunnels if connected through proxy 2024-09-06 19:04:07 -04:00
orignal
855fd4d471 eliminate extra parsing of RouterInfo coming as RouterInfo block 2024-09-06 09:49:24 -04:00
orignal
e0af7b077f set max number of tags adequate to max window size 2024-09-05 20:28:40 -04:00
orignal
fde301deaf check for duplicated routers in NTCP2. Insert router into netdb right a way 2024-09-05 19:24:23 -04:00
orignal
9a77c0a4b1 recognize compiler version without gexpr 2024-09-05 07:43:24 -04:00
orignal
306ea2df37 don't apply std::move to prvalue 2024-09-04 14:12:30 -04:00
orignal
cab671e177 use gexpr instead expr 2024-09-04 13:28:34 -04:00
orignal
2ee5af0c06 C++20 for clang >= 16 2024-09-04 13:23:12 -04:00
orignal
911620bcd3 C++20 for clang >= 16 2024-09-04 13:21:13 -04:00
orignal
d1620d70bb consider N routers as low bandwidth 2024-09-03 18:43:25 -04:00
orignal
53db54dafb consider N routers as low bandwidth 2024-09-03 18:36:50 -04:00
orignal
ead1b72886 set half of window on remote lease change 2024-09-03 15:20:33 -04:00
orignal
ae65af07c2 handle immediate ack request 2024-09-03 13:00:04 -04:00
orignal
0046a8b3ec pass const strings for HTTP headers 2024-09-02 21:05:40 -04:00
orignal
06e3a1b57a fixed typo 2024-09-02 18:32:52 -04:00
orignal
cc59003560 removed C++17 check 2024-09-02 18:22:33 -04:00
orignal
a3e0b3710c pass std::string_view to parse 2024-09-02 17:34:15 -04:00
orignal
56b8534e0c gcc7 support 2024-09-02 15:00:25 -04:00
orignal
c21cf0565b removed C++11 support 2024-09-02 10:55:22 -04:00
orignal
9668ea9338 removed C++11 support 2024-09-02 10:18:08 -04:00
orignal
a837e5c502 use rng from pool for lease selection 2024-09-01 16:39:19 -04:00
orignal
bbadbdbfdb Switch to C++17 for Mac OS X 2024-09-01 15:23:14 -04:00
orignal
509c039e2f use Rng from pool if possible 2024-08-31 08:02:56 -04:00
orignal
8cf9cc1a01 removed dependency from boost::filesystem 2024-08-30 18:25:16 -04:00
orignal
a1f40d3048 use __has_include(<filesystem>)) to detect if std::filesystem can be used 2024-08-30 17:56:34 -04:00
orignal
83c0764ed4 link with stdc++fs for g++8 and g++9 2024-08-30 17:54:56 -04:00
orignal
2f5f39aaf2 disable clock_cast iuntil implemented 2024-08-30 17:51:03 -04:00
orignal
5cc15fac31 rollback 2024-08-30 15:03:50 -04:00
orignal
ea3f356856 removed dependency from boost::filesystem 2024-08-30 14:40:44 -04:00
orignal
8189ff0f48 innclude <filesystem> before _cpp_lib_filesystem check 2024-08-30 14:36:56 -04:00
orignal
2679e8cfd8 removed dependency from boost::filesystem 2024-08-30 13:28:01 -04:00
orignal
3679c6aea0 switch to C++17 2024-08-30 11:03:06 -04:00
orignal
604bdf314f improved window size reculculation algorithm 2024-08-29 18:57:14 -04:00
orignal
937809bc0f don't use clock_cast with clang 2024-08-29 15:14:17 -04:00
orignal
d71f3d40fa Merge pull request #2096 from Vort/utf8
implement UTF-8 conversion with WinAPI for Windows platform
2024-08-29 12:42:25 -04:00
Vort
e87ace0c3d implement UTF-8 conversion with WinAPI for Windows platform 2024-08-29 16:52:25 +03:00
orignal
bc48e6881d use boost::filesystem for Win32 and clang 2024-08-28 18:25:42 -04:00
orignal
e957d7bbfb use std::filesystem for windows 2024-08-28 17:52:08 -04:00
orignal
b3aa5ad998 don't link with boost::filesystem in newer versions 2024-08-28 15:12:29 -04:00
orignal
ac876a0cd5 use boost::filesystem for Mac OS X 2024-08-28 13:43:01 -04:00
orignal
d85cb6e30a fixed build for Debian Bookworm 2024-08-28 12:01:19 -04:00
orignal
4a4b76141a C++20 support 2024-08-28 11:34:07 -04:00
orignal
a93043f064 check for __cpp_lib_filesystem 2024-08-27 22:12:13 -04:00
orignal
ae309ca632 use std::filesystem for C++17 2024-08-27 21:49:23 -04:00
orignal
9037e8b2b1 Merge pull request #2095 from PurpleI2P/openssl
recent changes
2024-08-27 21:41:15 -04:00
orignal
3ff79038b5 handle individual NACKs 2024-08-27 15:33:59 -04:00
orignal
da0e527777 termination check timeout variance 2024-08-26 20:57:28 -04:00
orignal
66223792f3 use std::mt19937 instead rand(). termination timeout variance 2024-08-26 19:35:13 -04:00
orignal
a69eade1f4 use pool's rng for random tunnel/remote lease selection 2024-08-25 21:35:33 -04:00
orignal
0992a5124f removed dependency from boost::date_time 2024-08-25 20:18:55 -04:00
orignal
e7423b1ffc save timestamp from epoch instead local time to profiles 2024-08-25 19:07:01 -04:00
orignal
65ceb08290 correct translation of Network status 2024-08-23 22:35:52 -04:00
orignal
879d54fad4 replaced boost::date_time by functions from std 2024-08-23 22:01:08 -04:00
orignal
ff5c76f8f2 don't include expired lease to LeaseSet for I2Cp 2024-08-21 19:21:02 -04:00
orignal
0191e58b05 adjust number of leases in LS2 if expired tunnels 2024-08-21 16:28:19 -04:00
orignal
c43926083e don't include already expired lease to LeaseSet 2024-08-21 14:07:04 -04:00
orignal
bd98f2c3ee max flush interval 2024-08-20 21:21:28 -04:00
orignal
02c52f59cb max flush interval 2024-08-20 20:59:41 -04:00
orignal
fa218d3cf5 reset routing path if session is stuck 2024-08-20 15:50:37 -04:00
orignal
d169b422da drop window if remote lease changes 2024-08-19 19:10:00 -04:00
orignal
7be64dad89 limit number of resent packets at the time 2024-08-19 18:30:49 -04:00
orignal
3720a5fce3 don't select same peer too often 2024-08-19 15:51:56 -04:00
orignal
0df895b6a7 check minimal router version for connected peer selection 2024-08-19 14:39:07 -04:00
orignal
32ab95478e check crypto type and congestion of connected peer for tunnel 2024-08-18 18:34:28 -04:00
orignal
28adb54c0a don't send quick ack for each duplicated packet 2024-08-18 13:33:16 -04:00
orignal
b4fcf76480 fixed warning 2024-08-17 17:52:42 -04:00
orignal
fb8e0e1b5b limit stream's inbound speed 2024-08-17 17:11:28 -04:00
orignal
41dd8b527d reduce number of acks being sent 2024-08-17 08:30:16 -04:00
orignal
7376f7c399 generate max number of tags for follow on tagsets 2024-08-17 08:13:17 -04:00
orignal
d47ae3012a periodic sending ack requests in ECEISX25519 session and dead path detection in I2CP 2024-08-15 13:35:51 -04:00
orignal
09dbe9fc03 check option and packet length 2024-08-14 13:43:24 -04:00
orignal
11328a429d set minimal resend interval. Resend attempt after only Ack 2024-08-14 10:13:35 -04:00
orignal
0c924836cf fixed AEAD verification for LibreSSL 2024-08-13 15:36:13 -04:00
orignal
52a313bb65 force LeaseSet timestamp update if published at the same second 2024-08-12 21:29:05 -04:00
orignal
d75f15104e select only established sessions for peer tests 2024-08-10 14:43:29 -04:00
orignal
b306bf2db9 don't handle connect timer for already terminated session 2024-08-09 08:14:39 -04:00
orignal
349c4e30b6 correct receive ratchet tagsetid calculation 2024-08-06 13:58:21 -04:00
orignal
3c69e0b2af keep sending reverse key until tag received on new tagset 2024-08-05 14:44:10 -04:00
orignal
8e1fb8ca9f send status failure and drop shared routing path if message was not sent 2024-08-02 14:39:04 -04:00
orignal
42782944fb Streaming congestion control improvements. Patch by onon 2024-08-01 13:49:32 -04:00
orignal
efd754eb93 set min pacing time to 250 microseconds 2024-07-31 07:56:40 -04:00
orignal
46c72a7137 Merge pull request #2050 from PurpleI2P/openssl
2.51.0
2024-04-08 18:55:45 -04:00
orignal
0141489d34 Merge pull request #2040 from PurpleI2P/openssl
Recent changes
2024-03-11 07:59:32 -04:00
orignal
612f51ba7f Merge pull request #2028 from PurpleI2P/openssl
Recent changes
2024-02-27 07:59:43 -05:00
orignal
245e6b6efd Merge pull request #1911 from PurpleI2P/openssl
recents changes
2023-04-05 19:20:08 -04:00
orignal
6930106d26 Merge pull request #1886 from PurpleI2P/openssl
recent changes
2023-02-22 15:54:30 -05:00
orignal
9a2f744630 Merge pull request #1827 from PurpleI2P/openssl
recent changes
2022-12-16 14:11:16 -05:00
orignal
4540d22de8 Merge pull request #1825 from PurpleI2P/openssl
recent changes
2022-12-11 18:20:33 -05:00
orignal
6e3aef0b9b Merge pull request #1815 from PurpleI2P/openssl
Recent changes
2022-12-02 08:42:50 -05:00
R4SAS
74b2ba7ae2 Merge pull request #1805 from PurpleI2P/openssl
2.44.0
2022-11-22 20:57:22 +00:00
orignal
d5e1d56fde Merge pull request #1771 from PurpleI2P/openssl
recent changes
2022-06-30 11:59:46 -04:00
orignal
9685754511 Merge pull request #1696 from PurpleI2P/openssl
recent changes
2021-10-15 13:02:32 -04:00
orignal
88145eaf94 Merge pull request #1662 from PurpleI2P/openssl
recent changes
2021-06-16 18:12:21 -04:00
orignal
1b3c3fae89 Merge pull request #1656 from PurpleI2P/openssl
2.38.0
2021-05-17 12:12:26 -04:00
orignal
fde79eecc6 Merge pull request #1641 from PurpleI2P/openssl
2.37.0
2021-03-15 12:36:35 -04:00
orignal
a518320e3b Merge pull request #1638 from PurpleI2P/openssl
recent changes
2021-03-01 18:42:36 -05:00
orignal
f6a09f59a3 Merge pull request #1634 from PurpleI2P/openssl
2.36.0
2021-02-16 10:50:03 -05:00
orignal
a2c28d0837 Merge pull request #1610 from PurpleI2P/openssl
recent chamges
2020-12-30 08:27:57 -05:00
orignal
2ff6f9d346 Merge pull request #1583 from PurpleI2P/openssl
2.35.0
2020-11-30 14:53:45 -05:00
orignal
975d5f44b6 Merge pull request #1565 from PurpleI2P/openssl
recent changes
2020-10-24 18:40:07 -04:00
orignal
260564345a Merge pull request #1556 from PurpleI2P/openssl
recent changes
2020-10-10 21:22:00 -04:00
orignal
305a654a37 Merge pull request #1550 from PurpleI2P/openssl
2.33.0
2020-08-24 13:02:22 -04:00
R4SAS
d510f7e473 Merge branch 'openssl' of https://github.com/PurpleI2P/i2pd 2020-06-02 20:39:55 +03:00
orignal
b992fbab52 Merge pull request #1525 from PurpleI2P/openssl
recent changes
2020-05-30 13:33:45 -04:00
orignal
ddf3774aec Merge pull request #1502 from PurpleI2P/openssl
recent fixes
2020-04-10 10:35:04 -04:00
orignal
814f854c5a Merge pull request #1499 from PurpleI2P/openssl
recent updates
2020-03-30 18:20:07 -04:00
orignal
d3bbdb1011 Merge pull request #1475 from PurpleI2P/openssl
2.30.0
2020-02-25 15:27:02 -05:00
orignal
d6d9f05443 Merge pull request #1456 from PurpleI2P/openssl
recent changes
2020-01-16 14:20:47 -05:00
orignal
13e09e231d Merge pull request #1432 from PurpleI2P/openssl
2.29.0
2019-10-21 11:52:22 -04:00
orignal
7866f644d3 Merge pull request #1413 from PurpleI2P/openssl
2.28.0
2019-08-27 09:48:17 -04:00
orignal
84de3f081f Merge pull request #1384 from PurpleI2P/openssl
2.27.0
2019-07-03 12:42:49 -04:00
orignal
f0725c9b40 Merge pull request #1362 from PurpleI2P/openssl
2.26.0
2019-06-07 10:21:49 -04:00
orignal
f176f1909b Merge pull request #1347 from PurpleI2P/openssl
2.25.0
2019-05-09 11:38:44 -04:00
orignal
d231f944c0 Merge pull request #1313 from PurpleI2P/openssl
2.24.0
2019-03-21 11:46:11 -04:00
orignal
cc48436794 Merge pull request #1301 from PurpleI2P/openssl
recent changes
2019-02-22 10:49:51 -05:00
orignal
ea51fc8410 Merge pull request #1288 from PurpleI2P/openssl
2.23.0
2019-01-23 10:54:06 -05:00
orignal
c212a30d33 Merge pull request #1274 from PurpleI2P/openssl
recent changes
2018-12-02 14:18:48 -05:00
orignal
670bf16cd0 Merge pull request #1269 from PurpleI2P/openssl
2.22.0
2018-11-09 11:12:03 -05:00
133 changed files with 6344 additions and 4107 deletions

View File

@@ -128,8 +128,13 @@ jobs:
cache: true
update: true
- name: Clone MinGW packages repository
run: git clone https://github.com/msys2/MINGW-packages
- name: Clone MinGW packages repository and revert boost to 1.85.0
run: |
git clone https://github.com/msys2/MINGW-packages
cd MINGW-packages
git checkout 4cbb366edf2f268ac3146174b40ce38604646fc5 mingw-w64-boost
cd mingw-w64-boost
sed -i 's/boostorg.jfrog.io\/artifactory\/main/archives.boost.io/' PKGBUILD
# headers
- name: Get headers package version
@@ -205,26 +210,30 @@ jobs:
run: |
cd MINGW-packages/mingw-w64-openssl
gpg --recv-keys D894E2CE8B3D79F5
gpg --recv-keys 216094DFD0CB81EF
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: 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 }}
key: winxp-boost-1.85.0+crt-${{ steps.version-headers.outputs.version }}+ossl-${{ steps.version-openssl.outputs.version }}
# Rebuild package if packages above has changed
- 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: Remove boost packages
run: pacman --noconfirm -R mingw-w64-i686-boost mingw-w64-i686-boost-libs
- name: Install boost package
run: pacman --noconfirm -U MINGW-packages/mingw-w64-boost/mingw-w64-i686-*-any.pkg.tar.zst

102
ChangeLog
View File

@@ -1,6 +1,108 @@
# for this file format description,
# see https://github.com/olivierlacan/keep-a-changelog
## [2.56.0] - 2025-02-11
### Added
- Config params for shared local destination
- AddressBook full addresses cache
- Decline transit tunnel to duplicated router
- Recreate tunnels in random order
### Changed
- Exclude disk operations from SSU2 and NTCP2 threads
- Set minimal version for peer test to 0.9.62
- Send ack requested flag after second SSU2 resend attempt
- Shorter ECIESx25519 ack request interval for datagram and I2CP sessions
- Don't change datagram routing path too often if unidirectional data stream
- Reduce LeaseSet and local RouterInfo publishing confirmation intervals
- Don't delete buffer of connected routers or if an update received
- Smaller RouterInfo request timeout if sent directly
- Persist local RouterInfo in separate thread
- Don't recalculate and process ranges for every SSU2 Ack block
- Reseeds list
### Fixed
- Termination deadlock if SAM session is active
- Race condition at tunnel endpoint
- Inbound tunnel build encryption
## [2.55.0] - 2024-12-30
### Added
- Support boost 1.87
- "i2p.streaming.maxConcurrentStreams" tunnel's param to limit number of simultaneous streams
- Separate thread for tunnel build requests
- Show next peer and connectivity on "Transit tunnels" page
- Tunnel name for local destination thread
- Throttle incoming ECIESx25519 sessions
- Send tunnel data to transport session directly if possible
- Publish 'R' cap for yggdrasil-only routers, and 'U' cap for routers through proxy
- Random tunnel rejection when medium congestion
- Save unreachable router's endpoint to use it next time without introducers
- Recognize symmetric NAT from peer test message 7
- Resend HolePunch and RelayResponse messages
### Changed
- Removed own implementation of AESNI and always use one from openssl
- Renamed main thread to i2pd-daemon
- Set i2p.streaming.profile=2 for shared local destination
- Reduced LeaseSet and RouterInfo lookup timeouts
- Cleanup ECIES sessions and tags more often
- Check LeaseSet expiration time
- Handle NTCP2 session handshakes in separate thread
- Limit last decline time by 1.5 hours in router's profile
- Don't handle RelayRequest and RelayIntro with same nonce twice
- Increased hole punch expiration interval
- Send peer test message 6 with delay if message 4 was received before message 5
- Pre-calculate more x25519 keys for transports in runtime
- Don't request LeaseSet for incoming stream
- Terminate incoming stream right away if no remote LeaseSet
- Handle choked, new RTO and window size calculation and resetting algorithm for streams
### Fixed
- Empty string in addressbook subscriptions
- ECIESx25519 sessions without destination
- Missing RouterInfo buffer in NetDb
- Invalid I2PControl certificate
- Routers disappear from NetDb when offline
- Peer test message 6 sent to unknown endpoint
- Race condition with LeaseSet update
- Excessive CPU usage by streams
- Crash on shutdown
## [2.54.0] - 2024-10-06
### Added
- Maintain recently connected routers list to avoid false-positive peer test
- Limited connectivity mode(through proxy)
- "i2p.streaming.profile" tunnel's param to let tunnel select also low-bandwidth routers
- Limit stream's inbound speed
- Periodic ack requests in ratchets session
- Set congestion cap G immediately if through proxy
- Show tunnel's routers bandwidth caps in web console
- Handle immediate ack requested flag in SSU2 data packets
- Resend and ack peer test and relay messages
- "senduseragent" HTTP proxy's param to pass through user's User-Agent
### Changed
- Exclude 'N' routers from high-bandwidth routers for client tunnels
- C++11 support has been dropped, the minimal requirement is C++17 now, C++20 for some compilers
- Removed dependency from boost::date_time and boost::filesystem
- Set default i2cp.leaseSetEncType to 0,4 and to 4 for server tunnels
- Handle i2cp.inboundlimit and i2cp.outboundlimit params in I2CP
- Publish LeaseSet with new timestamp update if tunnel was replaced in the same second
- Increase max number of generated tags to 800 per tagset
- Routing path expiration by time instead num attempts
- Save timestamp from epoch instead local time to profiles
- Update introducer's iTag if session to introducer was replaced to new one
- RTT, window size and number of NACKs calculation for streaming
- Don't select same peer for tunnel too often
- Use WinApi for data path UTF-8 conversion for Windows
### Fixed
- Jump link crash if address book is disabled
- Race condition if connect through an introducer
- "Date" header in I2PControl response
- Incomplete response from web console
- AEAD verification with LibreSSL
- Number of generated tags and new keys for follow-on tagsets
- Expired leases in LeaseSet
- Attempts to send HolePunch to 0.0.0.0
- Incorrect options size in quick ack streaming packet
- Low bandwidth router appeared as first peer in high-bandwidth client tunnel
## [2.53.1] - 2024-07-29
### Changed
- I2CP performance improvement

View File

@@ -29,7 +29,6 @@ DAEMON_SRC_DIR := daemon
# import source files lists
include filelist.mk
USE_AESNI := $(or $(USE_AESNI),yes)
USE_STATIC := $(or $(USE_STATIC),no)
USE_UPNP := $(or $(USE_UPNP),no)
DEBUG := $(or $(DEBUG),yes)

View File

@@ -1,18 +1,22 @@
CXX = clang++
CXXFLAGS ?= ${CXX_DEBUG} -Wall -Wextra -Wno-unused-parameter -pedantic -Wno-misleading-indentation
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
LDLIBS = -lssl -lcrypto -lz -lpthread -lboost_system -lboost_program_options
## NOTE: NEEDED_CXXFLAGS is here so that custom CXXFLAGS can be specified at build time
## **without** overwriting the CXXFLAGS which we need in order to build.
## For example, when adding 'hardening flags' to the build
## (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.
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
CXXVER := $(shell $(CXX) -dumpversion|cut -c 1-2)
ifeq (${CXXVER}, "4.") # older clang always returned 4.2.1
$(error Compiler too old)
else ifeq (${CXXVER}, ${filter ${CXXVER},16 17 18 19}) # clang 16 - 19
NEEDED_CXXFLAGS = -std=c++20
else
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
LDLIBS = -lcrypto -lssl -lz -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread

View File

@@ -1,8 +1,8 @@
CXX = g++
CXXFLAGS := -Wall -std=c++11
CXXFLAGS := -Wall -std=c++17
INCFLAGS = -I/system/develop/headers
DEFINES = -D_DEFAULT_SOURCE -D_GNU_SOURCE
LDLIBS = -lbe -lbsd -lnetwork -lz -lcrypto -lssl -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread
LDLIBS = -lbe -lbsd -lnetwork -lz -lssl -lcrypto -lboost_system -lboost_program_options -lpthread
ifeq ($(USE_UPNP),yes)
DEFINES += -DUSE_UPNP

View File

@@ -5,20 +5,20 @@ SSLROOT = ${BREWROOT}/opt/openssl@1.1
UPNPROOT = ${BREWROOT}/opt/miniupnpc
CXXFLAGS ?= ${CXX_DEBUG} -Wall -Wno-overloaded-virtual
NEEDED_CXXFLAGS ?= -std=c++11
NEEDED_CXXFLAGS ?= -std=c++17
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
LDLIBS = -lz ${SSLROOT}/lib/libcrypto.a ${SSLROOT}/lib/libssl.a ${BOOSTROOT}/lib/libboost_system.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
LDLIBS = -lz -lssl -lcrypto -lboost_system -lboost_filesystem -lboost_program_options -lpthread
ifeq ($(USE_UPNP),yes)
LDFLAGS += -L${UPNPROOT}/lib
LDLIBS += -lminiupnpc
@@ -30,13 +30,6 @@ ifeq ($(USE_UPNP),yes)
INCFLAGS += -I${UPNPROOT}/include
endif
ifeq ($(USE_AESNI),yes)
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
install -d ${PREFIX}/bin
install -m 755 ${I2PD} ${PREFIX}/bin

View File

@@ -9,24 +9,17 @@ LDFLAGS ?= ${LD_DEBUG}
## -std=c++11. If you want to remove this variable please do so in a way that allows setting
## custom FDLAGS to work at build-time.
# detect proper flag for c++11 support by compilers
# detect proper flag for c++17 support by compilers
CXXVER := $(shell $(CXX) -dumpversion)
ifeq ($(shell expr match $(CXX) 'clang'),5)
NEEDED_CXXFLAGS += -std=c++11
else ifeq ($(shell expr match ${CXXVER} "4\.[0-9][0-9]"),4) # gcc >= 4.10
NEEDED_CXXFLAGS += -std=c++11
else ifeq ($(shell expr match ${CXXVER} "4\.[8-9]"),3) # gcc 4.8 - 4.9
NEEDED_CXXFLAGS += -std=c++11 -D_GLIBCXX_USE_NANOSLEEP=1
else ifeq ($(shell expr match ${CXXVER} "[5-6]"),1) # gcc 5 - 6
NEEDED_CXXFLAGS += -std=c++11
LDLIBS = -latomic
else ifeq ($(shell expr match ${CXXVER} "[7-9]"),1) # gcc 7 - 9
NEEDED_CXXFLAGS += -std=c++17
LDLIBS = -latomic
else ifeq ($(shell expr match ${CXXVER} "1[0-9]"),2) # gcc 10+
# NEEDED_CXXFLAGS += -std=c++20
else ifeq ($(shell expr match ${CXXVER} "[8-9]"),1) # gcc 8 - 9
NEEDED_CXXFLAGS += -std=c++17
LDLIBS = -latomic
LDLIBS = -lboost_system -lstdc++fs
else ifeq ($(shell expr match ${CXXVER} "1[0-2]"),2) # gcc 10 - 12
NEEDED_CXXFLAGS += -std=c++17
else ifeq ($(shell expr match ${CXXVER} "1[3-9]"),2) # gcc 13+
NEEDED_CXXFLAGS += -std=c++20
else # not supported
$(error Compiler too old)
endif
@@ -38,9 +31,6 @@ ifeq ($(USE_STATIC),yes)
# Using 'getaddrinfo' in statically linked applications requires at runtime
# the shared libraries from the glibc version used for linking
LIBDIR := /usr/lib/$(SYS)
LDLIBS += $(LIBDIR)/libboost_system.a
LDLIBS += $(LIBDIR)/libboost_date_time.a
LDLIBS += $(LIBDIR)/libboost_filesystem.a
LDLIBS += $(LIBDIR)/libboost_program_options.a
LDLIBS += $(LIBDIR)/libssl.a
LDLIBS += $(LIBDIR)/libcrypto.a
@@ -50,7 +40,7 @@ ifeq ($(USE_UPNP),yes)
endif
LDLIBS += -lpthread -ldl
else
LDLIBS += -lcrypto -lssl -lz -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread
LDLIBS += -lssl -lcrypto -lz -lboost_program_options -lpthread -latomic
ifeq ($(USE_UPNP),yes)
LDLIBS += -lminiupnpc
endif
@@ -61,13 +51,6 @@ ifeq ($(USE_UPNP),yes)
DEFINES += -DUSE_UPNP
endif
ifeq ($(USE_AESNI),yes)
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
install -d ${PREFIX}/bin
install -m 755 ${I2PD} ${PREFIX}/bin

View File

@@ -7,7 +7,7 @@ CXXFLAGS := $(CXX_DEBUG) -fPIC -msse
INCFLAGS := -I$(DAEMON_SRC_DIR) -IWin32
LDFLAGS := ${LD_DEBUG} -static -fPIC -msse
NEEDED_CXXFLAGS += -std=c++17
NEEDED_CXXFLAGS += -std=c++20
DEFINES += -DWIN32_LEAN_AND_MEAN
# UPNP Support
@@ -16,9 +16,11 @@ ifeq ($(USE_UPNP),yes)
LDLIBS = -lminiupnpc
endif
ifeq ($(USE_WINXP_FLAGS), yes)
DEFINES += -DWINVER=0x0501 -D_WIN32_WINNT=0x0501
endif
LDLIBS += \
$(MINGW_PREFIX)/lib/libboost_system-mt.a \
$(MINGW_PREFIX)/lib/libboost_date_time-mt.a \
$(MINGW_PREFIX)/lib/libboost_filesystem-mt.a \
$(MINGW_PREFIX)/lib/libboost_program_options-mt.a \
$(MINGW_PREFIX)/lib/libssl.a \
@@ -40,16 +42,6 @@ ifeq ($(USE_WIN32_APP), yes)
DAEMON_OBJS += $(patsubst %.rc,obj/%.o,$(DAEMON_RC))
endif
ifeq ($(USE_WINXP_FLAGS), yes)
DEFINES += -DWINVER=0x0501 -D_WIN32_WINNT=0x0501
endif
ifeq ($(USE_AESNI),yes)
NEEDED_CXXFLAGS += -maes
LDFLAGS += -maes
DEFINES += -D__AES__
endif
ifeq ($(USE_ASLR),yes)
LDFLAGS += -Wl,--nxcompat -Wl,--high-entropy-va -Wl,--dynamicbase,--export-all-symbols
endif

View File

@@ -1,5 +1,5 @@
CXX = clang++
CXXFLAGS := ${CXX_DEBUG} -Wall -std=c++11
CXXFLAGS := ${CXX_DEBUG} -Wall -std=c++17
INCFLAGS = -I/usr/local/include
DEFINES := -DMAC_OSX
LDFLAGS := -Wl,-rpath,/usr/local/lib -L/usr/local/lib
@@ -7,9 +7,9 @@ LDFLAGS += -Wl,-dead_strip
LDFLAGS += -Wl,-dead_strip_dylibs
ifeq ($(USE_STATIC),yes)
LDLIBS = -lz /usr/local/lib/libcrypto.a /usr/local/lib/libssl.a /usr/local/lib/libboost_system.a /usr/local/lib/libboost_date_time.a /usr/local/lib/libboost_filesystem.a /usr/local/lib/libboost_program_options.a -lpthread
LDLIBS = -lz /usr/local/lib/libssl.a /usr/local/lib/libcrypto.a /usr/local/lib/libboost_system.a /usr/local/lib/libboost_filesystem.a /usr/local/lib/libboost_program_options.a -lpthread
else
LDLIBS = -lz -lcrypto -lssl -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread
LDLIBS = -lz -lssl -lcrypto -lboost_system -lboost_filesystem -lboost_program_options -lpthread
endif
ifeq ($(USE_UPNP),yes)
@@ -25,9 +25,5 @@ endif
OSARCH = $(shell uname -p)
ifneq ($(OSARCH),powerpc)
ifeq ($(USE_AESNI),yes)
CXXFLAGS += -D__AES__ -maes
else
CXXFLAGS += -msse
endif
CXXFLAGS += -msse
endif

View File

@@ -313,7 +313,7 @@ namespace win32
}
case ID_DATADIR:
{
std::string datadir(i2p::fs::GetUTF8DataDir());
std::string datadir(i2p::fs::GetDataDir());
ShellExecute(NULL, "explore", datadir.c_str(), NULL, NULL, SW_SHOWNORMAL);
return 0;
}
@@ -355,9 +355,7 @@ namespace win32
}
}
}
#if (__cplusplus >= 201703L) // C++ 17 or higher
[[fallthrough]];
#endif
}
case WM_TRAYICON:
{

View File

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

View File

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

View File

@@ -29,7 +29,6 @@ project(
)
# configurable options
option(WITH_AESNI "Use AES-NI instructions set" ON)
option(WITH_HARDENING "Use hardening compiler flags" OFF)
option(WITH_LIBRARY "Build library" ON)
option(WITH_BINARY "Build binary" ON)
@@ -156,20 +155,6 @@ else()
endif()
set(CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS_MINSIZEREL} -ffunction-sections -fdata-sections")
set(CMAKE_EXE_LINKER_FLAGS_MINSIZEREL "-Wl,--gc-sections") # -flto is added from above
# check for c++17 & c++11 support
include(CheckCXXCompilerFlag)
CHECK_CXX_COMPILER_FLAG("-std=c++17" CXX17_SUPPORTED)
CHECK_CXX_COMPILER_FLAG("-std=c++11" CXX11_SUPPORTED)
if(CXX17_SUPPORTED)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17")
elseif(CXX11_SUPPORTED)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
else()
message(SEND_ERROR "C++17 nor C++11 standard not seems to be supported by compiler. Too old version?")
endif()
endif()
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
@@ -199,16 +184,6 @@ if(UNIX)
endif()
endif()
# Note: AES-NI and AVX is available on x86-based CPU's.
# Here also ARM64 implementation, but currently we don't support it.
# MSVC is not supported due to different ASM processing, so we hope OpenSSL has its own checks to run optimized code.
if(WITH_AESNI AND (ARCHITECTURE MATCHES "x86_64" OR ARCHITECTURE MATCHES "i386"))
if(NOT MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -maes")
endif()
add_definitions(-D__AES__)
endif()
if(WITH_ADDRSANITIZER)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address")
@@ -223,6 +198,10 @@ if(WITH_THREADSANITIZER)
endif()
endif()
if (CMAKE_COMPILER_IS_GNUCC AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 10.0 AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 8.0) # gcc 8-9
list(APPEND CMAKE_REQUIRED_LIBRARIES "stdc++fs")
endif()
# Use std::atomic instead of GCC builtins on macOS PowerPC:
# For more information refer to: https://github.com/PurpleI2P/i2pd/issues/1726#issuecomment-1306335111
# This has been fixed in Boost 1.81, nevertheless we retain the setting for the sake of compatibility.
@@ -277,14 +256,14 @@ else()
if(NOT MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC")
endif()
add_definitions(-DBOOST_ATOMIC_DYN_LINK -DBOOST_SYSTEM_DYN_LINK -DBOOST_FILESYSTEM_DYN_LINK -DBOOST_PROGRAM_OPTIONS_DYN_LINK -DBOOST_DATE_TIME_DYN_LINK -DBOOST_REGEX_DYN_LINK)
add_definitions(-DBOOST_ATOMIC_DYN_LINK -DBOOST_SYSTEM_DYN_LINK -DBOOST_FILESYSTEM_DYN_LINK -DBOOST_PROGRAM_OPTIONS_DYN_LINK)
if(WIN32)
set(Boost_USE_STATIC_LIBS OFF)
set(Boost_USE_STATIC_RUNTIME OFF)
endif()
endif()
find_package(Boost REQUIRED COMPONENTS system filesystem program_options date_time OPTIONAL_COMPONENTS atomic)
find_package(Boost REQUIRED COMPONENTS system filesystem program_options)
if(NOT DEFINED Boost_FOUND)
message(SEND_ERROR "Boost is not found, or your boost version was below 1.46. Please download Boost!")
endif()
@@ -312,6 +291,26 @@ if(ZLIB_FOUND)
link_directories(${ZLIB_ROOT}/lib)
endif()
# C++ standard to use, based on compiler and version of boost
if(NOT MSVC)
# check for c++20 & c++17 support
include(CheckCXXCompilerFlag)
if(Boost_VERSION VERSION_GREATER_EQUAL "1.83") # min boost version for c++20
CHECK_CXX_COMPILER_FLAG("-std=c++20" CXX20_SUPPORTED)
endif()
CHECK_CXX_COMPILER_FLAG("-std=c++17" CXX17_SUPPORTED)
if(CXX20_SUPPORTED)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++20")
elseif(CXX17_SUPPORTED)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17")
else()
message(SEND_ERROR "C++20 nor C++17 standard not seems to be supported by compiler. Too old version?")
endif()
endif()
# load includes
include_directories(SYSTEM ${Boost_INCLUDE_DIRS} ${OPENSSL_INCLUDE_DIR} ${ZLIB_INCLUDE_DIR})
@@ -322,9 +321,9 @@ message(STATUS "Compiler vendor : ${CMAKE_CXX_COMPILER_ID}")
message(STATUS "Compiler version : ${CMAKE_CXX_COMPILER_VERSION}")
message(STATUS "Compiler path : ${CMAKE_CXX_COMPILER}")
message(STATUS "Architecture : ${ARCHITECTURE}")
message(STATUS "Compiler flags : ${CMAKE_CXX_FLAGS}")
message(STATUS "Install prefix: : ${CMAKE_INSTALL_PREFIX}")
message(STATUS "Options:")
message(STATUS " AESNI : ${WITH_AESNI}")
message(STATUS " HARDENING : ${WITH_HARDENING}")
message(STATUS " LIBRARY : ${WITH_LIBRARY}")
message(STATUS " BINARY : ${WITH_BINARY}")

View File

@@ -8,7 +8,7 @@ INCLUDE(CheckLibraryExists)
function(check_working_cxx_atomics varname)
set(OLD_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS})
set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -std=c++11")
set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -std=c++17")
CHECK_CXX_SOURCE_COMPILES("
#include <atomic>
std::atomic<int> x;
@@ -25,7 +25,7 @@ endfunction(check_working_cxx_atomics)
function(check_working_cxx_atomics64 varname)
set(OLD_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS})
set(CMAKE_REQUIRED_FLAGS "-std=c++11 ${CMAKE_REQUIRED_FLAGS}")
set(CMAKE_REQUIRED_FLAGS "-std=c++17 ${CMAKE_REQUIRED_FLAGS}")
CHECK_CXX_SOURCE_COMPILES("
#include <atomic>
#include <cstdint>

View File

@@ -59,7 +59,7 @@ get_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH)
# function returns an empty string via _git_dir_var.
#
# Example: Given a path C:/bla/foo/bar and assuming C:/bla/.git exists and
# neither foo nor bar contain a file/directory .git. This wil return
# neither foo nor bar contain a file/directory .git. This will return
# C:/bla/.git
#
function(_git_find_closest_git_dir _start_dir _git_dir_var)

View File

@@ -1,32 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIFgTCCA2mgAwIBAgIETWAY1DANBgkqhkiG9w0BAQ0FADBxMQswCQYDVQQGEwJY
WDELMAkGA1UECAwCWFgxCzAJBgNVBAcMAlhYMR4wHAYDVQQKDBVJMlAgQW5vbnlt
b3VzIE5ldHdvcmsxDDAKBgNVBAsMA0kyUDEaMBgGA1UEAwwRaGlkdXNlcjBAbWFp
bC5pMnAwHhcNMjExMjEzMTU0MDI3WhcNMzExMjExMTU0MDI3WjBxMQswCQYDVQQG
EwJYWDELMAkGA1UECAwCWFgxCzAJBgNVBAcMAlhYMR4wHAYDVQQKDBVJMlAgQW5v
bnltb3VzIE5ldHdvcmsxDDAKBgNVBAsMA0kyUDEaMBgGA1UEAwwRaGlkdXNlcjBA
bWFpbC5pMnAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXnjJ8UQ0f
lHHpfPMiHofBPSuL4sbOJY6fOXwPhSg/h6THh9DS/ZWmJXQ3qRD0glDVtv4/Dr/9
ldGQ5eltF9iCFXCQlMEy2HjQrBKq0nsl7RpYK12cyMaod0kkzCUk9ITLi9CmHM3Z
gQZcmG8TWjFEpDR+idx/QkQt2pcO4vzWlDit3Vh4ivnbX5jGQHbsVjQEMQWxr+pX
dsS+YQpjZ6RBmrooGTPO8QDOOeYLAn0lCjmffc/kzIH9E/p4/O0rOpyhVYbdxUD1
5wkqN9l4yrtxmORG/PudnRQQ0r4TUq8vsxfGY0Euo9IbhgXF2Parel1ZhDxB1WZV
VwWtgLIh9jGA1UMa8SYKnEfp8LWNZ3b3mUUnZb3kMrLk6jGYRWNsHmamhd4mC7AZ
qf/8lOkEIw3bPd3YguCDRVcLui5BwIEZmqXg8uoESxfO/sW3pBrN/8M7MkTex9kN
vjitGDDXvenK27qmNgZxbBlX72yTSfys7XTYTLnxZC8AwdAo2Wz9Z6HhGiPonf2h
vZkc9ZxuE0jFIrsbJra4X7iyjXgi4vV4ARNg/9Ft6F4/OIbECgeDcBQqq4TlT2bZ
EfWVrBbqXoj5vNsLigIkd+AyUNwPYEcB5IFSiiOh98pC7BH3pg0m8U5YBjxe1i+9
EQOOG0Qtx+JigXZHu6bGE0Twy9zy+UzoKQIDAQABoyEwHzAdBgNVHQ4EFgQUGK1b
0DkL6aLalcfBc/Uj/SF08C0wDQYJKoZIhvcNAQENBQADggIBAMpXM82bJDpH1TlH
TvhU3Z7nfZdvEhOQfujaFUYiuNripuEKcFGn948+DvAG0FUN+uNlJoqOVs8D7InD
gWlA9zpqw5Cl5Hij/Wns9QbXuAHJeA23fVUoaM2A6v9ifcIQ1A+rDuRQAo6/64KW
ChTg2e99RBpfGOyqgeh7tLLe0lPPekVpKHFuXabokaKRDuBcVHcUL4tWXe3dcyqa
Ej/PJrrS+nWL0EGZ4q80CEd2LPuDzPxNGCJt/R7ZfadENWajcgcXGceh1QBzozrB
SL/Ya6wF9SrsB7V/r5wX0LM4ZdDaLWbtmUe5Op0h/ZMH25Sa8xAXVz+O9L6sWSoO
FaiYTOvAiyyPz+nsxKa3xYryDHno7eKSt+hGOcaurhxbdZaEFY/CegEc73tCt9xK
e9qF8O/WkDLmixuErw3f5en4IfzGR7p3lJAwW/8WD8C6HS39h/eE7dVZNaWgtQnZ
SgGjgZMTJqTcQ3aZmfuCZefxGFok8w6AIkdbnd1pdMBRjYu8aXgl2hQSB9ZADDE9
R5d3rXi0PkSFLIvsNjVa5KXrZk/tB0Hpfmepq7CufBqjP/LG9TieRoXzLYUKFF74
QRwjP+y7AJ+VDUTpY1NV1P+k+2raubU2bOnLF3zL5DtyoyieGPhyeMMvp0fRIxdg
bSl5VHgPXHNM8mcnndMAuzvl7jEK
-----END CERTIFICATE-----

View File

@@ -2,13 +2,13 @@ Description: Enable UPnP usage in package
Author: r4sas <r4sas@i2pmail.org>
Reviewed-By: r4sas <r4sas@i2pmail.org>
Last-Update: 2022-03-23
Last-Update: 2024-12-30
--- i2pd.orig/Makefile
+++ i2pd/Makefile
@@ -31,7 +31,7 @@ include filelist.mk
@@ -31,7 +31,7 @@ # import source files lists
include filelist.mk
USE_AESNI := $(or $(USE_AESNI),yes)
USE_STATIC := $(or $(USE_STATIC),no)
-USE_UPNP := $(or $(USE_UPNP),no)
+USE_UPNP := $(or $(USE_UPNP),yes)

View File

@@ -2,13 +2,13 @@ Description: Enable UPnP usage in package
Author: r4sas <r4sas@i2pmail.org>
Reviewed-By: r4sas <r4sas@i2pmail.org>
Last-Update: 2022-03-23
Last-Update: 2024-12-30
--- i2pd.orig/Makefile
+++ i2pd/Makefile
@@ -31,7 +31,7 @@ include filelist.mk
@@ -31,7 +31,7 @@ # import source files lists
include filelist.mk
USE_AESNI := $(or $(USE_AESNI),yes)
USE_STATIC := $(or $(USE_STATIC),no)
-USE_UPNP := $(or $(USE_UPNP),no)
+USE_UPNP := $(or $(USE_UPNP),yes)

View File

@@ -243,7 +243,7 @@ verify = true
## Default: reg.i2p at "mainline" I2P Network
# defaulturl = http://shx5vqsw7usdaunyzr2qmes2fq37oumybpudrd4jjj4e4vk4uusa.b32.i2p/hosts.txt
## Optional subscriptions URLs, separated by comma
# subscriptions = http://reg.i2p/hosts.txt,http://identiguy.i2p/hosts.txt,http://stats.i2p/cgi-bin/newhosts.txt,http://rus.i2p/hosts.txt
# subscriptions = http://reg.i2p/hosts.txt,http://identiguy.i2p/hosts.txt,http://stats.i2p/cgi-bin/newhosts.txt
[limits]
## Maximum active transit sessions (default: 5000)
@@ -277,9 +277,3 @@ verify = true
## Save full addresses on disk (default: true)
# addressbook = true
[cpuext]
## Use CPU AES-NI instructions set when work with cryptography when available (default: true)
# aesni = true
## Force usage of CPU instructions set, even if they not found (default: false)
## DO NOT TOUCH that option if you really don't know what are you doing!
# force = false

View File

@@ -2,6 +2,7 @@
Description=I2P Router written in C++
Documentation=man:i2pd(1) https://i2pd.readthedocs.io/en/latest/
After=network.target
Wants=yggdrasil.service
[Service]
User=i2pd

View File

@@ -1,7 +1,7 @@
%define git_hash %(git rev-parse HEAD | cut -c -7)
Name: i2pd-git
Version: 2.53.1
Version: 2.56.0
Release: git%{git_hash}%{?dist}
Summary: I2P router written in C++
Conflicts: i2pd
@@ -24,7 +24,7 @@ BuildRequires: openssl-devel
BuildRequires: miniupnpc-devel
BuildRequires: systemd-units
%if 0%{?fedora} > 40 || 0%{?eln}
%if 0%{?fedora} == 41
BuildRequires: openssl-devel-engine
%endif
@@ -148,6 +148,15 @@ getent passwd i2pd >/dev/null || \
%changelog
* Tue Feb 11 2025 orignal <orignal@i2pmail.org> - 2.56.0
- update to 2.56.0
* Mon Dec 30 2024 orignal <orignal@i2pmail.org> - 2.55.0
- update to 2.55.0
* Sun Oct 6 2024 orignal <orignal@i2pmail.org> - 2.54.0
- update to 2.54.0
* Tue Jul 30 2024 orignal <orignal@i2pmail.org> - 2.53.1
- update to 2.53.1

View File

@@ -1,5 +1,5 @@
Name: i2pd
Version: 2.53.1
Version: 2.56.0
Release: 1%{?dist}
Summary: I2P router written in C++
Conflicts: i2pd-git
@@ -22,7 +22,7 @@ BuildRequires: openssl-devel
BuildRequires: miniupnpc-devel
BuildRequires: systemd-units
%if 0%{?fedora} > 40 || 0%{?eln}
%if 0%{?fedora} == 41
BuildRequires: openssl-devel-engine
%endif
@@ -146,6 +146,15 @@ getent passwd i2pd >/dev/null || \
%changelog
* Tue Feb 11 2025 orignal <orignal@i2pmail.org> - 2.56.0
- update to 2.56.0
* Mon Dec 30 2024 orignal <orignal@i2pmail.org> - 2.55.0
- update to 2.55.0
* Sun Oct 6 2024 orignal <orignal@i2pmail.org> - 2.54.0
- update to 2.54.0
* Tue Jul 30 2024 orignal <orignal@i2pmail.org> - 2.53.1
- update to 2.53.1

View File

@@ -5,6 +5,7 @@ port = 6668
destination = irc.ilita.i2p
destinationport = 6667
keys = irc-keys.dat
i2p.streaming.profile=2
#[IRC-IRC2P]
#type = client

View File

@@ -149,12 +149,10 @@ namespace util
LogPrint(eLogDebug, "FS: Certificates directory: ", certsdir);
bool precomputation; i2p::config::GetOption("precomputation.elgamal", precomputation);
bool aesni; i2p::config::GetOption("cpuext.aesni", aesni);
bool forceCpuExt; i2p::config::GetOption("cpuext.force", forceCpuExt);
bool ssu; i2p::config::GetOption("ssu", ssu);
if (!ssu && i2p::config::IsDefault ("precomputation.elgamal"))
precomputation = false; // we don't elgamal table if no ssu, unless it's specified explicitly
i2p::crypto::InitCrypto (precomputation, aesni, forceCpuExt);
i2p::crypto::InitCrypto (precomputation);
i2p::transport::InitAddressFromIface (); // get address4/6 from interfaces

View File

@@ -701,6 +701,7 @@ namespace http {
{
s << "<b>" << tr("Tunnels") << ":</b><br>\r\n";
s << "<b>" << tr("Queue size") << ":</b> " << i2p::tunnel::tunnels.GetQueueSize () << "<br>\r\n<br>\r\n";
s << "<b>" << tr("TBM Queue size") << ":</b> " << i2p::tunnel::tunnels.GetTBMQueueSize () << "<br>\r\n<br>\r\n";
auto ExplPool = i2p::tunnel::tunnels.GetExploratoryPool ();
@@ -825,7 +826,7 @@ namespace http {
if (i2p::tunnel::tunnels.CountTransitTunnels())
{
s << "<b>" << tr("Transit Tunnels") << ":</b><br>\r\n";
s << "<table><thead><th>&#8658;</th><th>ID</th><th>&#8658;</th><th>" << tr("Amount") << "</th></thead><tbody class=\"tableitem\">";
s << "<table><thead><th>&#8658;</th><th>ID</th><th>&#8658;</th><th>" << tr("Amount") << "</th><th>" << tr("Next") << "</th></thead><tbody class=\"tableitem\">";
for (const auto& it: i2p::tunnel::tunnels.GetTransitTunnels ())
{
if (std::dynamic_pointer_cast<i2p::tunnel::TransitTunnelGateway>(it))
@@ -835,7 +836,7 @@ namespace http {
else
s << "<tr><td>&#8658;</td><td>" << it->GetTunnelID () << "</td><td>&#8658;</td><td>";
ShowTraffic(s, it->GetNumTransmittedBytes ());
s << "</td></tr>\r\n";
s << "</td><td>" << it->GetNextPeerName () << "</td></tr>\r\n";
}
s << "</tbody></table>\r\n";
}
@@ -1480,13 +1481,13 @@ namespace http {
reply.body = content;
m_SendBuffer = reply.to_string();
boost::asio::async_write (*m_Socket, boost::asio::buffer(m_SendBuffer),
boost::asio::async_write (*m_Socket, boost::asio::buffer(m_SendBuffer), boost::asio::transfer_all (),
std::bind (&HTTPConnection::Terminate, shared_from_this (), std::placeholders::_1));
}
HTTPServer::HTTPServer (const std::string& address, int port):
m_IsRunning (false), m_Thread (nullptr), m_Work (m_Service),
m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint (boost::asio::ip::address::from_string(address), port)),
m_IsRunning (false), m_Thread (nullptr), m_Work (m_Service.get_executor ()),
m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint (boost::asio::ip::make_address(address), port)),
m_Hostname(address)
{
}

View File

@@ -83,8 +83,8 @@ namespace http
bool m_IsRunning;
std::unique_ptr<std::thread> m_Thread;
boost::asio::io_service m_Service;
boost::asio::io_service::work m_Work;
boost::asio::io_context m_Service;
boost::asio::executor_work_guard<boost::asio::io_context::executor_type> m_Work;
boost::asio::ip::tcp::acceptor m_Acceptor;
std::string m_Hostname;
};

View File

@@ -8,14 +8,12 @@
#include <stdio.h>
#include <sstream>
#include <iomanip>
#include <openssl/x509.h>
#include <openssl/pem.h>
// Use global placeholders from boost introduced when local_time.hpp is loaded
#define BOOST_BIND_GLOBAL_PLACEHOLDERS
#include <boost/date_time/local_time/local_time.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <boost/lexical_cast.hpp>
@@ -33,7 +31,7 @@ namespace client
{
I2PControlService::I2PControlService (const std::string& address, int port):
m_IsRunning (false), m_Thread (nullptr),
m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(address), port)),
m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::make_address(address), port)),
m_SSLContext (boost::asio::ssl::context::sslv23),
m_ShutdownTimer (m_Service)
{
@@ -47,15 +45,29 @@ namespace client
i2pcp_crt = i2p::fs::DataDirPath(i2pcp_crt);
if (i2pcp_key.at(0) != '/')
i2pcp_key = i2p::fs::DataDirPath(i2pcp_key);
if (!i2p::fs::Exists (i2pcp_crt) || !i2p::fs::Exists (i2pcp_key)) {
if (!i2p::fs::Exists (i2pcp_crt) || !i2p::fs::Exists (i2pcp_key))
{
LogPrint (eLogInfo, "I2PControl: Creating new certificate for control connection");
CreateCertificate (i2pcp_crt.c_str(), i2pcp_key.c_str());
} else {
}
else
LogPrint(eLogDebug, "I2PControl: Using cert from ", i2pcp_crt);
}
m_SSLContext.set_options (boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2 | boost::asio::ssl::context::single_dh_use);
m_SSLContext.use_certificate_file (i2pcp_crt, boost::asio::ssl::context::pem);
m_SSLContext.use_private_key_file (i2pcp_key, boost::asio::ssl::context::pem);
boost::system::error_code ec;
m_SSLContext.use_certificate_file (i2pcp_crt, boost::asio::ssl::context::pem, ec);
if (!ec)
m_SSLContext.use_private_key_file (i2pcp_key, boost::asio::ssl::context::pem, ec);
if (ec)
{
LogPrint (eLogInfo, "I2PControl: Failed to load ceritifcate: ", ec.message (), ". Recreating");
CreateCertificate (i2pcp_crt.c_str(), i2pcp_key.c_str());
m_SSLContext.use_certificate_file (i2pcp_crt, boost::asio::ssl::context::pem, ec);
if (!ec)
m_SSLContext.use_private_key_file (i2pcp_key, boost::asio::ssl::context::pem, ec);
if (ec)
// give up
LogPrint (eLogError, "I2PControl: Can't load certificates");
}
// handlers
m_MethodHandlers["Authenticate"] = &I2PControlService::AuthenticateHandler;
@@ -258,9 +270,9 @@ namespace client
header << "Content-Length: " << boost::lexical_cast<std::string>(len) << "\r\n";
header << "Content-Type: application/json\r\n";
header << "Date: ";
auto facet = new boost::local_time::local_time_facet ("%a, %d %b %Y %H:%M:%S GMT");
header.imbue(std::locale (header.getloc(), facet));
header << boost::posix_time::second_clock::local_time() << "\r\n";
std::time_t t = std::time (nullptr);
std::tm tm = *std::gmtime (&t);
header << std::put_time(&tm, "%a, %d %b %Y %T GMT") << "\r\n";
header << "\r\n";
offset = header.str ().size ();
memcpy (buf->data (), header.str ().c_str (), offset);
@@ -405,7 +417,7 @@ namespace client
X509_NAME_add_entry_by_txt (name, "O", MBSTRING_ASC, (unsigned char *)I2P_CONTROL_CERTIFICATE_ORGANIZATION, -1, -1, 0); // organization
X509_NAME_add_entry_by_txt (name, "CN", MBSTRING_ASC, (unsigned char *)I2P_CONTROL_CERTIFICATE_COMMON_NAME, -1, -1, 0); // common name
X509_set_issuer_name (x509, name); // set issuer to ourselves
X509_sign (x509, pkey, EVP_sha1 ()); // sign
X509_sign (x509, pkey, EVP_sha1 ()); // sign, last param must be NULL for EdDSA
// save cert
if ((f = fopen (crt_path, "wb")) != NULL) {

View File

@@ -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
*
@@ -88,7 +88,7 @@ namespace client
bool m_IsRunning;
std::thread * m_Thread;
boost::asio::io_service m_Service;
boost::asio::io_context m_Service;
boost::asio::ip::tcp::acceptor m_Acceptor;
boost::asio::ssl::context m_SSLContext;
boost::asio::deadline_timer m_ShutdownTimer;

View File

@@ -52,7 +52,7 @@ namespace transport
{
m_IsRunning = true;
LogPrint(eLogInfo, "UPnP: Starting");
m_Service.post (std::bind (&UPnP::Discover, this));
boost::asio::post (m_Service, std::bind (&UPnP::Discover, this));
std::unique_lock<std::mutex> l(m_StartedMutex);
m_Thread.reset (new std::thread (std::bind (&UPnP::Run, this)));
m_Started.wait_for (l, std::chrono::seconds (5)); // 5 seconds maximum
@@ -150,7 +150,7 @@ namespace transport
// UPnP discovered
LogPrint (eLogDebug, "UPnP: ExternalIPAddress is ", m_externalIPAddress);
i2p::context.UpdateAddress (boost::asio::ip::address::from_string (m_externalIPAddress));
i2p::context.UpdateAddress (boost::asio::ip::make_address (m_externalIPAddress));
// port mapping
PortMapping ();
}

View File

@@ -67,7 +67,7 @@ namespace transport
std::unique_ptr<std::thread> m_Thread;
std::condition_variable m_Started;
std::mutex m_StartedMutex;
boost::asio::io_service m_Service;
boost::asio::io_context m_Service;
boost::asio::deadline_timer m_Timer;
bool m_upnpUrlsInitialized = false;
struct UPNPUrls m_upnpUrls;

View File

@@ -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
*
@@ -25,6 +25,7 @@
#include "RouterContext.h"
#include "ClientContext.h"
#include "Transports.h"
#include "util.h"
void handle_signal(int sig)
{
@@ -220,6 +221,7 @@ namespace i2p
void DaemonLinux::run ()
{
i2p::util::SetThreadName ("i2pd-daemon");
while (running)
{
std::this_thread::sleep_for (std::chrono::seconds(1));

18
debian/changelog vendored
View File

@@ -1,3 +1,21 @@
i2pd (2.56.0-1) unstable; urgency=medium
* updated to version 2.56.0/0.9.65
-- orignal <orignal@i2pmail.org> Tue, 11 Feb 2025 16:00:00 +0000
i2pd (2.55.0-1) unstable; urgency=medium
* updated to version 2.55.0
-- orignal <orignal@i2pmail.org> Mon, 30 Dec 2024 16:00:00 +0000
i2pd (2.54.0-1) unstable; urgency=medium
* updated to version 2.54.0/0.9.64
-- orignal <orignal@i2pmail.org> Sun, 6 Oct 2024 16:00:00 +0000
i2pd (2.53.1-1) unstable; urgency=medium
* updated to version 2.53.1

View File

@@ -2,13 +2,13 @@ Description: Enable UPnP usage in package
Author: r4sas <r4sas@i2pmail.org>
Reviewed-By: r4sas <r4sas@i2pmail.org>
Last-Update: 2022-03-23
Last-Update: 2024-12-30
--- i2pd.orig/Makefile
+++ i2pd/Makefile
@@ -31,7 +31,7 @@ include filelist.mk
@@ -31,7 +31,7 @@ # import source files lists
include filelist.mk
USE_AESNI := $(or $(USE_AESNI),yes)
USE_STATIC := $(or $(USE_STATIC),no)
-USE_UPNP := $(or $(USE_UPNP),no)
+USE_UPNP := $(or $(USE_UPNP),yes)

View File

@@ -64,7 +64,7 @@ namespace chinese // language namespace
{"Full cone NAT", "全锥型NAT"},
{"No Descriptors", "无描述符"},
{"Uptime", "运行时间"},
{"Network status", "IPv4 网络状态"},
{"Network status", "网络状态"},
{"Network status v6", "IPv6 网络状态"},
{"Stopping in", "距停止还有:"},
{"Family", "家族"},

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2022, The PurpleI2P Project
* Copyright (c) 2013-2025, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -152,11 +152,11 @@ namespace data
m_BlindedSigType = m_SigType;
}
BlindedPublicKey::BlindedPublicKey (const std::string& b33):
BlindedPublicKey::BlindedPublicKey (std::string_view b33):
m_SigType (0) // 0 means invalid, we can't blind DSA, set it later
{
uint8_t addr[40]; // TODO: define length from b33
size_t l = i2p::data::Base32ToByteStream (b33.c_str (), b33.length (), addr, 40);
size_t l = i2p::data::Base32ToByteStream (b33.data (), b33.length (), addr, 40);
if (l < 32)
{
LogPrint (eLogError, "Blinding: Malformed b33 ", b33);

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2020, The PurpleI2P Project
* Copyright (c) 2013-2025, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -11,6 +11,7 @@
#include <inttypes.h>
#include <string>
#include <string_view>
#include <vector>
#include "Identity.h"
@@ -23,7 +24,7 @@ namespace data
public:
BlindedPublicKey (std::shared_ptr<const IdentityEx> identity, bool clientAuth = false);
BlindedPublicKey (const std::string& b33); // from b33 without .b32.i2p
BlindedPublicKey (std::string_view b33); // from b33 without .b32.i2p
std::string ToB33 () const;
const uint8_t * GetPublicKey () const { return m_PublicKey.data (); };

View File

@@ -1,68 +0,0 @@
/*
* Copyright (c) 2013-2023, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
* See full license text in LICENSE file at top of project tree
*/
#include "CPU.h"
#include "Log.h"
#ifndef bit_AES
#define bit_AES (1 << 25)
#endif
#if defined(__GNUC__) && __GNUC__ < 6 && IS_X86
#include <cpuid.h>
#endif
#ifdef _MSC_VER
#include <intrin.h>
#endif
namespace i2p
{
namespace cpu
{
bool aesni = false;
inline bool cpu_support_aes()
{
#if IS_X86
#if defined(__clang__)
# if (__clang_major__ >= 6)
__builtin_cpu_init();
# endif
return __builtin_cpu_supports("aes");
#elif (defined(__GNUC__) && __GNUC__ >= 6)
__builtin_cpu_init();
return __builtin_cpu_supports("aes");
#elif (defined(__GNUC__) && __GNUC__ < 6)
int cpu_info[4];
bool flag = false;
__cpuid(0, cpu_info[0], cpu_info[1], cpu_info[2], cpu_info[3]);
if (cpu_info[0] >= 0x00000001) {
__cpuid(0x00000001, cpu_info[0], cpu_info[1], cpu_info[2], cpu_info[3]);
flag = ((cpu_info[2] & bit_AES) != 0);
}
return flag;
#elif defined(_MSC_VER)
int cpu_info[4];
__cpuid(cpu_info, 1);
return ((cpu_info[2] & bit_AES) != 0);
#endif
#endif
return false;
}
void Detect(bool AesSwitch, bool force)
{
if ((cpu_support_aes() && AesSwitch) || (AesSwitch && force)) {
aesni = true;
}
LogPrint(eLogInfo, "AESNI ", (aesni ? "enabled" : "disabled"));
}
}
}

View File

@@ -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,20 +21,4 @@
# define IS_X86_64 0
#endif
#if defined(__AES__) && !defined(_MSC_VER) && IS_X86
# define SUPPORTS_AES 1
#else
# define SUPPORTS_AES 0
#endif
namespace i2p
{
namespace cpu
{
extern bool aesni;
void Detect(bool AesSwitch, bool force);
}
}
#endif

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2024, The PurpleI2P Project
* Copyright (c) 2013-2025, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -117,9 +117,14 @@ namespace config {
("httpproxy.latency.max", value<std::string>()->default_value("0"), "HTTP proxy max latency for tunnels")
("httpproxy.outproxy", value<std::string>()->default_value(""), "HTTP proxy upstream out proxy url")
("httpproxy.addresshelper", value<bool>()->default_value(true), "Enable or disable addresshelper")
("httpproxy.senduseragent", value<bool>()->default_value(false), "Pass through user's User-Agent if enabled. Disabled by default")
("httpproxy.i2cp.leaseSetType", value<std::string>()->default_value("3"), "Local destination's LeaseSet type")
("httpproxy.i2cp.leaseSetEncType", value<std::string>()->default_value("0,4"), "Local destination's LeaseSet encryption type")
("httpproxy.i2cp.leaseSetPrivKey", value<std::string>()->default_value(""), "LeaseSet private key")
("httpproxy.i2p.streaming.maxOutboundSpeed", value<std::string>()->default_value("1730000000"), "Max outbound speed of HTTP proxy stream in bytes/sec")
("httpproxy.i2p.streaming.maxInboundSpeed", value<std::string>()->default_value("1730000000"), "Max inbound speed of HTTP proxy stream in bytes/sec")
("httpproxy.i2p.streaming.profile", value<std::string>()->default_value("1"), "HTTP Proxy bandwidth usage profile. 1 - bulk(high), 2- interactive(low)")
;
options_description socksproxy("SOCKS Proxy options");
@@ -144,8 +149,22 @@ namespace config {
("socksproxy.i2cp.leaseSetType", value<std::string>()->default_value("3"), "Local destination's LeaseSet type")
("socksproxy.i2cp.leaseSetEncType", value<std::string>()->default_value("0,4"), "Local destination's LeaseSet encryption type")
("socksproxy.i2cp.leaseSetPrivKey", value<std::string>()->default_value(""), "LeaseSet private key")
("socksproxy.i2p.streaming.maxOutboundSpeed", value<std::string>()->default_value("1730000000"), "Max outbound speed of SOCKS proxy stream in bytes/sec")
("socksproxy.i2p.streaming.maxInboundSpeed", value<std::string>()->default_value("1730000000"), "Max inbound speed of SOCKS proxy stream in bytes/sec")
("socksproxy.i2p.streaming.profile", value<std::string>()->default_value("1"), "SOCKS Proxy bandwidth usage profile. 1 - bulk(high), 2- interactive(low)")
;
options_description shareddest("Shared local destination options");
shareddest.add_options()
("shareddest.inbound.length", value<std::string>()->default_value("3"), "Shared local destination inbound tunnel length")
("shareddest.outbound.length", value<std::string>()->default_value("3"), "Shared local destination outbound tunnel length")
("shareddest.inbound.quantity", value<std::string>()->default_value("3"), "Shared local destination inbound tunnels quantity")
("shareddest.outbound.quantity", value<std::string>()->default_value("3"), "Shared local destination outbound tunnels quantity")
("shareddest.i2cp.leaseSetType", value<std::string>()->default_value("3"), "Shared local destination's LeaseSet type")
("shareddest.i2cp.leaseSetEncType", value<std::string>()->default_value("0,4"), "Shared local destination's LeaseSet encryption type")
("shareddest.i2p.streaming.profile", value<std::string>()->default_value("2"), "Shared local destination bandwidth usage profile. 1 - bulk(high), 2- interactive(low)")
;
options_description sam("SAM bridge options");
sam.add_options()
("sam.enabled", value<bool>()->default_value(true), "Enable or disable SAM Application bridge")
@@ -219,7 +238,7 @@ namespace config {
"https://reseed.onion.im/,"
"https://i2pseed.creativecowpat.net:8443/,"
"https://reseed.i2pgit.org/,"
"https://banana.incognet.io/,"
"https://coconut.incognet.io/,"
"https://reseed-pl.i2pd.xyz/,"
"https://www2.mk16.de/,"
"https://i2p.ghativega.in/,"
@@ -230,7 +249,6 @@ namespace config {
"http://[324:71e:281a:9ed3::ace]:7070/,"
"http://[301:65b9:c7cd:9a36::1]:18801/,"
"http://[320:8936:ec1a:31f1::216]/,"
"http://[306:3834:97b9:a00a::1]/,"
"http://[316:f9e0:f22e:a74f::216]/"
), "Reseed URLs through the Yggdrasil, separated by comma")
;
@@ -308,11 +326,11 @@ namespace config {
("persist.addressbook", value<bool>()->default_value(true), "Persist full addresses (default: true)")
;
options_description cpuext("CPU encryption extensions options");
options_description cpuext("CPU encryption extensions options. Deprecated");
cpuext.add_options()
("cpuext.aesni", bool_switch()->default_value(true), "Use auto detection for AESNI CPU extensions. If false, AESNI will be not used")
("cpuext.aesni", bool_switch()->default_value(true), "Deprecated option")
("cpuext.avx", bool_switch()->default_value(false), "Deprecated option")
("cpuext.force", bool_switch()->default_value(false), "Force usage of CPU extensions. Useful when cpuinfo is not available on virtual machines")
("cpuext.force", bool_switch()->default_value(false), "Deprecated option")
;
options_description meshnets("Meshnet transports options");
@@ -334,6 +352,7 @@ namespace config {
.add(httpserver)
.add(httpproxy)
.add(socksproxy)
.add(shareddest)
.add(sam)
.add(bob)
.add(i2cp)

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2024, The PurpleI2P Project
* Copyright (c) 2013-2025, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -19,6 +19,7 @@
#if OPENSSL_HKDF
#include <openssl/kdf.h>
#endif
#include "CPU.h"
#include "Crypto.h"
#include "Ed25519.h"
#include "I2PEndian.h"
@@ -240,17 +241,12 @@ namespace crypto
// x25519
X25519Keys::X25519Keys ()
{
#if OPENSSL_X25519
m_Ctx = EVP_PKEY_CTX_new_id (NID_X25519, NULL);
m_Pkey = nullptr;
#else
m_Ctx = BN_CTX_new ();
#endif
}
X25519Keys::X25519Keys (const uint8_t * priv, const uint8_t * pub)
{
#if OPENSSL_X25519
m_Pkey = EVP_PKEY_new_raw_private_key (EVP_PKEY_X25519, NULL, priv, 32);
m_Ctx = EVP_PKEY_CTX_new (m_Pkey, NULL);
if (pub)
@@ -260,29 +256,16 @@ namespace crypto
size_t len = 32;
EVP_PKEY_get_raw_public_key (m_Pkey, m_PublicKey, &len);
}
#else
m_Ctx = BN_CTX_new ();
memcpy (m_PrivateKey, priv, 32);
if (pub)
memcpy (m_PublicKey, pub, 32);
else
GetEd25519 ()->ScalarMulB (m_PrivateKey, m_PublicKey, m_Ctx);
#endif
}
X25519Keys::~X25519Keys ()
{
#if OPENSSL_X25519
EVP_PKEY_CTX_free (m_Ctx);
if (m_Pkey) EVP_PKEY_free (m_Pkey);
#else
BN_CTX_free (m_Ctx);
#endif
}
void X25519Keys::GenerateKeys ()
{
#if OPENSSL_X25519
if (m_Pkey)
{
EVP_PKEY_free (m_Pkey);
@@ -294,16 +277,11 @@ namespace crypto
m_Ctx = EVP_PKEY_CTX_new (m_Pkey, NULL); // TODO: do we really need to re-create m_Ctx?
size_t len = 32;
EVP_PKEY_get_raw_public_key (m_Pkey, m_PublicKey, &len);
#else
RAND_bytes (m_PrivateKey, 32);
GetEd25519 ()->ScalarMulB (m_PrivateKey, m_PublicKey, m_Ctx);
#endif
}
bool X25519Keys::Agree (const uint8_t * pub, uint8_t * shared)
{
if (!pub || (pub[31] & 0x80)) return false; // not x25519 key
#if OPENSSL_X25519
EVP_PKEY_derive_init (m_Ctx);
auto pkey = EVP_PKEY_new_raw_public_key (EVP_PKEY_X25519, NULL, pub, 32);
if (!pkey) return false;
@@ -311,25 +289,17 @@ namespace crypto
size_t len = 32;
EVP_PKEY_derive (m_Ctx, shared, &len);
EVP_PKEY_free (pkey);
#else
GetEd25519 ()->ScalarMul (pub, m_PrivateKey, shared, m_Ctx);
#endif
return true;
}
void X25519Keys::GetPrivateKey (uint8_t * priv) const
{
#if OPENSSL_X25519
size_t len = 32;
EVP_PKEY_get_raw_private_key (m_Pkey, priv, &len);
#else
memcpy (priv, m_PrivateKey, 32);
#endif
}
void X25519Keys::SetPrivateKey (const uint8_t * priv, bool calculatePublic)
{
#if OPENSSL_X25519
if (m_Ctx) EVP_PKEY_CTX_free (m_Ctx);
if (m_Pkey) EVP_PKEY_free (m_Pkey);
m_Pkey = EVP_PKEY_new_raw_private_key (EVP_PKEY_X25519, NULL, priv, 32);
@@ -339,11 +309,6 @@ namespace crypto
size_t len = 32;
EVP_PKEY_get_raw_public_key (m_Pkey, m_PublicKey, &len);
}
#else
memcpy (m_PrivateKey, priv, 32);
if (calculatePublic)
GetEd25519 ()->ScalarMulB (m_PrivateKey, m_PublicKey, m_Ctx);
#endif
}
// ElGamal
@@ -477,9 +442,8 @@ namespace crypto
// encrypt
CBCEncryption encryption;
encryption.SetKey (shared);
encryption.SetIV (iv);
encrypted[257] = 0;
encryption.Encrypt (m, 256, encrypted + 258);
encryption.Encrypt (m, 256, iv, encrypted + 258);
EC_POINT_free (p);
BN_CTX_end (ctx);
BN_CTX_free (ctx);
@@ -512,8 +476,7 @@ namespace crypto
uint8_t m[256];
CBCDecryption decryption;
decryption.SetKey (shared);
decryption.SetIV (iv);
decryption.Decrypt (encrypted + 258, 256, m);
decryption.Decrypt (encrypted + 258, 256, iv, m);
// verify and copy
uint8_t hash[32];
SHA256 (m + 33, 222, hash);
@@ -551,440 +514,114 @@ namespace crypto
}
// AES
#if SUPPORTS_AES
#define KeyExpansion256(round0,round1) \
"pshufd $0xff, %%xmm2, %%xmm2 \n" \
"movaps %%xmm1, %%xmm4 \n" \
"pslldq $4, %%xmm4 \n" \
"pxor %%xmm4, %%xmm1 \n" \
"pslldq $4, %%xmm4 \n" \
"pxor %%xmm4, %%xmm1 \n" \
"pslldq $4, %%xmm4 \n" \
"pxor %%xmm4, %%xmm1 \n" \
"pxor %%xmm2, %%xmm1 \n" \
"movaps %%xmm1, "#round0"(%[sched]) \n" \
"aeskeygenassist $0, %%xmm1, %%xmm4 \n" \
"pshufd $0xaa, %%xmm4, %%xmm2 \n" \
"movaps %%xmm3, %%xmm4 \n" \
"pslldq $4, %%xmm4 \n" \
"pxor %%xmm4, %%xmm3 \n" \
"pslldq $4, %%xmm4 \n" \
"pxor %%xmm4, %%xmm3 \n" \
"pslldq $4, %%xmm4 \n" \
"pxor %%xmm4, %%xmm3 \n" \
"pxor %%xmm2, %%xmm3 \n" \
"movaps %%xmm3, "#round1"(%[sched]) \n"
#endif
#if SUPPORTS_AES
void ECBCryptoAESNI::ExpandKey (const AESKey& key)
ECBEncryption::ECBEncryption ()
{
__asm__
(
"movups (%[key]), %%xmm1 \n"
"movups 16(%[key]), %%xmm3 \n"
"movaps %%xmm1, (%[sched]) \n"
"movaps %%xmm3, 16(%[sched]) \n"
"aeskeygenassist $1, %%xmm3, %%xmm2 \n"
KeyExpansion256(32,48)
"aeskeygenassist $2, %%xmm3, %%xmm2 \n"
KeyExpansion256(64,80)
"aeskeygenassist $4, %%xmm3, %%xmm2 \n"
KeyExpansion256(96,112)
"aeskeygenassist $8, %%xmm3, %%xmm2 \n"
KeyExpansion256(128,144)
"aeskeygenassist $16, %%xmm3, %%xmm2 \n"
KeyExpansion256(160,176)
"aeskeygenassist $32, %%xmm3, %%xmm2 \n"
KeyExpansion256(192,208)
"aeskeygenassist $64, %%xmm3, %%xmm2 \n"
// key expansion final
"pshufd $0xff, %%xmm2, %%xmm2 \n"
"movaps %%xmm1, %%xmm4 \n"
"pslldq $4, %%xmm4 \n"
"pxor %%xmm4, %%xmm1 \n"
"pslldq $4, %%xmm4 \n"
"pxor %%xmm4, %%xmm1 \n"
"pslldq $4, %%xmm4 \n"
"pxor %%xmm4, %%xmm1 \n"
"pxor %%xmm2, %%xmm1 \n"
"movups %%xmm1, 224(%[sched]) \n"
: // output
: [key]"r"((const uint8_t *)key), [sched]"r"(GetKeySchedule ()) // input
: "%xmm1", "%xmm2", "%xmm3", "%xmm4", "memory" // clogged
);
m_Ctx = EVP_CIPHER_CTX_new ();
}
#endif
#if SUPPORTS_AES
#define EncryptAES256(sched) \
"pxor (%["#sched"]), %%xmm0 \n" \
"aesenc 16(%["#sched"]), %%xmm0 \n" \
"aesenc 32(%["#sched"]), %%xmm0 \n" \
"aesenc 48(%["#sched"]), %%xmm0 \n" \
"aesenc 64(%["#sched"]), %%xmm0 \n" \
"aesenc 80(%["#sched"]), %%xmm0 \n" \
"aesenc 96(%["#sched"]), %%xmm0 \n" \
"aesenc 112(%["#sched"]), %%xmm0 \n" \
"aesenc 128(%["#sched"]), %%xmm0 \n" \
"aesenc 144(%["#sched"]), %%xmm0 \n" \
"aesenc 160(%["#sched"]), %%xmm0 \n" \
"aesenc 176(%["#sched"]), %%xmm0 \n" \
"aesenc 192(%["#sched"]), %%xmm0 \n" \
"aesenc 208(%["#sched"]), %%xmm0 \n" \
"aesenclast 224(%["#sched"]), %%xmm0 \n"
#endif
void ECBEncryption::Encrypt (const ChipherBlock * in, ChipherBlock * out)
ECBEncryption::~ECBEncryption ()
{
#if SUPPORTS_AES
if(i2p::cpu::aesni)
{
__asm__
(
"movups (%[in]), %%xmm0 \n"
EncryptAES256(sched)
"movups %%xmm0, (%[out]) \n"
:
: [sched]"r"(GetKeySchedule ()), [in]"r"(in), [out]"r"(out)
: "%xmm0", "memory"
);
}
else
#endif
{
AES_encrypt (in->buf, out->buf, &m_Key);
}
if (m_Ctx)
EVP_CIPHER_CTX_free (m_Ctx);
}
void ECBEncryption::Encrypt (const uint8_t * in, uint8_t * out)
{
EVP_EncryptInit_ex (m_Ctx, EVP_aes_256_ecb(), NULL, m_Key, NULL);
EVP_CIPHER_CTX_set_padding (m_Ctx, 0);
int len;
EVP_EncryptUpdate (m_Ctx, out, &len, in, 16);
EVP_EncryptFinal_ex (m_Ctx, out + len, &len);
}
#if SUPPORTS_AES
#define DecryptAES256(sched) \
"pxor 224(%["#sched"]), %%xmm0 \n" \
"aesdec 208(%["#sched"]), %%xmm0 \n" \
"aesdec 192(%["#sched"]), %%xmm0 \n" \
"aesdec 176(%["#sched"]), %%xmm0 \n" \
"aesdec 160(%["#sched"]), %%xmm0 \n" \
"aesdec 144(%["#sched"]), %%xmm0 \n" \
"aesdec 128(%["#sched"]), %%xmm0 \n" \
"aesdec 112(%["#sched"]), %%xmm0 \n" \
"aesdec 96(%["#sched"]), %%xmm0 \n" \
"aesdec 80(%["#sched"]), %%xmm0 \n" \
"aesdec 64(%["#sched"]), %%xmm0 \n" \
"aesdec 48(%["#sched"]), %%xmm0 \n" \
"aesdec 32(%["#sched"]), %%xmm0 \n" \
"aesdec 16(%["#sched"]), %%xmm0 \n" \
"aesdeclast (%["#sched"]), %%xmm0 \n"
#endif
void ECBDecryption::Decrypt (const ChipherBlock * in, ChipherBlock * out)
ECBDecryption::ECBDecryption ()
{
#if SUPPORTS_AES
if(i2p::cpu::aesni)
{
__asm__
(
"movups (%[in]), %%xmm0 \n"
DecryptAES256(sched)
"movups %%xmm0, (%[out]) \n"
:
: [sched]"r"(GetKeySchedule ()), [in]"r"(in), [out]"r"(out)
: "%xmm0", "memory"
);
}
else
#endif
{
AES_decrypt (in->buf, out->buf, &m_Key);
}
m_Ctx = EVP_CIPHER_CTX_new ();
}
ECBDecryption::~ECBDecryption ()
{
if (m_Ctx)
EVP_CIPHER_CTX_free (m_Ctx);
}
void ECBDecryption::Decrypt (const uint8_t * in, uint8_t * out)
{
EVP_DecryptInit_ex (m_Ctx, EVP_aes_256_ecb(), NULL, m_Key, NULL);
EVP_CIPHER_CTX_set_padding (m_Ctx, 0);
int len;
EVP_DecryptUpdate (m_Ctx, out, &len, in, 16);
EVP_DecryptFinal_ex (m_Ctx, out + len, &len);
}
#if SUPPORTS_AES
#define CallAESIMC(offset) \
"movaps "#offset"(%[shed]), %%xmm0 \n" \
"aesimc %%xmm0, %%xmm0 \n" \
"movaps %%xmm0, "#offset"(%[shed]) \n"
#endif
void ECBEncryption::SetKey (const AESKey& key)
{
#if SUPPORTS_AES
if(i2p::cpu::aesni)
{
ExpandKey (key);
}
else
#endif
{
AES_set_encrypt_key (key, 256, &m_Key);
}
CBCEncryption::CBCEncryption ()
{
m_Ctx = EVP_CIPHER_CTX_new ();
}
void ECBDecryption::SetKey (const AESKey& key)
CBCEncryption::~CBCEncryption ()
{
#if SUPPORTS_AES
if(i2p::cpu::aesni)
{
ExpandKey (key); // expand encryption key first
// then invert it using aesimc
__asm__
(
CallAESIMC(16)
CallAESIMC(32)
CallAESIMC(48)
CallAESIMC(64)
CallAESIMC(80)
CallAESIMC(96)
CallAESIMC(112)
CallAESIMC(128)
CallAESIMC(144)
CallAESIMC(160)
CallAESIMC(176)
CallAESIMC(192)
CallAESIMC(208)
:
: [shed]"r"(GetKeySchedule ())
: "%xmm0", "memory"
);
}
else
#endif
{
AES_set_decrypt_key (key, 256, &m_Key);
}
}
void CBCEncryption::Encrypt (int numBlocks, const ChipherBlock * in, ChipherBlock * out)
{
#if SUPPORTS_AES
if(i2p::cpu::aesni)
{
__asm__
(
"movups (%[iv]), %%xmm1 \n"
"1: \n"
"movups (%[in]), %%xmm0 \n"
"pxor %%xmm1, %%xmm0 \n"
EncryptAES256(sched)
"movaps %%xmm0, %%xmm1 \n"
"movups %%xmm0, (%[out]) \n"
"add $16, %[in] \n"
"add $16, %[out] \n"
"dec %[num] \n"
"jnz 1b \n"
"movups %%xmm1, (%[iv]) \n"
:
: [iv]"r"((uint8_t *)m_LastBlock), [sched]"r"(m_ECBEncryption.GetKeySchedule ()),
[in]"r"(in), [out]"r"(out), [num]"r"(numBlocks)
: "%xmm0", "%xmm1", "cc", "memory"
);
}
else
#endif
{
for (int i = 0; i < numBlocks; i++)
{
*m_LastBlock.GetChipherBlock () ^= in[i];
m_ECBEncryption.Encrypt (m_LastBlock.GetChipherBlock (), m_LastBlock.GetChipherBlock ());
out[i] = *m_LastBlock.GetChipherBlock ();
}
}
}
void CBCEncryption::Encrypt (const uint8_t * in, std::size_t len, uint8_t * out)
if (m_Ctx)
EVP_CIPHER_CTX_free (m_Ctx);
}
void CBCEncryption::Encrypt (const uint8_t * in, size_t len, const uint8_t * iv, uint8_t * out)
{
// len/16
int numBlocks = len >> 4;
if (numBlocks > 0)
Encrypt (numBlocks, (const ChipherBlock *)in, (ChipherBlock *)out);
EVP_EncryptInit_ex (m_Ctx, EVP_aes_256_cbc(), NULL, m_Key, iv);
EVP_CIPHER_CTX_set_padding (m_Ctx, 0);
int l;
EVP_EncryptUpdate (m_Ctx, out, &l, in, len);
EVP_EncryptFinal_ex (m_Ctx, out + l, &l);
}
void CBCEncryption::Encrypt (const uint8_t * in, uint8_t * out)
{
#if SUPPORTS_AES
if(i2p::cpu::aesni)
{
__asm__
(
"movups (%[iv]), %%xmm1 \n"
"movups (%[in]), %%xmm0 \n"
"pxor %%xmm1, %%xmm0 \n"
EncryptAES256(sched)
"movups %%xmm0, (%[out]) \n"
"movups %%xmm0, (%[iv]) \n"
:
: [iv]"r"((uint8_t *)m_LastBlock), [sched]"r"(m_ECBEncryption.GetKeySchedule ()),
[in]"r"(in), [out]"r"(out)
: "%xmm0", "%xmm1", "memory"
);
}
else
#endif
Encrypt (1, (const ChipherBlock *)in, (ChipherBlock *)out);
CBCDecryption::CBCDecryption ()
{
m_Ctx = EVP_CIPHER_CTX_new ();
}
void CBCDecryption::Decrypt (int numBlocks, const ChipherBlock * in, ChipherBlock * out)
CBCDecryption::~CBCDecryption ()
{
#if SUPPORTS_AES
if(i2p::cpu::aesni)
{
__asm__
(
"movups (%[iv]), %%xmm1 \n"
"1: \n"
"movups (%[in]), %%xmm0 \n"
"movaps %%xmm0, %%xmm2 \n"
DecryptAES256(sched)
"pxor %%xmm1, %%xmm0 \n"
"movups %%xmm0, (%[out]) \n"
"movaps %%xmm2, %%xmm1 \n"
"add $16, %[in] \n"
"add $16, %[out] \n"
"dec %[num] \n"
"jnz 1b \n"
"movups %%xmm1, (%[iv]) \n"
:
: [iv]"r"((uint8_t *)m_IV), [sched]"r"(m_ECBDecryption.GetKeySchedule ()),
[in]"r"(in), [out]"r"(out), [num]"r"(numBlocks)
: "%xmm0", "%xmm1", "%xmm2", "cc", "memory"
);
}
else
#endif
{
for (int i = 0; i < numBlocks; i++)
{
ChipherBlock tmp = in[i];
m_ECBDecryption.Decrypt (in + i, out + i);
out[i] ^= *m_IV.GetChipherBlock ();
*m_IV.GetChipherBlock () = tmp;
}
}
}
void CBCDecryption::Decrypt (const uint8_t * in, std::size_t len, uint8_t * out)
if (m_Ctx)
EVP_CIPHER_CTX_free (m_Ctx);
}
void CBCDecryption::Decrypt (const uint8_t * in, size_t len, const uint8_t * iv, uint8_t * out)
{
int numBlocks = len >> 4;
if (numBlocks > 0)
Decrypt (numBlocks, (const ChipherBlock *)in, (ChipherBlock *)out);
}
void CBCDecryption::Decrypt (const uint8_t * in, uint8_t * out)
{
#if SUPPORTS_AES
if(i2p::cpu::aesni)
{
__asm__
(
"movups (%[iv]), %%xmm1 \n"
"movups (%[in]), %%xmm0 \n"
"movups %%xmm0, (%[iv]) \n"
DecryptAES256(sched)
"pxor %%xmm1, %%xmm0 \n"
"movups %%xmm0, (%[out]) \n"
:
: [iv]"r"((uint8_t *)m_IV), [sched]"r"(m_ECBDecryption.GetKeySchedule ()),
[in]"r"(in), [out]"r"(out)
: "%xmm0", "%xmm1", "memory"
);
}
else
#endif
Decrypt (1, (const ChipherBlock *)in, (ChipherBlock *)out);
// len/16
EVP_DecryptInit_ex (m_Ctx, EVP_aes_256_cbc(), NULL, m_Key, iv);
EVP_CIPHER_CTX_set_padding (m_Ctx, 0);
int l;
EVP_DecryptUpdate (m_Ctx, out, &l, in, len);
EVP_DecryptFinal_ex (m_Ctx, out + l, &l);
}
void TunnelEncryption::Encrypt (const uint8_t * in, uint8_t * out)
{
#if SUPPORTS_AES
if(i2p::cpu::aesni)
{
__asm__
(
// encrypt IV
"movups (%[in]), %%xmm0 \n"
EncryptAES256(sched_iv)
"movaps %%xmm0, %%xmm1 \n"
// double IV encryption
EncryptAES256(sched_iv)
"movups %%xmm0, (%[out]) \n"
// encrypt data, IV is xmm1
"1: \n"
"add $16, %[in] \n"
"add $16, %[out] \n"
"movups (%[in]), %%xmm0 \n"
"pxor %%xmm1, %%xmm0 \n"
EncryptAES256(sched_l)
"movaps %%xmm0, %%xmm1 \n"
"movups %%xmm0, (%[out]) \n"
"dec %[num] \n"
"jnz 1b \n"
:
: [sched_iv]"r"(m_IVEncryption.GetKeySchedule ()), [sched_l]"r"(m_LayerEncryption.ECB().GetKeySchedule ()),
[in]"r"(in), [out]"r"(out), [num]"r"(63) // 63 blocks = 1008 bytes
: "%xmm0", "%xmm1", "cc", "memory"
);
}
else
#endif
{
m_IVEncryption.Encrypt ((const ChipherBlock *)in, (ChipherBlock *)out); // iv
m_LayerEncryption.SetIV (out);
m_LayerEncryption.Encrypt (in + 16, i2p::tunnel::TUNNEL_DATA_ENCRYPTED_SIZE, out + 16); // data
m_IVEncryption.Encrypt ((ChipherBlock *)out, (ChipherBlock *)out); // double iv
}
uint8_t iv[16];
m_IVEncryption.Encrypt (in, iv); // iv
m_LayerEncryption.Encrypt (in + 16, i2p::tunnel::TUNNEL_DATA_ENCRYPTED_SIZE, iv, out + 16); // data
m_IVEncryption.Encrypt (iv, out); // double iv
}
void TunnelDecryption::Decrypt (const uint8_t * in, uint8_t * out)
{
#if SUPPORTS_AES
if(i2p::cpu::aesni)
{
__asm__
(
// decrypt IV
"movups (%[in]), %%xmm0 \n"
DecryptAES256(sched_iv)
"movaps %%xmm0, %%xmm1 \n"
// double IV encryption
DecryptAES256(sched_iv)
"movups %%xmm0, (%[out]) \n"
// decrypt data, IV is xmm1
"1: \n"
"add $16, %[in] \n"
"add $16, %[out] \n"
"movups (%[in]), %%xmm0 \n"
"movaps %%xmm0, %%xmm2 \n"
DecryptAES256(sched_l)
"pxor %%xmm1, %%xmm0 \n"
"movups %%xmm0, (%[out]) \n"
"movaps %%xmm2, %%xmm1 \n"
"dec %[num] \n"
"jnz 1b \n"
:
: [sched_iv]"r"(m_IVDecryption.GetKeySchedule ()), [sched_l]"r"(m_LayerDecryption.ECB().GetKeySchedule ()),
[in]"r"(in), [out]"r"(out), [num]"r"(63) // 63 blocks = 1008 bytes
: "%xmm0", "%xmm1", "%xmm2", "cc", "memory"
);
}
else
#endif
{
m_IVDecryption.Decrypt ((const ChipherBlock *)in, (ChipherBlock *)out); // iv
m_LayerDecryption.SetIV (out);
m_LayerDecryption.Decrypt (in + 16, i2p::tunnel::TUNNEL_DATA_ENCRYPTED_SIZE, out + 16); // data
m_IVDecryption.Decrypt ((ChipherBlock *)out, (ChipherBlock *)out); // double iv
}
uint8_t iv[16];
m_IVDecryption.Decrypt (in, iv); // iv
m_LayerDecryption.Decrypt (in + 16, i2p::tunnel::TUNNEL_DATA_ENCRYPTED_SIZE, iv, out + 16); // data
m_IVDecryption.Decrypt (iv, out); // double iv
}
// AEAD/ChaCha20/Poly1305
bool AEADChaCha20Poly1305 (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen, const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len, bool encrypt)
static bool AEADChaCha20Poly1305 (EVP_CIPHER_CTX * ctx, const uint8_t * msg, size_t msgLen,
const uint8_t * ad, size_t adLen, const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len, bool encrypt)
{
if (len < msgLen) return false;
if (!ctx || len < msgLen) return false;
if (encrypt && len < msgLen + 16) return false;
bool ret = true;
int outlen = 0;
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new ();
if (encrypt)
{
EVP_EncryptInit_ex(ctx, EVP_chacha20_poly1305(), 0, 0, 0);
@@ -997,6 +634,15 @@ namespace crypto
}
else
{
#if defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x4000000fL
std::vector<uint8_t> m(msgLen + 16);
if (msg == buf)
{
// we have to use different buffers otherwise verification fails
memcpy (m.data (), msg, msgLen + 16);
msg = m.data ();
}
#endif
EVP_DecryptInit_ex(ctx, EVP_chacha20_poly1305(), 0, 0, 0);
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, 12, 0);
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, 16, (uint8_t *)(msg + msgLen));
@@ -1005,38 +651,100 @@ namespace crypto
EVP_DecryptUpdate(ctx, buf, &outlen, msg, msgLen);
ret = EVP_DecryptFinal_ex(ctx, buf + outlen, &outlen) > 0;
}
return ret;
}
bool AEADChaCha20Poly1305 (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen,
const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len, bool encrypt)
{
EVP_CIPHER_CTX * ctx = EVP_CIPHER_CTX_new ();
auto ret = AEADChaCha20Poly1305 (ctx, msg, msgLen, ad, adLen, key, nonce, buf, len, encrypt);
EVP_CIPHER_CTX_free (ctx);
return ret;
}
void AEADChaCha20Poly1305Encrypt (const std::vector<std::pair<uint8_t *, size_t> >& bufs, const uint8_t * key, const uint8_t * nonce, uint8_t * mac)
AEADChaCha20Poly1305Encryptor::AEADChaCha20Poly1305Encryptor ()
{
m_Ctx = EVP_CIPHER_CTX_new ();
}
AEADChaCha20Poly1305Encryptor::~AEADChaCha20Poly1305Encryptor ()
{
if (m_Ctx)
EVP_CIPHER_CTX_free (m_Ctx);
}
bool AEADChaCha20Poly1305Encryptor::Encrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen,
const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len)
{
return AEADChaCha20Poly1305 (m_Ctx, msg, msgLen, ad, adLen, key, nonce, buf, len, true);
}
void AEADChaCha20Poly1305Encryptor::Encrypt (const std::vector<std::pair<uint8_t *, size_t> >& bufs,
const uint8_t * key, const uint8_t * nonce, uint8_t * mac)
{
if (bufs.empty ()) return;
int outlen = 0;
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new ();
EVP_EncryptInit_ex(ctx, EVP_chacha20_poly1305(), 0, 0, 0);
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, 12, 0);
EVP_EncryptInit_ex(ctx, NULL, NULL, key, nonce);
EVP_EncryptInit_ex(m_Ctx, EVP_chacha20_poly1305(), 0, 0, 0);
EVP_CIPHER_CTX_ctrl(m_Ctx, EVP_CTRL_AEAD_SET_IVLEN, 12, 0);
EVP_EncryptInit_ex(m_Ctx, NULL, NULL, key, nonce);
for (const auto& it: bufs)
EVP_EncryptUpdate(ctx, it.first, &outlen, it.first, it.second);
EVP_EncryptFinal_ex(ctx, NULL, &outlen);
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, 16, mac);
EVP_CIPHER_CTX_free (ctx);
EVP_EncryptUpdate(m_Ctx, it.first, &outlen, it.first, it.second);
EVP_EncryptFinal_ex(m_Ctx, NULL, &outlen);
EVP_CIPHER_CTX_ctrl(m_Ctx, EVP_CTRL_AEAD_GET_TAG, 16, mac);
}
AEADChaCha20Poly1305Decryptor::AEADChaCha20Poly1305Decryptor ()
{
m_Ctx = EVP_CIPHER_CTX_new ();
}
AEADChaCha20Poly1305Decryptor::~AEADChaCha20Poly1305Decryptor ()
{
if (m_Ctx)
EVP_CIPHER_CTX_free (m_Ctx);
}
bool AEADChaCha20Poly1305Decryptor::Decrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen,
const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len)
{
return AEADChaCha20Poly1305 (m_Ctx, msg, msgLen, ad, adLen, key, nonce, buf, len, false);
}
void ChaCha20 (const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out)
static void ChaCha20 (EVP_CIPHER_CTX *ctx, const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out)
{
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new ();
uint32_t iv[4];
iv[0] = htole32 (1); memcpy (iv + 1, nonce, 12); // counter | nonce
EVP_EncryptInit_ex(ctx, EVP_chacha20 (), NULL, key, (const uint8_t *)iv);
int outlen = 0;
EVP_EncryptUpdate(ctx, out, &outlen, msg, msgLen);
EVP_EncryptFinal_ex(ctx, NULL, &outlen);
}
void ChaCha20 (const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out)
{
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new ();
ChaCha20 (ctx, msg, msgLen, key, nonce, out);
EVP_CIPHER_CTX_free (ctx);
}
ChaCha20Context::ChaCha20Context ()
{
m_Ctx = EVP_CIPHER_CTX_new ();
}
ChaCha20Context::~ChaCha20Context ()
{
if (m_Ctx)
EVP_CIPHER_CTX_free (m_Ctx);
}
void ChaCha20Context::operator ()(const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out)
{
ChaCha20 (m_Ctx, msg, msgLen, key, nonce, out);
}
void HKDF (const uint8_t * salt, const uint8_t * key, size_t keyLen, const std::string& info,
uint8_t * out, size_t outLen)
{
@@ -1184,9 +892,8 @@ namespace crypto
}
}*/
void InitCrypto (bool precomputation, bool aesni, bool force)
void InitCrypto (bool precomputation)
{
i2p::cpu::Detect (aesni, force);
/* auto numLocks = CRYPTO_num_locks();
for (int i = 0; i < numLocks; i++)
m_OpenSSLMutexes.emplace_back (new std::mutex);

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2024, The PurpleI2P Project
* Copyright (c) 2013-2025, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -25,13 +25,11 @@
#include "Base.h"
#include "Tag.h"
#include "CPU.h"
// recognize openssl version and features
#if (OPENSSL_VERSION_NUMBER >= 0x010101000) // 1.1.1
# define OPENSSL_HKDF 1
# define OPENSSL_EDDSA 1
# define OPENSSL_X25519 1
# if (!defined(LIBRESSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER != 0x030000000)) // 3.0.0, regression in SipHash, not implemented in LibreSSL
# define OPENSSL_SIPHASH 1
# endif
@@ -70,13 +68,8 @@ namespace crypto
private:
uint8_t m_PublicKey[32];
#if OPENSSL_X25519
EVP_PKEY_CTX * m_Ctx;
EVP_PKEY * m_Pkey;
#else
BN_CTX * m_Ctx;
uint8_t m_PrivateKey[32];
#endif
bool m_IsElligatorIneligible = false; // true if definitely ineligible
};
@@ -91,142 +84,70 @@ namespace crypto
void GenerateECIESKeyPair (const EC_GROUP * curve, BIGNUM *& priv, EC_POINT *& pub);
// AES
struct ChipherBlock
{
uint8_t buf[16];
void operator^=(const ChipherBlock& other) // XOR
{
if (!(((size_t)buf | (size_t)other.buf) & 0x03)) // multiple of 4 ?
{
for (int i = 0; i < 4; i++)
reinterpret_cast<uint32_t *>(buf)[i] ^= reinterpret_cast<const uint32_t *>(other.buf)[i];
}
else
{
for (int i = 0; i < 16; i++)
buf[i] ^= other.buf[i];
}
}
};
typedef i2p::data::Tag<32> AESKey;
template<size_t sz>
class AESAlignedBuffer // 16 bytes alignment
{
public:
AESAlignedBuffer ()
{
m_Buf = m_UnalignedBuffer;
uint8_t rem = ((size_t)m_Buf) & 0x0f;
if (rem)
m_Buf += (16 - rem);
}
operator uint8_t * () { return m_Buf; };
operator const uint8_t * () const { return m_Buf; };
ChipherBlock * GetChipherBlock () { return (ChipherBlock *)m_Buf; };
const ChipherBlock * GetChipherBlock () const { return (const ChipherBlock *)m_Buf; };
private:
uint8_t m_UnalignedBuffer[sz + 15]; // up to 15 bytes alignment
uint8_t * m_Buf;
};
#if SUPPORTS_AES
class ECBCryptoAESNI
{
public:
uint8_t * GetKeySchedule () { return m_KeySchedule; };
protected:
void ExpandKey (const AESKey& key);
private:
AESAlignedBuffer<240> m_KeySchedule; // 14 rounds for AES-256, 240 bytes
};
#endif
#if SUPPORTS_AES
class ECBEncryption: public ECBCryptoAESNI
#else
class ECBEncryption
#endif
{
public:
void SetKey (const AESKey& key);
ECBEncryption ();
~ECBEncryption ();
void SetKey (const uint8_t * key) { m_Key = key; };
void Encrypt(const uint8_t * in, uint8_t * out);
void Encrypt(const ChipherBlock * in, ChipherBlock * out);
private:
private:
AES_KEY m_Key;
AESKey m_Key;
EVP_CIPHER_CTX * m_Ctx;
};
#if SUPPORTS_AES
class ECBDecryption: public ECBCryptoAESNI
#else
class ECBDecryption
#endif
{
public:
void SetKey (const AESKey& key);
void Decrypt (const ChipherBlock * in, ChipherBlock * out);
ECBDecryption ();
~ECBDecryption ();
void SetKey (const uint8_t * key) { m_Key = key; };
void Decrypt (const uint8_t * in, uint8_t * out);
private:
AES_KEY m_Key;
AESKey m_Key;
EVP_CIPHER_CTX * m_Ctx;
};
class CBCEncryption
{
public:
CBCEncryption () { memset ((uint8_t *)m_LastBlock, 0, 16); };
void SetKey (const AESKey& key) { m_ECBEncryption.SetKey (key); }; // 32 bytes
void SetIV (const uint8_t * iv) { memcpy ((uint8_t *)m_LastBlock, iv, 16); }; // 16 bytes
void GetIV (uint8_t * iv) const { memcpy (iv, (const uint8_t *)m_LastBlock, 16); };
void Encrypt (int numBlocks, const ChipherBlock * in, ChipherBlock * out);
void Encrypt (const uint8_t * in, std::size_t len, uint8_t * out);
void Encrypt (const uint8_t * in, uint8_t * out); // one block
ECBEncryption & ECB() { return m_ECBEncryption; }
CBCEncryption ();
~CBCEncryption ();
void SetKey (const uint8_t * key) { m_Key = key; }; // 32 bytes
void Encrypt (const uint8_t * in, size_t len, const uint8_t * iv, uint8_t * out);
private:
AESAlignedBuffer<16> m_LastBlock;
ECBEncryption m_ECBEncryption;
AESKey m_Key;
EVP_CIPHER_CTX * m_Ctx;
};
class CBCDecryption
{
public:
CBCDecryption () { memset ((uint8_t *)m_IV, 0, 16); };
void SetKey (const AESKey& key) { m_ECBDecryption.SetKey (key); }; // 32 bytes
void SetIV (const uint8_t * iv) { memcpy ((uint8_t *)m_IV, iv, 16); }; // 16 bytes
void GetIV (uint8_t * iv) const { memcpy (iv, (const uint8_t *)m_IV, 16); };
void Decrypt (int numBlocks, const ChipherBlock * in, ChipherBlock * out);
void Decrypt (const uint8_t * in, std::size_t len, uint8_t * out);
void Decrypt (const uint8_t * in, uint8_t * out); // one block
ECBDecryption & ECB() { return m_ECBDecryption; }
CBCDecryption ();
~CBCDecryption ();
void SetKey (const uint8_t * key) { m_Key = key; }; // 32 bytes
void Decrypt (const uint8_t * in, size_t len, const uint8_t * iv, uint8_t * out);
private:
AESAlignedBuffer<16> m_IV;
ECBDecryption m_ECBDecryption;
AESKey m_Key;
EVP_CIPHER_CTX * m_Ctx;
};
class TunnelEncryption // with double IV encryption
@@ -266,13 +187,58 @@ namespace crypto
};
// AEAD/ChaCha20/Poly1305
bool AEADChaCha20Poly1305 (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen, const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len, bool encrypt); // msgLen is len without tag
void AEADChaCha20Poly1305Encrypt (const std::vector<std::pair<uint8_t *, size_t> >& bufs, const uint8_t * key, const uint8_t * nonce, uint8_t * mac); // encrypt multiple buffers with zero ad
class AEADChaCha20Poly1305Encryptor
{
public:
AEADChaCha20Poly1305Encryptor ();
~AEADChaCha20Poly1305Encryptor ();
bool Encrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen,
const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len); // msgLen is len without tag
void Encrypt (const std::vector<std::pair<uint8_t *, size_t> >& bufs, const uint8_t * key, const uint8_t * nonce, uint8_t * mac); // encrypt multiple buffers with zero ad
private:
EVP_CIPHER_CTX * m_Ctx;
};
class AEADChaCha20Poly1305Decryptor
{
public:
AEADChaCha20Poly1305Decryptor ();
~AEADChaCha20Poly1305Decryptor ();
bool Decrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen,
const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len); // msgLen is len without tag
private:
EVP_CIPHER_CTX * m_Ctx;
};
bool AEADChaCha20Poly1305 (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen,
const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len, bool encrypt); // msgLen is len without tag
// ChaCha20
void ChaCha20 (const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out);
class ChaCha20Context
{
public:
ChaCha20Context ();
~ChaCha20Context ();
void operator ()(const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out);
private:
EVP_CIPHER_CTX * m_Ctx;
};
// HKDF
void HKDF (const uint8_t * salt, const uint8_t * key, size_t keyLen, const std::string& info, uint8_t * out, size_t outLen = 64); // salt - 32, out - 32 or 64, info <= 32
@@ -294,7 +260,7 @@ namespace crypto
void InitNoiseIKState (NoiseSymmetricState& state, const uint8_t * pub); // Noise_IK (ratchets)
// init and terminate
void InitCrypto (bool precomputation, bool aesni, bool force);
void InitCrypto (bool precomputation);
void TerminateCrypto ();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2024, The PurpleI2P Project
* Copyright (c) 2013-2025, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -104,8 +104,7 @@ namespace datagram
if (verified)
{
auto h = identity.GetIdentHash();
auto session = ObtainSession(h);
auto session = ObtainSession (identity.GetIdentHash());
session->Ack();
auto r = FindReceiver(toPort);
if(r)
@@ -288,8 +287,8 @@ namespace datagram
DatagramSession::DatagramSession(std::shared_ptr<i2p::client::ClientDestination> localDestination,
const i2p::data::IdentHash & remoteIdent) :
m_LocalDestination(localDestination),
m_RemoteIdent(remoteIdent),
m_LocalDestination(localDestination), m_RemoteIdent(remoteIdent),
m_LastUse (0), m_LastFlush (0),
m_RequestingLS(false)
{
}
@@ -310,8 +309,12 @@ namespace datagram
if (msg || m_SendQueue.empty ())
m_SendQueue.push_back(msg);
// flush queue right away if full
if (!msg || m_SendQueue.size() >= DATAGRAM_SEND_QUEUE_MAX_SIZE)
if (!msg || m_SendQueue.size() >= DATAGRAM_SEND_QUEUE_MAX_SIZE ||
m_LastUse > m_LastFlush + DATAGRAM_MAX_FLUSH_INTERVAL)
{
FlushSendQueue();
m_LastFlush = m_LastUse;
}
}
DatagramSession::Info DatagramSession::GetSessionInfo() const
@@ -344,7 +347,7 @@ namespace datagram
if(path)
path->updateTime = i2p::util::GetSecondsSinceEpoch ();
if (IsRatchets ())
SendMsg (nullptr); // send empty message in case if we have some data to send
SendMsg (nullptr); // send empty message in case if we don't have some data to send
}
std::shared_ptr<i2p::garlic::GarlicRoutingPath> DatagramSession::GetSharedRoutingPath ()
@@ -377,15 +380,19 @@ namespace datagram
if (!found)
{
m_RoutingSession = m_LocalDestination->GetRoutingSession(m_RemoteLeaseSet, true);
if (!m_RoutingSession->GetOwner () || !m_RoutingSession->IsReadyToSend ())
m_PendingRoutingSessions.push_back (m_RoutingSession);
if (m_RoutingSession)
{
m_RoutingSession->SetAckRequestInterval (DATAGRAM_SESSION_ACK_REQUEST_INTERVAL);
if (!m_RoutingSession->GetOwner () || !m_RoutingSession->IsReadyToSend ())
m_PendingRoutingSessions.push_back (m_RoutingSession);
}
}
}
auto path = m_RoutingSession->GetSharedRoutingPath();
if (path && m_RoutingSession->IsRatchets () &&
m_LastUse > m_RoutingSession->GetLastActivityTimestamp ()*1000 + DATAGRAM_SESSION_PATH_TIMEOUT)
if (path && m_RoutingSession->IsRatchets () && m_RoutingSession->CleanupUnconfirmedTags ())
{
LogPrint (eLogDebug, "Datagram: path reset");
m_RoutingSession->SetSharedRoutingPath (nullptr);
path = nullptr;
}
@@ -413,7 +420,14 @@ namespace datagram
auto sz = ls.size();
if (sz)
{
auto idx = rand() % sz;
int idx = -1;
if (m_LocalDestination)
{
auto pool = m_LocalDestination->GetTunnelPool ();
if (pool)
idx = pool->GetRng ()() % sz;
}
if (idx < 0) idx = rand () % sz;
path->remoteLease = ls[idx];
}
else
@@ -439,7 +453,14 @@ namespace datagram
auto sz = ls.size();
if (sz)
{
auto idx = rand() % sz;
int idx = -1;
if (m_LocalDestination)
{
auto pool = m_LocalDestination->GetTunnelPool ();
if (pool)
idx = pool->GetRng ()() % sz;
}
if (idx < 0) idx = rand () % sz;
path->remoteLease = ls[idx];
}
else

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2024, The PurpleI2P Project
* Copyright (c) 2013-2025, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -31,8 +31,6 @@ namespace datagram
{
// milliseconds for max session idle time
const uint64_t DATAGRAM_SESSION_MAX_IDLE = 10 * 60 * 1000;
// milliseconds for how long we try sticking to a dead routing path before trying to switch
const uint64_t DATAGRAM_SESSION_PATH_TIMEOUT = 10 * 1000;
// milliseconds interval a routing path is used before switching
const uint64_t DATAGRAM_SESSION_PATH_SWITCH_INTERVAL = 20 * 60 * 1000;
// milliseconds before lease expire should we try switching leases
@@ -43,6 +41,8 @@ namespace datagram
const uint64_t DATAGRAM_SESSION_PATH_MIN_LIFETIME = 5 * 1000;
// max 64 messages buffered in send queue for each datagram session
const size_t DATAGRAM_SEND_QUEUE_MAX_SIZE = 64;
const uint64_t DATAGRAM_MAX_FLUSH_INTERVAL = 5; // in milliseconds
const int DATAGRAM_SESSION_ACK_REQUEST_INTERVAL = 5500; // in milliseconds
class DatagramSession : public std::enable_shared_from_this<DatagramSession>
{
@@ -98,7 +98,7 @@ namespace datagram
std::shared_ptr<i2p::garlic::GarlicRoutingSession> m_RoutingSession;
std::vector<std::shared_ptr<i2p::garlic::GarlicRoutingSession> > m_PendingRoutingSessions;
std::vector<std::shared_ptr<I2NPMessage> > m_SendQueue;
uint64_t m_LastUse;
uint64_t m_LastUse, m_LastFlush; // milliseconds
bool m_RequestingLS;
};

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2024, The PurpleI2P Project
* Copyright (c) 2013-2025, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -23,7 +23,7 @@ namespace i2p
{
namespace client
{
LeaseSetDestination::LeaseSetDestination (boost::asio::io_service& service,
LeaseSetDestination::LeaseSetDestination (boost::asio::io_context& service,
bool isPublic, const std::map<std::string, std::string> * params):
m_Service (service), m_IsPublic (isPublic), m_PublishReplyToken (0),
m_LastSubmissionTime (0), m_PublishConfirmationTimer (m_Service),
@@ -37,6 +37,7 @@ namespace client
int inVar = DEFAULT_INBOUND_TUNNELS_LENGTH_VARIANCE;
int outVar = DEFAULT_OUTBOUND_TUNNELS_LENGTH_VARIANCE;
int numTags = DEFAULT_TAGS_TO_SEND;
bool isHighBandwidth = true;
std::shared_ptr<std::vector<i2p::data::IdentHash> > explicitPeers;
try
{
@@ -92,7 +93,7 @@ namespace client
it = params->find (I2CP_PARAM_DONT_PUBLISH_LEASESET);
if (it != params->end ())
{
// oveeride isPublic
// override isPublic
m_IsPublic = (it->second != "true");
}
it = params->find (I2CP_PARAM_LEASESET_TYPE);
@@ -121,6 +122,9 @@ namespace client
m_LeaseSetPrivKey.reset (nullptr);
}
}
it = params->find (I2CP_PARAM_STREAMING_PROFILE);
if (it != params->end ())
isHighBandwidth = std::stoi (it->second) != STREAMING_PROFILE_INTERACTIVE;
}
}
catch (std::exception & ex)
@@ -128,7 +132,7 @@ namespace client
LogPrint(eLogError, "Destination: Unable to parse parameters for destination: ", ex.what());
}
SetNumTags (numTags);
m_Pool = i2p::tunnel::tunnels.CreateTunnelPool (inLen, outLen, inQty, outQty, inVar, outVar);
m_Pool = i2p::tunnel::tunnels.CreateTunnelPool (inLen, outLen, inQty, outQty, inVar, outVar, isHighBandwidth);
if (explicitPeers)
m_Pool->SetExplicitPeers (explicitPeers);
if(params)
@@ -164,7 +168,7 @@ namespace client
LoadTags ();
m_Pool->SetLocalDestination (shared_from_this ());
m_Pool->SetActive (true);
m_CleanupTimer.expires_from_now (boost::posix_time::minutes (DESTINATION_CLEANUP_TIMEOUT));
m_CleanupTimer.expires_from_now (boost::posix_time::seconds (DESTINATION_CLEANUP_TIMEOUT));
m_CleanupTimer.async_wait (std::bind (&LeaseSetDestination::HandleCleanupTimer,
shared_from_this (), std::placeholders::_1));
}
@@ -191,7 +195,7 @@ namespace client
m_IsPublic = itr->second != "true";
}
int inLen, outLen, inQuant, outQuant, numTags, minLatency, maxLatency;
int inLen = 0, outLen = 0, inQuant = 0, outQuant = 0, numTags = 0, minLatency = 0, maxLatency = 0;
std::map<std::string, int&> intOpts = {
{I2CP_PARAM_INBOUND_TUNNEL_LENGTH, inLen},
{I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH, outLen},
@@ -290,7 +294,7 @@ namespace client
if (m_IsPublic)
{
auto s = shared_from_this ();
m_Service.post ([s](void)
boost::asio::post (m_Service, [s](void)
{
s->m_PublishVerificationTimer.cancel ();
s->Publish ();
@@ -318,7 +322,7 @@ namespace client
memcpy (data.k, key, 32);
memcpy (data.t, tag, 32);
auto s = shared_from_this ();
m_Service.post ([s,data](void)
boost::asio::post (m_Service, [s,data](void)
{
s->AddSessionKey (data.k, data.t);
});
@@ -335,7 +339,7 @@ namespace client
memcpy (data.k, key, 32);
data.t = tag;
auto s = shared_from_this ();
m_Service.post ([s,data](void)
boost::asio::post (m_Service, [s,data](void)
{
s->AddECIESx25519Key (data.k, data.t);
});
@@ -343,13 +347,30 @@ namespace client
void LeaseSetDestination::ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg)
{
m_Service.post (std::bind (&LeaseSetDestination::HandleGarlicMessage, shared_from_this (), msg));
if (!msg) return;
bool empty = false;
{
std::lock_guard<std::mutex> l(m_IncomingMsgsQueueMutex);
empty = m_IncomingMsgsQueue.empty ();
m_IncomingMsgsQueue.push_back (msg);
}
if (empty)
boost::asio::post (m_Service, [s = shared_from_this ()]()
{
std::list<std::shared_ptr<I2NPMessage> > receivedMsgs;
{
std::lock_guard<std::mutex> l(s->m_IncomingMsgsQueueMutex);
s->m_IncomingMsgsQueue.swap (receivedMsgs);
}
for (auto& it: receivedMsgs)
s->HandleGarlicMessage (it);
});
}
void LeaseSetDestination::ProcessDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg)
{
uint32_t msgID = bufbe32toh (msg->GetPayload () + DELIVERY_STATUS_MSGID_OFFSET);
m_Service.post (std::bind (&LeaseSetDestination::HandleDeliveryStatusMessage, shared_from_this (), msgID));
boost::asio::post (m_Service, std::bind (&LeaseSetDestination::HandleDeliveryStatusMessage, shared_from_this (), msgID));
}
void LeaseSetDestination::HandleI2NPMessage (const uint8_t * buf, size_t len)
@@ -467,7 +488,7 @@ namespace client
{
auto it2 = m_LeaseSetRequests.find (key);
if (it2 != m_LeaseSetRequests.end ())
{
{
request = it2->second;
m_LeaseSetRequests.erase (it2);
if (request->requestedBlindedKey)
@@ -489,14 +510,14 @@ namespace client
// 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,
{
auto ls = std::make_shared<i2p::data::LeaseSet2> (i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2,
localLeaseSet->GetBuffer (), localLeaseSet->GetBufferLen (), false);
leaseSet = ls;
}
leaseSet = ls;
}
else
LogPrint (eLogWarning, "Destination: Encrypted LeaseSet2 received for request without blinded key");
}
}
}
else
LogPrint (eLogWarning, "Destination: Couldn't find request for encrypted LeaseSet2");
@@ -507,14 +528,14 @@ namespace client
}
if (!request)
{
{
auto it1 = m_LeaseSetRequests.find (key);
if (it1 != m_LeaseSetRequests.end ())
{
{
request = it1->second;
m_LeaseSetRequests.erase (it1);
}
}
}
}
if (request)
{
request->requestTimeoutTimer.cancel ();
@@ -546,7 +567,7 @@ namespace client
LogPrint (eLogWarning, "Destination: Request for ", key.ToBase64 (), " not found");
}
void LeaseSetDestination::SendNextLeaseSetRequest (const i2p::data::IdentHash& key,
void LeaseSetDestination::SendNextLeaseSetRequest (const i2p::data::IdentHash& key,
std::shared_ptr<LeaseSetRequest> request)
{
bool found = false;
@@ -566,8 +587,8 @@ namespace client
request->Complete (nullptr);
m_LeaseSetRequests.erase (key);
}
}
}
void LeaseSetDestination::HandleDeliveryStatusMessage (uint32_t msgID)
{
if (msgID == m_PublishReplyToken)
@@ -576,7 +597,8 @@ namespace client
m_ExcludedFloodfills.clear ();
m_PublishReplyToken = 0;
// schedule verification
m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_VERIFICATION_TIMEOUT));
m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_VERIFICATION_TIMEOUT +
(m_Pool ? m_Pool->GetRng ()() % PUBLISH_VERIFICATION_TIMEOUT_VARIANCE : 0)));
m_PublishVerificationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishVerificationTimer,
shared_from_this (), std::placeholders::_1));
}
@@ -584,9 +606,12 @@ namespace client
i2p::garlic::GarlicDestination::HandleDeliveryStatusMessage (msgID);
}
void LeaseSetDestination::SetLeaseSetUpdated ()
void LeaseSetDestination::SetLeaseSetUpdated (bool post)
{
UpdateLeaseSet ();
if (post)
boost::asio::post (m_Service, [s = shared_from_this ()]() { s->UpdateLeaseSet (); });
else
UpdateLeaseSet ();
}
void LeaseSetDestination::Publish ()
@@ -624,7 +649,7 @@ namespace client
if (!outbound || !inbound)
{
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);
@@ -642,18 +667,18 @@ namespace client
}
else
LogPrint (eLogError, "Destination: Can't publish LeaseSet, no more floodfills found");
}
}
else
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));
LogPrint (eLogInfo, "Destination: Can't publish LeasetSet because destination is not ready. Try publishing again after ", PUBLISH_CONFIRMATION_TIMEOUT, " milliseconds");
m_PublishConfirmationTimer.expires_from_now (boost::posix_time::milliseconds(PUBLISH_CONFIRMATION_TIMEOUT));
m_PublishConfirmationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishConfirmationTimer,
shared_from_this (), std::placeholders::_1));
return;
@@ -666,13 +691,13 @@ namespace client
auto s = shared_from_this ();
msg->onDrop = [s]()
{
s->GetService ().post([s]()
boost::asio::post (s->GetService (), [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.expires_from_now (boost::posix_time::milliseconds(PUBLISH_CONFIRMATION_TIMEOUT));
m_PublishConfirmationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishConfirmationTimer,
shared_from_this (), std::placeholders::_1));
outbound->SendTunnelDataMsgTo (floodfill->GetIdentHash (), 0, msg);
@@ -688,15 +713,15 @@ 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 or failed. will try again");
LogPrint (eLogWarning, "Destination: Publish confirmation was not received in ", PUBLISH_CONFIRMATION_TIMEOUT, " milliseconds or failed. will try again");
Publish ();
}
else
{
LogPrint (eLogWarning, "Destination: Publish confirmation was not received in ", PUBLISH_CONFIRMATION_TIMEOUT, " seconds from Java floodfill for crypto type ", (int)GetIdentity ()->GetCryptoKeyType ());
LogPrint (eLogWarning, "Destination: Publish confirmation was not received in ", PUBLISH_CONFIRMATION_TIMEOUT, " milliseconds from Java floodfill for crypto type ", (int)GetIdentity ()->GetCryptoKeyType ());
// Java floodfill never sends confirmation back for unknown crypto type
// assume it successive and try to verify
m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_VERIFICATION_TIMEOUT));
m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_VERIFICATION_TIMEOUT + PUBLISH_VERIFICATION_TIMEOUT_VARIANCE)); // always max
m_PublishVerificationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishVerificationTimer,
shared_from_this (), std::placeholders::_1));
@@ -751,10 +776,10 @@ namespace client
if (!m_Pool || !IsReady ())
{
if (requestComplete)
m_Service.post ([requestComplete](void){requestComplete (nullptr);});
boost::asio::post (m_Service, [requestComplete](void){requestComplete (nullptr);});
return false;
}
m_Service.post (std::bind (&LeaseSetDestination::RequestLeaseSet, shared_from_this (), dest, requestComplete, nullptr));
boost::asio::post (m_Service, std::bind (&LeaseSetDestination::RequestLeaseSet, shared_from_this (), dest, requestComplete, nullptr));
return true;
}
@@ -763,7 +788,7 @@ namespace client
if (!dest || !m_Pool || !IsReady ())
{
if (requestComplete)
m_Service.post ([requestComplete](void){requestComplete (nullptr);});
boost::asio::post (m_Service, [requestComplete](void){requestComplete (nullptr);});
return false;
}
auto storeHash = dest->GetStoreHash ();
@@ -771,17 +796,17 @@ namespace client
if (leaseSet)
{
if (requestComplete)
m_Service.post ([requestComplete, leaseSet](void){requestComplete (leaseSet);});
boost::asio::post (m_Service, [requestComplete, leaseSet](void){requestComplete (leaseSet);});
return true;
}
m_Service.post (std::bind (&LeaseSetDestination::RequestLeaseSet, shared_from_this (), storeHash, requestComplete, dest));
boost::asio::post (m_Service, std::bind (&LeaseSetDestination::RequestLeaseSet, shared_from_this (), storeHash, requestComplete, dest));
return true;
}
void LeaseSetDestination::CancelDestinationRequest (const i2p::data::IdentHash& dest, bool notify)
{
auto s = shared_from_this ();
m_Service.post ([dest, notify, s](void)
boost::asio::post (m_Service, [dest, notify, s](void)
{
auto it = s->m_LeaseSetRequests.find (dest);
if (it != s->m_LeaseSetRequests.end ())
@@ -809,7 +834,7 @@ namespace client
request->requestedBlindedKey = requestedBlindedKey; // for encrypted LeaseSet2
if (requestComplete)
request->requestComplete.push_back (requestComplete);
auto ts = i2p::util::GetSecondsSinceEpoch ();
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
auto ret = m_LeaseSetRequests.insert (std::pair<i2p::data::IdentHash, std::shared_ptr<LeaseSetRequest> >(dest,request));
if (ret.second) // inserted
{
@@ -873,17 +898,17 @@ namespace client
AddECIESx25519Key (replyKey, replyTag);
else
AddSessionKey (replyKey, replyTag);
auto msg = WrapMessageForRouter (nextFloodfill,
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]()
boost::asio::post (s->GetService (), [s, dest, request]()
{
s->SendNextLeaseSetRequest (dest, request);
});
};
};
request->outboundTunnel->SendTunnelDataMsgs (
{
i2p::tunnel::TunnelMessageBlock
@@ -892,7 +917,7 @@ namespace client
nextFloodfill->GetIdentHash (), 0, msg
}
});
request->requestTimeoutTimer.expires_from_now (boost::posix_time::seconds(LEASESET_REQUEST_TIMEOUT));
request->requestTimeoutTimer.expires_from_now (boost::posix_time::milliseconds(LEASESET_REQUEST_TIMEOUT));
request->requestTimeoutTimer.async_wait (std::bind (&LeaseSetDestination::HandleRequestTimoutTimer,
shared_from_this (), std::placeholders::_1, dest));
}
@@ -909,7 +934,7 @@ namespace client
if (it != m_LeaseSetRequests.end ())
{
bool done = false;
uint64_t ts = i2p::util::GetSecondsSinceEpoch ();
uint64_t ts = i2p::util::GetMillisecondsSinceEpoch ();
if (ts < it->second->requestTime + MAX_LEASESET_REQUEST_TIMEOUT)
{
auto floodfill = i2p::data::netdb.GetClosestFloodfill (dest, it->second->excluded);
@@ -946,7 +971,8 @@ namespace client
CleanupExpiredTags ();
CleanupRemoteLeaseSets ();
CleanupDestination ();
m_CleanupTimer.expires_from_now (boost::posix_time::minutes (DESTINATION_CLEANUP_TIMEOUT));
m_CleanupTimer.expires_from_now (boost::posix_time::seconds (DESTINATION_CLEANUP_TIMEOUT +
(m_Pool ? m_Pool->GetRng ()() % DESTINATION_CLEANUP_TIMEOUT_VARIANCE : 0)));
m_CleanupTimer.async_wait (std::bind (&LeaseSetDestination::HandleCleanupTimer,
shared_from_this (), std::placeholders::_1));
}
@@ -960,7 +986,7 @@ namespace client
{
if (it->second->IsEmpty () || ts > it->second->GetExpirationTime ()) // leaseset expired
{
LogPrint (eLogWarning, "Destination: Remote LeaseSet ", it->second->GetIdentHash ().ToBase64 (), " expired");
LogPrint (eLogDebug, "Destination: Remote LeaseSet ", it->second->GetIdentHash ().ToBase64 (), " expired");
it = m_RemoteLeaseSets.erase (it);
}
else
@@ -975,13 +1001,15 @@ namespace client
return i2p::data::CRYPTO_KEY_TYPE_ELGAMAL;
}
ClientDestination::ClientDestination (boost::asio::io_service& service, const i2p::data::PrivateKeys& keys,
ClientDestination::ClientDestination (boost::asio::io_context& service, const i2p::data::PrivateKeys& keys,
bool isPublic, const std::map<std::string, std::string> * params):
LeaseSetDestination (service, isPublic, params),
m_Keys (keys), m_StreamingAckDelay (DEFAULT_INITIAL_ACK_DELAY),
m_StreamingOutboundSpeed (DEFAULT_MAX_OUTBOUND_SPEED),
m_StreamingInboundSpeed (DEFAULT_MAX_INBOUND_SPEED),
m_StreamingMaxConcurrentStreams (DEFAULT_MAX_CONCURRENT_STREAMS),
m_IsStreamingAnswerPings (DEFAULT_ANSWER_PINGS), m_LastPort (0),
m_DatagramDestination (nullptr), m_RefCounter (0),
m_DatagramDestination (nullptr), m_RefCounter (0), m_LastPublishedTimestamp (0),
m_ReadyChecker(service)
{
if (keys.IsOfflineSignature () && GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_LEASESET)
@@ -1012,18 +1040,15 @@ namespace client
}
}
// if no param or valid crypto type use from identity
bool isSingleKey = false;
if (encryptionKeyTypes.empty ())
{
isSingleKey = true;
encryptionKeyTypes.insert (GetIdentity ()->GetCryptoKeyType ());
}
encryptionKeyTypes.insert ( { GetIdentity ()->GetCryptoKeyType (),
i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD }); // usually 0,4
for (auto& it: encryptionKeyTypes)
{
auto encryptionKey = new EncryptionKey (it);
if (IsPublic ())
PersistTemporaryKeys (encryptionKey, isSingleKey);
PersistTemporaryKeys (encryptionKey);
else
encryptionKey->GenerateKeys ();
encryptionKey->CreateDecryptor ();
@@ -1051,6 +1076,11 @@ namespace client
it = params->find (I2CP_PARAM_STREAMING_MAX_OUTBOUND_SPEED);
if (it != params->end ())
m_StreamingOutboundSpeed = std::stoi(it->second);
it = params->find (I2CP_PARAM_STREAMING_MAX_INBOUND_SPEED);
if (it != params->end ())
m_StreamingInboundSpeed = std::stoi(it->second);
if (it != params->end ())
m_StreamingMaxConcurrentStreams = std::stoi(it->second);
it = params->find (I2CP_PARAM_STREAMING_ANSWER_PINGS);
if (it != params->end ())
m_IsStreamingAnswerPings = std::stoi (it->second); // 1 for true
@@ -1101,7 +1131,6 @@ namespace client
void ClientDestination::Stop ()
{
LogPrint(eLogDebug, "Destination: Stopping destination ", GetIdentHash().ToBase32(), ".b32.i2p");
LeaseSetDestination::Stop ();
m_ReadyChecker.cancel();
LogPrint(eLogDebug, "Destination: -> Stopping Streaming Destination");
m_StreamingDestination->Stop ();
@@ -1123,6 +1152,7 @@ namespace client
delete m_DatagramDestination;
m_DatagramDestination = nullptr;
}
LeaseSetDestination::Stop ();
LogPrint(eLogDebug, "Destination: -> Stopping done");
}
@@ -1186,7 +1216,7 @@ namespace client
if (leaseSet)
{
auto stream = CreateStream (leaseSet, port);
GetService ().post ([streamRequestComplete, stream]()
boost::asio::post (GetService (), [streamRequestComplete, stream]()
{
streamRequestComplete(stream);
});
@@ -1379,12 +1409,11 @@ namespace client
return ret;
}
void ClientDestination::PersistTemporaryKeys (EncryptionKey * keys, bool isSingleKey)
void ClientDestination::PersistTemporaryKeys (EncryptionKey * keys)
{
if (!keys) return;
std::string ident = GetIdentHash().ToBase32();
std::string path = i2p::fs::DataDirPath("destinations",
isSingleKey ? (ident + ".dat") : (ident + "." + std::to_string (keys->keyType) + ".dat"));
std::string path = i2p::fs::DataDirPath("destinations", ident + "." + std::to_string (keys->keyType) + ".dat");
std::ifstream f(path, std::ifstream::binary);
if (f) {
@@ -1430,12 +1459,19 @@ namespace client
if (m_StandardEncryptionKey)
keySections.push_back ({m_StandardEncryptionKey->keyType, (uint16_t)m_StandardEncryptionKey->decryptor->GetPublicKeyLen (), m_StandardEncryptionKey->pub} );
auto publishedTimestamp = i2p::util::GetSecondsSinceEpoch ();
if (publishedTimestamp <= m_LastPublishedTimestamp)
{
LogPrint (eLogDebug, "Destination: LeaseSet update at the same second");
publishedTimestamp++; // force newer timestamp
}
bool isPublishedEncrypted = GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2;
auto ls2 = std::make_shared<i2p::data::LocalLeaseSet2> (i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2,
m_Keys, keySections, tunnels, IsPublic (), isPublishedEncrypted);
m_Keys, keySections, tunnels, IsPublic (), publishedTimestamp, isPublishedEncrypted);
if (isPublishedEncrypted) // encrypt if type 5
ls2 = std::make_shared<i2p::data::LocalEncryptedLeaseSet2> (ls2, m_Keys, GetAuthType (), m_AuthKeys);
leaseSet = ls2;
m_LastPublishedTimestamp = publishedTimestamp;
}
SetLeaseSet (leaseSet);
}
@@ -1500,6 +1536,8 @@ namespace client
RunnableService ("Destination"),
ClientDestination (GetIOService (), keys, isPublic, params)
{
if (!GetNickname ().empty ())
RunnableService::SetName (GetNickname ());
}
RunnableClientDestination::~RunnableClientDestination ()

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2024, The PurpleI2P Project
* Copyright (c) 2013-2025, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -36,13 +36,15 @@ namespace client
const uint8_t PROTOCOL_TYPE_STREAMING = 6;
const uint8_t PROTOCOL_TYPE_DATAGRAM = 17;
const uint8_t PROTOCOL_TYPE_RAW = 18;
const int PUBLISH_CONFIRMATION_TIMEOUT = 5; // in seconds
const int PUBLISH_VERIFICATION_TIMEOUT = 10; // in seconds after successful publish
const int PUBLISH_CONFIRMATION_TIMEOUT = 1800; // in milliseconds
const int PUBLISH_VERIFICATION_TIMEOUT = 5; // in seconds after successful publish
const int PUBLISH_VERIFICATION_TIMEOUT_VARIANCE = 3; // in seconds
const int PUBLISH_MIN_INTERVAL = 20; // in seconds
const int PUBLISH_REGULAR_VERIFICATION_INTERNAL = 100; // in seconds periodically
const int LEASESET_REQUEST_TIMEOUT = 5; // in seconds
const int MAX_LEASESET_REQUEST_TIMEOUT = 40; // in seconds
const int DESTINATION_CLEANUP_TIMEOUT = 3; // in minutes
const int LEASESET_REQUEST_TIMEOUT = 1600; // in milliseconds
const int MAX_LEASESET_REQUEST_TIMEOUT = 12000; // in milliseconds
const int DESTINATION_CLEANUP_TIMEOUT = 44; // in seconds
const int DESTINATION_CLEANUP_TIMEOUT_VARIANCE = 30; // in seconds
const unsigned int MAX_NUM_FLOODFILLS_PER_REQUEST = 7;
// I2CP
@@ -86,9 +88,17 @@ namespace client
const int DEFAULT_INITIAL_ACK_DELAY = 200; // milliseconds
const char I2CP_PARAM_STREAMING_MAX_OUTBOUND_SPEED[] = "i2p.streaming.maxOutboundSpeed"; // bytes/sec
const int DEFAULT_MAX_OUTBOUND_SPEED = 1730000000; // no more than 1.73 Gbytes/s
const char I2CP_PARAM_STREAMING_MAX_INBOUND_SPEED[] = "i2p.streaming.maxInboundSpeed"; // bytes/sec
const int DEFAULT_MAX_INBOUND_SPEED = 1730000000; // no more than 1.73 Gbytes/s
const char I2CP_PARAM_STREAMING_ANSWER_PINGS[] = "i2p.streaming.answerPings";
const int DEFAULT_ANSWER_PINGS = true;
const char I2CP_PARAM_STREAMING_PROFILE[] = "i2p.streaming.profile";
const int STREAMING_PROFILE_BULK = 1; // high bandwidth
const int STREAMING_PROFILE_INTERACTIVE = 2; // low bandwidth
const int DEFAULT_STREAMING_PROFILE = STREAMING_PROFILE_BULK;
const char I2CP_PARAM_STREAMING_MAX_CONCURRENT_STREAMS[] = "i2p.streaming.maxConcurrentStreams";
const int DEFAULT_MAX_CONCURRENT_STREAMS = 2048;
typedef std::function<void (std::shared_ptr<i2p::stream::Stream> stream)> StreamRequestComplete;
class LeaseSetDestination: public i2p::garlic::GarlicDestination,
@@ -98,7 +108,7 @@ namespace client
// leaseSet = nullptr means not found
struct LeaseSetRequest
{
LeaseSetRequest (boost::asio::io_service& service): requestTime (0), requestTimeoutTimer (service) {};
LeaseSetRequest (boost::asio::io_context& service): requestTime (0), requestTimeoutTimer (service) {};
std::unordered_set<i2p::data::IdentHash> excluded;
uint64_t requestTime;
boost::asio::deadline_timer requestTimeoutTimer;
@@ -116,10 +126,10 @@ namespace client
public:
LeaseSetDestination (boost::asio::io_service& service, bool isPublic, const std::map<std::string, std::string> * params = nullptr);
LeaseSetDestination (boost::asio::io_context& service, bool isPublic, const std::map<std::string, std::string> * params = nullptr);
~LeaseSetDestination ();
const std::string& GetNickname () const { return m_Nickname; };
boost::asio::io_service& GetService () { return m_Service; };
auto& GetService () { return m_Service; };
virtual void Start ();
virtual void Stop ();
@@ -136,15 +146,15 @@ namespace client
void CancelDestinationRequestWithEncryptedLeaseSet (std::shared_ptr<const i2p::data::BlindedPublicKey> dest, bool notify = true);
// implements GarlicDestination
std::shared_ptr<const i2p::data::LocalLeaseSet> GetLeaseSet ();
std::shared_ptr<i2p::tunnel::TunnelPool> GetTunnelPool () const { return m_Pool; }
std::shared_ptr<const i2p::data::LocalLeaseSet> GetLeaseSet () override;
std::shared_ptr<i2p::tunnel::TunnelPool> GetTunnelPool () const override { return m_Pool; }
// override GarlicDestination
bool SubmitSessionKey (const uint8_t * key, const uint8_t * tag);
void SubmitECIESx25519Key (const uint8_t * key, uint64_t tag);
void ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg);
void ProcessDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg);
void SetLeaseSetUpdated ();
bool SubmitSessionKey (const uint8_t * key, const uint8_t * tag) override;
void SubmitECIESx25519Key (const uint8_t * key, uint64_t tag) override;
void ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg) override;
void ProcessDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg) override;
void SetLeaseSetUpdated (bool post) override;
bool IsPublic () const { return m_IsPublic; };
void SetPublic (bool pub) { m_IsPublic = pub; };
@@ -152,8 +162,8 @@ namespace client
protected:
// implements GarlicDestination
void HandleI2NPMessage (const uint8_t * buf, size_t len);
bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID);
void HandleI2NPMessage (const uint8_t * buf, size_t len) override;
bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID) override;
void SetLeaseSet (std::shared_ptr<const i2p::data::LocalLeaseSet> newLeaseSet);
int GetLeaseSetType () const { return m_LeaseSetType; };
@@ -186,11 +196,14 @@ namespace client
private:
boost::asio::io_service& m_Service;
boost::asio::io_context& m_Service;
mutable std::mutex m_RemoteLeaseSetsMutex;
std::unordered_map<i2p::data::IdentHash, std::shared_ptr<i2p::data::LeaseSet> > m_RemoteLeaseSets;
std::unordered_map<i2p::data::IdentHash, std::shared_ptr<LeaseSetRequest> > m_LeaseSetRequests;
std::list<std::shared_ptr<I2NPMessage> > m_IncomingMsgsQueue;
mutable std::mutex m_IncomingMsgsQueueMutex;
std::shared_ptr<i2p::tunnel::TunnelPool> m_Pool;
std::mutex m_LeaseSetMutex;
std::shared_ptr<const i2p::data::LocalLeaseSet> m_LeaseSet;
@@ -229,7 +242,7 @@ namespace client
public:
ClientDestination (boost::asio::io_service& service, const i2p::data::PrivateKeys& keys,
ClientDestination (boost::asio::io_context& service, const i2p::data::PrivateKeys& keys,
bool isPublic, const std::map<std::string, std::string> * params = nullptr);
~ClientDestination ();
@@ -262,6 +275,8 @@ namespace client
void AcceptOnce (const i2p::stream::StreamingDestination::Acceptor& acceptor);
int GetStreamingAckDelay () const { return m_StreamingAckDelay; }
int GetStreamingOutboundSpeed () const { return m_StreamingOutboundSpeed; }
int GetStreamingInboundSpeed () const { return m_StreamingInboundSpeed; }
int GetStreamingMaxConcurrentStreams () const { return m_StreamingMaxConcurrentStreams; }
bool IsStreamingAnswerPings () const { return m_IsStreamingAnswerPings; }
// datagram
@@ -286,7 +301,7 @@ namespace client
std::shared_ptr<ClientDestination> GetSharedFromThis () {
return std::static_pointer_cast<ClientDestination>(shared_from_this ());
}
void PersistTemporaryKeys (EncryptionKey * keys, bool isSingleKey);
void PersistTemporaryKeys (EncryptionKey * keys);
void ReadAuthKey (const std::string& group, const std::map<std::string, std::string> * params);
template<typename Dest>
@@ -298,14 +313,14 @@ namespace client
std::unique_ptr<EncryptionKey> m_StandardEncryptionKey;
std::unique_ptr<EncryptionKey> m_ECIESx25519EncryptionKey;
int m_StreamingAckDelay;
int m_StreamingOutboundSpeed;
int m_StreamingAckDelay,m_StreamingOutboundSpeed, m_StreamingInboundSpeed, m_StreamingMaxConcurrentStreams;
bool m_IsStreamingAnswerPings;
std::shared_ptr<i2p::stream::StreamingDestination> m_StreamingDestination; // default
std::map<uint16_t, std::shared_ptr<i2p::stream::StreamingDestination> > m_StreamingDestinationsByPorts;
std::shared_ptr<i2p::stream::StreamingDestination> m_LastStreamingDestination; uint16_t m_LastPort; // for server tunnels
i2p::datagram::DatagramDestination * m_DatagramDestination;
int m_RefCounter; // how many clients(tunnels) use this destination
uint64_t m_LastPublishedTimestamp;
boost::asio::deadline_timer m_ReadyChecker;

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2024, The PurpleI2P Project
* Copyright (c) 2013-2025, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -229,6 +229,29 @@ namespace garlic
tagsetNsr->NextSessionTagRatchet ();
}
bool ECIESX25519AEADRatchetSession::MessageConfirmed (uint32_t msgID)
{
auto ret = GarlicRoutingSession::MessageConfirmed (msgID); // LeaseSet
if (m_AckRequestMsgID && m_AckRequestMsgID == msgID)
{
m_AckRequestMsgID = 0;
m_AckRequestNumAttempts = 0;
ret = true;
}
return ret;
}
bool ECIESX25519AEADRatchetSession::CleanupUnconfirmedTags ()
{
if (m_AckRequestMsgID && m_AckRequestNumAttempts > ECIESX25519_ACK_REQUEST_MAX_NUM_ATTEMPTS)
{
m_AckRequestMsgID = 0;
m_AckRequestNumAttempts = 0;
return true;
}
return false;
}
bool ECIESX25519AEADRatchetSession::HandleNewIncomingSession (const uint8_t * buf, size_t len)
{
if (!GetOwner ()) return false;
@@ -335,7 +358,7 @@ namespace garlic
{
uint32_t tagsetid = bufbe16toh (buf + offset1); offset1 += 2; // tagsetid
uint16_t n = bufbe16toh (buf + offset1); offset1 += 2; // N
MessageConfirmed ((tagsetid << 16) + n); // msgid
MessageConfirmed ((tagsetid << 16) + n); // msgid = (tagsetid << 16) + N
}
break;
}
@@ -398,7 +421,6 @@ namespace garlic
{
uint16_t keyID = bufbe16toh (buf); buf += 2; // keyID
bool newKey = flag & ECIESX25519_NEXT_KEY_REQUEST_REVERSE_KEY_FLAG;
m_SendReverseKey = true;
if (!m_NextReceiveRatchet)
m_NextReceiveRatchet.reset (new DHRatchet ());
else
@@ -410,15 +432,14 @@ namespace garlic
}
m_NextReceiveRatchet->keyID = keyID;
}
int tagsetID = 2*keyID;
if (newKey)
{
m_NextReceiveRatchet->key = i2p::transport::transports.GetNextX25519KeysPair ();
m_NextReceiveRatchet->newKey = true;
tagsetID++;
}
else
m_NextReceiveRatchet->newKey = false;
auto tagsetID = m_NextReceiveRatchet->GetReceiveTagSetID ();
if (flag & ECIESX25519_NEXT_KEY_KEY_PRESENT_FLAG)
memcpy (m_NextReceiveRatchet->remote, buf, 32);
@@ -432,7 +453,9 @@ namespace garlic
GenerateMoreReceiveTags (newTagset, (GetOwner () && GetOwner ()->GetNumRatchetInboundTags () > 0) ?
GetOwner ()->GetNumRatchetInboundTags () : ECIESX25519_MAX_NUM_GENERATED_TAGS);
receiveTagset->Expire ();
LogPrint (eLogDebug, "Garlic: Next receive tagset ", tagsetID, " created");
m_SendReverseKey = true;
}
}
@@ -702,6 +725,8 @@ namespace garlic
bool ECIESX25519AEADRatchetSession::NewExistingSessionMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen)
{
auto owner = GetOwner ();
if (!owner) return false;
uint8_t nonce[12];
auto index = m_SendTagset->GetNextIndex ();
CreateNonce (index, nonce); // tag's index
@@ -709,8 +734,7 @@ namespace garlic
if (!tag)
{
LogPrint (eLogError, "Garlic: Can't create new ECIES-X25519-AEAD-Ratchet tag for send tagset");
if (GetOwner ())
GetOwner ()->RemoveECIESx25519Session (m_RemoteStaticKey);
owner->RemoveECIESx25519Session (m_RemoteStaticKey);
return false;
}
memcpy (out, &tag, 8);
@@ -718,7 +742,7 @@ namespace garlic
// ciphertext = ENCRYPT(k, n, payload, ad)
uint8_t key[32];
m_SendTagset->GetSymmKey (index, key);
if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, out, 8, key, nonce, out + 8, outLen - 8, true)) // encrypt
if (!owner->AEADChaCha20Poly1305Encrypt (payload, len, out, 8, key, nonce, out + 8, outLen - 8))
{
LogPrint (eLogWarning, "Garlic: Payload section AEAD encryption failed");
return false;
@@ -737,33 +761,35 @@ namespace garlic
uint8_t * payload = buf + 8;
uint8_t key[32];
receiveTagset->GetSymmKey (index, key);
if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len - 16, buf, 8, key, nonce, payload, len - 16, false)) // decrypt
auto owner = GetOwner ();
if (!owner) return true; // drop message
if (!owner->AEADChaCha20Poly1305Decrypt (payload, len - 16, buf, 8, key, nonce, payload, len - 16))
{
LogPrint (eLogWarning, "Garlic: Payload section AEAD decryption failed");
return false;
}
HandlePayload (payload, len - 16, receiveTagset, index);
if (GetOwner ())
int moreTags = 0;
if (owner->GetNumRatchetInboundTags () > 0) // override in settings?
{
int moreTags = 0;
if (GetOwner ()->GetNumRatchetInboundTags () > 0) // override in settings?
{
if (receiveTagset->GetNextIndex () - index < GetOwner ()->GetNumRatchetInboundTags ()/2)
moreTags = GetOwner ()->GetNumRatchetInboundTags ();
index -= GetOwner ()->GetNumRatchetInboundTags (); // trim behind
}
else
{
moreTags = ECIESX25519_MIN_NUM_GENERATED_TAGS + (index >> 2); // N/4
if (moreTags > ECIESX25519_MAX_NUM_GENERATED_TAGS) moreTags = ECIESX25519_MAX_NUM_GENERATED_TAGS;
moreTags -= (receiveTagset->GetNextIndex () - index);
index -= ECIESX25519_MAX_NUM_GENERATED_TAGS; // trim behind
}
if (moreTags > 0)
GenerateMoreReceiveTags (receiveTagset, moreTags);
if (index > 0)
receiveTagset->SetTrimBehind (index);
if (receiveTagset->GetNextIndex () - index < owner->GetNumRatchetInboundTags ()/2)
moreTags = owner->GetNumRatchetInboundTags ();
index -= owner->GetNumRatchetInboundTags (); // trim behind
}
else
{
moreTags = (receiveTagset->GetTagSetID () > 0) ? ECIESX25519_MAX_NUM_GENERATED_TAGS : // for non first tagset
(ECIESX25519_MIN_NUM_GENERATED_TAGS + (index >> 1)); // N/2
if (moreTags > ECIESX25519_MAX_NUM_GENERATED_TAGS) moreTags = ECIESX25519_MAX_NUM_GENERATED_TAGS;
moreTags -= (receiveTagset->GetNextIndex () - index);
index -= ECIESX25519_MAX_NUM_GENERATED_TAGS; // trim behind
}
if (moreTags > 0)
GenerateMoreReceiveTags (receiveTagset, moreTags);
if (index > 0)
receiveTagset->SetTrimBehind (index);
return true;
}
@@ -777,10 +803,10 @@ namespace garlic
m_State = eSessionStateEstablished;
m_NSRSendTagset = nullptr;
m_EphemeralKeys = nullptr;
#if (__cplusplus >= 201703L) // C++ 17 or higher
[[fallthrough]];
#endif
case eSessionStateEstablished:
if (m_SendReverseKey && receiveTagset->GetTagSetID () == m_NextReceiveRatchet->GetReceiveTagSetID ())
m_SendReverseKey = false; // tag received on new tagset
if (receiveTagset->IsNS ())
{
// our of sequence NSR
@@ -858,6 +884,7 @@ namespace garlic
{
uint64_t ts = i2p::util::GetMillisecondsSinceEpoch ();
size_t payloadLen = 0;
bool sendAckRequest = false;
if (first) payloadLen += 7;// datatime
if (msg)
{
@@ -876,13 +903,28 @@ namespace garlic
payloadLen += leaseSet->GetBufferLen () + DATABASE_STORE_HEADER_SIZE + 13;
if (!first)
{
// ack request
// ack request for LeaseSet
m_AckRequestMsgID = m_SendTagset->GetMsgID ();
sendAckRequest = true;
// update LeaseSet status
SetLeaseSetUpdateStatus (eLeaseSetSubmitted);
SetLeaseSetUpdateMsgID ((m_SendTagset->GetTagSetID () << 16) + m_SendTagset->GetNextIndex ()); // (tagsetid << 16) + N
SetLeaseSetUpdateMsgID (m_AckRequestMsgID);
SetLeaseSetSubmissionTime (ts);
payloadLen += 4;
}
}
if (!sendAckRequest && !first &&
((!m_AckRequestMsgID && ts > m_LastAckRequestSendTime + m_AckRequestInterval) || // regular request
(m_AckRequestMsgID && ts > m_LastAckRequestSendTime + LEASESET_CONFIRMATION_TIMEOUT))) // previous request failed. try again
{
// not LeaseSet
m_AckRequestMsgID = m_SendTagset->GetMsgID ();
if (m_AckRequestMsgID)
{
m_AckRequestNumAttempts++;
sendAckRequest = true;
}
}
if (sendAckRequest) payloadLen += 4;
if (m_AckRequests.size () > 0)
payloadLen += m_AckRequests.size ()*4 + 3;
if (m_SendReverseKey)
@@ -934,16 +976,15 @@ namespace garlic
}
// LeaseSet
if (leaseSet)
{
offset += CreateLeaseSetClove (leaseSet, ts, payload + offset, payloadLen - offset);
if (!first)
{
// ack request
payload[offset] = eECIESx25519BlkAckRequest; offset++;
htobe16buf (payload + offset, 1); offset += 2;
payload[offset] = 0; offset++; // flags
}
}
// ack request
if (sendAckRequest)
{
payload[offset] = eECIESx25519BlkAckRequest; offset++;
htobe16buf (payload + offset, 1); offset += 2;
payload[offset] = 0; offset++; // flags
m_LastAckRequestSendTime = ts;
}
// msg
if (msg)
offset += CreateGarlicClove (msg, payload + offset, payloadLen - offset);
@@ -978,7 +1019,6 @@ namespace garlic
memcpy (payload + offset, m_NextReceiveRatchet->key->GetPublicKey (), 32);
offset += 32; // public key
}
m_SendReverseKey = false;
}
if (m_SendForwardKey)
{
@@ -1074,6 +1114,8 @@ namespace garlic
bool ECIESX25519AEADRatchetSession::CheckExpired (uint64_t ts)
{
CleanupUnconfirmedLeaseSet (ts);
if (!m_Destination && ts > m_LastActivityTimestamp + ECIESX25519_SESSION_CREATE_TIMEOUT) return true; // m_LastActivityTimestamp is NS receive time
if (m_State != eSessionStateEstablished && m_SessionCreatedTimestamp && ts > m_SessionCreatedTimestamp + ECIESX25519_SESSION_ESTABLISH_TIMEOUT) return true;
return ts > m_LastActivityTimestamp + ECIESX25519_RECEIVE_EXPIRATION_TIMEOUT && // seconds
ts*1000 > m_LastSentTimestamp + ECIESX25519_SEND_EXPIRATION_TIMEOUT*1000; // milliseconds
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2024, The PurpleI2P Project
* Copyright (c) 2013-2025, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -30,10 +30,14 @@ namespace garlic
const int ECIESX25519_SEND_INACTIVITY_TIMEOUT = 5000; // number of milliseconds we can send empty(pyaload only) packet after
const int ECIESX25519_SEND_EXPIRATION_TIMEOUT = 480; // in seconds
const int ECIESX25519_RECEIVE_EXPIRATION_TIMEOUT = 600; // in seconds
const int ECIESX25519_PREVIOUS_TAGSET_EXPIRATION_TIMEOUT = 180; // 180
const int ECIESX25519_SESSION_CREATE_TIMEOUT = 3; // in seconds, NSR must be send after NS received
const int ECIESX25519_SESSION_ESTABLISH_TIMEOUT = 15; // in seconds
const int ECIESX25519_PREVIOUS_TAGSET_EXPIRATION_TIMEOUT = 180; // in seconds
const int ECIESX25519_DEFAULT_ACK_REQUEST_INTERVAL = 33000; // in milliseconds
const int ECIESX25519_ACK_REQUEST_MAX_NUM_ATTEMPTS = 3;
const int ECIESX25519_TAGSET_MAX_NUM_TAGS = 8192; // number of tags we request new tagset after
const int ECIESX25519_MIN_NUM_GENERATED_TAGS = 24;
const int ECIESX25519_MAX_NUM_GENERATED_TAGS = 320;
const int ECIESX25519_MAX_NUM_GENERATED_TAGS = 800;
const int ECIESX25519_NSR_NUM_GENERATED_TAGS = 12;
const size_t ECIESX25519_OPTIMAL_PAYLOAD_SIZE = 1912; // 1912 = 1956 /* to fit 2 tunnel messages */
@@ -57,6 +61,8 @@ namespace garlic
int GetTagSetID () const { return m_TagSetID; };
void SetTagSetID (int tagsetID) { m_TagSetID = tagsetID; };
uint32_t GetMsgID () const { return (m_TagSetID << 16) + m_NextIndex; }; // (tagsetid << 16) + N
private:
i2p::data::Tag<64> m_SessionTagKeyData;
@@ -149,6 +155,7 @@ namespace garlic
std::shared_ptr<i2p::crypto::X25519Keys> key;
uint8_t remote[32]; // last remote public key
bool newKey = true;
int GetReceiveTagSetID () const { return newKey ? (2*keyID + 1) : 2*keyID; }
};
public:
@@ -157,14 +164,14 @@ namespace garlic
~ECIESX25519AEADRatchetSession ();
bool HandleNextMessage (uint8_t * buf, size_t len, std::shared_ptr<ReceiveRatchetTagSet> receiveTagset, int index = 0);
std::shared_ptr<I2NPMessage> WrapSingleMessage (std::shared_ptr<const I2NPMessage> msg);
std::shared_ptr<I2NPMessage> WrapSingleMessage (std::shared_ptr<const I2NPMessage> msg) override;
std::shared_ptr<I2NPMessage> WrapOneTimeMessage (std::shared_ptr<const I2NPMessage> msg);
const uint8_t * GetRemoteStaticKey () const { return m_RemoteStaticKey; }
void SetRemoteStaticKey (const uint8_t * key) { memcpy (m_RemoteStaticKey, key, 32); }
void Terminate () { m_IsTerminated = true; }
void SetDestination (const i2p::data::IdentHash& dest) // TODO:
void SetDestination (const i2p::data::IdentHash& dest)
{
if (!m_Destination) m_Destination.reset (new i2p::data::IdentHash (dest));
}
@@ -173,18 +180,21 @@ namespace garlic
bool CanBeRestarted (uint64_t ts) const { return ts > m_SessionCreatedTimestamp + ECIESX25519_RESTART_TIMEOUT; }
bool IsInactive (uint64_t ts) const { return ts > m_LastActivityTimestamp + ECIESX25519_INACTIVITY_TIMEOUT && CanBeRestarted (ts); }
bool IsRatchets () const { return true; };
bool IsReadyToSend () const { return m_State != eSessionStateNewSessionSent; };
bool IsTerminated () const { return m_IsTerminated; }
uint64_t GetLastActivityTimestamp () const { return m_LastActivityTimestamp; };
bool IsRatchets () const override { return true; };
bool IsReadyToSend () const override { return m_State != eSessionStateNewSessionSent; };
bool IsTerminated () const override { return m_IsTerminated; }
uint64_t GetLastActivityTimestamp () const override { return m_LastActivityTimestamp; };
void SetAckRequestInterval (int interval) override { m_AckRequestInterval = interval; };
bool CleanupUnconfirmedTags () override; // return true if unaswered Ack requests, called from I2CP
protected:
i2p::crypto::NoiseSymmetricState& GetNoiseState () { return *this; };
void SetNoiseState (const i2p::crypto::NoiseSymmetricState& state) { GetNoiseState () = state; };
void CreateNonce (uint64_t seqn, uint8_t * nonce);
void HandlePayload (const uint8_t * buf, size_t len, const std::shared_ptr<ReceiveRatchetTagSet>& receiveTagset, int index);
bool MessageConfirmed (uint32_t msgID) override;
private:
bool GenerateEphemeralKeysAndEncode (uint8_t * buf); // buf is 32 bytes
@@ -217,12 +227,17 @@ namespace garlic
uint64_t m_SessionCreatedTimestamp = 0, m_LastActivityTimestamp = 0, // incoming (in seconds)
m_LastSentTimestamp = 0; // in milliseconds
std::shared_ptr<RatchetTagSet> m_SendTagset, m_NSRSendTagset;
std::unique_ptr<i2p::data::IdentHash> m_Destination;// TODO: might not need it
std::list<std::pair<uint16_t, int> > m_AckRequests; // (tagsetid, index)
std::unique_ptr<i2p::data::IdentHash> m_Destination;// must be set for NS if outgoing and NSR if incoming
std::list<std::pair<uint16_t, int> > m_AckRequests; // incoming (tagsetid, index)
bool m_SendReverseKey = false, m_SendForwardKey = false, m_IsTerminated = false;
std::unique_ptr<DHRatchet> m_NextReceiveRatchet, m_NextSendRatchet;
uint8_t m_PaddingSizes[32], m_NextPaddingSize;
uint64_t m_LastAckRequestSendTime = 0; // milliseconds
uint32_t m_AckRequestMsgID = 0;
int m_AckRequestNumAttempts = 0;
int m_AckRequestInterval = ECIESX25519_DEFAULT_ACK_REQUEST_INTERVAL; // milliseconds
public:
// for HTTP only

View File

@@ -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
*
@@ -457,86 +457,6 @@ namespace crypto
}
}
#if !OPENSSL_X25519
BIGNUM * Ed25519::ScalarMul (const BIGNUM * u, const BIGNUM * k, BN_CTX * ctx) const
{
BN_CTX_start (ctx);
auto x1 = BN_CTX_get (ctx); BN_copy (x1, u);
auto x2 = BN_CTX_get (ctx); BN_one (x2);
auto z2 = BN_CTX_get (ctx); BN_zero (z2);
auto x3 = BN_CTX_get (ctx); BN_copy (x3, u);
auto z3 = BN_CTX_get (ctx); BN_one (z3);
auto c121666 = BN_CTX_get (ctx); BN_set_word (c121666, 121666);
auto tmp0 = BN_CTX_get (ctx); auto tmp1 = BN_CTX_get (ctx);
unsigned int swap = 0;
auto bits = BN_num_bits (k);
while(bits)
{
--bits;
auto k_t = BN_is_bit_set(k, bits) ? 1 : 0;
swap ^= k_t;
if (swap)
{
std::swap (x2, x3);
std::swap (z2, z3);
}
swap = k_t;
BN_mod_sub(tmp0, x3, z3, q, ctx);
BN_mod_sub(tmp1, x2, z2, q, ctx);
BN_mod_add(x2, x2, z2, q, ctx);
BN_mod_add(z2, x3, z3, q, ctx);
BN_mod_mul(z3, tmp0, x2, q, ctx);
BN_mod_mul(z2, z2, tmp1, q, ctx);
BN_mod_sqr(tmp0, tmp1, q, ctx);
BN_mod_sqr(tmp1, x2, q, ctx);
BN_mod_add(x3, z3, z2, q, ctx);
BN_mod_sub(z2, z3, z2, q, ctx);
BN_mod_mul(x2, tmp1, tmp0, q, ctx);
BN_mod_sub(tmp1, tmp1, tmp0, q, ctx);
BN_mod_sqr(z2, z2, q, ctx);
BN_mod_mul(z3, tmp1, c121666, q, ctx);
BN_mod_sqr(x3, x3, q, ctx);
BN_mod_add(tmp0, tmp0, z3, q, ctx);
BN_mod_mul(z3, x1, z2, q, ctx);
BN_mod_mul(z2, tmp1, tmp0, q, ctx);
}
if (swap)
{
std::swap (x2, x3);
std::swap (z2, z3);
}
BN_mod_inverse (z2, z2, q, ctx);
BIGNUM * res = BN_new (); // not from ctx
BN_mod_mul(res, x2, z2, q, ctx);
BN_CTX_end (ctx);
return res;
}
void Ed25519::ScalarMul (const uint8_t * p, const uint8_t * e, uint8_t * buf, BN_CTX * ctx) const
{
BIGNUM * p1 = DecodeBN<32> (p);
uint8_t k[32];
memcpy (k, e, 32);
k[0] &= 248; k[31] &= 127; k[31] |= 64;
BIGNUM * n = DecodeBN<32> (k);
BIGNUM * q1 = ScalarMul (p1, n, ctx);
EncodeBN (q1, buf, 32);
BN_free (p1); BN_free (n); BN_free (q1);
}
void Ed25519::ScalarMulB (const uint8_t * e, uint8_t * buf, BN_CTX * ctx) const
{
BIGNUM *p1 = BN_new (); BN_set_word (p1, 9);
uint8_t k[32];
memcpy (k, e, 32);
k[0] &= 248; k[31] &= 127; k[31] |= 64;
BIGNUM * n = DecodeBN<32> (k);
BIGNUM * q1 = ScalarMul (p1, n, ctx);
EncodeBN (q1, buf, 32);
BN_free (p1); BN_free (n); BN_free (q1);
}
#endif
void Ed25519::BlindPublicKey (const uint8_t * pub, const uint8_t * seed, uint8_t * blinded)
{
BN_CTX * ctx = BN_CTX_new ();

View File

@@ -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
*
@@ -84,10 +84,7 @@ namespace crypto
EDDSAPoint GeneratePublicKey (const uint8_t * expandedPrivateKey, BN_CTX * ctx) const;
EDDSAPoint DecodePublicKey (const uint8_t * buf, BN_CTX * ctx) const;
void EncodePublicKey (const EDDSAPoint& publicKey, uint8_t * buf, BN_CTX * ctx) const;
#if !OPENSSL_X25519
void ScalarMul (const uint8_t * p, const uint8_t * e, uint8_t * buf, BN_CTX * ctx) const; // p is point, e is number for x25519
void ScalarMulB (const uint8_t * e, uint8_t * buf, BN_CTX * ctx) const;
#endif
void BlindPublicKey (const uint8_t * pub, const uint8_t * seed, uint8_t * blinded); // for encrypted LeaseSet2, pub - 32, seed - 64, blinded - 32
void BlindPrivateKey (const uint8_t * priv, const uint8_t * seed, uint8_t * blindedPriv, uint8_t * blindedPub); // for encrypted LeaseSet2, pub - 32, seed - 64, blinded - 32
@@ -115,11 +112,6 @@ namespace crypto
BIGNUM * DecodeBN (const uint8_t * buf) const;
void EncodeBN (const BIGNUM * bn, uint8_t * buf, size_t len) const;
#if !OPENSSL_X25519
// for x25519
BIGNUM * ScalarMul (const BIGNUM * p, const BIGNUM * e, BN_CTX * ctx) const;
#endif
private:
BIGNUM * q, * l, * d, * I;

View File

@@ -7,10 +7,11 @@
*/
#include <algorithm>
#include <boost/filesystem.hpp>
#if defined(MAC_OSX)
#if !STD_FILESYSTEM
#include <boost/system/system_error.hpp>
#endif
#include <TargetConditionals.h>
#endif
@@ -25,6 +26,14 @@
#include "Log.h"
#include "Garlic.h"
#if STD_FILESYSTEM
#include <filesystem>
namespace fs_lib = std::filesystem;
#else
#include <boost/filesystem.hpp>
namespace fs_lib = boost::filesystem;
#endif
namespace i2p {
namespace fs {
std::string appName = "i2pd";
@@ -54,15 +63,17 @@ 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
return dataDirUTF8;
int size = MultiByteToWideChar(CP_ACP, 0,
dataDir.c_str(), dataDir.size(), nullptr, 0);
std::wstring utf16Str(size, L'\0');
MultiByteToWideChar(CP_ACP, 0,
dataDir.c_str(), dataDir.size(), &utf16Str[0], size);
int utf8Size = WideCharToMultiByte(CP_UTF8, 0,
utf16Str.c_str(), utf16Str.size(), nullptr, 0, nullptr, nullptr);
std::string utf8Str(utf8Size, '\0');
WideCharToMultiByte(CP_UTF8, 0,
utf16Str.c_str(), utf16Str.size(), &utf8Str[0], utf8Size, nullptr, nullptr);
return utf8Str;
#else
return dataDir; // linux, osx, android uses UTF-8 by default
#endif
@@ -91,10 +102,10 @@ namespace fs {
}
else
{
#if (BOOST_VERSION >= 108500)
dataDir = boost::filesystem::path(commonAppData).string() + "\\" + appName;
#if ((BOOST_VERSION >= 108500) || STD_FILESYSTEM)
dataDir = fs_lib::path(commonAppData).string() + "\\" + appName;
#else
dataDir = boost::filesystem::wpath(commonAppData).string() + "\\" + appName;
dataDir = fs_lib::wpath(commonAppData).string() + "\\" + appName;
#endif
}
#else
@@ -120,14 +131,14 @@ namespace fs {
}
else
{
#if (BOOST_VERSION >= 108500)
auto execPath = boost::filesystem::path(localAppData).parent_path();
#if ((BOOST_VERSION >= 108500) || STD_FILESYSTEM)
auto execPath = fs_lib::path(localAppData).parent_path();
#else
auto execPath = boost::filesystem::wpath(localAppData).parent_path();
auto execPath = fs_lib::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
if(fs_lib::exists(execPath/"i2pd.conf")) // TODO: magic string
{
dataDir = execPath.string ();
} else // otherwise %appdata%
@@ -143,10 +154,10 @@ namespace fs {
}
else
{
#if (BOOST_VERSION >= 108500)
dataDir = boost::filesystem::path(localAppData).string() + "\\" + appName;
#if ((BOOST_VERSION >= 108500) || STD_FILESYSTEM)
dataDir = fs_lib::path(localAppData).string() + "\\" + appName;
#else
dataDir = boost::filesystem::wpath(localAppData).string() + "\\" + appName;
dataDir = fs_lib::wpath(localAppData).string() + "\\" + appName;
#endif
}
}
@@ -169,7 +180,7 @@ namespace fs {
#if defined(ANDROID)
const char * ext = getenv("EXTERNAL_STORAGE");
if (!ext) ext = "/sdcard";
if (boost::filesystem::exists(ext))
if (fs_lib::exists(ext))
{
dataDir = std::string (ext) + "/" + appName;
return;
@@ -202,16 +213,16 @@ namespace fs {
}
bool Init() {
if (!boost::filesystem::exists(dataDir))
boost::filesystem::create_directory(dataDir);
if (!fs_lib::exists(dataDir))
fs_lib::create_directory(dataDir);
std::string destinations = DataDirPath("destinations");
if (!boost::filesystem::exists(destinations))
boost::filesystem::create_directory(destinations);
if (!fs_lib::exists(destinations))
fs_lib::create_directory(destinations);
std::string tags = DataDirPath("tags");
if (!boost::filesystem::exists(tags))
boost::filesystem::create_directory(tags);
if (!fs_lib::exists(tags))
fs_lib::create_directory(tags);
else
i2p::garlic::CleanUpTagsFiles ();
@@ -219,13 +230,13 @@ namespace fs {
}
bool ReadDir(const std::string & path, std::vector<std::string> & files) {
if (!boost::filesystem::exists(path))
if (!fs_lib::exists(path))
return false;
boost::filesystem::directory_iterator it(path);
boost::filesystem::directory_iterator end;
fs_lib::directory_iterator it(path);
fs_lib::directory_iterator end;
for ( ; it != end; it++) {
if (!boost::filesystem::is_regular_file(it->status()))
if (!fs_lib::is_regular_file(it->status()))
continue;
files.push_back(it->path().string());
}
@@ -234,29 +245,42 @@ namespace fs {
}
bool Exists(const std::string & path) {
return boost::filesystem::exists(path);
return fs_lib::exists(path);
}
uint32_t GetLastUpdateTime (const std::string & path)
{
if (!boost::filesystem::exists(path))
if (!fs_lib::exists(path))
return 0;
#if STD_FILESYSTEM
std::error_code ec;
auto t = std::filesystem::last_write_time (path, ec);
if (ec) return 0;
/*#if __cplusplus >= 202002L // C++ 20 or higher
const auto sctp = std::chrono::clock_cast<std::chrono::system_clock>(t);
#else */ // TODO: wait until implemented
const auto sctp = std::chrono::time_point_cast<std::chrono::system_clock::duration>(
t - decltype(t)::clock::now() + std::chrono::system_clock::now());
/*#endif */
return std::chrono::system_clock::to_time_t(sctp);
#else
boost::system::error_code ec;
auto t = boost::filesystem::last_write_time (path, ec);
return ec ? 0 : t;
#endif
}
bool Remove(const std::string & path) {
if (!boost::filesystem::exists(path))
if (!fs_lib::exists(path))
return false;
return boost::filesystem::remove(path);
return fs_lib::remove(path);
}
bool CreateDirectory (const std::string& path)
{
if (boost::filesystem::exists(path) && boost::filesystem::is_directory (boost::filesystem::status (path)))
if (fs_lib::exists(path) && fs_lib::is_directory (fs_lib::status (path)))
return true;
return boost::filesystem::create_directory(path);
return fs_lib::create_directory(path);
}
void HashedStorage::SetPlace(const std::string &path) {
@@ -264,18 +288,18 @@ namespace fs {
}
bool HashedStorage::Init(const char * chars, size_t count) {
if (!boost::filesystem::exists(root)) {
boost::filesystem::create_directories(root);
if (!fs_lib::exists(root)) {
fs_lib::create_directories(root);
}
for (size_t i = 0; i < count; i++) {
auto p = root + i2p::fs::dirSep + prefix1 + chars[i];
if (boost::filesystem::exists(p))
if (fs_lib::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))
if (fs_lib::create_directory(p, ec))
continue;
switch (ec.value()) {
case boost::system::errc::file_exists:
@@ -285,7 +309,7 @@ namespace fs {
throw boost::system::system_error( ec, __func__ );
}
#else
if (boost::filesystem::create_directory(p))
if (fs_lib::create_directory(p))
continue; /* ^ throws exception on failure */
#endif
return false;
@@ -308,9 +332,9 @@ namespace fs {
void HashedStorage::Remove(const std::string & ident) {
std::string path = Path(ident);
if (!boost::filesystem::exists(path))
if (!fs_lib::exists(path))
return;
boost::filesystem::remove(path);
fs_lib::remove(path);
}
void HashedStorage::Traverse(std::vector<std::string> & files) {
@@ -321,12 +345,12 @@ namespace fs {
void HashedStorage::Iterate(FilenameVisitor v)
{
boost::filesystem::path p(root);
boost::filesystem::recursive_directory_iterator it(p);
boost::filesystem::recursive_directory_iterator end;
fs_lib::path p(root);
fs_lib::recursive_directory_iterator it(p);
fs_lib::recursive_directory_iterator end;
for ( ; it != end; it++) {
if (!boost::filesystem::is_regular_file( it->status() ))
if (!fs_lib::is_regular_file( it->status() ))
continue;
const std::string & t = it->path().string();
v(t);

View File

@@ -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
*
@@ -15,6 +15,16 @@
#include <sstream>
#include <functional>
#ifndef STD_FILESYSTEM
# if (_WIN32 && __GNUG__) // MinGW GCC somehow incorrectly converts paths
# define STD_FILESYSTEM 0
# elif (!TARGET_OS_SIMULATOR && __has_include(<filesystem>)) // supports std::filesystem
# define STD_FILESYSTEM 1
# else
# define STD_FILESYSTEM 0
# endif
#endif
namespace i2p {
namespace fs {
extern std::string dirSep;

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2023, The PurpleI2P Project
* Copyright (c) 2013-2025, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -7,7 +7,6 @@
*/
#include <string.h>
#include <openssl/evp.h>
#include <openssl/ssl.h>
#include "Crypto.h"
#include "FS.h"
@@ -25,6 +24,8 @@ namespace data
Families::~Families ()
{
for (auto it : m_SigningKeys)
if (it.second.first) EVP_PKEY_free (it.second.first);
}
void Families::LoadCertificate (const std::string& filename)
@@ -47,48 +48,16 @@ namespace data
cn += 3;
char * family = strstr (cn, ".family");
if (family) family[0] = 0;
}
auto pkey = X509_get_pubkey (cert);
int keyType = EVP_PKEY_base_id (pkey);
switch (keyType)
{
case EVP_PKEY_DSA:
// TODO:
break;
case EVP_PKEY_EC:
{
EC_KEY * ecKey = EVP_PKEY_get1_EC_KEY (pkey);
if (ecKey)
auto pkey = X509_get_pubkey (cert);
if (pkey)
{
if (!m_SigningKeys.emplace (cn, std::make_pair(pkey, (int)m_SigningKeys.size () + 1)).second)
{
auto group = EC_KEY_get0_group (ecKey);
if (group)
{
int curve = EC_GROUP_get_curve_name (group);
if (curve == NID_X9_62_prime256v1)
{
uint8_t signingKey[64];
BIGNUM * x = BN_new(), * y = BN_new();
EC_POINT_get_affine_coordinates_GFp (group,
EC_KEY_get0_public_key (ecKey), x, y, NULL);
i2p::crypto::bn2buf (x, signingKey, 32);
i2p::crypto::bn2buf (y, signingKey + 32, 32);
BN_free (x); BN_free (y);
verifier = std::make_shared<i2p::crypto::ECDSAP256Verifier>();
verifier->SetPublicKey (signingKey);
}
else
LogPrint (eLogWarning, "Family: elliptic curve ", curve, " is not supported");
}
EC_KEY_free (ecKey);
}
break;
}
default:
LogPrint (eLogWarning, "Family: Certificate key type ", keyType, " is not supported");
EVP_PKEY_free (pkey);
LogPrint (eLogError, "Family: Duplicated family name ", cn);
}
}
}
EVP_PKEY_free (pkey);
if (verifier && cn)
m_SigningKeys.emplace (cn, std::make_pair(verifier, (int)m_SigningKeys.size () + 1));
}
SSL_free (ssl);
}
@@ -121,23 +90,31 @@ namespace data
}
bool Families::VerifyFamily (const std::string& family, const IdentHash& ident,
const char * signature, const char * key) const
std::string_view signature, const char * key) const
{
uint8_t buf[100], signatureBuf[64];
size_t len = family.length (), signatureLen = strlen (signature);
size_t len = family.length ();
if (len + 32 > 100)
{
LogPrint (eLogError, "Family: ", family, " is too long");
return false;
}
memcpy (buf, family.c_str (), len);
memcpy (buf + len, (const uint8_t *)ident, 32);
len += 32;
Base64ToByteStream (signature, signatureLen, signatureBuf, 64);
auto it = m_SigningKeys.find (family);
if (it != m_SigningKeys.end ())
return it->second.first->Verify (buf, len, signatureBuf);
if (it != m_SigningKeys.end () && it->second.first)
{
memcpy (buf, family.c_str (), len);
memcpy (buf + len, (const uint8_t *)ident, 32);
len += 32;
auto signatureBufLen = Base64ToByteStream (signature.data (), signature.length (), signatureBuf, 64);
if (signatureBufLen)
{
EVP_MD_CTX * ctx = EVP_MD_CTX_create ();
EVP_DigestVerifyInit (ctx, NULL, NULL, NULL, it->second.first);
auto ret = EVP_DigestVerify (ctx, signatureBuf, signatureBufLen, buf, len);
EVP_MD_CTX_destroy (ctx);
return ret;
}
}
// TODO: process key
return true;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2022, The PurpleI2P Project
* Copyright (c) 2013-2025, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -11,8 +11,9 @@
#include <map>
#include <string>
#include <string_view>
#include <memory>
#include "Signature.h"
#include <openssl/evp.h>
#include "Identity.h"
namespace i2p
@@ -28,7 +29,7 @@ namespace data
~Families ();
void LoadCertificates ();
bool VerifyFamily (const std::string& family, const IdentHash& ident,
const char * signature, const char * key = nullptr) const;
std::string_view signature, const char * key = nullptr) const;
FamilyID GetFamilyID (const std::string& family) const;
private:
@@ -37,7 +38,7 @@ namespace data
private:
std::map<std::string, std::pair<std::shared_ptr<i2p::crypto::Verifier>, FamilyID> > m_SigningKeys; // family -> (verifier, id)
std::map<std::string, std::pair<EVP_PKEY *, FamilyID> > m_SigningKeys; // family -> (verification pkey, id)
};
std::string CreateFamilySignature (const std::string& family, const IdentHash& ident);

View File

@@ -160,7 +160,7 @@ namespace garlic
uint8_t iv[32]; // IV is first 16 bytes
SHA256(elGamal.preIV, 32, iv);
m_Destination->Encrypt ((uint8_t *)&elGamal, buf);
m_Encryption.SetIV (iv);
m_IV = iv;
buf += 514;
len += 514;
}
@@ -170,7 +170,7 @@ namespace garlic
memcpy (buf, tag, 32);
uint8_t iv[32]; // IV is first 16 bytes
SHA256(tag, 32, iv);
m_Encryption.SetIV (iv);
m_IV = iv;
buf += 32;
len += 32;
}
@@ -210,7 +210,7 @@ namespace garlic
size_t rem = blockSize % 16;
if (rem)
blockSize += (16-rem); //padding
m_Encryption.Encrypt(buf, blockSize, buf);
m_Encryption.Encrypt(buf, blockSize, m_IV, buf);
return blockSize;
}
@@ -426,7 +426,8 @@ namespace garlic
}
GarlicDestination::GarlicDestination (): m_NumTags (32), // 32 tags by default
m_PayloadBuffer (nullptr), m_NumRatchetInboundTags (0) // 0 means standard
m_PayloadBuffer (nullptr), m_LastIncomingSessionTimestamp (0),
m_NumRatchetInboundTags (0) // 0 means standard
{
}
@@ -513,8 +514,7 @@ namespace garlic
{
uint8_t iv[32]; // IV is first 16 bytes
SHA256(buf, 32, iv);
decryption->SetIV (iv);
decryption->Decrypt (buf + 32, length - 32, buf + 32);
decryption->Decrypt (buf + 32, length - 32, iv, buf + 32);
HandleAESBlock (buf + 32, length - 32, decryption, msg->from);
found = true;
}
@@ -532,43 +532,23 @@ namespace garlic
auto decryption = std::make_shared<AESDecryption>(elGamal.sessionKey);
uint8_t iv[32]; // IV is first 16 bytes
SHA256(elGamal.preIV, 32, iv);
decryption->SetIV (iv);
decryption->Decrypt(buf + 514, length - 514, buf + 514);
decryption->Decrypt(buf + 514, length - 514, iv, buf + 514);
HandleAESBlock (buf + 514, length - 514, decryption, msg->from);
}
else if (SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD))
{
// otherwise ECIESx25519
auto session = std::make_shared<ECIESX25519AEADRatchetSession> (this, false); // incoming
if (!session->HandleNextMessage (buf, length, nullptr, 0))
{
// try to generate more tags for last tagset
if (m_LastTagset && (m_LastTagset->GetNextIndex () - m_LastTagset->GetTrimBehind () < 3*ECIESX25519_MAX_NUM_GENERATED_TAGS))
{
uint64_t missingTag; memcpy (&missingTag, buf, 8);
auto maxTags = std::max (m_NumRatchetInboundTags, ECIESX25519_MAX_NUM_GENERATED_TAGS);
LogPrint (eLogWarning, "Garlic: Trying to generate more ECIES-X25519-AEAD-Ratchet tags");
for (int i = 0; i < maxTags; i++)
{
auto nextTag = AddECIESx25519SessionNextTag (m_LastTagset);
if (!nextTag)
{
LogPrint (eLogError, "Garlic: Can't create new ECIES-X25519-AEAD-Ratchet tag for last tagset");
break;
}
if (nextTag == missingTag)
{
LogPrint (eLogDebug, "Garlic: Missing ECIES-X25519-AEAD-Ratchet tag was generated");
if (m_LastTagset->HandleNextMessage (buf, length, m_ECIESx25519Tags[nextTag].index))
found = true;
break;
}
}
if (!found) m_LastTagset = nullptr;
}
if (!found)
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
if (ts > m_LastIncomingSessionTimestamp + INCOMING_SESSIONS_MINIMAL_INTERVAL)
{
auto session = std::make_shared<ECIESX25519AEADRatchetSession> (this, false); // incoming
if (session->HandleNextMessage (buf, length, nullptr, 0))
m_LastIncomingSessionTimestamp = ts;
else
LogPrint (eLogError, "Garlic: Can't handle ECIES-X25519-AEAD-Ratchet message");
}
else
LogPrint (eLogWarning, "Garlic: Incoming sessions come too often");
}
else
LogPrint (eLogError, "Garlic: Failed to decrypt message");
@@ -583,9 +563,7 @@ namespace garlic
auto it = m_ECIESx25519Tags.find (tag);
if (it != m_ECIESx25519Tags.end ())
{
if (it->second.tagset && it->second.tagset->HandleNextMessage (buf, len, it->second.index))
m_LastTagset = it->second.tagset;
else
if (!it->second.tagset || !it->second.tagset->HandleNextMessage (buf, len, it->second.index))
LogPrint (eLogError, "Garlic: Can't handle ECIES-X25519-AEAD-Ratchet message");
m_ECIESx25519Tags.erase (it);
return true;
@@ -766,7 +744,8 @@ namespace garlic
}
std::shared_ptr<GarlicRoutingSession> GarlicDestination::GetRoutingSession (
std::shared_ptr<const i2p::data::RoutingDestination> destination, bool attachLeaseSet)
std::shared_ptr<const i2p::data::RoutingDestination> destination, bool attachLeaseSet,
bool requestNewIfNotFound)
{
if (destination->GetEncryptionType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD &&
SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD))
@@ -781,16 +760,17 @@ namespace garlic
if (session->IsInactive (i2p::util::GetSecondsSinceEpoch ()))
{
LogPrint (eLogDebug, "Garlic: Session restarted");
requestNewIfNotFound = true; // it's not a new session
session = nullptr;
}
}
if (!session)
if (!session && requestNewIfNotFound)
{
session = std::make_shared<ECIESX25519AEADRatchetSession> (this, true);
session->SetRemoteStaticKey (staticKey);
}
if (destination->IsDestination ())
session->SetDestination (destination->GetIdentHash ()); // TODO: remove
if (session && destination->IsDestination ())
session->SetDestination (destination->GetIdentHash ()); // NS or NSR
return session;
}
else
@@ -893,8 +873,6 @@ namespace garlic
}
if (numExpiredTags > 0)
LogPrint (eLogDebug, "Garlic: ", numExpiredTags, " ECIESx25519 tags expired for ", GetIdentHash().ToBase64 ());
if (m_LastTagset && m_LastTagset->IsExpired (ts))
m_LastTagset = nullptr;
}
void GarlicDestination::RemoveDeliveryStatusSession (uint32_t msgID)
@@ -928,7 +906,7 @@ namespace garlic
}
}
void GarlicDestination::SetLeaseSetUpdated ()
void GarlicDestination::SetLeaseSetUpdated (bool post)
{
{
std::unique_lock<std::mutex> l(m_SessionsMutex);
@@ -1031,9 +1009,7 @@ namespace garlic
case eGarlicDeliveryTypeDestination:
LogPrint (eLogDebug, "Garlic: Type destination");
buf += 32; // TODO: check destination
#if (__cplusplus >= 201703L) // C++ 17 or higher
[[fallthrough]];
#endif
// no break here
case eGarlicDeliveryTypeLocal:
{
@@ -1127,5 +1103,17 @@ namespace garlic
m_PayloadBuffer = new uint8_t[I2NP_MAX_MESSAGE_SIZE];
return m_PayloadBuffer;
}
bool GarlicDestination::AEADChaCha20Poly1305Encrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen,
const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len)
{
return m_Encryptor.Encrypt (msg, msgLen, ad, adLen, key, nonce, buf, len);
}
bool GarlicDestination::AEADChaCha20Poly1305Decrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen,
const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len)
{
return m_Decryptor.Decrypt (msg, msgLen, ad, adLen, key, nonce, buf, len);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2024, The PurpleI2P Project
* Copyright (c) 2013-2025, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -52,6 +52,7 @@ namespace garlic
const int OUTGOING_TAGS_CONFIRMATION_TIMEOUT = 10; // 10 seconds
const int LEASESET_CONFIRMATION_TIMEOUT = 4000; // in milliseconds
const int ROUTING_PATH_EXPIRATION_TIMEOUT = 120; // in seconds
const int INCOMING_SESSIONS_MINIMAL_INTERVAL = 200; // in milliseconds
struct SessionTag: public i2p::data::Tag<32>
{
@@ -109,13 +110,14 @@ namespace garlic
GarlicRoutingSession ();
virtual ~GarlicRoutingSession ();
virtual std::shared_ptr<I2NPMessage> WrapSingleMessage (std::shared_ptr<const I2NPMessage> msg) = 0;
virtual bool CleanupUnconfirmedTags () { return false; }; // for I2CP, override in ElGamalAESSession
virtual bool CleanupUnconfirmedTags () { return false; }; // for I2CP, override in ElGamalAESSession and ECIESX25519AEADRatchetSession
virtual bool MessageConfirmed (uint32_t msgID);
virtual bool IsRatchets () const { return false; };
virtual bool IsReadyToSend () const { return true; };
virtual bool IsTerminated () const { return !GetOwner (); };
virtual uint64_t GetLastActivityTimestamp () const { return 0; }; // non-zero for rathets only
virtual void SetAckRequestInterval (int interval) {}; // in milliseconds, override in ECIESX25519AEADRatchetSession
void SetLeaseSetUpdated ()
{
if (m_LeaseSetUpdateStatus != eLeaseSetDoNotSend) m_LeaseSetUpdateStatus = eLeaseSetUpdated;
@@ -204,6 +206,7 @@ namespace garlic
std::map<uint32_t, std::unique_ptr<UnconfirmedTags> > m_UnconfirmedTagsMsgs; // msgID->tags
i2p::crypto::CBCEncryption m_Encryption;
i2p::data::Tag<16> m_IV;
public:
@@ -234,11 +237,17 @@ namespace garlic
int GetNumTags () const { return m_NumTags; };
void SetNumRatchetInboundTags (int numTags) { m_NumRatchetInboundTags = numTags; };
int GetNumRatchetInboundTags () const { return m_NumRatchetInboundTags; };
std::shared_ptr<GarlicRoutingSession> GetRoutingSession (std::shared_ptr<const i2p::data::RoutingDestination> destination, bool attachLeaseSet);
std::shared_ptr<GarlicRoutingSession> GetRoutingSession (std::shared_ptr<const i2p::data::RoutingDestination> destination,
bool attachLeaseSet, bool requestNewIfNotFound = true);
void CleanupExpiredTags ();
void RemoveDeliveryStatusSession (uint32_t msgID);
std::shared_ptr<I2NPMessage> WrapMessageForRouter (std::shared_ptr<const i2p::data::RouterInfo> router,
std::shared_ptr<I2NPMessage> msg);
bool AEADChaCha20Poly1305Encrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen,
const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len);
bool AEADChaCha20Poly1305Decrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen,
const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len);
void AddSessionKey (const uint8_t * key, const uint8_t * tag); // one tag
void AddECIESx25519Key (const uint8_t * key, uint64_t tag); // one tag
@@ -253,7 +262,7 @@ namespace garlic
virtual void ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg);
virtual void ProcessDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg);
virtual void SetLeaseSetUpdated ();
virtual void SetLeaseSetUpdated (bool post = false);
virtual std::shared_ptr<const i2p::data::LocalLeaseSet> GetLeaseSet () = 0; // TODO
virtual std::shared_ptr<i2p::tunnel::TunnelPool> GetTunnelPool () const = 0;
@@ -284,15 +293,18 @@ namespace garlic
std::unordered_map<i2p::data::IdentHash, ElGamalAESSessionPtr> m_Sessions;
std::unordered_map<i2p::data::Tag<32>, ECIESX25519AEADRatchetSessionPtr> m_ECIESx25519Sessions; // static key -> session
uint8_t * m_PayloadBuffer; // for ECIESX25519AEADRatchet
uint64_t m_LastIncomingSessionTimestamp; // in milliseconds
// incoming
int m_NumRatchetInboundTags;
std::unordered_map<SessionTag, std::shared_ptr<AESDecryption>, std::hash<i2p::data::Tag<32> > > m_Tags;
std::unordered_map<uint64_t, ECIESX25519AEADRatchetIndexTagset> m_ECIESx25519Tags; // session tag -> session
ReceiveRatchetTagSetPtr m_LastTagset; // tagset last message came for
// DeliveryStatus
std::mutex m_DeliveryStatusSessionsMutex;
std::unordered_map<uint32_t, GarlicRoutingSessionPtr> m_DeliveryStatusSessions; // msgID -> session
// encryption
i2p::crypto::AEADChaCha20Poly1305Encryptor m_Encryptor;
i2p::crypto::AEADChaCha20Poly1305Decryptor m_Decryptor;
public:
// for HTTP only

View File

@@ -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,12 +40,13 @@ namespace http
inline bool is_http_method(const std::string & str) {
return std::find(HTTP_METHODS.begin(), HTTP_METHODS.end(), str) != std::end(HTTP_METHODS);
}
void strsplit(const std::string & line, std::vector<std::string> &tokens, char delim, std::size_t limit = 0) {
static void strsplit(std::stringstream& ss, std::vector<std::string> &tokens, char delim, std::size_t limit = 0)
{
std::size_t count = 0;
std::stringstream ss(line);
std::string token;
while (1) {
while (1)
{
count++;
if (limit > 0 && count >= limit)
delim = '\n'; /* reset delimiter */
@@ -55,21 +56,33 @@ namespace http
}
}
static std::pair<std::string, std::string> parse_header_line(const std::string& line)
static void strsplit(const std::string & line, std::vector<std::string> &tokens, char delim, std::size_t limit = 0)
{
std::stringstream ss{line};
strsplit (ss, tokens, delim, limit);
}
static void strsplit(std::string_view line, std::vector<std::string> &tokens, char delim, std::size_t limit = 0)
{
std::stringstream ss{std::string(line)};
strsplit (ss, tokens, delim, limit);
}
static std::pair<std::string, std::string> parse_header_line(std::string_view line)
{
std::size_t pos = 0;
std::size_t len = 1; /*: */
std::size_t max = line.length();
if ((pos = line.find(':', pos)) == std::string::npos)
return std::make_pair("", ""); // no ':' found
return std::pair{"", ""}; // no ':' found
if (pos + 1 < max) // ':' at the end of header is valid
{
while ((pos + len) < max && isspace(line.at(pos + len)))
len++;
if (len == 1)
return std::make_pair("", ""); // no following space, but something else
return std::pair{"", ""}; // no following space, but something else
}
return std::make_pair(line.substr(0, pos), line.substr(pos + len));
return std::pair{std::string (line.substr(0, pos)), std::string (line.substr(pos + len))};
}
void gen_rfc7231_date(std::string & out) {
@@ -83,15 +96,18 @@ namespace http
out = buf;
}
bool URL::parse(const char *str, std::size_t len) {
std::string url(str, len ? len : strlen(str));
return parse(url);
bool URL::parse(const char *str, std::size_t len)
{
return parse({str, len ? len : strlen(str)});
}
bool URL::parse(const std::string& url) {
bool URL::parse(std::string_view url)
{
if (url.empty ()) return false;
std::size_t pos_p = 0; /* < current parse position */
std::size_t pos_c = 0; /* < work position */
if(url.at(0) != '/' || pos_p > 0) {
if(url.at(0) != '/' || pos_p > 0)
{
std::size_t pos_s = 0;
/* schema */
@@ -141,7 +157,7 @@ namespace http
/* port[/path] */
pos_p = pos_c + 1;
pos_c = url.find('/', pos_p);
std::string port_str = (pos_c == std::string::npos)
std::string_view port_str = (pos_c == std::string::npos)
? url.substr(pos_p, std::string::npos)
: url.substr(pos_p, pos_c - pos_p);
/* stoi throws exception on failure, we don't need it */
@@ -253,7 +269,7 @@ namespace http
return host.rfind(".i2p") == ( host.size() - 4 );
}
void HTTPMsg::add_header(const char *name, std::string & value, bool replace) {
void HTTPMsg::add_header(const char *name, const std::string & value, bool replace) {
add_header(name, value.c_str(), replace);
}
@@ -272,12 +288,13 @@ namespace http
headers.erase(name);
}
int HTTPReq::parse(const char *buf, size_t len) {
std::string str(buf, len);
return parse(str);
int HTTPReq::parse(const char *buf, size_t len)
{
return parse({buf, len});
}
int HTTPReq::parse(const std::string& str) {
int HTTPReq::parse(std::string_view str)
{
enum { REQ_LINE, HEADER_LINE } expect = REQ_LINE;
std::size_t eoh = str.find(HTTP_EOH); /* request head size */
std::size_t eol = 0, pos = 0;
@@ -286,9 +303,11 @@ namespace http
if (eoh == std::string::npos)
return 0; /* str not contains complete request */
while ((eol = str.find(CRLF, pos)) != std::string::npos) {
if (expect == REQ_LINE) {
std::string line = str.substr(pos, eol - pos);
while ((eol = str.find(CRLF, pos)) != std::string::npos)
{
if (expect == REQ_LINE)
{
std::string_view line = str.substr(pos, eol - pos);
std::vector<std::string> tokens;
strsplit(line, tokens, ' ');
if (tokens.size() != 3)
@@ -307,7 +326,7 @@ namespace http
}
else
{
std::string line = str.substr(pos, eol - pos);
std::string_view line = str.substr(pos, eol - pos);
auto p = parse_header_line(line);
if (p.first.length () > 0)
headers.push_back (p);
@@ -413,12 +432,13 @@ namespace http
return length;
}
int HTTPRes::parse(const char *buf, size_t len) {
std::string str(buf, len);
return parse(str);
int HTTPRes::parse(const char *buf, size_t len)
{
return parse({buf,len});
}
int HTTPRes::parse(const std::string& str) {
int HTTPRes::parse(std::string_view str)
{
enum { RES_LINE, HEADER_LINE } expect = RES_LINE;
std::size_t eoh = str.find(HTTP_EOH); /* request head size */
std::size_t eol = 0, pos = 0;
@@ -426,9 +446,11 @@ namespace http
if (eoh == std::string::npos)
return 0; /* str not contains complete request */
while ((eol = str.find(CRLF, pos)) != std::string::npos) {
if (expect == RES_LINE) {
std::string line = str.substr(pos, eol - pos);
while ((eol = str.find(CRLF, pos)) != std::string::npos)
{
if (expect == RES_LINE)
{
std::string_view line = str.substr(pos, eol - pos);
std::vector<std::string> tokens;
strsplit(line, tokens, ' ', 3);
if (tokens.size() != 3)
@@ -442,8 +464,10 @@ namespace http
version = tokens[0];
status = tokens[2];
expect = HEADER_LINE;
} else {
std::string line = str.substr(pos, eol - pos);
}
else
{
std::string_view line = str.substr(pos, eol - pos);
auto p = parse_header_line(line);
if (p.first.length () > 0)
headers.insert (p);
@@ -508,14 +532,14 @@ namespace http
return ptr;
}
std::string UrlDecode(const std::string& data, bool allow_null)
std::string UrlDecode(std::string_view data, bool allow_null)
{
std::string decoded(data);
size_t pos = 0;
while ((pos = decoded.find('%', pos)) != std::string::npos)
{
char c = strtol(decoded.substr(pos + 1, 2).c_str(), NULL, 16);
if (c == '\0' && !allow_null)
char c = std::stol(decoded.substr(pos + 1, 2), nullptr, 16);
if (!c && !allow_null)
{
pos += 3;
continue;

View File

@@ -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
*
@@ -14,6 +14,7 @@
#include <list>
#include <sstream>
#include <string>
#include <string_view>
#include <vector>
namespace i2p
@@ -45,7 +46,7 @@ namespace http
* @return true on success, false on invalid url
*/
bool parse (const char *str, std::size_t len = 0);
bool parse (const std::string& url);
bool parse (std::string_view url);
/**
* @brief Parse query part of url to key/value map
@@ -69,7 +70,7 @@ namespace http
{
std::map<std::string, std::string> headers;
void add_header(const char *name, std::string & value, bool replace = false);
void add_header(const char *name, const std::string & value, bool replace = false);
void add_header(const char *name, const char *value, bool replace = false);
void del_header(const char *name);
@@ -92,7 +93,7 @@ namespace http
* @note Positive return value is a size of header
*/
int parse(const char *buf, size_t len);
int parse(const std::string& buf);
int parse(std::string_view buf);
/** @brief Serialize HTTP request to string */
std::string to_string();
@@ -128,7 +129,7 @@ namespace http
* @note Positive return value is a size of header
*/
int parse(const char *buf, size_t len);
int parse(const std::string& buf);
int parse(const std::string_view buf);
/**
* @brief Serialize HTTP response to string
@@ -161,7 +162,7 @@ namespace http
* @param null If set to true - decode also %00 sequence, otherwise - skip
* @return Decoded string
*/
std::string UrlDecode(const std::string& data, bool null = false);
std::string UrlDecode(std::string_view data, bool null = false);
/**
* @brief Merge HTTP response content with Transfer-Encoding: chunked

View File

@@ -10,20 +10,15 @@
#include <atomic>
#include "Base.h"
#include "Log.h"
#include "Crypto.h"
#include "I2PEndian.h"
#include "Timestamp.h"
#include "RouterContext.h"
#include "NetDb.hpp"
#include "Tunnel.h"
#include "Transports.h"
#include "Garlic.h"
#include "ECIESX25519AEADRatchetSession.h"
#include "TransitTunnel.h"
#include "I2NPProtocol.h"
#include "version.h"
using namespace i2p::transport;
namespace i2p
{
std::shared_ptr<I2NPMessage> NewI2NPMessage ()
@@ -376,337 +371,6 @@ namespace i2p
return !msg->GetPayload ()[DATABASE_STORE_TYPE_OFFSET]; // 0- RouterInfo
}
static bool HandleBuildRequestRecords (int num, uint8_t * records, uint8_t * clearText)
{
for (int i = 0; i < num; i++)
{
uint8_t * record = records + i*TUNNEL_BUILD_RECORD_SIZE;
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))
{
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.GetCongestionLevel (false) < CONGESTION_LEVEL_FULL)
{
auto transitTunnel = i2p::tunnel::CreateTransitTunnel (
bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET),
clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET,
bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET),
clearText + ECIES_BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET,
clearText + ECIES_BUILD_REQUEST_RECORD_IV_KEY_OFFSET,
clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_GATEWAY_FLAG,
clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG);
if (!i2p::tunnel::tunnels.AddTransitTunnel (transitTunnel))
retCode = 30;
}
else
retCode = 30; // always reject with bandwidth reason (30)
memset (record + ECIES_BUILD_RESPONSE_RECORD_OPTIONS_OFFSET, 0, 2); // no options
record[ECIES_BUILD_RESPONSE_RECORD_RET_OFFSET] = retCode;
// encrypt reply
i2p::crypto::CBCEncryption encryption;
for (int j = 0; j < num; j++)
{
uint8_t * reply = records + j*TUNNEL_BUILD_RECORD_SIZE;
if (j == i)
{
uint8_t nonce[12];
memset (nonce, 0, 12);
auto& noiseState = i2p::context.GetCurrentNoiseState ();
if (!i2p::crypto::AEADChaCha20Poly1305 (reply, TUNNEL_BUILD_RECORD_SIZE - 16,
noiseState.m_H, 32, noiseState.m_CK, nonce, reply, TUNNEL_BUILD_RECORD_SIZE, true)) // encrypt
{
LogPrint (eLogWarning, "I2NP: Reply AEAD encryption failed");
return false;
}
}
else
{
encryption.SetKey (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET);
encryption.SetIV (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_IV_OFFSET);
encryption.Encrypt(reply, TUNNEL_BUILD_RECORD_SIZE, reply);
}
}
return true;
}
}
return false;
}
static void HandleVariableTunnelBuildMsg (uint32_t replyMsgID, uint8_t * buf, size_t len)
{
int num = buf[0];
LogPrint (eLogDebug, "I2NP: VariableTunnelBuild ", num, " records");
if (num > i2p::tunnel::MAX_NUM_RECORDS)
{
LogPrint (eLogError, "I2NP: Too many records in VaribleTunnelBuild message ", num);
return;
}
if (len < num*TUNNEL_BUILD_RECORD_SIZE + 1)
{
LogPrint (eLogError, "I2NP: VaribleTunnelBuild message of ", num, " records is too short ", len);
return;
}
auto tunnel = i2p::tunnel::tunnels.GetPendingInboundTunnel (replyMsgID);
if (tunnel)
{
// endpoint of inbound tunnel
LogPrint (eLogDebug, "I2NP: VariableTunnelBuild reply for tunnel ", tunnel->GetTunnelID ());
if (tunnel->HandleTunnelBuildResponse (buf, len))
{
LogPrint (eLogInfo, "I2NP: Inbound tunnel ", tunnel->GetTunnelID (), " has been created");
tunnel->SetState (i2p::tunnel::eTunnelStateEstablished);
i2p::tunnel::tunnels.AddInboundTunnel (tunnel);
}
else
{
LogPrint (eLogInfo, "I2NP: Inbound tunnel ", tunnel->GetTunnelID (), " has been declined");
tunnel->SetState (i2p::tunnel::eTunnelStateBuildFailed);
}
}
else
{
uint8_t clearText[ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE];
if (HandleBuildRequestRecords (num, buf + 1, clearText))
{
if (clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG) // we are endpoint of outboud tunnel
{
// so we send it to reply tunnel
transports.SendMessage (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET,
CreateTunnelGatewayMsg (bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET),
eI2NPVariableTunnelBuildReply, buf, len,
bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET)));
}
else
transports.SendMessage (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET,
CreateI2NPMessage (eI2NPVariableTunnelBuild, buf, len,
bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET)));
}
}
}
static void HandleTunnelBuildMsg (uint8_t * buf, size_t len)
{
LogPrint (eLogWarning, "I2NP: TunnelBuild is too old for ECIES router");
}
static void HandleTunnelBuildReplyMsg (uint32_t replyMsgID, uint8_t * buf, size_t len, bool isShort)
{
int num = buf[0];
LogPrint (eLogDebug, "I2NP: TunnelBuildReplyMsg of ", num, " records replyMsgID=", replyMsgID);
if (num > i2p::tunnel::MAX_NUM_RECORDS)
{
LogPrint (eLogError, "I2NP: Too many records in TunnelBuildReply message ", num);
return;
}
size_t recordSize = isShort ? SHORT_TUNNEL_BUILD_RECORD_SIZE : TUNNEL_BUILD_RECORD_SIZE;
if (len < num*recordSize + 1)
{
LogPrint (eLogError, "I2NP: TunnelBuildReply message of ", num, " records is too short ", len);
return;
}
auto tunnel = i2p::tunnel::tunnels.GetPendingOutboundTunnel (replyMsgID);
if (tunnel)
{
// reply for outbound tunnel
if (tunnel->HandleTunnelBuildResponse (buf, len))
{
LogPrint (eLogInfo, "I2NP: Outbound tunnel ", tunnel->GetTunnelID (), " has been created");
tunnel->SetState (i2p::tunnel::eTunnelStateEstablished);
i2p::tunnel::tunnels.AddOutboundTunnel (tunnel);
}
else
{
LogPrint (eLogInfo, "I2NP: Outbound tunnel ", tunnel->GetTunnelID (), " has been declined");
tunnel->SetState (i2p::tunnel::eTunnelStateBuildFailed);
}
}
else
LogPrint (eLogWarning, "I2NP: Pending tunnel for message ", replyMsgID, " not found");
}
static void HandleShortTunnelBuildMsg (uint32_t replyMsgID, uint8_t * buf, size_t len)
{
int num = buf[0];
LogPrint (eLogDebug, "I2NP: ShortTunnelBuild ", num, " records");
if (num > i2p::tunnel::MAX_NUM_RECORDS)
{
LogPrint (eLogError, "I2NP: Too many records in ShortTunnelBuild message ", num);
return;
}
if (len < num*SHORT_TUNNEL_BUILD_RECORD_SIZE + 1)
{
LogPrint (eLogError, "I2NP: ShortTunnelBuild message of ", num, " records is too short ", len);
return;
}
auto tunnel = i2p::tunnel::tunnels.GetPendingInboundTunnel (replyMsgID);
if (tunnel)
{
// endpoint of inbound tunnel
LogPrint (eLogDebug, "I2NP: ShortTunnelBuild reply for tunnel ", tunnel->GetTunnelID ());
if (tunnel->HandleTunnelBuildResponse (buf, len))
{
LogPrint (eLogInfo, "I2NP: Inbound tunnel ", tunnel->GetTunnelID (), " has been created");
tunnel->SetState (i2p::tunnel::eTunnelStateEstablished);
i2p::tunnel::tunnels.AddInboundTunnel (tunnel);
}
else
{
LogPrint (eLogInfo, "I2NP: Inbound tunnel ", tunnel->GetTunnelID (), " has been declined");
tunnel->SetState (i2p::tunnel::eTunnelStateBuildFailed);
}
return;
}
const uint8_t * record = buf + 1;
for (int i = 0; i < num; i++)
{
if (!memcmp (record, (const uint8_t *)i2p::context.GetRouterInfo ().GetIdentHash (), 16))
{
LogPrint (eLogDebug, "I2NP: Short request record ", i, " is ours");
uint8_t clearText[SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE];
if (!i2p::context.DecryptTunnelShortRequestRecord (record + SHORT_REQUEST_RECORD_ENCRYPTED_OFFSET, clearText))
{
LogPrint (eLogWarning, "I2NP: Can't decrypt short request record ", i);
return;
}
if (clearText[SHORT_REQUEST_RECORD_LAYER_ENCRYPTION_TYPE]) // not AES
{
LogPrint (eLogWarning, "I2NP: Unknown layer encryption type ", clearText[SHORT_REQUEST_RECORD_LAYER_ENCRYPTION_TYPE], " in short request record");
return;
}
auto& noiseState = i2p::context.GetCurrentNoiseState ();
uint8_t replyKey[32]; // AEAD/Chacha20/Poly1305
i2p::crypto::AESKey layerKey, ivKey; // AES
i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "SMTunnelReplyKey", noiseState.m_CK);
memcpy (replyKey, noiseState.m_CK + 32, 32);
i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "SMTunnelLayerKey", noiseState.m_CK);
memcpy (layerKey, noiseState.m_CK + 32, 32);
bool isEndpoint = clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG;
if (isEndpoint)
{
i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "TunnelLayerIVKey", noiseState.m_CK);
memcpy (ivKey, noiseState.m_CK + 32, 32);
}
else
{
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.GetCongestionLevel (false) >= CONGESTION_LEVEL_FULL)
retCode = 30;
if (!retCode)
{
// create new transit tunnel
transitTunnel = i2p::tunnel::CreateTransitTunnel (
bufbe32toh (clearText + SHORT_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET),
clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET,
bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET),
layerKey, ivKey,
clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_GATEWAY_FLAG,
clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG);
if (!i2p::tunnel::tunnels.AddTransitTunnel (transitTunnel))
retCode = 30;
}
// encrypt reply
uint8_t nonce[12];
memset (nonce, 0, 12);
uint8_t * reply = buf + 1;
for (int j = 0; j < num; j++)
{
nonce[4] = j; // nonce is record #
if (j == i)
{
memset (reply + SHORT_RESPONSE_RECORD_OPTIONS_OFFSET, 0, 2); // no options
reply[SHORT_RESPONSE_RECORD_RET_OFFSET] = retCode;
if (!i2p::crypto::AEADChaCha20Poly1305 (reply, SHORT_TUNNEL_BUILD_RECORD_SIZE - 16,
noiseState.m_H, 32, replyKey, nonce, reply, SHORT_TUNNEL_BUILD_RECORD_SIZE, true)) // encrypt
{
LogPrint (eLogWarning, "I2NP: Short reply AEAD encryption failed");
return;
}
}
else
i2p::crypto::ChaCha20 (reply, SHORT_TUNNEL_BUILD_RECORD_SIZE, replyKey, nonce, reply);
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?
{
i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "RGarlicKeyAndTag", noiseState.m_CK);
uint64_t tag;
memcpy (&tag, noiseState.m_CK, 8);
// we send it to reply tunnel
transports.SendMessage (clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET,
CreateTunnelGatewayMsg (bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET),
i2p::garlic::WrapECIESX25519Message (replyMsg, noiseState.m_CK + 32, tag)));
}
else
{
// IBGW is local
uint32_t tunnelID = bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET);
auto tunnel = i2p::tunnel::tunnels.GetTunnel (tunnelID);
if (tunnel)
{
tunnel->SendTunnelDataMsg (replyMsg);
tunnel->FlushTunnelDataMsgs ();
}
else
LogPrint (eLogWarning, "I2NP: Tunnel ", tunnelID, " not found for short tunnel build reply");
}
}
else
{
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;
}
}
std::shared_ptr<I2NPMessage> CreateTunnelDataMsg (const uint8_t * buf)
{
auto msg = NewI2NPTunnelMessage (false);
@@ -802,41 +466,6 @@ namespace i2p
return l;
}
void HandleTunnelBuildI2NPMessage (std::shared_ptr<I2NPMessage> msg)
{
if (msg)
{
uint8_t typeID = msg->GetTypeID();
uint32_t msgID = msg->GetMsgID();
LogPrint (eLogDebug, "I2NP: Handling tunnel build message with len=", msg->GetLength(),", type=", (int)typeID, ", msgID=", (unsigned int)msgID);
uint8_t * payload = msg->GetPayload();
auto size = msg->GetPayloadLength();
switch (typeID)
{
case eI2NPVariableTunnelBuild:
HandleVariableTunnelBuildMsg (msgID, payload, size);
break;
case eI2NPShortTunnelBuild:
HandleShortTunnelBuildMsg (msgID, payload, size);
break;
case eI2NPVariableTunnelBuildReply:
HandleTunnelBuildReplyMsg (msgID, payload, size, false);
break;
case eI2NPShortTunnelBuildReply:
HandleTunnelBuildReplyMsg (msgID, payload, size, true);
break;
case eI2NPTunnelBuild:
HandleTunnelBuildMsg (payload, size);
break;
case eI2NPTunnelBuildReply:
// TODO:
break;
default:
LogPrint (eLogError, "I2NP: Unexpected message with type", (int)typeID, " during handling TBM; skipping");
}
}
}
void HandleI2NPMessage (std::shared_ptr<I2NPMessage> msg)
{
if (msg)
@@ -932,14 +561,8 @@ namespace i2p
void I2NPMessagesHandler::Flush ()
{
if (!m_TunnelMsgs.empty ())
{
i2p::tunnel::tunnels.PostTunnelData (m_TunnelMsgs);
m_TunnelMsgs.clear ();
}
if (!m_TunnelGatewayMsgs.empty ())
{
i2p::tunnel::tunnels.PostTunnelData (m_TunnelGatewayMsgs);
m_TunnelGatewayMsgs.clear ();
}
}
}

View File

@@ -13,6 +13,7 @@
#include <string.h>
#include <unordered_set>
#include <memory>
#include <list>
#include <functional>
#include "Crypto.h"
#include "I2PEndian.h"
@@ -107,7 +108,6 @@ namespace i2p
enum I2NPMessageType
{
eI2NPDummyMsg = 0,
eI2NPDatabaseStore = 1,
eI2NPDatabaseLookup = 2,
eI2NPDatabaseSearchReply = 3,
@@ -316,7 +316,6 @@ namespace tunnel
std::shared_ptr<I2NPMessage> CreateTunnelGatewayMsg (uint32_t tunnelID, std::shared_ptr<I2NPMessage> msg);
size_t GetI2NPMessageLength (const uint8_t * msg, size_t len);
void HandleTunnelBuildI2NPMessage (std::shared_ptr<I2NPMessage> msg);
void HandleI2NPMessage (std::shared_ptr<I2NPMessage> msg);
class I2NPMessagesHandler
@@ -329,7 +328,7 @@ namespace tunnel
private:
std::vector<std::shared_ptr<I2NPMessage> > m_TunnelMsgs, m_TunnelGatewayMsgs;
std::list<std::shared_ptr<I2NPMessage> > m_TunnelMsgs, m_TunnelGatewayMsgs;
};
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2024, The PurpleI2P Project
* Copyright (c) 2013-2025, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -27,18 +27,15 @@ namespace data
size_t Identity::FromBuffer (const uint8_t * buf, size_t len)
{
if ( len < DEFAULT_IDENTITY_SIZE ) {
// buffer too small, don't overflow
return 0;
}
memcpy (publicKey, buf, DEFAULT_IDENTITY_SIZE);
if (len < DEFAULT_IDENTITY_SIZE) return 0; // buffer too small, don't overflow
memcpy (this, buf, DEFAULT_IDENTITY_SIZE);
return DEFAULT_IDENTITY_SIZE;
}
IdentHash Identity::Hash () const
{
IdentHash hash;
SHA256(publicKey, DEFAULT_IDENTITY_SIZE, hash);
SHA256((const uint8_t *)this, DEFAULT_IDENTITY_SIZE, hash);
return hash;
}
@@ -262,11 +259,11 @@ namespace data
return fullLen;
}
size_t IdentityEx::FromBase64(const std::string& s)
size_t IdentityEx::FromBase64(std::string_view s)
{
const size_t slen = s.length();
std::vector<uint8_t> buf(slen); // binary data can't exceed base64
const size_t len = Base64ToByteStream (s.c_str(), slen, buf.data(), slen);
const size_t len = Base64ToByteStream (s.data(), slen, buf.data(), slen);
return FromBuffer (buf.data(), len);
}
@@ -728,9 +725,7 @@ namespace data
case SIGNING_KEY_TYPE_RSA_SHA384_3072:
case SIGNING_KEY_TYPE_RSA_SHA512_4096:
LogPrint (eLogWarning, "Identity: RSA signature type is not supported. Creating EdDSA");
#if (__cplusplus >= 201703L) // C++ 17 or higher
[[fallthrough]];
#endif
// no break here
case SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519:
i2p::crypto::CreateEDDSA25519RandomKeys (priv, pub);

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2024, The PurpleI2P Project
* Copyright (c) 2013-2025, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -12,6 +12,7 @@
#include <inttypes.h>
#include <string.h>
#include <string>
#include <string_view>
#include <memory>
#include <vector>
#include "Base.h"
@@ -99,7 +100,7 @@ namespace data
size_t FromBuffer (const uint8_t * buf, size_t len);
size_t ToBuffer (uint8_t * buf, size_t len) const;
size_t FromBase64(const std::string& s);
size_t FromBase64(std::string_view s);
std::string ToBase64 () const;
const Identity& GetStandardIdentity () const { return m_StandardIdentity; };

View File

@@ -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
*
@@ -425,6 +425,16 @@ namespace data
if (offset + 1 > len) return 0;
int numLeases = buf[offset]; offset++;
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
if (GetExpirationTime () > ts + LEASESET_EXPIRATION_TIME_THRESHOLD)
{
LogPrint (eLogWarning, "LeaseSet2: Expiration time is from future ", GetExpirationTime ()/1000LL);
return 0;
}
if (ts > m_PublishedTimestamp*1000LL + LEASESET_EXPIRATION_TIME_THRESHOLD)
{
LogPrint (eLogWarning, "LeaseSet2: Published time is too old ", m_PublishedTimestamp);
return 0;
}
if (IsStoreLeases ())
{
UpdateLeasesBegin ();
@@ -435,6 +445,11 @@ namespace data
lease.tunnelGateway = buf + offset; offset += 32; // gateway
lease.tunnelID = bufbe32toh (buf + offset); offset += 4; // tunnel ID
lease.endDate = bufbe32toh (buf + offset)*1000LL; offset += 4; // end date
if (lease.endDate > ts + LEASESET_EXPIRATION_TIME_THRESHOLD)
{
LogPrint (eLogWarning, "LeaseSet2: Lease end date is from future ", lease.endDate);
return 0;
}
UpdateLease (lease, ts);
}
UpdateLeasesEnd ();
@@ -728,25 +743,41 @@ namespace data
memset (m_Buffer + offset, 0, signingKeyLen);
offset += signingKeyLen;
// num leases
auto numLeasesPos = offset;
m_Buffer[offset] = num;
offset++;
// leases
m_Leases = m_Buffer + offset;
auto currentTime = i2p::util::GetMillisecondsSinceEpoch ();
int skipped = 0;
for (int i = 0; i < num; i++)
{
uint64_t ts = tunnels[i]->GetCreationTime () + i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT - i2p::tunnel::TUNNEL_EXPIRATION_THRESHOLD; // 1 minute before expiration
ts *= 1000; // in milliseconds
if (ts <= currentTime)
{
// already expired, skip
skipped++;
continue;
}
if (ts > m_ExpirationTime) m_ExpirationTime = ts;
// make sure leaseset is newer than previous, but adding some time to expiration date
ts += (currentTime - tunnels[i]->GetCreationTime ()*1000LL)*2/i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT; // up to 2 secs
memcpy (m_Buffer + offset, tunnels[i]->GetNextIdentHash (), 32);
offset += 32; // gateway id
htobe32buf (m_Buffer + offset, tunnels[i]->GetNextTunnelID ());
offset += 4; // tunnel id
uint64_t ts = tunnels[i]->GetCreationTime () + i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT - i2p::tunnel::TUNNEL_EXPIRATION_THRESHOLD; // 1 minute before expiration
ts *= 1000; // in milliseconds
if (ts > m_ExpirationTime) m_ExpirationTime = ts;
// make sure leaseset is newer than previous, but adding some time to expiration date
ts += (currentTime - tunnels[i]->GetCreationTime ()*1000LL)*2/i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT; // up to 2 secs
htobe64buf (m_Buffer + offset, ts);
offset += 8; // end date
}
if (skipped > 0)
{
// adjust num leases
if (skipped > num) skipped = num;
num -= skipped;
m_BufferLen -= skipped*LEASE_SIZE;
m_Buffer[numLeasesPos] = num;
}
// we don't sign it yet. must be signed later on
}
@@ -808,7 +839,7 @@ namespace data
LocalLeaseSet2::LocalLeaseSet2 (uint8_t storeType, const i2p::data::PrivateKeys& keys,
const KeySections& encryptionKeys, const std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> >& tunnels,
bool isPublic, bool isPublishedEncrypted):
bool isPublic, uint64_t publishedTimestamp, bool isPublishedEncrypted):
LocalLeaseSet (keys.GetPublic (), nullptr, 0)
{
auto identity = keys.GetPublic ();
@@ -837,8 +868,7 @@ namespace data
m_Buffer[0] = storeType;
// LS2 header
auto offset = identity->ToBuffer (m_Buffer + 1, m_BufferLen) + 1;
auto timestamp = i2p::util::GetSecondsSinceEpoch ();
htobe32buf (m_Buffer + offset, timestamp); offset += 4; // published timestamp (seconds)
htobe32buf (m_Buffer + offset, publishedTimestamp); offset += 4; // published timestamp (seconds)
uint8_t * expiresBuf = m_Buffer + offset; offset += 2; // expires, fill later
htobe16buf (m_Buffer + offset, flags); offset += 2; // flags
if (keys.IsOfflineSignature ())
@@ -859,29 +889,44 @@ namespace data
}
// leases
uint32_t expirationTime = 0; // in seconds
int skipped = 0; auto numLeasesPos = offset;
m_Buffer[offset] = num; offset++; // num leases
for (int i = 0; i < num; i++)
{
auto ts = tunnels[i]->GetCreationTime () + i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT - i2p::tunnel::TUNNEL_EXPIRATION_THRESHOLD; // in seconds, 1 minute before expiration
if (ts <= publishedTimestamp)
{
// already expired, skip
skipped++;
continue;
}
if (ts > expirationTime) expirationTime = ts;
memcpy (m_Buffer + offset, tunnels[i]->GetNextIdentHash (), 32);
offset += 32; // gateway id
htobe32buf (m_Buffer + offset, tunnels[i]->GetNextTunnelID ());
offset += 4; // tunnel id
auto ts = tunnels[i]->GetCreationTime () + i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT - i2p::tunnel::TUNNEL_EXPIRATION_THRESHOLD; // in seconds, 1 minute before expiration
if (ts > expirationTime) expirationTime = ts;
htobe32buf (m_Buffer + offset, ts);
offset += 4; // end date
}
if (skipped > 0)
{
// adjust num leases
if (skipped > num) skipped = num;
num -= skipped;
m_BufferLen -= skipped*LEASE2_SIZE;
m_Buffer[numLeasesPos] = num;
}
// update expiration
if (expirationTime)
{
SetExpirationTime (expirationTime*1000LL);
auto expires = (int)expirationTime - timestamp;
auto expires = (int)expirationTime - publishedTimestamp;
htobe16buf (expiresBuf, expires > 0 ? expires : 0);
}
else
{
// no tunnels or withdraw
SetExpirationTime (timestamp*1000LL);
SetExpirationTime (publishedTimestamp*1000LL);
memset (expiresBuf, 0, 2); // expires immeditely
}
// sign

View File

@@ -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
*
@@ -62,7 +62,8 @@ namespace data
const size_t LEASE_SIZE = 44; // 32 + 4 + 8
const size_t LEASE2_SIZE = 40; // 32 + 4 + 4
const uint8_t MAX_NUM_LEASES = 16;
const uint64_t LEASESET_EXPIRATION_TIME_THRESHOLD = 12*60*1000; // in milliseconds
const uint8_t NETDB_STORE_TYPE_LEASESET = 1;
class LeaseSet: public RoutingDestination
{
@@ -180,7 +181,7 @@ namespace data
private:
uint8_t m_StoreType;
uint32_t m_PublishedTimestamp = 0;
uint32_t m_PublishedTimestamp = 0; // seconds
bool m_IsPublic = true, m_IsPublishedEncrypted = false;
std::shared_ptr<i2p::crypto::Verifier> m_TransientVerifier;
CryptoKeyType m_EncryptionType;
@@ -256,7 +257,8 @@ namespace data
LocalLeaseSet2 (uint8_t storeType, const i2p::data::PrivateKeys& keys,
const KeySections& encryptionKeys,
const std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> >& tunnels,
bool isPublic, bool isPublishedEncrypted = false);
bool isPublic, uint64_t publishedTimestamp,
bool isPublishedEncrypted = false);
LocalLeaseSet2 (uint8_t storeType, std::shared_ptr<const IdentityEx> identity, const uint8_t * buf, size_t len); // from I2CP

View File

@@ -172,16 +172,6 @@ void LogPrint (std::stringstream& s, TValue&& arg) noexcept
s << std::forward<TValue>(arg);
}
#if (__cplusplus < 201703L) // below C++ 17
/** internal usage only -- folding args array to single string */
template<typename TValue, typename... TArgs>
void LogPrint (std::stringstream& s, TValue&& arg, TArgs&&... args) noexcept
{
LogPrint (s, std::forward<TValue>(arg));
LogPrint (s, std::forward<TArgs>(args)...);
}
#endif
/**
* @brief Create log message and send it to queue
* @param level Message level (eLogError, eLogInfo, ...)
@@ -194,13 +184,7 @@ void LogPrint (LogLevel level, TArgs&&... args) noexcept
// fold message to single string
std::stringstream ss;
#if (__cplusplus >= 201703L) // C++ 17 or higher
(LogPrint (ss, std::forward<TArgs>(args)), ...);
#else
LogPrint (ss, std::forward<TArgs>(args)...);
#endif
auto msg = std::make_shared<i2p::log::LogMsg>(level, std::time(nullptr), std::move(ss).str());
msg->tid = std::this_thread::get_id();
i2p::log::Logger().Append(msg);
@@ -217,11 +201,7 @@ void ThrowFatal (TArgs&&... args) noexcept
if (!f) return;
// fold message to single string
std::stringstream ss("");
#if (__cplusplus >= 201703L) // C++ 17 or higher
(LogPrint (ss, std::forward<TArgs>(args)), ...);
#else
LogPrint (ss, std::forward<TArgs>(args)...);
#endif
f (ss.str ());
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2024, The PurpleI2P Project
* Copyright (c) 2013-2025, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -42,28 +42,29 @@ namespace transport
delete[] m_SessionConfirmedBuffer;
}
void NTCP2Establisher::KeyDerivationFunction1 (const uint8_t * pub, i2p::crypto::X25519Keys& priv, const uint8_t * rs, const uint8_t * epub)
bool NTCP2Establisher::KeyDerivationFunction1 (const uint8_t * pub, i2p::crypto::X25519Keys& priv, const uint8_t * rs, const uint8_t * epub)
{
i2p::crypto::InitNoiseXKState (*this, rs);
// h = SHA256(h || epub)
MixHash (epub, 32);
// x25519 between pub and priv
uint8_t inputKeyMaterial[32];
priv.Agree (pub, inputKeyMaterial);
if (!priv.Agree (pub, inputKeyMaterial)) return false;
MixKey (inputKeyMaterial);
return true;
}
void NTCP2Establisher::KDF1Alice ()
bool NTCP2Establisher::KDF1Alice ()
{
KeyDerivationFunction1 (m_RemoteStaticKey, *m_EphemeralKeys, m_RemoteStaticKey, GetPub ());
return KeyDerivationFunction1 (m_RemoteStaticKey, *m_EphemeralKeys, m_RemoteStaticKey, GetPub ());
}
void NTCP2Establisher::KDF1Bob ()
bool NTCP2Establisher::KDF1Bob ()
{
KeyDerivationFunction1 (GetRemotePub (), i2p::context.GetNTCP2StaticKeys (), i2p::context.GetNTCP2StaticPublicKey (), GetRemotePub ());
return KeyDerivationFunction1 (GetRemotePub (), i2p::context.GetNTCP2StaticKeys (), i2p::context.GetNTCP2StaticPublicKey (), GetRemotePub ());
}
void NTCP2Establisher::KeyDerivationFunction2 (const uint8_t * sessionRequest, size_t sessionRequestLen, const uint8_t * epub)
bool NTCP2Establisher::KeyDerivationFunction2 (const uint8_t * sessionRequest, size_t sessionRequestLen, const uint8_t * epub)
{
MixHash (sessionRequest + 32, 32); // encrypted payload
@@ -74,33 +75,35 @@ namespace transport
// x25519 between remote pub and ephemaral priv
uint8_t inputKeyMaterial[32];
m_EphemeralKeys->Agree (GetRemotePub (), inputKeyMaterial);
if (!m_EphemeralKeys->Agree (GetRemotePub (), inputKeyMaterial)) return false;
MixKey (inputKeyMaterial);
return true;
}
void NTCP2Establisher::KDF2Alice ()
bool NTCP2Establisher::KDF2Alice ()
{
KeyDerivationFunction2 (m_SessionRequestBuffer, m_SessionRequestBufferLen, GetRemotePub ());
return KeyDerivationFunction2 (m_SessionRequestBuffer, m_SessionRequestBufferLen, GetRemotePub ());
}
void NTCP2Establisher::KDF2Bob ()
bool NTCP2Establisher::KDF2Bob ()
{
KeyDerivationFunction2 (m_SessionRequestBuffer, m_SessionRequestBufferLen, GetPub ());
return KeyDerivationFunction2 (m_SessionRequestBuffer, m_SessionRequestBufferLen, GetPub ());
}
void NTCP2Establisher::KDF3Alice ()
bool NTCP2Establisher::KDF3Alice ()
{
uint8_t inputKeyMaterial[32];
i2p::context.GetNTCP2StaticKeys ().Agree (GetRemotePub (), inputKeyMaterial);
if (!i2p::context.GetNTCP2StaticKeys ().Agree (GetRemotePub (), inputKeyMaterial)) return false;
MixKey (inputKeyMaterial);
return true;
}
void NTCP2Establisher::KDF3Bob ()
bool NTCP2Establisher::KDF3Bob ()
{
uint8_t inputKeyMaterial[32];
m_EphemeralKeys->Agree (m_RemoteStaticKey, inputKeyMaterial);
if (!m_EphemeralKeys->Agree (m_RemoteStaticKey, inputKeyMaterial)) return false;
MixKey (inputKeyMaterial);
return true;
}
void NTCP2Establisher::CreateEphemeralKey ()
@@ -108,20 +111,19 @@ namespace transport
m_EphemeralKeys = i2p::transport::transports.GetNextX25519KeysPair ();
}
void NTCP2Establisher::CreateSessionRequestMessage ()
bool NTCP2Establisher::CreateSessionRequestMessage (std::mt19937& rng)
{
// create buffer and fill padding
auto paddingLength = rand () % (NTCP2_SESSION_REQUEST_MAX_SIZE - 64); // message length doesn't exceed 287 bytes
auto paddingLength = rng () % (NTCP2_SESSION_REQUEST_MAX_SIZE - 64); // message length doesn't exceed 287 bytes
m_SessionRequestBufferLen = paddingLength + 64;
RAND_bytes (m_SessionRequestBuffer + 64, paddingLength);
// encrypt X
i2p::crypto::CBCEncryption encryption;
encryption.SetKey (m_RemoteIdentHash);
encryption.SetIV (m_IV);
encryption.Encrypt (GetPub (), 32, m_SessionRequestBuffer); // X
encryption.GetIV (m_IV); // save IV for SessionCreated
encryption.Encrypt (GetPub (), 32, m_IV, m_SessionRequestBuffer); // X
memcpy (m_IV, m_SessionRequestBuffer + 16, 16); // save last block as IV for SessionCreated
// encryption key for next block
KDF1Alice ();
if (!KDF1Alice ()) return false;
// fill options
uint8_t options[32]; // actual options size is 16 bytes
memset (options, 0, 16);
@@ -147,20 +149,20 @@ namespace transport
uint8_t nonce[12];
memset (nonce, 0, 12); // set nonce to zero
i2p::crypto::AEADChaCha20Poly1305 (options, 16, GetH (), 32, GetK (), nonce, m_SessionRequestBuffer + 32, 32, true); // encrypt
return true;
}
void NTCP2Establisher::CreateSessionCreatedMessage ()
bool NTCP2Establisher::CreateSessionCreatedMessage (std::mt19937& rng)
{
auto paddingLen = rand () % (NTCP2_SESSION_CREATED_MAX_SIZE - 64);
auto paddingLen = rng () % (NTCP2_SESSION_CREATED_MAX_SIZE - 64);
m_SessionCreatedBufferLen = paddingLen + 64;
RAND_bytes (m_SessionCreatedBuffer + 64, paddingLen);
// encrypt Y
i2p::crypto::CBCEncryption encryption;
encryption.SetKey (i2p::context.GetIdentHash ());
encryption.SetIV (m_IV);
encryption.Encrypt (GetPub (), 32, m_SessionCreatedBuffer); // Y
encryption.Encrypt (GetPub (), 32, m_IV, m_SessionCreatedBuffer); // Y
// encryption key for next block (m_K)
KDF2Bob ();
if (!KDF2Bob ()) return false;
uint8_t options[16];
memset (options, 0, 16);
htobe16buf (options + 2, paddingLen); // padLen
@@ -169,7 +171,7 @@ namespace transport
uint8_t nonce[12];
memset (nonce, 0, 12); // set nonce to zero
i2p::crypto::AEADChaCha20Poly1305 (options, 16, GetH (), 32, GetK (), nonce, m_SessionCreatedBuffer + 32, 32, true); // encrypt
return true;
}
void NTCP2Establisher::CreateSessionConfirmedMessagePart1 (const uint8_t * nonce)
@@ -184,17 +186,18 @@ namespace transport
i2p::crypto::AEADChaCha20Poly1305 (i2p::context.GetNTCP2StaticPublicKey (), 32, GetH (), 32, GetK (), nonce, m_SessionConfirmedBuffer, 48, true); // encrypt
}
void NTCP2Establisher::CreateSessionConfirmedMessagePart2 (const uint8_t * nonce)
bool NTCP2Establisher::CreateSessionConfirmedMessagePart2 (const uint8_t * nonce)
{
// part 2
// update AD again
MixHash (m_SessionConfirmedBuffer, 48);
// encrypt m3p2, it must be filled in SessionRequest
KDF3Alice ();
if (!KDF3Alice ()) return false;
uint8_t * m3p2 = m_SessionConfirmedBuffer + 48;
i2p::crypto::AEADChaCha20Poly1305 (m3p2, m3p2Len - 16, GetH (), 32, GetK (), nonce, m3p2, m3p2Len, true); // encrypt
// update h again
MixHash (m3p2, m3p2Len); //h = SHA256(h || ciphertext)
return true;
}
bool NTCP2Establisher::ProcessSessionRequestMessage (uint16_t& paddingLen, bool& clockSkew)
@@ -203,11 +206,14 @@ namespace transport
// decrypt X
i2p::crypto::CBCDecryption decryption;
decryption.SetKey (i2p::context.GetIdentHash ());
decryption.SetIV (i2p::context.GetNTCP2IV ());
decryption.Decrypt (m_SessionRequestBuffer, 32, GetRemotePub ());
decryption.GetIV (m_IV); // save IV for SessionCreated
decryption.Decrypt (m_SessionRequestBuffer, 32, i2p::context.GetNTCP2IV (), GetRemotePub ());
memcpy (m_IV, m_SessionRequestBuffer + 16, 16); // save last block as IV for SessionCreated
// decryption key for next block
KDF1Bob ();
if (!KDF1Bob ())
{
LogPrint (eLogWarning, "NTCP2: SessionRequest KDF failed");
return false;
}
// verify MAC and decrypt options block (32 bytes), use m_H as AD
uint8_t nonce[12], options[16];
memset (nonce, 0, 12); // set nonce to zero
@@ -259,10 +265,13 @@ namespace transport
// decrypt Y
i2p::crypto::CBCDecryption decryption;
decryption.SetKey (m_RemoteIdentHash);
decryption.SetIV (m_IV);
decryption.Decrypt (m_SessionCreatedBuffer, 32, GetRemotePub ());
decryption.Decrypt (m_SessionCreatedBuffer, 32, m_IV, GetRemotePub ());
// decryption key for next block (m_K)
KDF2Alice ();
if (!KDF2Alice ())
{
LogPrint (eLogWarning, "NTCP2: SessionCreated KDF failed");
return false;
}
// decrypt and verify MAC
uint8_t payload[16];
uint8_t nonce[12];
@@ -309,7 +318,11 @@ namespace transport
// update AD again
MixHash (m_SessionConfirmedBuffer, 48);
KDF3Bob ();
if (!KDF3Bob ())
{
LogPrint (eLogWarning, "NTCP2: SessionConfirmed Part2 KDF failed");
return false;
}
if (i2p::crypto::AEADChaCha20Poly1305 (m_SessionConfirmedBuffer + 48, m3p2Len - 16, GetH (), 32, GetK (), nonce, m3p2Buf, m3p2Len - 16, false)) // decrypt
// calculate new h again for KDF data
MixHash (m_SessionConfirmedBuffer + 48, m3p2Len); // h = SHA256(h || ciphertext)
@@ -327,6 +340,7 @@ namespace transport
m_Server (server), m_Socket (m_Server.GetService ()),
m_IsEstablished (false), m_IsTerminated (false),
m_Establisher (new NTCP2Establisher),
m_SendKey (nullptr), m_ReceiveKey (nullptr),
#if OPENSSL_SIPHASH
m_SendMDCtx(nullptr), m_ReceiveMDCtx (nullptr),
#else
@@ -349,7 +363,7 @@ namespace transport
LogPrint (eLogWarning, "NTCP2: Missing NTCP2 address");
}
m_NextRouterInfoResendTime = i2p::util::GetSecondsSinceEpoch () + NTCP2_ROUTERINFO_RESEND_INTERVAL +
rand ()%NTCP2_ROUTERINFO_RESEND_INTERVAL_THRESHOLD;
m_Server.GetRng ()() % NTCP2_ROUTERINFO_RESEND_INTERVAL_THRESHOLD;
}
NTCP2Session::~NTCP2Session ()
@@ -375,6 +389,8 @@ namespace transport
m_Socket.close ();
transports.PeerDisconnected (shared_from_this ());
m_Server.RemoveNTCP2Session (shared_from_this ());
if (!m_IntermediateQueue.empty ())
m_SendQueue.splice (m_SendQueue.end (), m_IntermediateQueue);
for (auto& it: m_SendQueue)
it->Drop ();
m_SendQueue.clear ();
@@ -404,14 +420,14 @@ namespace transport
void NTCP2Session::Done ()
{
m_Server.GetService ().post (std::bind (&NTCP2Session::Terminate, shared_from_this ()));
boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ()));
}
void NTCP2Session::Established ()
{
m_IsEstablished = true;
m_Establisher.reset (nullptr);
SetTerminationTimeout (NTCP2_TERMINATION_TIMEOUT);
SetTerminationTimeout (NTCP2_TERMINATION_TIMEOUT + m_Server.GetRng ()() % NTCP2_TERMINATION_TIMEOUT_VARIANCE);
SendQueue ();
transports.PeerConnected (shared_from_this ());
}
@@ -464,7 +480,12 @@ namespace transport
void NTCP2Session::SendSessionRequest ()
{
m_Establisher->CreateSessionRequestMessage ();
if (!m_Establisher->CreateSessionRequestMessage (m_Server.GetRng ()))
{
LogPrint (eLogWarning, "NTCP2: Send SessionRequest KDF failed");
boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ()));
return;
}
// send message
m_HandshakeInterval = i2p::util::GetMillisecondsSinceEpoch ();
boost::asio::async_write (m_Socket, boost::asio::buffer (m_Establisher->m_SessionRequestBuffer, m_Establisher->m_SessionRequestBufferLen), boost::asio::transfer_all (),
@@ -489,7 +510,6 @@ namespace transport
void NTCP2Session::HandleSessionRequestReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred)
{
(void) bytes_transferred;
if (ecode)
{
LogPrint (eLogWarning, "NTCP2: SessionRequest read error: ", ecode.message ());
@@ -497,38 +517,47 @@ namespace transport
}
else
{
LogPrint (eLogDebug, "NTCP2: SessionRequest received ", bytes_transferred);
uint16_t paddingLen = 0;
bool clockSkew = false;
if (m_Establisher->ProcessSessionRequestMessage (paddingLen, clockSkew))
{
if (clockSkew)
boost::asio::post (m_Server.GetEstablisherService (),
[s = shared_from_this (), bytes_transferred] ()
{
// we don't care about padding, send SessionCreated and close session
SendSessionCreated ();
m_Server.GetService ().post (std::bind (&NTCP2Session::Terminate, shared_from_this ()));
}
else if (paddingLen > 0)
{
if (paddingLen <= NTCP2_SESSION_REQUEST_MAX_SIZE - 64) // session request is 287 bytes max
{
boost::asio::async_read (m_Socket, boost::asio::buffer(m_Establisher->m_SessionRequestBuffer + 64, paddingLen), boost::asio::transfer_all (),
std::bind(&NTCP2Session::HandleSessionRequestPaddingReceived, shared_from_this (), std::placeholders::_1, std::placeholders::_2));
}
else
{
LogPrint (eLogWarning, "NTCP2: SessionRequest padding length ", (int)paddingLen, " is too long");
Terminate ();
}
}
else
SendSessionCreated ();
}
else
Terminate ();
s->ProcessSessionRequest (bytes_transferred);;
});
}
}
void NTCP2Session::ProcessSessionRequest (size_t len)
{
LogPrint (eLogDebug, "NTCP2: SessionRequest received ", len);
uint16_t paddingLen = 0;
bool clockSkew = false;
if (m_Establisher->ProcessSessionRequestMessage (paddingLen, clockSkew))
{
if (clockSkew)
{
// we don't care about padding, send SessionCreated and close session
SendSessionCreated ();
boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ()));
}
else if (paddingLen > 0)
{
if (paddingLen <= NTCP2_SESSION_REQUEST_MAX_SIZE - 64) // session request is 287 bytes max
{
boost::asio::async_read (m_Socket, boost::asio::buffer(m_Establisher->m_SessionRequestBuffer + 64, paddingLen), boost::asio::transfer_all (),
std::bind(&NTCP2Session::HandleSessionRequestPaddingReceived, shared_from_this (), std::placeholders::_1, std::placeholders::_2));
}
else
{
LogPrint (eLogWarning, "NTCP2: SessionRequest padding length ", (int)paddingLen, " is too long");
boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ()));
}
}
else
SendSessionCreated ();
}
else
boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ()));
}
void NTCP2Session::HandleSessionRequestPaddingReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred)
{
if (ecode)
@@ -537,12 +566,23 @@ namespace transport
Terminate ();
}
else
SendSessionCreated ();
{
boost::asio::post (m_Server.GetEstablisherService (),
[s = shared_from_this ()] ()
{
s->SendSessionCreated ();
});
}
}
void NTCP2Session::SendSessionCreated ()
{
m_Establisher->CreateSessionCreatedMessage ();
if (!m_Establisher->CreateSessionCreatedMessage (m_Server.GetRng ()))
{
LogPrint (eLogWarning, "NTCP2: Send SessionCreated KDF failed");
boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ()));
return;
}
// send message
m_HandshakeInterval = i2p::util::GetMillisecondsSinceEpoch ();
boost::asio::async_write (m_Socket, boost::asio::buffer (m_Establisher->m_SessionCreatedBuffer, m_Establisher->m_SessionCreatedBufferLen), boost::asio::transfer_all (),
@@ -559,35 +599,44 @@ namespace transport
else
{
m_HandshakeInterval = i2p::util::GetMillisecondsSinceEpoch () - m_HandshakeInterval;
LogPrint (eLogDebug, "NTCP2: SessionCreated received ", bytes_transferred);
uint16_t paddingLen = 0;
if (m_Establisher->ProcessSessionCreatedMessage (paddingLen))
{
if (paddingLen > 0)
boost::asio::post (m_Server.GetEstablisherService (),
[s = shared_from_this (), bytes_transferred] ()
{
if (paddingLen <= NTCP2_SESSION_CREATED_MAX_SIZE - 64) // session created is 287 bytes max
{
boost::asio::async_read (m_Socket, boost::asio::buffer(m_Establisher->m_SessionCreatedBuffer + 64, paddingLen), boost::asio::transfer_all (),
std::bind(&NTCP2Session::HandleSessionCreatedPaddingReceived, shared_from_this (), std::placeholders::_1, std::placeholders::_2));
}
else
{
LogPrint (eLogWarning, "NTCP2: SessionCreated padding length ", (int)paddingLen, " is too long");
Terminate ();
}
}
else
SendSessionConfirmed ();
}
else
{
if (GetRemoteIdentity ())
i2p::data::netdb.SetUnreachable (GetRemoteIdentity ()->GetIdentHash (), true); // assume wrong s key
Terminate ();
}
s->ProcessSessionCreated (bytes_transferred);
});
}
}
void NTCP2Session::ProcessSessionCreated (size_t len)
{
LogPrint (eLogDebug, "NTCP2: SessionCreated received ", len);
uint16_t paddingLen = 0;
if (m_Establisher->ProcessSessionCreatedMessage (paddingLen))
{
if (paddingLen > 0)
{
if (paddingLen <= NTCP2_SESSION_CREATED_MAX_SIZE - 64) // session created is 287 bytes max
{
boost::asio::async_read (m_Socket, boost::asio::buffer(m_Establisher->m_SessionCreatedBuffer + 64, paddingLen), boost::asio::transfer_all (),
std::bind(&NTCP2Session::HandleSessionCreatedPaddingReceived, shared_from_this (), std::placeholders::_1, std::placeholders::_2));
}
else
{
LogPrint (eLogWarning, "NTCP2: SessionCreated padding length ", (int)paddingLen, " is too long");
boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ()));
}
}
else
SendSessionConfirmed ();
}
else
{
if (GetRemoteIdentity ())
i2p::data::netdb.SetUnreachable (GetRemoteIdentity ()->GetIdentHash (), true); // assume wrong s key
boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ()));
}
}
void NTCP2Session::HandleSessionCreatedPaddingReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred)
{
if (ecode)
@@ -598,7 +647,11 @@ namespace transport
else
{
m_Establisher->m_SessionCreatedBufferLen += bytes_transferred;
SendSessionConfirmed ();
boost::asio::post (m_Server.GetEstablisherService (),
[s = shared_from_this ()] ()
{
s->SendSessionConfirmed ();
});
}
}
@@ -608,7 +661,12 @@ namespace transport
CreateNonce (1, nonce); // set nonce to 1
m_Establisher->CreateSessionConfirmedMessagePart1 (nonce);
memset (nonce, 0, 12); // set nonce back to 0
m_Establisher->CreateSessionConfirmedMessagePart2 (nonce);
if (!m_Establisher->CreateSessionConfirmedMessagePart2 (nonce))
{
LogPrint (eLogWarning, "NTCP2: Send SessionConfirmed Part2 KDF failed");
boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ()));
return;
}
// send message
boost::asio::async_write (m_Socket, boost::asio::buffer (m_Establisher->m_SessionConfirmedBuffer, m_Establisher->m3p2Len + 48), boost::asio::transfer_all (),
std::bind(&NTCP2Session::HandleSessionConfirmedSent, shared_from_this (), std::placeholders::_1, std::placeholders::_2));
@@ -660,6 +718,7 @@ namespace transport
void NTCP2Session::HandleSessionConfirmedReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred)
{
(void) bytes_transferred;
if (ecode)
{
LogPrint (eLogWarning, "NTCP2: SessionConfirmed read error: ", ecode.message ());
@@ -668,101 +727,152 @@ namespace transport
else
{
m_HandshakeInterval = i2p::util::GetMillisecondsSinceEpoch () - m_HandshakeInterval;
LogPrint (eLogDebug, "NTCP2: SessionConfirmed received");
// part 1
uint8_t nonce[12];
CreateNonce (1, nonce);
if (m_Establisher->ProcessSessionConfirmedMessagePart1 (nonce))
{
// part 2
std::vector<uint8_t> buf(m_Establisher->m3p2Len - 16); // -MAC
memset (nonce, 0, 12); // set nonce to 0 again
if (m_Establisher->ProcessSessionConfirmedMessagePart2 (nonce, buf.data ()))
boost::asio::post (m_Server.GetEstablisherService (),
[s = shared_from_this ()] ()
{
KeyDerivationFunctionDataPhase ();
// Bob data phase keys
m_SendKey = m_Kba;
m_ReceiveKey = m_Kab;
SetSipKeys (m_Sipkeysba, m_Sipkeysab);
memcpy (m_ReceiveIV.buf, m_Sipkeysab + 16, 8);
memcpy (m_SendIV.buf, m_Sipkeysba + 16, 8);
// payload
// process RI
if (buf[0] != eNTCP2BlkRouterInfo)
{
LogPrint (eLogWarning, "NTCP2: Unexpected block ", (int)buf[0], " in SessionConfirmed");
Terminate ();
return;
}
auto size = bufbe16toh (buf.data () + 1);
if (size > buf.size () - 3)
{
LogPrint (eLogError, "NTCP2: Unexpected RouterInfo size ", size, " in SessionConfirmed");
Terminate ();
return;
}
// TODO: check flag
i2p::data::RouterInfo ri (buf.data () + 4, size - 1); // 1 byte block type + 2 bytes size + 1 byte flag
if (ri.IsUnreachable ())
{
LogPrint (eLogError, "NTCP2: RouterInfo verification failed in SessionConfirmed from ", GetRemoteEndpoint ());
SendTerminationAndTerminate (eNTCP2RouterInfoSignatureVerificationFail);
return;
}
LogPrint(eLogDebug, "NTCP2: SessionConfirmed from ", GetRemoteEndpoint (),
" (", i2p::data::GetIdentHashAbbreviation (ri.GetIdentHash ()), ")");
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
if (ts > ri.GetTimestamp () + i2p::data::NETDB_MIN_EXPIRATION_TIMEOUT*1000LL) // 90 minutes
{
LogPrint (eLogError, "NTCP2: RouterInfo is too old in SessionConfirmed for ", (ts - ri.GetTimestamp ())/1000LL, " seconds");
SendTerminationAndTerminate (eNTCP2Message3Error);
return;
}
if (ts + i2p::data::NETDB_EXPIRATION_TIMEOUT_THRESHOLD*1000LL < ri.GetTimestamp ()) // 2 minutes
{
LogPrint (eLogError, "NTCP2: RouterInfo is from future for ", (ri.GetTimestamp () - ts)/1000LL, " seconds");
SendTerminationAndTerminate (eNTCP2Message3Error);
return;
}
auto addr = m_RemoteEndpoint.address ().is_v4 () ? ri.GetNTCP2V4Address () :
(i2p::util::net::IsYggdrasilAddress (m_RemoteEndpoint.address ()) ? ri.GetYggdrasilAddress () : ri.GetNTCP2V6Address ());
if (!addr || memcmp (m_Establisher->m_RemoteStaticKey, addr->s, 32))
{
LogPrint (eLogError, "NTCP2: Wrong static key in SessionConfirmed");
Terminate ();
return;
}
if (addr->IsPublishedNTCP2 () && m_RemoteEndpoint.address () != addr->host &&
(!m_RemoteEndpoint.address ().is_v6 () || (i2p::util::net::IsYggdrasilAddress (m_RemoteEndpoint.address ()) ?
memcmp (m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data () + 1, addr->host.to_v6 ().to_bytes ().data () + 1, 7) : // from the same yggdrasil subnet
memcmp (m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data (), addr->host.to_v6 ().to_bytes ().data (), 8)))) // temporary address
{
LogPrint (eLogError, "NTCP2: Host mismatch between published address ", addr->host, " and actual endpoint ", m_RemoteEndpoint.address ());
Terminate ();
return;
}
i2p::data::netdb.PostI2NPMsg (CreateI2NPMessage (eI2NPDummyMsg, buf.data () + 3, size)); // TODO: should insert ri and not parse it twice
// TODO: process options
// ready to communicate
auto existing = i2p::data::netdb.FindRouter (ri.GetRouterIdentity ()->GetIdentHash ()); // check if exists already
SetRemoteIdentity (existing ? existing->GetRouterIdentity () : ri.GetRouterIdentity ());
if (m_Server.AddNTCP2Session (shared_from_this (), true))
{
Established ();
ReceiveLength ();
}
else
Terminate ();
}
else
Terminate ();
}
else
Terminate ();
s->ProcessSessionConfirmed ();;
});
}
}
void NTCP2Session::ProcessSessionConfirmed ()
{
// run on establisher thread
LogPrint (eLogDebug, "NTCP2: SessionConfirmed received");
// part 1
uint8_t nonce[12];
CreateNonce (1, nonce);
if (m_Establisher->ProcessSessionConfirmedMessagePart1 (nonce))
{
// part 2
auto buf = std::make_shared<std::vector<uint8_t> > (m_Establisher->m3p2Len - 16); // -MAC
memset (nonce, 0, 12); // set nonce to 0 again
if (m_Establisher->ProcessSessionConfirmedMessagePart2 (nonce, buf->data ())) // TODO:handle in establisher thread
{
// payload
// RI block must be first
if ((*buf)[0] != eNTCP2BlkRouterInfo)
{
LogPrint (eLogWarning, "NTCP2: Unexpected block ", (int)(*buf)[0], " in SessionConfirmed");
boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ()));
return;
}
auto size = bufbe16toh (buf->data () + 1);
if (size > buf->size () - 3 || size > i2p::data::MAX_RI_BUFFER_SIZE + 1)
{
LogPrint (eLogError, "NTCP2: Unexpected RouterInfo size ", size, " in SessionConfirmed");
boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ()));
return;
}
boost::asio::post (m_Server.GetService (),
[s = shared_from_this (), buf, size] ()
{
s->EstablishSessionAfterSessionConfirmed (buf, size);
});
}
else
boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ()));
}
else
boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ()));
}
void NTCP2Session::EstablishSessionAfterSessionConfirmed (std::shared_ptr<std::vector<uint8_t> > buf, size_t size)
{
// run on main NTCP2 thread
KeyDerivationFunctionDataPhase ();
// Bob data phase keys
m_SendKey = m_Kba;
m_ReceiveKey = m_Kab;
SetSipKeys (m_Sipkeysba, m_Sipkeysab);
memcpy (m_ReceiveIV.buf, m_Sipkeysab + 16, 8);
memcpy (m_SendIV.buf, m_Sipkeysba + 16, 8);
// we need to set keys for SendTerminationAndTerminate
// TODO: check flag
i2p::data::RouterInfo ri (buf->data () + 4, size - 1); // 1 byte block type + 2 bytes size + 1 byte flag
if (ri.IsUnreachable ())
{
LogPrint (eLogError, "NTCP2: RouterInfo verification failed in SessionConfirmed from ", GetRemoteEndpoint ());
SendTerminationAndTerminate (eNTCP2RouterInfoSignatureVerificationFail);
return;
}
LogPrint(eLogDebug, "NTCP2: SessionConfirmed from ", GetRemoteEndpoint (),
" (", i2p::data::GetIdentHashAbbreviation (ri.GetIdentHash ()), ")");
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
if (ts > ri.GetTimestamp () + i2p::data::NETDB_MIN_EXPIRATION_TIMEOUT*1000LL) // 90 minutes
{
LogPrint (eLogError, "NTCP2: RouterInfo is too old in SessionConfirmed for ", (ts - ri.GetTimestamp ())/1000LL, " seconds");
SendTerminationAndTerminate (eNTCP2Message3Error);
return;
}
if (ts + i2p::data::NETDB_EXPIRATION_TIMEOUT_THRESHOLD*1000LL < ri.GetTimestamp ()) // 2 minutes
{
LogPrint (eLogError, "NTCP2: RouterInfo is from future for ", (ri.GetTimestamp () - ts)/1000LL, " seconds");
SendTerminationAndTerminate (eNTCP2Message3Error);
return;
}
// update RouterInfo in netdb
auto ri1 = i2p::data::netdb.AddRouterInfo (ri.GetBuffer (), ri.GetBufferLen ()); // ri1 points to one from netdb now
if (!ri1)
{
LogPrint (eLogError, "NTCP2: Couldn't update RouterInfo from SessionConfirmed in netdb");
Terminate ();
return;
}
bool isOlder = false;
if (ri.GetTimestamp () + i2p::data::NETDB_EXPIRATION_TIMEOUT_THRESHOLD*1000LL < ri1->GetTimestamp ())
{
// received RouterInfo is older than one in netdb
isOlder = true;
if (ri1->HasProfile ())
{
auto profile = i2p::data::GetRouterProfile (ri1->GetIdentHash ()); // retrieve profile
if (profile && profile->IsDuplicated ())
{
SendTerminationAndTerminate (eNTCP2Banned);
return;
}
}
}
auto addr = m_RemoteEndpoint.address ().is_v4 () ? ri1->GetNTCP2V4Address () :
(i2p::util::net::IsYggdrasilAddress (m_RemoteEndpoint.address ()) ? ri1->GetYggdrasilAddress () : ri1->GetNTCP2V6Address ());
if (!addr || memcmp (m_Establisher->m_RemoteStaticKey, addr->s, 32))
{
LogPrint (eLogError, "NTCP2: Wrong static key in SessionConfirmed");
Terminate ();
return;
}
if (addr->IsPublishedNTCP2 () && m_RemoteEndpoint.address () != addr->host &&
(!m_RemoteEndpoint.address ().is_v6 () || (i2p::util::net::IsYggdrasilAddress (m_RemoteEndpoint.address ()) ?
memcmp (m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data () + 1, addr->host.to_v6 ().to_bytes ().data () + 1, 7) : // from the same yggdrasil subnet
memcmp (m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data (), addr->host.to_v6 ().to_bytes ().data (), 8)))) // temporary address
{
if (isOlder) // older router?
i2p::data::UpdateRouterProfile (ri1->GetIdentHash (),
[](std::shared_ptr<i2p::data::RouterProfile> profile)
{
if (profile) profile->Duplicated (); // mark router as duplicated in profile
});
else
LogPrint (eLogInfo, "NTCP2: Host mismatch between published address ", addr->host, " and actual endpoint ", m_RemoteEndpoint.address ());
SendTerminationAndTerminate (eNTCP2Banned);
return;
}
// TODO: process options block
// ready to communicate
SetRemoteIdentity (ri1->GetRouterIdentity ());
if (m_Server.AddNTCP2Session (shared_from_this (), true))
{
Established ();
ReceiveLength ();
}
else
Terminate ();
}
void NTCP2Session::SetSipKeys (const uint8_t * sendSipKey, const uint8_t * receiveSipKey)
{
#if OPENSSL_SIPHASH
@@ -788,7 +898,11 @@ namespace transport
void NTCP2Session::ClientLogin ()
{
m_Establisher->CreateEphemeralKey ();
SendSessionRequest ();
boost::asio::post (m_Server.GetEstablisherService (),
[s = shared_from_this ()] ()
{
s->SendSessionRequest ();
});
}
void NTCP2Session::ServerLogin ()
@@ -886,7 +1000,7 @@ namespace transport
i2p::transport::transports.UpdateReceivedBytes (bytes_transferred + 2);
uint8_t nonce[12];
CreateNonce (m_ReceiveSequenceNumber, nonce); m_ReceiveSequenceNumber++;
if (i2p::crypto::AEADChaCha20Poly1305 (m_NextReceivedBuffer, m_NextReceivedLen-16, nullptr, 0, m_ReceiveKey, nonce, m_NextReceivedBuffer, m_NextReceivedLen, false))
if (m_Server.AEADChaCha20Poly1305Decrypt (m_NextReceivedBuffer, m_NextReceivedLen-16, nullptr, 0, m_ReceiveKey, nonce, m_NextReceivedBuffer, m_NextReceivedLen))
{
LogPrint (eLogDebug, "NTCP2: Received message decrypted");
ProcessNextFrame (m_NextReceivedBuffer, m_NextReceivedLen-16);
@@ -938,8 +1052,20 @@ namespace transport
break;
case eNTCP2BlkRouterInfo:
{
LogPrint (eLogDebug, "NTCP2: RouterInfo flag=", (int)frame[offset]);
i2p::data::netdb.PostI2NPMsg (CreateI2NPMessage (eI2NPDummyMsg, frame + offset, size));
LogPrint (eLogDebug, "NTCP2: RouterInfo flag=", (int)frame[offset]);
if (size <= i2p::data::MAX_RI_BUFFER_SIZE + 1)
{
auto newRi = i2p::data::netdb.AddRouterInfo (frame + offset + 1, size - 1);
if (newRi)
{
auto remoteIdentity = GetRemoteIdentity ();
if (remoteIdentity && remoteIdentity->GetIdentHash () == newRi->GetIdentHash ())
// peer's RouterInfo update
SetRemoteIdentity (newRi->GetIdentity ());
}
}
else
LogPrint (eLogInfo, "NTCP2: RouterInfo block is too long ", size);
break;
}
case eNTCP2BlkI2NPMessage:
@@ -1063,7 +1189,7 @@ namespace transport
}
uint8_t nonce[12];
CreateNonce (m_SendSequenceNumber, nonce); m_SendSequenceNumber++;
i2p::crypto::AEADChaCha20Poly1305Encrypt (encryptBufs, m_SendKey, nonce, macBuf); // encrypt buffers
m_Server.AEADChaCha20Poly1305Encrypt (encryptBufs, m_SendKey, nonce, macBuf); // encrypt buffers
SetNextSentFrameLength (totalLen + 16, first->GetNTCP2Header () - 5); // frame length right before first block
// send buffers
@@ -1094,7 +1220,7 @@ namespace transport
// encrypt
uint8_t nonce[12];
CreateNonce (m_SendSequenceNumber, nonce); m_SendSequenceNumber++;
i2p::crypto::AEADChaCha20Poly1305Encrypt ({ {m_NextSendBuffer + 2, payloadLen} }, m_SendKey, nonce, m_NextSendBuffer + payloadLen + 2);
m_Server.AEADChaCha20Poly1305Encrypt ({ {m_NextSendBuffer + 2, payloadLen} }, m_SendKey, nonce, m_NextSendBuffer + payloadLen + 2);
SetNextSentFrameLength (payloadLen + 16, m_NextSendBuffer);
// send
m_IsSending = true;
@@ -1121,7 +1247,7 @@ namespace transport
if (GetLastActivityTimestamp () > m_NextRouterInfoResendTime)
{
m_NextRouterInfoResendTime += NTCP2_ROUTERINFO_RESEND_INTERVAL +
rand ()%NTCP2_ROUTERINFO_RESEND_INTERVAL_THRESHOLD;
m_Server.GetRng ()() % NTCP2_ROUTERINFO_RESEND_INTERVAL_THRESHOLD;
SendRouterInfo ();
}
else
@@ -1174,7 +1300,7 @@ namespace transport
void NTCP2Session::MoveSendQueue (std::shared_ptr<NTCP2Session> other)
{
if (!other || m_SendQueue.empty ()) return;
std::vector<std::shared_ptr<I2NPMessage> > msgs;
std::list<std::shared_ptr<I2NPMessage> > msgs;
auto ts = i2p::util::GetMillisecondsSinceEpoch ();
for (auto it: m_SendQueue)
if (!it->IsExpired (ts))
@@ -1183,7 +1309,7 @@ namespace transport
it->Drop ();
m_SendQueue.clear ();
if (!msgs.empty ())
other->PostI2NPMessages (msgs);
other->SendI2NPMessages (msgs);
}
size_t NTCP2Session::CreatePaddingBlock (size_t msgLen, uint8_t * buf, size_t len)
@@ -1261,23 +1387,45 @@ namespace transport
void NTCP2Session::SendTerminationAndTerminate (NTCP2TerminationReason reason)
{
SendTermination (reason);
m_Server.GetService ().post (std::bind (&NTCP2Session::Terminate, shared_from_this ())); // let termination message go
boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::Terminate, shared_from_this ())); // let termination message go
}
void NTCP2Session::SendI2NPMessages (const std::vector<std::shared_ptr<I2NPMessage> >& msgs)
void NTCP2Session::SendI2NPMessages (std::list<std::shared_ptr<I2NPMessage> >& msgs)
{
m_Server.GetService ().post (std::bind (&NTCP2Session::PostI2NPMessages, shared_from_this (), msgs));
if (m_IsTerminated || msgs.empty ())
{
msgs.clear ();
return;
}
bool empty = false;
{
std::lock_guard<std::mutex> l(m_IntermediateQueueMutex);
empty = m_IntermediateQueue.empty ();
m_IntermediateQueue.splice (m_IntermediateQueue.end (), msgs);
}
if (empty)
boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::PostI2NPMessages, shared_from_this ()));
}
void NTCP2Session::PostI2NPMessages (std::vector<std::shared_ptr<I2NPMessage> > msgs)
void NTCP2Session::PostI2NPMessages ()
{
if (m_IsTerminated) return;
std::list<std::shared_ptr<I2NPMessage> > msgs;
{
std::lock_guard<std::mutex> l(m_IntermediateQueueMutex);
m_IntermediateQueue.swap (msgs);
}
bool isSemiFull = m_SendQueue.size () > NTCP2_MAX_OUTGOING_QUEUE_SIZE/2;
for (auto it: msgs)
if (isSemiFull && it->onDrop)
it->Drop (); // drop earlier because we can handle it
else
m_SendQueue.push_back (std::move (it));
if (isSemiFull)
{
for (auto it: msgs)
if (it->onDrop)
it->Drop (); // drop earlier because we can handle it
else
m_SendQueue.push_back (std::move (it));
}
else
m_SendQueue.splice (m_SendQueue.end (), msgs);
if (!m_IsSending && m_IsEstablished)
SendQueue ();
@@ -1293,12 +1441,19 @@ namespace transport
void NTCP2Session::SendLocalRouterInfo (bool update)
{
if (update || !IsOutgoing ()) // we send it in SessionConfirmed for outgoing session
m_Server.GetService ().post (std::bind (&NTCP2Session::SendRouterInfo, shared_from_this ()));
boost::asio::post (m_Server.GetService (), std::bind (&NTCP2Session::SendRouterInfo, shared_from_this ()));
}
i2p::data::RouterInfo::SupportedTransports NTCP2Session::GetTransportType () const
{
if (m_RemoteEndpoint.address ().is_v4 ()) return i2p::data::RouterInfo::eNTCP2V4;
return i2p::util::net::IsYggdrasilAddress (m_RemoteEndpoint.address ()) ? i2p::data::RouterInfo::eNTCP2V6Mesh : i2p::data::RouterInfo::eNTCP2V6;
}
NTCP2Server::NTCP2Server ():
RunnableServiceWithWork ("NTCP2"), m_TerminationTimer (GetService ()),
m_ProxyType(eNoProxy), m_Resolver(GetService ())
m_ProxyType(eNoProxy), m_Resolver(GetService ()),
m_Rng(i2p::util::GetMonotonicMicroseconds ()%1000000LL)
{
}
@@ -1309,6 +1464,7 @@ namespace transport
void NTCP2Server::Start ()
{
m_EstablisherService.Start ();
if (!IsRunning ())
{
StartIOService ();
@@ -1316,14 +1472,13 @@ namespace transport
{
LogPrint(eLogInfo, "NTCP2: Using proxy to connect to peers");
// TODO: resolve proxy until it is resolved
boost::asio::ip::tcp::resolver::query q(m_ProxyAddress, std::to_string(m_ProxyPort));
boost::system::error_code e;
auto itr = m_Resolver.resolve(q, e);
auto itr = m_Resolver.resolve(m_ProxyAddress, std::to_string(m_ProxyPort), e);
if(e)
LogPrint(eLogCritical, "NTCP2: Failed to resolve proxy ", e.message());
else
{
m_ProxyEndpoint.reset (new boost::asio::ip::tcp::endpoint(*itr));
m_ProxyEndpoint.reset (new boost::asio::ip::tcp::endpoint(*itr.begin ()));
if (m_ProxyEndpoint)
LogPrint(eLogDebug, "NTCP2: m_ProxyEndpoint ", *m_ProxyEndpoint);
}
@@ -1404,6 +1559,7 @@ namespace transport
void NTCP2Server::Stop ()
{
m_EstablisherService.Stop ();
{
// we have to copy it because Terminate changes m_NTCP2Sessions
auto ntcpSessions = m_NTCP2Sessions;
@@ -1483,7 +1639,7 @@ namespace transport
}
LogPrint (eLogDebug, "NTCP2: Connecting to ", conn->GetRemoteEndpoint (),
" (", i2p::data::GetIdentHashAbbreviation (conn->GetRemoteIdentity ()->GetIdentHash ()), ")");
GetService ().post([this, conn]()
boost::asio::post (GetService (), [this, conn]()
{
if (this->AddNTCP2Session (conn))
{
@@ -1641,7 +1797,8 @@ namespace transport
void NTCP2Server::ScheduleTermination ()
{
m_TerminationTimer.expires_from_now (boost::posix_time::seconds(NTCP2_TERMINATION_CHECK_TIMEOUT));
m_TerminationTimer.expires_from_now (boost::posix_time::seconds(
NTCP2_TERMINATION_CHECK_TIMEOUT + m_Rng () % NTCP2_TERMINATION_CHECK_TIMEOUT_VARIANCE));
m_TerminationTimer.async_wait (std::bind (&NTCP2Server::HandleTerminationTimer,
this, std::placeholders::_1));
}
@@ -1703,7 +1860,7 @@ namespace transport
LogPrint (eLogError, "NTCP2: Can't connect to unspecified address");
return;
}
GetService().post([this, conn]()
boost::asio::post (GetService(), [this, conn]()
{
if (this->AddNTCP2Session (conn))
{
@@ -1787,7 +1944,7 @@ namespace transport
LogPrint(eLogError, "NTCP2: HTTP proxy write error ", ec.message());
});
boost::asio::streambuf * readbuff = new boost::asio::streambuf;
auto readbuff = std::make_shared<boost::asio::streambuf>();
boost::asio::async_read_until(conn->GetSocket(), *readbuff, "\r\n\r\n",
[readbuff, timer, conn] (const boost::system::error_code & ec, std::size_t transferred)
{
@@ -1801,13 +1958,12 @@ namespace transport
{
readbuff->commit(transferred);
i2p::http::HTTPRes res;
if(res.parse(boost::asio::buffer_cast<const char*>(readbuff->data()), readbuff->size()) > 0)
if(res.parse(std::string {boost::asio::buffers_begin(readbuff->data ()), boost::asio::buffers_begin(readbuff->data ()) + readbuff->size ()}) > 0)
{
if(res.code == 200)
{
timer->cancel();
conn->ClientLogin();
delete readbuff;
return;
}
else
@@ -1817,7 +1973,6 @@ namespace transport
LogPrint(eLogError, "NTCP2: HTTP proxy gave malformed response");
timer->cancel();
conn->Terminate();
delete readbuff;
}
});
break;
@@ -1840,5 +1995,17 @@ namespace transport
else
m_Address4 = addr;
}
void NTCP2Server::AEADChaCha20Poly1305Encrypt (const std::vector<std::pair<uint8_t *, size_t> >& bufs,
const uint8_t * key, const uint8_t * nonce, uint8_t * mac)
{
return m_Encryptor.Encrypt (bufs, key, nonce, mac);
}
bool NTCP2Server::AEADChaCha20Poly1305Decrypt (const uint8_t * msg, size_t msgLen,
const uint8_t * ad, size_t adLen, const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len)
{
return m_Decryptor.Decrypt (msg, msgLen, ad, adLen, key, nonce, buf, len);
}
}
}

View File

@@ -14,6 +14,7 @@
#include <list>
#include <map>
#include <array>
#include <random>
#include <openssl/bn.h>
#include <openssl/evp.h>
#include <boost/asio.hpp>
@@ -35,8 +36,10 @@ namespace transport
const int NTCP2_CONNECT_TIMEOUT = 5; // 5 seconds
const int NTCP2_ESTABLISH_TIMEOUT = 10; // 10 seconds
const int NTCP2_TERMINATION_TIMEOUT = 120; // 2 minutes
const int NTCP2_TERMINATION_CHECK_TIMEOUT = 30; // 30 seconds
const int NTCP2_TERMINATION_TIMEOUT = 115; // 2 minutes - 5 seconds
const int NTCP2_TERMINATION_TIMEOUT_VARIANCE = 10; // 10 seconds
const int NTCP2_TERMINATION_CHECK_TIMEOUT = 28; // 28 seconds
const int NTCP2_TERMINATION_CHECK_TIMEOUT_VARIANCE = 5; // 5 seconds
const int NTCP2_RECEIVE_BUFFER_DELETION_TIMEOUT = 3; // 3 seconds
const int NTCP2_ROUTERINFO_RESEND_INTERVAL = 25*60; // 25 minuntes in seconds
const int NTCP2_ROUTERINFO_RESEND_INTERVAL_THRESHOLD = 25*60; // 25 minuntes
@@ -92,21 +95,21 @@ namespace transport
const uint8_t * GetCK () const { return m_CK; };
const uint8_t * GetH () const { return m_H; };
void KDF1Alice ();
void KDF1Bob ();
void KDF2Alice ();
void KDF2Bob ();
void KDF3Alice (); // for SessionConfirmed part 2
void KDF3Bob ();
bool KDF1Alice ();
bool KDF1Bob ();
bool KDF2Alice ();
bool KDF2Bob ();
bool KDF3Alice (); // for SessionConfirmed part 2
bool KDF3Bob ();
void KeyDerivationFunction1 (const uint8_t * pub, i2p::crypto::X25519Keys& priv, const uint8_t * rs, const uint8_t * epub); // for SessionRequest, (pub, priv) for DH
void KeyDerivationFunction2 (const uint8_t * sessionRequest, size_t sessionRequestLen, const uint8_t * epub); // for SessionCreate
bool KeyDerivationFunction1 (const uint8_t * pub, i2p::crypto::X25519Keys& priv, const uint8_t * rs, const uint8_t * epub); // for SessionRequest, (pub, priv) for DH
bool KeyDerivationFunction2 (const uint8_t * sessionRequest, size_t sessionRequestLen, const uint8_t * epub); // for SessionCreate
void CreateEphemeralKey ();
void CreateSessionRequestMessage ();
void CreateSessionCreatedMessage ();
bool CreateSessionRequestMessage (std::mt19937& rng);
bool CreateSessionCreatedMessage (std::mt19937& rng);
void CreateSessionConfirmedMessagePart1 (const uint8_t * nonce);
void CreateSessionConfirmedMessagePart2 (const uint8_t * nonce);
bool CreateSessionConfirmedMessagePart2 (const uint8_t * nonce);
bool ProcessSessionRequestMessage (uint16_t& paddingLen, bool& clockSkew);
bool ProcessSessionCreatedMessage (uint16_t& paddingLen);
@@ -144,13 +147,14 @@ namespace transport
void SetRemoteEndpoint (const boost::asio::ip::tcp::endpoint& ep) { m_RemoteEndpoint = ep; };
bool IsEstablished () const override { return m_IsEstablished; };
i2p::data::RouterInfo::SupportedTransports GetTransportType () const override;
bool IsTerminated () const { return m_IsTerminated; };
void ClientLogin (); // Alice
void ServerLogin (); // Bob
void SendLocalRouterInfo (bool update) override; // after handshake or by update
void SendI2NPMessages (const std::vector<std::shared_ptr<I2NPMessage> >& msgs) override;
void SendI2NPMessages (std::list<std::shared_ptr<I2NPMessage> >& msgs) override;
void MoveSendQueue (std::shared_ptr<NTCP2Session> other);
private:
@@ -169,13 +173,17 @@ namespace transport
void HandleSessionRequestSent (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void HandleSessionRequestReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void ProcessSessionRequest (size_t len);
void HandleSessionRequestPaddingReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void HandleSessionCreatedSent (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void HandleSessionCreatedReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void ProcessSessionCreated (size_t len);
void HandleSessionCreatedPaddingReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void HandleSessionConfirmedSent (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void HandleSessionConfirmedReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void ProcessSessionConfirmed ();
void EstablishSessionAfterSessionConfirmed (std::shared_ptr<std::vector<uint8_t> > buf, size_t size);
// data
void ReceiveLength ();
void HandleReceivedLength (const boost::system::error_code& ecode, std::size_t bytes_transferred);
@@ -193,7 +201,7 @@ namespace transport
void SendRouterInfo ();
void SendTermination (NTCP2TerminationReason reason);
void SendTerminationAndTerminate (NTCP2TerminationReason reason);
void PostI2NPMessages (std::vector<std::shared_ptr<I2NPMessage> > msgs);
void PostI2NPMessages ();
private:
@@ -226,13 +234,28 @@ namespace transport
bool m_IsSending, m_IsReceiving;
std::list<std::shared_ptr<I2NPMessage> > m_SendQueue;
uint64_t m_NextRouterInfoResendTime; // seconds since epoch
std::list<std::shared_ptr<I2NPMessage> > m_IntermediateQueue; // from transports
mutable std::mutex m_IntermediateQueueMutex;
uint16_t m_PaddingSizes[16];
int m_NextPaddingSize;
};
class NTCP2Server: private i2p::util::RunnableServiceWithWork
{
private:
class EstablisherService: public i2p::util::RunnableServiceWithWork
{
public:
EstablisherService (): RunnableServiceWithWork ("NTCP2e") {};
auto& GetService () { return GetIOService (); };
void Start () { StartIOService (); };
void Stop () { StopIOService (); };
};
public:
enum ProxyType
@@ -241,13 +264,20 @@ namespace transport
eSocksProxy,
eHTTPProxy
};
NTCP2Server ();
~NTCP2Server ();
void Start ();
void Stop ();
boost::asio::io_service& GetService () { return GetIOService (); };
auto& GetService () { return GetIOService (); };
auto& GetEstablisherService () { return m_EstablisherService.GetService (); };
std::mt19937& GetRng () { return m_Rng; };
void AEADChaCha20Poly1305Encrypt (const std::vector<std::pair<uint8_t *, size_t> >& bufs,
const uint8_t * key, const uint8_t * nonce, uint8_t * mac);
bool AEADChaCha20Poly1305Decrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen,
const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len);
bool AddNTCP2Session (std::shared_ptr<NTCP2Session> session, bool incoming = false);
void RemoveNTCP2Session (std::shared_ptr<NTCP2Session> session);
@@ -285,8 +315,13 @@ namespace transport
uint16_t m_ProxyPort;
boost::asio::ip::tcp::resolver m_Resolver;
std::unique_ptr<boost::asio::ip::tcp::endpoint> m_ProxyEndpoint;
std::shared_ptr<boost::asio::ip::tcp::endpoint> m_Address4, m_Address6, m_YggdrasilAddress;
std::mt19937 m_Rng;
EstablisherService m_EstablisherService;
i2p::crypto::AEADChaCha20Poly1305Encryptor m_Encryptor;
i2p::crypto::AEADChaCha20Poly1305Decryptor m_Decryptor;
public:
// for HTTP/I2PControl

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2024, The PurpleI2P Project
* Copyright (c) 2013-2025, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -10,7 +10,6 @@
#include <fstream>
#include <vector>
#include <map>
#include <random>
#include <boost/asio.hpp>
#include <stdexcept>
@@ -40,7 +39,7 @@ namespace data
NetDb::NetDb (): m_IsRunning (false), m_Thread (nullptr), m_Reseeder (nullptr),
m_Storage("netDb", "r", "routerInfo-", "dat"), m_PersistProfiles (true),
m_LastExploratorySelectionUpdateTime (0)
m_LastExploratorySelectionUpdateTime (0), m_Rng(i2p::util::GetMonotonicMicroseconds () % 1000000LL)
{
}
@@ -69,7 +68,7 @@ namespace data
{
Reseed ();
}
else if (!GetRandomRouter (i2p::context.GetSharedRouterInfo (), false, false))
else if (!GetRandomRouter (i2p::context.GetSharedRouterInfo (), false, false, false))
Reseed (); // we don't have a router we can connect to. Trying to reseed
auto it = m_RouterInfos.find (i2p::context.GetIdentHash ());
@@ -119,19 +118,22 @@ namespace data
i2p::util::SetThreadName("NetDB");
uint64_t lastManage = 0;
uint64_t lastProfilesCleanup = i2p::util::GetMonotonicMilliseconds (), lastObsoleteProfilesCleanup = lastProfilesCleanup;
int16_t profilesCleanupVariance = 0, obsoleteProfilesCleanVariance = 0;
uint64_t lastProfilesCleanup = i2p::util::GetMonotonicMilliseconds (),
lastObsoleteProfilesCleanup = lastProfilesCleanup, lastApplyingProfileUpdates = lastProfilesCleanup;
int16_t profilesCleanupVariance = 0, obsoleteProfilesCleanVariance = 0, applyingProfileUpdatesVariance = 0;
std::list<std::shared_ptr<const I2NPMessage> > msgs;
while (m_IsRunning)
{
try
{
auto msg = m_Queue.GetNextWithTimeout (1000); // 1 sec
if (msg)
if (m_Queue.Wait (1,0)) // 1 sec
{
int numMsgs = 0;
while (msg)
m_Queue.GetWholeQueue (msgs);
while (!msgs.empty ())
{
auto msg = msgs.front (); msgs.pop_front ();
if (!msg) continue;
LogPrint(eLogDebug, "NetDb: Got request with type ", (int) msg->GetTypeID ());
switch (msg->GetTypeID ())
{
@@ -141,17 +143,10 @@ namespace data
case eI2NPDatabaseLookup:
HandleDatabaseLookupMsg (msg);
break;
case eI2NPDummyMsg:
// plain RouterInfo from NTCP2 with flags for now
HandleNTCP2RouterInfoMsg (msg);
break;
default: // WTF?
LogPrint (eLogError, "NetDb: Unexpected message type ", (int) msg->GetTypeID ());
//i2p::HandleI2NPMessage (msg);
}
if (numMsgs > 100) break;
msg = m_Queue.Get ();
numMsgs++;
}
}
if (!m_IsRunning) break;
@@ -186,7 +181,7 @@ namespace data
LogPrint (eLogWarning, "NetDb: Can't persist profiles. Profiles are being saved to disk");
}
lastProfilesCleanup = mts;
profilesCleanupVariance = rand () % i2p::data::PEER_PROFILE_AUTOCLEAN_VARIANCE;
profilesCleanupVariance = m_Rng () % i2p::data::PEER_PROFILE_AUTOCLEAN_VARIANCE;
}
if (mts >= lastObsoleteProfilesCleanup + (uint64_t)(i2p::data::PEER_PROFILE_OBSOLETE_PROFILES_CLEAN_TIMEOUT + obsoleteProfilesCleanVariance)*1000)
@@ -202,7 +197,20 @@ namespace data
else
LogPrint (eLogWarning, "NetDb: Can't delete profiles. Profiles are being deleted from disk");
lastObsoleteProfilesCleanup = mts;
obsoleteProfilesCleanVariance = rand () % i2p::data::PEER_PROFILE_OBSOLETE_PROFILES_CLEAN_VARIANCE;
obsoleteProfilesCleanVariance = m_Rng () % i2p::data::PEER_PROFILE_OBSOLETE_PROFILES_CLEAN_VARIANCE;
}
if (mts >= lastApplyingProfileUpdates + i2p::data::PEER_PROFILE_APPLY_POSTPONED_TIMEOUT + applyingProfileUpdatesVariance)
{
bool isApplying = m_ApplyingProfileUpdates.valid ();
if (isApplying && m_ApplyingProfileUpdates.wait_for(std::chrono::seconds(0)) == std::future_status::ready) // still active?
{
m_ApplyingProfileUpdates.get ();
isApplying = false;
}
if (!isApplying)
m_ApplyingProfileUpdates = i2p::data::FlushPostponedRouterProfileUpdates ();
lastApplyingProfileUpdates = mts;
applyingProfileUpdatesVariance = m_Rng () % i2p::data::PEER_PROFILE_APPLY_POSTPONED_TIMEOUT_VARIANCE;
}
}
catch (std::exception& ex)
@@ -286,6 +294,7 @@ namespace data
}
else
{
r->CancelBufferToDelete (); // since an update received
if (CheckLogLevel (eLogDebug))
LogPrint (eLogDebug, "NetDb: RouterInfo is older: ", ident.ToBase64());
updated = false;
@@ -299,7 +308,8 @@ namespace data
{
auto mts = i2p::util::GetMillisecondsSinceEpoch ();
isValid = mts + NETDB_EXPIRATION_TIMEOUT_THRESHOLD*1000LL > r->GetTimestamp () && // from future
mts < r->GetTimestamp () + NETDB_MAX_EXPIRATION_TIMEOUT*1000LL; // too old
(mts < r->GetTimestamp () + NETDB_MAX_EXPIRATION_TIMEOUT*1000LL || // too old
context.GetUptime () < NETDB_CHECK_FOR_EXPIRATION_UPTIME/10); // enough uptime
}
if (isValid)
{
@@ -388,8 +398,7 @@ namespace data
if (it == m_LeaseSets.end () || it->second->GetStoreType () != storeType ||
leaseSet->GetPublishedTimestamp () > it->second->GetPublishedTimestamp ())
{
if (leaseSet->IsPublic () && !leaseSet->IsExpired () &&
i2p::util::GetSecondsSinceEpoch () + NETDB_EXPIRATION_TIMEOUT_THRESHOLD > leaseSet->GetPublishedTimestamp ())
if (leaseSet->IsPublic () && !leaseSet->IsExpired ())
{
// TODO: implement actual update
if (CheckLogLevel (eLogInfo))
@@ -484,7 +493,7 @@ namespace data
void NetDb::ReseedFromFloodfill(const RouterInfo & ri, int numRouters, int numFloodfills)
{
LogPrint(eLogInfo, "NetDB: Reseeding from floodfill ", ri.GetIdentHashBase64());
std::vector<std::shared_ptr<i2p::I2NPMessage> > requests;
std::list<std::shared_ptr<i2p::I2NPMessage> > requests;
i2p::data::IdentHash ourIdent = i2p::context.GetIdentHash();
i2p::data::IdentHash ih = ri.GetIdentHash();
@@ -507,7 +516,7 @@ namespace data
}
// send them off
i2p::transport::transports.SendMessages(ih, requests);
i2p::transport::transports.SendMessages(ih, std::move (requests));
}
bool NetDb::LoadRouterInfo (const std::string& path, uint64_t ts)
@@ -562,7 +571,7 @@ namespace data
while(n > 0)
{
std::lock_guard<std::mutex> lock(m_RouterInfosMutex);
uint32_t idx = rand () % m_RouterInfos.size ();
uint32_t idx = m_Rng () % m_RouterInfos.size ();
uint32_t i = 0;
for (const auto & it : m_RouterInfos) {
if(i >= idx) // are we at the random start point?
@@ -642,70 +651,79 @@ namespace data
if (checkForExpiration && uptime > i2p::transport::SSU2_TO_INTRODUCER_SESSION_DURATION) // 1 hour
expirationTimeout = i2p::context.IsFloodfill () ? NETDB_FLOODFILL_EXPIRATION_TIMEOUT*1000LL :
NETDB_MIN_EXPIRATION_TIMEOUT*1000LL + (NETDB_MAX_EXPIRATION_TIMEOUT - NETDB_MIN_EXPIRATION_TIMEOUT)*1000LL*NETDB_MIN_ROUTERS/total;
bool isOffline = checkForExpiration && i2p::transport::transports.GetNumPeers () < NETDB_MIN_TRANSPORTS; // enough routers and uptime, but no transports
std::list<std::pair<std::string, std::shared_ptr<RouterInfo::Buffer> > > saveToDisk;
std::list<std::string> removeFromDisk;
auto own = i2p::context.GetSharedRouterInfo ();
for (auto& it: m_RouterInfos)
for (auto [ident, r]: m_RouterInfos)
{
if (!it.second || it.second == own) continue; // skip own
std::string ident = it.second->GetIdentHashBase64();
if (it.second->IsUpdated ())
if (!r || r == own) continue; // skip own
if (r->IsBufferScheduledToDelete ()) // from previous SaveUpdated, we assume m_PersistingRouters complete
{
if (it.second->GetBuffer ())
std::lock_guard<std::mutex> l(m_RouterInfosMutex); // possible collision between DeleteBuffer and Update
r->DeleteBuffer ();
}
if (r->IsUpdated ())
{
if (r->GetBuffer () && !r->IsUnreachable ())
{
// we have something to save
std::shared_ptr<RouterInfo::Buffer> buffer;
{
std::lock_guard<std::mutex> l(m_RouterInfosMutex); // possible collision between DeleteBuffer and Update
buffer = it.second->GetSharedBuffer ();
it.second->DeleteBuffer ();
buffer = r->CopyBuffer ();
}
if (buffer && !it.second->IsUnreachable ()) // don't save bad router
saveToDisk.push_back(std::make_pair(ident, buffer));
it.second->SetUnreachable (false);
if (!i2p::transport::transports.IsConnected (ident))
r->ScheduleBufferToDelete ();
if (buffer)
saveToDisk.emplace_back(ident.ToBase64 (), buffer);
}
it.second->SetUpdated (false);
r->SetUpdated (false);
updatedCount++;
continue;
}
if (it.second->GetProfile ()->IsUnreachable ())
it.second->SetUnreachable (true);
else if (r->GetBuffer () && ts > r->GetTimestamp () + NETDB_MIN_EXPIRATION_TIMEOUT*1000LL)
// since update was long time ago we assume that router is not connected anymore
r->ScheduleBufferToDelete ();
if (r->GetProfile ()->IsUnreachable ())
r->SetUnreachable (true);
// make router reachable back if too few routers or floodfills
if (it.second->IsUnreachable () && (total - deletedCount < NETDB_MIN_ROUTERS || isLowRate ||
(it.second->IsFloodfill () && totalFloodfills - deletedFloodfillsCount < NETDB_MIN_FLOODFILLS)))
it.second->SetUnreachable (false);
if (!it.second->IsUnreachable ())
if (r->IsUnreachable () && (total - deletedCount < NETDB_MIN_ROUTERS || isLowRate || isOffline ||
(r->IsFloodfill () && totalFloodfills - deletedFloodfillsCount < NETDB_MIN_FLOODFILLS)))
r->SetUnreachable (false);
if (!r->IsUnreachable ())
{
// find & mark expired routers
if (!it.second->GetCompatibleTransports (true)) // non reachable by any transport
it.second->SetUnreachable (true);
else if (ts + NETDB_EXPIRATION_TIMEOUT_THRESHOLD*1000LL < it.second->GetTimestamp ())
if (!r->GetCompatibleTransports (true)) // non reachable by any transport
r->SetUnreachable (true);
else if (ts + NETDB_EXPIRATION_TIMEOUT_THRESHOLD*1000LL < r->GetTimestamp ())
{
LogPrint (eLogWarning, "NetDb: RouterInfo is from future for ", (it.second->GetTimestamp () - ts)/1000LL, " seconds");
it.second->SetUnreachable (true);
LogPrint (eLogWarning, "NetDb: RouterInfo is from future for ", (r->GetTimestamp () - ts)/1000LL, " seconds");
r->SetUnreachable (true);
}
else if (checkForExpiration)
{
if (ts > it.second->GetTimestamp () + expirationTimeout)
it.second->SetUnreachable (true);
else if ((ts > it.second->GetTimestamp () + expirationTimeout/2) && // more than half of expiration
total > NETDB_NUM_ROUTERS_THRESHOLD && !it.second->IsHighBandwidth() && // low bandwidth
!it.second->IsFloodfill() && (!i2p::context.IsFloodfill () || // non floodfill
(CreateRoutingKey (it.second->GetIdentHash ()) ^ i2p::context.GetIdentHash ()).metric[0] >= 0x02)) // different first 7 bits
it.second->SetUnreachable (true);
if (ts > r->GetTimestamp () + expirationTimeout)
r->SetUnreachable (true);
else if ((ts > r->GetTimestamp () + expirationTimeout/2) && // more than half of expiration
total > NETDB_NUM_ROUTERS_THRESHOLD && !r->IsHighBandwidth() && // low bandwidth
!r->IsFloodfill() && (!i2p::context.IsFloodfill () || // non floodfill
(CreateRoutingKey (ident) ^ i2p::context.GetIdentHash ()).metric[0] >= 0x02)) // different first 7 bits
r->SetUnreachable (true);
}
}
// make router reachable back if connected now
if (it.second->IsUnreachable () && i2p::transport::transports.IsConnected (it.second->GetIdentHash ()))
it.second->SetUnreachable (false);
if (r->IsUnreachable () && i2p::transport::transports.IsConnected (ident))
r->SetUnreachable (false);
if (it.second->IsUnreachable ())
if (r->IsUnreachable ())
{
if (it.second->IsFloodfill ()) deletedFloodfillsCount++;
if (r->IsFloodfill ()) deletedFloodfillsCount++;
// delete RI file
removeFromDisk.push_back (ident);
removeFromDisk.emplace_back (ident.ToBase64());
deletedCount++;
if (total - deletedCount < NETDB_MIN_ROUTERS) checkForExpiration = false;
}
@@ -763,7 +781,8 @@ namespace data
void NetDb::RequestDestination (const IdentHash& destination, RequestedDestination::RequestComplete requestComplete, bool direct)
{
if (direct && i2p::transport::transports.RoutesRestricted ()) direct = false; // always use tunnels for restricted routes
if (direct && (i2p::transport::transports.RoutesRestricted () || i2p::context.IsLimitedConnectivity ()))
direct = false; // always use tunnels for restricted routes or limited connectivity
if (m_Requests)
m_Requests->PostRequestDestination (destination, requestComplete, direct);
else
@@ -1133,15 +1152,18 @@ namespace data
}
std::shared_ptr<const RouterInfo> NetDb::GetRandomRouter (std::shared_ptr<const RouterInfo> compatibleWith,
bool reverse, bool endpoint) const
bool reverse, bool endpoint, bool clientTunnel) const
{
bool checkIsReal = clientTunnel && i2p::tunnel::tunnels.GetPreciseTunnelCreationSuccessRate () < NETDB_TUNNEL_CREATION_RATE_THRESHOLD && // too low rate
context.GetUptime () > NETDB_CHECK_FOR_EXPIRATION_UPTIME; // after 10 minutes uptime
return GetRandomRouter (
[compatibleWith, reverse, endpoint](std::shared_ptr<const RouterInfo> router)->bool
[compatibleWith, reverse, endpoint, clientTunnel, checkIsReal](std::shared_ptr<const RouterInfo> router)->bool
{
return !router->IsHidden () && router != compatibleWith &&
(reverse ? (compatibleWith->IsReachableFrom (*router) && router->GetCompatibleTransports (true)):
router->IsReachableFrom (*compatibleWith)) && !router->IsNAT2NATOnly (*compatibleWith) &&
router->IsECIES () && !router->IsHighCongestion (false) &&
router->IsECIES () && !router->IsHighCongestion (clientTunnel) &&
(!checkIsReal || router->GetProfile ()->IsReal ()) &&
(!endpoint || (router->IsV4 () && (!reverse || router->IsPublished (true)))); // endpoint must be ipv4 and published if inbound(reverse)
});
}
@@ -1314,12 +1336,8 @@ namespace data
{
// update selection
m_ExploratorySelection.clear ();
#if (__cplusplus >= 201703L) // C++ 17 or higher
std::vector<std::shared_ptr<const RouterInfo> > eligible;
eligible.reserve (m_RouterInfos.size ());
#else
auto& eligible = m_ExploratorySelection;
#endif
eligible.reserve (m_RouterInfos.size ());
{
// collect eligible from current netdb
bool checkIsReal = i2p::tunnel::tunnels.GetPreciseTunnelCreationSuccessRate () < NETDB_TUNNEL_CREATION_RATE_THRESHOLD; // too low rate
@@ -1329,22 +1347,13 @@ namespace data
(!checkIsReal || (it.second->HasProfile () && it.second->GetProfile ()->IsReal ())))
eligible.push_back (it.second);
}
#if (__cplusplus >= 201703L) // C++ 17 or higher
if (eligible.size () > NETDB_MAX_EXPLORATORY_SELECTION_SIZE)
{
std::sample (eligible.begin(), eligible.end(), std::back_inserter(m_ExploratorySelection),
NETDB_MAX_EXPLORATORY_SELECTION_SIZE, std::mt19937(ts));
NETDB_MAX_EXPLORATORY_SELECTION_SIZE, m_Rng);
}
else
std::swap (m_ExploratorySelection, eligible);
#else
if (m_ExploratorySelection.size () > NETDB_MAX_EXPLORATORY_SELECTION_SIZE)
{
// reduce number of eligible to max selection size
std::shuffle (m_ExploratorySelection.begin(), m_ExploratorySelection.end(), std::mt19937(ts));
m_ExploratorySelection.resize (NETDB_MAX_EXPLORATORY_SELECTION_SIZE);
}
#endif
std::swap (m_ExploratorySelection, eligible);
m_LastExploratorySelectionUpdateTime = ts;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2024, The PurpleI2P Project
* Copyright (c) 2013-2025, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -16,6 +16,7 @@
#include <thread>
#include <mutex>
#include <future>
#include <random>
#include "Base.h"
#include "Gzip.h"
@@ -39,6 +40,7 @@ namespace data
{
const int NETDB_MIN_ROUTERS = 90;
const int NETDB_MIN_FLOODFILLS = 5;
const int NETDB_MIN_TRANSPORTS = 10 ; // otherwise assume offline
const int NETDB_NUM_FLOODFILLS_THRESHOLD = 1200;
const int NETDB_NUM_ROUTERS_THRESHOLD = 4*NETDB_NUM_FLOODFILLS_THRESHOLD;
const int NETDB_TUNNEL_CREATION_RATE_THRESHOLD = 10; // in %
@@ -51,6 +53,7 @@ namespace data
const int NETDB_MIN_HIGHBANDWIDTH_VERSION = MAKE_VERSION_NUMBER(0, 9, 58); // 0.9.58
const int NETDB_MIN_FLOODFILL_VERSION = MAKE_VERSION_NUMBER(0, 9, 59); // 0.9.59
const int NETDB_MIN_SHORT_TUNNEL_BUILD_VERSION = MAKE_VERSION_NUMBER(0, 9, 51); // 0.9.51
const int NETDB_MIN_PEER_TEST_VERSION = MAKE_VERSION_NUMBER(0, 9, 62); // 0.9.62
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
@@ -87,7 +90,7 @@ namespace data
void RequestDestination (const IdentHash& destination, RequestedDestination::RequestComplete requestComplete = nullptr, bool direct = true);
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> GetRandomRouter (std::shared_ptr<const RouterInfo> compatibleWith, bool reverse, bool endpoint, bool clientTunnel) 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::unordered_set<IdentHash>& excluded) const;
std::shared_ptr<const RouterInfo> GetRandomSSU2Introducer (bool v4, const std::unordered_set<IdentHash>& excluded) const;
@@ -127,12 +130,12 @@ namespace data
}
bool PopulateRouterInfoBuffer (std::shared_ptr<RouterInfo> r);
std::shared_ptr<RouterInfo::Address> NewRouterInfoAddress () { return m_RouterInfoAddressesPool.AcquireSharedMt (); };
boost::shared_ptr<RouterInfo::Addresses> NewRouterInfoAddresses ()
RouterInfo::AddressesPtr NewRouterInfoAddresses ()
{
return boost::shared_ptr<RouterInfo::Addresses>(m_RouterInfoAddressVectorsPool.AcquireMt (),
return RouterInfo::AddressesPtr{m_RouterInfoAddressVectorsPool.AcquireMt (),
std::bind <void (i2p::util::MemoryPoolMt<RouterInfo::Addresses>::*)(RouterInfo::Addresses *)>
(&i2p::util::MemoryPoolMt<RouterInfo::Addresses>::ReleaseMt,
&m_RouterInfoAddressVectorsPool, std::placeholders::_1));
&m_RouterInfoAddressVectorsPool, std::placeholders::_1)};
};
std::shared_ptr<Lease> NewLease (const Lease& lease) { return m_LeasesPool.AcquireSharedMt (lease); };
std::shared_ptr<IdentityEx> NewIdentity (const uint8_t * buf, size_t len) { return m_IdentitiesPool.AcquireSharedMt (buf, len); };
@@ -184,10 +187,11 @@ namespace data
std::shared_ptr<NetDbRequests> m_Requests;
bool m_PersistProfiles;
std::future<void> m_SavingProfiles, m_DeletingProfiles, m_PersistingRouters;
std::future<void> m_SavingProfiles, m_DeletingProfiles, m_ApplyingProfileUpdates, m_PersistingRouters;
std::vector<std::shared_ptr<const RouterInfo> > m_ExploratorySelection;
uint64_t m_LastExploratorySelectionUpdateTime; // in monotonic seconds
std::mt19937 m_Rng;
i2p::util::MemoryPoolMt<RouterInfo::Buffer> m_RouterInfoBuffersPool;
i2p::util::MemoryPoolMt<RouterInfo::Address> m_RouterInfoAddressesPool;

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2024, The PurpleI2P Project
* Copyright (c) 2013-2025, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -20,8 +20,10 @@ 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)
m_Destination (destination), m_IsExploratory (isExploratory), m_IsDirect (direct),
m_IsActive (true), m_IsSentDirectly (false),
m_CreationTime (i2p::util::GetMillisecondsSinceEpoch ()),
m_LastRequestTime (0), m_NumAttempts (0)
{
if (i2p::context.IsFloodfill ())
m_ExcludedPeers.insert (i2p::context.GetIdentHash ()); // exclude self if floodfill
@@ -44,8 +46,9 @@ namespace data
msg = i2p::CreateRouterInfoDatabaseLookupMsg(m_Destination, i2p::context.GetIdentHash(), 0, m_IsExploratory, &m_ExcludedPeers);
if(router)
m_ExcludedPeers.insert (router->GetIdentHash ());
m_LastRequestTime = i2p::util::GetSecondsSinceEpoch ();
m_LastRequestTime = i2p::util::GetMillisecondsSinceEpoch ();
m_NumAttempts++;
m_IsSentDirectly = false;
return msg;
}
@@ -55,7 +58,8 @@ namespace data
i2p::context.GetRouterInfo ().GetIdentHash () , 0, false, &m_ExcludedPeers);
m_ExcludedPeers.insert (floodfill);
m_NumAttempts++;
m_LastRequestTime = i2p::util::GetSecondsSinceEpoch ();
m_LastRequestTime = i2p::util::GetMillisecondsSinceEpoch ();
m_IsSentDirectly = true;
return msg;
}
@@ -179,7 +183,7 @@ namespace data
void NetDbRequests::RequestComplete (const IdentHash& ident, std::shared_ptr<RouterInfo> r)
{
GetIOService ().post ([this, ident, r]()
boost::asio::post (GetIOService (), [this, ident, r]()
{
std::shared_ptr<RequestedDestination> request;
auto it = m_RequestedDestinations.find (ident);
@@ -210,7 +214,7 @@ namespace data
void NetDbRequests::ManageRequests ()
{
uint64_t ts = i2p::util::GetSecondsSinceEpoch ();
uint64_t ts = i2p::util::GetMillisecondsSinceEpoch ();
for (auto it = m_RequestedDestinations.begin (); it != m_RequestedDestinations.end ();)
{
auto& dest = it->second;
@@ -222,7 +226,8 @@ namespace data
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
if (ts > dest->GetLastRequestTime () + (dest->IsSentDirectly () ? MIN_DIRECT_REQUEST_TIME : MIN_REQUEST_TIME))
// try next floodfill if no response after min interval
done = !SendNextRequest (dest);
}
else // request is expired
@@ -267,7 +272,7 @@ namespace data
{
if (dest->IsActive ())
{
s->GetIOService ().post ([s, dest]()
boost::asio::post (s->GetIOService (), [s, dest]()
{
if (dest->IsActive ()) s->SendNextRequest (dest);
});
@@ -328,7 +333,8 @@ namespace data
void NetDbRequests::ScheduleManageRequests ()
{
m_ManageRequestsTimer.expires_from_now (boost::posix_time::seconds(MANAGE_REQUESTS_INTERVAL));
m_ManageRequestsTimer.expires_from_now (boost::posix_time::milliseconds(MANAGE_REQUESTS_INTERVAL +
m_Rng () % MANAGE_REQUESTS_INTERVAL_VARIANCE));
m_ManageRequestsTimer.async_wait (std::bind (&NetDbRequests::HandleManageRequestsTimer,
this, std::placeholders::_1));
}
@@ -345,7 +351,7 @@ namespace data
void NetDbRequests::PostDatabaseSearchReplyMsg (std::shared_ptr<const I2NPMessage> msg)
{
GetIOService ().post ([this, msg]()
boost::asio::post (GetIOService (), [this, msg]()
{
HandleDatabaseSearchReplyMsg (msg);
});
@@ -431,7 +437,7 @@ namespace data
void NetDbRequests::PostRequestDestination (const IdentHash& destination,
const RequestedDestination::RequestComplete& requestComplete, bool direct)
{
GetIOService ().post ([this, destination, requestComplete, direct]()
boost::asio::post (GetIOService (), [this, destination, requestComplete, direct]()
{
RequestDestination (destination, requestComplete, direct);
});

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2024, The PurpleI2P Project
* Copyright (c) 2013-2025, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -24,15 +24,17 @@ 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 MANAGE_REQUESTS_INTERVAL = 400; // in milliseconds
const uint64_t MANAGE_REQUESTS_INTERVAL_VARIANCE = 300; // in milliseconds
const uint64_t MIN_REQUEST_TIME = 1200; // in milliseconds
const uint64_t MAX_REQUEST_TIME = MAX_NUM_REQUEST_ATTEMPTS * (MIN_REQUEST_TIME + MANAGE_REQUESTS_INTERVAL + MANAGE_REQUESTS_INTERVAL_VARIANCE);
const uint64_t MIN_DIRECT_REQUEST_TIME = 600; // in milliseconds
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 MAX_EXPLORATORY_REQUEST_TIME = 30000; // in milliseconds
const uint64_t REQUEST_CACHE_TIME = MAX_REQUEST_TIME + 40000; // in milliseconds
const uint64_t REQUESTED_DESTINATIONS_POOL_CLEANUP_INTERVAL = 191; // in seconds
class RequestedDestination
@@ -51,6 +53,7 @@ namespace data
bool IsExploratory () const { return m_IsExploratory; };
bool IsDirect () const { return m_IsDirect; };
bool IsActive () const { return m_IsActive; };
bool IsSentDirectly () const { return m_IsSentDirectly; };
bool IsExcluded (const IdentHash& ident) const;
uint64_t GetCreationTime () const { return m_CreationTime; };
uint64_t GetLastRequestTime () const { return m_LastRequestTime; };
@@ -69,9 +72,9 @@ namespace data
private:
IdentHash m_Destination;
bool m_IsExploratory, m_IsDirect, m_IsActive;
bool m_IsExploratory, m_IsDirect, m_IsActive, m_IsSentDirectly;
std::unordered_set<IdentHash> m_ExcludedPeers;
uint64_t m_CreationTime, m_LastRequestTime; // in seconds
uint64_t m_CreationTime, m_LastRequestTime; // in milliseconds
std::list<RequestComplete> m_RequestComplete;
int m_NumAttempts;
};
@@ -115,9 +118,9 @@ namespace data
private:
i2p::util::MemoryPoolMt<RequestedDestination> m_RequestedDestinationsPool;
std::unordered_map<IdentHash, std::shared_ptr<RequestedDestination> > m_RequestedDestinations;
std::list<IdentHash> m_DiscoveredRouterHashes;
i2p::util::MemoryPoolMt<RequestedDestination> m_RequestedDestinationsPool;
boost::asio::deadline_timer m_ManageRequestsTimer, m_ExploratoryTimer,
m_CleanupTimer, m_DiscoveredRoutersTimer;
std::mt19937 m_Rng;

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2024, The PurpleI2P Project
* Copyright (c) 2013-2025, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -10,6 +10,7 @@
#include <unordered_map>
#include <list>
#include <thread>
#include <iomanip>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/ini_parser.hpp>
#include "Base.h"
@@ -26,24 +27,21 @@ namespace data
static i2p::fs::HashedStorage g_ProfilesStorage("peerProfiles", "p", "profile-", "txt");
static std::unordered_map<i2p::data::IdentHash, std::shared_ptr<RouterProfile> > g_Profiles;
static std::mutex g_ProfilesMutex;
static boost::posix_time::ptime GetTime ()
{
return boost::posix_time::second_clock::local_time();
}
static std::list<std::pair<i2p::data::IdentHash, std::function<void (std::shared_ptr<RouterProfile>)> > > g_PostponedUpdates;
static std::mutex g_PostponedUpdatesMutex;
RouterProfile::RouterProfile ():
m_LastUpdateTime (GetTime ()), m_IsUpdated (false),
m_LastDeclineTime (0), m_LastUnreachableTime (0),
m_NumTunnelsAgreed (0), m_NumTunnelsDeclined (0), m_NumTunnelsNonReplied (0),
m_NumTimesTaken (0), m_NumTimesRejected (0), m_HasConnected (false),
m_IsDuplicated (false)
m_IsUpdated (false), m_LastDeclineTime (0), m_LastUnreachableTime (0),
m_LastUpdateTime (i2p::util::GetSecondsSinceEpoch ()), m_LastAccessTime (0),
m_LastPersistTime (0), m_NumTunnelsAgreed (0), m_NumTunnelsDeclined (0),
m_NumTunnelsNonReplied (0),m_NumTimesTaken (0), m_NumTimesRejected (0),
m_HasConnected (false), m_IsDuplicated (false)
{
}
void RouterProfile::UpdateTime ()
{
m_LastUpdateTime = GetTime ();
m_LastUpdateTime = i2p::util::GetSecondsSinceEpoch ();
m_IsUpdated = true;
}
@@ -62,7 +60,7 @@ namespace data
usage.put (PEER_PROFILE_USAGE_DUPLICATED, true);
// fill property tree
boost::property_tree::ptree pt;
pt.put (PEER_PROFILE_LAST_UPDATE_TIME, boost::posix_time::to_simple_string (m_LastUpdateTime));
pt.put (PEER_PROFILE_LAST_UPDATE_TIMESTAMP, m_LastUpdateTime);
if (m_LastUnreachableTime)
pt.put (PEER_PROFILE_LAST_UNREACHABLE_TIME, m_LastUnreachableTime);
pt.put_child (PEER_PROFILE_SECTION_PARTICIPATION, participation);
@@ -82,6 +80,7 @@ namespace data
void RouterProfile::Load (const IdentHash& identHash)
{
m_IsUpdated = false;
std::string ident = identHash.ToBase64 ();
std::string path = g_ProfilesStorage.Path(ident);
boost::property_tree::ptree pt;
@@ -104,10 +103,22 @@ namespace data
try
{
auto t = pt.get (PEER_PROFILE_LAST_UPDATE_TIME, "");
if (t.length () > 0)
m_LastUpdateTime = boost::posix_time::time_from_string (t);
if ((GetTime () - m_LastUpdateTime).hours () < PEER_PROFILE_EXPIRATION_TIMEOUT)
auto ts = pt.get (PEER_PROFILE_LAST_UPDATE_TIMESTAMP, 0);
if (ts)
m_LastUpdateTime = ts;
else
{
// try old lastupdatetime
auto ut = pt.get (PEER_PROFILE_LAST_UPDATE_TIME, "");
if (ut.length () > 0)
{
std::istringstream ss (ut); std::tm t;
ss >> std::get_time(&t, "%Y-%b-%d %H:%M:%S");
if (!ss.fail())
m_LastUpdateTime = mktime (&t); // t is local time
}
}
if (i2p::util::GetSecondsSinceEpoch () - m_LastUpdateTime < PEER_PROFILE_EXPIRATION_TIMEOUT)
{
m_LastUnreachableTime = pt.get (PEER_PROFILE_LAST_UNREACHABLE_TIME, 0);
try
@@ -198,10 +209,9 @@ namespace data
return m_NumTunnelsNonReplied > 10*(total + 1);
}
bool RouterProfile::IsDeclinedRecently ()
bool RouterProfile::IsDeclinedRecently (uint64_t ts)
{
if (!m_LastDeclineTime) return false;
auto ts = i2p::util::GetSecondsSinceEpoch ();
if (ts > m_LastDeclineTime + PEER_PROFILE_DECLINED_RECENTLY_INTERVAL ||
ts + PEER_PROFILE_DECLINED_RECENTLY_INTERVAL < m_LastDeclineTime)
m_LastDeclineTime = 0;
@@ -210,7 +220,10 @@ namespace data
bool RouterProfile::IsBad ()
{
if (IsDeclinedRecently () || IsUnreachable () || m_IsDuplicated) return true;
if (IsUnreachable () || m_IsDuplicated) return true;
auto ts = i2p::util::GetSecondsSinceEpoch ();
if (ts > PEER_PROFILE_MAX_DECLINED_INTERVAL + m_LastDeclineTime) return false;
if (IsDeclinedRecently (ts)) return true;
auto isBad = IsAlwaysDeclining () || IsLowPartcipationRate () /*|| IsLowReplyRate ()*/;
if (isBad && m_NumTimesRejected > 10*(m_NumTimesTaken + 1))
{
@@ -245,30 +258,42 @@ namespace data
std::unique_lock<std::mutex> l(g_ProfilesMutex);
auto it = g_Profiles.find (identHash);
if (it != g_Profiles.end ())
{
it->second->SetLastAccessTime (i2p::util::GetSecondsSinceEpoch ());
return it->second;
}
}
auto profile = netdb.NewRouterProfile ();
profile->Load (identHash); // if possible
std::unique_lock<std::mutex> l(g_ProfilesMutex);
std::lock_guard<std::mutex> l(g_ProfilesMutex);
g_Profiles.emplace (identHash, profile);
return profile;
}
bool IsRouterBanned (const IdentHash& identHash)
{
std::unique_lock<std::mutex> l(g_ProfilesMutex);
std::lock_guard<std::mutex> l(g_ProfilesMutex);
auto it = g_Profiles.find (identHash);
if (it != g_Profiles.end ())
return it->second->IsUnreachable ();
return false;
}
bool IsRouterDuplicated (const IdentHash& identHash)
{
std::lock_guard<std::mutex> l(g_ProfilesMutex);
auto it = g_Profiles.find (identHash);
if (it != g_Profiles.end ())
return it->second->IsDuplicated ();
return false;
}
void InitProfilesStorage ()
{
g_ProfilesStorage.SetPlace(i2p::fs::GetDataDir());
g_ProfilesStorage.Init(i2p::data::GetBase64SubstitutionTable(), 64);
}
static void SaveProfilesToDisk (std::list<std::pair<i2p::data::IdentHash, std::shared_ptr<RouterProfile> > >&& profiles)
{
for (auto& it: profiles)
@@ -277,18 +302,20 @@ namespace data
std::future<void> PersistProfiles ()
{
auto ts = GetTime ();
auto ts = i2p::util::GetSecondsSinceEpoch ();
std::list<std::pair<i2p::data::IdentHash, std::shared_ptr<RouterProfile> > > tmp;
{
std::unique_lock<std::mutex> l(g_ProfilesMutex);
std::lock_guard<std::mutex> l(g_ProfilesMutex);
for (auto it = g_Profiles.begin (); it != g_Profiles.end ();)
{
if ((ts - it->second->GetLastUpdateTime ()).total_seconds () > PEER_PROFILE_PERSIST_INTERVAL)
if (it->second->IsUpdated () && ts > it->second->GetLastPersistTime () + PEER_PROFILE_PERSIST_INTERVAL)
{
if (it->second->IsUpdated ())
tmp.push_back (std::make_pair (it->first, it->second));
tmp.push_back (std::make_pair (it->first, it->second));
it->second->SetLastPersistTime (ts);
it->second->SetUpdated (false);
}
if (!it->second->IsUpdated () && ts > std::max (it->second->GetLastUpdateTime (), it->second->GetLastAccessTime ()) + PEER_PROFILE_PERSIST_INTERVAL)
it = g_Profiles.erase (it);
}
else
it++;
}
@@ -302,12 +329,12 @@ namespace data
{
std::unordered_map<i2p::data::IdentHash, std::shared_ptr<RouterProfile> > tmp;
{
std::unique_lock<std::mutex> l(g_ProfilesMutex);
std::lock_guard<std::mutex> l(g_ProfilesMutex);
std::swap (tmp, g_Profiles);
}
auto ts = GetTime ();
auto ts = i2p::util::GetSecondsSinceEpoch ();
for (auto& it: tmp)
if (it.second->IsUseful() && (it.second->IsUpdated () || (ts - it.second->GetLastUpdateTime ()).total_seconds () < PEER_PROFILE_EXPIRATION_TIMEOUT*3600))
if (it.second->IsUseful() && (it.second->IsUpdated () || ts - it.second->GetLastUpdateTime () < PEER_PROFILE_EXPIRATION_TIMEOUT))
it.second->Save (it.first);
}
@@ -325,7 +352,7 @@ namespace data
LogPrint(eLogWarning, "Profiling: Can't stat(): ", path);
continue;
}
if (now - st.st_mtime >= PEER_PROFILE_EXPIRATION_TIMEOUT*3600)
if (now - st.st_mtime >= PEER_PROFILE_EXPIRATION_TIMEOUT)
{
LogPrint(eLogDebug, "Profiling: Removing expired peer profile: ", path);
i2p::fs::Remove(path);
@@ -336,11 +363,11 @@ namespace data
std::future<void> DeleteObsoleteProfiles ()
{
{
auto ts = GetTime ();
std::unique_lock<std::mutex> l(g_ProfilesMutex);
auto ts = i2p::util::GetSecondsSinceEpoch ();
std::lock_guard<std::mutex> l(g_ProfilesMutex);
for (auto it = g_Profiles.begin (); it != g_Profiles.end ();)
{
if ((ts - it->second->GetLastUpdateTime ()).total_seconds () >= PEER_PROFILE_EXPIRATION_TIMEOUT*3600)
if (ts - it->second->GetLastUpdateTime () >= PEER_PROFILE_EXPIRATION_TIMEOUT)
it = g_Profiles.erase (it);
else
it++;
@@ -349,5 +376,47 @@ namespace data
return std::async (std::launch::async, DeleteFilesFromDisk);
}
bool UpdateRouterProfile (const IdentHash& identHash, std::function<void (std::shared_ptr<RouterProfile>)> update)
{
if (!update) return true;
std::shared_ptr<RouterProfile> profile;
{
std::lock_guard<std::mutex> l(g_ProfilesMutex);
auto it = g_Profiles.find (identHash);
if (it != g_Profiles.end ())
profile = it->second;
}
if (profile)
{
update (profile);
return true;
}
// postpone
std::lock_guard<std::mutex> l(g_PostponedUpdatesMutex);
g_PostponedUpdates.emplace_back (identHash, update);
return false;
}
static void ApplyPostponedUpdates (std::list<std::pair<i2p::data::IdentHash, std::function<void (std::shared_ptr<RouterProfile>)> > >&& updates)
{
for (const auto& [ident, update] : updates)
{
auto profile = GetRouterProfile (ident);
update (profile);
}
}
std::future<void> FlushPostponedRouterProfileUpdates ()
{
if (g_PostponedUpdates.empty ()) return std::future<void>();
std::list<std::pair<i2p::data::IdentHash, std::function<void (std::shared_ptr<RouterProfile>)> > > updates;
{
std::lock_guard<std::mutex> l(g_PostponedUpdatesMutex);
g_PostponedUpdates.swap (updates);
}
return std::async (std::launch::async, ApplyPostponedUpdates, std::move (updates));
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2024, The PurpleI2P Project
* Copyright (c) 2013-2025, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -11,7 +11,8 @@
#include <memory>
#include <future>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <functional>
#include <boost/asio.hpp>
#include "Identity.h"
namespace i2p
@@ -22,7 +23,8 @@ namespace data
const char PEER_PROFILE_SECTION_PARTICIPATION[] = "participation";
const char PEER_PROFILE_SECTION_USAGE[] = "usage";
// params
const char PEER_PROFILE_LAST_UPDATE_TIME[] = "lastupdatetime";
const char PEER_PROFILE_LAST_UPDATE_TIME[] = "lastupdatetime"; // deprecated
const char PEER_PROFILE_LAST_UPDATE_TIMESTAMP[] = "lastupdatetimestamp";
const char PEER_PROFILE_LAST_UNREACHABLE_TIME[] = "lastunreachabletime";
const char PEER_PROFILE_PARTICIPATION_AGREED[] = "agreed";
const char PEER_PROFILE_PARTICIPATION_DECLINED[] = "declined";
@@ -32,16 +34,20 @@ namespace data
const char PEER_PROFILE_USAGE_CONNECTED[] = "connected";
const char PEER_PROFILE_USAGE_DUPLICATED[] = "duplicated";
const int PEER_PROFILE_EXPIRATION_TIMEOUT = 36; // in hours (1.5 days)
const int PEER_PROFILE_EXPIRATION_TIMEOUT = 36*60*60; // in seconds (1.5 days)
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_DECLINED_RECENTLY_INTERVAL = 330; // in seconds (5.5 minutes)
const int PEER_PROFILE_MAX_DECLINED_INTERVAL = 4400; // in second (1.5 hours)
const int PEER_PROFILE_PERSIST_INTERVAL = 3300; // in seconds (55 minutes)
const int PEER_PROFILE_UNREACHABLE_INTERVAL = 480; // in seconds (8 minutes)
const int PEER_PROFILE_USEFUL_THRESHOLD = 3;
const int PEER_PROFILE_ALWAYS_DECLINING_NUM = 5; // num declines in row to consider always declined
const int PEER_PROFILE_APPLY_POSTPONED_TIMEOUT = 2100; // in milliseconds
const int PEER_PROFILE_APPLY_POSTPONED_TIMEOUT_VARIANCE = 500; // in milliseconds
class RouterProfile
{
public:
@@ -62,11 +68,21 @@ namespace data
void Connected ();
void Duplicated ();
boost::posix_time::ptime GetLastUpdateTime () const { return m_LastUpdateTime; };
uint64_t GetLastUpdateTime () const { return m_LastUpdateTime; };
bool IsUpdated () const { return m_IsUpdated; };
void SetUpdated (bool updated) { m_IsUpdated = updated; }
uint64_t GetLastAccessTime () const { return m_LastAccessTime; };
void SetLastAccessTime (uint64_t ts) { m_LastAccessTime = ts; };
uint64_t GetLastPersistTime () const { return m_LastPersistTime; };
void SetLastPersistTime (uint64_t ts) { m_LastPersistTime = ts; };
bool IsUseful() const;
bool IsDuplicated () const { return m_IsDuplicated; };
const boost::asio::ip::udp::endpoint& GetLastEndpoint () const { return m_LastEndpoint; }
void SetLastEndpoint (const boost::asio::ip::udp::endpoint& ep) { m_LastEndpoint = ep; }
bool HasLastEndpoint (bool v4) const { return !m_LastEndpoint.address ().is_unspecified () && m_LastEndpoint.port () &&
((v4 && m_LastEndpoint.address ().is_v4 ()) || (!v4 && m_LastEndpoint.address ().is_v6 ())); }
private:
@@ -75,13 +91,13 @@ namespace data
bool IsAlwaysDeclining () const { return !m_NumTunnelsAgreed && m_NumTunnelsDeclined >= 5; };
bool IsLowPartcipationRate () const;
bool IsLowReplyRate () const;
bool IsDeclinedRecently ();
bool IsDeclinedRecently (uint64_t ts);
private:
boost::posix_time::ptime m_LastUpdateTime; // TODO: use std::chrono
bool m_IsUpdated;
uint64_t m_LastDeclineTime, m_LastUnreachableTime; // in seconds
uint64_t m_LastDeclineTime, m_LastUnreachableTime, m_LastUpdateTime,
m_LastAccessTime, m_LastPersistTime; // in seconds
// participation
uint32_t m_NumTunnelsAgreed;
uint32_t m_NumTunnelsDeclined;
@@ -91,14 +107,19 @@ namespace data
uint32_t m_NumTimesRejected;
bool m_HasConnected; // successful trusted(incoming or NTCP2) connection
bool m_IsDuplicated;
// connectivity
boost::asio::ip::udp::endpoint m_LastEndpoint; // SSU2 for non-published addresses
};
std::shared_ptr<RouterProfile> GetRouterProfile (const IdentHash& identHash);
bool IsRouterBanned (const IdentHash& identHash); // check only existing profiles
bool IsRouterDuplicated (const IdentHash& identHash); // check only existing profiles
void InitProfilesStorage ();
std::future<void> DeleteObsoleteProfiles ();
void SaveProfiles ();
std::future<void> PersistProfiles ();
bool UpdateRouterProfile (const IdentHash& identHash, std::function<void (std::shared_ptr<RouterProfile>)> update); // return true if updated immediately, and false if postponed
std::future<void> FlushPostponedRouterProfileUpdates ();
}
}

View File

@@ -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,8 +9,7 @@
#ifndef QUEUE_H__
#define QUEUE_H__
#include <queue>
#include <vector>
#include <list>
#include <mutex>
#include <thread>
#include <condition_variable>
@@ -29,22 +28,20 @@ namespace util
void Put (Element e)
{
std::unique_lock<std::mutex> l(m_QueueMutex);
m_Queue.push (std::move(e));
m_Queue.push_back (std::move(e));
m_NonEmpty.notify_one ();
}
template<template<typename, typename...>class Container, typename... R>
void Put (const Container<Element, R...>& vec)
void Put (std::list<Element>& list)
{
if (!vec.empty ())
if (!list.empty ())
{
std::unique_lock<std::mutex> l(m_QueueMutex);
for (const auto& it: vec)
m_Queue.push (std::move(it));
m_Queue.splice (m_Queue.end (), list);
m_NonEmpty.notify_one ();
}
}
}
}
Element GetNext ()
{
std::unique_lock<std::mutex> l(m_QueueMutex);
@@ -87,7 +84,7 @@ namespace util
return m_Queue.empty ();
}
int GetSize ()
int GetSize () const
{
std::unique_lock<std::mutex> l(m_QueueMutex);
return m_Queue.size ();
@@ -107,15 +104,28 @@ namespace util
return GetNonThreadSafe (true);
}
private:
void GetWholeQueue (std::list<Element>& queue)
{
if (!queue.empty ())
{
std::list<Element> newQueue;
queue.swap (newQueue);
}
{
std::unique_lock<std::mutex> l(m_QueueMutex);
m_Queue.swap (queue);
}
}
private:
Element GetNonThreadSafe (bool peek = false)
{
if (!m_Queue.empty ())
{
auto el = m_Queue.front ();
if (!peek)
m_Queue.pop ();
m_Queue.pop_front ();
return el;
}
return nullptr;
@@ -123,8 +133,8 @@ namespace util
private:
std::queue<Element> m_Queue;
std::mutex m_QueueMutex;
std::list<Element> m_Queue;
mutable std::mutex m_QueueMutex;
std::condition_variable m_NonEmpty;
};
}

View File

@@ -552,7 +552,7 @@ namespace data
if (!url.port)
url.port = 443;
boost::asio::io_service service;
boost::asio::io_context service;
boost::system::error_code ecode;
boost::asio::ssl::context ctx(boost::asio::ssl::context::sslv23);
@@ -562,11 +562,10 @@ namespace data
if(proxyUrl.schema.size())
{
// proxy connection
auto it = boost::asio::ip::tcp::resolver(service).resolve (
boost::asio::ip::tcp::resolver::query (proxyUrl.host, std::to_string(proxyUrl.port)), ecode);
auto it = boost::asio::ip::tcp::resolver(service).resolve (proxyUrl.host, std::to_string(proxyUrl.port), ecode);
if(!ecode)
{
s.lowest_layer().connect(*it, ecode);
s.lowest_layer().connect(*it.begin (), ecode);
if(!ecode)
{
auto & sock = s.next_layer();
@@ -599,7 +598,7 @@ namespace data
LogPrint(eLogError, "Reseed: HTTP CONNECT read error: ", ecode.message());
return "";
}
if(proxyRes.parse(boost::asio::buffer_cast<const char *>(readbuf.data()), readbuf.size()) <= 0)
if(proxyRes.parse(std::string {boost::asio::buffers_begin(readbuf.data ()), boost::asio::buffers_begin(readbuf.data ()) + readbuf.size ()}) <= 0)
{
sock.close();
LogPrint(eLogError, "Reseed: HTTP CONNECT malformed reply");
@@ -638,15 +637,13 @@ namespace data
else
{
// direct connection
auto it = boost::asio::ip::tcp::resolver(service).resolve (
boost::asio::ip::tcp::resolver::query (url.host, std::to_string(url.port)), ecode);
auto endpoints = boost::asio::ip::tcp::resolver(service).resolve (url.host, std::to_string(url.port), ecode);
if (!ecode)
{
bool connected = false;
boost::asio::ip::tcp::resolver::iterator end;
while (it != end)
for (const auto& it: endpoints)
{
boost::asio::ip::tcp::endpoint ep = *it;
boost::asio::ip::tcp::endpoint ep = it;
bool supported = false;
if (!ep.address ().is_unspecified ())
{
@@ -666,7 +663,6 @@ namespace data
break;
}
}
it++;
}
if (!connected)
{
@@ -746,19 +742,16 @@ namespace data
if (!url.port) url.port = 80;
boost::system::error_code ecode;
boost::asio::io_service service;
boost::asio::io_context service;
boost::asio::ip::tcp::socket s(service, boost::asio::ip::tcp::v6());
auto it = boost::asio::ip::tcp::resolver(service).resolve (
boost::asio::ip::tcp::resolver::query (url.host, std::to_string(url.port)), ecode);
auto endpoints = boost::asio::ip::tcp::resolver(service).resolve (url.host, std::to_string(url.port), ecode);
if (!ecode)
{
bool connected = false;
boost::asio::ip::tcp::resolver::iterator end;
while (it != end)
for (const auto& it: endpoints)
{
boost::asio::ip::tcp::endpoint ep = *it;
boost::asio::ip::tcp::endpoint ep = it;
if (
i2p::util::net::IsYggdrasilAddress (ep.address ()) &&
i2p::context.SupportsMesh ()
@@ -772,7 +765,6 @@ namespace data
break;
}
}
it++;
}
if (!connected)
{

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2024, The PurpleI2P Project
* Copyright (c) 2013-2025, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -33,13 +33,14 @@ namespace i2p
m_ShareRatio (100), m_Status (eRouterStatusUnknown), m_StatusV6 (eRouterStatusUnknown),
m_Error (eRouterErrorNone), m_ErrorV6 (eRouterErrorNone),
m_Testing (false), m_TestingV6 (false), m_NetID (I2PD_NET_ID),
m_PublishReplyToken (0), m_IsHiddenMode (false)
m_PublishReplyToken (0), m_IsHiddenMode (false),
m_Rng(i2p::util::GetMonotonicMicroseconds () % 1000000LL), m_IsSaving (false)
{
}
void RouterContext::Init ()
{
srand (i2p::util::GetMillisecondsSinceEpoch () % 1000);
srand (m_Rng () % 1000);
m_StartupTime = i2p::util::GetMonotonicSeconds ();
if (!Load ())
@@ -76,7 +77,7 @@ namespace i2p
m_CongestionUpdateTimer->cancel ();
m_Service->Stop ();
CleanUp (); // GarlicDestination
}
}
}
std::shared_ptr<i2p::data::RouterInfo::Buffer> RouterContext::CopyRouterInfoBuffer () const
@@ -140,7 +141,7 @@ namespace i2p
{
boost::asio::ip::address addr;
if (!host.empty ())
addr = boost::asio::ip::address::from_string (host);
addr = boost::asio::ip::make_address (host);
if (!addr.is_v4())
addr = boost::asio::ip::address_v4 ();
routerInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, addr, ntcp2Port);
@@ -161,7 +162,7 @@ namespace i2p
{
boost::asio::ip::address addr;
if (!host.empty ())
addr = boost::asio::ip::address::from_string (host);
addr = boost::asio::ip::make_address (host);
if (!addr.is_v4())
addr = boost::asio::ip::address_v4 ();
routerInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, addr, ssu2Port);
@@ -192,7 +193,7 @@ namespace i2p
ntcp2Host = host;
boost::asio::ip::address addr;
if (!ntcp2Host.empty ())
addr = boost::asio::ip::address::from_string (ntcp2Host);
addr = boost::asio::ip::make_address (ntcp2Host);
if (!addr.is_v6())
addr = boost::asio::ip::address_v6 ();
routerInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, addr, ntcp2Port);
@@ -211,7 +212,7 @@ namespace i2p
{
boost::asio::ip::address addr;
if (!host.empty ())
addr = boost::asio::ip::address::from_string (host);
addr = boost::asio::ip::make_address (host);
if (!addr.is_v6())
addr = boost::asio::ip::address_v6 ();
routerInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, addr, ssu2Port);
@@ -253,11 +254,36 @@ namespace i2p
void RouterContext::UpdateRouterInfo ()
{
std::shared_ptr<i2p::data::RouterInfo::Buffer> buffer;
{
std::lock_guard<std::mutex> l(m_RouterInfoMutex);
m_RouterInfo.CreateBuffer (m_Keys);
buffer = m_RouterInfo.CopyBuffer ();
}
m_RouterInfo.SaveToFile (i2p::fs::DataDirPath (ROUTER_INFO));
{
// update save buffer to latest
std::lock_guard<std::mutex> l(m_SaveBufferMutex);
m_SaveBuffer = buffer;
}
bool isSaving = false;
if (m_IsSaving.compare_exchange_strong (isSaving, true)) // try to save only if not being saved
{
auto savingRouterInfo = std::async (std::launch::async, [this]()
{
std::shared_ptr<i2p::data::RouterInfo::Buffer> buffer;
while (m_SaveBuffer)
{
{
std::lock_guard<std::mutex> l(m_SaveBufferMutex);
buffer = m_SaveBuffer;
m_SaveBuffer = nullptr;
}
if (buffer)
i2p::data::RouterInfo::SaveToFile (i2p::fs::DataDirPath (ROUTER_INFO), buffer);
}
m_IsSaving = false;
});
}
m_LastUpdateTime = i2p::util::GetSecondsSinceEpoch ();
}
@@ -323,6 +349,12 @@ namespace i2p
case eRouterStatusFirewalled:
SetUnreachable (true, false); // ipv4
break;
case eRouterStatusMesh:
m_RouterInfo.UpdateCaps (m_RouterInfo.GetCaps () | i2p::data::RouterInfo::eReachable);
break;
case eRouterStatusProxy:
m_RouterInfo.UpdateCaps (m_RouterInfo.GetCaps () | i2p::data::RouterInfo::eUnreachable);
break;
default:
;
}
@@ -553,6 +585,12 @@ namespace i2p
UpdateRouterInfo ();
}
void RouterContext::UpdateSSU2Introducer (const i2p::data::IdentHash& h, bool v4, uint32_t iTag, uint32_t iExp)
{
if (m_RouterInfo.UpdateSSU2Introducer (h, v4, iTag, iExp))
UpdateRouterInfo ();
}
void RouterContext::ClearSSU2Introducers (bool v4)
{
auto addr = m_RouterInfo.GetSSU2Address (v4);
@@ -610,8 +648,8 @@ 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_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_LOW_BANDWIDTH4 : limit = 128; type = low; break;
case i2p::data::CAPS_FLAG_HIGH_BANDWIDTH : 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:
@@ -626,9 +664,7 @@ namespace i2p
case low : /* not set */; break;
case extra : caps |= i2p::data::RouterInfo::eExtraBandwidth; break; // 'P'
case unlim : caps |= i2p::data::RouterInfo::eExtraBandwidth;
#if (__cplusplus >= 201703L) // C++ 17 or higher
[[fallthrough]];
#endif
// no break here, extra + high means 'X'
case high : caps |= i2p::data::RouterInfo::eHighBandwidth; break;
}
@@ -639,12 +675,12 @@ namespace i2p
void RouterContext::SetBandwidth (int limit)
{
if (limit > 2000) { SetBandwidth('X'); }
else if (limit > 256) { SetBandwidth('P'); }
else if (limit > 128) { SetBandwidth('O'); }
else if (limit > 64) { SetBandwidth('N'); }
else if (limit > 48) { SetBandwidth('M'); }
else if (limit > 12) { SetBandwidth('L'); }
if (limit > (int)i2p::data::EXTRA_BANDWIDTH_LIMIT) { SetBandwidth('X'); }
else if (limit > (int)i2p::data::HIGH_BANDWIDTH_LIMIT) { SetBandwidth('P'); }
else if (limit > 128) { SetBandwidth('O'); }
else if (limit > 64) { SetBandwidth('N'); }
else if (limit > (int)i2p::data::LOW_BANDWIDTH_LIMIT) { SetBandwidth('M'); }
else if (limit > 12) { SetBandwidth('L'); }
else { SetBandwidth('K'); }
m_BandwidthLimit = limit; // set precise limit
}
@@ -792,7 +828,7 @@ namespace i2p
i2p::config::GetOption("host", ntcp2Host);
if (!ntcp2Host.empty () && ntcp2Port)
{
auto addr = boost::asio::ip::address::from_string (ntcp2Host);
auto addr = boost::asio::ip::make_address (ntcp2Host);
if (addr.is_v6 ())
{
m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, addr, ntcp2Port);
@@ -821,7 +857,7 @@ namespace i2p
std::string host; i2p::config::GetOption("host", host);
if (!host.empty ())
{
auto addr = boost::asio::ip::address::from_string (host);
auto addr = boost::asio::ip::make_address (host);
if (addr.is_v6 ())
{
m_RouterInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, addr, ssu2Port);
@@ -890,7 +926,7 @@ namespace i2p
std::string host; i2p::config::GetOption("host", host);
if (!host.empty ())
{
auto addr = boost::asio::ip::address::from_string (host);
auto addr = boost::asio::ip::make_address (host);
if (addr.is_v4 ())
{
m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, addr, ntcp2Port);
@@ -920,7 +956,7 @@ namespace i2p
std::string host; i2p::config::GetOption("host", host);
if (!host.empty ())
{
auto addr = boost::asio::ip::address::from_string (host);
auto addr = boost::asio::ip::make_address (host);
if (addr.is_v4 ())
{
m_RouterInfo.AddSSU2Address (m_SSU2Keys->staticPublicKey, m_SSU2Keys->intro, addr, ssu2Port);
@@ -1175,7 +1211,7 @@ namespace i2p
void RouterContext::ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg)
{
if (m_Service)
m_Service->GetService ().post (std::bind (&RouterContext::PostGarlicMessage, this, msg));
boost::asio::post (m_Service->GetService (), std::bind (&RouterContext::PostGarlicMessage, this, msg));
else
LogPrint (eLogError, "Router: service is NULL");
}
@@ -1203,7 +1239,7 @@ namespace i2p
void RouterContext::ProcessDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg)
{
if (m_Service)
m_Service->GetService ().post (std::bind (&RouterContext::PostDeliveryStatusMessage, this, msg));
boost::asio::post (m_Service->GetService (), std::bind (&RouterContext::PostDeliveryStatusMessage, this, msg));
else
LogPrint (eLogError, "Router: service is NULL");
}
@@ -1232,7 +1268,7 @@ namespace i2p
} data;
memcpy (data.k, key, 32);
data.t = tag;
m_Service->GetService ().post ([this,data](void)
boost::asio::post (m_Service->GetService (), [this,data](void)
{
AddECIESx25519Key (data.k, data.t);
});
@@ -1349,7 +1385,7 @@ namespace i2p
{
m_PublishTimer->cancel ();
m_PublishTimer->expires_from_now (boost::posix_time::seconds(ROUTER_INFO_PUBLISH_INTERVAL +
rand () % ROUTER_INFO_PUBLISH_INTERVAL_VARIANCE));
m_Rng () % ROUTER_INFO_PUBLISH_INTERVAL_VARIANCE));
m_PublishTimer->async_wait (std::bind (&RouterContext::HandlePublishTimer,
this, std::placeholders::_1));
}
@@ -1398,7 +1434,7 @@ namespace i2p
auto onDrop = [this]()
{
if (m_Service)
m_Service->GetService ().post ([this]() { HandlePublishResendTimer (boost::system::error_code ()); });
boost::asio::post (m_Service->GetService (), [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
@@ -1424,7 +1460,7 @@ namespace i2p
i2p::garlic::WrapECIESX25519MessageForRouter (msg, floodfill->GetIdentity ()->GetEncryptionPublicKey ()));
}
else
LogPrint (eLogInfo, "Router: Can't publish our RouterInfo. No tunnles. Try again in ", ROUTER_INFO_CONFIRMATION_TIMEOUT, " seconds");
LogPrint (eLogInfo, "Router: Can't publish our RouterInfo. No tunnels. Try again in ", ROUTER_INFO_CONFIRMATION_TIMEOUT, " milliseconds");
}
m_PublishExcluded.insert (floodfill->GetIdentHash ());
m_PublishReplyToken = replyToken;
@@ -1438,7 +1474,7 @@ namespace i2p
if (m_PublishTimer)
{
m_PublishTimer->cancel ();
m_PublishTimer->expires_from_now (boost::posix_time::seconds(ROUTER_INFO_CONFIRMATION_TIMEOUT));
m_PublishTimer->expires_from_now (boost::posix_time::milliseconds(ROUTER_INFO_CONFIRMATION_TIMEOUT));
m_PublishTimer->async_wait (std::bind (&RouterContext::HandlePublishResendTimer,
this, std::placeholders::_1));
}
@@ -1461,7 +1497,8 @@ namespace i2p
if (m_CongestionUpdateTimer)
{
m_CongestionUpdateTimer->cancel ();
m_CongestionUpdateTimer->expires_from_now (boost::posix_time::seconds(ROUTER_INFO_CONGESTION_UPDATE_INTERVAL));
m_CongestionUpdateTimer->expires_from_now (boost::posix_time::seconds(
ROUTER_INFO_CONGESTION_UPDATE_INTERVAL + m_Rng () % ROUTER_INFO_CONGESTION_UPDATE_INTERVAL_VARIANCE));
m_CongestionUpdateTimer->async_wait (std::bind (&RouterContext::HandleCongestionUpdateTimer,
this, std::placeholders::_1));
}
@@ -1481,7 +1518,7 @@ namespace i2p
void RouterContext::UpdateCongestion ()
{
auto c = i2p::data::RouterInfo::eLowCongestion;
if (!AcceptsTunnels () || !m_ShareRatio)
if (!AcceptsTunnels () || !m_ShareRatio)
c = i2p::data::RouterInfo::eRejectAll;
else
{
@@ -1500,7 +1537,7 @@ namespace i2p
if (m_CleanupTimer)
{
m_CleanupTimer->cancel ();
m_CleanupTimer->expires_from_now (boost::posix_time::minutes(ROUTER_INFO_CLEANUP_INTERVAL));
m_CleanupTimer->expires_from_now (boost::posix_time::seconds(ROUTER_INFO_CLEANUP_INTERVAL));
m_CleanupTimer->async_wait (std::bind (&RouterContext::HandleCleanupTimer,
this, std::placeholders::_1));
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2024, The PurpleI2P Project
* Copyright (c) 2013-2025, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -12,6 +12,7 @@
#include <inttypes.h>
#include <string>
#include <memory>
#include <random>
#include <unordered_set>
#include <boost/asio.hpp>
#include "Identity.h"
@@ -34,10 +35,11 @@ namespace garlic
const int ROUTER_INFO_PUBLISH_INTERVAL = 39*60; // in seconds
const int ROUTER_INFO_INITIAL_PUBLISH_INTERVAL = 10; // in seconds
const int ROUTER_INFO_PUBLISH_INTERVAL_VARIANCE = 105;// in seconds
const int ROUTER_INFO_CONFIRMATION_TIMEOUT = 5; // in seconds
const int ROUTER_INFO_CONFIRMATION_TIMEOUT = 1600; // in milliseconds
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
const int ROUTER_INFO_CONGESTION_UPDATE_INTERVAL = 11*60; // in seconds
const int ROUTER_INFO_CONGESTION_UPDATE_INTERVAL_VARIANCE = 130; // in seconds
const int ROUTER_INFO_CLEANUP_INTERVAL = 102; // in seconds
enum RouterStatus
{
@@ -90,7 +92,7 @@ namespace garlic
public:
RouterService (): RunnableServiceWithWork ("Router") {};
boost::asio::io_service& GetService () { return GetIOService (); };
auto& GetService () { return GetIOService (); };
void Start () { StartIOService (); };
void Stop () { StopIOService (); };
};
@@ -146,7 +148,6 @@ 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
@@ -154,6 +155,7 @@ namespace garlic
void PublishSSU2Address (int port, bool publish, bool v4, bool v6);
bool AddSSU2Introducer (const i2p::data::RouterInfo::Introducer& introducer, bool v4);
void RemoveSSU2Introducer (const i2p::data::IdentHash& h, bool v4);
void UpdateSSU2Introducer (const i2p::data::IdentHash& h, bool v4, uint32_t iTag, uint32_t iExp);
void ClearSSU2Introducers (bool v4);
bool IsUnreachable () const;
void SetUnreachable (bool v4, bool v6);
@@ -177,6 +179,7 @@ namespace garlic
void SetMTU (int mtu, bool v4);
void SetHidden(bool hide) { m_IsHiddenMode = hide; };
bool IsHidden() const { return m_IsHiddenMode; };
bool IsLimitedConnectivity () const { return m_Status == eRouterStatusProxy; }; // TODO: implement other cases
i2p::crypto::NoiseSymmetricState& GetCurrentNoiseState () { return m_CurrentNoiseState; };
void UpdateNTCP2V6Address (const boost::asio::ip::address& host); // called from Daemon. TODO: remove
@@ -184,24 +187,24 @@ namespace garlic
void UpdateTimestamp (uint64_t ts); // in seconds, called from NetDb before publishing
// implements LocalDestination
std::shared_ptr<const i2p::data::IdentityEx> GetIdentity () const { return m_Keys.GetPublic (); };
bool Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const;
void Sign (const uint8_t * buf, int len, uint8_t * signature) const { m_Keys.Sign (buf, len, signature); };
void SetLeaseSetUpdated () {};
std::shared_ptr<const i2p::data::IdentityEx> GetIdentity () const override{ return m_Keys.GetPublic (); };
bool Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const override;
void SetLeaseSetUpdated (bool post) override {};
// implements GarlicDestination
std::shared_ptr<const i2p::data::LocalLeaseSet> GetLeaseSet () { return nullptr; };
std::shared_ptr<i2p::tunnel::TunnelPool> GetTunnelPool () const;
std::shared_ptr<const i2p::data::LocalLeaseSet> GetLeaseSet () override { return nullptr; };
std::shared_ptr<i2p::tunnel::TunnelPool> GetTunnelPool () const override;
// override GarlicDestination
void ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg);
void ProcessDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg);
void ProcessGarlicMessage (std::shared_ptr<I2NPMessage> msg) override;
void ProcessDeliveryStatusMessage (std::shared_ptr<I2NPMessage> msg) override;
void SubmitECIESx25519Key (const uint8_t * key, uint64_t tag) override;
protected:
// implements GarlicDestination
void HandleI2NPMessage (const uint8_t * buf, size_t len);
bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID);
void HandleI2NPMessage (const uint8_t * buf, size_t len) override;
bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID) override;
private:
@@ -214,6 +217,7 @@ namespace garlic
void UpdateSSU2Keys ();
bool Load ();
void SaveKeys ();
void Sign (const uint8_t * buf, int len, uint8_t * signature) const { m_Keys.Sign (buf, len, signature); };
uint16_t SelectRandomPort () const;
void PublishNTCP2Address (std::shared_ptr<i2p::data::RouterInfo::Address> address, int port, bool publish) const;
@@ -261,6 +265,10 @@ namespace garlic
uint32_t m_PublishReplyToken;
bool m_IsHiddenMode; // not publish
mutable std::mutex m_RouterInfoMutex;
std::mt19937 m_Rng;
std::shared_ptr<i2p::data::RouterInfo::Buffer> m_SaveBuffer;
std::mutex m_SaveBufferMutex; // TODO: make m_SaveBuffer atomic
std::atomic<bool> m_IsSaving;
};
extern RouterContext context;

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2024, The PurpleI2P Project
* Copyright (c) 2013-2025, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -10,9 +10,10 @@
#include <string.h>
#include "I2PEndian.h"
#include <fstream>
#include <boost/lexical_cast.hpp>
#include <boost/make_shared.hpp>
#if (BOOST_VERSION >= 105300)
#include <memory>
#include <charconv>
#include <boost/algorithm/string.hpp> // for boost::to_lower
#ifndef __cpp_lib_atomic_shared_ptr
#include <boost/atomic.hpp>
#endif
#include "version.h"
@@ -39,27 +40,28 @@ namespace data
RouterInfo::RouterInfo (): m_Buffer (nullptr)
{
m_Addresses = boost::make_shared<Addresses>(); // create empty list
m_Addresses = AddressesPtr(new Addresses ()); // create empty list
}
RouterInfo::RouterInfo (const std::string& fullPath):
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_IsBufferScheduledToDelete (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
m_Buffer = NewBuffer (); // always RouterInfo's
m_Addresses = AddressesPtr(new Addresses ()); // create empty list
m_Buffer = RouterInfo::NewBuffer (); // always RouterInfo's
ReadFromFile (fullPath);
}
RouterInfo::RouterInfo (std::shared_ptr<Buffer>&& buf, size_t len):
m_FamilyID (0), m_IsUpdated (true), m_IsUnreachable (false), m_IsFloodfill (false),
m_SupportedTransports (0), m_ReachableTransports (0), m_PublishedTransports (0),
m_IsBufferScheduledToDelete (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_Addresses = AddressesPtr(new Addresses ()); // create empty list
m_Buffer = buf;
if (m_Buffer) m_Buffer->SetBufferLen (len);
ReadFromBuffer (true);
@@ -73,7 +75,7 @@ namespace data
}
RouterInfo::RouterInfo (const uint8_t * buf, size_t len):
RouterInfo (std::make_shared<Buffer> (buf, len), len)
RouterInfo (netdb.NewRouterInfoBuffer (buf, len), len)
{
}
@@ -104,8 +106,7 @@ namespace data
// skip identity
size_t identityLen = m_RouterIdentity->GetFullLen ();
// read new RI
std::stringstream str (std::string ((char *)buf + identityLen, len - identityLen));
ReadFromStream (str);
ReadFromBuffer (buf + identityLen, len - identityLen);
if (!m_IsUnreachable)
UpdateBuffer (buf, len); // save buffer
// don't delete buffer until saved to the file
@@ -193,39 +194,34 @@ namespace data
}
}
// parse RI
std::stringstream str;
str.write ((const char *)m_Buffer->data () + identityLen, bufferLen - identityLen);
ReadFromStream (str);
if (!str)
if (!ReadFromBuffer (m_Buffer->data () + identityLen, bufferLen - identityLen))
{
LogPrint (eLogError, "RouterInfo: Malformed message");
m_IsUnreachable = true;
}
}
}
void RouterInfo::ReadFromStream (std::istream& s)
bool RouterInfo::ReadFromBuffer (const uint8_t * buf, size_t len)
{
if (!s) return;
if (len < 9) return false;
m_Caps = 0; m_Congestion = eLowCongestion;
s.read ((char *)&m_Timestamp, sizeof (m_Timestamp));
m_Timestamp = be64toh (m_Timestamp);
m_Timestamp = bufbe64toh (buf);
size_t offset = 8; // timestamp
// read addresses
auto addresses = NewAddresses ();
uint8_t numAddresses;
s.read ((char *)&numAddresses, sizeof (numAddresses));
uint8_t numAddresses = buf[offset]; offset++;
for (int i = 0; i < numAddresses; i++)
{
if (offset + 9 > len) return false; // 1 byte cost + 8 bytes date
uint8_t supportedTransports = 0;
auto address = NewAddress ();
uint8_t cost; // ignore
s.read ((char *)&cost, sizeof (cost));
s.read ((char *)&address->date, sizeof (address->date));
offset++; // cost, ignore
address->date = bufbe64toh (buf + offset); offset += 8; // date
bool isHost = false, isStaticKey = false, isV2 = false, isIntroKey = false;
char transportStyle[6];
ReadString (transportStyle, 6, s);
if (!strncmp (transportStyle, "NTCP", 4)) // NTCP or NTCP2
auto transportStyle = ExtractString (buf + offset, len - offset); offset += transportStyle.length () + 1;
if (!transportStyle.compare (0, 4, "NTCP")) // NTCP or NTCP2
address->transportStyle = eTransportNTCP2;
else if (!strncmp (transportStyle, "SSU", 3)) // SSU or SSU2
else if (!transportStyle.compare (0, 3, "SSU")) // SSU or SSU2
{
address->transportStyle = eTransportSSU2;
address->ssu.reset (new SSUExt ());
@@ -235,27 +231,25 @@ namespace data
address->transportStyle = eTransportUnknown;
address->caps = 0;
address->port = 0;
uint16_t size, r = 0;
s.read ((char *)&size, sizeof (size)); if (!s) return;
size = be16toh (size);
if (offset + 2 > len) return false;
uint16_t size = bufbe16toh (buf + offset); offset += 2; // size
if (offset + size >= len) return false;
if (address->transportStyle == eTransportUnknown)
{
// skip unknown address
s.seekg (size, std::ios_base::cur);
if (s) continue; else return;
offset += size;
continue;
}
size_t r = 0;
while (r < size)
{
char key[255], value[255];
r += ReadString (key, 255, s);
s.seekg (1, std::ios_base::cur); r++; // =
r += ReadString (value, 255, s);
s.seekg (1, std::ios_base::cur); r++; // ;
if (!s) return;
if (!strcmp (key, "host"))
auto [key, value, sz] = ExtractParam (buf + offset, len - offset);
r += sz; offset += sz;
if (key.empty ()) continue;
if (key == "host")
{
boost::system::error_code ecode;
address->host = boost::asio::ip::address::from_string (value, ecode);
address->host = boost::asio::ip::make_address (value, ecode);
if (!ecode && !address->host.is_unspecified ())
{
if (!i2p::transport::transports.IsInReservedRange (address->host) ||
@@ -266,63 +260,53 @@ namespace data
address->transportStyle = eTransportUnknown;
}
}
else if (!strcmp (key, "port"))
else if (key == "port")
{
try
{
address->port = boost::lexical_cast<int>(value);
}
catch (std::exception& ex)
{
LogPrint (eLogWarning, "RouterInfo: 'port' exception ", ex.what ());
}
auto res = std::from_chars(value.data(), value.data() + value.size(), address->port);
if (res.ec != std::errc())
LogPrint (eLogWarning, "RouterInfo: 'port' conversion error: ", std::make_error_code (res.ec).message ());
}
else if (!strcmp (key, "mtu"))
else if (key == "mtu")
{
if (address->ssu)
{
try
{
address->ssu->mtu = boost::lexical_cast<int>(value);
}
catch (std::exception& ex)
{
LogPrint (eLogWarning, "RouterInfo: 'mtu' exception ", ex.what ());
}
auto res = std::from_chars(value.data(), value.data() + value.size(), address->ssu->mtu);
if (res.ec != std::errc())
LogPrint (eLogWarning, "RouterInfo: 'mtu' conversion error: ", std::make_error_code (res.ec).message ());
}
else
LogPrint (eLogWarning, "RouterInfo: Unexpected field 'mtu' for NTCP2");
}
else if (!strcmp (key, "caps"))
else if (key == "caps")
address->caps = ExtractAddressCaps (value);
else if (!strcmp (key, "s")) // ntcp2 or ssu2 static key
else if (key == "s") // ntcp2 or ssu2 static key
{
if (Base64ToByteStream (value, strlen (value), address->s, 32) == 32 &&
if (Base64ToByteStream (value.data (), value.length (), 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
else if (key == "i") // ntcp2 iv or ssu2 intro
{
if (address->IsNTCP2 ())
{
if (Base64ToByteStream (value, strlen (value), address->i, 16) == 16)
if (Base64ToByteStream (value.data (), value.length (), address->i, 16) == 16)
address->published = true; // presence of "i" means "published" NTCP2
else
address->transportStyle = eTransportUnknown; // invalid address
}
else if (address->IsSSU2 ())
{
if (Base64ToByteStream (value, strlen (value), address->i, 32) == 32)
if (Base64ToByteStream (value.data (), value.length (), address->i, 32) == 32)
isIntroKey = true;
else
address->transportStyle = eTransportUnknown; // invalid address
}
}
else if (!strcmp (key, "v"))
else if (key == "v")
{
if (!strcmp (value, "2"))
if (value == "2")
isV2 = true;
else
{
@@ -338,13 +322,11 @@ namespace data
LogPrint (eLogError, "RouterInfo: Introducer is presented for non-SSU address. Skipped");
continue;
}
size_t l = strlen(key);
unsigned char index = key[l-1] - '0'; // TODO:
key[l-1] = 0;
unsigned char index = key[key.length () - 1] - '0'; // TODO:
if (index > 9)
{
LogPrint (eLogError, "RouterInfo: Unexpected introducer's index ", index, " skipped");
if (s) continue; else return;
continue;
}
if (index >= address->ssu->introducers.size ())
{
@@ -353,34 +335,23 @@ namespace data
address->ssu->introducers.resize (index + 1);
}
Introducer& introducer = address->ssu->introducers.at (index);
if (!strcmp (key, "itag"))
auto key1 = key.substr(0, key.length () - 1);
if (key1 == "itag")
{
try
{
introducer.iTag = boost::lexical_cast<uint32_t>(value);
}
catch (std::exception& ex)
{
LogPrint (eLogWarning, "RouterInfo: 'itag' exception ", ex.what ());
}
auto res = std::from_chars(value.data(), value.data() + value.size(), introducer.iTag);
if (res.ec != std::errc())
LogPrint (eLogWarning, "RouterInfo: 'itag' conversion error: ", std::make_error_code (res.ec).message ());
}
else if (!strcmp (key, "ih"))
Base64ToByteStream (value, strlen (value), introducer.iH, 32);
else if (!strcmp (key, "iexp"))
else if (key1 == "ih")
Base64ToByteStream (value.data (), value.length (), introducer.iH, 32);
else if (key1 == "iexp")
{
try
{
introducer.iExp = boost::lexical_cast<uint32_t>(value);
}
catch (std::exception& ex)
{
LogPrint (eLogWarning, "RouterInfo: 'iexp' exception ", ex.what ());
}
auto res = std::from_chars(value.data(), value.data() + value.size(), introducer.iExp);
if (res.ec != std::errc())
LogPrint (eLogWarning, "RouterInfo: 'iexp' conversion error: ", std::make_error_code (res.ec).message ());
}
}
if (!s) return;
}
}
if (address->transportStyle == eTransportNTCP2)
{
if (isStaticKey)
@@ -438,72 +409,79 @@ namespace data
}
m_ReachableTransports |= m_PublishedTransports;
// update addresses
#if (BOOST_VERSION >= 105300)
#ifdef __cpp_lib_atomic_shared_ptr
m_Addresses = addresses;
#else
boost::atomic_store (&m_Addresses, addresses);
#else
m_Addresses = addresses; // race condition
#endif
// read peers
uint8_t numPeers;
s.read ((char *)&numPeers, sizeof (numPeers)); if (!s) return;
s.seekg (numPeers*32, std::ios_base::cur); // TODO: read peers
if (offset + 1 > len) return false;
uint8_t numPeers = buf[offset]; offset++; // num peers
offset += numPeers*32; // TODO: read peers
// read properties
if (offset + 2 > len) return false;
m_Version = 0;
bool isNetId = false;
std::string family;
uint16_t size, r = 0;
s.read ((char *)&size, sizeof (size)); if (!s) return;
size = be16toh (size);
uint16_t size = bufbe16toh (buf + offset); offset += 2; // size
if (offset + size > len) return false;
size_t r = 0;
while (r < size)
{
char key[255], value[255];
r += ReadString (key, 255, s);
s.seekg (1, std::ios_base::cur); r++; // =
r += ReadString (value, 255, s);
s.seekg (1, std::ios_base::cur); r++; // ;
if (!s) return;
auto [key, value, sz] = ExtractParam (buf + offset, len - offset);
r += sz; offset += sz;
if (key.empty ()) continue;
SetProperty (key, value);
// extract caps
if (!strcmp (key, "caps"))
if (key == "caps")
{
ExtractCaps (value);
m_IsFloodfill = IsDeclaredFloodfill ();
}
// extract version
else if (!strcmp (key, ROUTER_INFO_PROPERTY_VERSION))
else if (key == ROUTER_INFO_PROPERTY_VERSION)
{
m_Version = 0;
char * ch = value;
while (*ch)
for (auto ch: value)
{
if (*ch >= '0' && *ch <= '9')
if (ch >= '0' && ch <= '9')
{
m_Version *= 10;
m_Version += (*ch - '0');
m_Version += (ch - '0');
}
ch++;
}
if (m_Version < NETDB_MIN_PEER_TEST_VERSION && (m_SupportedTransports & (eSSU2V4 | eSSU2V6)))
{
auto addresses = GetAddresses ();
if (addresses)
{
if ((*addresses)[eSSU2V4Idx]) (*addresses)[eSSU2V4Idx]->caps &= ~eSSUTesting;
if ((*addresses)[eSSU2V6Idx]) (*addresses)[eSSU2V6Idx]->caps &= ~eSSUTesting;
}
}
}
// check netId
else if (!strcmp (key, ROUTER_INFO_PROPERTY_NETID))
else if (key == ROUTER_INFO_PROPERTY_NETID)
{
isNetId = true;
if (atoi (value) != i2p::context.GetNetID ())
int netID;
auto res = std::from_chars(value.data(), value.data() + value.size(), netID);
if (res.ec != std::errc() || netID != i2p::context.GetNetID ())
{
LogPrint (eLogError, "RouterInfo: Unexpected ", ROUTER_INFO_PROPERTY_NETID, "=", value);
m_IsUnreachable = true;
}
}
// family
else if (!strcmp (key, ROUTER_INFO_PROPERTY_FAMILY))
else if (key == ROUTER_INFO_PROPERTY_FAMILY)
{
family = value;
boost::to_lower (family);
}
else if (!strcmp (key, ROUTER_INFO_PROPERTY_FAMILY_SIG))
else if (key == ROUTER_INFO_PROPERTY_FAMILY_SIG)
{
if (netdb.GetFamilies ().VerifyFamily (family, GetIdentHash (), value))
if (netdb.GetFamilies ().VerifyFamily (family, GetIdentHash (), value)) // TODO
m_FamilyID = netdb.GetFamilies ().GetFamilyID (family);
else
{
@@ -511,25 +489,24 @@ namespace data
SetUnreachable (true);
}
}
if (!s) return;
}
if (!m_SupportedTransports || !isNetId || !m_Version)
SetUnreachable (true);
}
return true;
}
bool RouterInfo::IsFamily (FamilyID famid) const
{
return m_FamilyID == famid;
}
void RouterInfo::ExtractCaps (const char * value)
void RouterInfo::ExtractCaps (std::string_view value)
{
const char * cap = value;
while (*cap)
for (auto cap: value)
{
switch (*cap)
switch (cap)
{
case CAPS_FLAG_FLOODFILL:
m_Caps |= Caps::eFloodfill;
@@ -537,17 +514,17 @@ namespace data
case CAPS_FLAG_LOW_BANDWIDTH1:
case CAPS_FLAG_LOW_BANDWIDTH2:
case CAPS_FLAG_LOW_BANDWIDTH3:
m_BandwidthCap = *cap;
case CAPS_FLAG_LOW_BANDWIDTH4:
m_BandwidthCap = cap;
break;
case CAPS_FLAG_HIGH_BANDWIDTH1:
case CAPS_FLAG_HIGH_BANDWIDTH2:
case CAPS_FLAG_HIGH_BANDWIDTH:
m_Caps |= Caps::eHighBandwidth;
m_BandwidthCap = *cap;
m_BandwidthCap = cap;
break;
case CAPS_FLAG_EXTRA_BANDWIDTH1:
case CAPS_FLAG_EXTRA_BANDWIDTH2:
m_Caps |= Caps::eExtraBandwidth | Caps::eHighBandwidth;
m_BandwidthCap = *cap;
m_BandwidthCap = cap;
break;
case CAPS_FLAG_HIDDEN:
m_Caps |= Caps::eHidden;
@@ -569,17 +546,15 @@ namespace data
break;
default: ;
}
cap++;
}
}
uint8_t RouterInfo::ExtractAddressCaps (const char * value) const
}
uint8_t RouterInfo::ExtractAddressCaps (std::string_view value) const
{
uint8_t caps = 0;
const char * cap = value;
while (*cap)
for (auto cap: value)
{
switch (*cap)
switch (cap)
{
case CAPS_FLAG_V4:
caps |= AddressCaps::eV4;
@@ -595,11 +570,10 @@ namespace data
break;
default: ;
}
cap++;
}
return caps;
}
}
void RouterInfo::UpdateIntroducers (std::shared_ptr<Address> address, uint64_t ts)
{
if (!address || !address->ssu) return;
@@ -659,25 +633,41 @@ namespace data
return SaveToFile (fullPath, m_Buffer);
}
size_t RouterInfo::ReadString (char * str, size_t len, std::istream& s) const
std::string_view RouterInfo::ExtractString (const uint8_t * buf, size_t len) const
{
uint8_t l;
s.read ((char *)&l, 1);
if (l < len)
{
s.read (str, l);
if (!s) l = 0; // failed, return empty string
str[l] = 0;
}
else
uint8_t l = buf[0];
if (l > len)
{
LogPrint (eLogWarning, "RouterInfo: String length ", (int)l, " exceeds buffer size ", len);
s.seekg (l, std::ios::cur); // skip
str[0] = 0;
}
return l+1;
l = len;
}
return { (const char *)(buf + 1), l };
}
std::tuple<std::string_view, std::string_view, size_t> RouterInfo::ExtractParam (const uint8_t * buf, size_t len) const
{
auto key = ExtractString (buf, len);
size_t offset = key.length () + 1;
if (offset >= len) return { std::string_view(), std::string_view(), len };
if (buf[offset] != '=')
{
LogPrint (eLogWarning, "RouterInfo: Unexpected character ", buf[offset], " instead '=' after ", key);
key = std::string_view();
}
offset++;
if (offset >= len) return { key, std::string_view(), len };
auto value = ExtractString (buf + offset, len - offset);
offset += value.length () + 1;
if (offset >= len) return { key, std::string_view(), len };
if (buf[offset] != ';')
{
LogPrint (eLogWarning, "RouterInfo: Unexpected character ", buf[offset], " instead ';' after ", value);
value = std::string_view();
}
offset++;
return { key, value, offset };
}
void RouterInfo::AddNTCP2Address (const uint8_t * staticKey, const uint8_t * iv,int port, uint8_t caps)
{
auto addr = std::make_shared<Address>();
@@ -691,12 +681,12 @@ namespace data
if (addr->IsV4 ())
{
m_SupportedTransports |= eNTCP2V4;
(*m_Addresses)[eNTCP2V4Idx] = addr;
(*GetAddresses ())[eNTCP2V4Idx] = addr;
}
if (addr->IsV6 ())
{
m_SupportedTransports |= eNTCP2V6;
(*m_Addresses)[eNTCP2V6Idx] = addr;
(*GetAddresses ())[eNTCP2V6Idx] = addr;
}
}
@@ -717,11 +707,12 @@ namespace data
if (host.is_v4 ()) addr->caps |= eV4;
if (host.is_v6 ()) addr->caps |= eV6;
}
auto addresses = GetAddresses ();
if (addr->IsV4 ())
{
m_SupportedTransports |= eNTCP2V4;
m_ReachableTransports |= eNTCP2V4;
(*m_Addresses)[eNTCP2V4Idx] = addr;
(*addresses)[eNTCP2V4Idx] = addr;
}
if (addr->IsV6 ())
{
@@ -729,30 +720,31 @@ namespace data
{
m_SupportedTransports |= eNTCP2V6Mesh;
m_ReachableTransports |= eNTCP2V6Mesh;
(*m_Addresses)[eNTCP2V6MeshIdx] = addr;
(*addresses)[eNTCP2V6MeshIdx] = addr;
}
else
{
m_SupportedTransports |= eNTCP2V6;
m_ReachableTransports |= eNTCP2V6;
(*m_Addresses)[eNTCP2V6Idx] = addr;
(*addresses)[eNTCP2V6Idx] = addr;
}
}
}
void RouterInfo::RemoveNTCP2Address (bool v4)
{
auto addresses = GetAddresses ();
if (v4)
{
if ((*m_Addresses)[eNTCP2V6Idx])
(*m_Addresses)[eNTCP2V6Idx]->caps &= ~AddressCaps::eV4;
(*m_Addresses)[eNTCP2V4Idx].reset ();
if ((*addresses)[eNTCP2V6Idx])
(*addresses)[eNTCP2V6Idx]->caps &= ~AddressCaps::eV4;
(*addresses)[eNTCP2V4Idx].reset ();
}
else
{
if ((*m_Addresses)[eNTCP2V4Idx])
(*m_Addresses)[eNTCP2V4Idx]->caps &= ~AddressCaps::eV6;
(*m_Addresses)[eNTCP2V6Idx].reset ();
if ((*addresses)[eNTCP2V4Idx])
(*addresses)[eNTCP2V4Idx]->caps &= ~AddressCaps::eV6;
(*addresses)[eNTCP2V6Idx].reset ();
}
UpdateSupportedTransports ();
}
@@ -768,15 +760,16 @@ namespace data
addr->ssu->mtu = 0;
memcpy (addr->s, staticKey, 32);
memcpy (addr->i, introKey, 32);
auto addresses = GetAddresses ();
if (addr->IsV4 ())
{
m_SupportedTransports |= eSSU2V4;
(*m_Addresses)[eSSU2V4Idx] = addr;
(*addresses)[eSSU2V4Idx] = addr;
}
if (addr->IsV6 ())
{
m_SupportedTransports |= eSSU2V6;
(*m_Addresses)[eSSU2V6Idx] = addr;
(*addresses)[eSSU2V6Idx] = addr;
}
}
@@ -801,33 +794,35 @@ namespace data
if (host.is_v4 ()) addr->caps |= eV4;
if (host.is_v6 ()) addr->caps |= eV6;
}
auto addresses = GetAddresses ();
if (addr->IsV4 ())
{
m_SupportedTransports |= eSSU2V4;
m_ReachableTransports |= eSSU2V4;
(*m_Addresses)[eSSU2V4Idx] = addr;
(*addresses)[eSSU2V4Idx] = addr;
}
if (addr->IsV6 ())
{
m_SupportedTransports |= eSSU2V6;
m_ReachableTransports |= eSSU2V6;
(*m_Addresses)[eSSU2V6Idx] = addr;
(*addresses)[eSSU2V6Idx] = addr;
}
}
void RouterInfo::RemoveSSU2Address (bool v4)
{
auto addresses = GetAddresses ();
if (v4)
{
if ((*m_Addresses)[eSSU2V6Idx])
(*m_Addresses)[eSSU2V6Idx]->caps &= ~AddressCaps::eV4;
(*m_Addresses)[eSSU2V4Idx].reset ();
if ((*addresses)[eSSU2V6Idx])
(*addresses)[eSSU2V6Idx]->caps &= ~AddressCaps::eV4;
(*addresses)[eSSU2V4Idx].reset ();
}
else
{
if ((*m_Addresses)[eSSU2V4Idx])
(*m_Addresses)[eSSU2V4Idx]->caps &= ~AddressCaps::eV6;
(*m_Addresses)[eSSU2V6Idx].reset ();
if ((*addresses)[eSSU2V4Idx])
(*addresses)[eSSU2V4Idx]->caps &= ~AddressCaps::eV6;
(*addresses)[eSSU2V6Idx].reset ();
}
UpdateSupportedTransports ();
}
@@ -868,17 +863,18 @@ namespace data
{
if (IsV6 ())
{
if ((*m_Addresses)[eNTCP2V6Idx])
auto addresses = GetAddresses ();
if ((*addresses)[eNTCP2V6Idx])
{
if ((*m_Addresses)[eNTCP2V6Idx]->IsV4 () && (*m_Addresses)[eNTCP2V4Idx])
(*m_Addresses)[eNTCP2V4Idx]->caps &= ~AddressCaps::eV6;
(*m_Addresses)[eNTCP2V6Idx].reset ();
if ((*addresses)[eNTCP2V6Idx]->IsV4 () && (*addresses)[eNTCP2V4Idx])
(*addresses)[eNTCP2V4Idx]->caps &= ~AddressCaps::eV6;
(*addresses)[eNTCP2V6Idx].reset ();
}
if ((*m_Addresses)[eSSU2V6Idx])
if ((*addresses)[eSSU2V6Idx])
{
if ((*m_Addresses)[eSSU2V6Idx]->IsV4 () && (*m_Addresses)[eSSU2V4Idx])
(*m_Addresses)[eSSU2V4Idx]->caps &= ~AddressCaps::eV6;
(*m_Addresses)[eSSU2V6Idx].reset ();
if ((*addresses)[eSSU2V6Idx]->IsV4 () && (*addresses)[eSSU2V4Idx])
(*addresses)[eSSU2V4Idx]->caps &= ~AddressCaps::eV6;
(*addresses)[eSSU2V6Idx].reset ();
}
UpdateSupportedTransports ();
}
@@ -888,17 +884,18 @@ namespace data
{
if (IsV4 ())
{
if ((*m_Addresses)[eNTCP2V4Idx])
auto addresses = GetAddresses ();
if ((*addresses)[eNTCP2V4Idx])
{
if ((*m_Addresses)[eNTCP2V4Idx]->IsV6 () && (*m_Addresses)[eNTCP2V6Idx])
(*m_Addresses)[eNTCP2V6Idx]->caps &= ~AddressCaps::eV4;
(*m_Addresses)[eNTCP2V4Idx].reset ();
if ((*addresses)[eNTCP2V4Idx]->IsV6 () && (*addresses)[eNTCP2V6Idx])
(*addresses)[eNTCP2V6Idx]->caps &= ~AddressCaps::eV4;
(*addresses)[eNTCP2V4Idx].reset ();
}
if ((*m_Addresses)[eSSU2V4Idx])
if ((*addresses)[eSSU2V4Idx])
{
if ((*m_Addresses)[eSSU2V4Idx]->IsV6 () && (*m_Addresses)[eSSU2V6Idx])
(*m_Addresses)[eSSU2V6Idx]->caps &= ~AddressCaps::eV4;
(*m_Addresses)[eSSU2V4Idx].reset ();
if ((*addresses)[eSSU2V4Idx]->IsV6 () && (*addresses)[eSSU2V6Idx])
(*addresses)[eSSU2V6Idx]->caps &= ~AddressCaps::eV4;
(*addresses)[eSSU2V4Idx].reset ();
}
UpdateSupportedTransports ();
}
@@ -919,7 +916,7 @@ namespace data
{
m_SupportedTransports &= ~eNTCP2V6Mesh;
m_ReachableTransports &= ~eNTCP2V6Mesh;
(*m_Addresses)[eNTCP2V6MeshIdx].reset ();
(*GetAddresses ())[eNTCP2V6MeshIdx].reset ();
}
}
@@ -948,12 +945,12 @@ namespace data
return nullptr;
}
boost::shared_ptr<RouterInfo::Addresses> RouterInfo::GetAddresses () const
RouterInfo::AddressesPtr RouterInfo::GetAddresses () const
{
#if (BOOST_VERSION >= 105300)
return boost::atomic_load (&m_Addresses);
#else
#ifdef __cpp_lib_atomic_shared_ptr
return m_Addresses;
#else
return boost::atomic_load (&m_Addresses);
#endif
}
@@ -961,10 +958,10 @@ namespace data
std::shared_ptr<const RouterInfo::Address> RouterInfo::GetAddress (Filter filter) const
{
// TODO: make it more generic using comparator
#if (BOOST_VERSION >= 105300)
#ifdef __cpp_lib_atomic_shared_ptr
AddressesPtr addresses = m_Addresses;
#else
auto addresses = boost::atomic_load (&m_Addresses);
#else
auto addresses = m_Addresses;
#endif
for (const auto& address : *addresses)
if (address && filter (address)) return address;
@@ -1061,7 +1058,7 @@ namespace data
void RouterInfo::SetUnreachableAddressesTransportCaps (uint8_t transports)
{
for (auto& addr: *m_Addresses)
for (auto& addr: *GetAddresses ())
{
if (addr && !addr->published)
{
@@ -1075,7 +1072,7 @@ namespace data
{
m_SupportedTransports = 0;
m_ReachableTransports = 0;
for (const auto& addr: *m_Addresses)
for (const auto& addr: *GetAddresses ())
{
if (!addr) continue;
uint8_t transports = 0;
@@ -1127,6 +1124,7 @@ namespace data
void RouterInfo::UpdateBuffer (const uint8_t * buf, size_t len)
{
m_IsBufferScheduledToDelete = false;
if (!m_Buffer)
m_Buffer = NewBuffer ();
if (len > m_Buffer->size ()) len = m_Buffer->size ();
@@ -1150,7 +1148,7 @@ namespace data
return netdb.NewRouterInfoAddress ();
}
boost::shared_ptr<RouterInfo::Addresses> RouterInfo::NewAddresses () const
RouterInfo::AddressesPtr RouterInfo::NewAddresses () const
{
return netdb.NewRouterInfoAddresses ();
}
@@ -1185,6 +1183,19 @@ namespace data
return false;
}
}
std::string RouterInfo::GetTransportName (SupportedTransports tr)
{
switch (tr)
{
case eNTCP2V4: return "NTCP2V4";
case eNTCP2V6: return "NTCP2V6";
case eSSU2V4: return "SSU2V4";
case eSSU2V6: return "SSU2V6";
case eNTCP2V6Mesh: return "Mesh";
default: return "";
}
}
void LocalRouterInfo::CreateBuffer (const PrivateKeys& privateKeys)
{
@@ -1223,7 +1234,7 @@ namespace data
CAPS_FLAG_EXTRA_BANDWIDTH2 : // 'X'
CAPS_FLAG_EXTRA_BANDWIDTH1; // 'P'
else
caps += CAPS_FLAG_HIGH_BANDWIDTH2; // 'O'
caps += CAPS_FLAG_HIGH_BANDWIDTH; // 'O'
caps += CAPS_FLAG_FLOODFILL; // floodfill
}
else
@@ -1231,7 +1242,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_BANDWIDTH2 /* 'O' */: CAPS_FLAG_LOW_BANDWIDTH2 /* 'L' */; // bandwidth
caps += (c & eHighBandwidth) ? CAPS_FLAG_HIGH_BANDWIDTH /* 'O' */: CAPS_FLAG_LOW_BANDWIDTH2 /* 'L' */; // bandwidth
}
if (c & eHidden) caps += CAPS_FLAG_HIDDEN; // hidden
if (c & eReachable) caps += CAPS_FLAG_REACHABLE; // reachable
@@ -1370,9 +1381,9 @@ namespace data
if (!introducer.iTag) continue;
if (introducer.iExp) // expiration is specified
{
WriteString ("iexp" + boost::lexical_cast<std::string>(i), properties);
WriteString ("iexp" + std::to_string(i), properties);
properties << '=';
WriteString (boost::lexical_cast<std::string>(introducer.iExp), properties);
WriteString (std::to_string(introducer.iExp), properties);
properties << ';';
}
i++;
@@ -1381,7 +1392,7 @@ namespace data
for (const auto& introducer: address.ssu->introducers)
{
if (!introducer.iTag) continue;
WriteString ("ih" + boost::lexical_cast<std::string>(i), properties);
WriteString ("ih" + std::to_string(i), properties);
properties << '=';
char value[64];
size_t l = ByteStreamToBase64 (introducer.iH, 32, value, 64);
@@ -1394,9 +1405,9 @@ namespace data
for (const auto& introducer: address.ssu->introducers)
{
if (!introducer.iTag) continue;
WriteString ("itag" + boost::lexical_cast<std::string>(i), properties);
WriteString ("itag" + std::to_string(i), properties);
properties << '=';
WriteString (boost::lexical_cast<std::string>(introducer.iTag), properties);
WriteString (std::to_string(introducer.iTag), properties);
properties << ';';
i++;
}
@@ -1410,7 +1421,7 @@ namespace data
{
WriteString ("mtu", properties);
properties << '=';
WriteString (boost::lexical_cast<std::string>(address.ssu->mtu), properties);
WriteString (std::to_string(address.ssu->mtu), properties);
properties << ';';
}
}
@@ -1418,7 +1429,7 @@ namespace data
{
WriteString ("port", properties);
properties << '=';
WriteString (boost::lexical_cast<std::string>(address.port), properties);
WriteString (std::to_string(address.port), properties);
properties << ';';
}
if (address.IsNTCP2 () || address.IsSSU2 ())
@@ -1453,9 +1464,11 @@ namespace data
s.write (properties.str ().c_str (), properties.str ().size ());
}
void LocalRouterInfo::SetProperty (const std::string& key, const std::string& value)
void LocalRouterInfo::SetProperty (std::string_view key, std::string_view value)
{
m_Properties[key] = value;
auto [it, inserted] = m_Properties.emplace (key, value);
if (!inserted)
it->second = value;
}
void LocalRouterInfo::DeleteProperty (const std::string& key)
@@ -1502,9 +1515,9 @@ namespace data
return std::make_shared<Address> ();
}
boost::shared_ptr<RouterInfo::Addresses> LocalRouterInfo::NewAddresses () const
RouterInfo::AddressesPtr LocalRouterInfo::NewAddresses () const
{
return boost::make_shared<Addresses> ();
return RouterInfo::AddressesPtr(new RouterInfo::Addresses ());
}
std::shared_ptr<IdentityEx> LocalRouterInfo::NewIdentity (const uint8_t * buf, size_t len) const
@@ -1546,5 +1559,23 @@ namespace data
}
return false;
}
bool LocalRouterInfo::UpdateSSU2Introducer (const IdentHash& h, bool v4, uint32_t iTag, uint32_t iExp)
{
auto addresses = GetAddresses ();
if (!addresses) return false;
auto addr = (*addresses)[v4 ? eSSU2V4Idx : eSSU2V6Idx];
if (addr)
{
for (auto& it: addr->ssu->introducers)
if (h == it.iH)
{
it.iTag = iTag;
it.iExp = iExp;
return true;
}
}
return false;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2024, The PurpleI2P Project
* Copyright (c) 2013-2025, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -11,12 +11,17 @@
#include <inttypes.h>
#include <string>
#include <string_view>
#include <tuple>
#include <map>
#include <vector>
#include <array>
#include <iostream>
#include <memory>
#include <boost/asio.hpp>
#ifndef __cpp_lib_atomic_shared_ptr
#include <boost/shared_ptr.hpp>
#endif
#include "Identity.h"
#include "Profiling.h"
#include "Family.h"
@@ -40,8 +45,8 @@ namespace data
const char CAPS_FLAG_LOW_BANDWIDTH1 = 'K'; /* < 12 KBps */
const char CAPS_FLAG_LOW_BANDWIDTH2 = 'L'; /* 12-48 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_LOW_BANDWIDTH4 = 'N'; /* 64-128 KBps */
const char CAPS_FLAG_HIGH_BANDWIDTH = '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
@@ -199,7 +204,11 @@ namespace data
};
typedef std::array<std::shared_ptr<Address>, eNumTransports> Addresses;
#ifdef __cpp_lib_atomic_shared_ptr
typedef std::shared_ptr<Addresses> AddressesPtr;
#else
typedef boost::shared_ptr<Addresses> AddressesPtr;
#endif
RouterInfo (const std::string& fullPath);
RouterInfo (const RouterInfo& ) = default;
RouterInfo& operator=(const RouterInfo& ) = default;
@@ -212,9 +221,9 @@ namespace data
std::string GetIdentHashBase64 () const { return GetIdentHash ().ToBase64 (); };
uint64_t GetTimestamp () const { return m_Timestamp; };
int GetVersion () const { return m_Version; };
virtual void SetProperty (const std::string& key, const std::string& value) {};
virtual void SetProperty (std::string_view key, std::string_view value) {};
virtual void ClearProperties () {};
boost::shared_ptr<Addresses> GetAddresses () const; // should be called for local RI only, otherwise must return shared_ptr
AddressesPtr GetAddresses () const; // should be called for local RI only, otherwise must return shared_ptr
std::shared_ptr<const Address> GetNTCP2V4Address () const;
std::shared_ptr<const Address> GetNTCP2V6Address () const;
std::shared_ptr<const Address> GetPublishedNTCP2V4Address () const;
@@ -283,9 +292,12 @@ 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_Buffer ? m_Buffer->GetBufferLen () : 0; };
void DeleteBuffer () { m_Buffer = nullptr; };
void DeleteBuffer () { m_Buffer = nullptr; m_IsBufferScheduledToDelete = false; };
std::shared_ptr<Buffer> GetSharedBuffer () const { return m_Buffer; };
std::shared_ptr<Buffer> CopyBuffer () const;
void ScheduleBufferToDelete () { m_IsBufferScheduledToDelete = true; };
void CancelBufferToDelete () { m_IsBufferScheduledToDelete = false; };
bool IsBufferScheduledToDelete () const { return m_IsBufferScheduledToDelete; };
bool IsUpdated () const { return m_IsUpdated; };
void SetUpdated (bool updated) { m_IsUpdated = updated; };
@@ -323,17 +335,18 @@ namespace data
bool LoadFile (const std::string& fullPath);
void ReadFromFile (const std::string& fullPath);
void ReadFromStream (std::istream& s);
bool ReadFromBuffer (const uint8_t * buf, size_t len); // return false if malformed
void ReadFromBuffer (bool verifySignature);
size_t ReadString (char* str, size_t len, std::istream& s) const;
void ExtractCaps (const char * value);
uint8_t ExtractAddressCaps (const char * value) const;
std::string_view ExtractString (const uint8_t * buf, size_t len) const;
std::tuple<std::string_view, std::string_view, size_t> ExtractParam (const uint8_t * buf, size_t len) const;
void ExtractCaps (std::string_view value);
uint8_t ExtractAddressCaps (std::string_view value) const;
void UpdateIntroducers (std::shared_ptr<Address> address, uint64_t ts);
template<typename Filter>
std::shared_ptr<const Address> GetAddress (Filter filter) const;
virtual std::shared_ptr<Buffer> NewBuffer () const;
virtual std::shared_ptr<Address> NewAddress () const;
virtual boost::shared_ptr<Addresses> NewAddresses () const;
virtual AddressesPtr NewAddresses () const;
virtual std::shared_ptr<IdentityEx> NewIdentity (const uint8_t * buf, size_t len) const;
private:
@@ -342,14 +355,22 @@ namespace data
std::shared_ptr<const IdentityEx> m_RouterIdentity;
std::shared_ptr<Buffer> m_Buffer;
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, m_IsFloodfill;
#ifdef __cpp_lib_atomic_shared_ptr
std::atomic<AddressesPtr> m_Addresses;
#else
AddressesPtr m_Addresses;
#endif
bool m_IsUpdated, m_IsUnreachable, m_IsFloodfill, m_IsBufferScheduledToDelete;
CompatibleTransports m_SupportedTransports, m_ReachableTransports, m_PublishedTransports;
uint8_t m_Caps;
char m_BandwidthCap;
int m_Version;
Congestion m_Congestion;
mutable std::shared_ptr<RouterProfile> m_Profile;
public:
static std::string GetTransportName (SupportedTransports tr);
};
class LocalRouterInfo: public RouterInfo
@@ -361,7 +382,7 @@ namespace data
void UpdateCaps (uint8_t caps);
bool UpdateCongestion (Congestion c); // returns true if updated
void SetProperty (const std::string& key, const std::string& value) override;
void SetProperty (std::string_view key, std::string_view value) override;
void DeleteProperty (const std::string& key);
std::string GetProperty (const std::string& key) const;
void ClearProperties () override { m_Properties.clear (); };
@@ -369,6 +390,7 @@ namespace data
bool AddSSU2Introducer (const Introducer& introducer, bool v4);
bool RemoveSSU2Introducer (const IdentHash& h, bool v4);
bool UpdateSSU2Introducer (const IdentHash& h, bool v4, uint32_t iTag, uint32_t iExp);
private:
@@ -377,7 +399,7 @@ namespace data
void WriteString (const std::string& str, std::ostream& s) const;
std::shared_ptr<Buffer> NewBuffer () const override;
std::shared_ptr<Address> NewAddress () const override;
boost::shared_ptr<Addresses> NewAddresses () const override;
RouterInfo::AddressesPtr NewAddresses () const override;
std::shared_ptr<IdentityEx> NewIdentity (const uint8_t * buf, size_t len) const override;
private:

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022-2024, The PurpleI2P Project
* Copyright (c) 2022-2025, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -81,7 +81,7 @@ namespace transport
found = true;
LogPrint (eLogDebug, "SSU2: Opening IPv4 socket at Start");
OpenSocket (boost::asio::ip::udp::endpoint (m_AddressV4, port));
m_ReceiveService.GetService ().post(
boost::asio::post (m_ReceiveService.GetService (),
[this]()
{
Receive (m_SocketV4);
@@ -93,8 +93,8 @@ namespace transport
found = true;
LogPrint (eLogDebug, "SSU2: Opening IPv6 socket at Start");
OpenSocket (boost::asio::ip::udp::endpoint (m_AddressV6, port));
m_ReceiveService.GetService ().post(
[this]()
boost::asio::post (m_ReceiveService.GetService (),
[this]()
{
Receive (m_SocketV6);
});
@@ -152,8 +152,14 @@ namespace transport
m_SessionsByRouterHash.clear ();
m_PendingOutgoingSessions.clear ();
m_Relays.clear ();
m_PeerTests.clear ();
m_Introducers.clear ();
m_IntroducersV6.clear ();
m_ConnectedRecently.clear ();
m_RequestedPeerTests.clear ();
m_PacketsPool.ReleaseMt (m_ReceivedPacketsQueue);
m_ReceivedPacketsQueue.clear ();
}
void SSU2Server::SetLocalAddress (const boost::asio::ip::address& localAddress)
@@ -210,6 +216,31 @@ namespace transport
return ep.port ();
}
bool SSU2Server::IsConnectedRecently (const boost::asio::ip::udp::endpoint& ep, bool max)
{
if (!ep.port () || ep.address ().is_unspecified ()) return false;
std::lock_guard<std::mutex> l(m_ConnectedRecentlyMutex);
auto it = m_ConnectedRecently.find (ep);
if (it != m_ConnectedRecently.end ())
{
if (i2p::util::GetSecondsSinceEpoch () <= it->second + (max ? SSU2_MAX_HOLE_PUNCH_EXPIRATION : SSU2_MIN_HOLE_PUNCH_EXPIRATION))
return true;
else if (max)
m_ConnectedRecently.erase (it);
}
return false;
}
void SSU2Server::AddConnectedRecently (const boost::asio::ip::udp::endpoint& ep, uint64_t ts)
{
if (!ep.port () || ep.address ().is_unspecified () ||
i2p::util::GetSecondsSinceEpoch () > ts + SSU2_MAX_HOLE_PUNCH_EXPIRATION) return;
std::lock_guard<std::mutex> l(m_ConnectedRecentlyMutex);
auto [it, added] = m_ConnectedRecently.try_emplace (ep, ts);
if (!added && ts > it->second)
it->second = ts; // renew timestamp of existing endpoint
}
void SSU2Server::AdjustTimeOffset (int64_t offset, std::shared_ptr<const i2p::data::IdentityEx> from)
{
if (offset)
@@ -338,21 +369,24 @@ namespace transport
return;
}
packet->len = bytes_transferred;
boost::system::error_code ec;
size_t moreBytes = socket.available (ec);
if (!ec && moreBytes)
{
std::vector<Packet *> packets;
std::list<Packet *> packets;
packets.push_back (packet);
while (moreBytes && packets.size () < 32)
{
while (moreBytes && packets.size () < SSU2_MAX_NUM_PACKETS_PER_BATCH)
{
packet = m_PacketsPool.AcquireMt ();
packet->len = socket.receive_from (boost::asio::buffer (packet->buf, SSU2_MAX_PACKET_SIZE), packet->from, 0, ec);
if (!ec)
{
i2p::transport::transports.UpdateReceivedBytes (packet->len);
packets.push_back (packet);
if (packet->len >= SSU2_MIN_RECEIVED_PACKET_SIZE)
packets.push_back (packet);
else // drop too short packets
m_PacketsPool.ReleaseMt (packet);
moreBytes = socket.available(ec);
if (ec) break;
}
@@ -363,10 +397,10 @@ namespace transport
break;
}
}
GetService ().post (std::bind (&SSU2Server::HandleReceivedPackets, this, packets));
InsertToReceivedPacketsQueue (packets);
}
else
GetService ().post (std::bind (&SSU2Server::HandleReceivedPacket, this, packet));
InsertToReceivedPacketsQueue (packet);
Receive (socket);
}
else
@@ -393,40 +427,75 @@ namespace transport
}
}
void SSU2Server::HandleReceivedPacket (Packet * packet)
{
if (packet)
{
if (m_IsThroughProxy)
ProcessNextPacketFromProxy (packet->buf, packet->len);
else
ProcessNextPacket (packet->buf, packet->len, packet->from);
m_PacketsPool.ReleaseMt (packet);
if (m_LastSession && m_LastSession->GetState () != eSSU2SessionStateTerminated)
m_LastSession->FlushData ();
}
}
void SSU2Server::HandleReceivedPackets (std::vector<Packet *> packets)
void SSU2Server::HandleReceivedPackets (std::list<Packet *>&& packets)
{
if (packets.empty ()) return;
if (m_IsThroughProxy)
for (auto& packet: packets)
ProcessNextPacketFromProxy (packet->buf, packet->len);
for (auto it: packets)
ProcessNextPacketFromProxy (it->buf, it->len);
else
for (auto& packet: packets)
ProcessNextPacket (packet->buf, packet->len, packet->from);
for (auto it: packets)
ProcessNextPacket (it->buf, it->len, it->from);
m_PacketsPool.ReleaseMt (packets);
if (m_LastSession && m_LastSession->GetState () != eSSU2SessionStateTerminated)
m_LastSession->FlushData ();
}
void SSU2Server::AddSession (std::shared_ptr<SSU2Session> session)
void SSU2Server::InsertToReceivedPacketsQueue (Packet * packet)
{
if (!packet) return;
bool empty = false;
{
std::lock_guard<std::mutex> l(m_ReceivedPacketsQueueMutex);
empty = m_ReceivedPacketsQueue.empty ();
m_ReceivedPacketsQueue.push_back (packet);
}
if (empty)
boost::asio::post (GetService (), [this]() { HandleReceivedPacketsQueue (); });
}
void SSU2Server::InsertToReceivedPacketsQueue (std::list<Packet *>& packets)
{
if (packets.empty ()) return;
size_t queueSize = 0;
{
std::lock_guard<std::mutex> l(m_ReceivedPacketsQueueMutex);
queueSize = m_ReceivedPacketsQueue.size ();
if (queueSize < SSU2_MAX_RECEIVED_QUEUE_SIZE)
m_ReceivedPacketsQueue.splice (m_ReceivedPacketsQueue.end (), packets);
else
{
LogPrint (eLogError, "SSU2: Received queue size ", queueSize, " exceeds max size", SSU2_MAX_RECEIVED_QUEUE_SIZE);
m_PacketsPool.ReleaseMt (packets);
queueSize = 0; // invoke processing just in case
}
}
if (!queueSize)
boost::asio::post (GetService (), [this]() { HandleReceivedPacketsQueue (); });
}
void SSU2Server::HandleReceivedPacketsQueue ()
{
std::list<Packet *> receivedPackets;
{
std::lock_guard<std::mutex> l(m_ReceivedPacketsQueueMutex);
m_ReceivedPacketsQueue.swap (receivedPackets);
}
HandleReceivedPackets (std::move (receivedPackets));
}
bool SSU2Server::AddSession (std::shared_ptr<SSU2Session> session)
{
if (session)
{
m_Sessions.emplace (session->GetConnID (), session);
AddSessionByRouterHash (session);
if (m_Sessions.emplace (session->GetConnID (), session).second)
{
if (session->GetState () != eSSU2SessionStatePeerTest)
AddSessionByRouterHash (session);
return true;
}
}
return false;
}
void SSU2Server::RemoveSession (uint64_t connID)
@@ -434,19 +503,28 @@ namespace transport
auto it = m_Sessions.find (connID);
if (it != m_Sessions.end ())
{
auto ident = it->second->GetRemoteIdentity ();
if (ident)
{
auto it1 = m_SessionsByRouterHash.find (ident->GetIdentHash ());
if (it1 != m_SessionsByRouterHash.end () && it->second == it1->second)
m_SessionsByRouterHash.erase (it1);
}
if (it->second->GetState () != eSSU2SessionStatePeerTest)
{
auto ident = it->second->GetRemoteIdentity ();
if (ident)
{
std::lock_guard<std::mutex> l(m_SessionsByRouterHashMutex);
auto it1 = m_SessionsByRouterHash.find (ident->GetIdentHash ());
if (it1 != m_SessionsByRouterHash.end () && it->second == it1->second.lock ())
m_SessionsByRouterHash.erase (it1);
}
}
if (m_LastSession == it->second)
m_LastSession = nullptr;
m_Sessions.erase (it);
}
}
void SSU2Server::RequestRemoveSession (uint64_t connID)
{
boost::asio::post (GetService (), [connID, this]() { RemoveSession (connID); });
}
void SSU2Server::AddSessionByRouterHash (std::shared_ptr<SSU2Session> session)
{
if (session)
@@ -454,18 +532,26 @@ namespace transport
auto ident = session->GetRemoteIdentity ();
if (ident)
{
auto ret = m_SessionsByRouterHash.emplace (ident->GetIdentHash (), session);
if (!ret.second && ret.first->second != session)
std::shared_ptr<SSU2Session> oldSession;
{
std::lock_guard<std::mutex> l(m_SessionsByRouterHashMutex);
auto ret = m_SessionsByRouterHash.emplace (ident->GetIdentHash (), session);
if (!ret.second)
{
oldSession = ret.first->second.lock ();
// update session
ret.first->second = session;
}
}
if (oldSession && oldSession != 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);
oldSession->MoveSendQueue (session);
// terminate existing
GetService ().post (std::bind (&SSU2Session::RequestTermination, ret.first->second, eSSU2TerminationReasonReplacedByNewSession));
// update session
ret.first->second = session;
}
boost::asio::post (GetService (), std::bind (&SSU2Session::RequestTermination, oldSession, eSSU2TerminationReasonReplacedByNewSession));
}
}
}
}
@@ -473,21 +559,30 @@ namespace transport
bool SSU2Server::AddPendingOutgoingSession (std::shared_ptr<SSU2Session> session)
{
if (!session) return false;
std::unique_lock<std::mutex> l(m_PendingOutgoingSessionsMutex);
std::lock_guard<std::mutex> l(m_PendingOutgoingSessionsMutex);
return m_PendingOutgoingSessions.emplace (session->GetRemoteEndpoint (), session).second;
}
std::shared_ptr<SSU2Session> SSU2Server::FindSession (const i2p::data::IdentHash& ident) const
std::shared_ptr<SSU2Session> SSU2Server::FindSession (const i2p::data::IdentHash& ident)
{
std::lock_guard<std::mutex> l(m_SessionsByRouterHashMutex);
auto it = m_SessionsByRouterHash.find (ident);
if (it != m_SessionsByRouterHash.end ())
return it->second;
{
if (!it->second.expired ())
{
auto s = it->second.lock ();
if (s && s->GetState () != eSSU2SessionStateTerminated)
return s;
}
m_SessionsByRouterHash.erase (it);
}
return nullptr;
}
std::shared_ptr<SSU2Session> SSU2Server::FindPendingOutgoingSession (const boost::asio::ip::udp::endpoint& ep) const
{
std::unique_lock<std::mutex> l(m_PendingOutgoingSessionsMutex);
std::lock_guard<std::mutex> l(m_PendingOutgoingSessionsMutex);
auto it = m_PendingOutgoingSessions.find (ep);
if (it != m_PendingOutgoingSessions.end ())
return it->second;
@@ -496,22 +591,20 @@ namespace transport
void SSU2Server::RemovePendingOutgoingSession (const boost::asio::ip::udp::endpoint& ep)
{
std::unique_lock<std::mutex> l(m_PendingOutgoingSessionsMutex);
std::lock_guard<std::mutex> l(m_PendingOutgoingSessionsMutex);
m_PendingOutgoingSessions.erase (ep);
}
std::shared_ptr<SSU2Session> SSU2Server::GetRandomPeerTestSession (
i2p::data::RouterInfo::CompatibleTransports remoteTransports, const i2p::data::IdentHash& excluded) const
i2p::data::RouterInfo::CompatibleTransports remoteTransports, const i2p::data::IdentHash& excluded)
{
if (m_Sessions.empty ()) return nullptr;
uint16_t ind;
RAND_bytes ((uint8_t *)&ind, sizeof (ind));
ind %= m_Sessions.size ();
int ind = m_Rng () % m_Sessions.size ();
auto it = m_Sessions.begin ();
std::advance (it, ind);
while (it != m_Sessions.end ())
{
if ((it->second->GetRemotePeerTestTransports () & remoteTransports) &&
if (it->second->IsEstablished () && (it->second->GetRemotePeerTestTransports () & remoteTransports) &&
it->second->GetRemoteIdentity ()->GetIdentHash () != excluded)
return it->second;
it++;
@@ -520,7 +613,7 @@ namespace transport
it = m_Sessions.begin ();
while (it != m_Sessions.end () && ind)
{
if ((it->second->GetRemotePeerTestTransports () & remoteTransports) &&
if (it->second->IsEstablished () && (it->second->GetRemotePeerTestTransports () & remoteTransports) &&
it->second->GetRemoteIdentity ()->GetIdentHash () != excluded)
return it->second;
it++; ind--;
@@ -543,14 +636,51 @@ namespace transport
auto it = m_Relays.find (tag);
if (it != m_Relays.end ())
{
if (it->second->IsEstablished ())
return it->second;
else
m_Relays.erase (it);
if (!it->second.expired ())
{
auto s = it->second.lock ();
if (s && s->IsEstablished ())
return s;
}
m_Relays.erase (it);
}
return nullptr;
}
bool SSU2Server::AddPeerTest (uint32_t nonce, std::shared_ptr<SSU2Session> aliceSession, uint64_t ts)
{
return m_PeerTests.emplace (nonce, std::pair{ aliceSession, ts }).second;
}
std::shared_ptr<SSU2Session> SSU2Server::GetPeerTest (uint32_t nonce)
{
auto it = m_PeerTests.find (nonce);
if (it != m_PeerTests.end ())
{
auto s = it->second.first.lock ();
m_PeerTests.erase (it);
return s;
}
return nullptr;
}
bool SSU2Server::AddRequestedPeerTest (uint32_t nonce, std::shared_ptr<SSU2PeerTestSession> session, uint64_t ts)
{
return m_RequestedPeerTests.emplace (nonce, std::pair{ session, ts }).second;
}
std::shared_ptr<SSU2PeerTestSession> SSU2Server::GetRequestedPeerTest (uint32_t nonce)
{
auto it = m_RequestedPeerTests.find (nonce);
if (it != m_RequestedPeerTests.end ())
{
auto s = it->second.first.lock ();
m_RequestedPeerTests.erase (it);
return s;
}
return nullptr;
}
void SSU2Server::ProcessNextPacket (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint)
{
if (len < 24) return;
@@ -598,6 +728,9 @@ namespace transport
m_LastSession->SetRemoteEndpoint (senderEndpoint);
m_LastSession->ProcessPeerTest (buf, len);
break;
case eSSU2SessionStateHolePunch:
m_LastSession->ProcessFirstIncomingMessage (connID, buf, len); // SessionRequest
break;
case eSSU2SessionStateClosing:
m_LastSession->ProcessData (buf, len, senderEndpoint); // we might receive termintaion block
if (m_LastSession && m_LastSession->GetState () == eSSU2SessionStateClosing)
@@ -620,7 +753,7 @@ namespace transport
if (it1->second->GetState () == eSSU2SessionStateSessionRequestSent &&
it1->second->ProcessSessionCreated (buf, len))
{
std::unique_lock<std::mutex> l(m_PendingOutgoingSessionsMutex);
std::lock_guard<std::mutex> l(m_PendingOutgoingSessionsMutex);
m_PendingOutgoingSessions.erase (it1); // we are done with that endpoint
}
else
@@ -711,21 +844,41 @@ namespace transport
}
}
bool SSU2Server::CheckPendingOutgoingSession (const boost::asio::ip::udp::endpoint& ep, bool peerTest)
{
auto s = FindPendingOutgoingSession (ep);
if (s)
{
if (peerTest)
{
// if peer test requested add it to the list for pending session
auto onEstablished = s->GetOnEstablished ();
if (onEstablished)
s->SetOnEstablished ([s, onEstablished]()
{
onEstablished ();
s->SendPeerTest ();
});
else
s->SetOnEstablished ([s]() { s->SendPeerTest (); });
}
return true;
}
return false;
}
bool SSU2Server::CreateSession (std::shared_ptr<const i2p::data::RouterInfo> router,
std::shared_ptr<const i2p::data::RouterInfo::Address> address, bool peerTest)
{
if (router && address)
{
// check if no session
auto it = m_SessionsByRouterHash.find (router->GetIdentHash ());
if (it != m_SessionsByRouterHash.end ())
auto existingSession = FindSession (router->GetIdentHash ());
if (existingSession)
{
// session with router found, trying to send peer test if requested
if (peerTest && it->second->IsEstablished ())
{
auto session = it->second;
GetService ().post ([session]() { session->SendPeerTest (); });
}
if (peerTest && existingSession->IsEstablished ())
boost::asio::post (GetService (), [existingSession]() { existingSession->SendPeerTest (); });
return false;
}
// check is no pending session
@@ -733,34 +886,28 @@ namespace transport
if (isValidEndpoint)
{
if (i2p::transport::transports.IsInReservedRange(address->host)) return false;
auto s = FindPendingOutgoingSession (boost::asio::ip::udp::endpoint (address->host, address->port));
if (s)
{
if (peerTest)
{
// if peer test requested add it to the list for pending session
auto onEstablished = s->GetOnEstablished ();
if (onEstablished)
s->SetOnEstablished ([s, onEstablished]()
{
onEstablished ();
s->SendPeerTest ();
});
else
s->SetOnEstablished ([s]() { s->SendPeerTest (); });
}
return false;
}
if (CheckPendingOutgoingSession (boost::asio::ip::udp::endpoint (address->host, address->port), peerTest)) return false;
}
auto session = std::make_shared<SSU2Session> (*this, router, address);
if (!isValidEndpoint && router->HasProfile () && router->GetProfile ()->HasLastEndpoint (address->IsV4 ()))
{
// router doesn't publish endpoint, but we connected before and hole punch might be alive
auto ep = router->GetProfile ()->GetLastEndpoint ();
if (IsConnectedRecently (ep, false))
{
if (CheckPendingOutgoingSession (ep, peerTest)) return false;
session->SetRemoteEndpoint (ep);
isValidEndpoint = true;
}
}
if (peerTest)
session->SetOnEstablished ([session]() {session->SendPeerTest (); });
if (address->UsesIntroducer ())
GetService ().post (std::bind (&SSU2Server::ConnectThroughIntroducer, this, session));
else if (isValidEndpoint) // we can't connect without endpoint
GetService ().post ([session]() { session->Connect (); });
if (isValidEndpoint) // we know endpoint
boost::asio::post (GetService (), [session]() { session->Connect (); });
else if (address->UsesIntroducer ()) // we don't know endpoint yet
boost::asio::post (GetService (), std::bind (&SSU2Server::ConnectThroughIntroducer, this, session));
else
return false;
}
@@ -782,15 +929,15 @@ namespace transport
{
if (it.iTag && ts < it.iExp)
{
auto it1 = m_SessionsByRouterHash.find (it.iH);
if (it1 != m_SessionsByRouterHash.end ())
auto s = FindSession (it.iH);
if (s)
{
auto addr = it1->second->GetAddress ();
auto addr = s->GetAddress ();
if (addr && addr->IsIntroducer ())
{
it1->second->Introduce (session, it.iTag);
s->Introduce (session, it.iTag);
return;
}
}
}
else
indices.push_back(i);
@@ -896,17 +1043,16 @@ namespace transport
if (!router) return false;
auto addr = v4 ? router->GetSSU2V4Address () : router->GetSSU2V6Address ();
if (!addr) return false;
auto it = m_SessionsByRouterHash.find (router->GetIdentHash ());
if (it != m_SessionsByRouterHash.end ())
auto session = FindSession (router->GetIdentHash ());
if (session)
{
auto remoteAddr = it->second->GetAddress ();
auto remoteAddr = session->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 (); });
(v4 && !remoteAddr->IsV4 ()) || (!v4 && !remoteAddr->IsV6 ())) return false;
if (session->IsEstablished ())
boost::asio::post (GetService (), [session]() { session->SendPeerTest (); });
else
s->SetOnEstablished ([s]() { s->SendPeerTest (); });
session->SetOnEstablished ([session]() { session->SendPeerTest (); });
return true;
}
else
@@ -916,7 +1062,8 @@ namespace transport
void SSU2Server::ScheduleTermination ()
{
m_TerminationTimer.expires_from_now (boost::posix_time::seconds(SSU2_TERMINATION_CHECK_TIMEOUT));
m_TerminationTimer.expires_from_now (boost::posix_time::seconds(
SSU2_TERMINATION_CHECK_TIMEOUT + m_Rng () % SSU2_TERMINATION_CHECK_TIMEOUT_VARIANCE));
m_TerminationTimer.async_wait (std::bind (&SSU2Server::HandleTerminationTimer,
this, std::placeholders::_1));
}
@@ -926,17 +1073,20 @@ namespace transport
if (ecode != boost::asio::error::operation_aborted)
{
auto ts = i2p::util::GetSecondsSinceEpoch ();
for (auto it = m_PendingOutgoingSessions.begin (); it != m_PendingOutgoingSessions.end ();)
{
if (it->second->IsTerminationTimeoutExpired (ts))
std::lock_guard<std::mutex> l(m_PendingOutgoingSessionsMutex);
for (auto it = m_PendingOutgoingSessions.begin (); it != m_PendingOutgoingSessions.end ();)
{
//it->second->Terminate ();
std::unique_lock<std::mutex> l(m_PendingOutgoingSessionsMutex);
it = m_PendingOutgoingSessions.erase (it);
if (it->second->IsTerminationTimeoutExpired (ts))
{
//it->second->Terminate ();
it = m_PendingOutgoingSessions.erase (it);
}
else
it++;
}
else
it++;
}
}
for (auto it: m_Sessions)
{
@@ -954,14 +1104,6 @@ namespace transport
it.second->CleanUp (ts);
}
for (auto it = m_SessionsByRouterHash.begin (); it != m_SessionsByRouterHash.begin ();)
{
if (it->second && it->second->GetState () == eSSU2SessionStateTerminated)
it = m_SessionsByRouterHash.erase (it);
else
it++;
}
ScheduleTermination ();
}
}
@@ -980,12 +1122,23 @@ namespace transport
auto ts = i2p::util::GetSecondsSinceEpoch ();
for (auto it = m_Relays.begin (); it != m_Relays.begin ();)
{
if (it->second && it->second->GetState () == eSSU2SessionStateTerminated)
if (it->second.expired ())
it = m_Relays.erase (it);
else
it++;
}
for (auto it = m_PeerTests.begin (); it != m_PeerTests.end ();)
{
if (ts > it->second.second + SSU2_PEER_TEST_EXPIRATION_TIMEOUT || it->second.first.expired ())
{
LogPrint (eLogInfo, "SSU2: Peer test nonce ", it->first, " was not responded in ", SSU2_PEER_TEST_EXPIRATION_TIMEOUT, " seconds or session invalid. Deleted");
it = m_PeerTests.erase (it);
}
else
it++;
}
for (auto it = m_IncomingTokens.begin (); it != m_IncomingTokens.end (); )
{
if (ts > it->second.second)
@@ -1002,6 +1155,33 @@ namespace transport
it++;
}
for (auto it = m_ConnectedRecently.begin (); it != m_ConnectedRecently.end (); )
{
if (ts > it->second + SSU2_MAX_HOLE_PUNCH_EXPIRATION)
it = m_ConnectedRecently.erase (it);
else
it++;
}
for (auto it = m_RequestedPeerTests.begin (); it != m_RequestedPeerTests.end ();)
{
if (ts > it->second.second + SSU2_PEER_TEST_EXPIRATION_TIMEOUT)
it = m_RequestedPeerTests.erase (it);
else
it++;
}
{
std::lock_guard<std::mutex> l(m_SessionsByRouterHashMutex);
for (auto it = m_SessionsByRouterHash.begin (); it != m_SessionsByRouterHash.begin ();)
{
if (it->second.expired ())
it = m_SessionsByRouterHash.erase (it);
else
it++;
}
}
m_PacketsPool.CleanUpMt ();
m_SentPacketsPool.CleanUp ();
m_IncompleteMessagesPool.CleanUp ();
@@ -1071,112 +1251,118 @@ namespace transport
}
uint64_t token;
RAND_bytes ((uint8_t *)&token, 8);
m_IncomingTokens.emplace (ep, std::make_pair (token, uint32_t(ts + SSU2_TOKEN_EXPIRATION_TIMEOUT)));
if (!token) token = 1; // token can't be zero
m_IncomingTokens.try_emplace (ep, token, uint32_t(ts + SSU2_TOKEN_EXPIRATION_TIMEOUT));
return token;
}
std::pair<uint64_t, uint32_t> SSU2Server::NewIncomingToken (const boost::asio::ip::udp::endpoint& ep)
{
m_IncomingTokens.erase (ep); // drop previous
uint64_t token;
RAND_bytes ((uint8_t *)&token, 8);
auto ret = std::make_pair (token, uint32_t(i2p::util::GetSecondsSinceEpoch () + SSU2_NEXT_TOKEN_EXPIRATION_TIMEOUT));
m_IncomingTokens.emplace (ep, ret);
return ret;
if (!token) token = 1; // token can't be zero
uint32_t expires = i2p::util::GetSecondsSinceEpoch () + SSU2_NEXT_TOKEN_EXPIRATION_TIMEOUT;
auto [it, inserted] = m_IncomingTokens.try_emplace (ep, token, expires);
if (!inserted)
it->second = { token, expires }; // override
return it->second;
}
std::vector<std::shared_ptr<SSU2Session> > SSU2Server::FindIntroducers (int maxNumIntroducers,
bool v4, const std::unordered_set<i2p::data::IdentHash>& excluded) const
bool v4, const std::unordered_set<i2p::data::IdentHash>& excluded)
{
std::vector<std::shared_ptr<SSU2Session> > ret;
if (maxNumIntroducers <= 0) return ret;
auto newer = [](const std::shared_ptr<SSU2Session>& s1, const std::shared_ptr<SSU2Session>& s2) -> bool
{
auto t1 = s1->GetCreationTime (), t2 = s2->GetCreationTime ();
return (t1 != t2) ? (t1 > t2) : (s1->GetConnID () > s2->GetConnID ());
};
std::set<std::shared_ptr<SSU2Session>, decltype (newer)> introducers(newer);
if (maxNumIntroducers <= 0 || m_Sessions.empty ()) return ret;
std::vector<std::shared_ptr<SSU2Session> > eligible;
eligible.reserve (m_Sessions.size ()/2);
auto ts = i2p::util::GetSecondsSinceEpoch ();
for (const auto& s : m_Sessions)
{
if (s.second->IsEstablished () && (s.second->GetRelayTag () && s.second->IsOutgoing ()) &&
ts < s.second->GetCreationTime () + SSU2_TO_INTRODUCER_SESSION_DURATION/2 &&
!excluded.count (s.second->GetRemoteIdentity ()->GetIdentHash ()) &&
((v4 && (s.second->GetRemoteTransports () & i2p::data::RouterInfo::eSSU2V4)) ||
(!v4 && (s.second->GetRemoteTransports () & i2p::data::RouterInfo::eSSU2V6))))
introducers.insert (s.second);
eligible.push_back (s.second);
}
int i = 0;
for (auto it: introducers)
{
ret.push_back (it);
i++;
if (i >= maxNumIntroducers) break;
}
if (eligible.size () <= (size_t)maxNumIntroducers)
return eligible;
else
std::sample (eligible.begin(), eligible.end(), std::back_inserter(ret), maxNumIntroducers, m_Rng);
return ret;
}
void SSU2Server::UpdateIntroducers (bool v4)
{
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
std::list<i2p::data::IdentHash> newList, impliedList;
std::list<std::pair<i2p::data::IdentHash, uint32_t> > newList, impliedList;
auto& introducers = v4 ? m_Introducers : m_IntroducersV6;
std::unordered_set<i2p::data::IdentHash> excluded;
for (const auto& it : introducers)
for (const auto& [ident, tag] : introducers)
{
std::shared_ptr<SSU2Session> session;
auto it1 = m_SessionsByRouterHash.find (it);
if (it1 != m_SessionsByRouterHash.end ())
{
session = it1->second;
excluded.insert (it);
}
if (session && session->IsEstablished () && session->GetRelayTag () && session->IsOutgoing () && // still session with introducer?
ts < session->GetCreationTime () + SSU2_TO_INTRODUCER_SESSION_EXPIRATION)
std::shared_ptr<SSU2Session> session = FindSession (ident);
if (session)
excluded.insert (ident);
if (session)
{
session->SendKeepAlive ();
if (ts < session->GetCreationTime () + SSU2_TO_INTRODUCER_SESSION_DURATION)
newList.push_back (it);
else
if (session->IsEstablished () && session->GetRelayTag () && session->IsOutgoing () && // still session with introducer?
ts < session->GetCreationTime () + SSU2_TO_INTRODUCER_SESSION_EXPIRATION)
{
impliedList.push_back (it); // keep in introducers list, but not publish
session = nullptr;
}
session->SendKeepAlive ();
if (ts < session->GetCreationTime () + SSU2_TO_INTRODUCER_SESSION_DURATION)
{
newList.push_back ({ident, session->GetRelayTag ()});
if (tag != session->GetRelayTag ())
{
LogPrint (eLogDebug, "SSU2: Introducer session to ", session->GetIdentHashBase64() , " was replaced. iTag ", tag, "->", session->GetRelayTag ());
i2p::context.UpdateSSU2Introducer (ident, v4, session->GetRelayTag (),
session->GetCreationTime () + SSU2_TO_INTRODUCER_SESSION_EXPIRATION);
}
}
else
{
impliedList.push_back ({ident, session->GetRelayTag ()}); // keep in introducers list, but not publish
session = nullptr;
}
}
else
session = nullptr;
}
else
session = nullptr;
if (!session)
i2p::context.RemoveSSU2Introducer (it, v4);
i2p::context.RemoveSSU2Introducer (ident, v4);
}
int numOldSessions = 0;
if (newList.size () < SSU2_MAX_NUM_INTRODUCERS)
{
auto sessions = FindIntroducers (SSU2_MAX_NUM_INTRODUCERS - newList.size (), v4, excluded);
if (sessions.empty () && !introducers.empty ())
if (sessions.empty () && !impliedList.empty ())
{
// bump creation time for previous introducers if no new sessions found
LogPrint (eLogDebug, "SSU2: No new introducers found. Trying to reuse existing");
impliedList.clear ();
for (auto& it : introducers)
for (const auto& it : impliedList)
{
auto it1 = m_SessionsByRouterHash.find (it);
if (it1 != m_SessionsByRouterHash.end ())
auto session = FindSession (it.first);
if (session)
{
auto session = it1->second;
if (session->IsEstablished () && session->GetRelayTag () && session->IsOutgoing ())
{
session->SetCreationTime (session->GetCreationTime () + SSU2_TO_INTRODUCER_SESSION_DURATION);
if (std::find (newList.begin (), newList.end (), it) == newList.end ())
sessions.push_back (session);
}
if (std::find_if (newList.begin (), newList.end (),
[&ident = it.first](const auto& s){ return ident == s.first; }) == newList.end ())
{
sessions.push_back (session);
numOldSessions++;
}
}
}
impliedList.clear ();
}
for (const auto& it : sessions)
{
uint32_t tag = it->GetRelayTag ();
uint32_t tag = it->GetRelayTag ();
uint32_t exp = it->GetCreationTime () + SSU2_TO_INTRODUCER_SESSION_EXPIRATION;
if (!tag || ts + SSU2_TO_INTRODUCER_SESSION_DURATION/2 > exp)
continue; // don't pick too old session for introducer
if (!tag && ts >= exp)
continue; // don't publish expired introducer
i2p::data::RouterInfo::Introducer introducer;
introducer.iTag = tag;
introducer.iH = it->GetRemoteIdentity ()->GetIdentHash ();
@@ -1186,16 +1372,28 @@ namespace transport
{
LogPrint (eLogDebug, "SSU2: Introducer added ", it->GetRelayTag (), " at ",
i2p::data::GetIdentHashAbbreviation (it->GetRemoteIdentity ()->GetIdentHash ()));
newList.push_back (it->GetRemoteIdentity ()->GetIdentHash ());
newList.push_back ({ it->GetRemoteIdentity ()->GetIdentHash (), tag });
it->SendKeepAlive ();
if (newList.size () >= SSU2_MAX_NUM_INTRODUCERS) break;
}
}
}
introducers = newList;
if (introducers.size () < SSU2_MAX_NUM_INTRODUCERS)
if (introducers.size () < SSU2_MAX_NUM_INTRODUCERS || numOldSessions)
{
for (auto i = introducers.size (); i < SSU2_MAX_NUM_INTRODUCERS; i++)
// we need to create more sessions with relay tag
// exclude all existing sessions
excluded.clear ();
{
std::lock_guard<std::mutex> l(m_SessionsByRouterHashMutex);
for (const auto& [ident, s] : m_SessionsByRouterHash)
excluded.insert (ident);
}
// session about to expire are not counted
for (auto i = introducers.size (); i < SSU2_MAX_NUM_INTRODUCERS + numOldSessions; i++)
{
auto introducer = i2p::data::netdb.GetRandomSSU2Introducer (v4, excluded);
if (introducer)
@@ -1321,6 +1519,23 @@ namespace transport
}
}
bool SSU2Server::AEADChaCha20Poly1305Encrypt (const uint8_t * msg, size_t msgLen,
const uint8_t * ad, size_t adLen, const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len)
{
return m_Encryptor.Encrypt (msg, msgLen, ad, adLen, key, nonce, buf, len);
}
bool SSU2Server::AEADChaCha20Poly1305Decrypt (const uint8_t * msg, size_t msgLen,
const uint8_t * ad, size_t adLen, const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len)
{
return m_Decryptor.Decrypt (msg, msgLen, ad, adLen, key, nonce, buf, len);
}
void SSU2Server::ChaCha20 (const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out)
{
m_ChaCha20 (msg, msgLen, key, nonce, out);
}
void SSU2Server::SendThroughProxy (const uint8_t * header, size_t headerLen, const uint8_t * headerX, size_t headerXLen,
const uint8_t * payload, size_t payloadLen, const boost::asio::ip::udp::endpoint& to)
{
@@ -1569,7 +1784,7 @@ namespace transport
bool SSU2Server::SetProxy (const std::string& address, uint16_t port)
{
boost::system::error_code ecode;
auto addr = boost::asio::ip::address::from_string (address, ecode);
auto addr = boost::asio::ip::make_address (address, ecode);
if (!ecode && !addr.is_unspecified () && port)
{
m_IsThroughProxy = true;

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022-2024, The PurpleI2P Project
* Copyright (c) 2022-2025, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -12,17 +12,21 @@
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include <list>
#include <array>
#include <mutex>
#include <random>
#include "util.h"
#include "SSU2Session.h"
#include "SSU2OutOfSession.h"
#include "Socks5.h"
namespace i2p
{
namespace transport
{
const int SSU2_TERMINATION_CHECK_TIMEOUT = 25; // in seconds
const int SSU2_TERMINATION_CHECK_TIMEOUT = 23; // in seconds
const int SSU2_TERMINATION_CHECK_TIMEOUT_VARIANCE = 5; // in seconds
const int SSU2_CLEANUP_INTERVAL = 72; // in seconds
const int SSU2_RESEND_CHECK_TIMEOUT = 40; // in milliseconds
const int SSU2_RESEND_CHECK_TIMEOUT_VARIANCE = 10; // in milliseconds
@@ -33,11 +37,15 @@ namespace transport
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 size_t SSU2_MAX_RECEIVED_QUEUE_SIZE = 2500; // in packets
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
const int SSU2_KEEP_ALIVE_INTERVAL_VARIANCE = 4; // in seconds
const int SSU2_PROXY_CONNECT_RETRY_TIMEOUT = 30; // in seconds
const int SSU2_MIN_HOLE_PUNCH_EXPIRATION = 30; // in seconds
const int SSU2_MAX_HOLE_PUNCH_EXPIRATION = 160; // in seconds
const size_t SSU2_MAX_NUM_PACKETS_PER_BATCH = 64;
class SSU2Server: private i2p::util::RunnableServiceWithWork
{
@@ -47,13 +55,13 @@ namespace transport
size_t len;
boost::asio::ip::udp::endpoint from;
};
class ReceiveService: public i2p::util::RunnableService
{
public:
ReceiveService (const std::string& name): RunnableService (name) {};
boost::asio::io_service& GetService () { return GetIOService (); };
auto& GetService () { return GetIOService (); };
void Start () { StartIOService (); };
void Stop () { StopIOService (); };
};
@@ -65,31 +73,45 @@ namespace transport
void Start ();
void Stop ();
boost::asio::io_service& GetService () { return GetIOService (); };
auto& GetService () { return GetIOService (); };
void SetLocalAddress (const boost::asio::ip::address& localAddress);
bool SetProxy (const std::string& address, uint16_t port);
bool UsesProxy () const { return m_IsThroughProxy; };
bool IsSupported (const boost::asio::ip::address& addr) const;
uint16_t GetPort (bool v4) const;
bool IsConnectedRecently (const boost::asio::ip::udp::endpoint& ep, bool max = true);
void AddConnectedRecently (const boost::asio::ip::udp::endpoint& ep, uint64_t ts);
std::mt19937& GetRng () { return m_Rng; }
bool AEADChaCha20Poly1305Encrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen,
const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len);
bool AEADChaCha20Poly1305Decrypt (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen,
const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len);
void ChaCha20 (const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out);
bool IsMaxNumIntroducers (bool v4) const { return (v4 ? m_Introducers.size () : m_IntroducersV6.size ()) >= SSU2_MAX_NUM_INTRODUCERS; }
bool IsSyncClockFromPeers () const { return m_IsSyncClockFromPeers; };
void AdjustTimeOffset (int64_t offset, std::shared_ptr<const i2p::data::IdentityEx> from);
void AddSession (std::shared_ptr<SSU2Session> session);
bool AddSession (std::shared_ptr<SSU2Session> session);
void RemoveSession (uint64_t connID);
void RequestRemoveSession (uint64_t connID);
void AddSessionByRouterHash (std::shared_ptr<SSU2Session> session);
bool AddPendingOutgoingSession (std::shared_ptr<SSU2Session> session);
void RemovePendingOutgoingSession (const boost::asio::ip::udp::endpoint& ep);
std::shared_ptr<SSU2Session> FindSession (const i2p::data::IdentHash& ident) const;
std::shared_ptr<SSU2Session> FindSession (const i2p::data::IdentHash& ident);
std::shared_ptr<SSU2Session> FindPendingOutgoingSession (const boost::asio::ip::udp::endpoint& ep) const;
std::shared_ptr<SSU2Session> GetRandomPeerTestSession (i2p::data::RouterInfo::CompatibleTransports remoteTransports,
const i2p::data::IdentHash& excluded) const;
const i2p::data::IdentHash& excluded);
void AddRelay (uint32_t tag, std::shared_ptr<SSU2Session> relay);
void RemoveRelay (uint32_t tag);
std::shared_ptr<SSU2Session> FindRelaySession (uint32_t tag);
bool AddPeerTest (uint32_t nonce, std::shared_ptr<SSU2Session> aliceSession, uint64_t ts);
std::shared_ptr<SSU2Session> GetPeerTest (uint32_t nonce);
bool AddRequestedPeerTest (uint32_t nonce, std::shared_ptr<SSU2PeerTestSession> session, uint64_t ts);
std::shared_ptr<SSU2PeerTestSession> GetRequestedPeerTest (uint32_t nonce);
void Send (const uint8_t * header, size_t headerLen, const uint8_t * payload, size_t payloadLen,
const boost::asio::ip::udp::endpoint& to);
void Send (const uint8_t * header, size_t headerLen, const uint8_t * headerX, size_t headerXLen,
@@ -117,10 +139,12 @@ namespace transport
void Receive (boost::asio::ip::udp::socket& socket);
void HandleReceivedFrom (const boost::system::error_code& ecode, size_t bytes_transferred,
Packet * packet, boost::asio::ip::udp::socket& socket);
void HandleReceivedPacket (Packet * packet);
void HandleReceivedPackets (std::vector<Packet *> packets);
void HandleReceivedPackets (std::list<Packet *>&& packets);
void ProcessNextPacket (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint);
void InsertToReceivedPacketsQueue (Packet * packet);
void InsertToReceivedPacketsQueue (std::list<Packet *>& packets);
void HandleReceivedPacketsQueue ();
void ScheduleTermination ();
void HandleTerminationTimer (const boost::system::error_code& ecode);
@@ -130,9 +154,10 @@ namespace transport
void ScheduleResend (bool more);
void HandleResendTimer (const boost::system::error_code& ecode);
bool CheckPendingOutgoingSession (const boost::asio::ip::udp::endpoint& ep, bool peerTest);
void ConnectThroughIntroducer (std::shared_ptr<SSU2Session> session);
std::vector<std::shared_ptr<SSU2Session> > FindIntroducers (int maxNumIntroducers,
bool v4, const std::unordered_set<i2p::data::IdentHash>& excluded) const;
bool v4, const std::unordered_set<i2p::data::IdentHash>& excluded);
void UpdateIntroducers (bool v4);
void ScheduleIntroducersUpdateTimer ();
void HandleIntroducersUpdateTimer (const boost::system::error_code& ecode, bool v4);
@@ -155,12 +180,14 @@ namespace transport
boost::asio::ip::udp::socket m_SocketV4, m_SocketV6;
boost::asio::ip::address m_AddressV4, m_AddressV6;
std::unordered_map<uint64_t, std::shared_ptr<SSU2Session> > m_Sessions;
std::unordered_map<i2p::data::IdentHash, std::shared_ptr<SSU2Session> > m_SessionsByRouterHash;
std::unordered_map<i2p::data::IdentHash, std::weak_ptr<SSU2Session> > m_SessionsByRouterHash;
mutable std::mutex m_SessionsByRouterHashMutex;
std::map<boost::asio::ip::udp::endpoint, std::shared_ptr<SSU2Session> > m_PendingOutgoingSessions;
mutable std::mutex m_PendingOutgoingSessionsMutex;
std::map<boost::asio::ip::udp::endpoint, std::pair<uint64_t, uint32_t> > m_IncomingTokens, m_OutgoingTokens; // remote endpoint -> (token, expires in seconds)
std::map<uint32_t, std::shared_ptr<SSU2Session> > m_Relays; // we are introducer, relay tag -> session
std::list<i2p::data::IdentHash> m_Introducers, m_IntroducersV6; // introducers we are connected to
std::unordered_map<uint32_t, std::weak_ptr<SSU2Session> > m_Relays; // we are introducer, relay tag -> session
std::unordered_map<uint32_t, std::pair <std::weak_ptr<SSU2Session>, uint64_t > > m_PeerTests; // nonce->(Alice, timestamp). We are Bob
std::list<std::pair<i2p::data::IdentHash, uint32_t> > m_Introducers, m_IntroducersV6; // introducers we are connected to
i2p::util::MemoryPoolMt<Packet> m_PacketsPool;
i2p::util::MemoryPool<SSU2SentPacket> m_SentPacketsPool;
i2p::util::MemoryPool<SSU2IncompleteMessage> m_IncompleteMessagesPool;
@@ -173,7 +200,15 @@ namespace transport
int64_t m_PendingTimeOffset; // during peer test
std::shared_ptr<const i2p::data::IdentityEx> m_PendingTimeOffsetFrom;
std::mt19937 m_Rng;
std::map<boost::asio::ip::udp::endpoint, uint64_t> m_ConnectedRecently; // endpoint -> last activity time in seconds
mutable std::mutex m_ConnectedRecentlyMutex;
std::unordered_map<uint32_t, std::pair <std::weak_ptr<SSU2PeerTestSession>, uint64_t > > m_RequestedPeerTests; // nonce->(Alice, timestamp)
std::list<Packet *> m_ReceivedPacketsQueue;
mutable std::mutex m_ReceivedPacketsQueueMutex;
i2p::crypto::AEADChaCha20Poly1305Encryptor m_Encryptor;
i2p::crypto::AEADChaCha20Poly1305Decryptor m_Decryptor;
i2p::crypto::ChaCha20Context m_ChaCha20;
// proxy
bool m_IsThroughProxy;
uint8_t m_UDPRequestHeader[SOCKS5_UDP_IPV6_REQUEST_HEADER_SIZE];

View File

@@ -0,0 +1,345 @@
/*
* Copyright (c) 2024-2025, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
* See full license text in LICENSE file at top of project tree
*/
#include "Log.h"
#include "SSU2.h"
#include "SSU2OutOfSession.h"
namespace i2p
{
namespace transport
{
SSU2PeerTestSession::SSU2PeerTestSession (SSU2Server& server, uint64_t sourceConnID, uint64_t destConnID):
SSU2Session (server, nullptr, nullptr, false),
m_MsgNumReceived (0), m_NumResends (0),m_IsConnectedRecently (false), m_IsStatusChanged (false),
m_PeerTestResendTimer (server.GetService ())
{
if (!sourceConnID) sourceConnID = ~destConnID;
if (!destConnID) destConnID = ~sourceConnID;
SetSourceConnID (sourceConnID);
SetDestConnID (destConnID);
SetState (eSSU2SessionStatePeerTest);
SetTerminationTimeout (SSU2_PEER_TEST_EXPIRATION_TIMEOUT);
}
bool SSU2PeerTestSession::ProcessPeerTest (uint8_t * buf, size_t len)
{
// we are Alice or Charlie, msgs 5,6,7
Header header;
memcpy (header.buf, buf, 16);
header.ll[0] ^= CreateHeaderMask (i2p::context.GetSSU2IntroKey (), buf + (len - 24));
header.ll[1] ^= CreateHeaderMask (i2p::context.GetSSU2IntroKey (), buf + (len - 12));
if (header.h.type != eSSU2PeerTest)
{
LogPrint (eLogWarning, "SSU2: Unexpected message type ", (int)header.h.type, " instead ", (int)eSSU2PeerTest);
return false;
}
if (len < 48)
{
LogPrint (eLogWarning, "SSU2: PeerTest message too short ", len);
return false;
}
uint8_t nonce[12] = {0};
uint64_t headerX[2]; // sourceConnID, token
GetServer ().ChaCha20 (buf + 16, 16, i2p::context.GetSSU2IntroKey (), nonce, (uint8_t *)headerX);
SetDestConnID (headerX[0]);
// decrypt and handle payload
uint8_t * payload = buf + 32;
CreateNonce (be32toh (header.h.packetNum), nonce);
uint8_t h[32];
memcpy (h, header.buf, 16);
memcpy (h + 16, &headerX, 16);
if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len - 48, h, 32,
i2p::context.GetSSU2IntroKey (), nonce, payload, len - 48, false))
{
LogPrint (eLogWarning, "SSU2: PeerTest AEAD verification failed ");
return false;
}
HandlePayload (payload, len - 48);
SetIsDataReceived (false);
return true;
}
void SSU2PeerTestSession::HandleAddress (const uint8_t * buf, size_t len)
{
if (!ExtractEndpoint (buf, len, m_OurEndpoint))
LogPrint (eLogWarning, "SSU2: Can't handle address block from peer test message");
}
void SSU2PeerTestSession::HandlePeerTest (const uint8_t * buf, size_t len)
{
// msgs 5-7
if (len < 8) return;
uint8_t msg = buf[0];
if (msg <= m_MsgNumReceived)
{
LogPrint (eLogDebug, "SSU2: PeerTest msg num ", msg, " received after ", m_MsgNumReceived, ". Ignored");
return;
}
size_t offset = 3; // points to signed data after msg + code + flag
uint32_t nonce = bufbe32toh (buf + offset + 1); // 1 - ver
switch (msg) // msg
{
case 5: // Alice from Charlie 1
{
if (htobe64 (((uint64_t)nonce << 32) | nonce) == GetSourceConnID ())
{
m_PeerTestResendTimer.cancel (); // cancel delayed msg 6 if any
m_IsConnectedRecently = GetServer ().IsConnectedRecently (GetRemoteEndpoint ());
if (GetAddress ())
{
if (!m_IsConnectedRecently)
SetRouterStatus (eRouterStatusOK);
else if (m_IsStatusChanged && GetRouterStatus () == eRouterStatusFirewalled)
SetRouterStatus (eRouterStatusUnknown);
SendPeerTest (6, buf + offset, len - offset);
}
}
else
LogPrint (eLogWarning, "SSU2: Peer test 5 nonce mismatch ", nonce, " connID=", GetSourceConnID ());
break;
}
case 6: // Charlie from Alice
{
m_PeerTestResendTimer.cancel (); // no more msg 5 resends
if (GetAddress ())
SendPeerTest (7, buf + offset, len - offset);
else
LogPrint (eLogWarning, "SSU2: Unknown address for peer test 6");
GetServer ().RequestRemoveSession (GetConnID ());
break;
}
case 7: // Alice from Charlie 2
{
m_PeerTestResendTimer.cancel (); // no more msg 6 resends
if (m_MsgNumReceived < 5 && m_OurEndpoint.port ()) // msg 5 was not received
{
if (m_OurEndpoint.address ().is_v4 ()) // ipv4
{
if (i2p::context.GetStatus () == eRouterStatusFirewalled)
{
if (m_OurEndpoint.port () != GetServer ().GetPort (true))
i2p::context.SetError (eRouterErrorSymmetricNAT);
else if (i2p::context.GetError () == eRouterErrorSymmetricNAT)
i2p::context.SetError (eRouterErrorNone);
}
}
else
{
if (i2p::context.GetStatusV6 () == eRouterStatusFirewalled)
{
if (m_OurEndpoint.port () != GetServer ().GetPort (false))
i2p::context.SetErrorV6 (eRouterErrorSymmetricNAT);
else if (i2p::context.GetErrorV6 () == eRouterErrorSymmetricNAT)
i2p::context.SetErrorV6 (eRouterErrorNone);
}
}
}
GetServer ().RequestRemoveSession (GetConnID ());
break;
}
default:
LogPrint (eLogWarning, "SSU2: PeerTest unexpected msg num ", msg);
return;
}
m_MsgNumReceived = msg;
}
void SSU2PeerTestSession::SendPeerTest (uint8_t msg)
{
auto addr = GetAddress ();
if (!addr) return;
Header header;
uint8_t h[32], payload[SSU2_MAX_PACKET_SIZE];
// fill packet
header.h.connID = GetDestConnID (); // dest id
RAND_bytes (header.buf + 8, 4); // random packet num
header.h.type = eSSU2PeerTest;
header.h.flags[0] = 2; // ver
header.h.flags[1] = (uint8_t)i2p::context.GetNetID (); // netID
header.h.flags[2] = 0; // flag
memcpy (h, header.buf, 16);
htobuf64 (h + 16, GetSourceConnID ()); // source id
// payload
payload[0] = eSSU2BlkDateTime;
htobe16buf (payload + 1, 4);
htobe32buf (payload + 3, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000);
size_t payloadSize = 7;
if (msg == 6 || msg == 7)
payloadSize += CreateAddressBlock (payload + payloadSize, GetMaxPayloadSize () - payloadSize, GetRemoteEndpoint ());
payloadSize += CreatePeerTestBlock (payload + payloadSize, GetMaxPayloadSize () - payloadSize,
msg, eSSU2PeerTestCodeAccept, nullptr, m_SignedData.data (), m_SignedData.size ());
payloadSize += CreatePaddingBlock (payload + payloadSize, GetMaxPayloadSize () - payloadSize);
// encrypt
uint8_t n[12];
CreateNonce (be32toh (header.h.packetNum), n);
i2p::crypto::AEADChaCha20Poly1305 (payload, payloadSize, h, 32, addr->i, n, payload, payloadSize + 16, true);
payloadSize += 16;
header.ll[0] ^= CreateHeaderMask (addr->i, payload + (payloadSize - 24));
header.ll[1] ^= CreateHeaderMask (addr->i, payload + (payloadSize - 12));
memset (n, 0, 12);
GetServer ().ChaCha20 (h + 16, 16, addr->i, n, h + 16);
// send
GetServer ().Send (header.buf, 16, h + 16, 16, payload, payloadSize, GetRemoteEndpoint ());
UpdateNumSentBytes (payloadSize + 32);
}
void SSU2PeerTestSession::SendPeerTest (uint8_t msg, const uint8_t * signedData, size_t signedDataLen, bool delayed)
{
m_SignedData.assign (signedData, signedData + signedDataLen);
if (!delayed)
SendPeerTest (msg);
// schedule resend for msgs 5 or 6
if (msg == 5 || msg == 6)
ScheduleResend (msg);
}
void SSU2PeerTestSession::SendPeerTest (uint8_t msg, const uint8_t * signedData, size_t signedDataLen,
std::shared_ptr<const i2p::data::RouterInfo::Address> addr, bool delayed)
{
if (!addr) return;
SetAddress (addr);
SendPeerTest (msg, signedData, signedDataLen, delayed);
}
void SSU2PeerTestSession::Connect ()
{
LogPrint (eLogError, "SSU2: Can't connect peer test session");
}
bool SSU2PeerTestSession::ProcessFirstIncomingMessage (uint64_t connID, uint8_t * buf, size_t len)
{
LogPrint (eLogError, "SSU2: Can't handle incoming message in peer test session");
return false;
}
void SSU2PeerTestSession::ScheduleResend (uint8_t msg)
{
if (m_NumResends < SSU2_PEER_TEST_MAX_NUM_RESENDS)
{
m_PeerTestResendTimer.expires_from_now (boost::posix_time::milliseconds(
SSU2_PEER_TEST_RESEND_INTERVAL + GetServer ().GetRng ()() % SSU2_PEER_TEST_RESEND_INTERVAL_VARIANCE));
std::weak_ptr<SSU2PeerTestSession> s(std::static_pointer_cast<SSU2PeerTestSession>(shared_from_this ()));
m_PeerTestResendTimer.async_wait ([s, msg](const boost::system::error_code& ecode)
{
if (ecode != boost::asio::error::operation_aborted)
{
auto s1 = s.lock ();
if (s1)
{
if (msg > s1->m_MsgNumReceived)
{
s1->SendPeerTest (msg);
s1->m_NumResends++;
s1->ScheduleResend (msg);
}
}
}
});
}
}
SSU2HolePunchSession::SSU2HolePunchSession (SSU2Server& server, uint32_t nonce,
const boost::asio::ip::udp::endpoint& remoteEndpoint,
std::shared_ptr<const i2p::data::RouterInfo::Address> addr):
SSU2Session (server), // we create full incoming session
m_NumResends (0), m_HolePunchResendTimer (server.GetService ())
{
// we are Charlie
uint64_t destConnID = htobe64 (((uint64_t)nonce << 32) | nonce); // dest id
uint64_t sourceConnID = ~destConnID;
SetSourceConnID (sourceConnID);
SetDestConnID (destConnID);
SetState (eSSU2SessionStateHolePunch);
SetRemoteEndpoint (remoteEndpoint);
SetAddress (addr);
SetTerminationTimeout (SSU2_RELAY_NONCE_EXPIRATION_TIMEOUT);
}
void SSU2HolePunchSession::SendHolePunch ()
{
auto addr = GetAddress ();
if (!addr) return;
auto& ep = GetRemoteEndpoint ();
LogPrint (eLogDebug, "SSU2: Sending HolePunch to ", ep);
Header header;
uint8_t h[32], payload[SSU2_MAX_PACKET_SIZE];
// fill packet
header.h.connID = GetDestConnID (); // dest id
RAND_bytes (header.buf + 8, 4); // random packet num
header.h.type = eSSU2HolePunch;
header.h.flags[0] = 2; // ver
header.h.flags[1] = (uint8_t)i2p::context.GetNetID (); // netID
header.h.flags[2] = 0; // flag
memcpy (h, header.buf, 16);
htobuf64 (h + 16, GetSourceConnID ()); // source id
RAND_bytes (h + 24, 8); // header token, to be ignored by Alice
// payload
payload[0] = eSSU2BlkDateTime;
htobe16buf (payload + 1, 4);
htobe32buf (payload + 3, (i2p::util::GetMillisecondsSinceEpoch () + 500)/1000);
size_t payloadSize = 7;
payloadSize += CreateAddressBlock (payload + payloadSize, GetMaxPayloadSize () - payloadSize, ep);
// relay response block
if (payloadSize + m_RelayResponseBlock.size () < GetMaxPayloadSize ())
{
memcpy (payload + payloadSize, m_RelayResponseBlock.data (), m_RelayResponseBlock.size ());
payloadSize += m_RelayResponseBlock.size ();
}
payloadSize += CreatePaddingBlock (payload + payloadSize, GetMaxPayloadSize () - payloadSize);
// encrypt
uint8_t n[12];
CreateNonce (be32toh (header.h.packetNum), n);
i2p::crypto::AEADChaCha20Poly1305 (payload, payloadSize, h, 32, addr->i, n, payload, payloadSize + 16, true);
payloadSize += 16;
header.ll[0] ^= CreateHeaderMask (addr->i, payload + (payloadSize - 24));
header.ll[1] ^= CreateHeaderMask (addr->i, payload + (payloadSize - 12));
memset (n, 0, 12);
GetServer ().ChaCha20 (h + 16, 16, addr->i, n, h + 16);
// send
GetServer ().Send (header.buf, 16, h + 16, 16, payload, payloadSize, ep);
UpdateNumSentBytes (payloadSize + 32);
}
void SSU2HolePunchSession::SendHolePunch (const uint8_t * relayResponseBlock, size_t relayResponseBlockLen)
{
m_RelayResponseBlock.assign (relayResponseBlock, relayResponseBlock + relayResponseBlockLen);
SendHolePunch ();
ScheduleResend ();
}
void SSU2HolePunchSession::ScheduleResend ()
{
if (m_NumResends < SSU2_HOLE_PUNCH_MAX_NUM_RESENDS)
{
m_HolePunchResendTimer.expires_from_now (boost::posix_time::milliseconds(
SSU2_HOLE_PUNCH_RESEND_INTERVAL + GetServer ().GetRng ()() % SSU2_HOLE_PUNCH_RESEND_INTERVAL_VARIANCE));
std::weak_ptr<SSU2HolePunchSession> s(std::static_pointer_cast<SSU2HolePunchSession>(shared_from_this ()));
m_HolePunchResendTimer.async_wait ([s](const boost::system::error_code& ecode)
{
if (ecode != boost::asio::error::operation_aborted)
{
auto s1 = s.lock ();
if (s1 && s1->GetState () == eSSU2SessionStateHolePunch)
{
s1->SendHolePunch ();
s1->m_NumResends++;
s1->ScheduleResend ();
}
}
});
}
}
bool SSU2HolePunchSession::ProcessFirstIncomingMessage (uint64_t connID, uint8_t * buf, size_t len)
{
m_HolePunchResendTimer.cancel ();
return SSU2Session::ProcessFirstIncomingMessage (connID, buf, len);
}
}
}

View File

@@ -0,0 +1,86 @@
/*
* 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 SSU2_OUT_OF_SESSION_H__
#define SSU2_OUT_OF_SESSION_H__
#include <vector>
#include "SSU2Session.h"
namespace i2p
{
namespace transport
{
const int SSU2_PEER_TEST_RESEND_INTERVAL = 3000; // in milliseconds
const int SSU2_PEER_TEST_RESEND_INTERVAL_VARIANCE = 2000; // in milliseconds
const int SSU2_PEER_TEST_MAX_NUM_RESENDS = 3;
class SSU2PeerTestSession: public SSU2Session // for PeerTest msgs 5,6,7
{
public:
SSU2PeerTestSession (SSU2Server& server, uint64_t sourceConnID, uint64_t destConnID);
uint8_t GetMsgNumReceived () const { return m_MsgNumReceived; }
bool IsConnectedRecently () const { return m_IsConnectedRecently; }
void SetStatusChanged () { m_IsStatusChanged = true; }
void SendPeerTest (uint8_t msg, const uint8_t * signedData, size_t signedDataLen,
std::shared_ptr<const i2p::data::RouterInfo::Address> addr, bool delayed = false);
bool ProcessPeerTest (uint8_t * buf, size_t len) override;
void Connect () override; // outgoing
bool ProcessFirstIncomingMessage (uint64_t connID, uint8_t * buf, size_t len) override; // incoming
private:
void SendPeerTest (uint8_t msg, const uint8_t * signedData, size_t signedDataLen, bool delayed = false); // PeerTest message
void SendPeerTest (uint8_t msg); // send or resend m_SignedData
void HandlePeerTest (const uint8_t * buf, size_t len) override;
void HandleAddress (const uint8_t * buf, size_t len) override;
void ScheduleResend (uint8_t msg);
private:
uint8_t m_MsgNumReceived, m_NumResends;
bool m_IsConnectedRecently, m_IsStatusChanged;
std::vector<uint8_t> m_SignedData; // for resends
boost::asio::deadline_timer m_PeerTestResendTimer;
boost::asio::ip::udp::endpoint m_OurEndpoint; // as seen by peer
};
const int SSU2_HOLE_PUNCH_RESEND_INTERVAL = 1000; // in milliseconds
const int SSU2_HOLE_PUNCH_RESEND_INTERVAL_VARIANCE = 500; // in milliseconds
const int SSU2_HOLE_PUNCH_MAX_NUM_RESENDS = 3;
class SSU2HolePunchSession: public SSU2Session // Charlie
{
public:
SSU2HolePunchSession (SSU2Server& server, uint32_t nonce, const boost::asio::ip::udp::endpoint& remoteEndpoint,
std::shared_ptr<const i2p::data::RouterInfo::Address> addr);
void SendHolePunch (const uint8_t * relayResponseBlock, size_t relayResponseBlockLen);
bool ProcessFirstIncomingMessage (uint64_t connID, uint8_t * buf, size_t len) override; // SessionRequest
private:
void SendHolePunch ();
void ScheduleResend ();
private:
int m_NumResends;
std::vector<uint8_t> m_RelayResponseBlock;
boost::asio::deadline_timer m_HolePunchResendTimer;
};
}
}
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022-2024, The PurpleI2P Project
* Copyright (c) 2022-2025, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -15,6 +15,7 @@
#include <set>
#include <list>
#include <boost/asio.hpp>
#include "version.h"
#include "Crypto.h"
#include "RouterInfo.h"
#include "RouterContext.h"
@@ -37,6 +38,7 @@ namespace transport
const size_t SSU2_MIN_PACKET_SIZE = 1280;
const int SSU2_HANDSHAKE_RESEND_INTERVAL = 1000; // in milliseconds
const int SSU2_MAX_NUM_RESENDS = 5;
const int SSU2_RESEND_ATTEMPT_MIN_INTERVAL = 3; // in milliseconds
const int SSU2_INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT = 30; // in seconds
const int SSU2_MAX_NUM_RECEIVED_I2NP_MSGIDS = 5000; // how many msgID we store for duplicates check
const int SSU2_RECEIVED_I2NP_MSGIDS_CLEANUP_TIMEOUT = 10; // in seconds
@@ -54,6 +56,7 @@ namespace transport
const int SSU2_MAX_NUM_ACK_RANGES = 32; // to send
const uint8_t SSU2_MAX_NUM_FRAGMENTS = 64;
const int SSU2_SEND_DATETIME_NUM_PACKETS = 256;
const int SSU2_MIN_RELAY_RESPONSE_RESEND_VERSION = MAKE_VERSION_NUMBER(0, 9, 64); // 0.9.64
// flags
const uint8_t SSU2_FLAG_IMMEDIATE_ACK_REQUESTED = 0x01;
@@ -111,8 +114,8 @@ namespace transport
eSSU2SessionStateTerminated,
eSSU2SessionStateFailed,
eSSU2SessionStateIntroduced,
eSSU2SessionStateHolePunch,
eSSU2SessionStatePeerTest,
eSSU2SessionStatePeerTestReceived, // 5 before 4
eSSU2SessionStateTokenRequestReceived
};
@@ -205,36 +208,40 @@ namespace transport
class SSU2Server;
class SSU2Session: public TransportSession, public std::enable_shared_from_this<SSU2Session>
{
union Header
{
uint64_t ll[2];
uint8_t buf[16];
struct
protected:
union Header
{
uint64_t connID;
uint32_t packetNum;
uint8_t type;
uint8_t flags[3];
} h;
};
uint64_t ll[2];
uint8_t buf[16];
struct
{
uint64_t connID;
uint32_t packetNum;
uint8_t type;
uint8_t flags[3];
} h;
};
struct HandshakePacket
{
Header header;
uint8_t headerX[48]; // part1 for SessionConfirmed
uint8_t payload[SSU2_MAX_PACKET_SIZE*2];
size_t payloadSize = 0;
uint64_t sendTime = 0; // in milliseconds
bool isSecondFragment = false; // for SessionConfirmed
};
private:
struct HandshakePacket
{
Header header;
uint8_t headerX[48]; // part1 for SessionConfirmed
uint8_t payload[SSU2_MAX_PACKET_SIZE*2];
size_t payloadSize = 0;
uint64_t sendTime = 0; // in milliseconds
bool isSecondFragment = false; // for SessionConfirmed
};
typedef std::function<void ()> OnEstablished;
typedef std::function<void ()> OnEstablished;
public:
SSU2Session (SSU2Server& server, std::shared_ptr<const i2p::data::RouterInfo> in_RemoteRouter = nullptr,
std::shared_ptr<const i2p::data::RouterInfo::Address> addr = nullptr);
~SSU2Session ();
std::shared_ptr<const i2p::data::RouterInfo::Address> addr = nullptr, bool noise = true);
virtual ~SSU2Session ();
void SetRemoteEndpoint (const boost::asio::ip::udp::endpoint& ep) { m_RemoteEndpoint = ep; };
const boost::asio::ip::udp::endpoint& GetRemoteEndpoint () const { return m_RemoteEndpoint; };
@@ -244,7 +251,7 @@ namespace transport
void SetOnEstablished (OnEstablished e) { m_OnEstablished = e; };
OnEstablished GetOnEstablished () const { return m_OnEstablished; };
void Connect ();
virtual void Connect ();
bool Introduce (std::shared_ptr<SSU2Session> session, uint32_t relayTag);
void WaitForIntroduction ();
void SendPeerTest (); // Alice, Data message
@@ -254,31 +261,54 @@ namespace transport
void FlushData ();
void Done () override;
void SendLocalRouterInfo (bool update) override;
void SendI2NPMessages (const std::vector<std::shared_ptr<I2NPMessage> >& msgs) override;
void SendI2NPMessages (std::list<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 of resent packets
uint64_t GetLastResendTime () const { return m_LastResendTime; };
bool IsEstablished () const override { return m_State == eSSU2SessionStateEstablished; };
i2p::data::RouterInfo::SupportedTransports GetTransportType () const override;
uint64_t GetConnID () const { return m_SourceConnID; };
SSU2SessionState GetState () const { return m_State; };
void SetState (SSU2SessionState state) { m_State = state; };
bool ProcessFirstIncomingMessage (uint64_t connID, uint8_t * buf, size_t len);
virtual bool ProcessFirstIncomingMessage (uint64_t connID, uint8_t * buf, size_t len);
bool ProcessSessionCreated (uint8_t * buf, size_t len);
bool ProcessSessionConfirmed (uint8_t * buf, size_t len);
bool ProcessRetry (uint8_t * buf, size_t len);
bool ProcessHolePunch (uint8_t * buf, size_t len);
bool ProcessPeerTest (uint8_t * buf, size_t len);
virtual bool ProcessPeerTest (uint8_t * buf, size_t len);
void ProcessData (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& from);
protected:
SSU2Server& GetServer () { return m_Server; }
RouterStatus GetRouterStatus () const;
void SetRouterStatus (RouterStatus status) const;
size_t GetMaxPayloadSize () const { return m_MaxPayloadSize; }
void SetIsDataReceived (bool dataReceived) { m_IsDataReceived = dataReceived; };
uint64_t GetSourceConnID () const { return m_SourceConnID; }
void SetSourceConnID (uint64_t sourceConnID) { m_SourceConnID = sourceConnID; }
uint64_t GetDestConnID () const { return m_DestConnID; }
void SetDestConnID (uint64_t destConnID) { m_DestConnID = destConnID; }
void SetAddress (std::shared_ptr<const i2p::data::RouterInfo::Address> addr) { m_Address = addr; }
void HandlePayload (const uint8_t * buf, size_t len);
size_t CreateAddressBlock (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& ep);
size_t CreatePaddingBlock (uint8_t * buf, size_t len, size_t minSize = 0);
size_t CreatePeerTestBlock (uint8_t * buf, size_t len, uint8_t msg, SSU2PeerTestCode code, const uint8_t * routerHash, const uint8_t * signedData, size_t signedDataLen);
bool ExtractEndpoint (const uint8_t * buf, size_t size, boost::asio::ip::udp::endpoint& ep);
private:
void Terminate ();
void Established ();
void ScheduleConnectTimer ();
void HandleConnectTimer (const boost::system::error_code& ecode);
void PostI2NPMessages (std::vector<std::shared_ptr<I2NPMessage> > msgs);
void PostI2NPMessages ();
bool SendQueue (); // returns true if ack block was sent
bool SendFragmentedMessage (std::shared_ptr<I2NPMessage> msg);
void ResendHandshakePacket ();
@@ -296,50 +326,41 @@ namespace transport
uint32_t SendData (const uint8_t * buf, size_t len, uint8_t flags = 0); // returns packet num
void SendQuickAck ();
void SendTermination ();
void SendHolePunch (uint32_t nonce, const boost::asio::ip::udp::endpoint& ep, const uint8_t * introKey, uint64_t token);
void SendPeerTest (uint8_t msg, const uint8_t * signedData, size_t signedDataLen, const uint8_t * introKey); // PeerTest message
void SendPathResponse (const uint8_t * data, size_t len);
void SendPathChallenge ();
void HandlePayload (const uint8_t * buf, size_t len);
void HandleDateTime (const uint8_t * buf, size_t len);
void HandleRouterInfo (const uint8_t * buf, size_t len);
void HandleAck (const uint8_t * buf, size_t len);
void HandleAckRange (uint32_t firstPacketNum, uint32_t lastPacketNum, uint64_t ts);
void HandleAddress (const uint8_t * buf, size_t len);
bool ExtractEndpoint (const uint8_t * buf, size_t size, boost::asio::ip::udp::endpoint& ep);
virtual void HandleAddress (const uint8_t * buf, size_t len);
size_t CreateEndpoint (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& ep);
std::shared_ptr<const i2p::data::RouterInfo::Address> FindLocalAddress () const;
void AdjustMaxPayloadSize ();
RouterStatus GetRouterStatus () const;
void SetRouterStatus (RouterStatus status) const;
bool GetTestingState () const;
void SetTestingState(bool testing) const;
std::shared_ptr<const i2p::data::RouterInfo> ExtractRouterInfo (const uint8_t * buf, size_t size);
void CreateNonce (uint64_t seqn, uint8_t * nonce);
bool UpdateReceivePacketNum (uint32_t packetNum); // for Ack, returns false if duplicate
void HandleFirstFragment (const uint8_t * buf, size_t len);
void HandleFollowOnFragment (const uint8_t * buf, size_t len);
void HandleRelayRequest (const uint8_t * buf, size_t len);
void HandleRelayIntro (const uint8_t * buf, size_t len, int attempts = 0);
void HandleRelayResponse (const uint8_t * buf, size_t len);
void HandlePeerTest (const uint8_t * buf, size_t len);
virtual void HandlePeerTest (const uint8_t * buf, size_t len);
void HandleI2NPMsg (std::shared_ptr<I2NPMessage>&& msg);
size_t CreateAddressBlock (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& ep);
size_t CreateRouterInfoBlock (uint8_t * buf, size_t len, std::shared_ptr<const i2p::data::RouterInfo> r);
size_t 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);
size_t CreateFirstFragmentBlock (uint8_t * buf, size_t len, std::shared_ptr<I2NPMessage> msg);
size_t CreateFollowOnFragmentBlock (uint8_t * buf, size_t len, std::shared_ptr<I2NPMessage> msg, uint8_t& fragmentNum, uint32_t msgID);
size_t CreateRelayIntroBlock (uint8_t * buf, size_t len, const uint8_t * introData, size_t introDataLen);
size_t CreateRelayResponseBlock (uint8_t * buf, size_t len, SSU2RelayResponseCode code, uint32_t nonce, uint64_t token, bool v4);
size_t CreatePeerTestBlock (uint8_t * buf, size_t len, uint8_t msg, SSU2PeerTestCode code, const uint8_t * routerHash, const uint8_t * signedData, size_t signedDataLen);
size_t CreatePeerTestBlock (uint8_t * buf, size_t len, uint32_t nonce); // Alice
size_t CreateTerminationBlock (uint8_t * buf, size_t len);
private:
SSU2Server& m_Server;
@@ -350,6 +371,7 @@ namespace transport
std::shared_ptr<const i2p::data::RouterInfo::Address> m_Address;
boost::asio::ip::udp::endpoint m_RemoteEndpoint;
i2p::data::RouterInfo::CompatibleTransports m_RemoteTransports, m_RemotePeerTestTransports;
int m_RemoteVersion;
uint64_t m_DestConnID, m_SourceConnID;
SSU2SessionState m_State;
uint8_t m_KeyDataSend[64], m_KeyDataReceive[64];
@@ -357,10 +379,11 @@ namespace transport
std::set<uint32_t> m_OutOfSequencePackets; // packet nums > receive packet num
std::map<uint32_t, std::shared_ptr<SSU2SentPacket> > m_SentPackets; // packetNum -> packet
std::unordered_map<uint32_t, std::shared_ptr<SSU2IncompleteMessage> > m_IncompleteMessages; // msgID -> I2NP
std::map<uint32_t, std::pair <std::shared_ptr<SSU2Session>, uint64_t > > m_RelaySessions; // nonce->(Alice, timestamp) for Bob or nonce->(Charlie, timestamp) for Alice
std::map<uint32_t, std::pair <std::shared_ptr<SSU2Session>, uint64_t > > m_PeerTests; // same as for relay sessions
std::unordered_map<uint32_t, std::pair <std::shared_ptr<SSU2Session>, uint64_t > > m_RelaySessions; // nonce->(Alice, timestamp) for Bob or nonce->(Charlie, timestamp) for Alice
std::list<std::shared_ptr<I2NPMessage> > m_SendQueue;
i2p::I2NPMessagesHandler m_Handler;
std::list<std::shared_ptr<I2NPMessage> > m_IntermediateQueue; // from transports
mutable std::mutex m_IntermediateQueueMutex;
bool m_IsDataReceived;
double m_RTT;
int m_MsgLocalExpirationTimeout;
@@ -373,15 +396,23 @@ 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
uint64_t m_LastResendTime, m_LastResendAttemptTime; // in milliseconds
int m_NumRanges;
uint8_t m_Ranges[SSU2_MAX_NUM_ACK_RANGES*2]; // ranges sent with previous Ack if any
};
inline uint64_t CreateHeaderMask (const uint8_t * kh, const uint8_t * nonce)
{
uint64_t data = 0;
i2p::crypto::ChaCha20 ((uint8_t *)&data, 8, kh, nonce, (uint8_t *)&data);
return data;
}
inline void CreateNonce (uint64_t seqn, uint8_t * nonce)
{
memset (nonce, 0, 4);
htole64buf (nonce + 4, seqn);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -19,6 +19,7 @@
#include <mutex>
#include <boost/asio.hpp>
#include "Base.h"
#include "Gzip.h"
#include "I2PEndian.h"
#include "Identity.h"
#include "LeaseSet.h"
@@ -54,20 +55,28 @@ namespace stream
const size_t COMPRESSION_THRESHOLD_SIZE = 66;
const int MAX_NUM_RESEND_ATTEMPTS = 10;
const int INITIAL_WINDOW_SIZE = 10;
const int MIN_WINDOW_SIZE = 1;
const int MAX_WINDOW_SIZE = 128;
const double RTT_EWMA_ALPHA = 0.8;
const int MIN_WINDOW_SIZE = 3;
const int MAX_WINDOW_SIZE = 512;
const double RTT_EWMA_ALPHA = 0.25;
const double SLOWRTT_EWMA_ALPHA = 0.05;
const double PREV_SPEED_KEEP_TIME_COEFF = 0.35; // 0.1 - 1 // how long will the window size stay around the previous drop level, less is longer
const int MIN_RTO = 20; // in milliseconds
const int INITIAL_RTT = 8000; // in milliseconds
const int INITIAL_RTT = 1500; // in milliseconds
const int INITIAL_RTO = 9000; // in milliseconds
const int INITIAL_PACING_TIME = 1000 * INITIAL_RTT / INITIAL_WINDOW_SIZE; // in microseconds
const int MIN_SEND_ACK_TIMEOUT = 2; // in milliseconds
const int SYN_TIMEOUT = 200; // how long we wait for SYN after follow-on, in milliseconds
const size_t MAX_PENDING_INCOMING_BACKLOG = 128;
const size_t MAX_PENDING_INCOMING_BACKLOG = 1024;
const int PENDING_INCOMING_TIMEOUT = 10; // in seconds
const int MAX_RECEIVE_TIMEOUT = 20; // in seconds
const uint16_t DELAY_CHOKING = 60000; // in milliseconds
const uint64_t SEND_INTERVAL = 10000; // in microseconds
const uint64_t SEND_INTERVAL_VARIANCE = 2000; // in microseconds
const uint64_t REQUEST_IMMEDIATE_ACK_INTERVAL = 7500; // in milliseconds
const uint64_t REQUEST_IMMEDIATE_ACK_INTERVAL_VARIANCE = 3200; // in milliseconds
const bool LOSS_BASED_CONTROL_ENABLED = 1; // 0/1
const uint64_t STREAMING_DESTINATION_POOLS_CLEANUP_INTERVAL = 646; // in seconds
struct Packet
{
size_t len, offset;
@@ -77,7 +86,7 @@ namespace stream
Packet (): len (0), offset (0), sendTime (0), resent (false) {};
uint8_t * GetBuffer () { return buf + offset; };
size_t GetLength () const { return len - offset; };
size_t GetLength () const { return len > offset ? len - offset : 0; };
uint32_t GetSendStreamID () const { return bufbe32toh (buf); };
uint32_t GetReceiveStreamID () const { return bufbe32toh (buf + 4); };
@@ -167,9 +176,9 @@ namespace stream
{
public:
Stream (boost::asio::io_service& service, StreamingDestination& local,
Stream (boost::asio::io_context& service, StreamingDestination& local,
std::shared_ptr<const i2p::data::LeaseSet> remote, int port = 0); // outgoing
Stream (boost::asio::io_service& service, StreamingDestination& local); // incoming
Stream (boost::asio::io_context& service, StreamingDestination& local); // incoming
~Stream ();
uint32_t GetSendStreamID () const { return m_SendStreamID; };
@@ -178,6 +187,7 @@ namespace stream
std::shared_ptr<const i2p::data::IdentityEx> GetRemoteIdentity () const { return m_RemoteIdentity; };
bool IsOpen () const { return m_Status == eStreamStatusOpen; };
bool IsEstablished () const { return m_SendStreamID; };
bool IsIncoming () const { return m_IsIncoming; };
StreamStatus GetStatus () const { return m_Status; };
StreamingDestination& GetLocalDestination () { return m_LocalDestination; };
void ResetRoutingPath ();
@@ -193,7 +203,7 @@ namespace stream
size_t ReadSome (uint8_t * buf, size_t len) { return ConcatenatePackets (buf, len); };
size_t Receive (uint8_t * buf, size_t len, int timeout);
void AsyncClose() { m_Service.post(std::bind(&Stream::Close, shared_from_this())); };
void AsyncClose() { boost::asio::post(m_Service, std::bind(&Stream::Close, shared_from_this())); };
/** only call close from destination thread, use Stream::AsyncClose for other threads */
void Close ();
@@ -229,7 +239,7 @@ namespace stream
void UpdateCurrentRemoteLease (bool expired = false);
template<typename Buffer, typename ReceiveHandler>
void HandleReceiveTimer (const boost::system::error_code& ecode, const Buffer& buffer, ReceiveHandler handler, int remainingTimeout);
void HandleReceiveTimer (const boost::system::error_code& ecode, Buffer& buffer, ReceiveHandler handler, int remainingTimeout);
void ScheduleSend ();
void HandleSendTimer (const boost::system::error_code& ecode);
@@ -240,39 +250,58 @@ namespace stream
void HandleAckSendTimer (const boost::system::error_code& ecode);
void UpdatePacingTime ();
void ProcessWindowDrop ();
void ResetWindowSize ();
void CancelRemoteLeaseChange ();
private:
boost::asio::io_service& m_Service;
boost::asio::io_context& m_Service;
uint32_t m_SendStreamID, m_RecvStreamID, m_SequenceNumber;
uint32_t m_DropWindowDelaySequenceNumber;
uint32_t m_TunnelsChangeSequenceNumber;
int32_t m_LastReceivedSequenceNumber;
int32_t m_PreviousReceivedSequenceNumber;
int32_t m_LastConfirmedReceivedSequenceNumber; // for limit inbound speed
StreamStatus m_Status;
bool m_IsIncoming;
bool m_IsAckSendScheduled;
bool m_IsNAcked;
bool m_IsFirstACK;
bool m_IsResendNeeded;
bool m_IsFirstRttSample;
bool m_IsSendTime;
bool m_IsWinDropped;
bool m_IsClientChoked;
bool m_IsTimeOutResend;
bool m_IsImmediateAckRequested;
bool m_IsRemoteLeaseChangeInProgress;
bool m_DoubleWinIncCounter;
StreamingDestination& m_LocalDestination;
std::shared_ptr<const i2p::data::IdentityEx> m_RemoteIdentity;
std::shared_ptr<const i2p::crypto::Verifier> m_TransientVerifier; // in case of offline key
std::shared_ptr<const i2p::data::LeaseSet> m_RemoteLeaseSet;
std::shared_ptr<i2p::garlic::GarlicRoutingSession> m_RoutingSession;
std::shared_ptr<const i2p::data::Lease> m_CurrentRemoteLease;
std::shared_ptr<const i2p::data::Lease> m_NextRemoteLease;
std::shared_ptr<i2p::tunnel::OutboundTunnel> m_CurrentOutboundTunnel;
std::queue<Packet *> m_ReceiveQueue;
std::set<Packet *, PacketCmp> m_SavedPackets;
std::set<Packet *, PacketCmp> m_SentPackets;
std::set<Packet *, PacketCmp> m_NACKedPackets;
boost::asio::deadline_timer m_ReceiveTimer, m_SendTimer, m_ResendTimer, m_AckSendTimer;
size_t m_NumSentBytes, m_NumReceivedBytes;
uint16_t m_Port;
SendBufferQueue m_SendBuffer;
double m_RTT;
int m_WindowSize, m_RTO, m_AckDelay, m_PrevRTTSample, m_PrevRTT, m_Jitter;
uint64_t m_MinPacingTime, m_PacingTime;
int m_NumResendAttempts;
double m_RTT, m_SlowRTT, m_SlowRTT2;
float m_WindowSize, m_LastWindowDropSize, m_WindowDropTargetSize;
int m_WindowIncCounter, m_RTO, m_AckDelay, m_PrevRTTSample, m_WindowSizeTail;
double m_Jitter;
uint64_t m_MinPacingTime, m_PacingTime, m_PacingTimeRem, // microseconds
m_LastSendTime, m_LastACKRecieveTime, m_ACKRecieveInterval, m_RemoteLeaseChangeTime; // milliseconds
uint64_t m_LastACKSendTime, m_PacketACKInterval, m_PacketACKIntervalRem; // for limit inbound speed
int m_NumResendAttempts, m_NumPacketsToSend;
size_t m_MTU;
};
@@ -308,6 +337,7 @@ namespace stream
Packet * NewPacket () { return m_PacketsPool.Acquire(); }
void DeletePacket (Packet * p) { return m_PacketsPool.Release(p); }
uint32_t GetRandom ();
private:
@@ -331,7 +361,8 @@ namespace stream
i2p::util::MemoryPool<Packet> m_PacketsPool;
i2p::util::MemoryPool<I2NPMessageBuffer<I2NP_MAX_SHORT_MESSAGE_SIZE> > m_I2NPMsgsPool;
uint64_t m_LastCleanupTime; // in seconds
public:
i2p::data::GzipInflator m_Inflator;
@@ -347,7 +378,7 @@ namespace stream
void Stream::AsyncReceive (const Buffer& buffer, ReceiveHandler handler, int timeout)
{
auto s = shared_from_this();
m_Service.post ([s, buffer, handler, timeout](void)
boost::asio::post (m_Service, [s, buffer, handler, timeout](void)
{
if (!s->m_ReceiveQueue.empty () || s->m_Status == eStreamStatusReset)
s->HandleReceiveTimer (boost::asio::error::make_error_code (boost::asio::error::operation_aborted), buffer, handler, 0);
@@ -366,9 +397,9 @@ namespace stream
}
template<typename Buffer, typename ReceiveHandler>
void Stream::HandleReceiveTimer (const boost::system::error_code& ecode, const Buffer& buffer, ReceiveHandler handler, int remainingTimeout)
void Stream::HandleReceiveTimer (const boost::system::error_code& ecode, Buffer& buffer, ReceiveHandler handler, int remainingTimeout)
{
size_t received = ConcatenatePackets (boost::asio::buffer_cast<uint8_t *>(buffer), boost::asio::buffer_size(buffer));
size_t received = ConcatenatePackets ((uint8_t *)buffer.data (), buffer.size ());
if (received > 0)
handler (boost::system::error_code (), received);
else if (ecode == boost::asio::error::operation_aborted)

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2023, The PurpleI2P Project
* Copyright (c) 2013-2025, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -12,10 +12,14 @@
#include <boost/static_assert.hpp>
#include <string.h>
#include <openssl/rand.h>
#include <string>
#include <string_view>
#include "Base.h"
namespace i2p {
namespace data {
namespace i2p
{
namespace data
{
template<size_t sz>
class Tag
{
@@ -70,14 +74,14 @@ namespace data {
return std::string (str, str + l);
}
size_t FromBase32 (const std::string& s)
size_t FromBase32 (std::string_view s)
{
return i2p::data::Base32ToByteStream (s.c_str (), s.length (), m_Buf, sz);
return i2p::data::Base32ToByteStream (s.data (), s.length (), m_Buf, sz);
}
size_t FromBase64 (const std::string& s)
size_t FromBase64 (std::string_view s)
{
return i2p::data::Base64ToByteStream (s.c_str (), s.length (), m_Buf, sz);
return i2p::data::Base64ToByteStream (s.data (), s.length (), m_Buf, sz);
}
uint8_t GetBit (int i) const

View File

@@ -60,18 +60,16 @@ namespace util
static void SyncTimeWithNTP (const std::string& address)
{
LogPrint (eLogInfo, "Timestamp: NTP request to ", address);
boost::asio::io_service service;
boost::asio::io_context service;
boost::system::error_code ec;
auto it = boost::asio::ip::udp::resolver (service).resolve (
boost::asio::ip::udp::resolver::query (address, "ntp"), ec);
auto endpoints = boost::asio::ip::udp::resolver (service).resolve (address, "ntp", ec);
if (!ec)
{
bool found = false;
boost::asio::ip::udp::resolver::iterator end;
boost::asio::ip::udp::endpoint ep;
while (it != end)
for (const auto& it: endpoints)
{
ep = *it;
ep = it;
if (!ep.address ().is_unspecified ())
{
if (ep.address ().is_v4 ())
@@ -88,7 +86,6 @@ namespace util
}
}
if (found) break;
it++;
}
if (!found)
{
@@ -154,7 +151,7 @@ namespace util
{
m_IsRunning = true;
LogPrint(eLogInfo, "Timestamp: NTP time sync starting");
m_Service.post (std::bind (&NTPTimeSync::Sync, this));
boost::asio::post (m_Service, std::bind (&NTPTimeSync::Sync, this));
m_Thread.reset (new std::thread (std::bind (&NTPTimeSync::Run, this)));
}
else

View File

@@ -52,7 +52,7 @@ namespace util
bool m_IsRunning;
std::unique_ptr<std::thread> m_Thread;
boost::asio::io_service m_Service;
boost::asio::io_context m_Service;
boost::asio::deadline_timer m_Timer;
int m_SyncInterval;
std::vector<std::string> m_NTPServersList;

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2022, The PurpleI2P Project
* Copyright (c) 2013-2025, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -8,9 +8,14 @@
#include <string.h>
#include "I2PEndian.h"
#include "Crypto.h"
#include "Log.h"
#include "Identity.h"
#include "RouterInfo.h"
#include "RouterContext.h"
#include "I2NPProtocol.h"
#include "Garlic.h"
#include "ECIESX25519AEADRatchetSession.h"
#include "Tunnel.h"
#include "Transports.h"
#include "TransitTunnel.h"
@@ -38,6 +43,21 @@ namespace tunnel
i2p::transport::transports.UpdateTotalTransitTransmittedBytes (TUNNEL_DATA_MSG_SIZE);
}
std::string TransitTunnel::GetNextPeerName () const
{
return i2p::data::GetIdentHashAbbreviation (GetNextIdentHash ());
}
void TransitTunnel::SendTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage> msg)
{
LogPrint (eLogError, "TransitTunnel: We are not a gateway for ", GetTunnelID ());
}
void TransitTunnel::HandleTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage>&& tunnelMsg)
{
LogPrint (eLogError, "TransitTunnel: Incoming tunnel message is not supported ", GetTunnelID ());
}
TransitTunnelParticipant::~TransitTunnelParticipant ()
{
}
@@ -59,45 +79,100 @@ namespace tunnel
auto num = m_TunnelDataMsgs.size ();
if (num > 1)
LogPrint (eLogDebug, "TransitTunnel: ", GetTunnelID (), "->", GetNextTunnelID (), " ", num);
i2p::transport::transports.SendMessages (GetNextIdentHash (), m_TunnelDataMsgs);
m_TunnelDataMsgs.clear ();
if (!m_Sender) m_Sender = std::make_unique<TunnelTransportSender>();
m_Sender->SendMessagesTo (GetNextIdentHash (), m_TunnelDataMsgs); // send and clear
}
}
void TransitTunnel::SendTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage> msg)
std::string TransitTunnelParticipant::GetNextPeerName () const
{
LogPrint (eLogError, "TransitTunnel: We are not a gateway for ", GetTunnelID ());
}
void TransitTunnel::HandleTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage>&& tunnelMsg)
{
LogPrint (eLogError, "TransitTunnel: Incoming tunnel message is not supported ", GetTunnelID ());
}
if (m_Sender)
{
auto transport = m_Sender->GetCurrentTransport ();
if (transport)
return TransitTunnel::GetNextPeerName () + "-" +
i2p::data::RouterInfo::GetTransportName (transport->GetTransportType ());
}
return TransitTunnel::GetNextPeerName ();
}
void TransitTunnelGateway::SendTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage> msg)
{
TunnelMessageBlock block;
block.deliveryType = eDeliveryTypeLocal;
block.data = msg;
std::unique_lock<std::mutex> l(m_SendMutex);
std::lock_guard<std::mutex> l(m_SendMutex);
m_Gateway.PutTunnelDataMsg (block);
}
void TransitTunnelGateway::FlushTunnelDataMsgs ()
{
std::unique_lock<std::mutex> l(m_SendMutex);
std::lock_guard<std::mutex> l(m_SendMutex);
m_Gateway.SendBuffer ();
}
std::string TransitTunnelGateway::GetNextPeerName () const
{
const auto& sender = m_Gateway.GetSender ();
if (sender)
{
auto transport = sender->GetCurrentTransport ();
if (transport)
return TransitTunnel::GetNextPeerName () + "-" +
i2p::data::RouterInfo::GetTransportName (transport->GetTransportType ());
}
return TransitTunnel::GetNextPeerName ();
}
void TransitTunnelEndpoint::HandleTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage>&& tunnelMsg)
{
auto newMsg = CreateEmptyTunnelDataMsg (true);
EncryptTunnelMsg (tunnelMsg, newMsg);
LogPrint (eLogDebug, "TransitTunnel: handle msg for endpoint ", GetTunnelID ());
m_Endpoint.HandleDecryptedTunnelDataMsg (newMsg);
std::lock_guard<std::mutex> l(m_HandleMutex);
if (!m_Endpoint) m_Endpoint = std::make_unique<TunnelEndpoint>(false); // transit endpoint is always outbound
m_Endpoint->HandleDecryptedTunnelDataMsg (newMsg);
}
void TransitTunnelEndpoint::FlushTunnelDataMsgs ()
{
if (m_Endpoint)
{
std::lock_guard<std::mutex> l(m_HandleMutex);
m_Endpoint->FlushI2NPMsgs ();
}
}
void TransitTunnelEndpoint::Cleanup ()
{
if (m_Endpoint)
{
std::lock_guard<std::mutex> l(m_HandleMutex);
m_Endpoint->Cleanup ();
}
}
std::string TransitTunnelEndpoint::GetNextPeerName () const
{
if (!m_Endpoint) return "";
auto hash = m_Endpoint->GetCurrentHash ();
if (hash)
{
const auto& sender = m_Endpoint->GetSender ();
if (sender)
{
auto transport = sender->GetCurrentTransport ();
if (transport)
return i2p::data::GetIdentHashAbbreviation (*hash) + "-" +
i2p::data::RouterInfo::GetTransportName (transport->GetTransportType ());
else
return i2p::data::GetIdentHashAbbreviation (*hash);
}
}
return "";
}
std::shared_ptr<TransitTunnel> CreateTransitTunnel (uint32_t receiveTunnelID,
const i2p::data::IdentHash& nextIdent, uint32_t nextTunnelID,
const i2p::crypto::AESKey& layerKey, const i2p::crypto::AESKey& ivKey,
@@ -119,5 +194,440 @@ namespace tunnel
return std::make_shared<TransitTunnelParticipant> (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey);
}
}
TransitTunnels::TransitTunnels ():
m_IsRunning (false), m_Rng(i2p::util::GetMonotonicMicroseconds ()%1000000LL)
{
}
TransitTunnels::~TransitTunnels ()
{
Stop ();
}
void TransitTunnels::Start ()
{
m_IsRunning = true;
m_Thread.reset (new std::thread (std::bind (&TransitTunnels::Run, this)));
}
void TransitTunnels::Stop ()
{
m_IsRunning = false;
m_TunnelBuildMsgQueue.WakeUp ();
if (m_Thread)
{
m_Thread->join ();
m_Thread = nullptr;
}
m_TransitTunnels.clear ();
}
void TransitTunnels::Run ()
{
i2p::util::SetThreadName("TBM");
uint64_t lastTs = 0;
std::list<std::shared_ptr<I2NPMessage> > msgs;
while (m_IsRunning)
{
try
{
if (m_TunnelBuildMsgQueue.Wait (TRANSIT_TUNNELS_QUEUE_WAIT_INTERVAL, 0))
{
m_TunnelBuildMsgQueue.GetWholeQueue (msgs);
while (!msgs.empty ())
{
auto msg = msgs.front (); msgs.pop_front ();
if (!msg) continue;
uint8_t typeID = msg->GetTypeID ();
switch (typeID)
{
case eI2NPShortTunnelBuild:
HandleShortTransitTunnelBuildMsg (std::move (msg));
break;
case eI2NPVariableTunnelBuild:
HandleVariableTransitTunnelBuildMsg (std::move (msg));
break;
default:
LogPrint (eLogWarning, "TransitTunnel: Unexpected message type ", (int) typeID);
}
if (!m_IsRunning) break;
}
}
if (m_IsRunning)
{
uint64_t ts = i2p::util::GetSecondsSinceEpoch ();
if (ts >= lastTs + TUNNEL_MANAGE_INTERVAL || ts + TUNNEL_MANAGE_INTERVAL < lastTs)
{
ManageTransitTunnels (ts);
lastTs = ts;
}
}
}
catch (std::exception& ex)
{
LogPrint (eLogError, "TransitTunnel: Runtime exception: ", ex.what ());
}
}
}
void TransitTunnels::PostTransitTunnelBuildMsg (std::shared_ptr<I2NPMessage>&& msg)
{
if (msg) m_TunnelBuildMsgQueue.Put (msg);
}
void TransitTunnels::HandleShortTransitTunnelBuildMsg (std::shared_ptr<I2NPMessage>&& msg)
{
if (!msg) return;
uint8_t * buf = msg->GetPayload();
size_t len = msg->GetPayloadLength();
int num = buf[0];
LogPrint (eLogDebug, "TransitTunnel: ShortTunnelBuild ", num, " records");
if (num > i2p::tunnel::MAX_NUM_RECORDS)
{
LogPrint (eLogError, "TransitTunnel: Too many records in ShortTunnelBuild message ", num);
return;
}
if (len < num*SHORT_TUNNEL_BUILD_RECORD_SIZE + 1)
{
LogPrint (eLogError, "TransitTunnel: ShortTunnelBuild message of ", num, " records is too short ", len);
return;
}
const uint8_t * record = buf + 1;
for (int i = 0; i < num; i++)
{
if (!memcmp (record, (const uint8_t *)i2p::context.GetRouterInfo ().GetIdentHash (), 16))
{
LogPrint (eLogDebug, "TransitTunnel: Short request record ", i, " is ours");
uint8_t clearText[SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE];
if (!i2p::context.DecryptTunnelShortRequestRecord (record + SHORT_REQUEST_RECORD_ENCRYPTED_OFFSET, clearText))
{
LogPrint (eLogWarning, "TransitTunnel: Can't decrypt short request record ", i);
return;
}
if (clearText[SHORT_REQUEST_RECORD_LAYER_ENCRYPTION_TYPE]) // not AES
{
LogPrint (eLogWarning, "TransitTunnel: Unknown layer encryption type ", clearText[SHORT_REQUEST_RECORD_LAYER_ENCRYPTION_TYPE], " in short request record");
return;
}
auto& noiseState = i2p::context.GetCurrentNoiseState ();
uint8_t replyKey[32]; // AEAD/Chacha20/Poly1305
i2p::crypto::AESKey layerKey, ivKey; // AES
i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "SMTunnelReplyKey", noiseState.m_CK);
memcpy (replyKey, noiseState.m_CK + 32, 32);
i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "SMTunnelLayerKey", noiseState.m_CK);
memcpy (layerKey, noiseState.m_CK + 32, 32);
bool isEndpoint = clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG;
if (isEndpoint)
{
i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "TunnelLayerIVKey", noiseState.m_CK);
memcpy (ivKey, noiseState.m_CK + 32, 32);
}
else
{
if (!memcmp ((const uint8_t *)i2p::context.GetIdentHash (), clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, 32)) // if next ident is now ours
{
LogPrint (eLogWarning, "TransitTunnel: 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 ())
{
auto congestionLevel = i2p::context.GetCongestionLevel (false);
if (congestionLevel < CONGESTION_LEVEL_FULL)
{
if (congestionLevel >= CONGESTION_LEVEL_MEDIUM)
{
// random reject depending on congestion level
int level = m_Rng () % (CONGESTION_LEVEL_FULL - CONGESTION_LEVEL_MEDIUM) + CONGESTION_LEVEL_MEDIUM;
if (congestionLevel > level)
retCode = 30;
}
}
else
retCode = 30;
}
else
retCode = 30;
if (!retCode)
{
i2p::data::IdentHash nextIdent(clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET);
bool isEndpoint = clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG;
if (isEndpoint || !i2p::data::IsRouterDuplicated (nextIdent))
{
// create new transit tunnel
transitTunnel = CreateTransitTunnel (
bufbe32toh (clearText + SHORT_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET),
nextIdent,
bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET),
layerKey, ivKey,
clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_GATEWAY_FLAG,
isEndpoint);
if (!AddTransitTunnel (transitTunnel))
retCode = 30;
}
else
// decline tunnel going to duplicated router
retCode = 30;
}
// encrypt reply
uint8_t nonce[12];
memset (nonce, 0, 12);
uint8_t * reply = buf + 1;
for (int j = 0; j < num; j++)
{
nonce[4] = j; // nonce is record #
if (j == i)
{
memset (reply + SHORT_RESPONSE_RECORD_OPTIONS_OFFSET, 0, 2); // no options
reply[SHORT_RESPONSE_RECORD_RET_OFFSET] = retCode;
if (!i2p::crypto::AEADChaCha20Poly1305 (reply, SHORT_TUNNEL_BUILD_RECORD_SIZE - 16,
noiseState.m_H, 32, replyKey, nonce, reply, SHORT_TUNNEL_BUILD_RECORD_SIZE, true)) // encrypt
{
LogPrint (eLogWarning, "TransitTunnel: Short reply AEAD encryption failed");
return;
}
}
else
i2p::crypto::ChaCha20 (reply, SHORT_TUNNEL_BUILD_RECORD_SIZE, replyKey, nonce, reply);
reply += SHORT_TUNNEL_BUILD_RECORD_SIZE;
}
// send reply
auto onDrop = [transitTunnel]()
{
if (transitTunnel)
{
LogPrint (eLogDebug, "TransitTunnel: Failed to send reply for transit tunnel ", transitTunnel->GetTunnelID ());
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?
{
i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "RGarlicKeyAndTag", noiseState.m_CK);
uint64_t tag;
memcpy (&tag, noiseState.m_CK, 8);
// we send it to reply tunnel
i2p::transport::transports.SendMessage (clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET,
CreateTunnelGatewayMsg (bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET),
i2p::garlic::WrapECIESX25519Message (replyMsg, noiseState.m_CK + 32, tag)));
}
else
{
// IBGW is local
uint32_t tunnelID = bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET);
auto tunnel = i2p::tunnel::tunnels.GetTunnel (tunnelID);
if (tunnel)
{
tunnel->SendTunnelDataMsg (replyMsg);
tunnel->FlushTunnelDataMsgs ();
}
else
LogPrint (eLogWarning, "I2NP: Tunnel ", tunnelID, " not found for short tunnel build reply");
}
}
else
{
auto msg = CreateI2NPMessage (eI2NPShortTunnelBuild, buf, len,
bufbe32toh (clearText + SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET));
if (transitTunnel) msg->onDrop = onDrop;
i2p::transport::transports.SendMessage (clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, msg);
}
return;
}
record += SHORT_TUNNEL_BUILD_RECORD_SIZE;
}
}
bool TransitTunnels::HandleBuildRequestRecords (int num, uint8_t * records, uint8_t * clearText)
{
for (int i = 0; i < num; i++)
{
uint8_t * record = records + i*TUNNEL_BUILD_RECORD_SIZE;
if (!memcmp (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)i2p::context.GetRouterInfo ().GetIdentHash (), 16))
{
LogPrint (eLogDebug, "TransitTunnel: Build request record ", i, " is ours");
if (!i2p::context.DecryptTunnelBuildRecord (record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, clearText))
{
LogPrint (eLogWarning, "TransitTunnel: 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, "TransitTunnel: Next ident is ours in tunnel build record");
return false;
}
uint8_t retCode = 0;
// decide if we should accept tunnel
bool accept = i2p::context.AcceptsTunnels ();
if (accept)
{
auto congestionLevel = i2p::context.GetCongestionLevel (false);
if (congestionLevel >= CONGESTION_LEVEL_MEDIUM)
{
if (congestionLevel < CONGESTION_LEVEL_FULL)
{
// random reject depending on congestion level
int level = m_Rng () % (CONGESTION_LEVEL_FULL - CONGESTION_LEVEL_MEDIUM) + CONGESTION_LEVEL_MEDIUM;
if (congestionLevel > level)
accept = false;
}
else
accept = false;
}
}
if (accept)
{
i2p::data::IdentHash nextIdent(clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET);
bool isEndpoint = clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG;
if (isEndpoint || !i2p::data::IsRouterDuplicated (nextIdent))
{
auto transitTunnel = CreateTransitTunnel (
bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET),
nextIdent,
bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET),
clearText + ECIES_BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET,
clearText + ECIES_BUILD_REQUEST_RECORD_IV_KEY_OFFSET,
clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_GATEWAY_FLAG,
isEndpoint);
if (!AddTransitTunnel (transitTunnel))
retCode = 30;
}
else
// decline tunnel going to duplicated router
retCode = 30;
}
else
retCode = 30; // always reject with bandwidth reason (30)
// replace record to reply
memset (record + ECIES_BUILD_RESPONSE_RECORD_OPTIONS_OFFSET, 0, 2); // no options
record[ECIES_BUILD_RESPONSE_RECORD_RET_OFFSET] = retCode;
// encrypt reply
i2p::crypto::CBCEncryption encryption;
for (int j = 0; j < num; j++)
{
uint8_t * reply = records + j*TUNNEL_BUILD_RECORD_SIZE;
if (j == i)
{
uint8_t nonce[12];
memset (nonce, 0, 12);
auto& noiseState = i2p::context.GetCurrentNoiseState ();
if (!i2p::crypto::AEADChaCha20Poly1305 (reply, TUNNEL_BUILD_RECORD_SIZE - 16,
noiseState.m_H, 32, noiseState.m_CK, nonce, reply, TUNNEL_BUILD_RECORD_SIZE, true)) // encrypt
{
LogPrint (eLogWarning, "TransitTunnel: Reply AEAD encryption failed");
return false;
}
}
else
{
encryption.SetKey (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET);
encryption.Encrypt(reply, TUNNEL_BUILD_RECORD_SIZE, clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_IV_OFFSET, reply);
}
}
return true;
}
}
return false;
}
void TransitTunnels::HandleVariableTransitTunnelBuildMsg (std::shared_ptr<I2NPMessage>&& msg)
{
if (!msg) return;
uint8_t * buf = msg->GetPayload();
size_t len = msg->GetPayloadLength();
int num = buf[0];
LogPrint (eLogDebug, "TransitTunnel: VariableTunnelBuild ", num, " records");
if (num > i2p::tunnel::MAX_NUM_RECORDS)
{
LogPrint (eLogError, "TransitTunnle: Too many records in VaribleTunnelBuild message ", num);
return;
}
if (len < num*TUNNEL_BUILD_RECORD_SIZE + 1)
{
LogPrint (eLogError, "TransitTunnel: VaribleTunnelBuild message of ", num, " records is too short ", len);
return;
}
uint8_t clearText[ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE];
if (HandleBuildRequestRecords (num, buf + 1, clearText))
{
if (clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG) // we are endpoint of outboud tunnel
{
// so we send it to reply tunnel
i2p::transport::transports.SendMessage (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET,
CreateTunnelGatewayMsg (bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET),
eI2NPVariableTunnelBuildReply, buf, len,
bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET)));
}
else
i2p::transport::transports.SendMessage (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET,
CreateI2NPMessage (eI2NPVariableTunnelBuild, buf, len,
bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET)));
}
}
bool TransitTunnels::AddTransitTunnel (std::shared_ptr<TransitTunnel> tunnel)
{
if (tunnels.AddTunnel (tunnel))
m_TransitTunnels.push_back (tunnel);
else
{
LogPrint (eLogError, "TransitTunnel: Tunnel with id ", tunnel->GetTunnelID (), " already exists");
return false;
}
return true;
}
void TransitTunnels::ManageTransitTunnels (uint64_t ts)
{
for (auto it = m_TransitTunnels.begin (); it != m_TransitTunnels.end ();)
{
auto tunnel = *it;
if (ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT ||
ts + TUNNEL_EXPIRATION_TIMEOUT < tunnel->GetCreationTime ())
{
LogPrint (eLogDebug, "TransitTunnel: Transit tunnel with id ", tunnel->GetTunnelID (), " expired");
tunnels.RemoveTunnel (tunnel->GetTunnelID ());
it = m_TransitTunnels.erase (it);
}
else
{
tunnel->Cleanup ();
it++;
}
}
}
int TransitTunnels::GetTransitTunnelsExpirationTimeout ()
{
int timeout = 0;
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
// TODO: possible race condition with I2PControl
for (const auto& it : m_TransitTunnels)
{
int t = it->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT - ts;
if (t > timeout) timeout = t;
}
return timeout;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2023, The PurpleI2P Project
* Copyright (c) 2013-2025, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -10,10 +10,11 @@
#define TRANSIT_TUNNEL_H__
#include <inttypes.h>
#include <vector>
#include <list>
#include <mutex>
#include <memory>
#include "Crypto.h"
#include "Queue.h"
#include "I2NPProtocol.h"
#include "TunnelEndpoint.h"
#include "TunnelGateway.h"
@@ -32,11 +33,13 @@ namespace tunnel
const i2p::crypto::AESKey& layerKey, const i2p::crypto::AESKey& ivKey);
virtual size_t GetNumTransmittedBytes () const { return 0; };
virtual std::string GetNextPeerName () const;
// implements TunnelBase
void SendTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage> msg) override;
void HandleTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage>&& tunnelMsg) override;
void EncryptTunnelMsg (std::shared_ptr<const I2NPMessage> in, std::shared_ptr<I2NPMessage> out) override;
private:
i2p::crypto::AESKey m_LayerKey, m_IVKey;
@@ -55,13 +58,15 @@ namespace tunnel
~TransitTunnelParticipant ();
size_t GetNumTransmittedBytes () const override { return m_NumTransmittedBytes; };
std::string GetNextPeerName () const override;
void HandleTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage>&& tunnelMsg) override;
void FlushTunnelDataMsgs () override;
private:
size_t m_NumTransmittedBytes;
std::vector<std::shared_ptr<i2p::I2NPMessage> > m_TunnelDataMsgs;
std::list<std::shared_ptr<i2p::I2NPMessage> > m_TunnelDataMsgs;
std::unique_ptr<TunnelTransportSender> m_Sender;
};
class TransitTunnelGateway: public TransitTunnel
@@ -72,12 +77,13 @@ namespace tunnel
const i2p::data::IdentHash& nextIdent, uint32_t nextTunnelID,
const i2p::crypto::AESKey& layerKey, const i2p::crypto::AESKey& ivKey):
TransitTunnel (receiveTunnelID, nextIdent, nextTunnelID,
layerKey, ivKey), m_Gateway(this) {};
layerKey, ivKey), m_Gateway(*this) {};
void SendTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage> msg) override;
void FlushTunnelDataMsgs () override;
size_t GetNumTransmittedBytes () const override { return m_Gateway.GetNumSentBytes (); };
std::string GetNextPeerName () const override;
private:
std::mutex m_SendMutex;
@@ -91,23 +97,68 @@ namespace tunnel
TransitTunnelEndpoint (uint32_t receiveTunnelID,
const i2p::data::IdentHash& nextIdent, uint32_t nextTunnelID,
const i2p::crypto::AESKey& layerKey, const i2p::crypto::AESKey& ivKey):
TransitTunnel (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey),
m_Endpoint (false) {}; // transit endpoint is always outbound
void Cleanup () override { m_Endpoint.Cleanup (); }
TransitTunnel (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey) {};
void Cleanup () override;
void HandleTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage>&& tunnelMsg) override;
size_t GetNumTransmittedBytes () const override { return m_Endpoint.GetNumReceivedBytes (); }
void FlushTunnelDataMsgs () override;
size_t GetNumTransmittedBytes () const override { return m_Endpoint ? m_Endpoint->GetNumReceivedBytes () : 0; }
std::string GetNextPeerName () const override;
private:
TunnelEndpoint m_Endpoint;
std::mutex m_HandleMutex;
std::unique_ptr<TunnelEndpoint> m_Endpoint;
};
std::shared_ptr<TransitTunnel> CreateTransitTunnel (uint32_t receiveTunnelID,
const i2p::data::IdentHash& nextIdent, uint32_t nextTunnelID,
const i2p::crypto::AESKey& layerKey, const i2p::crypto::AESKey& ivKey,
bool isGateway, bool isEndpoint);
const int TRANSIT_TUNNELS_QUEUE_WAIT_INTERVAL = 10; // in seconds
class TransitTunnels
{
public:
TransitTunnels ();
~TransitTunnels ();
void Start ();
void Stop ();
void PostTransitTunnelBuildMsg (std::shared_ptr<I2NPMessage>&& msg);
size_t GetNumTransitTunnels () const { return m_TransitTunnels.size (); }
int GetTransitTunnelsExpirationTimeout ();
private:
bool AddTransitTunnel (std::shared_ptr<TransitTunnel> tunnel);
void ManageTransitTunnels (uint64_t ts);
void HandleShortTransitTunnelBuildMsg (std::shared_ptr<I2NPMessage>&& msg);
void HandleVariableTransitTunnelBuildMsg (std::shared_ptr<I2NPMessage>&& msg);
bool HandleBuildRequestRecords (int num, uint8_t * records, uint8_t * clearText);
void Run ();
private:
volatile bool m_IsRunning;
std::unique_ptr<std::thread> m_Thread;
std::list<std::shared_ptr<TransitTunnel> > m_TransitTunnels;
i2p::util::Queue<std::shared_ptr<I2NPMessage> > m_TunnelBuildMsgQueue;
std::mt19937 m_Rng;
public:
// for HTTP only
const auto& GetTransitTunnels () const { return m_TransitTunnels; };
size_t GetTunnelBuildMsgQueueSize () const { return m_TunnelBuildMsgQueue.GetSize (); };
};
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2024, The PurpleI2P Project
* Copyright (c) 2013-2025, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -10,7 +10,7 @@
#define TRANSPORT_SESSION_H__
#include <inttypes.h>
#include <iostream>
#include <string.h>
#include <memory>
#include <vector>
#include <mutex>
@@ -28,45 +28,51 @@ namespace transport
const size_t IPV6_HEADER_SIZE = 40;
const size_t UDP_HEADER_SIZE = 8;
template<size_t sz>
class SignedData
{
public:
SignedData () {}
SignedData (): m_Size(0) {}
SignedData (const SignedData& other)
{
m_Stream << other.m_Stream.rdbuf ();
m_Size = other.m_Size;
memcpy (m_Buf, other.m_Buf, m_Size);
}
void Reset ()
{
m_Stream.str("");
m_Size = 0;
}
void Insert (const uint8_t * buf, size_t len)
size_t Insert (const uint8_t * buf, size_t len)
{
m_Stream.write ((char *)buf, len);
if (m_Size + len > sz) len = sz - m_Size;
memcpy (m_Buf + m_Size, buf, len);
m_Size += len;
return len;
}
template<typename T>
void Insert (T t)
{
m_Stream.write ((char *)&t, sizeof (T));
Insert ((const uint8_t *)&t, sizeof (T));
}
bool Verify (std::shared_ptr<const i2p::data::IdentityEx> ident, const uint8_t * signature) const
{
return ident->Verify ((const uint8_t *)m_Stream.str ().c_str (), m_Stream.str ().size (), signature);
return ident->Verify (m_Buf, m_Size, signature);
}
void Sign (const i2p::data::PrivateKeys& keys, uint8_t * signature) const
{
keys.Sign ((const uint8_t *)m_Stream.str ().c_str (), m_Stream.str ().size (), signature);
keys.Sign (m_Buf, m_Size, signature);
}
private:
std::stringstream m_Stream;
uint8_t m_Buf[sz];
size_t m_Size;
};
const int64_t TRANSPORT_SESSION_SLOWNESS_THRESHOLD = 500; // in milliseconds
@@ -144,9 +150,14 @@ namespace transport
void SetLastActivityTimestamp (uint64_t ts) { m_LastActivityTimestamp = ts; };
virtual uint32_t GetRelayTag () const { return 0; };
virtual void SendLocalRouterInfo (bool update = false) { SendI2NPMessages ({ CreateDatabaseStoreMsg () }); };
virtual void SendI2NPMessages (const std::vector<std::shared_ptr<I2NPMessage> >& msgs) = 0;
virtual void SendLocalRouterInfo (bool update = false)
{
std::list<std::shared_ptr<I2NPMessage> > msgs{ CreateDatabaseStoreMsg () };
SendI2NPMessages (msgs);
};
virtual void SendI2NPMessages (std::list<std::shared_ptr<I2NPMessage> >& msgs) = 0;
virtual bool IsEstablished () const = 0;
virtual i2p::data::RouterInfo::SupportedTransports GetTransportType () const = 0;
private:

View File

@@ -1,11 +1,12 @@
/*
* Copyright (c) 2013-2024, The PurpleI2P Project
* Copyright (c) 2013-2025, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
* See full license text in LICENSE file at top of project tree
*/
#include <boost/algorithm/string.hpp> // for boost::to_lower
#include "Log.h"
#include "Crypto.h"
#include "RouterContext.h"
@@ -24,7 +25,7 @@ namespace transport
{
template<typename Keys>
EphemeralKeysSupplier<Keys>::EphemeralKeysSupplier (int size):
m_QueueSize (size), m_IsRunning (false), m_Thread (nullptr)
m_QueueSize (size), m_IsRunning (false)
{
}
@@ -38,7 +39,7 @@ namespace transport
void EphemeralKeysSupplier<Keys>::Start ()
{
m_IsRunning = true;
m_Thread = new std::thread (std::bind (&EphemeralKeysSupplier<Keys>::Run, this));
m_Thread.reset (new std::thread (std::bind (&EphemeralKeysSupplier<Keys>::Run, this)));
}
template<typename Keys>
@@ -52,9 +53,15 @@ namespace transport
if (m_Thread)
{
m_Thread->join ();
delete m_Thread;
m_Thread = 0;
m_Thread = nullptr;
}
if (!m_Queue.empty ())
{
// clean up queue
std::queue<std::shared_ptr<Keys> > tmp;
std::swap (m_Queue, tmp);
}
m_KeysPool.CleanUpMt ();
}
template<typename Keys>
@@ -65,18 +72,19 @@ namespace transport
while (m_IsRunning)
{
int num, total = 0;
while ((num = m_QueueSize - (int)m_Queue.size ()) > 0 && total < 10)
while ((num = m_QueueSize - (int)m_Queue.size ()) > 0 && total < m_QueueSize)
{
CreateEphemeralKeys (num);
total += num;
}
if (total >= 10)
if (total > m_QueueSize)
{
LogPrint (eLogWarning, "Transports: ", total, " ephemeral keys generated at the time");
std::this_thread::sleep_for (std::chrono::seconds(1)); // take a break
}
else
{
m_KeysPool.CleanUpMt ();
std::unique_lock<std::mutex> l(m_AcquiredMutex);
if (!m_IsRunning) break;
m_Acquired.wait (l); // wait for element gets acquired
@@ -91,7 +99,7 @@ namespace transport
{
for (int i = 0; i < num; i++)
{
auto pair = std::make_shared<Keys> ();
auto pair = m_KeysPool.AcquireSharedMt ();
pair->GenerateKeys ();
std::unique_lock<std::mutex> l(m_AcquiredMutex);
m_Queue.push (pair);
@@ -113,7 +121,7 @@ namespace transport
}
}
// queue is empty, create new
auto pair = std::make_shared<Keys> ();
auto pair = m_KeysPool.AcquireSharedMt ();
pair->GenerateKeys ();
return pair;
}
@@ -123,25 +131,37 @@ namespace transport
{
if (pair)
{
std::unique_lock<std::mutex>l(m_AcquiredMutex);
std::unique_lock<std::mutex> l(m_AcquiredMutex);
if ((int)m_Queue.size () < 2*m_QueueSize)
m_Queue.push (pair);
}
else
LogPrint(eLogError, "Transports: Return null DHKeys");
LogPrint(eLogError, "Transports: Return null keys");
}
void Peer::UpdateParams (std::shared_ptr<const i2p::data::RouterInfo> router)
{
if (router)
{
isHighBandwidth = router->IsHighBandwidth ();
isEligible =(bool)router->GetCompatibleTransports (true) && // reachable
router->GetCongestion () != i2p::data::RouterInfo::eRejectAll && // accepts tunnel
router->IsECIES () && router->GetVersion () >= NETDB_MIN_HIGHBANDWIDTH_VERSION; // not too old
}
}
Transports transports;
Transports::Transports ():
m_IsOnline (true), m_IsRunning (false), m_IsNAT (true), m_CheckReserved(true), m_Thread (nullptr),
m_Service (nullptr), m_Work (nullptr), m_PeerCleanupTimer (nullptr), m_PeerTestTimer (nullptr),
m_UpdateBandwidthTimer (nullptr), m_SSU2Server (nullptr), m_NTCP2Server (nullptr),
m_X25519KeysPairSupplier (15), // 15 pre-generated keys
m_X25519KeysPairSupplier (NUM_X25519_PRE_GENERATED_KEYS),
m_TotalSentBytes (0), m_TotalReceivedBytes (0), m_TotalTransitTransmittedBytes (0),
m_InBandwidth (0), m_OutBandwidth (0), m_TransitBandwidth (0),
m_InBandwidth15s (0), m_OutBandwidth15s (0), m_TransitBandwidth15s (0),
m_InBandwidth5m (0), m_OutBandwidth5m (0), m_TransitBandwidth5m (0)
m_InBandwidth5m (0), m_OutBandwidth5m (0), m_TransitBandwidth5m (0),
m_Rng(i2p::util::GetMonotonicMicroseconds () % 1000000LL)
{
}
@@ -162,8 +182,8 @@ namespace transport
{
if (!m_Service)
{
m_Service = new boost::asio::io_service ();
m_Work = new boost::asio::io_service::work (*m_Service);
m_Service = new boost::asio::io_context ();
m_Work = new boost::asio::executor_work_guard<boost::asio::io_context::executor_type> (m_Service->get_executor ());
m_PeerCleanupTimer = new boost::asio::deadline_timer (*m_Service);
m_PeerTestTimer = new boost::asio::deadline_timer (*m_Service);
m_UpdateBandwidthTimer = new boost::asio::deadline_timer (*m_Service);
@@ -237,7 +257,7 @@ namespace transport
if (!address.empty ())
{
boost::system::error_code ec;
auto addr = boost::asio::ip::address::from_string (address, ec);
auto addr = boost::asio::ip::make_address (address, ec);
if (!ec)
{
if (m_NTCP2Server) m_NTCP2Server->SetLocalAddress (addr);
@@ -263,7 +283,7 @@ namespace transport
if (!address.empty ())
{
boost::system::error_code ec;
auto addr = boost::asio::ip::address::from_string (address, ec);
auto addr = boost::asio::ip::make_address (address, ec);
if (!ec)
{
if (m_NTCP2Server) m_NTCP2Server->SetLocalAddress (addr);
@@ -290,7 +310,7 @@ namespace transport
if (!address.empty ())
{
boost::system::error_code ec;
auto addr = boost::asio::ip::address::from_string (address, ec);
auto addr = boost::asio::ip::make_address (address, ec);
if (!ec && m_NTCP2Server && i2p::util::net::IsYggdrasilAddress (addr))
m_NTCP2Server->SetLocalAddress (addr);
}
@@ -319,7 +339,7 @@ namespace transport
if (m_IsNAT)
{
m_PeerTestTimer->expires_from_now (boost::posix_time::minutes(PEER_TEST_INTERVAL));
m_PeerTestTimer->expires_from_now (boost::posix_time::seconds(PEER_TEST_INTERVAL + m_Rng() % PEER_TEST_INTERVAL_VARIANCE));
m_PeerTestTimer->async_wait (std::bind (&Transports::HandlePeerTestTimer, this, std::placeholders::_1));
}
}
@@ -435,18 +455,22 @@ namespace transport
return std::max (bwCongestionLevel, tbwCongestionLevel);
}
void Transports::SendMessage (const i2p::data::IdentHash& ident, std::shared_ptr<i2p::I2NPMessage> msg)
std::future<std::shared_ptr<TransportSession> > Transports::SendMessage (const i2p::data::IdentHash& ident, std::shared_ptr<i2p::I2NPMessage> msg)
{
if (m_IsOnline)
SendMessages (ident, std::vector<std::shared_ptr<i2p::I2NPMessage> > {msg });
return SendMessages (ident, { msg });
return {}; // invalid future
}
void Transports::SendMessages (const i2p::data::IdentHash& ident, const std::vector<std::shared_ptr<i2p::I2NPMessage> >& msgs)
std::future<std::shared_ptr<TransportSession> > Transports::SendMessages (const i2p::data::IdentHash& ident, std::list<std::shared_ptr<i2p::I2NPMessage> >&& msgs)
{
m_Service->post (std::bind (&Transports::PostMessages, this, ident, msgs));
}
void Transports::PostMessages (i2p::data::IdentHash ident, std::vector<std::shared_ptr<i2p::I2NPMessage> > msgs)
return boost::asio::post (*m_Service, boost::asio::use_future ([this, ident, msgs = std::move(msgs)] () mutable
{
return PostMessages (ident, msgs);
}));
}
std::shared_ptr<TransportSession> Transports::PostMessages (const i2p::data::IdentHash& ident, std::list<std::shared_ptr<i2p::I2NPMessage> >& msgs)
{
if (ident == i2p::context.GetRouterInfo ().GetIdentHash ())
{
@@ -454,25 +478,30 @@ namespace transport
for (auto& it: msgs)
m_LoopbackHandler.PutNextMessage (std::move (it));
m_LoopbackHandler.Flush ();
return;
return nullptr;
}
if(RoutesRestricted() && !IsRestrictedPeer(ident)) return;
if(RoutesRestricted() && !IsRestrictedPeer(ident)) return nullptr;
std::shared_ptr<Peer> peer;
auto it = m_Peers.find (ident);
if (it == m_Peers.end ())
{
std::lock_guard<std::mutex> l(m_PeersMutex);
auto it = m_Peers.find (ident);
if (it != m_Peers.end ())
peer = it->second;
}
if (!peer)
{
// check if not banned
if (i2p::data::IsRouterBanned (ident)) return; // don't create peer to unreachable router
if (i2p::data::IsRouterBanned (ident)) return nullptr; // don't create peer to unreachable router
// try to connect
bool connected = false;
try
{
auto r = netdb.FindRouter (ident);
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);
if (r && (r->IsUnreachable () || !r->IsReachableFrom (i2p::context.GetRouterInfo ()))) return nullptr; // router found but non-reachable
peer = std::make_shared<Peer>(r, i2p::util::GetSecondsSinceEpoch ());
{
std::lock_guard<std::mutex> l(m_PeersMutex);
peer = m_Peers.emplace (ident, peer).first->second;
}
if (peer)
@@ -482,14 +511,16 @@ namespace transport
{
LogPrint (eLogError, "Transports: PostMessages exception:", ex.what ());
}
if (!connected) return;
if (!connected) return nullptr;
}
else
peer = it->second;
if (!peer) return;
if (!peer) return nullptr;
if (peer->IsConnected ())
peer->sessions.front ()->SendI2NPMessages (msgs);
{
auto session = peer->sessions.front ();
if (session) session->SendI2NPMessages (msgs);
return session;
}
else
{
auto sz = peer->delayedMessages.size ();
@@ -500,31 +531,44 @@ namespace transport
if (i2p::data::IsRouterBanned (ident))
{
LogPrint (eLogWarning, "Transports: Router ", ident.ToBase64 (), " is banned. Peer dropped");
std::unique_lock<std::mutex> l(m_PeersMutex);
std::lock_guard<std::mutex> l(m_PeersMutex);
m_Peers.erase (ident);
return;
return nullptr;
}
}
for (auto& it1: msgs)
if (sz > MAX_NUM_DELAYED_MESSAGES/2 && it1->onDrop)
it1->Drop (); // drop earlier because we can handle it
else
peer->delayedMessages.push_back (it1);
if (sz > MAX_NUM_DELAYED_MESSAGES/2)
{
for (auto& it1: msgs)
if (it1->onDrop)
it1->Drop (); // drop earlier because we can handle it
else
peer->delayedMessages.push_back (it1);
}
else
peer->delayedMessages.splice (peer->delayedMessages.end (), msgs);
}
else
{
LogPrint (eLogWarning, "Transports: Delayed messages queue size to ",
ident.ToBase64 (), " exceeds ", MAX_NUM_DELAYED_MESSAGES);
std::unique_lock<std::mutex> l(m_PeersMutex);
std::lock_guard<std::mutex> l(m_PeersMutex);
m_Peers.erase (ident);
}
}
return nullptr;
}
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
{
auto r = netdb.FindRouter (ident); // try to get new one from netdb
if (r)
{
peer->SetRouter (r);
r->CancelBufferToDelete ();
}
}
if (peer->router) // we have RI already
{
if (peer->priority.empty ())
@@ -587,10 +631,10 @@ namespace transport
}
LogPrint (eLogInfo, "Transports: No compatible addresses available");
if (peer->router->IsReachableFrom (i2p::context.GetRouterInfo ()))
if (!i2p::context.IsLimitedConnectivity () && 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 ();
std::unique_lock<std::mutex> l(m_PeersMutex);
std::lock_guard<std::mutex> l(m_PeersMutex);
m_Peers.erase (ident);
return false;
}
@@ -598,7 +642,7 @@ namespace transport
{
LogPrint (eLogWarning, "Transports: Router ", ident.ToBase64 (), " is banned. Peer dropped");
peer->Done ();
std::unique_lock<std::mutex> l(m_PeersMutex);
std::lock_guard<std::mutex> l(m_PeersMutex);
m_Peers.erase (ident);
return false;
}
@@ -611,7 +655,7 @@ namespace transport
return true;
}
void Transports::SetPriority (std::shared_ptr<Peer> peer) const
void Transports::SetPriority (std::shared_ptr<Peer> peer)
{
static const std::vector<i2p::data::RouterInfo::SupportedTransports>
ntcp2Priority =
@@ -637,7 +681,7 @@ namespace transport
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
bool ssu2 = isReal ? (m_Rng () & 1) : false; // try NTCP2 if router is not confirmed real
const auto& priority = ssu2 ? ssu2Priority : ntcp2Priority;
if (directTransports)
{
@@ -660,32 +704,64 @@ namespace transport
if (transport & compatibleTransports)
peer->priority.push_back (transport);
}
if (peer->priority.empty ())
{
// try recently connected SSU2 if any
auto supportedTransports = context.GetRouterInfo ().GetCompatibleTransports (false) &
peer->router->GetCompatibleTransports (false);
if ((supportedTransports & (i2p::data::RouterInfo::eSSU2V4 | i2p::data::RouterInfo::eSSU2V6)) &&
peer->router->HasProfile ())
{
auto ep = peer->router->GetProfile ()->GetLastEndpoint ();
if (!ep.address ().is_unspecified () && ep.port ())
{
if (ep.address ().is_v4 ())
{
if ((supportedTransports & i2p::data::RouterInfo::eSSU2V4) &&
m_SSU2Server->IsConnectedRecently (ep, false))
peer->priority.push_back (i2p::data::RouterInfo::eSSU2V4);
}
else if (ep.address ().is_v6 ())
{
if ((supportedTransports & i2p::data::RouterInfo::eSSU2V6) &&
m_SSU2Server->IsConnectedRecently (ep))
peer->priority.push_back (i2p::data::RouterInfo::eSSU2V6);
}
}
}
}
}
void Transports::RequestComplete (std::shared_ptr<const i2p::data::RouterInfo> r, const i2p::data::IdentHash& ident)
{
m_Service->post (std::bind (&Transports::HandleRequestComplete, this, r, ident));
boost::asio::post (*m_Service, std::bind (&Transports::HandleRequestComplete, this, r, ident));
}
void Transports::HandleRequestComplete (std::shared_ptr<const i2p::data::RouterInfo> r, i2p::data::IdentHash ident)
{
auto it = m_Peers.find (ident);
if (it != m_Peers.end ())
std::shared_ptr<Peer> peer;
{
if (r)
std::lock_guard<std::mutex> l(m_PeersMutex);
auto it = m_Peers.find (ident);
if (it != m_Peers.end ())
{
LogPrint (eLogDebug, "Transports: RouterInfo for ", ident.ToBase64 (), " found, trying to connect");
it->second->SetRouter (r);
if (!it->second->IsConnected ())
ConnectToPeer (ident, it->second);
}
else
{
LogPrint (eLogWarning, "Transports: RouterInfo not found, failed to send messages");
std::unique_lock<std::mutex> l(m_PeersMutex);
m_Peers.erase (it);
}
if (r)
peer = it->second;
else
m_Peers.erase (it);
}
}
if (peer && !peer->router && r)
{
LogPrint (eLogDebug, "Transports: RouterInfo for ", ident.ToBase64 (), " found, trying to connect");
peer->SetRouter (r);
if (!peer->IsConnected ())
ConnectToPeer (ident, peer);
}
else if (!r)
LogPrint (eLogInfo, "Transports: RouterInfo not found, failed to send messages");
}
void Transports::DetectExternalIP ()
@@ -724,7 +800,7 @@ namespace transport
}
else
{
testDelay += PEER_TEST_DELAY_INTERVAL + rand() % PEER_TEST_DELAY_INTERVAL_VARIANCE;
testDelay += PEER_TEST_DELAY_INTERVAL + m_Rng() % PEER_TEST_DELAY_INTERVAL_VARIANCE;
if (m_Service)
{
auto delayTimer = std::make_shared<boost::asio::deadline_timer>(*m_Service);
@@ -762,7 +838,7 @@ namespace transport
}
else
{
testDelay += PEER_TEST_DELAY_INTERVAL + rand() % PEER_TEST_DELAY_INTERVAL_VARIANCE;
testDelay += PEER_TEST_DELAY_INTERVAL + m_Rng() % PEER_TEST_DELAY_INTERVAL_VARIANCE;
if (m_Service)
{
auto delayTimer = std::make_shared<boost::asio::deadline_timer>(*m_Service);
@@ -795,7 +871,7 @@ namespace transport
void Transports::PeerConnected (std::shared_ptr<TransportSession> session)
{
m_Service->post([session, this]()
boost::asio::post (*m_Service, [session, this]()
{
auto remoteIdentity = session->GetRemoteIdentity ();
if (!remoteIdentity) return;
@@ -819,7 +895,11 @@ namespace transport
auto transport = peer->priority[peer->numAttempts-1];
if (transport == i2p::data::RouterInfo::eNTCP2V4 ||
transport == i2p::data::RouterInfo::eNTCP2V6 || transport == i2p::data::RouterInfo::eNTCP2V6Mesh)
peer->router->GetProfile ()->Connected (); // outgoing NTCP2 connection if always real
i2p::data::UpdateRouterProfile (ident,
[](std::shared_ptr<i2p::data::RouterProfile> profile)
{
if (profile) profile->Connected (); // outgoing NTCP2 connection if always real
});
i2p::data::netdb.SetUnreachable (ident, false); // clear unreachable
}
peer->numAttempts = 0;
@@ -828,7 +908,7 @@ namespace transport
if (it->second->delayedMessages.size () > 0)
{
// check if first message is our DatabaseStore (publishing)
auto firstMsg = peer->delayedMessages[0];
auto firstMsg = peer->delayedMessages.front ();
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
@@ -838,8 +918,7 @@ namespace transport
else
session->SetTerminationTimeout (10); // most likely it's publishing, no follow-up messages expected, set timeout to 10 seconds
peer->sessions.push_back (session);
session->SendI2NPMessages (peer->delayedMessages);
peer->delayedMessages.clear ();
session->SendI2NPMessages (peer->delayedMessages); // send and clear
}
else // incoming connection or peer test
{
@@ -850,14 +929,21 @@ namespace transport
return;
}
if (!session->IsOutgoing ()) // incoming
session->SendI2NPMessages ({ CreateDatabaseStoreMsg () }); // send DatabaseStore
{
std::list<std::shared_ptr<I2NPMessage> > msgs{ CreateDatabaseStoreMsg () };
session->SendI2NPMessages (msgs); // send DatabaseStore
}
auto r = i2p::data::netdb.FindRouter (ident); // router should be in netdb after SessionConfirmed
if (r) r->GetProfile ()->Connected ();
i2p::data::UpdateRouterProfile (ident,
[](std::shared_ptr<i2p::data::RouterProfile> profile)
{
if (profile) profile->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);
std::lock_guard<std::mutex> l(m_PeersMutex);
m_Peers.emplace (ident, peer);
}
});
@@ -865,7 +951,7 @@ namespace transport
void Transports::PeerDisconnected (std::shared_ptr<TransportSession> session)
{
m_Service->post([session, this]()
boost::asio::post (*m_Service, [session, this]()
{
auto remoteIdentity = session->GetRemoteIdentity ();
if (!remoteIdentity) return;
@@ -886,8 +972,13 @@ namespace transport
}
else
{
std::unique_lock<std::mutex> l(m_PeersMutex);
m_Peers.erase (it);
{
std::lock_guard<std::mutex> l(m_PeersMutex);
m_Peers.erase (it);
}
// delete buffer of just disconnected router
auto r = i2p::data::netdb.FindRouter (ident);
if (r && !r->IsUpdated ()) r->ScheduleBufferToDelete ();
}
}
}
@@ -896,9 +987,13 @@ namespace transport
bool Transports::IsConnected (const i2p::data::IdentHash& ident) const
{
std::unique_lock<std::mutex> l(m_PeersMutex);
std::lock_guard<std::mutex> l(m_PeersMutex);
#if __cplusplus >= 202002L // C++20
return m_Peers.contains (ident);
#else
auto it = m_Peers.find (ident);
return it != m_Peers.end ();
#endif
}
void Transports::HandlePeerCleanupTimer (const boost::system::error_code& ecode)
@@ -922,7 +1017,7 @@ namespace transport
auto profile = i2p::data::GetRouterProfile (it->first);
if (profile) profile->Unreachable ();
} */
std::unique_lock<std::mutex> l(m_PeersMutex);
std::lock_guard<std::mutex> l(m_PeersMutex);
it = m_Peers.erase (it);
}
else
@@ -933,7 +1028,7 @@ namespace transport
if (session)
session->SendLocalRouterInfo (true);
it->second->nextRouterInfoUpdateTime = ts + PEER_ROUTER_INFO_UPDATE_INTERVAL +
rand () % PEER_ROUTER_INFO_UPDATE_INTERVAL_VARIANCE;
m_Rng() % PEER_ROUTER_INFO_UPDATE_INTERVAL_VARIANCE;
}
++it;
}
@@ -947,7 +1042,7 @@ namespace transport
// if still testing or unknown, repeat peer test
if (ipv4Testing || ipv6Testing)
PeerTest (ipv4Testing, ipv6Testing);
m_PeerCleanupTimer->expires_from_now (boost::posix_time::seconds(3 * SESSION_CREATION_TIMEOUT));
m_PeerCleanupTimer->expires_from_now (boost::posix_time::seconds(2 * SESSION_CREATION_TIMEOUT + m_Rng() % SESSION_CREATION_TIMEOUT));
m_PeerCleanupTimer->async_wait (std::bind (&Transports::HandlePeerCleanupTimer, this, std::placeholders::_1));
}
}
@@ -957,7 +1052,7 @@ namespace transport
if (ecode != boost::asio::error::operation_aborted)
{
PeerTest ();
m_PeerTestTimer->expires_from_now (boost::posix_time::minutes(PEER_TEST_INTERVAL));
m_PeerTestTimer->expires_from_now (boost::posix_time::seconds(PEER_TEST_INTERVAL + m_Rng() % PEER_TEST_INTERVAL_VARIANCE));
m_PeerTestTimer->async_wait (std::bind (&Transports::HandlePeerTestTimer, this, std::placeholders::_1));
}
}
@@ -966,12 +1061,13 @@ namespace transport
std::shared_ptr<const i2p::data::RouterInfo> Transports::GetRandomPeer (Filter filter) const
{
if (m_Peers.empty()) return nullptr;
auto ts = i2p::util::GetSecondsSinceEpoch ();
bool found = false;
i2p::data::IdentHash ident;
{
uint16_t inds[3];
RAND_bytes ((uint8_t *)inds, sizeof (inds));
std::unique_lock<std::mutex> l(m_PeersMutex);
std::lock_guard<std::mutex> l(m_PeersMutex);
auto count = m_Peers.size ();
if(count == 0) return nullptr;
inds[0] %= count;
@@ -1006,9 +1102,11 @@ namespace transport
it = it1;
while (it != it2 && it != m_Peers.end ())
{
if (filter (it->second))
if (ts > it->second->lastSelectionTime + PEER_SELECTION_MIN_INTERVAL &&
filter (it->second))
{
ident = it->first;
it->second->lastSelectionTime = ts;
found = true;
break;
}
@@ -1020,9 +1118,11 @@ namespace transport
it = m_Peers.begin ();
while (it != it1 && it != m_Peers.end ())
{
if (filter (it->second))
if (ts > it->second->lastSelectionTime + PEER_SELECTION_MIN_INTERVAL &&
filter (it->second))
{
ident = it->first;
it->second->lastSelectionTime = ts;
found = true;
break;
}
@@ -1034,9 +1134,11 @@ namespace transport
it = it2;
while (it != m_Peers.end ())
{
if (filter (it->second))
if (ts > it->second->lastSelectionTime + PEER_SELECTION_MIN_INTERVAL &&
filter (it->second))
{
ident = it->first;
it->second->lastSelectionTime = ts;
found = true;
break;
}
@@ -1055,7 +1157,7 @@ namespace transport
[isHighBandwidth](std::shared_ptr<const Peer> peer)->bool
{
// connected, not overloaded and not slow
return !peer->router && peer->IsConnected () && peer->isReachable &&
return !peer->router && peer->IsConnected () && peer->isEligible &&
peer->sessions.front ()->GetSendQueueSize () <= PEER_ROUTER_INFO_OVERLOAD_QUEUE_SIZE &&
!peer->sessions.front ()->IsSlow () && !peer->sessions.front ()->IsBandwidthExceeded (peer->isHighBandwidth) &&
(!isHighBandwidth || peer->isHighBandwidth);
@@ -1097,7 +1199,7 @@ namespace transport
}
/** XXX: if routes are not restricted this dies */
std::shared_ptr<const i2p::data::RouterInfo> Transports::GetRestrictedPeer() const
std::shared_ptr<const i2p::data::RouterInfo> Transports::GetRestrictedPeer()
{
{
std::lock_guard<std::mutex> l(m_FamilyMutex);
@@ -1106,7 +1208,7 @@ namespace transport
if(sz > 1)
{
auto it = m_TrustedFamilies.begin ();
std::advance(it, rand() % sz);
std::advance(it, m_Rng() % sz);
fam = *it;
}
else if (sz == 1)
@@ -1124,7 +1226,7 @@ namespace transport
if(sz == 1)
return i2p::data::netdb.FindRouter(m_TrustedRouters[0]);
auto it = m_TrustedRouters.begin();
std::advance(it, rand() % sz);
std::advance(it, m_Rng() % sz);
return i2p::data::netdb.FindRouter(*it);
}
}
@@ -1202,7 +1304,7 @@ namespace transport
std::string yggaddress; i2p::config::GetOption ("meshnets.yggaddress", yggaddress);
if (!yggaddress.empty ())
{
yggaddr = boost::asio::ip::address_v6::from_string (yggaddress);
yggaddr = boost::asio::ip::make_address (yggaddress).to_v6 ();
if (yggaddr.is_unspecified () || !i2p::util::net::IsYggdrasilAddress (yggaddr) ||
!i2p::util::net::IsLocalAddress (yggaddr))
{
@@ -1247,7 +1349,7 @@ namespace transport
if (ipv6)
{
std::string ipv6Addr; i2p::config::GetOption("ntcp2.addressv6", ipv6Addr);
auto addr = boost::asio::ip::address_v6::from_string (ipv6Addr);
auto addr = boost::asio::ip::make_address (ipv6Addr).to_v6 ();
if (!addr.is_unspecified () && addr != boost::asio::ip::address_v6::any ())
i2p::context.UpdateNTCP2V6Address (addr); // set ipv6 address if configured
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2024, The PurpleI2P Project
* Copyright (c) 2013-2025, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -11,6 +11,7 @@
#include <thread>
#include <mutex>
#include <future>
#include <condition_variable>
#include <functional>
#include <unordered_map>
@@ -19,6 +20,7 @@
#include <string>
#include <memory>
#include <atomic>
#include <random>
#include <boost/asio.hpp>
#include "TransportSession.h"
#include "SSU2.h"
@@ -26,6 +28,7 @@
#include "RouterInfo.h"
#include "I2NPProtocol.h"
#include "Identity.h"
#include "util.h"
namespace i2p
{
@@ -52,10 +55,11 @@ namespace transport
private:
const int m_QueueSize;
i2p::util::MemoryPoolMt<Keys> m_KeysPool;
std::queue<std::shared_ptr<Keys> > m_Queue;
bool m_IsRunning;
std::thread * m_Thread;
std::unique_ptr<std::thread> m_Thread;
std::condition_variable m_Acquired;
std::mutex m_AcquiredMutex;
};
@@ -64,26 +68,23 @@ namespace transport
const int PEER_ROUTER_INFO_UPDATE_INTERVAL = 31*60; // in seconds
const int PEER_ROUTER_INFO_UPDATE_INTERVAL_VARIANCE = 7*60; // in seconds
const size_t PEER_ROUTER_INFO_OVERLOAD_QUEUE_SIZE = 25;
const int PEER_SELECTION_MIN_INTERVAL = 20; // in seconds
struct Peer
{
int numAttempts;
std::shared_ptr<const i2p::data::RouterInfo> router;
std::list<std::shared_ptr<TransportSession> > sessions;
uint64_t creationTime, nextRouterInfoUpdateTime;
std::vector<std::shared_ptr<i2p::I2NPMessage> > delayedMessages;
uint64_t creationTime, nextRouterInfoUpdateTime, lastSelectionTime;
std::list<std::shared_ptr<i2p::I2NPMessage> > delayedMessages;
std::vector<i2p::data::RouterInfo::SupportedTransports> priority;
bool isHighBandwidth, isReachable;
bool isHighBandwidth, isEligible;
Peer (std::shared_ptr<const i2p::data::RouterInfo> r, uint64_t ts):
numAttempts (0), router (r), creationTime (ts),
nextRouterInfoUpdateTime (ts + PEER_ROUTER_INFO_UPDATE_INTERVAL),
isHighBandwidth (false), isReachable (false)
lastSelectionTime (0), isHighBandwidth (false), isEligible (false)
{
if (router)
{
isHighBandwidth = router->IsHighBandwidth ();
isReachable = (bool)router->GetCompatibleTransports (true);
}
UpdateParams (router);
}
void Done ()
@@ -98,23 +99,22 @@ namespace transport
void SetRouter (std::shared_ptr<const i2p::data::RouterInfo> r)
{
router = r;
if (router)
{
isHighBandwidth = router->IsHighBandwidth ();
isReachable = (bool)router->GetCompatibleTransports (true);
}
UpdateParams (router);
}
bool IsConnected () const { return !sessions.empty (); }
bool IsConnected () const { return !sessions.empty (); }
void UpdateParams (std::shared_ptr<const i2p::data::RouterInfo> router);
};
const uint64_t SESSION_CREATION_TIMEOUT = 15; // in seconds
const int PEER_TEST_INTERVAL = 71; // in minutes
const int PEER_TEST_INTERVAL = 68*60; // in seconds
const int PEER_TEST_INTERVAL_VARIANCE = 3*60; // in seconds
const int PEER_TEST_DELAY_INTERVAL = 20; // in milliseconds
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 NUM_X25519_PRE_GENERATED_KEYS = 25; // pre-generated x25519 keys pairs
const int TRAFFIC_SAMPLE_COUNT = 301; // seconds
struct TrafficSample
@@ -142,12 +142,12 @@ namespace transport
bool IsOnline() const { return m_IsOnline; };
void SetOnline (bool online);
boost::asio::io_service& GetService () { return *m_Service; };
auto& GetService () { return *m_Service; };
std::shared_ptr<i2p::crypto::X25519Keys> GetNextX25519KeysPair ();
void ReuseX25519KeysPair (std::shared_ptr<i2p::crypto::X25519Keys> pair);
void SendMessage (const i2p::data::IdentHash& ident, std::shared_ptr<i2p::I2NPMessage> msg);
void SendMessages (const i2p::data::IdentHash& ident, const std::vector<std::shared_ptr<i2p::I2NPMessage> >& msgs);
std::future<std::shared_ptr<TransportSession> > SendMessage (const i2p::data::IdentHash& ident, std::shared_ptr<i2p::I2NPMessage> msg);
std::future<std::shared_ptr<TransportSession> > SendMessages (const i2p::data::IdentHash& ident, std::list<std::shared_ptr<i2p::I2NPMessage> >&& msgs);
void PeerConnected (std::shared_ptr<TransportSession> session);
void PeerDisconnected (std::shared_ptr<TransportSession> session);
@@ -170,7 +170,7 @@ namespace transport
std::shared_ptr<const i2p::data::RouterInfo> GetRandomPeer (bool isHighBandwidth) const;
/** get a trusted first hop for restricted routes */
std::shared_ptr<const i2p::data::RouterInfo> GetRestrictedPeer() const;
std::shared_ptr<const i2p::data::RouterInfo> GetRestrictedPeer();
/** do we want to use restricted routes? */
bool RoutesRestricted() const;
/** restrict routes to use only these router families for first hops */
@@ -191,9 +191,9 @@ namespace transport
void Run ();
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);
std::shared_ptr<TransportSession> PostMessages (const i2p::data::IdentHash& ident, std::list<std::shared_ptr<i2p::I2NPMessage> >& msgs);
bool ConnectToPeer (const i2p::data::IdentHash& ident, std::shared_ptr<Peer> peer);
void SetPriority (std::shared_ptr<Peer> peer) const;
void SetPriority (std::shared_ptr<Peer> peer);
void HandlePeerCleanupTimer (const boost::system::error_code& ecode);
void HandlePeerTestTimer (const boost::system::error_code& ecode);
void HandleUpdateBandwidthTimer (const boost::system::error_code& ecode);
@@ -209,8 +209,8 @@ namespace transport
volatile bool m_IsOnline;
bool m_IsRunning, m_IsNAT, m_CheckReserved;
std::thread * m_Thread;
boost::asio::io_service * m_Service;
boost::asio::io_service::work * m_Work;
boost::asio::io_context * m_Service;
boost::asio::executor_work_guard<boost::asio::io_context::executor_type> * m_Work;
boost::asio::deadline_timer * m_PeerCleanupTimer, * m_PeerTestTimer, * m_UpdateBandwidthTimer;
SSU2Server * m_SSU2Server;
@@ -241,6 +241,7 @@ namespace transport
mutable std::mutex m_TrustedRoutersMutex;
i2p::I2NPMessagesHandler m_LoopbackHandler;
std::mt19937 m_Rng;
public:

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2024, The PurpleI2P Project
* Copyright (c) 2013-2025, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -103,7 +103,7 @@ namespace tunnel
if (m_Config->IsShort ())
{
auto ident = m_Config->GetFirstHop () ? m_Config->GetFirstHop ()->ident : nullptr;
if (ident && ident->GetIdentHash () != outboundTunnel->GetNextIdentHash ()) // don't encrypt if IBGW = OBEP
if (ident && ident->GetIdentHash () != outboundTunnel->GetEndpointIdentHash ()) // don't encrypt if IBGW = OBEP
{
auto msg1 = i2p::garlic::WrapECIESX25519MessageForRouter (msg, ident->GetEncryptionPublicKey ());
if (msg1) msg = msg1;
@@ -130,8 +130,19 @@ namespace tunnel
bool Tunnel::HandleTunnelBuildResponse (uint8_t * msg, size_t len)
{
LogPrint (eLogDebug, "Tunnel: TunnelBuildResponse ", (int)msg[0], " records.");
int num = msg[0];
LogPrint (eLogDebug, "Tunnel: TunnelBuildResponse ", num, " records.");
if (num > MAX_NUM_RECORDS)
{
LogPrint (eLogError, "Tunnel: Too many records in TunnelBuildResponse", num);
return false;
}
if (len < num*m_Config->GetRecordSize () + 1)
{
LogPrint (eLogError, "Tunnel: TunnelBuildResponse of ", num, " records is too short ", len);
return false;
}
TunnelHopConfig * hop = m_Config->GetLastHop ();
while (hop)
{
@@ -152,7 +163,7 @@ namespace tunnel
while (hop1)
{
auto idx = hop1->recordIndex;
if (idx >= 0 && idx < msg[0])
if (idx >= 0 && idx < num)
hop->DecryptRecord (msg + 1, idx);
else
LogPrint (eLogWarning, "Tunnel: Hop index ", idx, " is out of range");
@@ -168,9 +179,12 @@ namespace tunnel
{
uint8_t ret = hop->GetRetCode (msg + 1);
LogPrint (eLogDebug, "Tunnel: Build response ret code=", (int)ret);
auto profile = i2p::data::netdb.FindRouterProfile (hop->ident->GetIdentHash ());
if (profile)
profile->TunnelBuildResponse (ret);
if (hop->ident)
i2p::data::UpdateRouterProfile (hop->ident->GetIdentHash (),
[ret](std::shared_ptr<i2p::data::RouterProfile> profile)
{
if (profile) profile->TunnelBuildResponse (ret);
});
if (ret)
// if any of participants declined the tunnel is not established
established = false;
@@ -250,12 +264,38 @@ namespace tunnel
void InboundTunnel::HandleTunnelDataMsg (std::shared_ptr<I2NPMessage>&& msg)
{
if (GetState () != eTunnelStateExpiring) SetState (eTunnelStateEstablished); // incoming messages means a tunnel is alive
if (!IsEstablished () && GetState () != eTunnelStateExpiring)
{
// incoming messages means a tunnel is alive
SetState (eTunnelStateEstablished);
auto pool = GetTunnelPool ();
if (pool)
{
// update LeaseSet
auto dest = pool->GetLocalDestination ();
if (dest) dest->SetLeaseSetUpdated (true);
}
}
EncryptTunnelMsg (msg, msg);
msg->from = GetSharedFromThis ();
m_Endpoint.HandleDecryptedTunnelDataMsg (msg);
}
bool InboundTunnel::Recreate ()
{
if (!IsRecreated ())
{
auto pool = GetTunnelPool ();
if (pool)
{
SetRecreated (true);
pool->RecreateInboundTunnel (std::static_pointer_cast<InboundTunnel>(shared_from_this ()));
return true;
}
}
return false;
}
ZeroHopsInboundTunnel::ZeroHopsInboundTunnel ():
InboundTunnel (std::make_shared<ZeroHopsTunnelConfig> ()),
m_NumReceivedBytes (0)
@@ -275,22 +315,28 @@ namespace tunnel
void OutboundTunnel::SendTunnelDataMsgTo (const uint8_t * gwHash, uint32_t gwTunnel, std::shared_ptr<i2p::I2NPMessage> msg)
{
TunnelMessageBlock block;
block.tunnelID = 0; // Initialize tunnelID to a default value
if (gwHash)
{
block.hash = gwHash;
if (gwTunnel)
{
block.deliveryType = eDeliveryTypeTunnel;
block.tunnelID = gwTunnel;
block.tunnelID = gwTunnel; // Set tunnelID only if gwTunnel is non-zero
}
else
{
block.deliveryType = eDeliveryTypeRouter;
}
}
else
{
block.deliveryType = eDeliveryTypeLocal;
}
block.data = msg;
SendTunnelDataMsgs ({block});
SendTunnelDataMsgs({block});
}
void OutboundTunnel::SendTunnelDataMsgs (const std::vector<TunnelMessageBlock>& msgs)
@@ -306,6 +352,21 @@ namespace tunnel
LogPrint (eLogError, "Tunnel: Incoming message for outbound tunnel ", GetTunnelID ());
}
bool OutboundTunnel::Recreate ()
{
if (!IsRecreated ())
{
auto pool = GetTunnelPool ();
if (pool)
{
SetRecreated (true);
pool->RecreateOutboundTunnel (std::static_pointer_cast<OutboundTunnel>(shared_from_this ()));
return true;
}
}
return false;
}
ZeroHopsOutboundTunnel::ZeroHopsOutboundTunnel ():
OutboundTunnel (std::make_shared<ZeroHopsTunnelConfig> ()),
m_NumSentBytes (0)
@@ -339,7 +400,8 @@ namespace tunnel
Tunnels::Tunnels (): m_IsRunning (false), m_Thread (nullptr), m_MaxNumTransitTunnels (DEFAULT_MAX_NUM_TRANSIT_TUNNELS),
m_TotalNumSuccesiveTunnelCreations (0), m_TotalNumFailedTunnelCreations (0), // for normal average
m_TunnelCreationSuccessRate (TCSR_START_VALUE), m_TunnelCreationAttemptsNum(0)
m_TunnelCreationSuccessRate (TCSR_START_VALUE), m_TunnelCreationAttemptsNum(0),
m_Rng(i2p::util::GetMonotonicMicroseconds ()%1000000LL)
{
}
@@ -350,12 +412,26 @@ namespace tunnel
std::shared_ptr<TunnelBase> Tunnels::GetTunnel (uint32_t tunnelID)
{
std::lock_guard<std::mutex> l(m_TunnelsMutex);
auto it = m_Tunnels.find(tunnelID);
if (it != m_Tunnels.end ())
return it->second;
return nullptr;
}
bool Tunnels::AddTunnel (std::shared_ptr<TunnelBase> tunnel)
{
if (!tunnel) return false;
std::lock_guard<std::mutex> l(m_TunnelsMutex);
return m_Tunnels.emplace (tunnel->GetTunnelID (), tunnel).second;
}
void Tunnels::RemoveTunnel (uint32_t tunnelID)
{
std::lock_guard<std::mutex> l(m_TunnelsMutex);
m_Tunnels.erase (tunnelID);
}
std::shared_ptr<InboundTunnel> Tunnels::GetPendingInboundTunnel (uint32_t replyMsgID)
{
return GetPendingTunnel (replyMsgID, m_PendingInboundTunnels);
@@ -397,7 +473,7 @@ namespace tunnel
std::shared_ptr<OutboundTunnel> Tunnels::GetNextOutboundTunnel ()
{
if (m_OutboundTunnels.empty ()) return nullptr;
uint32_t ind = rand () % m_OutboundTunnels.size (), i = 0;
uint32_t ind = m_Rng () % m_OutboundTunnels.size (), i = 0;
std::shared_ptr<OutboundTunnel> tunnel;
for (const auto& it: m_OutboundTunnels)
{
@@ -411,10 +487,12 @@ namespace tunnel
return tunnel;
}
std::shared_ptr<TunnelPool> Tunnels::CreateTunnelPool (int numInboundHops, int numOutboundHops,
int numInboundTunnels, int numOutboundTunnels, int inboundVariance, int outboundVariance)
std::shared_ptr<TunnelPool> Tunnels::CreateTunnelPool (int numInboundHops,
int numOutboundHops, int numInboundTunnels, int numOutboundTunnels,
int inboundVariance, int outboundVariance, bool isHighBandwidth)
{
auto pool = std::make_shared<TunnelPool> (numInboundHops, numOutboundHops, numInboundTunnels, numOutboundTunnels, inboundVariance, outboundVariance);
auto pool = std::make_shared<TunnelPool> (numInboundHops, numOutboundHops,
numInboundTunnels, numOutboundTunnels, inboundVariance, outboundVariance, isHighBandwidth);
std::unique_lock<std::mutex> l(m_PoolsMutex);
m_Pools.push_back (pool);
return pool;
@@ -441,26 +519,16 @@ namespace tunnel
}
}
bool Tunnels::AddTransitTunnel (std::shared_ptr<TransitTunnel> tunnel)
{
if (m_Tunnels.emplace (tunnel->GetTunnelID (), tunnel).second)
m_TransitTunnels.push_back (tunnel);
else
{
LogPrint (eLogError, "Tunnel: Tunnel with id ", tunnel->GetTunnelID (), " already exists");
return false;
}
return true;
}
void Tunnels::Start ()
{
m_IsRunning = true;
m_Thread = new std::thread (std::bind (&Tunnels::Run, this));
m_TransitTunnels.Start ();
}
void Tunnels::Stop ()
{
m_TransitTunnels.Stop ();
m_IsRunning = false;
m_Queue.WakeUp ();
if (m_Thread)
@@ -477,18 +545,21 @@ namespace tunnel
std::this_thread::sleep_for (std::chrono::seconds(1)); // wait for other parts are ready
uint64_t lastTs = 0, lastPoolsTs = 0, lastMemoryPoolTs = 0;
std::list<std::shared_ptr<I2NPMessage> > msgs;
while (m_IsRunning)
{
try
{
auto msg = m_Queue.GetNextWithTimeout (1000); // 1 sec
if (msg)
if (m_Queue.Wait (1,0)) // 1 sec
{
m_Queue.GetWholeQueue (msgs);
int numMsgs = 0;
uint32_t prevTunnelID = 0, tunnelID = 0;
std::shared_ptr<TunnelBase> prevTunnel;
do
while (!msgs.empty ())
{
auto msg = msgs.front (); msgs.pop_front ();
if (!msg) continue;
std::shared_ptr<TunnelBase> tunnel;
uint8_t typeID = msg->GetTypeID ();
switch (typeID)
@@ -516,29 +587,38 @@ namespace tunnel
break;
}
case eI2NPVariableTunnelBuild:
case eI2NPVariableTunnelBuildReply:
case eI2NPShortTunnelBuild:
HandleShortTunnelBuildMsg (msg);
break;
case eI2NPVariableTunnelBuild:
HandleVariableTunnelBuildMsg (msg);
break;
case eI2NPShortTunnelBuildReply:
HandleTunnelBuildReplyMsg (msg, true);
break;
case eI2NPVariableTunnelBuildReply:
HandleTunnelBuildReplyMsg (msg, false);
break;
case eI2NPTunnelBuild:
case eI2NPTunnelBuildReply:
HandleTunnelBuildI2NPMessage (msg);
break;
LogPrint (eLogWarning, "Tunnel: TunnelBuild is too old for ECIES router");
break;
default:
LogPrint (eLogWarning, "Tunnel: Unexpected message type ", (int) typeID);
}
msg = (numMsgs <= MAX_TUNNEL_MSGS_BATCH_SIZE) ? m_Queue.Get () : nullptr;
if (msg)
{
prevTunnelID = tunnelID;
prevTunnel = tunnel;
numMsgs++;
}
else if (tunnel)
tunnel->FlushTunnelDataMsgs ();
prevTunnelID = tunnelID;
prevTunnel = tunnel;
numMsgs++;
if (msgs.empty ())
{
if (numMsgs < MAX_TUNNEL_MSGS_BATCH_SIZE && !m_Queue.IsEmpty ())
m_Queue.GetWholeQueue (msgs); // try more
else if (tunnel)
tunnel->FlushTunnelDataMsgs (); // otherwise flush last
}
}
while (msg);
}
if (i2p::transport::transports.IsOnline())
@@ -595,12 +675,92 @@ namespace tunnel
tunnel->SendTunnelDataMsg (msg);
}
void Tunnels::HandleShortTunnelBuildMsg (std::shared_ptr<I2NPMessage> msg)
{
if (!msg) return;
auto tunnel = GetPendingInboundTunnel (msg->GetMsgID()); // replyMsgID
if (tunnel)
{
// endpoint of inbound tunnel
LogPrint (eLogDebug, "Tunnel: ShortTunnelBuild reply for tunnel ", tunnel->GetTunnelID ());
if (tunnel->HandleTunnelBuildResponse (msg->GetPayload(), msg->GetPayloadLength()))
{
LogPrint (eLogInfo, "Tunnel: Inbound tunnel ", tunnel->GetTunnelID (), " has been created");
tunnel->SetState (eTunnelStateEstablished);
AddInboundTunnel (tunnel);
}
else
{
LogPrint (eLogInfo, "Tunnel: Inbound tunnel ", tunnel->GetTunnelID (), " has been declined");
tunnel->SetState (eTunnelStateBuildFailed);
}
return;
}
else
m_TransitTunnels.PostTransitTunnelBuildMsg (std::move (msg));
}
void Tunnels::HandleVariableTunnelBuildMsg (std::shared_ptr<I2NPMessage> msg)
{
auto tunnel = GetPendingInboundTunnel (msg->GetMsgID()); // replyMsgID
if (tunnel)
{
// endpoint of inbound tunnel
LogPrint (eLogDebug, "Tunnel: VariableTunnelBuild reply for tunnel ", tunnel->GetTunnelID ());
if (tunnel->HandleTunnelBuildResponse (msg->GetPayload(), msg->GetPayloadLength()))
{
LogPrint (eLogInfo, "Tunnel: Inbound tunnel ", tunnel->GetTunnelID (), " has been created");
tunnel->SetState (eTunnelStateEstablished);
AddInboundTunnel (tunnel);
}
else
{
LogPrint (eLogInfo, "Tunnel: Inbound tunnel ", tunnel->GetTunnelID (), " has been declined");
tunnel->SetState (eTunnelStateBuildFailed);
}
}
else
m_TransitTunnels.PostTransitTunnelBuildMsg (std::move (msg));
}
void Tunnels::HandleTunnelBuildReplyMsg (std::shared_ptr<I2NPMessage> msg, bool isShort)
{
auto tunnel = GetPendingOutboundTunnel (msg->GetMsgID()); // replyMsgID
if (tunnel)
{
// reply for outbound tunnel
LogPrint (eLogDebug, "Tunnel: TunnelBuildReply for tunnel ", tunnel->GetTunnelID ());
if (tunnel->HandleTunnelBuildResponse (msg->GetPayload(), msg->GetPayloadLength()))
{
LogPrint (eLogInfo, "Tunnel: Outbound tunnel ", tunnel->GetTunnelID (), " has been created");
tunnel->SetState (eTunnelStateEstablished);
AddOutboundTunnel (tunnel);
}
else
{
LogPrint (eLogInfo, "Tunnel: Outbound tunnel ", tunnel->GetTunnelID (), " has been declined");
tunnel->SetState (eTunnelStateBuildFailed);
}
}
else
LogPrint (eLogWarning, "Tunnel: Pending tunnel for message ", msg->GetMsgID(), " not found");
}
void Tunnels::ManageTunnels (uint64_t ts)
{
ManagePendingTunnels (ts);
ManageInboundTunnels (ts);
ManageOutboundTunnels (ts);
ManageTransitTunnels (ts);
std::vector<std::shared_ptr<Tunnel> > tunnelsToRecreate;
ManageInboundTunnels (ts, tunnelsToRecreate);
ManageOutboundTunnels (ts, tunnelsToRecreate);
// rec-create in random order
if (!tunnelsToRecreate.empty ())
{
if (tunnelsToRecreate.size () > 1)
std::shuffle (tunnelsToRecreate.begin(), tunnelsToRecreate.end(), m_Rng);
for (auto& it: tunnelsToRecreate)
it->Recreate ();
}
}
void Tunnels::ManagePendingTunnels (uint64_t ts)
@@ -631,11 +791,11 @@ namespace tunnel
while (hop)
{
if (hop->ident)
{
auto profile = i2p::data::netdb.FindRouterProfile (hop->ident->GetIdentHash ());
if (profile)
profile->TunnelNonReplied ();
}
i2p::data::UpdateRouterProfile (hop->ident->GetIdentHash (),
[](std::shared_ptr<i2p::data::RouterProfile> profile)
{
if (profile) profile->TunnelNonReplied ();
});
hop = hop->next;
}
}
@@ -663,7 +823,7 @@ namespace tunnel
}
}
void Tunnels::ManageOutboundTunnels (uint64_t ts)
void Tunnels::ManageOutboundTunnels (uint64_t ts, std::vector<std::shared_ptr<Tunnel> >& toRecreate)
{
for (auto it = m_OutboundTunnels.begin (); it != m_OutboundTunnels.end ();)
{
@@ -687,10 +847,7 @@ namespace tunnel
auto pool = tunnel->GetTunnelPool ();
// let it die if the tunnel pool has been reconfigured and this is old
if (pool && tunnel->GetNumHops() == pool->GetNumOutboundHops())
{
tunnel->SetRecreated (true);
pool->RecreateOutboundTunnel (tunnel);
}
toRecreate.push_back (tunnel);
}
if (ts + TUNNEL_EXPIRATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT)
tunnel->SetState (eTunnelStateExpiring);
@@ -705,7 +862,7 @@ namespace tunnel
auto inboundTunnel = GetNextInboundTunnel ();
auto router = i2p::transport::transports.RoutesRestricted() ?
i2p::transport::transports.GetRestrictedPeer() :
i2p::data::netdb.GetRandomRouter (i2p::context.GetSharedRouterInfo (), false, true); // reachable by us
i2p::data::netdb.GetRandomRouter (i2p::context.GetSharedRouterInfo (), false, true, false); // reachable by us
if (!inboundTunnel || !router) return;
LogPrint (eLogDebug, "Tunnel: Creating one hop outbound tunnel");
CreateTunnel<OutboundTunnel> (
@@ -715,7 +872,7 @@ namespace tunnel
}
}
void Tunnels::ManageInboundTunnels (uint64_t ts)
void Tunnels::ManageInboundTunnels (uint64_t ts, std::vector<std::shared_ptr<Tunnel> >& toRecreate)
{
for (auto it = m_InboundTunnels.begin (); it != m_InboundTunnels.end ();)
{
@@ -727,7 +884,7 @@ namespace tunnel
auto pool = tunnel->GetTunnelPool ();
if (pool)
pool->TunnelExpired (tunnel);
m_Tunnels.erase (tunnel->GetTunnelID ());
RemoveTunnel (tunnel->GetTunnelID ());
it = m_InboundTunnels.erase (it);
}
else
@@ -739,10 +896,7 @@ namespace tunnel
auto pool = tunnel->GetTunnelPool ();
// let it die if the tunnel pool was reconfigured and has different number of hops
if (pool && tunnel->GetNumHops() == pool->GetNumInboundHops())
{
tunnel->SetRecreated (true);
pool->RecreateInboundTunnel (tunnel);
}
toRecreate.push_back (tunnel);
}
if (ts + TUNNEL_EXPIRATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT)
@@ -765,7 +919,7 @@ namespace tunnel
int obLen; i2p::config::GetOption("exploratory.outbound.length", obLen);
int ibNum; i2p::config::GetOption("exploratory.inbound.quantity", ibNum);
int obNum; i2p::config::GetOption("exploratory.outbound.quantity", obNum);
m_ExploratoryPool = CreateTunnelPool (ibLen, obLen, ibNum, obNum, 0, 0);
m_ExploratoryPool = CreateTunnelPool (ibLen, obLen, ibNum, obNum, 0, 0, false);
m_ExploratoryPool->SetLocalDestination (i2p::context.GetSharedDestination ());
}
return;
@@ -777,7 +931,7 @@ namespace tunnel
auto router = i2p::transport::transports.RoutesRestricted() ?
i2p::transport::transports.GetRestrictedPeer() :
// should be reachable by us because we send build request directly
i2p::data::netdb.GetRandomRouter (i2p::context.GetSharedRouterInfo (), false, true);
i2p::data::netdb.GetRandomRouter (i2p::context.GetSharedRouterInfo (), false, true, false);
if (!router) {
LogPrint (eLogWarning, "Tunnel: Can't find any router, skip creating tunnel");
return;
@@ -789,26 +943,6 @@ namespace tunnel
}
}
void Tunnels::ManageTransitTunnels (uint64_t ts)
{
for (auto it = m_TransitTunnels.begin (); it != m_TransitTunnels.end ();)
{
auto tunnel = *it;
if (ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT ||
ts + TUNNEL_EXPIRATION_TIMEOUT < tunnel->GetCreationTime ())
{
LogPrint (eLogDebug, "Tunnel: Transit tunnel with id ", tunnel->GetTunnelID (), " expired");
m_Tunnels.erase (tunnel->GetTunnelID ());
it = m_TransitTunnels.erase (it);
}
else
{
tunnel->Cleanup ();
it++;
}
}
}
void Tunnels::ManageTunnelPools (uint64_t ts)
{
std::unique_lock<std::mutex> l(m_PoolsMutex);
@@ -824,7 +958,7 @@ namespace tunnel
if (msg) m_Queue.Put (msg);
}
void Tunnels::PostTunnelData (const std::vector<std::shared_ptr<I2NPMessage> >& msgs)
void Tunnels::PostTunnelData (std::list<std::shared_ptr<I2NPMessage> >& msgs)
{
m_Queue.Put (msgs);
}
@@ -882,7 +1016,7 @@ namespace tunnel
void Tunnels::AddInboundTunnel (std::shared_ptr<InboundTunnel> newTunnel)
{
if (m_Tunnels.emplace (newTunnel->GetTunnelID (), newTunnel).second)
if (AddTunnel (newTunnel))
{
m_InboundTunnels.push_back (newTunnel);
auto pool = newTunnel->GetTunnelPool ();
@@ -912,7 +1046,7 @@ namespace tunnel
inboundTunnel->SetTunnelPool (pool);
inboundTunnel->SetState (eTunnelStateEstablished);
m_InboundTunnels.push_back (inboundTunnel);
m_Tunnels[inboundTunnel->GetTunnelID ()] = inboundTunnel;
AddTunnel (inboundTunnel);
return inboundTunnel;
}
@@ -946,21 +1080,12 @@ namespace tunnel
int Tunnels::GetTransitTunnelsExpirationTimeout ()
{
int timeout = 0;
uint32_t ts = i2p::util::GetSecondsSinceEpoch ();
// TODO: possible race condition with I2PControl
for (const auto& it : m_TransitTunnels)
{
int t = it->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT - ts;
if (t > timeout) timeout = t;
}
return timeout;
return m_TransitTunnels.GetTransitTunnelsExpirationTimeout ();
}
size_t Tunnels::CountTransitTunnels() const
{
// TODO: locking
return m_TransitTunnels.size();
return m_TransitTunnels.GetNumTransitTunnels ();
}
size_t Tunnels::CountInboundTunnels() const

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2024, The PurpleI2P Project
* Copyright (c) 2013-2025, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
@@ -18,6 +18,7 @@
#include <thread>
#include <mutex>
#include <memory>
#include <random>
#include "util.h"
#include "Queue.h"
#include "Crypto.h"
@@ -98,6 +99,7 @@ namespace tunnel
void SetRecreated (bool recreated) { m_IsRecreated = recreated; };
int GetNumHops () const { return m_Hops.size (); };
virtual bool IsInbound() const = 0;
virtual bool Recreate () = 0;
std::shared_ptr<TunnelPool> GetTunnelPool () const { return m_Pool; };
void SetTunnelPool (std::shared_ptr<TunnelPool> pool) { m_Pool = pool; };
@@ -138,7 +140,7 @@ namespace tunnel
public:
OutboundTunnel (std::shared_ptr<const TunnelConfig> config):
Tunnel (config), m_Gateway (this), m_EndpointIdentHash (config->GetLastIdentHash ()) {};
Tunnel (config), m_Gateway (*this), m_EndpointIdentHash (config->GetLastIdentHash ()) {};
void SendTunnelDataMsgTo (const uint8_t * gwHash, uint32_t gwTunnel, std::shared_ptr<i2p::I2NPMessage> msg);
virtual void SendTunnelDataMsgs (const std::vector<TunnelMessageBlock>& msgs); // multiple messages
@@ -149,6 +151,7 @@ namespace tunnel
void HandleTunnelDataMsg (std::shared_ptr<i2p::I2NPMessage>&& tunnelMsg) override;
bool IsInbound() const override { return false; }
bool Recreate () override;
private:
@@ -165,6 +168,7 @@ namespace tunnel
void HandleTunnelDataMsg (std::shared_ptr<I2NPMessage>&& msg) override;
virtual size_t GetNumReceivedBytes () const { return m_Endpoint.GetNumReceivedBytes (); };
bool IsInbound() const override { return true; }
bool Recreate () override;
// override TunnelBase
void Cleanup () override { m_Endpoint.Cleanup (); };
@@ -222,18 +226,20 @@ namespace tunnel
std::shared_ptr<OutboundTunnel> GetNextOutboundTunnel ();
std::shared_ptr<TunnelPool> GetExploratoryPool () const { return m_ExploratoryPool; };
std::shared_ptr<TunnelBase> GetTunnel (uint32_t tunnelID);
bool AddTunnel (std::shared_ptr<TunnelBase> tunnel);
void RemoveTunnel (uint32_t tunnelID);
int GetTransitTunnelsExpirationTimeout ();
bool AddTransitTunnel (std::shared_ptr<TransitTunnel> tunnel);
void AddOutboundTunnel (std::shared_ptr<OutboundTunnel> newTunnel);
void AddInboundTunnel (std::shared_ptr<InboundTunnel> newTunnel);
std::shared_ptr<InboundTunnel> CreateInboundTunnel (std::shared_ptr<TunnelConfig> config, std::shared_ptr<TunnelPool> pool, std::shared_ptr<OutboundTunnel> outboundTunnel);
std::shared_ptr<OutboundTunnel> CreateOutboundTunnel (std::shared_ptr<TunnelConfig> config, std::shared_ptr<TunnelPool> pool);
void PostTunnelData (std::shared_ptr<I2NPMessage> msg);
void PostTunnelData (const std::vector<std::shared_ptr<I2NPMessage> >& msgs);
void PostTunnelData (std::list<std::shared_ptr<I2NPMessage> >& msgs); // and cleanup msgs
void AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr<InboundTunnel> tunnel);
void AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr<OutboundTunnel> tunnel);
std::shared_ptr<TunnelPool> CreateTunnelPool (int numInboundHops, int numOuboundHops,
int numInboundTunnels, int numOutboundTunnels, int inboundVariance, int outboundVariance);
std::shared_ptr<TunnelPool> CreateTunnelPool (int numInboundHops,
int numOuboundHops, int numInboundTunnels, int numOutboundTunnels,
int inboundVariance, int outboundVariance, bool isHighBandwidth);
void DeleteTunnelPool (std::shared_ptr<TunnelPool> pool);
void StopTunnelPool (std::shared_ptr<TunnelPool> pool);
@@ -241,8 +247,8 @@ namespace tunnel
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; }
int GetCongestionLevel() const { return m_MaxNumTransitTunnels ? CONGESTION_LEVEL_FULL * m_TransitTunnels.GetNumTransitTunnels () / m_MaxNumTransitTunnels : CONGESTION_LEVEL_FULL; }
private:
template<class TTunnel>
@@ -253,12 +259,14 @@ namespace tunnel
std::shared_ptr<TTunnel> GetPendingTunnel (uint32_t replyMsgID, const std::map<uint32_t, std::shared_ptr<TTunnel> >& pendingTunnels);
void HandleTunnelGatewayMsg (std::shared_ptr<TunnelBase> tunnel, std::shared_ptr<I2NPMessage> msg);
void HandleShortTunnelBuildMsg (std::shared_ptr<I2NPMessage> msg);
void HandleVariableTunnelBuildMsg (std::shared_ptr<I2NPMessage> msg);
void HandleTunnelBuildReplyMsg (std::shared_ptr<I2NPMessage> msg, bool isShort);
void Run ();
void ManageTunnels (uint64_t ts);
void ManageOutboundTunnels (uint64_t ts);
void ManageInboundTunnels (uint64_t ts);
void ManageTransitTunnels (uint64_t ts);
void ManageOutboundTunnels (uint64_t ts, std::vector<std::shared_ptr<Tunnel> >& toRecreate);
void ManageInboundTunnels (uint64_t ts, std::vector<std::shared_ptr<Tunnel> >& toRecreate);
void ManagePendingTunnels (uint64_t ts);
template<class PendingTunnels>
void ManagePendingTunnels (PendingTunnels& pendingTunnels, uint64_t ts);
@@ -289,36 +297,39 @@ namespace tunnel
bool m_IsRunning;
std::thread * m_Thread;
i2p::util::MemoryPoolMt<I2NPMessageBuffer<I2NP_TUNNEL_ENPOINT_MESSAGE_SIZE> > m_I2NPTunnelEndpointMessagesMemoryPool;
i2p::util::MemoryPoolMt<I2NPMessageBuffer<I2NP_TUNNEL_MESSAGE_SIZE> > m_I2NPTunnelMessagesMemoryPool;
std::map<uint32_t, std::shared_ptr<InboundTunnel> > m_PendingInboundTunnels; // by replyMsgID
std::map<uint32_t, std::shared_ptr<OutboundTunnel> > m_PendingOutboundTunnels; // by replyMsgID
std::list<std::shared_ptr<InboundTunnel> > m_InboundTunnels;
std::list<std::shared_ptr<OutboundTunnel> > m_OutboundTunnels;
std::list<std::shared_ptr<TransitTunnel> > m_TransitTunnels;
mutable std::mutex m_TunnelsMutex;
std::unordered_map<uint32_t, std::shared_ptr<TunnelBase> > m_Tunnels; // tunnelID->tunnel known by this id
std::mutex m_PoolsMutex;
mutable std::mutex m_PoolsMutex;
std::list<std::shared_ptr<TunnelPool>> m_Pools;
std::shared_ptr<TunnelPool> m_ExploratoryPool;
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;
uint32_t m_MaxNumTransitTunnels;
// count of tunnels for total TCSR algorithm
int m_TotalNumSuccesiveTunnelCreations, m_TotalNumFailedTunnelCreations;
double m_TunnelCreationSuccessRate;
int m_TunnelCreationAttemptsNum;
std::mt19937 m_Rng;
TransitTunnels m_TransitTunnels;
public:
// for HTTP only
const decltype(m_OutboundTunnels)& GetOutboundTunnels () const { return m_OutboundTunnels; };
const decltype(m_InboundTunnels)& GetInboundTunnels () const { return m_InboundTunnels; };
const decltype(m_TransitTunnels)& GetTransitTunnels () const { return m_TransitTunnels; };
const auto& GetTransitTunnels () const { return m_TransitTunnels.GetTransitTunnels (); };
size_t CountTransitTunnels() const;
size_t CountInboundTunnels() const;
size_t CountOutboundTunnels() const;
int GetQueueSize () { return m_Queue.GetSize (); };
size_t GetQueueSize () const { return m_Queue.GetSize (); };
size_t GetTBMQueueSize () const { return m_TransitTunnels.GetTunnelBuildMsgQueueSize (); };
int GetTunnelCreationSuccessRate () const { return std::round(m_TunnelCreationSuccessRate * 100); } // in percents
double GetPreciseTunnelCreationSuccessRate () const { return m_TunnelCreationSuccessRate * 100; } // in percents
int GetTotalTunnelCreationSuccessRate () const // in percents

71
libi2pd/TunnelBase.cpp Normal file
View File

@@ -0,0 +1,71 @@
/*
* 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
*
*/
#include "Transports.h"
#include "TunnelBase.h"
namespace i2p
{
namespace tunnel
{
void TunnelTransportSender::SendMessagesTo (const i2p::data::IdentHash& to,
std::list<std::shared_ptr<I2NPMessage> >&& msgs)
{
if (msgs.empty ()) return;
auto currentTransport = m_CurrentTransport.lock ();
if (!currentTransport)
{
// try to obtain transport from pending request or send thought transport is not complete
if (m_PendingTransport.valid ()) // pending request?
{
if (m_PendingTransport.wait_for(std::chrono::seconds(0)) == std::future_status::ready)
{
// pending request complete
currentTransport = m_PendingTransport.get (); // take transports used in pending request
if (currentTransport)
{
if (currentTransport->IsEstablished ())
m_CurrentTransport = currentTransport;
else
currentTransport = nullptr;
}
}
else // still pending
{
// send through transports, but don't update pending transport
i2p::transport::transports.SendMessages (to, std::move (msgs));
return;
}
}
}
if (currentTransport) // session is good
// send to session directly
currentTransport->SendI2NPMessages (msgs);
else // no session yet
// send through transports
m_PendingTransport = i2p::transport::transports.SendMessages (to, std::move (msgs));
}
void TunnelTransportSender::SendMessagesTo (const i2p::data::IdentHash& to,
std::list<std::shared_ptr<I2NPMessage> >& msgs)
{
std::list<std::shared_ptr<i2p::I2NPMessage> > msgs1;
msgs.swap (msgs1);
SendMessagesTo (to, std::move (msgs1));
}
void TunnelTransportSender::Reset ()
{
m_CurrentTransport.reset ();
if (m_PendingTransport.valid ())
m_PendingTransport = std::future<std::shared_ptr<i2p::transport::TransportSession> >();
}
}
}

View File

@@ -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
*
@@ -11,12 +11,19 @@
#include <inttypes.h>
#include <memory>
#include <future>
#include <list>
#include "Timestamp.h"
#include "I2NPProtocol.h"
#include "Identity.h"
namespace i2p
{
namespace transport
{
class TransportSession;
}
namespace tunnel
{
const size_t TUNNEL_DATA_MSG_SIZE = 1028;
@@ -76,6 +83,25 @@ namespace tunnel
return t1 < t2;
}
};
class TunnelTransportSender final
{
public:
TunnelTransportSender () = default;
~TunnelTransportSender () = default;
void SendMessagesTo (const i2p::data::IdentHash& to, std::list<std::shared_ptr<I2NPMessage> >&& msgs);
void SendMessagesTo (const i2p::data::IdentHash& to, std::list<std::shared_ptr<I2NPMessage> >& msgs); // send and clear
std::shared_ptr<const i2p::transport::TransportSession> GetCurrentTransport () const { return m_CurrentTransport.lock (); }
void Reset ();
private:
std::weak_ptr<i2p::transport::TransportSession> m_CurrentTransport;
std::future<std::shared_ptr<i2p::transport::TransportSession> > m_PendingTransport;
};
}
}

View File

@@ -79,8 +79,7 @@ namespace tunnel
uint8_t * record = records + index*TUNNEL_BUILD_RECORD_SIZE;
i2p::crypto::CBCDecryption decryption;
decryption.SetKey (replyKey);
decryption.SetIV (replyIV);
decryption.Decrypt(record, TUNNEL_BUILD_RECORD_SIZE, record);
decryption.Decrypt(record, TUNNEL_BUILD_RECORD_SIZE, replyIV, record);
}
void ECIESTunnelHopConfig::EncryptECIES (const uint8_t * plainText, size_t len, uint8_t * encrypted)

View File

@@ -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
*
@@ -181,6 +181,8 @@ namespace tunnel
return peers;
}
size_t GetRecordSize () const { return m_IsShort ? SHORT_TUNNEL_BUILD_RECORD_SIZE : TUNNEL_BUILD_RECORD_SIZE; };
protected:
// this constructor can't be called from outside

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