Merge remote-tracking branch 'origin/stable' into debian/buster

debian/buster
Jason Rhinelander 2 years ago
commit 502d27e8a3
No known key found for this signature in database
GPG Key ID: C4992CE7A88D4262

@ -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": []
}
]
}

@ -3,7 +3,7 @@
* Act like a responsible adult. * Act like a responsible adult.
* RUN `make format` BEFORE COMMITING ALWAYS. * RUN `./contrib/format.sh` BEFORE COMMITING ALWAYS.
# Do NOT # Do NOT

@ -5,10 +5,10 @@
set(LOCAL_MIRROR "" CACHE STRING "local mirror path/URL for lib downloads") 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_MIRROR ${LOCAL_MIRROR} https://www.openssl.org/source CACHE STRING "openssl download mirror(s)")
set(OPENSSL_SOURCE openssl-${OPENSSL_VERSION}.tar.gz) set(OPENSSL_SOURCE openssl-${OPENSSL_VERSION}.tar.gz)
set(OPENSSL_HASH SHA256=aa7d8d9bef71ad6525c55ba11e5f4397889ce49c2c9349dcea6d3e4f0b024a7a set(OPENSSL_HASH SHA256=83049d042a260e696f62406ac5c08bf706fd84383f945cf21bd61e9ed95c396e
CACHE STRING "openssl source hash") CACHE STRING "openssl source hash")
set(EXPAT_VERSION 2.4.9 CACHE STRING "expat version") 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) 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 build_external(unbound
DEPENDS openssl_external expat_external DEPENDS openssl_external expat_external
${unbound_patch}
CONFIGURE_COMMAND ./configure ${cross_host} ${cross_rc} --prefix=${DEPS_DESTDIR} --disable-shared CONFIGURE_COMMAND ./configure ${cross_host} ${cross_rc} --prefix=${DEPS_DESTDIR} --disable-shared
--enable-static --with-libunbound-only --with-pic --enable-static --with-libunbound-only --with-pic
--$<IF:$<BOOL:${WITH_LTO}>,enable,disable>-flto --with-ssl=${DEPS_DESTDIR} --$<IF:$<BOOL:${WITH_LTO}>,enable,disable>-flto --with-ssl=${DEPS_DESTDIR}

@ -3,4 +3,15 @@ if(APPLE OR WIN32)
set(default_build_gui ON) set(default_build_gui ON)
endif() 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}) 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()

@ -1,24 +1,26 @@
set(default_gui_target pack) if(WIN32 AND GUI_EXE)
if(APPLE) message(STATUS "using pre-built lokinet gui executable: ${GUI_EXE}")
set(default_gui_target macos:raw) execute_process(COMMAND ${CMAKE_COMMAND} -E copy_if_different "${GUI_EXE}" "${PROJECT_BINARY_DIR}/gui/lokinet-gui.exe")
elseif(WIN32) elseif(BUILD_GUI)
set(default_gui_target win32) message(STATUS "Building lokinet-gui from source")
set(GUI_EXE "" CACHE FILEPATH "path to an externally built lokinet gui.exe")
endif() 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_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_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= # allow manually specifying yarn with -DYARN=
if(NOT YARN) if(NOT YARN)
find_program(YARN NAMES yarnpkg yarn REQUIRED) find_program(YARN NAMES yarnpkg yarn REQUIRED)
endif() endif()
message(STATUS "Building lokinet-gui with yarn ${YARN}, target ${GUI_YARN_TARGET}") message(STATUS "Building lokinet-gui with yarn ${YARN}, target ${GUI_YARN_TARGET}")
if(NOT WIN32) if(NOT WIN32)
add_custom_target(lokinet-gui add_custom_target(lokinet-gui
COMMAND ${YARN} install --frozen-lockfile && COMMAND ${YARN} install --frozen-lockfile &&
@ -45,25 +47,13 @@ if (BUILD_GUI)
) )
elseif(WIN32) elseif(WIN32)
file(MAKE_DIRECTORY "${PROJECT_BINARY_DIR}/gui") file(MAKE_DIRECTORY "${PROJECT_BINARY_DIR}/gui")
option(GUI_ZIP_FILE "custom lokinet gui for windows from zip file" OFF) add_custom_command(OUTPUT "${PROJECT_BINARY_DIR}/gui/lokinet-gui.exe"
if(GUI_ZIP_FILE) COMMAND ${YARN} install --frozen-lockfile &&
message(STATUS "using custom lokinet gui from ${GUI_ZIP_FILE}") USE_SYSTEM_7ZA=true DISPLAY= WINEDEBUG=-all WINEPREFIX="${PROJECT_BINARY_DIR}/wineprefix" ${YARN} ${GUI_YARN_EXTRA_OPTS} ${GUI_YARN_TARGET}
execute_process(COMMAND ${CMAKE_COMMAND} -E tar xf ${GUI_ZIP_FILE} COMMAND ${CMAKE_COMMAND} -E copy_if_different
WORKING_DIRECTORY ${PROJECT_BINARY_DIR}) "${PROJECT_SOURCE_DIR}/gui/release/Lokinet-GUI_portable.exe"
add_custom_target("${PROJECT_BINARY_DIR}/gui/lokinet-gui.exe" COMMAND "true") "${PROJECT_BINARY_DIR}/gui/lokinet-gui.exe"
elseif(GUI_EXE) WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}/gui")
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_target(assemble_gui ALL COMMAND "true" DEPENDS "${PROJECT_BINARY_DIR}/gui/lokinet-gui.exe") add_custom_target(assemble_gui ALL COMMAND "true" DEPENDS "${PROJECT_BINARY_DIR}/gui/lokinet-gui.exe")
else() else()
message(FATAL_ERROR "Building/bundling the GUI from this repository is not supported on this platform") message(FATAL_ERROR "Building/bundling the GUI from this repository is not supported on this platform")

