Merge pull request #2045 from majestrate/windows-service-issues

windows platform bug fixes
pull/2046/head
majestrate 2 years ago committed by GitHub
commit 1c51bb1041
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -163,7 +163,7 @@ local windows_cross_pipeline(name,
'echo "man-db man-db/auto-update boolean false" | debconf-set-selections',
apt_get_quiet + ' update',
apt_get_quiet + ' install -y eatmydata',
'eatmydata ' + apt_get_quiet + ' install --no-install-recommends -y build-essential cmake git pkg-config ccache g++-mingw-w64-x86-64-posix nsis zip icoutils automake libtool librsvg2-bin',
'eatmydata ' + apt_get_quiet + ' install --no-install-recommends -y build-essential cmake git pkg-config ccache g++-mingw-w64-x86-64-posix nsis zip icoutils automake libtool librsvg2-bin bison',
'update-alternatives --set x86_64-w64-mingw32-gcc /usr/bin/x86_64-w64-mingw32-gcc-posix',
'update-alternatives --set x86_64-w64-mingw32-g++ /usr/bin/x86_64-w64-mingw32-g++-posix',
'JOBS=' + jobs + ' VERBOSE=1 ./contrib/windows.sh -DSTRIP_SYMBOLS=ON -DGUI_EXE=$${DRONE_WORKSPACE}/gui/release/Lokinet-GUI_portable.exe' +

@ -306,8 +306,15 @@ build_external(expat
)
add_static_target(expat expat_external libexpat.a)
if(WIN32)
set(unbound_patch
PATCH_COMMAND ${PROJECT_SOURCE_DIR}/contrib/apply-patches.sh
${PROJECT_SOURCE_DIR}/contrib/patches/unbound-delete-crash-fix.patch)
endif()
build_external(unbound
DEPENDS openssl_external expat_external
${unbound_patch}
CONFIGURE_COMMAND ./configure ${cross_host} ${cross_rc} --prefix=${DEPS_DESTDIR} --disable-shared
--enable-static --with-libunbound-only --with-pic
--$<IF:$<BOOL:${WITH_LTO}>,enable,disable>-flto --with-ssl=${DEPS_DESTDIR}

@ -0,0 +1,33 @@
commit 56d816014d5e8a7eb055169c7e13a303dad5e50f
Author: Jason Rhinelander <jason@imaginary.ca>
Date: Mon Oct 31 22:07:03 2022 -0300
Set tube->ev_listen to NULL to prevent double unregister
On windows when using threaded mode (i.e. `ub_ctx_async(ctx, 1)`)
tube_remove_bg_listen gets called twice: once when the thread does its
own cleanup, then again in `tube_delete()`. Because `ev_listen` doesn't
get cleared, however, we end we calling ub_winsock_unregister_wsaevent
with a freed pointer.
This doesn't always manifest because, apparently, for various compilers
and settings that memory *might* be overwritten in which case the
additional check for ev->magic will prevent anything actually happening,
but in my case under mingw32 that doesn't happen and we end up
eventually crashing.
This fixes the crash by properly NULLing the pointer so that the second
ub_winsock_unregister_wsaevent(...) becomes a no-op.
diff --git a/util/tube.c b/util/tube.c
index 43455fee..a92dfa77 100644
--- a/util/tube.c
+++ b/util/tube.c
@@ -570,6 +570,7 @@ void tube_remove_bg_listen(struct tube* tube)
{
verbose(VERB_ALGO, "tube remove_bg_listen");
ub_winsock_unregister_wsaevent(tube->ev_listen);
+ tube->ev_listen = NULL;
}
void tube_remove_bg_write(struct tube* tube)

@ -7,7 +7,10 @@
#include <llarp/util/str.hpp>
#ifdef _WIN32
#include <llarp/win32/service_manager.hpp>
#include <dbghelp.h>
#else
#include <llarp/util/service_manager.hpp>
#endif
#include <csignal>
@ -21,25 +24,24 @@ int
lokinet_main(int, char**);
#ifdef _WIN32
#include <strsafe.h>
extern "C" LONG FAR PASCAL
win32_signal_handler(EXCEPTION_POINTERS*);
extern "C" VOID FAR PASCAL
win32_daemon_entry(DWORD, LPTSTR*);
BOOL ReportSvcStatus(DWORD, DWORD, DWORD);
VOID
insert_description();
SERVICE_STATUS SvcStatus;
SERVICE_STATUS_HANDLE SvcStatusHandle;
bool start_as_daemon = false;
#endif
static auto logcat = llarp::log::Cat("main");
std::shared_ptr<llarp::Context> ctx;
std::promise<int> exit_code;
void
handle_signal(int sig)
{
llarp::log::info(logcat, "Handling signal {}", sig);
if (ctx)
ctx->loop->call([sig] { ctx->HandleSignal(sig); });
else
@ -82,9 +84,6 @@ install_win32_daemon()
llarp::LogError("Cannot install service ", GetLastError());
return;
}
// just put the flag here. we eat it later on and specify the
// config path in the daemon entry point
StringCchCat(szPath.data(), 1024, " --win32-daemon");
// Get a handle to the SCM database.
schSCManager = OpenSCManager(
@ -292,37 +291,6 @@ run_main_context(std::optional<fs::path> confFile, const llarp::RuntimeOptions o
}
#ifdef _WIN32
void
TellWindowsServiceStopped()
{
::WSACleanup();
if (not start_as_daemon)
return;
llarp::LogInfo("Telling Windows the service has stopped.");
if (not ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0))
{
auto error_code = GetLastError();
if (error_code == ERROR_INVALID_DATA)
llarp::LogError(
"SetServiceStatus failed: \"The specified service status structure is invalid.\"");
else if (error_code == ERROR_INVALID_HANDLE)
llarp::LogError("SetServiceStatus failed: \"The specified handle is invalid.\"");
else
llarp::LogError("SetServiceStatus failed with an unknown error.");
}
}
class WindowsServiceStopped
{
public:
WindowsServiceStopped() = default;
~WindowsServiceStopped()
{
TellWindowsServiceStopped();
}
};
/// minidump generation for windows jizz
/// will make a coredump when there is an unhandled exception
@ -363,46 +331,57 @@ GenerateDump(EXCEPTION_POINTERS* pExceptionPointers)
int
main(int argc, char* argv[])
{
// Set up a default, stderr logging for very early logging; we'll replace this later once we read
// the desired log info from config.
llarp::log::add_sink(llarp::log::Type::Print, "stderr");
llarp::log::reset_level(llarp::log::Level::info);
llarp::logRingBuffer = std::make_shared<llarp::log::RingBufferSink>(100);
llarp::log::add_sink(llarp::logRingBuffer, llarp::log::DEFAULT_PATTERN_MONO);
#ifndef _WIN32
return lokinet_main(argc, argv);
#else
SERVICE_TABLE_ENTRY DispatchTable[] = {
{strdup("lokinet"), (LPSERVICE_MAIN_FUNCTION)win32_daemon_entry}, {NULL, NULL}};
if (lstrcmpi(argv[1], "--win32-daemon") == 0)
// Try first to run as a service; if this works it fires off to win32_daemon_entry and doesn't
// return until the service enters STOPPED state.
if (StartServiceCtrlDispatcher(DispatchTable))
return 0;
auto error = GetLastError();
// We'll get this error if not invoked as a service, which is fine: we can just run directly
if (error == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT)
{
start_as_daemon = true;
StartServiceCtrlDispatcher(DispatchTable);
llarp::sys::service_manager->disable();
return lokinet_main(argc, argv);
}
else
return lokinet_main(argc, argv);
{
llarp::log::critical(
logcat, "Error launching service: {}", std::system_category().message(error));
return 1;
}
#endif
}
int
lokinet_main(int argc, char* argv[])
lokinet_main(int argc, char** argv)
{
if (auto result = Lokinet_INIT())
return result;
// Set up a default, stderr logging for very early logging; we'll replace this later once we read
// the desired log info from config.
llarp::log::add_sink(llarp::log::Type::Print, "stderr");
llarp::log::reset_level(llarp::log::Level::info);
llarp::logRingBuffer = std::make_shared<llarp::log::RingBufferSink>(100);
llarp::log::add_sink(llarp::logRingBuffer, llarp::log::DEFAULT_PATTERN_MONO);
llarp::RuntimeOptions opts;
opts.showBanner = false;
#ifdef _WIN32
WindowsServiceStopped stopped_raii;
if (startWinsock())
return -1;
SetConsoleCtrlHandler(handle_signal_win32, TRUE);
// SetUnhandledExceptionFilter(win32_signal_handler);
#endif
cxxopts::Options options(
"lokinet",
"LokiNET is a free, open source, private, "
@ -543,13 +522,9 @@ lokinet_main(int argc, char* argv[])
SetUnhandledExceptionFilter(&GenerateDump);
#endif
std::thread main_thread{[&] { run_main_context(configFile, opts); }};
std::thread main_thread{[configFile, opts] { run_main_context(configFile, opts); }};
auto ftr = exit_code.get_future();
#ifdef _WIN32
ReportSvcStatus(SERVICE_RUNNING, NO_ERROR, 0);
#endif
do
{
// do periodic non lokinet related tasks here
@ -580,9 +555,7 @@ lokinet_main(int argc, char* argv[])
llarp::log::critical(deadlock_cat, wtf);
llarp::log::flush();
}
#ifdef _WIN32
TellWindowsServiceStopped();
#endif
llarp::sys::service_manager->failed();
std::abort();
}
} while (ftr.wait_for(std::chrono::seconds(1)) != std::future_status::ready);
@ -607,6 +580,7 @@ lokinet_main(int argc, char* argv[])
}
llarp::log::flush();
llarp::sys::service_manager->stopped();
if (ctx)
{
ctx.reset();
@ -615,29 +589,6 @@ lokinet_main(int argc, char* argv[])
}
#ifdef _WIN32
BOOL
ReportSvcStatus(DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwWaitHint)
{
static DWORD dwCheckPoint = 1;
// Fill in the SERVICE_STATUS structure.
SvcStatus.dwCurrentState = dwCurrentState;
SvcStatus.dwWin32ExitCode = dwWin32ExitCode;
SvcStatus.dwWaitHint = dwWaitHint;
if (dwCurrentState == SERVICE_START_PENDING)
SvcStatus.dwControlsAccepted = 0;
else
SvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
if ((dwCurrentState == SERVICE_RUNNING) || (dwCurrentState == SERVICE_STOPPED))
SvcStatus.dwCheckPoint = 0;
else
SvcStatus.dwCheckPoint = dwCheckPoint++;
// Report the status of the service to the SCM.
return SetServiceStatus(SvcStatusHandle, &SvcStatus);
}
VOID FAR PASCAL
SvcCtrlHandler(DWORD dwCtrl)
@ -647,44 +598,45 @@ SvcCtrlHandler(DWORD dwCtrl)
switch (dwCtrl)
{
case SERVICE_CONTROL_STOP:
ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0);
// Signal the service to stop.
// tell service we are stopping
llarp::log::debug(logcat, "Windows service controller gave SERVICE_CONTROL_STOP");
llarp::sys::service_manager->system_changed_our_state(llarp::sys::ServiceState::Stopping);
handle_signal(SIGINT);
return;
case SERVICE_CONTROL_INTERROGATE:
break;
// report status
llarp::log::debug(logcat, "Got win32 service interrogate signal");
llarp::sys::service_manager->report_changed_state();
return;
default:
llarp::log::debug(logcat, "Got win32 unhandled signal {}", dwCtrl);
break;
}
}
// The win32 daemon entry point is just a trampoline that returns control
// to the original lokinet entry
// and only gets called if we get --win32-daemon in the command line
// The win32 daemon entry point is where we go when invoked as a windows service; we do the required
// service dance and then pretend we were invoked via main().
VOID FAR PASCAL
win32_daemon_entry(DWORD argc, LPTSTR* argv)
win32_daemon_entry(DWORD, LPTSTR* argv)
{
// Register the handler function for the service
SvcStatusHandle = RegisterServiceCtrlHandler("lokinet", SvcCtrlHandler);
auto* svc = dynamic_cast<llarp::sys::SVC_Manager*>(llarp::sys::service_manager);
svc->handle = RegisterServiceCtrlHandler("lokinet", SvcCtrlHandler);
if (!SvcStatusHandle)
if (svc->handle == nullptr)
{
llarp::LogError("failed to register daemon control handler");
return;
}
// These SERVICE_STATUS members remain as set here
SvcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
SvcStatus.dwServiceSpecificExitCode = 0;
// Report initial status to the SCM
ReportSvcStatus(SERVICE_START_PENDING, NO_ERROR, 3000);
// SCM clobbers startup args, regenerate them here
argc = 2;
argv[1] = strdup("c:\\programdata\\lokinet\\lokinet.ini");
argv[2] = nullptr;
lokinet_main(argc, argv);
// we hard code the args to lokinet_main.
// we yoink argv[0] (lokinet.exe path) and pass in the new args.
std::array args = {
reinterpret_cast<char*>(argv[0]),
reinterpret_cast<char*>(strdup("c:\\programdata\\lokinet\\lokinet.ini")),
reinterpret_cast<char*>(0)};
lokinet_main(args.size() - 1, args.data());
}
#endif

