lokinet/llarp/link/link_manager.cpp

330 lines
7.9 KiB
C++
Raw Normal View History

#include "link_manager.hpp"
2023-08-28 20:50:06 +00:00
#include <llarp/router/i_rc_lookup_handler.hpp>
#include <llarp/nodedb.hpp>
#include <llarp/crypto/crypto.hpp>
#include <algorithm>
#include <set>
namespace llarp
{
2023-08-28 20:50:06 +00:00
link::Endpoint*
LinkManager::GetCompatibleLink(const RouterContact&)
{
if (stopping)
return nullptr;
2023-08-28 20:50:06 +00:00
for (auto& ep : endpoints)
{
2023-08-29 14:26:59 +00:00
// TODO: need some notion of "is this link compatible with that address".
// iwp just checks that the link dialect ("iwp") matches the address info dialect,
// but that feels insufficient. For now, just return the first endpoint we have;
// we should probably only have 1 for now anyway until we make ipv6 work.
return &ep;
}
return nullptr;
}
2023-08-29 14:26:59 +00:00
// TODO: replace with control/data message sending with libquic
bool
LinkManager::SendTo(
const RouterID& remote,
const llarp_buffer_t&,
2023-08-29 14:26:59 +00:00
AbstractLinkSession::CompletionHandler completed,
uint16_t)
{
if (stopping)
return false;
2023-08-28 20:50:06 +00:00
if (not HaveConnection(remote))
{
if (completed)
2019-07-26 16:19:31 +00:00
{
2023-08-29 14:26:59 +00:00
completed(AbstractLinkSession::DeliveryStatus::eDeliveryDropped);
2019-07-26 16:19:31 +00:00
}
return false;
}
2023-08-29 14:26:59 +00:00
// TODO: send the message
// TODO: if we keep bool return type, change this accordingly
2023-08-28 20:50:06 +00:00
return false;
}
bool
2023-08-28 20:50:06 +00:00
LinkManager::HaveConnection(const RouterID& remote, bool client_only) const
{
for (const auto& ep : endpoints)
{
if (auto itr = ep.connections.find(remote); itr != ep.connections.end())
{
2023-08-29 14:26:59 +00:00
if (not(itr->second.remote_is_relay and client_only))
2023-08-28 20:50:06 +00:00
return true;
return false;
}
}
return false;
}
2023-08-28 20:50:06 +00:00
bool
LinkManager::HaveClientConnection(const RouterID& remote) const
{
return HaveConnection(remote, true);
}
void
LinkManager::DeregisterPeer(RouterID remote)
{
m_PersistingSessions.erase(remote);
for (const auto& ep : endpoints)
{
if (auto itr = ep.connections.find(remote); itr != ep.connections.end())
2023-08-28 17:39:40 +00:00
{
2023-08-28 20:50:06 +00:00
/*
itr->second.conn->close(); //TODO: libquic needs some function for this
2023-08-28 20:50:06 +00:00
*/
2023-08-28 17:39:40 +00:00
}
}
LogInfo(remote, " has been de-registered");
}
void
2023-08-28 20:50:06 +00:00
LinkManager::AddLink(const oxen::quic::opt::local_addr& bind, bool inbound)
{
2023-08-29 14:26:59 +00:00
// TODO: libquic callbacks: new_conn_alpn_notify, new_conn_pubkey_ok, new_conn_established/ready
// stream_opened, stream_data, stream_closed, conn_closed
oxen::quic::dgram_data_callback dgram_cb =
[this](oxen::quic::dgram_interface& dgi, bstring dgram) {
HandleIncomingDataMessage(dgi, dgram);
};
auto ep = quic->endpoint(
bind,
std::move(dgram_cb),
oxen::quic::opt::enable_datagrams{oxen::quic::Splitting::ACTIVE});
endpoints.emplace_back();
auto& endp = endpoints.back();
endp.endpoint = std::move(ep);
if (inbound)
{
endp.endpoint->listen(tls_creds);
endp.inbound = true;
}
}
void
LinkManager::Stop()
{
if (stopping)
{
return;
}
De-abseil, part 2: mutex, locks, (most) time - util::Mutex is now a std::shared_timed_mutex, which is capable of exclusive and shared locks. - util::Lock is still present as a std::lock_guard<util::Mutex>. - the locking annotations are preserved, but updated to the latest supported by clang rather than using abseil's older/deprecated ones. - ACQUIRE_LOCK macro is gone since we don't pass mutexes by pointer into locks anymore (WTF abseil). - ReleasableLock is gone. Instead there are now some llarp::util helper methods to obtain unique and/or shared locks: - `auto lock = util::unique_lock(mutex);` gets an RAII-but-also unlockable object (std::unique_lock<T>, with T inferred from `mutex`). - `auto lock = util::shared_lock(mutex);` gets an RAII shared (i.e. "reader") lock of the mutex. - `auto lock = util::unique_locks(mutex1, mutex2, mutex3);` can be used to atomically lock multiple mutexes at once (returning a tuple of the locks). This are templated on the mutex which makes them a bit more flexible than using a concrete type: they can be used for any type of lockable mutex, not only util::Mutex. (Some of the code here uses them for getting locks around a std::mutex). Until C++17, using the RAII types is painfully verbose: ```C++ // pre-C++17 - needing to figure out the mutex type here is annoying: std::unique_lock<util::Mutex> lock(mutex); // pre-C++17 and even more verbose (but at least the type isn't needed): std::unique_lock<decltype(mutex)> lock(mutex); // our compromise: auto lock = util::unique_lock(mutex); // C++17: std::unique_lock lock(mutex); ``` All of these functions will also warn (under gcc or clang) if you discard the return value. You can also do fancy things like `auto l = util::unique_lock(mutex, std::adopt_lock)` (which lets a lock take over an already-locked mutex). - metrics code is gone, which also removes a big pile of code that was only used by metrics: - llarp::util::Scheduler - llarp::thread::TimerQueue - llarp::util::Stopwatch
2020-02-21 17:21:11 +00:00
util::Lock l(_mutex);
LogInfo("stopping links");
stopping = true;
quic.reset();
}
void
LinkManager::PersistSessionUntil(const RouterID& remote, llarp_time_t until)
{
if (stopping)
return;
De-abseil, part 2: mutex, locks, (most) time - util::Mutex is now a std::shared_timed_mutex, which is capable of exclusive and shared locks. - util::Lock is still present as a std::lock_guard<util::Mutex>. - the locking annotations are preserved, but updated to the latest supported by clang rather than using abseil's older/deprecated ones. - ACQUIRE_LOCK macro is gone since we don't pass mutexes by pointer into locks anymore (WTF abseil). - ReleasableLock is gone. Instead there are now some llarp::util helper methods to obtain unique and/or shared locks: - `auto lock = util::unique_lock(mutex);` gets an RAII-but-also unlockable object (std::unique_lock<T>, with T inferred from `mutex`). - `auto lock = util::shared_lock(mutex);` gets an RAII shared (i.e. "reader") lock of the mutex. - `auto lock = util::unique_locks(mutex1, mutex2, mutex3);` can be used to atomically lock multiple mutexes at once (returning a tuple of the locks). This are templated on the mutex which makes them a bit more flexible than using a concrete type: they can be used for any type of lockable mutex, not only util::Mutex. (Some of the code here uses them for getting locks around a std::mutex). Until C++17, using the RAII types is painfully verbose: ```C++ // pre-C++17 - needing to figure out the mutex type here is annoying: std::unique_lock<util::Mutex> lock(mutex); // pre-C++17 and even more verbose (but at least the type isn't needed): std::unique_lock<decltype(mutex)> lock(mutex); // our compromise: auto lock = util::unique_lock(mutex); // C++17: std::unique_lock lock(mutex); ``` All of these functions will also warn (under gcc or clang) if you discard the return value. You can also do fancy things like `auto l = util::unique_lock(mutex, std::adopt_lock)` (which lets a lock take over an already-locked mutex). - metrics code is gone, which also removes a big pile of code that was only used by metrics: - llarp::util::Scheduler - llarp::thread::TimerQueue - llarp::util::Stopwatch
2020-02-21 17:21:11 +00:00
util::Lock l(_mutex);
2021-06-06 14:51:29 +00:00
m_PersistingSessions[remote] = std::max(until, m_PersistingSessions[remote]);
if (HaveClientConnection(remote))
2021-06-07 12:39:38 +00:00
{
// mark this as a client so we don't try to back connect
m_Clients.Upsert(remote);
2021-06-07 12:39:38 +00:00
}
}
size_t
LinkManager::NumberOfConnectedRouters(bool clients_only) const
{
size_t count{0};
for (const auto& ep : endpoints)
{
for (const auto& conn : ep.connections)
{
2023-08-29 14:26:59 +00:00
if (not(conn.second.remote_is_relay and clients_only))
count++;
}
}
return count;
}
size_t
LinkManager::NumberOfConnectedClients() const
{
return NumberOfConnectedRouters(true);
2019-12-03 17:03:19 +00:00
}
bool
LinkManager::GetRandomConnectedRouter(RouterContact& router) const
{
std::unordered_map<RouterID, RouterContact> connectedRouters;
2023-08-28 20:50:06 +00:00
for (const auto& ep : endpoints)
{
for (const auto& [router_id, conn] : ep.connections)
{
connectedRouters.emplace(router_id, conn.remote_rc);
}
}
const auto sz = connectedRouters.size();
if (sz)
{
auto itr = connectedRouters.begin();
if (sz > 1)
{
std::advance(itr, randint() % sz);
}
router = itr->second;
return true;
}
return false;
}
2023-08-29 14:26:59 +00:00
// TODO: this? perhaps no longer necessary in the same way?
void
LinkManager::CheckPersistingSessions(llarp_time_t)
{
if (stopping)
return;
}
2023-08-29 14:26:59 +00:00
// TODO: do we still need this concept?
2020-06-04 16:00:30 +00:00
void
LinkManager::updatePeerDb(std::shared_ptr<PeerDb>)
2023-08-29 14:26:59 +00:00
{}
2020-06-04 16:00:30 +00:00
2023-08-29 14:26:59 +00:00
// TODO: this
util::StatusObject
LinkManager::ExtractStatus() const
{
return {};
}
void
LinkManager::Init(I_RCLookupHandler* rcLookup)
{
stopping = false;
_rcLookup = rcLookup;
_nodedb = router->nodedb();
}
void
LinkManager::Connect(RouterID router)
{
auto fn = [this](const RouterID&, const RouterContact* const rc, const RCRequestResult res) {
2023-08-29 14:26:59 +00:00
if (res == RCRequestResult::Success)
Connect(*rc);
/* TODO:
else
RC lookup failure callback here
*/
};
_rcLookup->GetRC(router, fn);
}
// This function assumes the RC has already had its signature verified and connection is allowed.
void
LinkManager::Connect(RouterContact rc)
{
2023-08-29 14:26:59 +00:00
// TODO: connection failed callback
if (HaveConnection(rc.pubkey))
return;
// RC shouldn't be valid if this is the case, but may as well sanity check...
2023-08-29 14:26:59 +00:00
// TODO: connection failed callback
if (rc.addrs.empty())
return;
2023-08-29 14:26:59 +00:00
// TODO: connection failed callback
auto* ep = GetCompatibleLink(rc);
if (ep == nullptr)
return;
2023-08-29 14:26:59 +00:00
// TODO: connection established/failed callbacks
oxen::quic::stream_data_callback stream_cb =
[this](oxen::quic::Stream& stream, bstring_view packet) {
HandleIncomingControlMessage(stream, packet);
};
2023-08-29 14:26:59 +00:00
// TODO: once "compatible link" cares about address, actually choose addr to connect to
// based on which one is compatible with the link we chose. For now, just use
// the first one.
auto& selected = rc.addrs[0];
2023-08-28 20:50:06 +00:00
oxen::quic::opt::remote_addr remote{selected.IPString(), selected.port};
2023-08-29 14:26:59 +00:00
// TODO: confirm remote end is using the expected pubkey (RouterID).
// TODO: ALPN for "client" vs "relay" (could just be set on endpoint creation)
// TODO: does connect() inherit the endpoint's datagram data callback, and do we want it to if
// so?
auto conn_interface = ep->endpoint->connect(remote, stream_cb, tls_creds);
std::shared_ptr<oxen::quic::Stream> stream = conn_interface->get_new_stream();
llarp::link::Connection conn;
conn.conn = conn_interface;
conn.control_stream = stream;
conn.remote_rc = rc;
conn.inbound = false;
conn.remote_is_relay = true;
ep->connections[rc.pubkey] = std::move(conn);
ep->connid_map[conn_interface->scid()] = rc.pubkey;
}
void
LinkManager::ConnectToRandomRouters(int numDesired)
{
std::set<RouterID> exclude;
2023-08-28 20:50:06 +00:00
auto remainingDesired = numDesired;
do
{
auto filter = [exclude](const auto& rc) -> bool { return exclude.count(rc.pubkey) == 0; };
RouterContact other;
if (const auto maybe = _nodedb->GetRandom(filter))
{
other = *maybe;
}
else
break;
exclude.insert(other.pubkey);
if (not _rcLookup->SessionIsAllowed(other.pubkey))
continue;
Connect(other);
--remainingDesired;
} while (remainingDesired > 0);
}
void
LinkManager::HandleIncomingDataMessage(oxen::quic::dgram_interface&, bstring)
{
2023-08-29 14:26:59 +00:00
// TODO: this
}
void
LinkManager::HandleIncomingControlMessage(oxen::quic::Stream&, bstring_view)
{
2023-08-29 14:26:59 +00:00
// TODO: this
}
} // namespace llarp