@ -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) install(DIRECTORY ${CMAKE_BINARY_DIR}/gui DESTINATION share COMPONENT gui)
if(WITH_WINDOWS_32) if(WITH_WINDOWS_32)

@ -14,13 +14,16 @@ mkdir -p "${outdir}"
for size in "${sizes[@]}"; do for size in "${sizes[@]}"; do
outf="${outdir}/${size}x${size}.png" outf="${outdir}/${size}x${size}.png"
if [ $size -lt 32 ]; then if [ $size -lt 32 ]; then
# For 16x16 and 24x24 we crop the image to 3/4 of its regular size before resizing and make # For 16x16 and 24x24 we crop the image to 2/3 of its regular size make it all white
# it all white (instead of transparent) which effectively zooms in on it a bit because if we # (instead of transparent) to zoom in on it a bit because if we resize the full icon to the
# resize the full icon it ends up a fuzzy mess, while the crop and resize lets us retain # target size it ends up a fuzzy mess, while the crop and resize lets us retain some detail
# some detail of the logo. # of the logo.
convert -background white -resize 512x512 "$svg" -gravity Center -extent 320x320 -resize ${size}x${size} -strip "png32:$outf" 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 else
convert -background transparent -resize ${size}x${size} "$svg" -strip "png32:$outf" rsvg-convert -b transparent -w $size -h $size "$svg" >"$outf"
fi fi
outs="-r $outf $outs" outs="-r $outf $outs"
done done

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

@ -1,8 +1,9 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# #
# create signed release tarball with submodules bundled # create signed release tarball with submodules bundled
# usage: ./contrib/tarball.sh [keyid]
# #
repo=$(readlink -e $(dirname $0)/..) repo=$(readlink -e $(dirname $0)/..)
branch=$(test -e $repo/.git/ && git rev-parse --abbrev-ref HEAD) 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" 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)

@ -241,9 +241,10 @@ main(int argc, char* argv[])
if (not maybe_result) if (not maybe_result)
return exit_error("could not add exit"); 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<std::string_view>()); return exit_error("{}", err_it.value());
} }
} }
if (goDown) if (goDown)

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

