mirror of https://github.com/oxen-io/lokinet
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1007 lines
29 KiB
C++
1007 lines
29 KiB
C++
#include "session.hpp"
|
|
|
|
#include <llarp/messages/link_intro.hpp>
|
|
#include <llarp/messages/discard.hpp>
|
|
#include <llarp/util/meta/memfn.hpp>
|
|
#include <llarp/router/abstractrouter.hpp>
|
|
|
|
namespace llarp
|
|
{
|
|
namespace iwp
|
|
{
|
|
ILinkSession::Packet_t
|
|
CreatePacket(Command cmd, size_t plainsize, size_t minpad, size_t variance)
|
|
{
|
|
const size_t pad = minpad > 0 ? minpad + (variance > 0 ? randint() % variance : 0) : 0;
|
|
ILinkSession::Packet_t pkt(PacketOverhead + plainsize + pad + CommandOverhead);
|
|
// randomize pad
|
|
if (pad)
|
|
{
|
|
CryptoManager::instance()->randbytes(
|
|
pkt.data() + PacketOverhead + CommandOverhead + plainsize, pad);
|
|
}
|
|
// randomize nounce
|
|
CryptoManager::instance()->randbytes(pkt.data() + HMACSIZE, TUNNONCESIZE);
|
|
pkt[PacketOverhead] = LLARP_PROTO_VERSION;
|
|
pkt[PacketOverhead + 1] = cmd;
|
|
return pkt;
|
|
}
|
|
|
|
constexpr size_t PlaintextQueueSize = 512;
|
|
|
|
Session::Session(LinkLayer* p, const RouterContact& rc, const AddressInfo& ai)
|
|
: m_State{State::Initial}
|
|
, m_Inbound{false}
|
|
, m_Parent(p)
|
|
, m_CreatedAt{p->Now()}
|
|
, m_RemoteAddr{ai}
|
|
, m_ChosenAI(ai)
|
|
, m_RemoteRC(rc)
|
|
, m_PlaintextRecv{PlaintextQueueSize}
|
|
{
|
|
token.Zero();
|
|
m_PlaintextEmpty.test_and_set();
|
|
GotLIM = util::memFn(&Session::GotOutboundLIM, this);
|
|
CryptoManager::instance()->shorthash(m_SessionKey, llarp_buffer_t(rc.pubkey));
|
|
}
|
|
|
|
Session::Session(LinkLayer* p, const SockAddr& from)
|
|
: m_State{State::Initial}
|
|
, m_Inbound{true}
|
|
, m_Parent(p)
|
|
, m_CreatedAt{p->Now()}
|
|
, m_RemoteAddr{from}
|
|
, m_PlaintextRecv{PlaintextQueueSize}
|
|
{
|
|
token.Randomize();
|
|
m_PlaintextEmpty.test_and_set();
|
|
GotLIM = util::memFn(&Session::GotInboundLIM, this);
|
|
const PubKey pk = m_Parent->GetOurRC().pubkey;
|
|
CryptoManager::instance()->shorthash(m_SessionKey, llarp_buffer_t(pk));
|
|
}
|
|
|
|
void
|
|
Session::Send_LL(const byte_t* buf, size_t sz)
|
|
{
|
|
LogTrace("send ", sz, " to ", m_RemoteAddr);
|
|
const llarp_buffer_t pkt(buf, sz);
|
|
m_Parent->SendTo_LL(m_RemoteAddr, pkt);
|
|
m_LastTX = time_now_ms();
|
|
m_TXRate += sz;
|
|
}
|
|
|
|
bool
|
|
Session::GotInboundLIM(const LinkIntroMessage* msg)
|
|
{
|
|
if (msg->rc.pubkey != m_ExpectedIdent)
|
|
{
|
|
LogError(
|
|
"ident key mismatch from ", m_RemoteAddr, " ", msg->rc.pubkey, " != ", m_ExpectedIdent);
|
|
return false;
|
|
}
|
|
m_State = State::Ready;
|
|
GotLIM = util::memFn(&Session::GotRenegLIM, this);
|
|
m_RemoteRC = msg->rc;
|
|
m_Parent->MapAddr(m_RemoteRC.pubkey, this);
|
|
return m_Parent->SessionEstablished(this, true);
|
|
}
|
|
|
|
bool
|
|
Session::GotOutboundLIM(const LinkIntroMessage* msg)
|
|
{
|
|
if (msg->rc.pubkey != m_RemoteRC.pubkey)
|
|
{
|
|
LogError("ident key mismatch");
|
|
return false;
|
|
}
|
|
|
|
m_RemoteRC = msg->rc;
|
|
GotLIM = util::memFn(&Session::GotRenegLIM, this);
|
|
assert(shared_from_this().use_count() > 1);
|
|
SendOurLIM([self = shared_from_this()](ILinkSession::DeliveryStatus st) {
|
|
if (st == ILinkSession::DeliveryStatus::eDeliverySuccess)
|
|
{
|
|
self->m_State = State::Ready;
|
|
self->m_Parent->MapAddr(self->m_RemoteRC.pubkey, self.get());
|
|
self->m_Parent->SessionEstablished(self.get(), false);
|
|
}
|
|
});
|
|
return true;
|
|
}
|
|
|
|
void
|
|
Session::SendOurLIM(ILinkSession::CompletionHandler h)
|
|
{
|
|
LinkIntroMessage msg;
|
|
msg.rc = m_Parent->GetOurRC();
|
|
msg.N.Randomize();
|
|
msg.P = 60000;
|
|
if (not msg.Sign(m_Parent->Sign))
|
|
{
|
|
LogError("failed to sign our RC for ", m_RemoteAddr);
|
|
return;
|
|
}
|
|
ILinkSession::Message_t data(LinkIntroMessage::MaxSize + PacketOverhead);
|
|
llarp_buffer_t buf(data);
|
|
if (not msg.BEncode(&buf))
|
|
{
|
|
LogError("failed to encode LIM for ", m_RemoteAddr);
|
|
}
|
|
if (!SendMessageBuffer(std::move(data), h))
|
|
{
|
|
LogError("failed to send LIM to ", m_RemoteAddr);
|
|
}
|
|
LogTrace("sent LIM to ", m_RemoteAddr);
|
|
}
|
|
|
|
void
|
|
Session::EncryptAndSend(ILinkSession::Packet_t data)
|
|
{
|
|
m_EncryptNext.emplace_back(std::move(data));
|
|
TriggerPump();
|
|
if (!IsEstablished())
|
|
{
|
|
EncryptWorker(std::move(m_EncryptNext));
|
|
m_EncryptNext = CryptoQueue_t{};
|
|
}
|
|
}
|
|
|
|
void
|
|
Session::EncryptWorker(CryptoQueue_t msgs)
|
|
{
|
|
LogTrace("encrypt worker ", msgs.size(), " messages");
|
|
for (auto& pkt : msgs)
|
|
{
|
|
llarp_buffer_t pktbuf{pkt};
|
|
const TunnelNonce nonce_ptr{pkt.data() + HMACSIZE};
|
|
pktbuf.base += PacketOverhead;
|
|
pktbuf.cur = pktbuf.base;
|
|
pktbuf.sz -= PacketOverhead;
|
|
CryptoManager::instance()->xchacha20(pktbuf, m_SessionKey, nonce_ptr);
|
|
pktbuf.base = pkt.data() + HMACSIZE;
|
|
pktbuf.sz = pkt.size() - HMACSIZE;
|
|
CryptoManager::instance()->hmac(pkt.data(), pktbuf, m_SessionKey);
|
|
Send_LL(pkt.data(), pkt.size());
|
|
}
|
|
}
|
|
|
|
void
|
|
Session::Close()
|
|
{
|
|
if (m_State == State::Closed)
|
|
return;
|
|
auto close_msg = CreatePacket(Command::eCLOS, 0, 16, 16);
|
|
if (m_State == State::Ready)
|
|
m_Parent->UnmapAddr(m_RemoteAddr);
|
|
m_State = State::Closed;
|
|
if (m_SentClosed.test_and_set())
|
|
return;
|
|
EncryptAndSend(std::move(close_msg));
|
|
|
|
LogInfo(m_Parent->PrintableName(), " closing connection to ", m_RemoteAddr);
|
|
}
|
|
|
|
bool
|
|
Session::SendMessageBuffer(
|
|
ILinkSession::Message_t buf, ILinkSession::CompletionHandler completed)
|
|
{
|
|
if (m_TXMsgs.size() >= MaxSendQueueSize)
|
|
{
|
|
if (completed)
|
|
completed(ILinkSession::DeliveryStatus::eDeliveryDropped);
|
|
return false;
|
|
}
|
|
const auto now = m_Parent->Now();
|
|
const auto msgid = m_TXID++;
|
|
const auto bufsz = buf.size();
|
|
auto& msg = m_TXMsgs.emplace(msgid, OutboundMessage{msgid, std::move(buf), now, completed})
|
|
.first->second;
|
|
TriggerPump();
|
|
EncryptAndSend(msg.XMIT());
|
|
if (bufsz > FragmentSize)
|
|
{
|
|
msg.FlushUnAcked(util::memFn(&Session::EncryptAndSend, this), now);
|
|
}
|
|
m_Stats.totalInFlightTX++;
|
|
LogDebug("send message ", msgid, " to ", m_RemoteAddr);
|
|
return true;
|
|
}
|
|
|
|
void
|
|
Session::SendMACK()
|
|
{
|
|
// send multi acks
|
|
while (not m_SendMACKs.empty())
|
|
{
|
|
const auto sz = m_SendMACKs.size();
|
|
const auto max = Session::MaxACKSInMACK;
|
|
auto numAcks = std::min(sz, max);
|
|
auto mack = CreatePacket(Command::eMACK, 1 + (numAcks * sizeof(uint64_t)));
|
|
mack[PacketOverhead + CommandOverhead] = byte_t{static_cast<byte_t>(numAcks)};
|
|
byte_t* ptr = mack.data() + 3 + PacketOverhead;
|
|
LogTrace("send ", numAcks, " macks to ", m_RemoteAddr);
|
|
const auto& itr = m_SendMACKs.top();
|
|
while (numAcks > 0)
|
|
{
|
|
htobe64buf(ptr, itr);
|
|
m_SendMACKs.pop();
|
|
numAcks--;
|
|
ptr += sizeof(uint64_t);
|
|
}
|
|
EncryptAndSend(std::move(mack));
|
|
}
|
|
}
|
|
|
|
void
|
|
Session::TriggerPump()
|
|
{
|
|
m_Parent->Router()->TriggerPump();
|
|
}
|
|
|
|
void
|
|
Session::Pump()
|
|
{
|
|
const auto now = m_Parent->Now();
|
|
if (m_State == State::Ready || m_State == State::LinkIntro)
|
|
{
|
|
if (ShouldPing())
|
|
SendKeepAlive();
|
|
for (auto& [id, msg] : m_RXMsgs)
|
|
{
|
|
if (msg.ShouldSendACKS(now))
|
|
{
|
|
msg.SendACKS(util::memFn(&Session::EncryptAndSend, this), now);
|
|
}
|
|
}
|
|
for (auto& [id, msg] : m_TXMsgs)
|
|
{
|
|
if (msg.ShouldFlush(now))
|
|
{
|
|
msg.FlushUnAcked(util::memFn(&Session::EncryptAndSend, this), now);
|
|
}
|
|
}
|
|
}
|
|
assert(shared_from_this().use_count() > 1);
|
|
if (not m_EncryptNext.empty())
|
|
{
|
|
m_Parent->QueueWork(
|
|
[self = shared_from_this(), data = m_EncryptNext] { self->EncryptWorker(data); });
|
|
m_EncryptNext.clear();
|
|
}
|
|
|
|
if (not m_DecryptNext.empty())
|
|
{
|
|
m_Parent->QueueWork(
|
|
[self = shared_from_this(), data = m_DecryptNext] { self->DecryptWorker(data); });
|
|
m_DecryptNext.clear();
|
|
}
|
|
}
|
|
|
|
bool
|
|
Session::GotRenegLIM(const LinkIntroMessage* lim)
|
|
{
|
|
LogDebug("renegotiate session on ", m_RemoteAddr);
|
|
return m_Parent->SessionRenegotiate(lim->rc, m_RemoteRC);
|
|
}
|
|
|
|
bool
|
|
Session::RenegotiateSession()
|
|
{
|
|
SendOurLIM();
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
Session::ShouldPing() const
|
|
{
|
|
if (m_State == State::Ready)
|
|
{
|
|
const auto now = m_Parent->Now();
|
|
return now - m_LastTX > PingInterval;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
SessionStats
|
|
Session::GetSessionStats() const
|
|
{
|
|
// TODO: thread safety
|
|
return m_Stats;
|
|
}
|
|
|
|
util::StatusObject
|
|
Session::ExtractStatus() const
|
|
{
|
|
const auto now = m_Parent->Now();
|
|
|
|
return {
|
|
{"txRateCurrent", m_Stats.currentRateTX},
|
|
{"rxRateCurrent", m_Stats.currentRateRX},
|
|
{"rxPktsRcvd", m_Stats.totalPacketsRX},
|
|
|
|
// leave 'tx' and 'rx' as duplicates of 'xRateCurrent' for compat
|
|
{"tx", m_Stats.currentRateTX},
|
|
{"rx", m_Stats.currentRateRX},
|
|
|
|
{"txPktsAcked", m_Stats.totalAckedTX},
|
|
{"txPktsDropped", m_Stats.totalDroppedTX},
|
|
{"txPktsInFlight", m_Stats.totalInFlightTX},
|
|
|
|
{"state", StateToString(m_State)},
|
|
{"inbound", m_Inbound},
|
|
{"replayFilter", m_ReplayFilter.size()},
|
|
{"txMsgQueueSize", m_TXMsgs.size()},
|
|
{"rxMsgQueueSize", m_RXMsgs.size()},
|
|
{"remoteAddr", m_RemoteAddr.toString()},
|
|
{"remoteRC", m_RemoteRC.ExtractStatus()},
|
|
{"created", to_json(m_CreatedAt)},
|
|
{"uptime", to_json(now - m_CreatedAt)}};
|
|
}
|
|
|
|
bool
|
|
Session::TimedOut(llarp_time_t now) const
|
|
{
|
|
if (m_State == State::Ready)
|
|
{
|
|
return now > m_LastRX
|
|
&& now - m_LastRX
|
|
> (m_Inbound and not m_RemoteRC.IsPublicRouter() ? DefaultLinkSessionLifetime
|
|
: SessionAliveTimeout);
|
|
}
|
|
return now - m_CreatedAt >= LinkLayerConnectTimeout;
|
|
}
|
|
|
|
bool
|
|
Session::ShouldResetRates(llarp_time_t now) const
|
|
{
|
|
return now >= m_ResetRatesAt;
|
|
}
|
|
|
|
void
|
|
Session::ResetRates()
|
|
{
|
|
m_Stats.currentRateTX = m_TXRate;
|
|
m_Stats.currentRateRX = m_RXRate;
|
|
m_RXRate = 0;
|
|
m_TXRate = 0;
|
|
}
|
|
|
|
void
|
|
Session::Tick(llarp_time_t now)
|
|
{
|
|
if (ShouldResetRates(now))
|
|
{
|
|
ResetRates();
|
|
m_ResetRatesAt = now + 1s;
|
|
}
|
|
// remove pending outbound messsages that timed out
|
|
// inform waiters
|
|
{
|
|
auto itr = m_TXMsgs.begin();
|
|
while (itr != m_TXMsgs.end())
|
|
{
|
|
if (itr->second.IsTimedOut(now))
|
|
{
|
|
m_Stats.totalDroppedTX++;
|
|
m_Stats.totalInFlightTX--;
|
|
LogTrace("Dropped unacked packet to ", m_RemoteAddr);
|
|
itr->second.InformTimeout();
|
|
itr = m_TXMsgs.erase(itr);
|
|
}
|
|
else
|
|
++itr;
|
|
}
|
|
}
|
|
{
|
|
// remove pending inbound messages that timed out
|
|
auto itr = m_RXMsgs.begin();
|
|
while (itr != m_RXMsgs.end())
|
|
{
|
|
if (itr->second.IsTimedOut(now))
|
|
{
|
|
m_ReplayFilter.emplace(itr->first, now);
|
|
itr = m_RXMsgs.erase(itr);
|
|
}
|
|
else
|
|
++itr;
|
|
}
|
|
}
|
|
{
|
|
// decay replay window
|
|
auto itr = m_ReplayFilter.begin();
|
|
while (itr != m_ReplayFilter.end())
|
|
{
|
|
if (itr->second + ReplayWindow <= now)
|
|
{
|
|
itr = m_ReplayFilter.erase(itr);
|
|
}
|
|
else
|
|
++itr;
|
|
}
|
|
}
|
|
}
|
|
|
|
using Introduction =
|
|
AlignedBuffer<PubKey::SIZE + PubKey::SIZE + TunnelNonce::SIZE + Signature::SIZE>;
|
|
|
|
void
|
|
Session::GenerateAndSendIntro()
|
|
{
|
|
TunnelNonce N;
|
|
N.Randomize();
|
|
{
|
|
ILinkSession::Packet_t req(Introduction::SIZE + PacketOverhead);
|
|
const auto pk = m_Parent->GetOurRC().pubkey;
|
|
const auto e_pk = m_Parent->RouterEncryptionSecret().toPublic();
|
|
auto itr = req.data() + PacketOverhead;
|
|
std::copy_n(pk.data(), pk.size(), itr);
|
|
itr += pk.size();
|
|
std::copy_n(e_pk.data(), e_pk.size(), itr);
|
|
itr += e_pk.size();
|
|
std::copy_n(N.data(), N.size(), itr);
|
|
Signature Z;
|
|
llarp_buffer_t signbuf(req.data() + PacketOverhead, Introduction::SIZE - Signature::SIZE);
|
|
m_Parent->Sign(Z, signbuf);
|
|
std::copy_n(
|
|
Z.data(),
|
|
Z.size(),
|
|
req.data() + PacketOverhead + (Introduction::SIZE - Signature::SIZE));
|
|
CryptoManager::instance()->randbytes(req.data() + HMACSIZE, TUNNONCESIZE);
|
|
EncryptAndSend(std::move(req));
|
|
}
|
|
m_State = State::Introduction;
|
|
if (not CryptoManager::instance()->transport_dh_client(
|
|
m_SessionKey, m_ChosenAI.pubkey, m_Parent->RouterEncryptionSecret(), N))
|
|
{
|
|
LogError("failed to transport_dh_client on outbound session to ", m_RemoteAddr);
|
|
return;
|
|
}
|
|
LogTrace("sent intro to ", m_RemoteAddr);
|
|
}
|
|
|
|
void
|
|
Session::HandleCreateSessionRequest(Packet_t pkt)
|
|
{
|
|
if (not DecryptMessageInPlace(pkt))
|
|
{
|
|
LogError(
|
|
m_Parent->PrintableName(), " failed to decrypt session request from ", m_RemoteAddr);
|
|
return;
|
|
}
|
|
if (pkt.size() < token.size() + PacketOverhead)
|
|
{
|
|
LogError(
|
|
m_Parent->PrintableName(),
|
|
" bad session request size, ",
|
|
pkt.size(),
|
|
" < ",
|
|
token.size() + PacketOverhead,
|
|
" from ",
|
|
m_RemoteAddr);
|
|
return;
|
|
}
|
|
const auto begin = pkt.data() + PacketOverhead;
|
|
if (not std::equal(begin, begin + token.size(), token.data()))
|
|
{
|
|
LogError(m_Parent->PrintableName(), " token mismatch from ", m_RemoteAddr);
|
|
return;
|
|
}
|
|
m_LastRX = m_Parent->Now();
|
|
m_State = State::LinkIntro;
|
|
SendOurLIM();
|
|
}
|
|
|
|
void
|
|
Session::HandleGotIntro(Packet_t pkt)
|
|
{
|
|
if (pkt.size() < (Introduction::SIZE + PacketOverhead))
|
|
{
|
|
LogWarn(m_Parent->PrintableName(), " intro too small from ", m_RemoteAddr);
|
|
return;
|
|
}
|
|
byte_t* ptr = pkt.data() + PacketOverhead;
|
|
TunnelNonce N;
|
|
std::copy_n(ptr, PubKey::SIZE, m_ExpectedIdent.data());
|
|
ptr += PubKey::SIZE;
|
|
std::copy_n(ptr, PubKey::SIZE, m_RemoteOnionKey.data());
|
|
ptr += PubKey::SIZE;
|
|
std::copy_n(ptr, TunnelNonce::SIZE, N.data());
|
|
ptr += TunnelNonce::SIZE;
|
|
Signature Z;
|
|
std::copy_n(ptr, Z.size(), Z.data());
|
|
const llarp_buffer_t verifybuf(
|
|
pkt.data() + PacketOverhead, Introduction::SIZE - Signature::SIZE);
|
|
if (!CryptoManager::instance()->verify(m_ExpectedIdent, verifybuf, Z))
|
|
{
|
|
LogError(m_Parent->PrintableName(), " intro verify failed from ", m_RemoteAddr);
|
|
return;
|
|
}
|
|
const PubKey pk = m_Parent->TransportSecretKey().toPublic();
|
|
LogDebug(
|
|
"got intro: remote-pk=",
|
|
m_RemoteOnionKey.ToHex(),
|
|
" N=",
|
|
N.ToHex(),
|
|
" local-pk=",
|
|
pk.ToHex());
|
|
if (not CryptoManager::instance()->transport_dh_server(
|
|
m_SessionKey, m_RemoteOnionKey, m_Parent->TransportSecretKey(), N))
|
|
{
|
|
LogError("failed to transport_dh_server on inbound intro from ", m_RemoteAddr);
|
|
return;
|
|
}
|
|
Packet_t reply(token.size() + PacketOverhead);
|
|
// random nonce
|
|
CryptoManager::instance()->randbytes(reply.data() + HMACSIZE, TUNNONCESIZE);
|
|
// set token
|
|
std::copy_n(token.data(), token.size(), reply.data() + PacketOverhead);
|
|
m_LastRX = m_Parent->Now();
|
|
EncryptAndSend(std::move(reply));
|
|
LogDebug("sent intro ack to ", m_RemoteAddr);
|
|
m_State = State::Introduction;
|
|
}
|
|
|
|
void
|
|
Session::HandleGotIntroAck(Packet_t pkt)
|
|
{
|
|
if (pkt.size() < (token.size() + PacketOverhead))
|
|
{
|
|
LogError(
|
|
m_Parent->PrintableName(),
|
|
" bad intro ack size ",
|
|
pkt.size(),
|
|
" < ",
|
|
token.size() + PacketOverhead,
|
|
" from ",
|
|
m_RemoteAddr);
|
|
return;
|
|
}
|
|
Packet_t reply(token.size() + PacketOverhead);
|
|
if (not DecryptMessageInPlace(pkt))
|
|
{
|
|
LogError(m_Parent->PrintableName(), " intro ack decrypt failed from ", m_RemoteAddr);
|
|
return;
|
|
}
|
|
m_LastRX = m_Parent->Now();
|
|
std::copy_n(pkt.data() + PacketOverhead, token.size(), token.data());
|
|
std::copy_n(token.data(), token.size(), reply.data() + PacketOverhead);
|
|
// random nounce
|
|
CryptoManager::instance()->randbytes(reply.data() + HMACSIZE, TUNNONCESIZE);
|
|
EncryptAndSend(std::move(reply));
|
|
LogDebug("sent session request to ", m_RemoteAddr);
|
|
m_State = State::LinkIntro;
|
|
}
|
|
|
|
bool
|
|
Session::DecryptMessageInPlace(Packet_t& pkt)
|
|
{
|
|
if (pkt.size() <= PacketOverhead)
|
|
{
|
|
LogError("packet too small from ", m_RemoteAddr);
|
|
return false;
|
|
}
|
|
const llarp_buffer_t buf(pkt);
|
|
ShortHash H;
|
|
llarp_buffer_t curbuf(buf.base, buf.sz);
|
|
curbuf.base += ShortHash::SIZE;
|
|
curbuf.sz -= ShortHash::SIZE;
|
|
if (not CryptoManager::instance()->hmac(H.data(), curbuf, m_SessionKey))
|
|
{
|
|
LogError("failed to caclulate keyed hash for ", m_RemoteAddr);
|
|
return false;
|
|
}
|
|
const ShortHash expected{buf.base};
|
|
if (H != expected)
|
|
{
|
|
LogDebug(
|
|
m_Parent->PrintableName(),
|
|
" keyed hash mismatch ",
|
|
H,
|
|
" != ",
|
|
expected,
|
|
" from ",
|
|
m_RemoteAddr,
|
|
" state=",
|
|
int(m_State),
|
|
" size=",
|
|
buf.sz);
|
|
return false;
|
|
}
|
|
const TunnelNonce N{curbuf.base};
|
|
curbuf.base += 32;
|
|
curbuf.sz -= 32;
|
|
LogTrace("decrypt: ", curbuf.sz, " bytes from ", m_RemoteAddr);
|
|
return CryptoManager::instance()->xchacha20(curbuf, m_SessionKey, N);
|
|
}
|
|
|
|
void
|
|
Session::Start()
|
|
{
|
|
if (m_Inbound)
|
|
return;
|
|
GenerateAndSendIntro();
|
|
}
|
|
|
|
void
|
|
Session::HandleSessionData(Packet_t pkt)
|
|
{
|
|
m_DecryptNext.emplace_back(std::move(pkt));
|
|
TriggerPump();
|
|
}
|
|
|
|
void
|
|
Session::DecryptWorker(CryptoQueue_t msgs)
|
|
{
|
|
auto itr = msgs.begin();
|
|
while (itr != msgs.end())
|
|
{
|
|
auto& pkt = *itr;
|
|
if (not DecryptMessageInPlace(pkt))
|
|
{
|
|
itr = msgs.erase(itr);
|
|
LogError("failed to decrypt session data from ", m_RemoteAddr);
|
|
continue;
|
|
}
|
|
if (pkt[PacketOverhead] != LLARP_PROTO_VERSION)
|
|
{
|
|
LogError(
|
|
"protocol version mismatch ", int(pkt[PacketOverhead]), " != ", LLARP_PROTO_VERSION);
|
|
itr = msgs.erase(itr);
|
|
continue;
|
|
}
|
|
++itr;
|
|
}
|
|
m_PlaintextRecv.tryPushBack(std::move(msgs));
|
|
m_PlaintextEmpty.clear();
|
|
m_Parent->WakeupPlaintext();
|
|
}
|
|
|
|
void
|
|
Session::HandlePlaintext()
|
|
{
|
|
if (m_PlaintextEmpty.test_and_set())
|
|
return;
|
|
while (auto maybe_queue = m_PlaintextRecv.tryPopFront())
|
|
{
|
|
for (auto& result : *maybe_queue)
|
|
{
|
|
LogTrace("Command ", int(result[PacketOverhead + 1]), " from ", m_RemoteAddr);
|
|
switch (result[PacketOverhead + 1])
|
|
{
|
|
case Command::eXMIT:
|
|
HandleXMIT(std::move(result));
|
|
break;
|
|
case Command::eDATA:
|
|
HandleDATA(std::move(result));
|
|
break;
|
|
case Command::eACKS:
|
|
HandleACKS(std::move(result));
|
|
break;
|
|
case Command::ePING:
|
|
HandlePING(std::move(result));
|
|
break;
|
|
case Command::eNACK:
|
|
HandleNACK(std::move(result));
|
|
break;
|
|
case Command::eCLOS:
|
|
HandleCLOS(std::move(result));
|
|
break;
|
|
case Command::eMACK:
|
|
HandleMACK(std::move(result));
|
|
break;
|
|
default:
|
|
LogError("invalid command ", int(result[PacketOverhead + 1]), " from ", m_RemoteAddr);
|
|
}
|
|
}
|
|
}
|
|
SendMACK();
|
|
m_Parent->WakeupPlaintext();
|
|
}
|
|
|
|
void
|
|
Session::HandleMACK(Packet_t data)
|
|
{
|
|
if (data.size() < (3 + PacketOverhead))
|
|
{
|
|
LogError("impossibly short mack from ", m_RemoteAddr);
|
|
return;
|
|
}
|
|
byte_t numAcks = data[CommandOverhead + PacketOverhead];
|
|
if (data.size() < 1 + CommandOverhead + PacketOverhead + (numAcks * sizeof(uint64_t)))
|
|
{
|
|
LogError("short mack from ", m_RemoteAddr);
|
|
return;
|
|
}
|
|
LogTrace("got ", int(numAcks), " mack from ", m_RemoteAddr);
|
|
byte_t* ptr = data.data() + CommandOverhead + PacketOverhead + 1;
|
|
while (numAcks > 0)
|
|
{
|
|
uint64_t acked = bufbe64toh(ptr);
|
|
LogTrace("mack containing txid=", acked, " from ", m_RemoteAddr);
|
|
auto itr = m_TXMsgs.find(acked);
|
|
if (itr != m_TXMsgs.end())
|
|
{
|
|
m_Stats.totalAckedTX++;
|
|
m_Stats.totalInFlightTX--;
|
|
itr->second.Completed();
|
|
m_TXMsgs.erase(itr);
|
|
}
|
|
else
|
|
{
|
|
LogTrace("ignored mack for txid=", acked, " from ", m_RemoteAddr);
|
|
}
|
|
ptr += sizeof(uint64_t);
|
|
numAcks--;
|
|
}
|
|
}
|
|
|
|
void
|
|
Session::HandleNACK(Packet_t data)
|
|
{
|
|
if (data.size() < (CommandOverhead + sizeof(uint64_t) + PacketOverhead))
|
|
{
|
|
LogError("short nack from ", m_RemoteAddr);
|
|
return;
|
|
}
|
|
uint64_t txid = bufbe64toh(data.data() + CommandOverhead + PacketOverhead);
|
|
LogTrace("got nack on ", txid, " from ", m_RemoteAddr);
|
|
auto itr = m_TXMsgs.find(txid);
|
|
if (itr != m_TXMsgs.end())
|
|
{
|
|
EncryptAndSend(itr->second.XMIT());
|
|
}
|
|
m_LastRX = m_Parent->Now();
|
|
}
|
|
|
|
void
|
|
Session::HandleXMIT(Packet_t data)
|
|
{
|
|
static constexpr size_t XMITOverhead =
|
|
(CommandOverhead + PacketOverhead + sizeof(uint16_t) + sizeof(uint64_t)
|
|
+ ShortHash::SIZE);
|
|
if (data.size() < XMITOverhead)
|
|
{
|
|
LogError("short XMIT from ", m_RemoteAddr);
|
|
return;
|
|
}
|
|
auto* pos = data.data() + CommandOverhead + PacketOverhead;
|
|
uint16_t sz = bufbe16toh(pos);
|
|
pos += sizeof(sz);
|
|
uint64_t rxid = bufbe64toh(pos);
|
|
pos += sizeof(rxid);
|
|
auto p2 = pos + ShortHash::SIZE;
|
|
assert(p2 == data.data() + XMITOverhead);
|
|
LogTrace("rxid=", rxid, " sz=", sz, " h=", oxenc::to_hex(pos, p2), " from ", m_RemoteAddr);
|
|
m_LastRX = m_Parent->Now();
|
|
{
|
|
// check for replay
|
|
auto itr = m_ReplayFilter.find(rxid);
|
|
if (itr != m_ReplayFilter.end())
|
|
{
|
|
m_SendMACKs.emplace(rxid);
|
|
LogTrace("duplicate rxid=", rxid, " from ", m_RemoteAddr);
|
|
return;
|
|
}
|
|
}
|
|
{
|
|
const auto now = m_Parent->Now();
|
|
auto itr = m_RXMsgs.find(rxid);
|
|
if (itr == m_RXMsgs.end())
|
|
{
|
|
itr = m_RXMsgs.emplace(rxid, InboundMessage{rxid, sz, ShortHash{pos}, m_Parent->Now()})
|
|
.first;
|
|
TriggerPump();
|
|
|
|
sz = std::min(sz, uint16_t{FragmentSize});
|
|
if ((data.size() - XMITOverhead) == sz)
|
|
{
|
|
{
|
|
const llarp_buffer_t buf(data.data() + (data.size() - sz), sz);
|
|
itr->second.HandleData(0, buf, now);
|
|
if (not itr->second.IsCompleted())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (not itr->second.Verify())
|
|
{
|
|
LogError("bad short xmit hash from ", m_RemoteAddr);
|
|
return;
|
|
}
|
|
}
|
|
HandleRecvMsgCompleted(itr->second);
|
|
}
|
|
}
|
|
else
|
|
LogTrace("got duplicate xmit on ", rxid, " from ", m_RemoteAddr);
|
|
}
|
|
}
|
|
|
|
void
|
|
Session::HandleDATA(Packet_t data)
|
|
{
|
|
if (data.size() < (CommandOverhead + sizeof(uint16_t) + sizeof(uint64_t) + PacketOverhead))
|
|
{
|
|
LogError("short DATA from ", m_RemoteAddr, " ", data.size());
|
|
return;
|
|
}
|
|
m_LastRX = m_Parent->Now();
|
|
uint16_t sz = bufbe16toh(data.data() + CommandOverhead + PacketOverhead);
|
|
uint64_t rxid = bufbe64toh(data.data() + CommandOverhead + sizeof(uint16_t) + PacketOverhead);
|
|
auto itr = m_RXMsgs.find(rxid);
|
|
if (itr == m_RXMsgs.end())
|
|
{
|
|
if (m_ReplayFilter.find(rxid) == m_ReplayFilter.end())
|
|
{
|
|
LogTrace("no rxid=", rxid, " for ", m_RemoteAddr);
|
|
auto nack = CreatePacket(Command::eNACK, 8);
|
|
htobe64buf(nack.data() + PacketOverhead + CommandOverhead, rxid);
|
|
EncryptAndSend(std::move(nack));
|
|
}
|
|
else
|
|
{
|
|
LogTrace("replay hit for rxid=", rxid, " for ", m_RemoteAddr);
|
|
m_SendMACKs.emplace(rxid);
|
|
}
|
|
return;
|
|
}
|
|
|
|
{
|
|
const llarp_buffer_t buf(
|
|
data.data() + PacketOverhead + 12, data.size() - (PacketOverhead + 12));
|
|
itr->second.HandleData(sz, buf, m_Parent->Now());
|
|
}
|
|
|
|
if (itr->second.IsCompleted())
|
|
{
|
|
if (itr->second.Verify())
|
|
{
|
|
HandleRecvMsgCompleted(itr->second);
|
|
}
|
|
else
|
|
{
|
|
LogError("hash mismatch for message ", itr->first);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
Session::HandleRecvMsgCompleted(const InboundMessage& msg)
|
|
{
|
|
const auto rxid = msg.m_MsgID;
|
|
if (m_ReplayFilter.emplace(rxid, m_Parent->Now()).second)
|
|
{
|
|
m_Parent->HandleMessage(this, msg.m_Data);
|
|
EncryptAndSend(msg.ACKS());
|
|
LogDebug("recv'd message ", rxid, " from ", m_RemoteAddr);
|
|
}
|
|
m_RXMsgs.erase(rxid);
|
|
}
|
|
|
|
void
|
|
Session::HandleACKS(Packet_t data)
|
|
{
|
|
if (data.size() < (11 + PacketOverhead))
|
|
{
|
|
LogError("short ACKS from ", m_RemoteAddr);
|
|
return;
|
|
}
|
|
const auto now = m_Parent->Now();
|
|
m_LastRX = now;
|
|
uint64_t txid = bufbe64toh(data.data() + 2 + PacketOverhead);
|
|
auto itr = m_TXMsgs.find(txid);
|
|
if (itr == m_TXMsgs.end())
|
|
{
|
|
LogTrace("no txid=", txid, " for ", m_RemoteAddr);
|
|
return;
|
|
}
|
|
itr->second.Ack(data[10 + PacketOverhead]);
|
|
|
|
if (itr->second.IsTransmitted())
|
|
{
|
|
LogDebug("sent message ", itr->first, " to ", m_RemoteAddr);
|
|
itr->second.Completed();
|
|
itr = m_TXMsgs.erase(itr);
|
|
}
|
|
else
|
|
{
|
|
itr->second.FlushUnAcked(util::memFn(&Session::EncryptAndSend, this), now);
|
|
}
|
|
}
|
|
|
|
void Session::HandleCLOS(Packet_t)
|
|
{
|
|
LogInfo("remote closed by ", m_RemoteAddr);
|
|
Close();
|
|
}
|
|
|
|
void Session::HandlePING(Packet_t)
|
|
{
|
|
m_LastRX = m_Parent->Now();
|
|
}
|
|
|
|
bool
|
|
Session::SendKeepAlive()
|
|
{
|
|
if (m_State == State::Ready)
|
|
{
|
|
EncryptAndSend(CreatePacket(Command::ePING, 0));
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
Session::IsEstablished() const
|
|
{
|
|
return m_State == State::Ready;
|
|
}
|
|
|
|
bool
|
|
Session::Recv_LL(ILinkSession::Packet_t data)
|
|
{
|
|
m_RXRate += data.size();
|
|
|
|
// TODO: differentiate between good and bad RX packets here
|
|
m_Stats.totalPacketsRX++;
|
|
switch (m_State)
|
|
{
|
|
case State::Initial:
|
|
if (m_Inbound)
|
|
{
|
|
// initial data
|
|
// enter introduction phase
|
|
if (DecryptMessageInPlace(data))
|
|
{
|
|
HandleGotIntro(std::move(data));
|
|
}
|
|
else
|
|
{
|
|
LogDebug("bad intro from ", m_RemoteAddr);
|
|
return false;
|
|
}
|
|
}
|
|
break;
|
|
case State::Introduction:
|
|
if (m_Inbound)
|
|
{
|
|
// we are replying to an intro ack
|
|
HandleCreateSessionRequest(std::move(data));
|
|
}
|
|
else
|
|
{
|
|
// we got an intro ack
|
|
// send a session request
|
|
HandleGotIntroAck(std::move(data));
|
|
}
|
|
break;
|
|
case State::LinkIntro:
|
|
default:
|
|
HandleSessionData(std::move(data));
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
std::string
|
|
Session::StateToString(State state)
|
|
{
|
|
switch (state)
|
|
{
|
|
case State::Initial:
|
|
return "Initial";
|
|
case State::Introduction:
|
|
return "Introduction";
|
|
case State::LinkIntro:
|
|
return "LinkIntro";
|
|
case State::Ready:
|
|
return "Ready";
|
|
case State::Closed:
|
|
return "Close";
|
|
default:
|
|
return "Invalid";
|
|
}
|
|
}
|
|
} // namespace iwp
|
|
} // namespace llarp
|