mirror of https://github.com/PurpleI2P/i2pd.git
orignal
1 month ago
5 changed files with 293 additions and 254 deletions
@ -0,0 +1,228 @@ |
|||||||
|
/*
|
||||||
|
* 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 "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
|
||||||
|
i2p::crypto::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::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_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 ().AddConnectedRecently (GetRemoteEndpoint (), i2p::util::GetSecondsSinceEpoch ()); |
||||||
|
GetServer ().RequestRemoveSession (GetConnID ()); |
||||||
|
break; |
||||||
|
} |
||||||
|
case 7: // Alice from Charlie 2
|
||||||
|
{ |
||||||
|
m_PeerTestResendTimer.cancel (); // no more msg 6 resends
|
||||||
|
auto addr = GetAddress (); |
||||||
|
if (addr && addr->IsV6 ()) |
||||||
|
i2p::context.SetStatusV6 (eRouterStatusOK); // set status OK for ipv6 even if from SSU2
|
||||||
|
GetServer ().AddConnectedRecently (GetRemoteEndpoint (), i2p::util::GetSecondsSinceEpoch ()); |
||||||
|
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); |
||||||
|
i2p::crypto::ChaCha20 (h + 16, 16, addr->i, n, h + 16); |
||||||
|
// send
|
||||||
|
GetServer ().Send (header.buf, 16, h + 16, 16, payload, payloadSize, GetRemoteEndpoint ()); |
||||||
|
} |
||||||
|
|
||||||
|
void SSU2PeerTestSession::SendPeerTest (uint8_t msg, const uint8_t * signedData, size_t signedDataLen) |
||||||
|
{ |
||||||
|
#if __cplusplus >= 202002L // C++20
|
||||||
|
m_SignedData.assign (signedData, signedData + signedDataLen); |
||||||
|
#else |
||||||
|
m_SignedData.resize (signedDataLen); |
||||||
|
memcpy (m_SignedData.data (), signedData, signedDataLen); |
||||||
|
#endif |
||||||
|
SendPeerTest (msg); |
||||||
|
// schedule resend for msgs 5 or 6
|
||||||
|
if (msg == 5 || msg == 6) |
||||||
|
ScheduleResend (); |
||||||
|
} |
||||||
|
|
||||||
|
void SSU2PeerTestSession::SendPeerTest (uint8_t msg, const uint8_t * signedData, size_t signedDataLen, |
||||||
|
std::shared_ptr<const i2p::data::RouterInfo::Address> addr) |
||||||
|
{ |
||||||
|
if (!addr) return; |
||||||
|
SetAddress (addr); |
||||||
|
SendPeerTest (msg, signedData, signedDataLen); |
||||||
|
} |
||||||
|
|
||||||
|
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 () |
||||||
|
{ |
||||||
|
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](const boost::system::error_code& ecode) |
||||||
|
{ |
||||||
|
if (ecode != boost::asio::error::operation_aborted) |
||||||
|
{ |
||||||
|
auto s1 = s.lock (); |
||||||
|
if (s1) |
||||||
|
{ |
||||||
|
int msg = 0; |
||||||
|
if (s1->m_MsgNumReceived < 6) |
||||||
|
msg = (s1->m_MsgNumReceived == 5) ? 6 : 5; |
||||||
|
if (msg) // 5 or 6
|
||||||
|
{ |
||||||
|
s1->SendPeerTest (msg); |
||||||
|
s1->ScheduleResend (); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
m_NumResends++; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,57 @@ |
|||||||
|
/*
|
||||||
|
* 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 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); // PeerTest message
|
||||||
|
void SendPeerTest (uint8_t msg); // send or resend m_SignedData
|
||||||
|
void HandlePeerTest (const uint8_t * buf, size_t len) override; |
||||||
|
|
||||||
|
void ScheduleResend (); |
||||||
|
|
||||||
|
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; |
||||||
|
}; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#endif |
Loading…
Reference in new issue