@ -5,7 +5,6 @@ If you are simply looking to install Lokinet and don't want to compile it yourse
Tier 1: Tier 1:
* [Linux](#linux-install) * [Linux](#linux-install)
* [Android](#apk-install)
* [Windows](#windows-install) * [Windows](#windows-install)
* [MacOS](#macos-install) * [MacOS](#macos-install)
@ -15,6 +14,7 @@ Tier 2:
Currently Unsupported Platforms: (maintainers welcome) Currently Unsupported Platforms: (maintainers welcome)
* [Android](#apk-install)
* Apple iPhone * Apple iPhone
* Homebrew * Homebrew
* \[Insert Flavor of the Month windows package manager here\] * \[Insert Flavor of the Month windows package manager here\]
@ -127,6 +127,7 @@ additional build requirements:
* nsis * nsis
* cpack * cpack
* rsvg-convert (`librsvg2-bin` package on Debian/Ubuntu)
setup: setup:

2
gui

@ -1 +1 @@
Subproject commit 7b0f1aacdf79b558adfc39dc9cccb7e348aeec03 Subproject commit 37f274e86fea7fe0fcc472727398d118a6917854

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

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

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

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

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

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

@ -17,6 +17,9 @@ namespace llarp::dns
/// the original dns query /// the original dns query
Message m_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: public:
explicit QueryJob_Base(Message query) : m_Query{std::move(query)} 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 /// cancel this operation and inform anyone who cares
void void
Cancel() const; Cancel();
/// send a raw buffer back to the querier /// send a raw buffer back to the querier
virtual void virtual void
SendReply(llarp::OwnedBuffer replyBuf) const = 0; SendReply(llarp::OwnedBuffer replyBuf) = 0;
}; };
class PacketSource_Base class PacketSource_Base
@ -130,7 +133,7 @@ namespace llarp::dns
{} {}
void void
SendReply(llarp::OwnedBuffer replyBuf) const override SendReply(llarp::OwnedBuffer replyBuf) override
{ {
src->SendTo(asker, resolver, std::move(replyBuf)); src->SendTo(asker, resolver, std::move(replyBuf));
} }

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

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

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

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

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

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

@ -411,13 +411,17 @@ namespace llarp
if (self->record.work && self->record.work->IsValid(now)) if (self->record.work && self->record.work->IsValid(now))
{ {
llarp::LogDebug( 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; self->hop->lifetime += self->record.work->extendedLifetime;
} }
else if (self->record.lifetime < path::default_lifetime && self->record.lifetime > 10s) else if (self->record.lifetime < path::default_lifetime && self->record.lifetime > 10s)
{ {
self->hop->lifetime = self->record.lifetime; 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 // TODO: check if we really want to accept it

@ -308,7 +308,7 @@ namespace llarp
} }
else if (st == ePathEstablished && _status == ePathBuilding) 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) else if (st == ePathTimeout && _status == ePathEstablished)
{ {
@ -449,7 +449,7 @@ namespace llarp
const auto dlt = now - buildStarted; const auto dlt = now - buildStarted;
if (dlt >= path::build_timeout) 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); r->routerProfiling().MarkPathFail(this);
EnterState(ePathExpired, now); EnterState(ePathExpired, now);
return; return;
@ -473,7 +473,7 @@ namespace llarp
dlt = now - m_LastRecvMessage; dlt = now - m_LastRecvMessage;
if (dlt >= path::alive_timeout) 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); r->routerProfiling().MarkPathFail(this);
EnterState(ePathTimeout, now); EnterState(ePathTimeout, now);
} }

@ -461,7 +461,7 @@ namespace llarp
buildIntervalLimit = PATH_BUILD_RATE; buildIntervalLimit = PATH_BUILD_RATE;
m_router->routerProfiling().MarkPathSuccess(p.get()); 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++; m_BuildStats.success++;
} }
@ -478,7 +478,7 @@ namespace llarp
static constexpr std::chrono::milliseconds MaxBuildInterval = 30s; static constexpr std::chrono::milliseconds MaxBuildInterval = 30s;
// linear backoff // linear backoff
buildIntervalLimit = std::min(PATH_BUILD_RATE + buildIntervalLimit, MaxBuildInterval); buildIntervalLimit = std::min(PATH_BUILD_RATE + buildIntervalLimit, MaxBuildInterval);
LogWarn(Name(), " build interval is now ", buildIntervalLimit); LogWarn(Name(), " build interval is now ", ToString(buildIntervalLimit));
} }
void void

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

