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

519 lines
14 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>
1 year ago
namespace llarp::path
{
1 year ago
Path::Path(
Router* rtr,
const std::vector<RemoteRC>& h,
1 year ago
std::weak_ptr<PathSet> pathset,
PathRole startingRoles,
std::string shortName)
: m_PathSet{std::move(pathset)}
, router{*rtr}
, _role{startingRoles}
, m_shortName{std::move(shortName)}
1 year ago
{
hops.resize(h.size());
size_t hsz = h.size();
for (size_t idx = 0; idx < hsz; ++idx)
{
1 year ago
hops[idx].rc = h[idx];
do
{
1 year ago
hops[idx].txID.Randomize();
} while (hops[idx].txID.IsZero());
1 year ago
do
{
1 year ago
hops[idx].rxID.Randomize();
} while (hops[idx].rxID.IsZero());
}
1 year ago
for (size_t idx = 0; idx < hsz - 1; ++idx)
{
1 year ago
hops[idx].txID = hops[idx + 1].rxID;
}
1 year ago
// initialize parts of the introduction
intro.router = hops[hsz - 1].rc.router_id();
intro.path_id = hops[hsz - 1].txID;
1 year 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(std::string)> 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(std::string)> 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(std::string)> 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(std::string)> 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(std::string)> 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(std::string)> func)
{
oxenc::bt_dict_producer btdp;
btdp.append("BODY", body);
btdp.append("METHOD", method);
auto payload = std::move(btdp).str();
// TODO: old impl padded messages if smaller than a certain size; do we still want to?
SymmNonce nonce;
nonce.Randomize();
// chacha and mutate nonce for each hop
for (const auto& hop : hops)
{
nonce = crypto::onion(
reinterpret_cast<unsigned char*>(payload.data()),
payload.size(),
hop.shared,
nonce,
hop.nonceXOR);
}
auto outer_payload = make_onion_payload(nonce, TXID(), payload);
return router.send_control_message(
upstream(),
"path_control",
std::move(outer_payload),
[response_cb = std::move(func), weak = weak_from_this()](oxen::quic::message m) {
auto self = weak.lock();
// TODO: do we want to allow empty callback here?
if ((not self) or (not response_cb))
return;
if (m.timed_out)
{
response_cb(messages::TIMEOUT_BT_DICT);
return;
}
SymmNonce nonce{};
std::string payload;
try
{
oxenc::bt_dict_consumer btdc{m.body()};
auto nonce = SymmNonce{btdc.require<ustring_view>("NONCE").data()};
auto payload = btdc.require<std::string>("PAYLOAD");
}
catch (const std::exception& e)
{
log::warning(path_cat, "Error parsing path control message response: {}", e.what());
response_cb(messages::ERROR_BT_DICT);
return;
}
for (const auto& hop : self->hops)
{
nonce = crypto::onion(
reinterpret_cast<unsigned char*>(payload.data()),
payload.size(),
hop.shared,
nonce,
hop.nonceXOR);
}
// TODO: should we do anything (even really simple) here to check if the decrypted
// response is sensible (e.g. is a bt dict)? Parsing and handling of the
// contents (errors or otherwise) is the currently responsibility of the callback.
response_cb(payload);
});
1 year ago
}
1 year ago
RouterID
Path::Endpoint() const
{
return hops[hops.size() - 1].rc.router_id();
1 year ago
}
1 year ago
PubKey
Path::EndpointPubKey() const
{
return hops[hops.size() - 1].rc.router_id();
1 year ago
}
1 year ago
PathID_t
Path::TXID() const
{
return hops[0].txID;
}
1 year ago
PathID_t
Path::RXID() const
{
return hops[0].rxID;
}
1 year ago
bool
Path::IsReady() const
{
if (Expired(llarp::time_now_ms()))
return false;
return intro.latency > 0s && _status == ePathEstablished;
}
6 years ago
1 year ago
bool
Path::is_endpoint(const RouterID& r, const PathID_t& id) const
1 year ago
{
return hops[hops.size() - 1].rc.router_id() == r && hops[hops.size() - 1].txID == id;
1 year ago
}
RouterID
Path::upstream() const
1 year ago
{
return hops[0].rc.router_id();
1 year ago
}
1 year 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)
{
1 year ago
if (!hops.empty())
hops_str += " -> ";
hops_str += hop.rc.router_id().ToView();
}
1 year ago
return hops_str;
}
void
Path::EnterState(PathStatus st, llarp_time_t now)
{
if (now == 0s) now = router.now();
1 year ago
if (st == ePathFailed)
{
1 year ago
_status = st;
return;
}
1 year ago
if (st == ePathExpired && _status == ePathBuilding)
{
1 year ago
_status = st;
if (auto parent = m_PathSet.lock())
{
1 year ago
parent->HandlePathBuildTimeout(shared_from_this());
}
}
1 year ago
else if (st == ePathBuilding)
6 years ago
{
LogInfo("path ", name(), " is building");
1 year ago
buildStarted = now;
}
1 year ago
else if (st == ePathEstablished && _status == ePathBuilding)
{
LogInfo("path ", name(), " is built, took ", ToString(now - buildStarted));
}
1 year ago
else if (st == ePathTimeout && _status == ePathEstablished)
{
LogInfo("path ", name(), " died");
1 year ago
_status = st;
if (auto parent = m_PathSet.lock())
{
1 year ago
parent->HandlePathDied(shared_from_this());
}
}
1 year ago
else if (st == ePathEstablished && _status == ePathTimeout)
{
LogInfo("path ", name(), " reanimated");
6 years ago
}
1 year ago
else if (st == ePathIgnore)
{
LogInfo("path ", name(), " ignored");
}
1 year ago
_status = st;
}
6 years ago
1 year ago
util::StatusObject
PathHopConfig::ExtractStatus() const
{
util::StatusObject obj{
{"ip", rc.addr().to_string()},
1 year ago
{"lifetime", to_json(lifetime)},
{"router", rc.router_id().ToHex()},
1 year ago
{"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},
{"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())
{
std::vector<RemoteRC> new_hops;
1 year ago
for (const auto& hop : hops)
new_hops.emplace_back(hop.rc);
LogInfo(name(), " rebuilding on ", ShortName());
parent->Build(new_hops);
}
1 year 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);
1 year ago
return true;
}
bool
Path::update_exit(uint64_t)
{
// TODO: do we still want this concept?
return false;
}
1 year ago
void
Path::Tick(llarp_time_t now, Router* r)
1 year ago
{
if (Expired(now))
return;
1 year ago
m_LastRXRate = m_RXRate;
m_LastTXRate = m_TXRate;
m_RXRate = 0;
m_TXRate = 0;
if (_status == ePathBuilding)
{
1 year ago
if (buildStarted == 0s)
return;
if (now >= buildStarted)
{
1 year 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);
1 year ago
EnterState(ePathExpired, now);
return;
}
}
}
1 year ago
// check to see if this path is dead
if (_status == ePathEstablished)
{
1 year ago
auto dlt = now - m_LastLatencyTestTime;
if (dlt > path::LATENCY_INTERVAL && m_LastLatencyTestID == 0)
{
1 year ago
SendLatencyMessage(r);
// latency test FEC
r->loop()->call_later(2s, [self = shared_from_this(), r]() {
if (self->m_LastLatencyTestID)
self->SendLatencyMessage(r);
});
return;
}
1 year 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);
1 year ago
EnterState(ePathTimeout, now);
}
}
if (_status == ePathIgnore and now - m_LastRecvMessage >= path::ALIVE_TIMEOUT)
1 year ago
{
// clean up this path as we dont use it anymore
EnterState(ePathExpired, now);
}
}
1 year ago
/// how long we wait for a path to become active again after it times out
constexpr auto PathReanimationTimeout = 45s;
1 year ago
bool
Path::Expired(llarp_time_t now) const
{
if (_status == ePathFailed)
return true;
if (_status == ePathBuilding)
return false;
1 year ago
if (_status == ePathTimeout)
{
return now >= m_LastRecvMessage + PathReanimationTimeout;
}
1 year ago
if (_status == ePathEstablished or _status == ePathIgnore)
{
1 year ago
return now >= ExpireTime();
}
1 year ago
return true;
}
1 year ago
std::string
Path::name() const
1 year ago
{
return fmt::format("TX={} RX={}", TXID(), RXID());
}
/** 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.
*/
/* TODO: replace this with sending an onion-ed data message
1 year ago
bool
Path::SendRoutingMessage(std::string payload, Router*)
1 year ago
{
std::string buf(MAX_LINK_MSG_SIZE / 2, '\0');
buf.insert(0, payload);
1 year ago
// make nonce
TunnelNonce N;
N.Randomize();
1 year ago
// pad smaller messages
if (payload.size() < PAD_SIZE)
1 year ago
{
// randomize padding
crypto::randbytes(
reinterpret_cast<unsigned char*>(buf.data()) + payload.size(), PAD_SIZE - payload.size());
1 year ago
}
log::debug(path_cat, "Sending {}B routing message to {}", buf.size(), Endpoint());
// TODO: path relaying here
return true;
1 year ago
}
*/
1 year 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