Skip to content

Commit

Permalink
system layer manager (llarp::sys::service_manager)
Browse files Browse the repository at this point in the history
the win32 and sd_notify components provided a disjointed set of
similar high level functionality so we consolidate these duplicate
code paths into one that has the same lifecycle regardless of platform
to reduce complexity of this feature.

this new component is responsible for reporting state changes to the
system layer and optionally propagating state change to lokinet
requested by the system layer (used by windows service).
  • Loading branch information
majestrate committed Nov 1, 2022
1 parent a7f3c35 commit 4103908
Show file tree
Hide file tree
Showing 13 changed files with 400 additions and 147 deletions.
108 changes: 22 additions & 86 deletions daemon/lokinet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -21,19 +24,18 @@ 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

bool run_as_daemon{false};

static auto logcat = llarp::log::Cat("main");
std::shared_ptr<llarp::Context> ctx;
std::promise<int> exit_code;
Expand Down Expand Up @@ -84,9 +86,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(
Expand Down Expand Up @@ -294,37 +293,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
Expand Down Expand Up @@ -370,9 +338,9 @@ main(int argc, char* argv[])
#else
SERVICE_TABLE_ENTRY DispatchTable[] = {
{strdup("lokinet"), (LPSERVICE_MAIN_FUNCTION)win32_daemon_entry}, {NULL, NULL}};
if (lstrcmpi(argv[1], "--win32-daemon") == 0)
if (std::string{argv[1]} == "--win32-daemon")
{
start_as_daemon = true;
run_as_daemon = true;
StartServiceCtrlDispatcher(DispatchTable);
}
else
Expand All @@ -383,6 +351,10 @@ main(int argc, char* argv[])
int
lokinet_main(int argc, char** argv)
{
// if we are not running as a service disable reporting
if (llarp::platform::is_windows and not run_as_daemon)
llarp::sys::service_manager->disable();

if (auto result = Lokinet_INIT())
return result;

Expand All @@ -398,7 +370,6 @@ lokinet_main(int argc, char** argv)
opts.showBanner = false;

#ifdef _WIN32
WindowsServiceStopped stopped_raii;
if (startWinsock())
return -1;
SetConsoleCtrlHandler(handle_signal_win32, TRUE);
Expand Down Expand Up @@ -545,13 +516,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
Expand Down Expand Up @@ -582,9 +549,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);
Expand All @@ -609,6 +574,7 @@ lokinet_main(int argc, char** argv)
}

llarp::log::flush();
llarp::sys::service_manager->stopped();
if (ctx)
{
ctx.reset();
Expand All @@ -617,29 +583,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)
Expand All @@ -651,14 +594,13 @@ SvcCtrlHandler(DWORD dwCtrl)
case SERVICE_CONTROL_STOP:
// tell service we are stopping
llarp::log::info(logcat, "Windows service controller gave SERVICE_CONTROL_STOP");
ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 0);
// do the actual tear down
handle_signal(SIGINT);
llarp::sys::service_manager->system_changed_our_state(llarp::sys::ServiceState::Stopping);
return;

case SERVICE_CONTROL_INTERROGATE:
// report status
SetServiceStatus(SvcStatusHandle, &SvcStatus);
llarp::log::debug(logcat, "Got win32 service interrogate signal");
llarp::sys::service_manager->report_changed_state();
return;

default:
Expand All @@ -673,21 +615,15 @@ VOID FAR PASCAL
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);

// we hard code the args to lokinet_main.
// we yoink argv[0] (lokinet.exe path) and pass in the new args.
std::array args = {
Expand Down
1 change: 1 addition & 0 deletions include/llarp.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ namespace llarp
std::shared_ptr<NodeDB> nodedb = nullptr;
std::string nodedb_dir;

Context();
virtual ~Context() = default;

void
Expand Down
9 changes: 8 additions & 1 deletion llarp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -312,6 +318,7 @@ endif()

if(APPLE)
add_subdirectory(apple)
target_sources(lokinet-platform PRIVATE util/nop_system_manager.cpp)
endif()

file(GLOB_RECURSE docs_SRC */*.hpp *.hpp)
Expand Down
8 changes: 8 additions & 0 deletions llarp/context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -213,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
60 changes: 60 additions & 0 deletions llarp/linux/sd_service_manager.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#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
{
assert(m_State != st);
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
3 changes: 3 additions & 0 deletions llarp/router/abstractrouter.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
Loading

0 comments on commit 4103908

Please sign in to comment.