@ -200,6 +200,7 @@ namespace llarp
{ {
stats["authCodes"] = services["default"]["authCodes"]; stats["authCodes"] = services["default"]["authCodes"];
stats["exitMap"] = services["default"]["exitMap"]; stats["exitMap"] = services["default"]["exitMap"];
stats["networkReady"] = services["default"]["networkReady"];
stats["lokiAddress"] = services["default"]["identity"]; stats["lokiAddress"] = services["default"]["identity"];
} }
return stats; return stats;
@ -393,6 +394,8 @@ namespace llarp
bool bool
Router::Configure(std::shared_ptr<Config> c, bool isSNode, std::shared_ptr<NodeDB> nodedb) Router::Configure(std::shared_ptr<Config> c, bool isSNode, std::shared_ptr<NodeDB> nodedb)
{ {
llarp::sys::service_manager->starting();
m_Config = std::move(c); m_Config = std::move(c);
auto& conf = *m_Config; auto& conf = *m_Config;
@ -515,9 +518,10 @@ namespace llarp
void void
Router::Close() Router::Close()
{ {
log::info(logcat, "closing");
if (_onDown) if (_onDown)
_onDown(); _onDown();
LogInfo("closing router"); log::debug(logcat, "stopping mainloop");
_loop->stop(); _loop->stop();
_running.store(false); _running.store(false);
} }
@ -862,14 +866,66 @@ namespace llarp
if (IsServiceNode()) if (IsServiceNode())
{ {
LogInfo(NumberOfConnectedClients(), " client connections"); LogInfo(NumberOfConnectedClients(), " client connections");
LogInfo(_rc.Age(now), " since we last updated our RC"); LogInfo(ToString(_rc.Age(now)), " since we last updated our RC");
LogInfo(_rc.TimeUntilExpires(now), " until our RC expires"); LogInfo(ToString(_rc.TimeUntilExpires(now)), " until our RC expires");
} }
if (m_LastStatsReport > 0s) if (m_LastStatsReport > 0s)
LogInfo(now - m_LastStatsReport, " last reported stats"); LogInfo(ToString(now - m_LastStatsReport), " last reported stats");
m_LastStatsReport = now; 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 void
Router::Tick() Router::Tick()
{ {
@ -880,63 +936,11 @@ namespace llarp
if (const auto delta = now - _lastTick; _lastTick != 0s and delta > TimeskipDetectedDuration) if (const auto delta = now - _lastTick; _lastTick != 0s and delta > TimeskipDetectedDuration)
{ {
// we detected a time skip into the futre, thaw the network // 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(); Thaw();
} }
#if defined(WITH_SYSTEMD) llarp::sys::service_manager->report_periodic_stats();
{
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<std::chrono::seconds>{_rcGossiper.NextGossipAt()});
if (auto maybe = _rcGossiper.LastGossipAt())
out = fmt::format_to(out, "{}", time_delta<std::chrono::seconds>{*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
m_PathBuildLimiter.Decay(now); m_PathBuildLimiter.Decay(now);
@ -1287,8 +1291,18 @@ namespace llarp
// override ip and port as needed // override ip and port as needed
if (_ourAddress) if (_ourAddress)
{ {
if (not Net().IsBogon(ai.ip)) const auto ai_ip = ai.IP();
throw std::runtime_error{"cannot override public ip, it is already set"}; 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); ai.fromSockAddr(*_ourAddress);
} }
if (RouterContact::BlockBogons && Net().IsBogon(ai.ip)) if (RouterContact::BlockBogons && Net().IsBogon(ai.ip))
@ -1391,9 +1405,6 @@ namespace llarp
m_RoutePoker->Start(this); m_RoutePoker->Start(this);
_running.store(true); _running.store(true);
_startedAt = Now(); _startedAt = Now();
#if defined(WITH_SYSTEMD)
::sd_notify(0, "READY=1");
#endif
if (whitelistRouters) if (whitelistRouters)
{ {
// do service node testing if we are in service node whitelist mode // 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; return _running;
} }
@ -1487,14 +1499,19 @@ namespace llarp
void void
Router::AfterStopLinks() Router::AfterStopLinks()
{ {
llarp::sys::service_manager->stopping();
Close(); Close();
log::debug(logcat, "stopping oxenmq");
m_lmq.reset(); m_lmq.reset();
} }
void void
Router::AfterStopIssued() Router::AfterStopIssued()
{ {
llarp::sys::service_manager->stopping();
log::debug(logcat, "stopping links");
StopLinks(); StopLinks();
log::debug(logcat, "saving nodedb to disk");
nodedb()->SaveToDisk(); nodedb()->SaveToDisk();
_loop->call_later(200ms, [this] { AfterStopLinks(); }); _loop->call_later(200ms, [this] { AfterStopLinks(); });
} }
@ -1517,9 +1534,7 @@ namespace llarp
if (log::get_level_default() != log::Level::off) if (log::get_level_default() != log::Level::off)
log::reset_level(log::Level::info); log::reset_level(log::Level::info);
LogWarn("stopping router hard"); LogWarn("stopping router hard");
#if defined(WITH_SYSTEMD) llarp::sys::service_manager->stopping();
sd_notify(0, "STOPPING=1\nSTATUS=Shutting down HARD");
#endif
hiddenServiceContext().StopAll(); hiddenServiceContext().StopAll();
_exitContext.Stop(); _exitContext.Stop();
StopLinks(); StopLinks();
@ -1530,20 +1545,32 @@ namespace llarp
Router::Stop() Router::Stop()
{ {
if (!_running) if (!_running)
{
log::debug(logcat, "Stop called, but not running");
return; return;
}
if (_stopping) if (_stopping)
{
log::debug(logcat, "Stop called, but already stopping");
return; return;
}
_stopping.store(true); _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); log::reset_level(log::Level::info);
LogInfo("stopping router"); log::info(logcat, "stopping");
#if defined(WITH_SYSTEMD) llarp::sys::service_manager->stopping();
sd_notify(0, "STOPPING=1\nSTATUS=Shutting down"); log::debug(logcat, "stopping hidden service context");
#endif
hiddenServiceContext().StopAll(); hiddenServiceContext().StopAll();
llarp::sys::service_manager->stopping();
log::debug(logcat, "stopping exit context");
_exitContext.Stop(); _exitContext.Stop();
llarp::sys::service_manager->stopping();
log::debug(logcat, "final upstream pump");
paths.PumpUpstream(); paths.PumpUpstream();
llarp::sys::service_manager->stopping();
log::debug(logcat, "final links pump");
_linkManager.PumpLinks(); _linkManager.PumpLinks();
_loop->call_later(200ms, [this] { AfterStopIssued(); }); _loop->call_later(200ms, [this] { AfterStopIssued(); });
} }

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

@ -116,10 +116,10 @@ namespace llarp
std::string result; std::string result;
auto out = std::back_inserter(result); auto out = std::back_inserter(result);
for (const auto& addr : addrs) for (const auto& addr : addrs)
out = fmt::format_to(out, "ai_addr={}; ai_pk={}; ", addr.toIpAddress(), addr.pubkey); 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, "updated={}; onion_pk={}; ", last_updated.count(), enckey.ToHex());
if (routerVersion.has_value()) if (routerVersion.has_value())
out = fmt::format_to(out, "router_version={}; ", *routerVersion); fmt::format_to(out, "router_version={}; ", *routerVersion);
return result; return result;
} }

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

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

@ -40,15 +40,12 @@
#include <uvw.hpp> #include <uvw.hpp>
#include <variant> #include <variant>
namespace
{
constexpr size_t MIN_ENDPOINTS_FOR_LNS_LOOKUP = 2;
} // namespace
namespace llarp namespace llarp
{ {
namespace service namespace service
{ {
static auto logcat = log::Cat("endpoint");
Endpoint::Endpoint(AbstractRouter* r, Context* parent) Endpoint::Endpoint(AbstractRouter* r, Context* parent)
: path::Builder{r, 3, path::default_len} : path::Builder{r, 3, path::default_len}
, context{parent} , context{parent}
@ -314,7 +311,7 @@ namespace llarp
auto obj = path::Builder::ExtractStatus(); auto obj = path::Builder::ExtractStatus();
obj["exitMap"] = m_ExitMap.ExtractStatus(); obj["exitMap"] = m_ExitMap.ExtractStatus();
obj["identity"] = m_Identity.pub.Addr().ToString(); obj["identity"] = m_Identity.pub.Addr().ToString();
obj["networkReady"] = ReadyToDoLookup(); obj["networkReady"] = ReadyForNetwork();
util::StatusObject authCodes; util::StatusObject authCodes;
for (const auto& [service, info] : m_RemoteAuthInfos) for (const auto& [service, info] : m_RemoteAuthInfos)
@ -384,9 +381,12 @@ namespace llarp
Endpoint::Stop() Endpoint::Stop()
{ {
// stop remote sessions // stop remote sessions
log::debug(logcat, "Endpoint stopping remote sessions.");
EndpointUtil::StopRemoteSessions(m_state->m_RemoteSessions); EndpointUtil::StopRemoteSessions(m_state->m_RemoteSessions);
// stop snode sessions // stop snode sessions
log::debug(logcat, "Endpoint stopping snode sessions.");
EndpointUtil::StopSnodeSessions(m_state->m_SNodeSessions); EndpointUtil::StopSnodeSessions(m_state->m_SNodeSessions);
log::debug(logcat, "Endpoint stopping its path builder.");
return path::Builder::Stop(); return path::Builder::Stop();
} }
@ -952,20 +952,28 @@ namespace llarp
return not m_ExitMap.Empty(); 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 bool
Endpoint::ReadyToDoLookup(std::optional<uint64_t> numPaths) const Endpoint::ReadyForNetwork() const
{ {
if (not numPaths) return IsReady() and ReadyToDoLookup(GetUniqueEndpointsForLookup().size());
{ }
path::Path::UniqueEndpointSet_t paths;
ForEachPath([&paths](auto path) {
if (path and path->IsReady())
paths.insert(path);
});
numPaths = paths.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 void
@ -986,12 +994,7 @@ namespace llarp
return; return;
} }
LogInfo(Name(), " looking up LNS name: ", name); LogInfo(Name(), " looking up LNS name: ", name);
path::Path::UniqueEndpointSet_t paths; auto paths = GetUniqueEndpointsForLookup();
ForEachPath([&paths](auto path) {
if (path and path->IsReady())
paths.insert(path);
});
// not enough paths // not enough paths
if (not ReadyToDoLookup(paths.size())) if (not ReadyToDoLookup(paths.size()))
{ {

@ -48,13 +48,16 @@ namespace llarp
struct OutboundContext; struct OutboundContext;
/// minimum interval for publishing introsets /// 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 /// 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 /// 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, struct Endpoint : public path::Builder,
public ILookupHolder, public ILookupHolder,
@ -64,7 +67,9 @@ namespace llarp
Endpoint(AbstractRouter* r, Context* parent); Endpoint(AbstractRouter* r, Context* parent);
~Endpoint() override; ~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 bool
IsReady() const; IsReady() const;
@ -521,10 +526,16 @@ namespace llarp
return false; return false;
} }
/// return true if we are ready to do outbound and inbound traffic
bool bool
ReadyToDoLookup(std::optional<uint64_t> numPaths = std::nullopt) const; ReadyForNetwork() const;
protected: protected:
bool
ReadyToDoLookup(size_t num_paths) const;
path::Path::UniqueEndpointSet_t
GetUniqueEndpointsForLookup() const;
IDataHandler* m_DataHandler = nullptr; IDataHandler* m_DataHandler = nullptr;
Identity m_Identity; Identity m_Identity;
net::IPRangeMap<service::Address> m_ExitMap; net::IPRangeMap<service::Address> m_ExitMap;

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

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

@ -1,6 +1,7 @@
#include "time.hpp" #include "time.hpp"
#include <chrono> #include <chrono>
#include <iomanip> #include <iomanip>
#include "types.hpp"
namespace llarp namespace llarp
{ {
@ -49,4 +50,59 @@ namespace llarp
{ {
return ToMS(t); return ToMS(t);
} }
static auto
extract_h_m_s_ms(const Duration_t& dur)
{
return std::make_tuple(
std::chrono::duration_cast<std::chrono::hours>(dur).count(),
(std::chrono::duration_cast<std::chrono::minutes>(dur) % 1h).count(),
(std::chrono::duration_cast<std::chrono::seconds>(dur) % 1min).count(),
(std::chrono::duration_cast<std::chrono::milliseconds>(dur) % 1s).count());
}
std::string
short_time_from_now(const TimePoint_t& t, const Duration_t& now_threshold)
{
auto delta = std::chrono::duration_cast<Duration_t>(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 } // namespace llarp

@ -25,69 +25,25 @@ namespace llarp
nlohmann::json nlohmann::json
to_json(const Duration_t& t); to_json(const Duration_t& t);
template <typename Time_Duration> // Returns a string such as "27m13s ago" or "in 1h12m" or "now". You get precision of minutes
struct time_delta // (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.
const TimePoint_t at; std::string
}; short_time_from_now(const TimePoint_t& t, const Duration_t& now_threshold = 1s);
} // namespace llarp
namespace fmt // Makes a duration human readable. This always has full millisecond precision, but formats up to
{ // hours. E.g. "-4h04m12.123s" or "1234h00m09.876s.
template <typename Time_Duration> std::string
struct formatter<llarp::time_delta<Time_Duration>> : formatter<std::string> ToString(Duration_t t);
{
template <typename FormatContext>
auto
format(const llarp::time_delta<Time_Duration>& td, FormatContext& ctx)
{
const auto dlt =
std::chrono::duration_cast<llarp::Duration_t>(llarp::TimePoint_t::clock::now() - td.at);
using Parent = formatter<std::string>;
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);
}
};
template <> } // namespace llarp
struct formatter<llarp::Duration_t> : formatter<std::string>
{
template <typename FormatContext>
auto
format(llarp::Duration_t elapsed, FormatContext& ctx)
{
bool neg = elapsed < 0s;
if (neg)
elapsed = -elapsed;
const auto hours = std::chrono::duration_cast<std::chrono::hours>(elapsed).count();
const auto mins = (std::chrono::duration_cast<std::chrono::minutes>(elapsed) % 1h).count();
const auto secs = (std::chrono::duration_cast<std::chrono::seconds>(elapsed) % 1min).count();
const auto ms = (std::chrono::duration_cast<std::chrono::milliseconds>(elapsed) % 1s).count();
return formatter<std::string>::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);
}
};
template <> // Duration_t is currently just a typedef to std::chrono::milliseconds, and specializing
struct formatter<llarp::TimePoint_t> : formatter<std::string> // that seems wrong; leaving this here to remind us not to add it back in again.
{ // namespace fmt
template <typename FormatContext> //{
auto // template <>
format(const llarp::TimePoint_t& tp, FormatContext& ctx) // struct formatter<llarp::Duration_t>
{ // {
return formatter<std::string>::format(fmt::format("{:%c %Z}", tp), ctx); // };
} //} // namespace fmt
};
} // namespace fmt

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

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

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

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

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

@ -6,7 +6,7 @@ Lokinet is the reference implementation of LLARP (low latency anonymous routing
### Installation instructions can be found [here](docs/install.md). ### 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) [![Build Status](https://ci.oxen.rocks/api/badges/oxen-io/lokinet/status.svg?ref=refs/heads/dev)](https://ci.oxen.rocks/oxen-io/lokinet)

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

@ -160,6 +160,26 @@ inbound=127.0.0.1:443
)"; )";
REQUIRE_THROWS(make_config(env, ini_str)); 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]") 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)); REQUIRE_THROWS(run_config_test(env, ini_str));
} }
} }
TEST_CASE("service node bind section with multiple public ip", "[config]") 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 = ""; std::string_view ini_str = "";
REQUIRE_NOTHROW(run_config_test(env, 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"( std::string_view ini_str = R"(
[bind] [bind]
@ -261,7 +282,7 @@ public-port=443
inbound=0.0.0.0: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") SECTION("with wildcard via inbound directive secondary public ip given")
{ {
@ -273,7 +294,7 @@ public-port=443
inbound=0.0.0.0: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") SECTION("with bind via interface name")
{ {

Loading…
Cancel
Save