2
gui

@ -1 +1 @@
Subproject commit 7b0f1aacdf79b558adfc39dc9cccb7e348aeec03
Subproject commit a66130d8d6d246737265d19249c3456575c0f1f1

@ -45,6 +45,7 @@ namespace llarp
std::shared_ptr<NodeDB> nodedb = nullptr;
std::string nodedb_dir;
Context();
virtual ~Context() = default;
void

@ -49,17 +49,23 @@ target_link_libraries(lokinet-platform PUBLIC lokinet-cryptography lokinet-util
target_link_libraries(lokinet-platform PRIVATE oxenmq::oxenmq)
if (ANDROID)
target_sources(lokinet-platform PRIVATE android/ifaddrs.c)
target_sources(lokinet-platform PRIVATE android/ifaddrs.c util/nop_service_manager.cpp)
endif()
if(CMAKE_SYSTEM_NAME MATCHES "Linux")
target_sources(lokinet-platform PRIVATE linux/dbus.cpp)
if(WITH_SYSTEMD)
target_sources(lokinet-platform PRIVATE linux/sd_service_manager.cpp)
else()
target_sources(lokinet-platform PRIVATE util/nop_service_manager.cpp)
endif()
endif()
if (WIN32)
target_sources(lokinet-platform PRIVATE
net/win32.cpp
vpn/win32.cpp
win32/service_manager.cpp
win32/exec.cpp)
add_library(lokinet-win32 STATIC
win32/dll.cpp
@ -312,6 +318,7 @@ endif()
if(APPLE)
add_subdirectory(apple)
target_sources(lokinet-platform PRIVATE util/nop_service_manager.cpp)
endif()
file(GLOB_RECURSE docs_SRC */*.hpp *.hpp)

@ -6,7 +6,6 @@ namespace llarp
// clang-format off
const std::array<uint16_t, 3> VERSION{{@lokinet_VERSION_MAJOR@, @lokinet_VERSION_MINOR@, @lokinet_VERSION_PATCH@}};
const std::array<uint64_t, 4> ROUTER_VERSION{{llarp::constants::proto_version, @lokinet_VERSION_MAJOR@, @lokinet_VERSION_MINOR@, @lokinet_VERSION_PATCH@}};
const char* const VERSION_STR = "@lokinet_VERSION_MAJOR@.@lokinet_VERSION_MINOR@.@lokinet_VERSION_PATCH@";
const char* const VERSION_TAG = "@VERSIONTAG@";
const char* const VERSION_FULL = "lokinet-@lokinet_VERSION_MAJOR@.@lokinet_VERSION_MINOR@.@lokinet_VERSION_PATCH@-@VERSIONTAG@";

@ -8,7 +8,6 @@ namespace llarp
// Given a full lokinet version of: lokinet-1.2.3-abc these are:
extern const std::array<uint16_t, 3> VERSION; // [1, 2, 3]
extern const std::array<uint64_t, 4> ROUTER_VERSION; // [proto, 1, 2, 3]
extern const char* const VERSION_STR; // "1.2.3"
extern const char* const VERSION_TAG; // "abc"
extern const char* const VERSION_FULL; // "lokinet-1.2.3-abc"

@ -12,6 +12,8 @@
#include "service/context.hpp"
#include "util/logging.hpp"
#include <llarp/util/service_manager.hpp>
#include <cxxopts.hpp>
#include <csignal>
#include <stdexcept>
@ -20,6 +22,8 @@
#include <pthread_np.h>
#endif
static auto logcat = llarp::log::Cat("llarp-context");
namespace llarp
{
bool
@ -159,6 +163,7 @@ namespace llarp
void
Context::HandleSignal(int sig)
{
llarp::log::debug(logcat, "Handling signal {}", sig);
if (sig == SIGINT || sig == SIGTERM)
{
SigINT();
@ -188,6 +193,7 @@ namespace llarp
{
if (router)
{
llarp::log::debug(logcat, "Handling SIGINT");
/// async stop router on sigint
router->Stop();
}
@ -209,4 +215,10 @@ namespace llarp
loop.reset();
}
Context::Context()
{
// service_manager is a global and context isnt
llarp::sys::service_manager->give_context(this);
}
} // namespace llarp

@ -16,14 +16,13 @@
#include "oxen/log.hpp"
#include "sd_platform.hpp"
#include "nm_platform.hpp"
#include "win32_platform.hpp"
namespace llarp::dns
{
static auto logcat = log::Cat("dns");
void
QueryJob_Base::Cancel() const
QueryJob_Base::Cancel()
{
Message reply{m_Query};
reply.AddServFail();
@ -87,7 +86,7 @@ namespace llarp::dns
{
class Resolver;
class Query : public QueryJob_Base
class Query : public QueryJob_Base, public std::enable_shared_from_this<Query>
{
std::shared_ptr<PacketSource_Base> src;
SockAddr resolverAddr;
@ -109,8 +108,8 @@ namespace llarp::dns
std::weak_ptr<Resolver> parent;
int id{};
virtual void
SendReply(llarp::OwnedBuffer replyBuf) const override;
void
SendReply(llarp::OwnedBuffer replyBuf) override;
};
/// Resolver_Base that uses libunbound
@ -127,7 +126,7 @@ namespace llarp::dns
#endif
std::optional<SockAddr> m_LocalAddr;
std::set<int> m_Pending;
std::unordered_set<std::shared_ptr<Query>> m_Pending;
struct ub_result_deleter
{
@ -149,9 +148,8 @@ namespace llarp::dns
{
// take ownership of ub_result
std::unique_ptr<ub_result, ub_result_deleter> result{_result};
// take ownership of our query
std::unique_ptr<Query> query{static_cast<Query*>(data)};
// borrow query
auto* query = static_cast<Query*>(data);
if (err)
{
// some kind of error from upstream
@ -168,9 +166,7 @@ namespace llarp::dns
hdr.id = query->Underlying().hdr_id;
buf.cur = buf.base;
hdr.Encode(&buf);
// remove pending query
if (auto ptr = query->parent.lock())
ptr->call([id = query->id, ptr]() { ptr->m_Pending.erase(id); });
// send reply
query->SendReply(std::move(pkt));
}
@ -344,6 +340,12 @@ namespace llarp::dns
return m_LocalAddr;
}
void
RemovePending(const std::shared_ptr<Query>& query)
{
m_Pending.erase(query);
}
void
Up(const llarp::DnsConfig& conf)
{
@ -379,10 +381,14 @@ namespace llarp::dns
runner = std::thread{[this]() {
while (running)
{
ub_wait(m_ctx);
std::this_thread::sleep_for(10ms);
// poll and process callbacks it this thread
if (ub_poll(m_ctx))
{
ub_process(m_ctx);
}
else // nothing to do, sleep.
std::this_thread::sleep_for(10ms);
}
ub_process(m_ctx);
}};
#else
if (auto loop = m_Loop.lock())
@ -404,22 +410,30 @@ namespace llarp::dns
{
#ifdef _WIN32
if (running.exchange(false))
{
log::debug(logcat, "shutting down win32 dns thread");
runner.join();
}
#else
if (m_Poller)
m_Poller->close();
#endif
if (m_ctx)
{
// cancel pending queries
// make copy as ub_cancel modifies m_Pending
const auto pending = m_Pending;
for (auto id : pending)
::ub_cancel(m_ctx, id);
m_Pending.clear();
::ub_ctx_delete(m_ctx);
m_ctx = nullptr;
// destroy any outstanding queries that unbound hasn't fired yet
if (not m_Pending.empty())
{
log::debug(logcat, "cancelling {} pending queries", m_Pending.size());
// We must copy because Cancel does a loop call to remove itself, but since we are
// already in the main loop it happens immediately, which would invalidate our iterator
// if we were looping through m_Pending at the time.
auto copy = m_Pending;
for (const auto& query : copy)
query->Cancel();
}
}
}
@ -471,8 +485,8 @@ namespace llarp::dns
{
if (WouldLoop(to, from))
return false;
// we use this unique ptr to clean up on fail
auto tmp = std::make_unique<Query>(weak_from_this(), query, source, to, from);
auto tmp = std::make_shared<Query>(weak_from_this(), query, source, to, from);
// no questions, send fail
if (query.questions.empty())
{
@ -495,6 +509,15 @@ namespace llarp::dns
tmp->Cancel();
return true;
}
#ifdef _WIN32
if (not running)
{
// we are stopping the win32 thread
tmp->Cancel();
return true;
}
#endif
const auto& q = query.questions[0];
if (auto err = ub_resolve_async(
m_ctx,
@ -503,33 +526,36 @@ namespace llarp::dns
q.qclass,
tmp.get(),
&Resolver::Callback,
&tmp->id))
nullptr))
{
log::warning(
logcat, "failed to send upstream query with libunbound: {}", ub_strerror(err));
tmp->Cancel();
}
else
{
m_Pending.insert(tmp->id);
// Leak the bare pointer we gave to unbound; we'll recapture it in Callback
(void)tmp.release();
}
m_Pending.insert(std::move(tmp));
return true;
}
};
void
Query::SendReply(llarp::OwnedBuffer replyBuf) const
Query::SendReply(llarp::OwnedBuffer replyBuf)
{
if (auto ptr = parent.lock())
if (m_Done.test_and_set())
return;
auto parent_ptr = parent.lock();
if (parent_ptr)
{
ptr->call([src = src, from = resolverAddr, to = askerAddr, buf = replyBuf.copy()] {
src->SendTo(to, from, OwnedBuffer::copy_from(buf));
});
parent_ptr->call(
[self = shared_from_this(), parent_ptr = std::move(parent_ptr), buf = replyBuf.copy()] {
self->src->SendTo(self->askerAddr, self->resolverAddr, OwnedBuffer::copy_from(buf));
// remove query
parent_ptr->RemovePending(self);
});
}
else
log::error(logcat, "no source or parent");
log::error(logcat, "no parent");
}
} // namespace libunbound
@ -570,10 +596,6 @@ namespace llarp::dns
plat->add_impl(std::make_unique<SD_Platform_t>());
plat->add_impl(std::make_unique<NM_Platform_t>());
}
if constexpr (llarp::platform::is_windows)
{
plat->add_impl(std::make_unique<Win32_Platform_t>());
}
return plat;
}

@ -17,6 +17,9 @@ namespace llarp::dns
/// the original dns query
Message m_Query;
/// True if we've sent a reply (including via a call to cancel)
std::atomic_flag m_Done = ATOMIC_FLAG_INIT;
public:
explicit QueryJob_Base(Message query) : m_Query{std::move(query)}
{}
@ -37,11 +40,11 @@ namespace llarp::dns
/// cancel this operation and inform anyone who cares
void
Cancel() const;
Cancel();
/// send a raw buffer back to the querier
virtual void
SendReply(llarp::OwnedBuffer replyBuf) const = 0;
SendReply(llarp::OwnedBuffer replyBuf) = 0;
};
class PacketSource_Base
@ -130,7 +133,7 @@ namespace llarp::dns
{}
void
SendReply(llarp::OwnedBuffer replyBuf) const override
SendReply(llarp::OwnedBuffer replyBuf) override
{
src->SendTo(asker, resolver, std::move(replyBuf));
}

@ -1,240 +0,0 @@
#include "unbound_resolver.hpp"
#include "server.hpp"
#include <llarp/constants/apple.hpp>
#include <llarp/constants/platform.hpp>
#include <llarp/util/buffer.hpp>
#include <sstream>
#include <llarp/util/str.hpp>
#include <unbound.h>
namespace llarp::dns
{
static auto logcat = log::Cat("dns");
struct PendingUnboundLookup
{
std::weak_ptr<UnboundResolver> resolver;
Message msg;
SockAddr resolverAddr;
SockAddr askerAddr;
};
void
UnboundResolver::Stop()
{
Reset();
}
void
UnboundResolver::Reset()
{
started = false;
#ifdef _WIN32
if (runner.joinable())
{
runner.join();
}
#else
if (udp)
{
udp->close();
}
udp.reset();
#endif
if (unboundContext)
{
ub_ctx_delete(unboundContext);
}
unboundContext = nullptr;
}
UnboundResolver::UnboundResolver(EventLoop_ptr _loop, ReplyFunction reply, FailFunction fail)
: unboundContext{nullptr}
, started{false}
, replyFunc{_loop->make_caller(std::move(reply))}
, failFunc{_loop->make_caller(std::move(fail))}
{
#ifndef _WIN32
loop = _loop->MaybeGetUVWLoop();
#endif
}
// static callback
void
UnboundResolver::Callback(void* data, int err, ub_result* result)
{
std::unique_ptr<PendingUnboundLookup> lookup{static_cast<PendingUnboundLookup*>(data)};
auto this_ptr = lookup->resolver.lock();
if (not this_ptr)
return; // resolver is gone, so we don't reply.
if (err != 0)
{
Message& msg = lookup->msg;
msg.AddServFail();
this_ptr->failFunc(lookup->askerAddr, lookup->resolverAddr, msg);
ub_resolve_free(result);
return;
}
OwnedBuffer pkt{(size_t)result->answer_len};
std::memcpy(pkt.buf.get(), result->answer_packet, pkt.sz);
llarp_buffer_t buf(pkt);
MessageHeader hdr;
hdr.Decode(&buf);
hdr.id = lookup->msg.hdr_id;
buf.cur = buf.base;
hdr.Encode(&buf);
this_ptr->replyFunc(lookup->askerAddr, lookup->resolverAddr, std::move(pkt));
ub_resolve_free(result);
}
bool
UnboundResolver::Init()
{
if (started)
{
Reset();
}
unboundContext = ub_ctx_create();
if (not unboundContext)
{
return false;
}
// disable ip6 for upstream dns
ub_ctx_set_option(unboundContext, "prefer-ip6", "0");
// enable async
ub_ctx_async(unboundContext, 1);
#ifdef _WIN32
runner = std::thread{[&]() {
while (started)
{
if (unboundContext)
ub_wait(unboundContext);
std::this_thread::sleep_for(25ms);
}
if (unboundContext)
ub_process(unboundContext);
}};
#else
if (auto loop_ptr = loop.lock())
{
udp = loop_ptr->resource<uvw::PollHandle>(ub_fd(unboundContext));
udp->on<uvw::PollEvent>([ptr = weak_from_this()](auto&, auto&) {
if (auto self = ptr.lock())
{
if (self->unboundContext)
{
ub_process(self->unboundContext);
}
}
});
udp->start(uvw::PollHandle::Event::READABLE);
}
#endif
started = true;
return true;
}
bool
UnboundResolver::AddUpstreamResolver(const SockAddr& upstreamResolver)
{
const auto hoststr = upstreamResolver.hostString();
std::string upstream = hoststr;
const auto port = upstreamResolver.getPort();
if (port != 53)
{
upstream += '@';
upstream += std::to_string(port);
}
log::info("Adding upstream resolver ", upstream);
if (ub_ctx_set_fwd(unboundContext, upstream.c_str()) != 0)
{
Reset();
return false;
}
if constexpr (platform::is_apple)
{
// On Apple, when we turn on exit mode, we can't directly connect to upstream from here
// because, from within the network extension, macOS ignores setting the tunnel as the default
// route and would leak all DNS; instead we have to bounce things through the objective C
// trampoline code so that it can call into Apple's special snowflake API to set up a socket
// that has the magic Apple snowflake sauce added on top so that it actually routes through
// the tunnel instead of around it.
//
// This behaviour is all carefully and explicitly documented by Apple with plenty of examples
// and other exposition, of course, just like all of their wonderful new APIs to reinvent
// standard unix interfaces.
if (hoststr == "127.0.0.1" && port == apple::dns_trampoline_port)
{
// Not at all clear why this is needed but without it we get "send failed: Can't assign
// requested address" when unbound tries to connect to the localhost address using a source
// address of 0.0.0.0. Yay apple.
ub_ctx_set_option(unboundContext, "outgoing-interface:", "127.0.0.1");
// The trampoline expects just a single source port (and sends everything back to it)
ub_ctx_set_option(unboundContext, "outgoing-range:", "1");
ub_ctx_set_option(unboundContext, "outgoing-port-avoid:", "0-65535");
ub_ctx_set_option(
unboundContext,
"outgoing-port-permit:",
std::to_string(apple::dns_trampoline_source_port).c_str());
}
}
return true;
}
void
UnboundResolver::AddHostsFile(const fs::path& file)
{
log::debug(logcat, "adding hosts file {}", file);
const auto str = file.u8string();
if (auto ret = ub_ctx_hosts(unboundContext, str.c_str()))
throw std::runtime_error{
fmt::format("Failed to add host file {}: {}", file, ub_strerror(ret))};
log::info(logcat, "added hosts file {}", file);
}
void
UnboundResolver::Lookup(SockAddr to, SockAddr from, Message msg)
{
if (not unboundContext)
{
msg.AddServFail();
failFunc(from, to, std::move(msg));
return;
}
const auto& q = msg.questions[0];
auto* lookup = new PendingUnboundLookup{weak_from_this(), msg, to, from};
int err = ub_resolve_async(
unboundContext,
q.Name().c_str(),
q.qtype,
q.qclass,
(void*)lookup,
&UnboundResolver::Callback,
nullptr);
if (err != 0)
{
msg.AddServFail();
failFunc(from, to, std::move(msg));
return;
}
}
} // namespace llarp::dns

@ -1,75 +0,0 @@
#pragma once
#include <mutex>
#include <atomic>
#include <memory>
#include <queue>
#include <llarp/ev/ev.hpp>
#include <llarp/util/fs.hpp>
#include "message.hpp"
#ifdef _WIN32
#include <thread>
#else
#include <uvw.hpp>
#endif
extern "C"
{
struct ub_ctx;
struct ub_result;
}
namespace llarp::dns
{
using ReplyFunction =
std::function<void(const SockAddr& reply_to, const SockAddr& from_resolver, OwnedBuffer buf)>;
using FailFunction =
std::function<void(const SockAddr& reply_to, const SockAddr& from_resolver, Message msg)>;
class UnboundResolver : public std::enable_shared_from_this<UnboundResolver>
{
private:
ub_ctx* unboundContext;
std::atomic<bool> started;
#ifdef _WIN32
std::thread runner;
#else
std::weak_ptr<uvw::Loop> loop;
std::shared_ptr<uvw::PollHandle> udp;
#endif
ReplyFunction replyFunc;
FailFunction failFunc;
void
Reset();
public:
UnboundResolver(EventLoop_ptr loop, ReplyFunction replyFunc, FailFunction failFunc);
static void
Callback(void* data, int err, ub_result* result);
// stop resolver thread
void
Stop();
// upstream resolver IP can be IPv4 or IPv6
bool
Init();
bool
AddUpstreamResolver(const SockAddr& upstreamResolverIP);
void
AddHostsFile(const fs::path& file);
void
Lookup(SockAddr to, SockAddr from, Message msg);
};
} // namespace llarp::dns

@ -1,51 +0,0 @@
#include "win32_platform.hpp"
#include <llarp/net/net.hpp>
namespace llarp::dns::win32
{
void
Platform::set_resolver(unsigned int index, llarp::SockAddr dns, bool)
{
#ifdef _WIN32
// clear any previous dns settings
m_UndoDNS.clear();
auto interfaces = m_Loop->Net_ptr()->AllNetworkInterfaces();
// remove dns
{
std::vector<llarp::win32::OneShotExec> jobs;
for (const auto& ent : interfaces)
{
if (ent.index == index)
continue;
jobs.emplace_back(
"netsh.exe", fmt::format("interface ipv4 delete dns \"{}\" all", ent.name));
jobs.emplace_back(
"netsh.exe", fmt::format("interface ipv6 delete dns \"{}\" all", ent.name));
}
}
// add new dns
{
std::vector<llarp::win32::OneShotExec> jobs;
for (const auto& ent : interfaces)
{
if (ent.index == index)
continue;
jobs.emplace_back(
"netsh.exe",
fmt::format("interface ipv4 add dns \"{}\" {} validate=no", ent.name, dns.asIPv4()));
jobs.emplace_back(
"netsh.exe",
fmt::format("interface ipv6 add dns \"{}\" {} validate=no", ent.name, dns.asIPv6()));
m_UndoDNS.emplace_back("netsh.exe", fmt::format("", index));
}
m_UndoDNS.emplace_back("netsh.exe", "winsock reset");
}
// flush dns
llarp::win32::Exec("ipconfig.exe", "/flushdns");
#endif
}
} // namespace llarp::dns::win32

@ -1,8 +0,0 @@
#pragma once
#include "platform.hpp"
namespace llarp::dns
{
// TODO: implement me
using Win32_Platform_t = Null_Platform;
} // namespace llarp::dns

@ -35,6 +35,8 @@ namespace llarp
{
namespace handlers
{
static auto logcat = log::Cat("tun");
bool
TunEndpoint::MaybeHookDNS(
std::shared_ptr<dns::PacketSource_Base> source,

@ -0,0 +1,59 @@
#include <llarp/util/service_manager.hpp>
#include <systemd/sd-daemon.h>
#include <cassert>
#include <llarp.hpp>
#include <llarp/router/router.hpp>
#include <llarp/util/logging.hpp>
namespace llarp::sys
{
class SD_Manager : public I_SystemLayerManager
{
llarp::sys::ServiceState m_State{ServiceState::Initial};
public:
/// change our state and report it to the system layer
void
we_changed_our_state(ServiceState st) override
{
m_State = st;
report_changed_state();
}
void
report_changed_state() override
{
if (m_State == ServiceState::Running)
{
::sd_notify(0, "READY=1");
return;
}
if (m_State == ServiceState::Stopping)
{
::sd_notify(0, "STOPPING=1");
return;
}
}
void
report_periodic_stats() override
{
if (m_Context and m_Context->router and not m_disable)
{
auto status = fmt::format("WATCHDOG=1\nSTATUS={}", m_Context->router->status_line());
::sd_notify(0, status.c_str());
}
}
void
system_changed_our_state(ServiceState) override
{
// not applicable on systemd
}
};
SD_Manager _manager{};
I_SystemLayerManager* const service_manager = &_manager;
} // namespace llarp::sys

@ -361,6 +361,9 @@ namespace llarp
virtual void
GossipRCIfNeeded(const RouterContact rc) = 0;
virtual std::string
status_line() = 0;
/// Templated convenience function to generate a RouterHive event and
/// delegate to non-templated (and overridable) function for handling.
template <class EventType, class... Params>

@ -200,6 +200,7 @@ namespace llarp
{
stats["authCodes"] = services["default"]["authCodes"];
stats["exitMap"] = services["default"]["exitMap"];
stats["networkReady"] = services["default"]["networkReady"];
stats["lokiAddress"] = services["default"]["identity"];
}
return stats;
@ -393,6 +394,8 @@ namespace llarp
bool
Router::Configure(std::shared_ptr<Config> c, bool isSNode, std::shared_ptr<NodeDB> nodedb)
{
llarp::sys::service_manager->starting();
m_Config = std::move(c);
auto& conf = *m_Config;
@ -515,9 +518,10 @@ namespace llarp
void
Router::Close()
{
log::info(logcat, "closing");
if (_onDown)
_onDown();
LogInfo("closing router");
log::debug(logcat, "stopping mainloop");
_loop->stop();
_running.store(false);
}
@ -870,6 +874,58 @@ namespace llarp
m_LastStatsReport = now;
}
std::string
Router::status_line()
{
std::string status;
auto out = std::back_inserter(status);
fmt::format_to(out, "v{}", fmt::join(llarp::VERSION, "."));
if (IsServiceNode())
{
fmt::format_to(
out,
" snode | known/svc/clients: {}/{}/{}",
nodedb()->NumLoaded(),
NumberOfConnectedRouters(),
NumberOfConnectedClients());
fmt::format_to(
out,
" | {} active paths | block {} ",
pathContext().CurrentTransitPaths(),
(m_lokidRpcClient ? m_lokidRpcClient->BlockHeight() : 0));
auto maybe_last = _rcGossiper.LastGossipAt();
fmt::format_to(
out,
" | gossip: (next/last) {} / {}",
short_time_from_now(_rcGossiper.NextGossipAt()),
maybe_last ? short_time_from_now(*maybe_last) : "never");
}
else
{
fmt::format_to(
out,
" client | known/connected: {}/{}",
nodedb()->NumLoaded(),
NumberOfConnectedRouters());
if (auto ep = hiddenServiceContext().GetDefault())
{
fmt::format_to(
out,
" | paths/endpoints {}/{}",
pathContext().CurrentOwnedPaths(),
ep->UniqueEndpoints());
if (auto success_rate = ep->CurrentBuildStats().SuccessRatio(); success_rate < 0.5)
{
fmt::format_to(
out, " [ !!! Low Build Success Rate ({:.1f}%) !!! ]", (100.0 * success_rate));
}
};
}
return status;
}
void
Router::Tick()
{
@ -884,57 +940,7 @@ namespace llarp
Thaw();
}
#if defined(WITH_SYSTEMD)
{
std::string status;
auto out = std::back_inserter(status);
fmt::format_to(out, "WATCHDOG=1\nSTATUS=v{}", llarp::VERSION_STR);
if (IsServiceNode())
{
fmt::format_to(
out,
" snode | known/svc/clients: {}/{}/{}",
nodedb()->NumLoaded(),
NumberOfConnectedRouters(),
NumberOfConnectedClients());
fmt::format_to(
out,
" | {} active paths | block {} ",
pathContext().CurrentTransitPaths(),
(m_lokidRpcClient ? m_lokidRpcClient->BlockHeight() : 0));
auto maybe_last = _rcGossiper.LastGossipAt();
fmt::format_to(
out,
" | gossip: (next/last) {} / {}",
short_time_from_now(_rcGossiper.NextGossipAt()),
maybe_last ? short_time_from_now(*maybe_last) : "never");
}
else
{
fmt::format_to(
out,
" client | known/connected: {}/{}",
nodedb()->NumLoaded(),
NumberOfConnectedRouters());
if (auto ep = hiddenServiceContext().GetDefault())
{
fmt::format_to(
out,
" | paths/endpoints {}/{}",
pathContext().CurrentOwnedPaths(),
ep->UniqueEndpoints());
if (auto success_rate = ep->CurrentBuildStats().SuccessRatio(); success_rate < 0.5)
{
fmt::format_to(
out, " [ !!! Low Build Success Rate ({:.1f}%) !!! ]", (100.0 * success_rate));
}
};
}
::sd_notify(0, status.c_str());
}
#endif
llarp::sys::service_manager->report_periodic_stats();
m_PathBuildLimiter.Decay(now);
@ -1399,9 +1405,6 @@ namespace llarp
m_RoutePoker->Start(this);
_running.store(true);
_startedAt = Now();
#if defined(WITH_SYSTEMD)
::sd_notify(0, "READY=1");
#endif
if (whitelistRouters)
{
// do service node testing if we are in service node whitelist mode
@ -1474,6 +1477,7 @@ namespace llarp
}
});
}
llarp::sys::service_manager->ready();
return _running;
}
@ -1495,14 +1499,19 @@ namespace llarp
void
Router::AfterStopLinks()
{
llarp::sys::service_manager->stopping();
Close();
log::debug(logcat, "stopping oxenmq");
m_lmq.reset();
}
void
Router::AfterStopIssued()
{
llarp::sys::service_manager->stopping();
log::debug(logcat, "stopping links");
StopLinks();
log::debug(logcat, "saving nodedb to disk");
nodedb()->SaveToDisk();
_loop->call_later(200ms, [this] { AfterStopLinks(); });
}
@ -1525,9 +1534,7 @@ namespace llarp
if (log::get_level_default() != log::Level::off)
log::reset_level(log::Level::info);
LogWarn("stopping router hard");
#if defined(WITH_SYSTEMD)
sd_notify(0, "STOPPING=1\nSTATUS=Shutting down HARD");
#endif
llarp::sys::service_manager->stopping();
hiddenServiceContext().StopAll();
_exitContext.Stop();
StopLinks();
@ -1538,20 +1545,32 @@ namespace llarp
Router::Stop()
{
if (!_running)
{
log::debug(logcat, "Stop called, but not running");
return;
}
if (_stopping)
{
log::debug(logcat, "Stop called, but already stopping");
return;
}
_stopping.store(true);
if (log::get_level_default() != log::Level::off)
if (auto level = log::get_level_default();
level > log::Level::info and level != log::Level::off)
log::reset_level(log::Level::info);
LogInfo("stopping router");
#if defined(WITH_SYSTEMD)
sd_notify(0, "STOPPING=1\nSTATUS=Shutting down");
#endif
log::info(logcat, "stopping");
llarp::sys::service_manager->stopping();
log::debug(logcat, "stopping hidden service context");
hiddenServiceContext().StopAll();
llarp::sys::service_manager->stopping();
log::debug(logcat, "stopping exit context");
_exitContext.Stop();
llarp::sys::service_manager->stopping();
log::debug(logcat, "final upstream pump");
paths.PumpUpstream();
llarp::sys::service_manager->stopping();
log::debug(logcat, "final links pump");
_linkManager.PumpLinks();
_loop->call_later(200ms, [this] { AfterStopIssued(); });
}

@ -35,6 +35,7 @@
#include <llarp/util/status.hpp>
#include <llarp/util/str.hpp>
#include <llarp/util/time.hpp>
#include <llarp/util/service_manager.hpp>
#include <functional>
#include <list>
@ -315,6 +316,9 @@ namespace llarp
RCLookupHandler _rcLookupHandler;
RCGossiper _rcGossiper;
std::string
status_line() override;
using Clock_t = std::chrono::steady_clock;
using TimePoint_t = Clock_t::time_point;

@ -146,7 +146,7 @@ namespace llarp::rpc
m_LMQ->listen_plain(url.zmq_address());
m_LMQ->add_category("llarp", oxenmq::AuthLevel::none)
.add_request_command("logs", [this](oxenmq::Message& msg) { HandleLogsSubRequest(msg); })
.add_command(
.add_request_command(
"halt",
[&](oxenmq::Message& msg) {
if (not m_Router->IsRunning())

@ -11,6 +11,7 @@ namespace llarp
{
namespace service
{
static auto logcat = log::Cat("service");
namespace
{
using EndpointConstructor =
@ -46,7 +47,9 @@ namespace llarp
auto itr = m_Endpoints.begin();
while (itr != m_Endpoints.end())
{
log::debug(logcat, "Stopping endpoint {}.", itr->first);
itr->second->Stop();
log::debug(logcat, "Endpoint {} stopped.", itr->first);
m_Stopped.emplace_back(std::move(itr->second));
itr = m_Endpoints.erase(itr);
}

@ -49,6 +49,9 @@ namespace llarp
{
namespace service
{
static auto logcat = log::Cat("endpoint");
Endpoint::Endpoint(AbstractRouter* r, Context* parent)
: path::Builder{r, 3, path::default_len}
, context{parent}
@ -384,9 +387,12 @@ namespace llarp
Endpoint::Stop()
{
// stop remote sessions
log::debug(logcat, "Endpoint stopping remote sessions.");
EndpointUtil::StopRemoteSessions(m_state->m_RemoteSessions);
// stop snode sessions
log::debug(logcat, "Endpoint stopping snode sessions.");
EndpointUtil::StopSnodeSessions(m_state->m_SNodeSessions);
log::debug(logcat, "Endpoint stopping its path builder.");
return path::Builder::Stop();
}

@ -0,0 +1,7 @@
#include "service_manager.hpp"
namespace llarp::sys
{
NOP_SystemLayerHandler _manager{};
I_SystemLayerManager* const service_manager = &_manager;
} // namespace llarp::sys

@ -0,0 +1,118 @@
#pragma once
namespace llarp
{
struct Context;
}
namespace llarp::sys
{
// what state lokinet will report we are in to the system layer
enum class ServiceState
{
Initial,
Starting,
Running,
Stopping,
Stopped,
HardStop,
Failed,
};
/// interface type for interacting with the os dependant system layer
class I_SystemLayerManager
{
protected:
bool m_disable{false};
llarp::Context* m_Context{nullptr};
/// change our state and report it to the system layer
virtual void
we_changed_our_state(ServiceState st) = 0;
public:
virtual ~I_SystemLayerManager() = default;
/// disable all reporting to system layer
inline void
disable()
{
m_disable = true;
}
/// give our current lokinet context to the system layer manager
inline void
give_context(llarp::Context* ctx)
{
m_Context = ctx;
}
/// system told us to enter this state
virtual void
system_changed_our_state(ServiceState st) = 0;
/// report our current state to the system layer
virtual void
report_changed_state() = 0;
/// report our stats on each timer tick
virtual void
report_periodic_stats(){};
void
starting()
{
if (m_disable)
return;
we_changed_our_state(ServiceState::Starting);
}
void
ready()
{
if (m_disable)
return;
we_changed_our_state(ServiceState::Running);
}
void
stopping()
{
if (m_disable)
return;
we_changed_our_state(ServiceState::Stopping);
}
void
stopped()
{
if (m_disable)
return;
we_changed_our_state(ServiceState::Stopped);
}
void
failed()
{
if (m_disable)
return;
we_changed_our_state(ServiceState::Failed);
}
};
extern I_SystemLayerManager* const service_manager;
class NOP_SystemLayerHandler : public I_SystemLayerManager
{
protected:
void
we_changed_our_state(ServiceState) override
{}
public:
void
report_changed_state() override{};
void system_changed_our_state(ServiceState) override{};
};
} // namespace llarp::sys

@ -59,8 +59,6 @@ namespace llarp::vpn
NetworkInterface(const NetworkInterface&) = delete;
NetworkInterface(NetworkInterface&&) = delete;
virtual ~NetworkInterface() = default;
const InterfaceInfo&
Info() const
{

@ -0,0 +1,112 @@
#include <windows.h>
#include <chrono>
#include <llarp.hpp>
#include <llarp/util/logging.hpp>
#include "service_manager.hpp"
#include <dbghelp.h>
#include <cassert>
#include <csignal>
#include <optional>
namespace llarp::sys
{
static auto logcat = log::Cat("svc");
namespace
{
std::optional<DWORD>
to_win32_state(ServiceState st)
{
switch (st)
{
case ServiceState::Starting:
return SERVICE_START_PENDING;
case ServiceState::Running:
return SERVICE_RUNNING;
case ServiceState::Stopping:
return SERVICE_STOP_PENDING;
case ServiceState::Stopped:
return SERVICE_STOPPED;
default:
return std::nullopt;
}
}
} // namespace
SVC_Manager::SVC_Manager()
{
_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
}
void
SVC_Manager::system_changed_our_state(ServiceState st)
{
if (m_disable)
return;
if (st == ServiceState::Stopping)
{
we_changed_our_state(st);
}
}
void
SVC_Manager::report_changed_state()
{
if (m_disable)
return;
log::debug(
logcat,
"Reporting Windows service status '{}', exit code {}, wait hint {}, dwCP {}, dwCA {}",
_status.dwCurrentState == SERVICE_START_PENDING ? "start pending"
: _status.dwCurrentState == SERVICE_RUNNING ? "running"
: _status.dwCurrentState == SERVICE_STOPPED ? "stopped"
: _status.dwCurrentState == SERVICE_STOP_PENDING
? "stop pending"
: fmt::format("unknown: {}", _status.dwCurrentState),
_status.dwWin32ExitCode,
_status.dwWaitHint,
_status.dwCheckPoint,
_status.dwControlsAccepted);
SetServiceStatus(handle, &_status);
}
void
SVC_Manager::we_changed_our_state(ServiceState st)
{
if (st == ServiceState::Failed)
{
_status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
_status.dwServiceSpecificExitCode = 2; // TODO: propagate more info ?
report_changed_state();
}
else if (auto maybe_state = to_win32_state(st))
{
auto new_state = *maybe_state;
_status.dwWin32ExitCode = NO_ERROR;
_status.dwCurrentState = new_state;
_status.dwControlsAccepted = st == ServiceState::Running ? SERVICE_ACCEPT_STOP : 0;
_status.dwWaitHint =
std::chrono::milliseconds{
st == ServiceState::Starting ? StartupTimeout
: st == ServiceState::Stopping ? StopTimeout
: 0s}
.count();
// dwCheckPoint gets incremented during a start/stop to tell windows "we're still
// starting/stopping" and to reset its must-be-hung timer. We increment it here so that this
// can be called multiple times to tells Windows something is happening.
if (st == ServiceState::Starting or st == ServiceState::Stopping)
_status.dwCheckPoint++;
else
_status.dwCheckPoint = 0;
report_changed_state();
}
}
SVC_Manager _manager{};
I_SystemLayerManager* const service_manager = &_manager;
} // namespace llarp::sys

@ -0,0 +1,35 @@
#pragma once
#include <chrono>
#include <llarp/util/service_manager.hpp>
#include <llarp/util/types.hpp>
namespace llarp::sys
{
class SVC_Manager : public I_SystemLayerManager
{
SERVICE_STATUS _status;
public:
SERVICE_STATUS_HANDLE handle;
// How long we tell Windows to give us to startup before assuming we have stalled/hung. The
// biggest potential time here is wintun, which if it is going to fail appears to take around
// 15s before doing so.
static constexpr auto StartupTimeout = 17s;
// How long we tell Windows to give us to fully stop before killing us.
static constexpr auto StopTimeout = 5s;
SVC_Manager();
void
system_changed_our_state(ServiceState st) override;
void
report_changed_state() override;
void
we_changed_our_state(ServiceState st) override;
};
} // namespace llarp::sys

@ -11,14 +11,11 @@ extern "C"
{
#include <windivert.h>
}
namespace L = llarp::log;
namespace llarp::win32
{
namespace
{
auto cat = L::Cat("windivert");
}
static auto logcat = log::Cat("windivert");
namespace wd
{
namespace
@ -64,6 +61,7 @@ namespace llarp::win32
HANDLE m_Handle;
std::thread m_Runner;
std::atomic<bool> m_Shutdown{false};
thread::Queue<Packet> m_RecvQueue;
// dns packet queue size
static constexpr size_t recv_queue_size = 64;
@ -73,7 +71,7 @@ namespace llarp::win32
: m_Wake{wake}, m_RecvQueue{recv_queue_size}
{
wd::Initialize();
L::info(cat, "load windivert with filterspec: '{}'", filter_spec);
log::info(logcat, "load windivert with filterspec: '{}'", filter_spec);
m_Handle = wd::open(filter_spec.c_str(), WINDIVERT_LAYER_NETWORK, 0, 0);
if (auto err = GetLastError())
@ -95,14 +93,20 @@ namespace llarp::win32
if (not wd::recv(m_Handle, pkt.data(), pkt.size(), &sz, &addr))
{
auto err = GetLastError();
if (err and err != ERROR_BROKEN_PIPE)
throw win32::error{
err, fmt::format("failed to receive packet from windivert (code={})", err)};
else if (err)
if (err == ERROR_NO_DATA)
// The handle is shut down and the packet queue is empty
return std::nullopt;
if (err == ERROR_BROKEN_PIPE)
{
SetLastError(0);
return std::nullopt;
return std::nullopt;
}
log::critical(logcat, "error receiving packet: {}", err);
throw win32::error{
err, fmt::format("failed to receive packet from windivert (code={})", err)};
}
L::trace(cat, "got packet of size {}B", sz);
log::trace(logcat, "got packet of size {}B", sz);
pkt.resize(sz);
return Packet{std::move(pkt), std::move(addr)};
}
@ -112,11 +116,10 @@ namespace llarp::win32
{
const auto& pkt = w_pkt.pkt;
const auto* addr = &w_pkt.addr;
L::trace(cat, "send dns packet of size {}B", pkt.size());
log::trace(logcat, "send dns packet of size {}B", pkt.size());
UINT sz{};
if (wd::send(m_Handle, pkt.data(), pkt.size(), &sz, addr))
return;
throw win32::error{"windivert send failed"};
if (!wd::send(m_Handle, pkt.data(), pkt.size(), &sz, addr))
throw win32::error{"windivert send failed"};
}
virtual int
@ -125,13 +128,13 @@ namespace llarp::win32
return -1;
}
virtual bool
bool
WritePacket(net::IPPacket) override
{
return false;
}
virtual net::IPPacket
net::IPPacket
ReadNextPacket() override
{
auto w_pkt = m_RecvQueue.tryPopFront();
@ -139,20 +142,21 @@ namespace llarp::win32
return net::IPPacket{};
net::IPPacket pkt{std::move(w_pkt->pkt)};
pkt.reply = [this, addr = std::move(w_pkt->addr)](auto pkt) {
send_packet(Packet{pkt.steal(), addr});
if (!m_Shutdown)
send_packet(Packet{pkt.steal(), addr});
};
return pkt;
}
virtual void
void
Start() override
{
L::info(cat, "starting windivert");
log::info(logcat, "starting windivert");
if (m_Runner.joinable())
throw std::runtime_error{"windivert thread is already running"};
auto read_loop = [this]() {
log::debug(cat, "windivert read loop start");
log::debug(logcat, "windivert read loop start");
while (true)
{
// in the read loop, read packets until they stop coming in
@ -166,16 +170,17 @@ namespace llarp::win32
else // leave loop on read fail
break;
}
log::debug(cat, "windivert read loop end");
log::debug(logcat, "windivert read loop end");
};
m_Runner = std::thread{std::move(read_loop)};
}
virtual void
void
Stop() override
{
L::info(cat, "stopping windivert");
log::info(logcat, "stopping windivert");
m_Shutdown = true;
wd::shutdown(m_Handle, WINDIVERT_SHUTDOWN_BOTH);
m_Runner.join();
}

@ -199,6 +199,8 @@ namespace llarp::win32
{
WINTUN_SESSION_HANDLE _impl;
HANDLE _handle;
std::atomic<bool> ended{false};
static_assert(std::atomic<bool>::is_always_lock_free);
public:
WintunSession() : _impl{nullptr}, _handle{nullptr}
@ -217,8 +219,9 @@ namespace llarp::win32
}
void
Stop() const
Stop()
{
ended = true;
end_session(_impl);
}
@ -233,11 +236,11 @@ namespace llarp::win32
[[nodiscard]] std::pair<std::unique_ptr<PacketWrapper>, bool>
ReadPacket() const
{
// typedef so the return statement fits on 1 line :^D
using Pkt_ptr = std::unique_ptr<PacketWrapper>;
if (ended)
return {nullptr, true};
DWORD sz;
if (auto* ptr = read_packet(_impl, &sz))
return {Pkt_ptr{new PacketWrapper{ptr, sz, _impl}}, false};
return {std::unique_ptr<PacketWrapper>{new PacketWrapper{ptr, sz, _impl}}, false};
const auto err = GetLastError();
if (err == ERROR_NO_MORE_ITEMS or err == ERROR_HANDLE_EOF)
{

@ -2,6 +2,7 @@
#include <catch2/catch.hpp>
#include <util/logging.hpp>
#include <util/service_manager.hpp>
#ifdef _WIN32
#include <winsock2.h>
@ -23,6 +24,7 @@ startWinsock()
int
main(int argc, char* argv[])
{
llarp::sys::service_manager->disable();
llarp::log::reset_level(llarp::log::Level::off);
#ifdef _WIN32

Loading…
Cancel
Save