diff --git a/CMakeSettings.json b/CMakeSettings.json deleted file mode 100644 index 20e32e018..000000000 --- a/CMakeSettings.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "configurations": [ - { - "name": "x64-Debug", - "generator": "Ninja", - "configurationType": "Debug", - "inheritEnvironments": [ "msvc_x64_x64" ], - "buildRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\build\\${name}", - "installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}", - "cmakeCommandArgs": "", - "buildCommandArgs": "-v", - "ctestCommandArgs": "", - "variables": [] - }, - { - "name": "x64-Release", - "generator": "Ninja", - "configurationType": "RelWithDebInfo", - "buildRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\build\\${name}", - "installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}", - "cmakeCommandArgs": "", - "buildCommandArgs": "-v", - "ctestCommandArgs": "", - "inheritEnvironments": [ "msvc_x64_x64" ], - "variables": [] - } - ] -} \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6d8e5deaf..9a7a0e67f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,7 +3,7 @@ * Act like a responsible adult. -* RUN `make format` BEFORE COMMITING ALWAYS. +* RUN `./contrib/format.sh` BEFORE COMMITING ALWAYS. # Do NOT diff --git a/cmake/StaticBuild.cmake b/cmake/StaticBuild.cmake index d0b9f7b29..ae4421bc9 100644 --- a/cmake/StaticBuild.cmake +++ b/cmake/StaticBuild.cmake @@ -5,10 +5,10 @@ set(LOCAL_MIRROR "" CACHE STRING "local mirror path/URL for lib downloads") -set(OPENSSL_VERSION 3.0.5 CACHE STRING "openssl version") +set(OPENSSL_VERSION 3.0.7 CACHE STRING "openssl version") set(OPENSSL_MIRROR ${LOCAL_MIRROR} https://www.openssl.org/source CACHE STRING "openssl download mirror(s)") set(OPENSSL_SOURCE openssl-${OPENSSL_VERSION}.tar.gz) -set(OPENSSL_HASH SHA256=aa7d8d9bef71ad6525c55ba11e5f4397889ce49c2c9349dcea6d3e4f0b024a7a +set(OPENSSL_HASH SHA256=83049d042a260e696f62406ac5c08bf706fd84383f945cf21bd61e9ed95c396e CACHE STRING "openssl source hash") set(EXPAT_VERSION 2.4.9 CACHE STRING "expat version") @@ -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/cmake/gui-option.cmake b/cmake/gui-option.cmake index 323f201fa..1ec141ea2 100644 --- a/cmake/gui-option.cmake +++ b/cmake/gui-option.cmake @@ -3,4 +3,15 @@ if(APPLE OR WIN32) set(default_build_gui ON) endif() +if(WIN32) + set(GUI_EXE "" CACHE FILEPATH "path to a pre-built Windows GUI .exe to use (implies -DBUILD_GUI=OFF)") + if(GUI_EXE) + set(default_build_gui OFF) + endif() +endif() + option(BUILD_GUI "build electron gui from 'gui' submodule source" ${default_build_gui}) + +if(BUILD_GUI AND GUI_EXE) + message(FATAL_ERROR "-DGUI_EXE=... and -DBUILD_GUI=ON are mutually exclusive") +endif() diff --git a/cmake/gui.cmake b/cmake/gui.cmake index 7bf8cc27b..6b74ab9ed 100644 --- a/cmake/gui.cmake +++ b/cmake/gui.cmake @@ -1,24 +1,26 @@ -set(default_gui_target pack) -if(APPLE) - set(default_gui_target macos:raw) -elseif(WIN32) - set(default_gui_target win32) - set(GUI_EXE "" CACHE FILEPATH "path to an externally built lokinet gui.exe") -endif() +if(WIN32 AND GUI_EXE) + message(STATUS "using pre-built lokinet gui executable: ${GUI_EXE}") + execute_process(COMMAND ${CMAKE_COMMAND} -E copy_if_different "${GUI_EXE}" "${PROJECT_BINARY_DIR}/gui/lokinet-gui.exe") +elseif(BUILD_GUI) + message(STATUS "Building lokinet-gui from source") + + set(default_gui_target pack) + if(APPLE) + set(default_gui_target macos:raw) + elseif(WIN32) + set(default_gui_target win32) + endif() -set(GUI_YARN_TARGET "${default_gui_target}" CACHE STRING "yarn target for building the GUI") -set(GUI_YARN_EXTRA_OPTS "" CACHE STRING "extra options to pass into the yarn build command") + set(GUI_YARN_TARGET "${default_gui_target}" CACHE STRING "yarn target for building the GUI") + set(GUI_YARN_EXTRA_OPTS "" CACHE STRING "extra options to pass into the yarn build command") -if (BUILD_GUI) - message(STATUS "Building lokinet-gui") # allow manually specifying yarn with -DYARN= if(NOT YARN) find_program(YARN NAMES yarnpkg yarn REQUIRED) endif() message(STATUS "Building lokinet-gui with yarn ${YARN}, target ${GUI_YARN_TARGET}") - if(NOT WIN32) add_custom_target(lokinet-gui COMMAND ${YARN} install --frozen-lockfile && @@ -45,25 +47,13 @@ if (BUILD_GUI) ) elseif(WIN32) file(MAKE_DIRECTORY "${PROJECT_BINARY_DIR}/gui") - option(GUI_ZIP_FILE "custom lokinet gui for windows from zip file" OFF) - if(GUI_ZIP_FILE) - message(STATUS "using custom lokinet gui from ${GUI_ZIP_FILE}") - execute_process(COMMAND ${CMAKE_COMMAND} -E tar xf ${GUI_ZIP_FILE} - WORKING_DIRECTORY ${PROJECT_BINARY_DIR}) - add_custom_target("${PROJECT_BINARY_DIR}/gui/lokinet-gui.exe" COMMAND "true") - elseif(GUI_EXE) - message(STATUS "using custom lokinet gui executable: ${GUI_EXE}") - execute_process(COMMAND ${CMAKE_COMMAND} -E copy_if_different "${GUI_EXE}" "${PROJECT_BINARY_DIR}/gui/lokinet-gui.exe") - add_custom_target("${PROJECT_BINARY_DIR}/gui/lokinet-gui.exe" COMMAND "true") - else() - add_custom_command(OUTPUT "${PROJECT_BINARY_DIR}/gui/lokinet-gui.exe" - COMMAND ${YARN} install --frozen-lockfile && - USE_SYSTEM_7ZA=true DISPLAY= WINEDEBUG=-all WINEPREFIX="${PROJECT_BINARY_DIR}/wineprefix" ${YARN} ${GUI_YARN_EXTRA_OPTS} ${GUI_YARN_TARGET} - COMMAND ${CMAKE_COMMAND} -E copy_if_different - "${PROJECT_SOURCE_DIR}/gui/release/Lokinet-GUI_portable.exe" - "${PROJECT_BINARY_DIR}/gui/lokinet-gui.exe" - WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}/gui") - endif() + add_custom_command(OUTPUT "${PROJECT_BINARY_DIR}/gui/lokinet-gui.exe" + COMMAND ${YARN} install --frozen-lockfile && + USE_SYSTEM_7ZA=true DISPLAY= WINEDEBUG=-all WINEPREFIX="${PROJECT_BINARY_DIR}/wineprefix" ${YARN} ${GUI_YARN_EXTRA_OPTS} ${GUI_YARN_TARGET} + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${PROJECT_SOURCE_DIR}/gui/release/Lokinet-GUI_portable.exe" + "${PROJECT_BINARY_DIR}/gui/lokinet-gui.exe" + WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}/gui") add_custom_target(assemble_gui ALL COMMAND "true" DEPENDS "${PROJECT_BINARY_DIR}/gui/lokinet-gui.exe") else() message(FATAL_ERROR "Building/bundling the GUI from this repository is not supported on this platform") diff --git a/cmake/win32_installer_deps.cmake b/cmake/win32_installer_deps.cmake index e0591466e..8099e9a00 100644 --- a/cmake/win32_installer_deps.cmake +++ b/cmake/win32_installer_deps.cmake @@ -1,22 +1,3 @@ -if(NOT BUILD_GUI) - if(NOT GUI_ZIP_URL) - set(GUI_ZIP_URL "https://oxen.rocks/oxen-io/lokinet-gui/dev/lokinet-windows-x64-20220331T180338Z-569f90ad8.zip") - set(GUI_ZIP_HASH_OPTS EXPECTED_HASH SHA256=316f10489f5907bfa9c74b21f8ef2fdd7b7c7e6a0f5bcedaed2ee5f4004eab52) - endif() - - file(DOWNLOAD - ${GUI_ZIP_URL} - ${CMAKE_BINARY_DIR}/lokinet-gui.zip - ${GUI_ZIP_HASH_OPTS}) - - # We expect the produced .zip file above to extract to ./gui/lokinet-gui.exe - execute_process(COMMAND ${CMAKE_COMMAND} -E tar xf ${CMAKE_BINARY_DIR}/lokinet-gui.zip - WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) - - if(NOT EXISTS ${CMAKE_BINARY_DIR}/gui/lokinet-gui.exe) - message(FATAL_ERROR "Downloaded gui archive from ${GUI_ZIP_URL} does not contain gui/lokinet-gui.exe!") - endif() -endif() install(DIRECTORY ${CMAKE_BINARY_DIR}/gui DESTINATION share COMPONENT gui) if(WITH_WINDOWS_32) diff --git a/contrib/make-ico.sh b/contrib/make-ico.sh index 9a0413a67..cdc74183d 100755 --- a/contrib/make-ico.sh +++ b/contrib/make-ico.sh @@ -14,13 +14,16 @@ mkdir -p "${outdir}" for size in "${sizes[@]}"; do outf="${outdir}/${size}x${size}.png" if [ $size -lt 32 ]; then - # For 16x16 and 24x24 we crop the image to 3/4 of its regular size before resizing and make - # it all white (instead of transparent) which effectively zooms in on it a bit because if we - # resize the full icon it ends up a fuzzy mess, while the crop and resize lets us retain - # some detail of the logo. - convert -background white -resize 512x512 "$svg" -gravity Center -extent 320x320 -resize ${size}x${size} -strip "png32:$outf" + # For 16x16 and 24x24 we crop the image to 2/3 of its regular size make it all white + # (instead of transparent) to zoom in on it a bit because if we resize the full icon to the + # target size it ends up a fuzzy mess, while the crop and resize lets us retain some detail + # of the logo. + rsvg-convert -b white \ + --page-height $size --page-width $size \ + -w $(($size*3/2)) -h $(($size*3/2)) --left " -$(($size/4))" --top " -$(($size/4))" \ + "$svg" >"$outf" else - convert -background transparent -resize ${size}x${size} "$svg" -strip "png32:$outf" + rsvg-convert -b transparent -w $size -h $size "$svg" >"$outf" fi outs="-r $outf $outs" done 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/contrib/tarball.sh b/contrib/tarball.sh index 60435b01d..54b62f1bd 100755 --- a/contrib/tarball.sh +++ b/contrib/tarball.sh @@ -1,8 +1,9 @@ #!/usr/bin/env bash # # create signed release tarball with submodules bundled +# usage: ./contrib/tarball.sh [keyid] # repo=$(readlink -e $(dirname $0)/..) branch=$(test -e $repo/.git/ && git rev-parse --abbrev-ref HEAD) out="lokinet-$(git describe --exact-match --tags $(git log -n1 --pretty='%h') 2> /dev/null || ( echo -n $branch- && git rev-parse --short HEAD)).tar.xz" -git-archive-all -C $repo --force-submodules $out && rm -f $out.sig && (gpg --sign --detach $out &> /dev/null && gpg --verify $out.sig) +git-archive-all -C $repo --force-submodules $out && rm -f $out.sig && (gpg -u ${1:-jeff@lokinet.io} --sign --detach $out &> /dev/null && gpg --verify $out.sig) diff --git a/daemon/lokinet-vpn.cpp b/daemon/lokinet-vpn.cpp index cf8bfc9bd..3cdc7955d 100644 --- a/daemon/lokinet-vpn.cpp +++ b/daemon/lokinet-vpn.cpp @@ -241,9 +241,10 @@ main(int argc, char* argv[]) if (not maybe_result) return exit_error("could not add exit"); - if (auto err_it = maybe_result->find("error"); err_it != maybe_result->end()) + if (auto err_it = maybe_result->find("error"); + err_it != maybe_result->end() and not err_it.value().is_null()) { - return exit_error("{}", err_it->get()); + return exit_error("{}", err_it.value()); } } if (goDown) 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/docs/install.md b/docs/install.md index 2061ea990..17cf9da08 100644 --- a/docs/install.md +++ b/docs/install.md @@ -5,7 +5,6 @@ If you are simply looking to install Lokinet and don't want to compile it yourse Tier 1: * [Linux](#linux-install) -* [Android](#apk-install) * [Windows](#windows-install) * [MacOS](#macos-install) @@ -15,6 +14,7 @@ Tier 2: Currently Unsupported Platforms: (maintainers welcome) +* [Android](#apk-install) * Apple iPhone * Homebrew * \[Insert Flavor of the Month windows package manager here\] @@ -127,6 +127,7 @@ additional build requirements: * nsis * cpack +* rsvg-convert (`librsvg2-bin` package on Debian/Ubuntu) setup: diff --git a/gui b/gui index 7b0f1aacd..37f274e86 160000 --- a/gui +++ b/gui @@ -1 +1 @@ -Subproject commit 7b0f1aacdf79b558adfc39dc9cccb7e348aeec03 +Subproject commit 37f274e86fea7fe0fcc472727398d118a6917854 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/messages/relay_commit.cpp b/llarp/messages/relay_commit.cpp index 4cffe912f..887fc680e 100644 --- a/llarp/messages/relay_commit.cpp +++ b/llarp/messages/relay_commit.cpp @@ -411,13 +411,17 @@ namespace llarp if (self->record.work && self->record.work->IsValid(now)) { llarp::LogDebug( - "LRCM extended lifetime by ", self->record.work->extendedLifetime, " for ", info); + "LRCM extended lifetime by ", + ToString(self->record.work->extendedLifetime), + " for ", + info); self->hop->lifetime += self->record.work->extendedLifetime; } else if (self->record.lifetime < path::default_lifetime && self->record.lifetime > 10s) { self->hop->lifetime = self->record.lifetime; - llarp::LogDebug("LRCM short lifespan set to ", self->hop->lifetime, " for ", info); + llarp::LogDebug( + "LRCM short lifespan set to ", ToString(self->hop->lifetime), " for ", info); } // TODO: check if we really want to accept it diff --git a/llarp/path/path.cpp b/llarp/path/path.cpp index 0790c1238..f777611e7 100644 --- a/llarp/path/path.cpp +++ b/llarp/path/path.cpp @@ -308,7 +308,7 @@ namespace llarp } else if (st == ePathEstablished && _status == ePathBuilding) { - LogInfo("path ", Name(), " is built, took ", now - buildStarted); + LogInfo("path ", Name(), " is built, took ", ToString(now - buildStarted)); } else if (st == ePathTimeout && _status == ePathEstablished) { @@ -449,7 +449,7 @@ namespace llarp const auto dlt = now - buildStarted; if (dlt >= path::build_timeout) { - LogWarn(Name(), " waited for ", dlt, " and no path was built"); + LogWarn(Name(), " waited for ", ToString(dlt), " and no path was built"); r->routerProfiling().MarkPathFail(this); EnterState(ePathExpired, now); return; @@ -473,7 +473,7 @@ namespace llarp dlt = now - m_LastRecvMessage; if (dlt >= path::alive_timeout) { - LogWarn(Name(), " waited for ", dlt, " and path looks dead"); + LogWarn(Name(), " waited for ", ToString(dlt), " and path looks dead"); r->routerProfiling().MarkPathFail(this); EnterState(ePathTimeout, now); } diff --git a/llarp/path/pathbuilder.cpp b/llarp/path/pathbuilder.cpp index 478060005..abf149024 100644 --- a/llarp/path/pathbuilder.cpp +++ b/llarp/path/pathbuilder.cpp @@ -461,7 +461,7 @@ namespace llarp buildIntervalLimit = PATH_BUILD_RATE; m_router->routerProfiling().MarkPathSuccess(p.get()); - LogInfo(p->Name(), " built latency=", p->intro.latency); + LogInfo(p->Name(), " built latency=", ToString(p->intro.latency)); m_BuildStats.success++; } @@ -478,7 +478,7 @@ namespace llarp static constexpr std::chrono::milliseconds MaxBuildInterval = 30s; // linear backoff buildIntervalLimit = std::min(PATH_BUILD_RATE + buildIntervalLimit, MaxBuildInterval); - LogWarn(Name(), " build interval is now ", buildIntervalLimit); + LogWarn(Name(), " build interval is now ", ToString(buildIntervalLimit)); } void 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 73e52bf4d..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); } @@ -862,14 +866,66 @@ namespace llarp if (IsServiceNode()) { LogInfo(NumberOfConnectedClients(), " client connections"); - LogInfo(_rc.Age(now), " since we last updated our RC"); - LogInfo(_rc.TimeUntilExpires(now), " until our RC expires"); + LogInfo(ToString(_rc.Age(now)), " since we last updated our RC"); + LogInfo(ToString(_rc.TimeUntilExpires(now)), " until our RC expires"); } if (m_LastStatsReport > 0s) - LogInfo(now - m_LastStatsReport, " last reported stats"); + LogInfo(ToString(now - m_LastStatsReport), " last reported stats"); 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() { @@ -880,63 +936,11 @@ namespace llarp if (const auto delta = now - _lastTick; _lastTick != 0s and delta > TimeskipDetectedDuration) { // we detected a time skip into the futre, thaw the network - LogWarn("Timeskip of ", delta, " detected. Resetting network state"); + LogWarn("Timeskip of ", ToString(delta), " detected. Resetting network state"); Thaw(); } -#if defined(WITH_SYSTEMD) - { - std::string status; - auto out = std::back_inserter(status); - out = fmt::format_to(out, "WATCHDOG=1\nSTATUS=v{}", llarp::VERSION_STR); - if (IsServiceNode()) - { - out = fmt::format_to( - out, - " snode | known/svc/clients: {}/{}/{}", - nodedb()->NumLoaded(), - NumberOfConnectedRouters(), - NumberOfConnectedClients()); - out = fmt::format_to( - out, - " | {} active paths | block {} ", - pathContext().CurrentTransitPaths(), - (m_lokidRpcClient ? m_lokidRpcClient->BlockHeight() : 0)); - out = fmt::format_to( - out, - " | gossip: (next/last) {} / ", - time_delta{_rcGossiper.NextGossipAt()}); - if (auto maybe = _rcGossiper.LastGossipAt()) - out = fmt::format_to(out, "{}", time_delta{*maybe}); - else - out = fmt::format_to(out, "never"); - } - else - { - out = fmt::format_to( - out, - " client | known/connected: {}/{}", - nodedb()->NumLoaded(), - NumberOfConnectedRouters()); - - if (auto ep = hiddenServiceContext().GetDefault()) - { - out = fmt::format_to( - out, - " | paths/endpoints {}/{}", - pathContext().CurrentOwnedPaths(), - ep->UniqueEndpoints()); - - if (auto success_rate = ep->CurrentBuildStats().SuccessRatio(); success_rate < 0.5) - { - out = 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); @@ -1287,8 +1291,18 @@ namespace llarp // override ip and port as needed if (_ourAddress) { - if (not Net().IsBogon(ai.ip)) - throw std::runtime_error{"cannot override public ip, it is already set"}; + const auto ai_ip = ai.IP(); + const auto override_ip = _ourAddress->getIP(); + + auto ai_ip_str = var::visit([](auto&& ip) { return ip.ToString(); }, ai_ip); + auto override_ip_str = var::visit([](auto&& ip) { return ip.ToString(); }, override_ip); + + if ((not Net().IsBogonIP(ai_ip)) and (not Net().IsBogonIP(override_ip)) + and ai_ip != override_ip) + throw std::runtime_error{ + "Lokinet is bound to public IP '{}', but public-ip is set to '{}'. Either fix the " + "[router]:public-ip setting or set a bind address in the [bind] section of the " + "config."_format(ai_ip_str, override_ip_str)}; ai.fromSockAddr(*_ourAddress); } if (RouterContact::BlockBogons && Net().IsBogon(ai.ip)) @@ -1391,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 @@ -1466,6 +1477,7 @@ namespace llarp } }); } + llarp::sys::service_manager->ready(); return _running; } @@ -1487,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(); }); } @@ -1517,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(); @@ -1530,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/router_contact.cpp b/llarp/router_contact.cpp index 9004c1c34..a0655f1c7 100644 --- a/llarp/router_contact.cpp +++ b/llarp/router_contact.cpp @@ -116,10 +116,10 @@ namespace llarp std::string result; auto out = std::back_inserter(result); for (const auto& addr : addrs) - out = fmt::format_to(out, "ai_addr={}; ai_pk={}; ", addr.toIpAddress(), addr.pubkey); - out = fmt::format_to(out, "updated={}; onion_pk={}; ", last_updated.count(), enckey.ToHex()); + fmt::format_to(out, "ai_addr={}; ai_pk={}; ", addr.toIpAddress(), addr.pubkey); + fmt::format_to(out, "updated={}; onion_pk={}; ", last_updated.count(), enckey.ToHex()); if (routerVersion.has_value()) - out = fmt::format_to(out, "router_version={}; ", *routerVersion); + fmt::format_to(out, "router_version={}; ", *routerVersion); return result; } 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..24839ff4d 100644 --- a/llarp/service/endpoint.cpp +++ b/llarp/service/endpoint.cpp @@ -40,15 +40,12 @@ #include #include -namespace -{ - constexpr size_t MIN_ENDPOINTS_FOR_LNS_LOOKUP = 2; -} // namespace - 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} @@ -314,7 +311,7 @@ namespace llarp auto obj = path::Builder::ExtractStatus(); obj["exitMap"] = m_ExitMap.ExtractStatus(); obj["identity"] = m_Identity.pub.Addr().ToString(); - obj["networkReady"] = ReadyToDoLookup(); + obj["networkReady"] = ReadyForNetwork(); util::StatusObject authCodes; for (const auto& [service, info] : m_RemoteAuthInfos) @@ -384,9 +381,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(); } @@ -952,20 +952,28 @@ namespace llarp return not m_ExitMap.Empty(); } + path::Path::UniqueEndpointSet_t + Endpoint::GetUniqueEndpointsForLookup() const + { + path::Path::UniqueEndpointSet_t paths; + ForEachPath([&paths](auto path) { + if (path and path->IsReady()) + paths.insert(path); + }); + return paths; + } + bool - Endpoint::ReadyToDoLookup(std::optional numPaths) const + Endpoint::ReadyForNetwork() const { - if (not numPaths) - { - path::Path::UniqueEndpointSet_t paths; - ForEachPath([&paths](auto path) { - if (path and path->IsReady()) - paths.insert(path); - }); - numPaths = paths.size(); - } + return IsReady() and ReadyToDoLookup(GetUniqueEndpointsForLookup().size()); + } - return numPaths >= MIN_ENDPOINTS_FOR_LNS_LOOKUP; + bool + Endpoint::ReadyToDoLookup(size_t num_paths) const + { + // Currently just checks the number of paths, but could do more checks in the future. + return num_paths >= MIN_ENDPOINTS_FOR_LNS_LOOKUP; } void @@ -986,12 +994,7 @@ namespace llarp return; } LogInfo(Name(), " looking up LNS name: ", name); - path::Path::UniqueEndpointSet_t paths; - ForEachPath([&paths](auto path) { - if (path and path->IsReady()) - paths.insert(path); - }); - + auto paths = GetUniqueEndpointsForLookup(); // not enough paths if (not ReadyToDoLookup(paths.size())) { diff --git a/llarp/service/endpoint.hpp b/llarp/service/endpoint.hpp index 09b960957..dd61c74b9 100644 --- a/llarp/service/endpoint.hpp +++ b/llarp/service/endpoint.hpp @@ -48,13 +48,16 @@ namespace llarp struct OutboundContext; /// minimum interval for publishing introsets - static constexpr auto IntrosetPublishInterval = path::intro_path_spread / 2; + inline constexpr auto IntrosetPublishInterval = path::intro_path_spread / 2; /// how agressively should we retry publishing introset on failure - static constexpr auto IntrosetPublishRetryCooldown = 1s; + inline constexpr auto IntrosetPublishRetryCooldown = 1s; /// how aggressively should we retry looking up introsets - static constexpr auto IntrosetLookupCooldown = 250ms; + inline constexpr auto IntrosetLookupCooldown = 250ms; + + /// number of unique snodes we want to talk to do to ons lookups + inline constexpr size_t MIN_ENDPOINTS_FOR_LNS_LOOKUP = 2; struct Endpoint : public path::Builder, public ILookupHolder, @@ -64,7 +67,9 @@ namespace llarp Endpoint(AbstractRouter* r, Context* parent); ~Endpoint() override; - /// return true if we are ready to recv packets from the void + /// return true if we are ready to recv packets from the void. + /// really should be ReadyForInboundTraffic() but the diff is HUGE and we need to rewrite this + /// component anyways. bool IsReady() const; @@ -521,10 +526,16 @@ namespace llarp return false; } + /// return true if we are ready to do outbound and inbound traffic bool - ReadyToDoLookup(std::optional numPaths = std::nullopt) const; + ReadyForNetwork() const; protected: + bool + ReadyToDoLookup(size_t num_paths) const; + path::Path::UniqueEndpointSet_t + GetUniqueEndpointsForLookup() const; + IDataHandler* m_DataHandler = nullptr; Identity m_Identity; net::IPRangeMap m_ExitMap; 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/util/time.cpp b/llarp/util/time.cpp index 46d9a5fa4..1b0b24351 100644 --- a/llarp/util/time.cpp +++ b/llarp/util/time.cpp @@ -1,6 +1,7 @@ #include "time.hpp" #include #include +#include "types.hpp" namespace llarp { @@ -49,4 +50,59 @@ namespace llarp { return ToMS(t); } + + static auto + extract_h_m_s_ms(const Duration_t& dur) + { + return std::make_tuple( + std::chrono::duration_cast(dur).count(), + (std::chrono::duration_cast(dur) % 1h).count(), + (std::chrono::duration_cast(dur) % 1min).count(), + (std::chrono::duration_cast(dur) % 1s).count()); + } + + std::string + short_time_from_now(const TimePoint_t& t, const Duration_t& now_threshold) + { + auto delta = std::chrono::duration_cast(llarp::TimePoint_t::clock::now() - t); + bool future = delta < 0s; + if (future) + delta = -delta; + + auto [hours, mins, secs, ms] = extract_h_m_s_ms(delta); + + using namespace fmt::literals; + return fmt::format( + delta < now_threshold ? "now" + : delta < 10s ? "{in}{secs:d}.{ms:03d}s{ago}" + : delta < 1h ? "{in}{mins:d}m{secs:02d}s{ago}" + : "{in}{hours:d}h{mins:02d}m{ago}", + "in"_a = future ? "in " : "", + "ago"_a = future ? "" : " ago", + "hours"_a = hours, + "mins"_a = mins, + "secs"_a = secs, + "ms"_a = ms); + } + + std::string + ToString(Duration_t delta) + { + bool neg = delta < 0s; + if (neg) + delta = -delta; + + auto [hours, mins, secs, ms] = extract_h_m_s_ms(delta); + + using namespace fmt::literals; + return fmt::format( + delta < 1min ? "{neg}{secs:d}.{ms:03d}s" + : delta < 1h ? "{neg}{mins:d}m{secs:02d}.{ms:03d}s" + : "{neg}{hours:d}h{mins:02d}m{secs:02d}.{ms:03d}s", + "neg"_a = neg ? "-" : "", + "hours"_a = hours, + "mins"_a = mins, + "secs"_a = secs, + "ms"_a = ms); + } } // namespace llarp diff --git a/llarp/util/time.hpp b/llarp/util/time.hpp index fbf999773..ef73f50fc 100644 --- a/llarp/util/time.hpp +++ b/llarp/util/time.hpp @@ -25,69 +25,25 @@ namespace llarp nlohmann::json to_json(const Duration_t& t); - template - struct time_delta - { - const TimePoint_t at; - }; -} // namespace llarp + // Returns a string such as "27m13s ago" or "in 1h12m" or "now". You get precision of minutes + // (for >=1h), seconds (>=10s), or milliseconds. The `now_threshold` argument controls how close + // to current time (default 1s) the time has to be to get the "now" argument. + std::string + short_time_from_now(const TimePoint_t& t, const Duration_t& now_threshold = 1s); -namespace fmt -{ - template - struct formatter> : formatter - { - template - auto - format(const llarp::time_delta& td, FormatContext& ctx) - { - const auto dlt = - std::chrono::duration_cast(llarp::TimePoint_t::clock::now() - td.at); - using Parent = formatter; - if (dlt > 0s) - return Parent::format(fmt::format("{} ago", dlt), ctx); - if (dlt < 0s) - return Parent::format(fmt::format("in {}", -dlt), ctx); - return Parent::format("now", ctx); - } - }; + // Makes a duration human readable. This always has full millisecond precision, but formats up to + // hours. E.g. "-4h04m12.123s" or "1234h00m09.876s. + std::string + ToString(Duration_t t); - template <> - struct formatter : formatter - { - template - auto - format(llarp::Duration_t elapsed, FormatContext& ctx) - { - bool neg = elapsed < 0s; - if (neg) - elapsed = -elapsed; - const auto hours = std::chrono::duration_cast(elapsed).count(); - const auto mins = (std::chrono::duration_cast(elapsed) % 1h).count(); - const auto secs = (std::chrono::duration_cast(elapsed) % 1min).count(); - const auto ms = (std::chrono::duration_cast(elapsed) % 1s).count(); - return formatter::format( - fmt::format( - elapsed >= 1h ? "{0}{1:d}h{2:02d}m{3:02d}.{4:03d}s" - : elapsed >= 1min ? "{0}{2:d}m{3:02d}.{4:03d}s" - : "{0}{3:d}.{4:03d}s", - neg ? "-" : "", - hours, - mins, - secs, - ms), - ctx); - } - }; +} // namespace llarp - template <> - struct formatter : formatter - { - template - auto - format(const llarp::TimePoint_t& tp, FormatContext& ctx) - { - return formatter::format(fmt::format("{:%c %Z}", tp), ctx); - } - }; -} // namespace fmt +// Duration_t is currently just a typedef to std::chrono::milliseconds, and specializing +// that seems wrong; leaving this here to remind us not to add it back in again. +// namespace fmt +//{ +// template <> +// struct formatter +// { +// }; +//} // namespace fmt 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/readme.md b/readme.md index c7283e7cc..670e1b793 100644 --- a/readme.md +++ b/readme.md @@ -6,7 +6,7 @@ Lokinet is the reference implementation of LLARP (low latency anonymous routing ### Installation instructions can be found [here](docs/install.md). -#### You can learn more about the high level, how to use it and the internals of the protocol [here](docs/) +#### You can learn more about the high level, how to use it and the internals of the protocol [here](docs/readme.md) [![Build Status](https://ci.oxen.rocks/api/badges/oxen-io/lokinet/status.svg?ref=refs/heads/dev)](https://ci.oxen.rocks/oxen-io/lokinet) 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 diff --git a/test/config/test_llarp_config_values.cpp b/test/config/test_llarp_config_values.cpp index 242f5d14d..8276cc59c 100644 --- a/test/config/test_llarp_config_values.cpp +++ b/test/config/test_llarp_config_values.cpp @@ -160,6 +160,26 @@ inbound=127.0.0.1:443 )"; REQUIRE_THROWS(make_config(env, ini_str)); } + SECTION("public ip provided but no bind section") + { + std::string_view ini_str = R"( +[router] +public-ip=1.1.1.1 +public-port=443 +)"; + REQUIRE_NOTHROW(run_config_test(env, ini_str)); + } + SECTION("public ip provided with ip in bind section") + { + std::string_view ini_str = R"( +[router] +public-ip=1.1.1.1 +public-port=443 +[bind] +1.1.1.1=443 +)"; + REQUIRE_NOTHROW(run_config_test(env, ini_str)); + } } TEST_CASE("service node bind section on nat network", "[config]") @@ -212,6 +232,7 @@ inbound=0.0.0.0:443 )"; REQUIRE_THROWS(run_config_test(env, ini_str)); } + } TEST_CASE("service node bind section with multiple public ip", "[config]") @@ -226,7 +247,7 @@ TEST_CASE("service node bind section with multiple public ip", "[config]") std::string_view ini_str = ""; REQUIRE_NOTHROW(run_config_test(env, ini_str)); } - SECTION("with old style wildcard for inbound and no public ip") + SECTION("with old style wildcard for inbound and no public ip, fails") { std::string_view ini_str = R"( [bind] @@ -261,7 +282,7 @@ public-port=443 inbound=0.0.0.0:443 )"; - REQUIRE_THROWS(run_config_test(env, ini_str)); + REQUIRE_NOTHROW(run_config_test(env, ini_str)); } SECTION("with wildcard via inbound directive secondary public ip given") { @@ -273,7 +294,7 @@ public-port=443 inbound=0.0.0.0:443 )"; - REQUIRE_THROWS(run_config_test(env, ini_str)); + REQUIRE_NOTHROW(run_config_test(env, ini_str)); } SECTION("with bind via interface name") {