mirror of
https://github.com/oxen-io/lokinet.git
synced 2024-11-11 07:10:36 +00:00
fc050b3a09
when setting libunbound's upstream dns, we need to not pass in the square braces of an ipv6 address. we also net udp handles have ipv6 address for the local ip.
385 lines
9.2 KiB
C++
385 lines
9.2 KiB
C++
#include "libuv.hpp"
|
|
#include <memory>
|
|
#include <thread>
|
|
#include <type_traits>
|
|
#include <cstring>
|
|
|
|
#include <llarp/util/exceptions.hpp>
|
|
#include <llarp/util/thread/queue.hpp>
|
|
#include <llarp/vpn/platform.hpp>
|
|
|
|
#include <uvw.hpp>
|
|
|
|
namespace llarp::uv
|
|
{
|
|
std::shared_ptr<uvw::Loop>
|
|
Loop::MaybeGetUVWLoop()
|
|
{
|
|
return m_Impl;
|
|
}
|
|
|
|
class UVWakeup final : public EventLoopWakeup
|
|
{
|
|
std::shared_ptr<uvw::AsyncHandle> async;
|
|
|
|
public:
|
|
UVWakeup(uvw::Loop& loop, std::function<void()> callback)
|
|
: async{loop.resource<uvw::AsyncHandle>()}
|
|
{
|
|
async->on<uvw::AsyncEvent>([f = std::move(callback)](auto&, auto&) { f(); });
|
|
}
|
|
|
|
void
|
|
Trigger() override
|
|
{
|
|
async->send();
|
|
}
|
|
|
|
~UVWakeup() override
|
|
{
|
|
async->close();
|
|
}
|
|
};
|
|
|
|
class UVRepeater final : public EventLoopRepeater
|
|
{
|
|
std::shared_ptr<uvw::TimerHandle> timer;
|
|
|
|
public:
|
|
UVRepeater(uvw::Loop& loop) : timer{loop.resource<uvw::TimerHandle>()}
|
|
{}
|
|
|
|
void
|
|
start(llarp_time_t every, std::function<void()> task) override
|
|
{
|
|
timer->start(every, every);
|
|
timer->on<uvw::TimerEvent>([task = std::move(task)](auto&, auto&) { task(); });
|
|
}
|
|
|
|
~UVRepeater() override
|
|
{
|
|
timer->stop();
|
|
}
|
|
};
|
|
|
|
struct UDPHandle final : llarp::UDPHandle
|
|
{
|
|
UDPHandle(uvw::Loop& loop, ReceiveFunc rf);
|
|
|
|
bool
|
|
listen(const SockAddr& addr) override;
|
|
|
|
bool
|
|
send(const SockAddr& dest, const llarp_buffer_t& buf) override;
|
|
|
|
std::optional<SockAddr>
|
|
LocalAddr() const override
|
|
{
|
|
if (auto addr = handle->sock<uvw::IPv4>(); not addr.ip.empty())
|
|
return SockAddr{addr.ip, huint16_t{static_cast<uint16_t>(addr.port)}};
|
|
if (auto addr = handle->sock<uvw::IPv6>(); not addr.ip.empty())
|
|
return SockAddr{addr.ip, huint16_t{static_cast<uint16_t>(addr.port)}};
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::optional<int>
|
|
file_descriptor() override
|
|
{
|
|
#ifndef _WIN32
|
|
if (int fd = handle->fd(); fd >= 0)
|
|
return fd;
|
|
#endif
|
|
return std::nullopt;
|
|
}
|
|
|
|
void
|
|
close() override;
|
|
|
|
~UDPHandle() override;
|
|
|
|
private:
|
|
std::shared_ptr<uvw::UDPHandle> handle;
|
|
|
|
void
|
|
reset_handle(uvw::Loop& loop);
|
|
};
|
|
|
|
void
|
|
Loop::FlushLogic()
|
|
{
|
|
llarp::LogTrace("Loop::FlushLogic() start");
|
|
while (not m_LogicCalls.empty())
|
|
{
|
|
auto f = m_LogicCalls.popFront();
|
|
f();
|
|
}
|
|
llarp::LogTrace("Loop::FlushLogic() end");
|
|
}
|
|
|
|
void
|
|
Loop::tick_event_loop()
|
|
{
|
|
llarp::LogTrace("ticking event loop.");
|
|
FlushLogic();
|
|
}
|
|
|
|
Loop::Loop(size_t queue_size) : llarp::EventLoop{}, m_LogicCalls{queue_size}
|
|
{
|
|
if (!(m_Impl = uvw::Loop::create()))
|
|
throw std::runtime_error{"Failed to construct libuv loop"};
|
|
|
|
#ifdef LOKINET_DEBUG
|
|
last_time = 0;
|
|
loop_run_count = 0;
|
|
#endif
|
|
|
|
#ifndef _WIN32
|
|
signal(SIGPIPE, SIG_IGN);
|
|
#endif
|
|
|
|
m_Run.store(true);
|
|
m_nextID.store(0);
|
|
if (!(m_WakeUp = m_Impl->resource<uvw::AsyncHandle>()))
|
|
throw std::runtime_error{"Failed to create libuv async"};
|
|
m_WakeUp->on<uvw::AsyncEvent>([this](const auto&, auto&) { tick_event_loop(); });
|
|
}
|
|
|
|
bool
|
|
Loop::running() const
|
|
{
|
|
return m_Run.load();
|
|
}
|
|
|
|
void
|
|
Loop::run()
|
|
{
|
|
llarp::LogTrace("Loop::run_loop()");
|
|
m_EventLoopThreadID = std::this_thread::get_id();
|
|
m_Impl->run();
|
|
m_Impl->close();
|
|
m_Impl.reset();
|
|
llarp::LogInfo("we have stopped");
|
|
}
|
|
|
|
void
|
|
Loop::wakeup()
|
|
{
|
|
m_WakeUp->send();
|
|
}
|
|
|
|
std::shared_ptr<llarp::UDPHandle>
|
|
Loop::make_udp(UDPReceiveFunc on_recv)
|
|
{
|
|
return std::static_pointer_cast<llarp::UDPHandle>(
|
|
std::make_shared<llarp::uv::UDPHandle>(*m_Impl, std::move(on_recv)));
|
|
}
|
|
|
|
static void
|
|
setup_oneshot_timer(uvw::Loop& loop, llarp_time_t delay, std::function<void()> callback)
|
|
{
|
|
auto timer = loop.resource<uvw::TimerHandle>();
|
|
timer->on<uvw::TimerEvent>([f = std::move(callback)](const auto&, auto& timer) {
|
|
f();
|
|
timer.stop();
|
|
timer.close();
|
|
});
|
|
timer->start(delay, 0ms);
|
|
}
|
|
|
|
void
|
|
Loop::call_later(llarp_time_t delay_ms, std::function<void(void)> callback)
|
|
{
|
|
llarp::LogTrace("Loop::call_after_delay()");
|
|
#ifdef TESTNET_SPEED
|
|
delay_ms *= TESTNET_SPEED;
|
|
#endif
|
|
|
|
if (inEventLoop())
|
|
setup_oneshot_timer(*m_Impl, delay_ms, std::move(callback));
|
|
else
|
|
{
|
|
call_soon([this, f = std::move(callback), target_time = time_now() + delay_ms] {
|
|
// Recalculate delay because it may have taken some time to get ourselves into the logic
|
|
// thread
|
|
auto updated_delay = target_time - time_now();
|
|
if (updated_delay <= 0ms)
|
|
f(); // Timer already expired!
|
|
else
|
|
setup_oneshot_timer(*m_Impl, updated_delay, std::move(f));
|
|
});
|
|
}
|
|
}
|
|
|
|
void
|
|
Loop::stop()
|
|
{
|
|
if (m_Run)
|
|
{
|
|
if (not inEventLoop())
|
|
return call_soon([this] { stop(); });
|
|
|
|
llarp::LogInfo("stopping event loop");
|
|
m_Impl->walk([](auto&& handle) {
|
|
if constexpr (!std::is_pointer_v<std::remove_reference_t<decltype(handle)>>)
|
|
handle.close();
|
|
});
|
|
llarp::LogDebug("Closed all handles, stopping the loop");
|
|
m_Impl->stop();
|
|
|
|
m_Run.store(false);
|
|
}
|
|
}
|
|
|
|
bool
|
|
Loop::add_ticker(std::function<void(void)> func)
|
|
{
|
|
auto check = m_Impl->resource<uvw::CheckHandle>();
|
|
check->on<uvw::CheckEvent>([f = std::move(func)](auto&, auto&) { f(); });
|
|
check->start();
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
Loop::add_network_interface(
|
|
std::shared_ptr<llarp::vpn::NetworkInterface> netif,
|
|
std::function<void(llarp::net::IPPacket)> handler)
|
|
{
|
|
#ifdef __linux__
|
|
using event_t = uvw::PollEvent;
|
|
auto handle = m_Impl->resource<uvw::PollHandle>(netif->PollFD());
|
|
#else
|
|
// we use a uv_prepare_t because it fires before blocking for new io events unconditionally
|
|
// we want to match what linux does, using a uv_check_t does not suffice as the order of
|
|
// operations is not what we need.
|
|
using event_t = uvw::PrepareEvent;
|
|
auto handle = m_Impl->resource<uvw::PrepareHandle>();
|
|
#endif
|
|
|
|
if (!handle)
|
|
return false;
|
|
|
|
handle->on<event_t>([netif = std::move(netif), handler = std::move(handler)](
|
|
const event_t&, [[maybe_unused]] auto& handle) {
|
|
for (auto pkt = netif->ReadNextPacket(); true; pkt = netif->ReadNextPacket())
|
|
{
|
|
if (pkt.empty())
|
|
return;
|
|
if (handler)
|
|
handler(std::move(pkt));
|
|
// on windows/apple, vpn packet io does not happen as an io action that wakes up the event
|
|
// loop thus, we must manually wake up the event loop when we get a packet on our interface.
|
|
// on linux/android this is a nop
|
|
netif->MaybeWakeUpperLayers();
|
|
}
|
|
});
|
|
|
|
#ifdef __linux__
|
|
handle->start(uvw::PollHandle::Event::READABLE);
|
|
#else
|
|
handle->start();
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
Loop::call_soon(std::function<void(void)> f)
|
|
{
|
|
if (not m_EventLoopThreadID.has_value())
|
|
{
|
|
m_LogicCalls.tryPushBack(f);
|
|
m_WakeUp->send();
|
|
return;
|
|
}
|
|
|
|
if (inEventLoop() and m_LogicCalls.full())
|
|
{
|
|
FlushLogic();
|
|
}
|
|
m_LogicCalls.pushBack(f);
|
|
m_WakeUp->send();
|
|
}
|
|
|
|
// Sets `handle` to a new uvw UDP handle, first initiating a close and then disowning the handle
|
|
// if already set, allocating the resource, and setting the receive event on it.
|
|
void
|
|
UDPHandle::reset_handle(uvw::Loop& loop)
|
|
{
|
|
if (handle)
|
|
handle->close();
|
|
handle = loop.resource<uvw::UDPHandle>();
|
|
handle->on<uvw::UDPDataEvent>([this](auto& event, auto& /*handle*/) {
|
|
on_recv(
|
|
*this,
|
|
SockAddr{event.sender.ip, huint16_t{static_cast<uint16_t>(event.sender.port)}},
|
|
OwnedBuffer{std::move(event.data), event.length});
|
|
});
|
|
}
|
|
|
|
llarp::uv::UDPHandle::UDPHandle(uvw::Loop& loop, ReceiveFunc rf) : llarp::UDPHandle{std::move(rf)}
|
|
{
|
|
reset_handle(loop);
|
|
}
|
|
|
|
bool
|
|
UDPHandle::listen(const SockAddr& addr)
|
|
{
|
|
if (handle->active())
|
|
reset_handle(handle->loop());
|
|
|
|
auto err = handle->on<uvw::ErrorEvent>([addr](auto& event, auto&) {
|
|
throw llarp::util::bind_socket_error{
|
|
fmt::format("failed to bind udp socket on {}: {}", addr, event.what())};
|
|
});
|
|
handle->bind(*static_cast<const sockaddr*>(addr));
|
|
handle->recv();
|
|
handle->erase(err);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
UDPHandle::send(const SockAddr& to, const llarp_buffer_t& buf)
|
|
{
|
|
return handle->trySend(
|
|
*static_cast<const sockaddr*>(to),
|
|
const_cast<char*>(reinterpret_cast<const char*>(buf.base)),
|
|
buf.sz)
|
|
>= 0;
|
|
}
|
|
|
|
void
|
|
UDPHandle::close()
|
|
{
|
|
handle->close();
|
|
handle.reset();
|
|
}
|
|
|
|
UDPHandle::~UDPHandle()
|
|
{
|
|
close();
|
|
}
|
|
|
|
std::shared_ptr<llarp::EventLoopWakeup>
|
|
Loop::make_waker(std::function<void()> callback)
|
|
{
|
|
return std::static_pointer_cast<llarp::EventLoopWakeup>(
|
|
std::make_shared<UVWakeup>(*m_Impl, std::move(callback)));
|
|
}
|
|
|
|
std::shared_ptr<EventLoopRepeater>
|
|
Loop::make_repeater()
|
|
{
|
|
return std::static_pointer_cast<EventLoopRepeater>(std::make_shared<UVRepeater>(*m_Impl));
|
|
}
|
|
|
|
bool
|
|
Loop::inEventLoop() const
|
|
{
|
|
if (m_EventLoopThreadID)
|
|
return *m_EventLoopThreadID == std::this_thread::get_id();
|
|
// assume we are in it because we haven't started up yet
|
|
return true;
|
|
}
|
|
|
|
} // namespace llarp::uv
|