diff --git a/.drone.jsonnet b/.drone.jsonnet index 10405784e..d73d4193a 100644 --- a/.drone.jsonnet +++ b/.drone.jsonnet @@ -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' + diff --git a/cmake/StaticBuild.cmake b/cmake/StaticBuild.cmake index d0b9f7b29..d7a6f19d5 100644 --- a/cmake/StaticBuild.cmake +++ b/cmake/StaticBuild.cmake @@ -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 --$,enable,disable>-flto --with-ssl=${DEPS_DESTDIR} diff --git a/contrib/patches/unbound-delete-crash-fix.patch b/contrib/patches/unbound-delete-crash-fix.patch new file mode 100644 index 000000000..d80799d5f --- /dev/null +++ b/contrib/patches/unbound-delete-crash-fix.patch @@ -0,0 +1,33 @@ +commit 56d816014d5e8a7eb055169c7e13a303dad5e50f +Author: Jason Rhinelander +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) diff --git a/daemon/lokinet.cpp b/daemon/lokinet.cpp index af71310a5..2683ccab0 100644 --- a/daemon/lokinet.cpp +++ b/daemon/lokinet.cpp @@ -7,7 +7,10 @@ #include #ifdef _WIN32 +#include #include +#else +#include #endif #include @@ -21,25 +24,24 @@ int lokinet_main(int, char**); #ifdef _WIN32 -#include 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 ctx; std::promise 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 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(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(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::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(argv[0]), + reinterpret_cast(strdup("c:\\programdata\\lokinet\\lokinet.ini")), + reinterpret_cast(0)}; + lokinet_main(args.size() - 1, args.data()); } #endif diff --git a/gui b/gui index 7b0f1aacd..a66130d8d 160000 --- a/gui +++ b/gui @@ -1 +1 @@ -Subproject commit 7b0f1aacdf79b558adfc39dc9cccb7e348aeec03 +Subproject commit a66130d8d6d246737265d19249c3456575c0f1f1 diff --git a/include/llarp.hpp b/include/llarp.hpp index 402ffa52e..cb8ca495b 100644 --- a/include/llarp.hpp +++ b/include/llarp.hpp @@ -45,6 +45,7 @@ namespace llarp std::shared_ptr nodedb = nullptr; std::string nodedb_dir; + Context(); virtual ~Context() = default; void diff --git a/llarp/CMakeLists.txt b/llarp/CMakeLists.txt index 2e3c558df..ce7eb7142 100644 --- a/llarp/CMakeLists.txt +++ b/llarp/CMakeLists.txt @@ -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) diff --git a/llarp/constants/version.cpp.in b/llarp/constants/version.cpp.in index 57156338d..bca06675c 100644 --- a/llarp/constants/version.cpp.in +++ b/llarp/constants/version.cpp.in @@ -6,7 +6,6 @@ namespace llarp // clang-format off const std::array VERSION{{@lokinet_VERSION_MAJOR@, @lokinet_VERSION_MINOR@, @lokinet_VERSION_PATCH@}}; const std::array 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@"; diff --git a/llarp/constants/version.hpp b/llarp/constants/version.hpp index 2cdfc7760..caa58b0af 100644 --- a/llarp/constants/version.hpp +++ b/llarp/constants/version.hpp @@ -8,7 +8,6 @@ namespace llarp // Given a full lokinet version of: lokinet-1.2.3-abc these are: extern const std::array VERSION; // [1, 2, 3] extern const std::array 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" diff --git a/llarp/context.cpp b/llarp/context.cpp index e5b2074e2..1901afc6e 100644 --- a/llarp/context.cpp +++ b/llarp/context.cpp @@ -12,6 +12,8 @@ #include "service/context.hpp" #include "util/logging.hpp" +#include + #include #include #include @@ -20,6 +22,8 @@ #include #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 diff --git a/llarp/dns/server.cpp b/llarp/dns/server.cpp index 49e4fef14..4a0d22da4 100644 --- a/llarp/dns/server.cpp +++ b/llarp/dns/server.cpp @@ -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 { std::shared_ptr src; SockAddr resolverAddr; @@ -109,8 +108,8 @@ namespace llarp::dns std::weak_ptr 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 m_LocalAddr; - std::set m_Pending; + std::unordered_set> m_Pending; struct ub_result_deleter { @@ -149,9 +148,8 @@ namespace llarp::dns { // take ownership of ub_result std::unique_ptr result{_result}; - // take ownership of our query - std::unique_ptr query{static_cast(data)}; - + // borrow query + auto* query = static_cast(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) + { + 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(weak_from_this(), query, source, to, from); + + auto tmp = std::make_shared(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()); plat->add_impl(std::make_unique()); } - if constexpr (llarp::platform::is_windows) - { - plat->add_impl(std::make_unique()); - } return plat; } diff --git a/llarp/dns/server.hpp b/llarp/dns/server.hpp index fcf72111a..7f22df48e 100644 --- a/llarp/dns/server.hpp +++ b/llarp/dns/server.hpp @@ -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)); } diff --git a/llarp/dns/unbound_resolver.cpp b/llarp/dns/unbound_resolver.cpp deleted file mode 100644 index e29c39389..000000000 --- a/llarp/dns/unbound_resolver.cpp +++ /dev/null @@ -1,240 +0,0 @@ -#include "unbound_resolver.hpp" - -#include "server.hpp" -#include -#include -#include -#include -#include - -#include - -namespace llarp::dns -{ - static auto logcat = log::Cat("dns"); - - struct PendingUnboundLookup - { - std::weak_ptr 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 lookup{static_cast(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(ub_fd(unboundContext)); - udp->on([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 diff --git a/llarp/dns/unbound_resolver.hpp b/llarp/dns/unbound_resolver.hpp deleted file mode 100644 index 4d79569ce..000000000 --- a/llarp/dns/unbound_resolver.hpp +++ /dev/null @@ -1,75 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -#include -#include - -#include "message.hpp" - -#ifdef _WIN32 -#include -#else -#include -#endif - -extern "C" -{ - struct ub_ctx; - struct ub_result; -} - -namespace llarp::dns -{ - using ReplyFunction = - std::function; - using FailFunction = - std::function; - - class UnboundResolver : public std::enable_shared_from_this - { - private: - ub_ctx* unboundContext; - - std::atomic started; - -#ifdef _WIN32 - std::thread runner; -#else - std::weak_ptr loop; - std::shared_ptr 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 diff --git a/llarp/dns/win32_platform.cpp b/llarp/dns/win32_platform.cpp deleted file mode 100644 index 6c268b05c..000000000 --- a/llarp/dns/win32_platform.cpp +++ /dev/null @@ -1,51 +0,0 @@ -#include "win32_platform.hpp" -#include - -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 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 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 diff --git a/llarp/dns/win32_platform.hpp b/llarp/dns/win32_platform.hpp deleted file mode 100644 index cb57a206c..000000000 --- a/llarp/dns/win32_platform.hpp +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once -#include "platform.hpp" - -namespace llarp::dns -{ - // TODO: implement me - using Win32_Platform_t = Null_Platform; -} // namespace llarp::dns diff --git a/llarp/handlers/tun.cpp b/llarp/handlers/tun.cpp index 89a27437b..e04e44ea7 100644 --- a/llarp/handlers/tun.cpp +++ b/llarp/handlers/tun.cpp @@ -35,6 +35,8 @@ namespace llarp { namespace handlers { + static auto logcat = log::Cat("tun"); + bool TunEndpoint::MaybeHookDNS( std::shared_ptr source, diff --git a/llarp/linux/sd_service_manager.cpp b/llarp/linux/sd_service_manager.cpp new file mode 100644 index 000000000..43d9ec47e --- /dev/null +++ b/llarp/linux/sd_service_manager.cpp @@ -0,0 +1,59 @@ +#include + +#include +#include +#include +#include +#include + +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 diff --git a/llarp/router/abstractrouter.hpp b/llarp/router/abstractrouter.hpp index 7f6a368a7..162b50616 100644 --- a/llarp/router/abstractrouter.hpp +++ b/llarp/router/abstractrouter.hpp @@ -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 diff --git a/llarp/router/router.cpp b/llarp/router/router.cpp index d0edc5cfa..f3a807edd 100644 --- a/llarp/router/router.cpp +++ b/llarp/router/router.cpp @@ -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 c, bool isSNode, std::shared_ptr 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(); }); } diff --git a/llarp/router/router.hpp b/llarp/router/router.hpp index 53b24e4aa..3e86cff07 100644 --- a/llarp/router/router.hpp +++ b/llarp/router/router.hpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include @@ -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; diff --git a/llarp/rpc/rpc_server.cpp b/llarp/rpc/rpc_server.cpp index 9ba21868d..afab58387 100644 --- a/llarp/rpc/rpc_server.cpp +++ b/llarp/rpc/rpc_server.cpp @@ -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()) diff --git a/llarp/service/context.cpp b/llarp/service/context.cpp index 60d23a916..4b7c00bb4 100644 --- a/llarp/service/context.cpp +++ b/llarp/service/context.cpp @@ -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); } diff --git a/llarp/service/endpoint.cpp b/llarp/service/endpoint.cpp index 48c33543a..d835f816d 100644 --- a/llarp/service/endpoint.cpp +++ b/llarp/service/endpoint.cpp @@ -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(); } diff --git a/llarp/util/nop_service_manager.cpp b/llarp/util/nop_service_manager.cpp new file mode 100644 index 000000000..e07025087 --- /dev/null +++ b/llarp/util/nop_service_manager.cpp @@ -0,0 +1,7 @@ +#include "service_manager.hpp" + +namespace llarp::sys +{ + NOP_SystemLayerHandler _manager{}; + I_SystemLayerManager* const service_manager = &_manager; +} // namespace llarp::sys diff --git a/llarp/util/service_manager.hpp b/llarp/util/service_manager.hpp new file mode 100644 index 000000000..d4f66eb22 --- /dev/null +++ b/llarp/util/service_manager.hpp @@ -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 diff --git a/llarp/vpn/platform.hpp b/llarp/vpn/platform.hpp index ebba6dee8..9ae880bb9 100644 --- a/llarp/vpn/platform.hpp +++ b/llarp/vpn/platform.hpp @@ -59,8 +59,6 @@ namespace llarp::vpn NetworkInterface(const NetworkInterface&) = delete; NetworkInterface(NetworkInterface&&) = delete; - virtual ~NetworkInterface() = default; - const InterfaceInfo& Info() const { diff --git a/llarp/win32/service_manager.cpp b/llarp/win32/service_manager.cpp new file mode 100644 index 000000000..986b0c024 --- /dev/null +++ b/llarp/win32/service_manager.cpp @@ -0,0 +1,112 @@ +#include +#include +#include +#include +#include "service_manager.hpp" +#include +#include +#include +#include + +namespace llarp::sys +{ + + static auto logcat = log::Cat("svc"); + + namespace + { + + std::optional + 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 diff --git a/llarp/win32/service_manager.hpp b/llarp/win32/service_manager.hpp new file mode 100644 index 000000000..6896e69e0 --- /dev/null +++ b/llarp/win32/service_manager.hpp @@ -0,0 +1,35 @@ +#pragma once +#include +#include +#include + +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 diff --git a/llarp/win32/windivert.cpp b/llarp/win32/windivert.cpp index 7dc181e91..344718db1 100644 --- a/llarp/win32/windivert.cpp +++ b/llarp/win32/windivert.cpp @@ -11,14 +11,11 @@ extern "C" { #include } -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 m_Shutdown{false}; thread::Queue 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(); } diff --git a/llarp/win32/wintun.cpp b/llarp/win32/wintun.cpp index b6fb30ba0..ca77c61fa 100644 --- a/llarp/win32/wintun.cpp +++ b/llarp/win32/wintun.cpp @@ -199,6 +199,8 @@ namespace llarp::win32 { WINTUN_SESSION_HANDLE _impl; HANDLE _handle; + std::atomic ended{false}; + static_assert(std::atomic::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, bool> ReadPacket() const { - // typedef so the return statement fits on 1 line :^D - using Pkt_ptr = std::unique_ptr; + 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{new PacketWrapper{ptr, sz, _impl}}, false}; const auto err = GetLastError(); if (err == ERROR_NO_MORE_ITEMS or err == ERROR_HANDLE_EOF) { diff --git a/test/check_main.cpp b/test/check_main.cpp index e98bdcbfb..87299c833 100644 --- a/test/check_main.cpp +++ b/test/check_main.cpp @@ -2,6 +2,7 @@ #include #include +#include #ifdef _WIN32 #include @@ -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