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.
lokinet/llarp/path/path.cpp

608 lines
15 KiB
C++

#include "path.hpp"
#include <llarp/messages/dht.hpp>
#include <llarp/messages/exit.hpp>
#include <llarp/profiling.hpp>
#include <llarp/router/router.hpp>
#include <llarp/util/buffer.hpp>
10 months ago
namespace llarp::path
{
10 months ago
Path::Path(
Router* rtr,
10 months ago
const std::vector<RouterContact>& h,
std::weak_ptr<PathSet> pathset,
PathRole startingRoles,
std::string shortName)
: m_PathSet{std::move(pathset)}
, router{*rtr}
, _role{startingRoles}
, m_shortName{std::move(shortName)}
10 months ago
{
hops.resize(h.size());
size_t hsz = h.size();
for (size_t idx = 0; idx < hsz; ++idx)
{
10 months ago
hops[idx].rc = h[idx];
do
{
10 months ago
hops[idx].txID.Randomize();
} while (hops[idx].txID.IsZero());
10 months ago
do
{
10 months ago
hops[idx].rxID.Randomize();
} while (hops[idx].rxID.IsZero());
}
10 months ago
for (size_t idx = 0; idx < hsz - 1; ++idx)
{
10 months ago
hops[idx].txID = hops[idx + 1].rxID;
}
10 months ago
// initialize parts of the introduction
intro.router = hops[hsz - 1].rc.pubkey;
intro.path_id = hops[hsz - 1].txID;
10 months ago
if (auto parent = m_PathSet.lock())
EnterState(ePathBuilding, parent->Now());
}
bool
Path::obtain_exit(
SecretKey sk,
uint64_t flag,
std::string tx_id,
std::function<void(oxen::quic::message m)> func)
{
return send_path_control_message(
"obtain_exit",
ObtainExitMessage::sign_and_serialize(sk, flag, std::move(tx_id)),
std::move(func));
}
bool
Path::close_exit(SecretKey sk, std::string tx_id, std::function<void(oxen::quic::message m)> func)
{
return send_path_control_message(
"close_exit", CloseExitMessage::sign_and_serialize(sk, std::move(tx_id)), std::move(func));
}
bool
Path::find_intro(
const dht::Key_t& location,
bool is_relayed,
uint64_t order,
std::function<void(oxen::quic::message m)> func)
{
return send_path_control_message(
"find_intro", FindIntroMessage::serialize(location, is_relayed, order), std::move(func));
}
bool
Path::find_name(std::string name, std::function<void(oxen::quic::message m)> func)
{
return send_path_control_message(
"find_name", FindNameMessage::serialize(std::move(name)), std::move(func));
}
bool
Path::find_router(std::string rid, std::function<void(oxen::quic::message m)> func)
{
return send_path_control_message(
"find_router", FindRouterMessage::serialize(std::move(rid), false, false), std::move(func));
}
bool
Path::send_path_control_message(
std::string method, std::string body, std::function<void(oxen::quic::message m)> func)
{
std::string payload;
{
oxenc::bt_dict_producer btdp;
btdp.append("BODY", body);
btdp.append("METHOD", method);
payload = std::move(btdp).str();
}
TunnelNonce nonce;
nonce.Randomize();
for (const auto& hop : hops)
{
// do a round of chacha for each hop and mutate the nonce with that hop's nonce
crypto::xchacha20(
reinterpret_cast<unsigned char*>(payload.data()), payload.size(), hop.shared, nonce);
nonce ^= hop.nonceXOR;
}
oxenc::bt_dict_producer outer_dict;
outer_dict.append("NONCE", nonce.ToView());
outer_dict.append("PATHID", TXID().ToView());
outer_dict.append("PAYLOAD", payload);
return router.send_control_message(
upstream(),
"path_control",
std::move(outer_dict).str(),
[response_cb = std::move(func)](oxen::quic::message m) {
if (m)
{
// do path hop logic here
}
});
10 months ago
}
10 months ago
bool
Path::HandleUpstream(const llarp_buffer_t& X, const TunnelNonce& Y, Router* r)
10 months ago
{
if (not m_UpstreamReplayFilter.Insert(Y))
return false;
return AbstractHopHandler::HandleUpstream(X, Y, r);
10 months ago
}
10 months ago
bool
Path::HandleDownstream(const llarp_buffer_t& X, const TunnelNonce& Y, Router* r)
10 months ago
{
if (not m_DownstreamReplayFilter.Insert(Y))
return false;
return AbstractHopHandler::HandleDownstream(X, Y, r);
10 months ago
}
10 months ago
RouterID
Path::Endpoint() const
{
return hops[hops.size() - 1].rc.pubkey;
}
10 months ago
PubKey
Path::EndpointPubKey() const
{
return hops[hops.size() - 1].rc.pubkey;
}
10 months ago
PathID_t
Path::TXID() const
{
return hops[0].txID;
}
10 months ago
PathID_t
Path::RXID() const
{
return hops[0].rxID;
}
10 months ago
bool
Path::IsReady() const
{
if (Expired(llarp::time_now_ms()))
return false;
return intro.latency > 0s && _status == ePathEstablished;
}
5 years ago
10 months ago
bool
Path::is_endpoint(const RouterID& r, const PathID_t& id) const
10 months ago
{
return hops[hops.size() - 1].rc.pubkey == r && hops[hops.size() - 1].txID == id;
}
RouterID
Path::upstream() const
10 months ago
{
return hops[0].rc.pubkey;
}
10 months ago
const std::string&
Path::ShortName() const
{
return m_shortName;
}
std::string
Path::HopsString() const
{
std::string hops_str;
hops_str.reserve(hops.size() * 62); // 52 for the pkey, 6 for .snode, 4 for the ' -> ' joiner
for (const auto& hop : hops)
{
10 months ago
if (!hops.empty())
hops_str += " -> ";
hops_str += RouterID(hop.rc.pubkey).ToString();
}
10 months ago
return hops_str;
}
void
Path::EnterState(PathStatus st, llarp_time_t now)
{
if (st == ePathFailed)
{
10 months ago
_status = st;
return;
}
10 months ago
if (st == ePathExpired && _status == ePathBuilding)
{
10 months ago
_status = st;
if (auto parent = m_PathSet.lock())
{
10 months ago
parent->HandlePathBuildTimeout(shared_from_this());
}
}
10 months ago
else if (st == ePathBuilding)
6 years ago
{
LogInfo("path ", name(), " is building");
10 months ago
buildStarted = now;
}
10 months ago
else if (st == ePathEstablished && _status == ePathBuilding)
{
LogInfo("path ", name(), " is built, took ", ToString(now - buildStarted));
}
10 months ago
else if (st == ePathTimeout && _status == ePathEstablished)
{
LogInfo("path ", name(), " died");
10 months ago
_status = st;
if (auto parent = m_PathSet.lock())
{
10 months ago
parent->HandlePathDied(shared_from_this());
}
}
10 months ago
else if (st == ePathEstablished && _status == ePathTimeout)
{
LogInfo("path ", name(), " reanimated");
6 years ago
}
10 months ago
else if (st == ePathIgnore)
{
LogInfo("path ", name(), " ignored");
}
10 months ago
_status = st;
}
6 years ago
10 months ago
util::StatusObject
PathHopConfig::ExtractStatus() const
{
util::StatusObject obj{
{"ip", rc.addr.to_string()},
10 months ago
{"lifetime", to_json(lifetime)},
{"router", rc.pubkey.ToHex()},
{"txid", txID.ToHex()},
{"rxid", rxID.ToHex()}};
return obj;
}
util::StatusObject
Path::ExtractStatus() const
{
auto now = llarp::time_now_ms();
util::StatusObject obj{
{"intro", intro.ExtractStatus()},
{"lastRecvMsg", to_json(m_LastRecvMessage)},
{"lastLatencyTest", to_json(m_LastLatencyTestTime)},
{"buildStarted", to_json(buildStarted)},
{"expired", Expired(now)},
{"expiresSoon", ExpiresSoon(now)},
{"expiresAt", to_json(ExpireTime())},
{"ready", IsReady()},
{"txRateCurrent", m_LastTXRate},
{"rxRateCurrent", m_LastRXRate},
{"replayTX", m_UpstreamReplayFilter.Size()},
{"replayRX", m_DownstreamReplayFilter.Size()},
{"hasExit", SupportsAnyRoles(ePathRoleExit)}};
std::vector<util::StatusObject> hopsObj;
std::transform(
hops.begin(),
hops.end(),
std::back_inserter(hopsObj),
[](const auto& hop) -> util::StatusObject { return hop.ExtractStatus(); });
obj["hops"] = hopsObj;
switch (_status)
{
case ePathBuilding:
obj["status"] = "building";
break;
case ePathEstablished:
obj["status"] = "established";
break;
case ePathTimeout:
obj["status"] = "timeout";
break;
case ePathExpired:
obj["status"] = "expired";
break;
case ePathFailed:
obj["status"] = "failed";
break;
case ePathIgnore:
obj["status"] = "ignored";
break;
default:
obj["status"] = "unknown";
break;
}
return obj;
}
void
Path::Rebuild()
{
if (auto parent = m_PathSet.lock())
{
10 months ago
std::vector<RouterContact> newHops;
for (const auto& hop : hops)
newHops.emplace_back(hop.rc);
LogInfo(name(), " rebuilding on ", ShortName());
10 months ago
parent->Build(newHops);
}
10 months ago
}
bool
Path::SendLatencyMessage(Router*)
{
// const auto now = r->now();
// // send path latency test
// routing::PathLatencyMessage latency{};
// latency.sent_time = randint();
// latency.sequence_number = NextSeqNo();
// m_LastLatencyTestID = latency.sent_time;
// m_LastLatencyTestTime = now;
// LogDebug(name(), " send latency test id=", latency.sent_time);
// if (not SendRoutingMessage(latency, r))
// return false;
// FlushUpstream(r);
10 months ago
return true;
}
bool
Path::update_exit(uint64_t)
{
// TODO: do we still want this concept?
return false;
}
10 months ago
void
Path::Tick(llarp_time_t now, Router* r)
10 months ago
{
if (Expired(now))
return;
10 months ago
m_LastRXRate = m_RXRate;
m_LastTXRate = m_TXRate;
m_RXRate = 0;
m_TXRate = 0;
if (_status == ePathBuilding)
{
10 months ago
if (buildStarted == 0s)
return;
if (now >= buildStarted)
{
10 months ago
const auto dlt = now - buildStarted;
if (dlt >= path::BUILD_TIMEOUT)
{
LogWarn(name(), " waited for ", ToString(dlt), " and no path was built");
r->router_profiling().MarkPathFail(this);
10 months ago
EnterState(ePathExpired, now);
return;
}
}
}
10 months ago
// check to see if this path is dead
if (_status == ePathEstablished)
{
10 months ago
auto dlt = now - m_LastLatencyTestTime;
if (dlt > path::LATENCY_INTERVAL && m_LastLatencyTestID == 0)
{
10 months ago
SendLatencyMessage(r);
// latency test FEC
r->loop()->call_later(2s, [self = shared_from_this(), r]() {
if (self->m_LastLatencyTestID)
self->SendLatencyMessage(r);
});
return;
}
10 months ago
dlt = now - m_LastRecvMessage;
if (dlt >= path::ALIVE_TIMEOUT)
{
LogWarn(name(), " waited for ", ToString(dlt), " and path looks dead");
r->router_profiling().MarkPathFail(this);
10 months ago
EnterState(ePathTimeout, now);
}
}
if (_status == ePathIgnore and now - m_LastRecvMessage >= path::ALIVE_TIMEOUT)
10 months ago
{
// clean up this path as we dont use it anymore
EnterState(ePathExpired, now);
}
}
10 months ago
void
Path::HandleAllUpstream(std::vector<RelayUpstreamMessage> msgs, Router* r)
10 months ago
{
for (const auto& msg : msgs)
6 years ago
{
8 months ago
if (r->send_data_message(upstream(), msg.bt_encode()))
6 years ago
{
10 months ago
m_TXRate += msg.enc.size();
6 years ago
}
10 months ago
else
6 years ago
{
LogDebug("failed to send upstream to ", upstream());
6 years ago
}
}
10 months ago
r->TriggerPump();
}
6 years ago
10 months ago
void
Path::UpstreamWork(TrafficQueue_t msgs, Router* r)
10 months ago
{
std::vector<RelayUpstreamMessage> sendmsgs(msgs.size());
size_t idx = 0;
for (auto& ev : msgs)
{
10 months ago
TunnelNonce n = ev.second;
uint8_t* buf = ev.first.data();
size_t sz = ev.first.size();
10 months ago
for (const auto& hop : hops)
{
crypto::xchacha20(buf, sz, hop.shared, n);
10 months ago
n ^= hop.nonceXOR;
}
10 months ago
auto& msg = sendmsgs[idx];
std::memcpy(msg.enc.data(), buf, sz);
10 months ago
msg.nonce = ev.second;
msg.pathid = TXID();
++idx;
}
10 months ago
r->loop()->call([self = shared_from_this(), data = std::move(sendmsgs), r]() mutable {
self->HandleAllUpstream(std::move(data), r);
});
}
10 months ago
void
Path::FlushUpstream(Router* r)
10 months ago
{
if (not m_UpstreamQueue.empty())
{
r->queue_work([self = shared_from_this(),
data = std::exchange(m_UpstreamQueue, {}),
r]() mutable { self->UpstreamWork(std::move(data), r); });
}
10 months ago
}
10 months ago
void
Path::FlushDownstream(Router* r)
10 months ago
{
if (not m_DownstreamQueue.empty())
{
r->queue_work([self = shared_from_this(),
data = std::exchange(m_DownstreamQueue, {}),
r]() mutable { self->DownstreamWork(std::move(data), r); });
}
10 months ago
}
10 months ago
/// how long we wait for a path to become active again after it times out
constexpr auto PathReanimationTimeout = 45s;
10 months ago
bool
Path::Expired(llarp_time_t now) const
{
if (_status == ePathFailed)
return true;
if (_status == ePathBuilding)
return false;
10 months ago
if (_status == ePathTimeout)
{
return now >= m_LastRecvMessage + PathReanimationTimeout;
}
10 months ago
if (_status == ePathEstablished or _status == ePathIgnore)
{
10 months ago
return now >= ExpireTime();
}
10 months ago
return true;
}
10 months ago
std::string
Path::name() const
10 months ago
{
return fmt::format("TX={} RX={}", TXID(), RXID());
}
void
Path::DownstreamWork(TrafficQueue_t msgs, Router* r)
10 months ago
{
std::vector<RelayDownstreamMessage> sendMsgs(msgs.size());
size_t idx = 0;
for (auto& ev : msgs)
{
10 months ago
sendMsgs[idx].nonce = ev.second;
uint8_t* buf = ev.first.data();
size_t sz = ev.first.size();
10 months ago
for (const auto& hop : hops)
{
10 months ago
sendMsgs[idx].nonce ^= hop.nonceXOR;
crypto::xchacha20(buf, sz, hop.shared, sendMsgs[idx].nonce);
}
std::memcpy(sendMsgs[idx].enc.data(), buf, sz);
10 months ago
++idx;
}
10 months ago
r->loop()->call([self = shared_from_this(), msgs = std::move(sendMsgs), r]() mutable {
self->HandleAllDownstream(std::move(msgs), r);
});
}
10 months ago
void
Path::HandleAllDownstream(std::vector<RelayDownstreamMessage> msgs, Router* /* r */)
10 months ago
{
for (const auto& msg : msgs)
{
10 months ago
const llarp_buffer_t buf{msg.enc};
m_RXRate += buf.sz;
// if (HandleRoutingMessage(buf, r))
// {
// r->TriggerPump();
// m_LastRecvMessage = r->now();
// }
}
10 months ago
}
/** Note: this is one of two places where AbstractRoutingMessage::bt_encode() is called, the
other of which is llarp/path/transit_hop.cpp in TransitHop::SendRoutingMessage(). For now,
we will default to the override of ::bt_encode() that returns an std::string. The role that
llarp_buffer_t plays here is likely superfluous, and can be replaced with either a leaner
llarp_buffer, or just handled using strings.
One important consideration is the frequency at which routing messages are sent, making
superfluous copies important to optimize out here. We have to instantiate at least one
std::string whether we pass a bt_dict_producer as a reference or create one within the
::bt_encode() call.
If we decide to stay with std::strings, the function Path::HandleUpstream (along with the
functions it calls and so on) will need to be modified to take an std::string that we can
std::move around.
*/
10 months ago
bool
Path::SendRoutingMessage(std::string payload, Router*)
10 months ago
{
std::string buf(MAX_LINK_MSG_SIZE / 2, '\0');
buf.insert(0, payload);
10 months ago
// make nonce
TunnelNonce N;
N.Randomize();
10 months ago
// pad smaller messages
if (payload.size() < PAD_SIZE)
10 months ago
{
// randomize padding
crypto::randbytes(
reinterpret_cast<unsigned char*>(buf.data()) + payload.size(), PAD_SIZE - payload.size());
10 months ago
}
log::debug(path_cat, "Sending {}B routing message to {}", buf.size(), Endpoint());
// TODO: path relaying here
return true;
10 months ago
}
template <typename Samples_t>
static llarp_time_t
computeLatency(const Samples_t& samps)
{
llarp_time_t mean = 0s;
if (samps.empty())
return mean;
for (const auto& samp : samps)
mean += samp;
return mean / samps.size();
}
} // namespace llarp::path