You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
lokinet/daemon/main.cpp

604 lines
16 KiB
C++

#include <config/config.hpp> // for ensure_config
#include <constants/version.hpp>
#include <llarp.hpp>
#include <util/lokinet_init.h>
#include <util/fs.hpp>
#include <util/logging/logger.hpp>
#include <util/logging/ostream_logger.hpp>
#include <util/str.hpp>
#include <util/thread/logic.hpp>
#include <csignal>
#include <cxxopts.hpp>
6 years ago
#include <string>
#include <iostream>
#include <future>
#ifdef USE_JEMALLOC
#include <new>
#include <jemalloc/jemalloc.h>
void*
operator new(std::size_t sz)
{
void* ptr = malloc(sz);
if (ptr)
return ptr;
else
throw std::bad_alloc{};
}
void
operator delete(void* ptr) noexcept
{
free(ptr);
}
void
operator delete(void* ptr, size_t) noexcept
{
free(ptr);
}
#endif
int
lokinet_main(int, char**);
#ifdef _WIN32
#include <strsafe.h>
5 years ago
extern "C" LONG FAR PASCAL
win32_signal_handler(EXCEPTION_POINTERS*);
extern "C" VOID FAR PASCAL
win32_daemon_entry(DWORD, LPTSTR*);
VOID ReportSvcStatus(DWORD, DWORD, DWORD);
VOID
insert_description();
SERVICE_STATUS SvcStatus;
SERVICE_STATUS_HANDLE SvcStatusHandle;
bool start_as_daemon = false;
#endif
std::shared_ptr<llarp::Context> ctx;
std::promise<int> exit_code;
6 years ago
void
handle_signal(int sig)
6 years ago
{
if (ctx)
LogicCall(ctx->logic, std::bind(&llarp::Context::HandleSignal, ctx.get(), sig));
else
std::cerr << "Received signal " << sig << ", but have no context yet. Ignoring!" << std::endl;
6 years ago
}
#ifdef _WIN32
int
startWinsock()
{
WSADATA wsockd;
int err;
err = ::WSAStartup(MAKEWORD(2, 2), &wsockd);
if (err)
{
perror("Failed to start Windows Sockets");
return err;
}
::CreateMutex(nullptr, FALSE, "lokinet_win32_daemon");
return 0;
}
extern "C" BOOL FAR PASCAL
handle_signal_win32(DWORD fdwCtrlType)
{
UNREFERENCED_PARAMETER(fdwCtrlType);
handle_signal(SIGINT);
return TRUE; // probably unreachable
}
void
install_win32_daemon()
{
SC_HANDLE schSCManager;
SC_HANDLE schService;
std::array<char, 1024> szPath{};
if (!GetModuleFileName(nullptr, szPath.data(), MAX_PATH))
{
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(
nullptr, // local computer
nullptr, // ServicesActive database
SC_MANAGER_ALL_ACCESS); // full access rights
if (nullptr == schSCManager)
{
llarp::LogError("OpenSCManager failed ", GetLastError());
return;
}
// Create the service
schService = CreateService(
schSCManager, // SCM database
"lokinet", // name of service
"Lokinet for Windows", // service name to display
SERVICE_ALL_ACCESS, // desired access
SERVICE_WIN32_OWN_PROCESS, // service type
SERVICE_DEMAND_START, // start type
SERVICE_ERROR_NORMAL, // error control type
szPath.data(), // path to service's binary
nullptr, // no load ordering group
nullptr, // no tag identifier
nullptr, // no dependencies
nullptr, // LocalSystem account
nullptr); // no password
if (schService == nullptr)
{
llarp::LogError("CreateService failed ", GetLastError());
CloseServiceHandle(schSCManager);
return;
}
else
llarp::LogInfo("Service installed successfully");
CloseServiceHandle(schService);
CloseServiceHandle(schSCManager);
insert_description();
}
VOID
insert_description()
{
SC_HANDLE schSCManager;
SC_HANDLE schService;
SERVICE_DESCRIPTION sd;
LPTSTR szDesc =
"LokiNET is a free, open source, private, "
"decentralized, \"market based sybil resistant\" "
"and IP based onion routing network";
// Get a handle to the SCM database.
schSCManager = OpenSCManager(
NULL, // local computer
NULL, // ServicesActive database
SC_MANAGER_ALL_ACCESS); // full access rights
if (nullptr == schSCManager)
{
llarp::LogError("OpenSCManager failed ", GetLastError());
return;
}
// Get a handle to the service.
schService = OpenService(
schSCManager, // SCM database
"lokinet", // name of service
SERVICE_CHANGE_CONFIG); // need change config access
if (schService == nullptr)
{
llarp::LogError("OpenService failed ", GetLastError());
CloseServiceHandle(schSCManager);
return;
}
// Change the service description.
sd.lpDescription = szDesc;
if (!ChangeServiceConfig2(
schService, // handle to service
SERVICE_CONFIG_DESCRIPTION, // change: description
&sd)) // new description
{
llarp::LogError("ChangeServiceConfig2 failed");
}
else
llarp::LogInfo("Service description updated successfully.");
CloseServiceHandle(schService);
CloseServiceHandle(schSCManager);
}
void
uninstall_win32_daemon()
{
SC_HANDLE schSCManager;
SC_HANDLE schService;
// Get a handle to the SCM database.
schSCManager = OpenSCManager(
nullptr, // local computer
nullptr, // ServicesActive database
SC_MANAGER_ALL_ACCESS); // full access rights
if (nullptr == schSCManager)
{
llarp::LogError("OpenSCManager failed ", GetLastError());
return;
}
// Get a handle to the service.
schService = OpenService(
schSCManager, // SCM database
"lokinet", // name of service
0x10000); // need delete access
if (schService == nullptr)
{
llarp::LogError("OpenService failed ", GetLastError());
CloseServiceHandle(schSCManager);
return;
}
// Delete the service.
if (!DeleteService(schService))
{
llarp::LogError("DeleteService failed ", GetLastError());
}
else
llarp::LogInfo("Service deleted successfully\n");
CloseServiceHandle(schService);
CloseServiceHandle(schSCManager);
}
#endif
/// this sets up, configures and runs the main context
static void
run_main_context(const fs::path confFile, const llarp::RuntimeOptions opts)
{
try
{
// this is important, can downgrade from Info though
llarp::LogDebug("Running from: ", fs::current_path().string());
llarp::LogInfo("Using config file: ", confFile);
llarp::Config conf;
Config file improvements (#1397) * Config file API/comment improvements API improvements: ================= Make the config API use position-independent tag parameters (Required, Default{123}, MultiValue) rather than a sequence of bools with overloads. For example, instead of: conf.defineOption<int>("a", "b", false, true, 123, [] { ... }); you now write: conf.defineOption<int>("a", "b", MultiValue, Default{123}, [] { ... }); The tags are: - Required - MultiValue - Default{value} plus new abilities (see below): - Hidden - RelayOnly - ClientOnly - Comment{"line1", "line2", "line3"} Made option definition more powerful: ===================================== - `Hidden` allows you to define an option that won't show up in the generated config file if it isn't set. - `RelayOnly`/`ClientOnly` sets up an option that is only accepted and only shows up for relay or client configs. (If neither is specified the option shows up in both modes). - `Comment{...}` lets the option comments be specified as part of the defineOption. Comment improvements ==================== - Rewrote comments for various options to expand on details. - Inlined all the comments with the option definitions. - Several options that were missing comments got comments added. - Made various options for deprecated and or internal options hidden by default so that they don't show up in a default config file. - show the section comment (but not option comments) *after* the [section] tag instead of before it as it makes more sense that way (particularly for the [bind] section which has a new long comment to describe how it works). Disable profiling by default ============================ We had this weird state where we use and store profiling by default but never *load* it when starting up. This commit makes us just not use profiling at all unless explicitly enabled. Other misc changes: =================== - change default worker threads to 0 (= num cpus) instead of 1, and fix it to allow 0. - Actually apply worker-threads option - fixed default data-dir value erroneously having quotes around it - reordered ifname/ifaddr/mapaddr (was previously mapaddr/ifaddr/ifname) as mapaddr is a sort of specialization of ifaddr and so makes more sense to come after it (particularly because it now references ifaddr in its help message). - removed peer-stats option (since we always require it for relays and never use it for clients) - removed router profiles filename option (this doesn't need to be configurable) - removed defunct `service-node-seed` option - Change default logging output file to "" (which means stdout), and also made "-" work for stdout. * Router hive compilation fixes * Comments for SNApp SRV settings in ini file * Add extra blank line after section comments * Better deprecated option handling Allow {client,relay}-only options in {relay,client} configs to be specified as implicitly deprecated options: they warn, and don't set anything. Add an explicit `Deprecated` tag and move deprecated option handling into definition.cpp. * Move backwards compat options into section definitions Keep the "addBackwardsCompatibleConfigOptions" only for options in sections that no longer exist. * Fix INI parsing issues & C++17-ify - don't allow inline comments because it seems they aren't allowed in ini formats in general, and is going to cause problems if there is a comment character in a value (e.g. an exit auth string). Additionally it was breaking on a line such as: # some comment; see? because it was treating only `; see?` as the comment and then producing an error message about the rest of the line being invalid. - make section parsing stricter: the `[` and `]` have to be at the beginning at end of the line now (after stripping whitespace). - Move whitespace stripping to the top since everything in here does it. - chop off string_view suffix/prefix rather than maintaining position values - fix potential infinite loop/segfault when given a line such as `]foo[` * Make config parsing failure fatal Load() LogError's and returns false on failure, so we weren't aborting on config file errors. * Formatting: allow `{}` for empty functions/structs Instead of using two lines when empty: { } * Make default dns bind 127.0.0.1 on non-Linux * Don't show empty section; fix tests We can conceivably have sections that only make sense for clients or relays, and so want to completely omit that section if we have no options for the type of config being generated. Also fixes missing empty lines between tests. Co-authored-by: Thomas Winget <tewinget@gmail.com>
4 years ago
if (!conf.Load(confFile, opts.isRouter, confFile.parent_path()))
throw std::runtime_error{"Config file parsing failed"};
ctx = std::make_shared<llarp::Context>();
ctx->Configure(conf);
signal(SIGINT, handle_signal);
signal(SIGTERM, handle_signal);
#ifndef _WIN32
signal(SIGHUP, handle_signal);
#endif
ctx->Setup(opts);
llarp::util::SetThreadName("llarp-mainloop");
auto result = ctx->Run(opts);
exit_code.set_value(result);
}
catch (std::exception& e)
{
llarp::LogError("Fatal: caught exception while running: ", e.what());
exit_code.set_exception(std::current_exception());
}
catch (...)
{
llarp::LogError("Fatal: caught non-standard exception while running");
exit_code.set_exception(std::current_exception());
}
}
int
main(int argc, char* argv[])
{
#ifndef _WIN32
return lokinet_main(argc, argv);
#else
4 years ago
SERVICE_TABLE_ENTRY DispatchTable[] = {{"lokinet", (LPSERVICE_MAIN_FUNCTION)win32_daemon_entry},
{NULL, NULL}};
if (lstrcmpi(argv[1], "--win32-daemon") == 0)
{
start_as_daemon = true;
StartServiceCtrlDispatcher(DispatchTable);
}
else
return lokinet_main(argc, argv);
#endif
}
int
lokinet_main(int argc, char* argv[])
{
auto result = Lokinet_INIT();
if (result)
{
return result;
}
llarp::RuntimeOptions opts;
#ifdef _WIN32
if (startWinsock())
return -1;
ReportSvcStatus(SERVICE_RUNNING, NO_ERROR, 0);
SetConsoleCtrlHandler(handle_signal_win32, TRUE);
5 years ago
// SetUnhandledExceptionFilter(win32_signal_handler);
#endif
cxxopts::Options options(
"lokinet",
"LokiNET is a free, open source, private, "
"decentralized, \"market based sybil resistant\" "
"and IP based onion routing network");
options.add_options()("v,verbose", "Verbose", cxxopts::value<bool>())
#ifdef _WIN32
("install", "install win32 daemon to SCM", cxxopts::value<bool>())(
"remove", "remove win32 daemon from SCM", cxxopts::value<bool>())
#endif
("h,help", "help", cxxopts::value<bool>())("version", "version", cxxopts::value<bool>())(
"g,generate", "generate client config", cxxopts::value<bool>())(
"r,router", "run as router instead of client", cxxopts::value<bool>())(
"f,force", "overwrite", cxxopts::value<bool>())(
"c,colour", "colour output", cxxopts::value<bool>()->default_value("true"))(
"b,background",
"background mode (start, but do not connect to the network)",
cxxopts::value<bool>())(
"config", "path to configuration file", cxxopts::value<std::string>());
options.parse_positional("config");
bool genconfigOnly = false;
bool overwrite = false;
fs::path configFile;
try
{
auto result = options.parse(argc, argv);
if (result.count("verbose") > 0)
{
SetLogLevel(llarp::eLogDebug);
llarp::LogDebug("debug logging activated");
}
if (!result["colour"].as<bool>())
{
llarp::LogContext::Instance().logStream =
std::make_unique<llarp::OStreamLogStream>(false, std::cerr);
}
if (result.count("help"))
{
std::cout << options.help() << std::endl;
return 0;
}
if (result.count("version"))
{
std::cout << llarp::VERSION_FULL << std::endl;
return 0;
}
#ifdef _WIN32
if (result.count("install"))
{
install_win32_daemon();
return 0;
}
if (result.count("remove"))
{
uninstall_win32_daemon();
return 0;
}
#endif
if (result.count("generate") > 0)
{
genconfigOnly = true;
}
if (result.count("background") > 0)
{
opts.background = true;
}
if (result.count("router") > 0)
{
opts.isRouter = true;
}
if (result.count("force") > 0)
{
overwrite = true;
}
if (result.count("config") > 0)
{
auto arg = result["config"].as<std::string>();
if (!arg.empty())
{
configFile = arg;
}
}
}
catch (const cxxopts::option_not_exists_exception& ex)
{
std::cerr << ex.what();
std::cout << options.help() << std::endl;
return 1;
}
if (!configFile.empty())
{
// when we have an explicit filepath
fs::path basedir = configFile.parent_path();
if (genconfigOnly)
{
llarp::ensureConfig(basedir, configFile, overwrite, opts.isRouter);
}
else
{
std::error_code ec;
if (!fs::exists(configFile, ec))
{
llarp::LogError("Config file not found ", configFile);
return 1;
}
if (ec)
throw std::runtime_error(llarp::stringify("filesystem error: ", ec));
}
}
else
{
llarp::ensureConfig(
llarp::GetDefaultDataDir(), llarp::GetDefaultConfigPath(), overwrite, opts.isRouter);
configFile = llarp::GetDefaultConfigPath();
}
if (genconfigOnly)
{
return 0;
}
std::thread main_thread{std::bind(&run_main_context, configFile, opts)};
auto ftr = exit_code.get_future();
do
{
// do periodic non lokinet related tasks here
if (ctx and ctx->IsUp() and not ctx->LooksAlive())
{
4 years ago
for (const auto& wtf : {"you have been visited by the mascott of the "
"deadlocked router.",
"⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⣀⣴⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣄⠄⠄⠄⠄",
"⠄⠄⠄⠄⠄⢀⣀⣀⡀⠄⠄⠄⡠⢲⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⡀⠄⠄",
"⠄⠄⠄⠔⣈⣀⠄⢔⡒⠳⡴⠊⠄⠸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠿⣿⣿⣧⠄⠄",
"⠄⢜⡴⢑⠖⠊⢐⣤⠞⣩⡇⠄⠄⠄⠙⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣆⠄⠝⠛⠋⠐",
"⢸⠏⣷⠈⠄⣱⠃⠄⢠⠃⠐⡀⠄⠄⠄⠄⠙⠻⢿⣿⣿⣿⣿⣿⣿⣿⡿⠛⠸⠄⠄⠄⠄",
"⠈⣅⠞⢁⣿⢸⠘⡄⡆⠄⠄⠈⠢⡀⠄⠄⠄⠄⠄⠄⠉⠙⠛⠛⠛⠉⠉⡀⠄⠡⢀⠄⣀",
"⠄⠙⡎⣹⢸⠄⠆⢘⠁⠄⠄⠄⢸⠈⠢⢄⡀⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠃⠄⠄⠄⠄⠄",
"⠄⠄⠑⢿⠈⢆⠘⢼⠄⠄⠄⠄⠸⢐⢾⠄⡘⡏⠲⠆⠠⣤⢤⢤⡤⠄⣖⡇⠄⠄⠄⠄⠄",
"⣴⣶⣿⣿⣣⣈⣢⣸⠄⠄⠄⠄⡾⣷⣾⣮⣤⡏⠁⠘⠊⢠⣷⣾⡛⡟⠈⠄⠄⠄⠄⠄⠄",
"⣿⣿⣿⣿⣿⠉⠒⢽⠄⠄⠄⠄⡇⣿⣟⣿⡇⠄⠄⠄⠄⢸⣻⡿⡇⡇⠄⠄⠄⠄⠄⠄⠄",
"⠻⣿⣿⣿⣿⣄⠰⢼⠄⠄⠄⡄⠁⢻⣍⣯⠃⠄⠄⠄⠄⠈⢿⣻⠃⠈⡆⡄⠄⠄⠄⠄⠄",
"⠄⠙⠿⠿⠛⣿⣶⣤⡇⠄⠄⢣⠄⠄⠈⠄⢠⠂⠄⠁⠄⡀⠄⠄⣀⠔⢁⠃⠄⠄⠄⠄⠄",
"⠄⠄⠄⠄⠄⣿⣿⣿⣿⣾⠢⣖⣶⣦⣤⣤⣬⣤⣤⣤⣴⣶⣶⡏⠠⢃⠌⠄⠄⠄⠄⠄⠄",
"⠄⠄⠄⠄⠄⠿⠿⠟⠛⡹⠉⠛⠛⠿⠿⣿⣿⣿⣿⣿⡿⠂⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄",
"⠠⠤⠤⠄⠄⣀⠄⠄⠄⠑⠠⣤⣀⣀⣀⡘⣿⠿⠙⠻⡍⢀⡈⠂⠄⠄⠄⠄⠄⠄⠄⠄⠄",
"⠄⠄⠄⠄⠄⠄⠑⠠⣠⣴⣾⣿⣿⣿⣿⣿⣿⣇⠉⠄⠻⣿⣷⣄⡀⠄⠄⠄⠄⠄⠄⠄⠄",
"file a bug report now or be cursed with this "
"annoying image in your syslog for all time."})
{
LogError(wtf);
llarp::LogContext::Instance().ImmediateFlush();
}
std::abort();
}
} while (ftr.wait_for(std::chrono::seconds(1)) != std::future_status::ready);
main_thread.join();
int code = 0;
try
{
code = ftr.get();
}
catch (const std::exception& e)
{
std::cerr << "main thread threw exception: " << e.what() << std::endl;
code = 1;
}
catch (...)
{
std::cerr << "main thread threw non-standard exception" << std::endl;
code = 2;
}
llarp::LogContext::Instance().ImmediateFlush();
#ifdef _WIN32
::WSACleanup();
ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, code);
#endif
if (ctx)
{
ctx.reset();
}
return code;
7 years ago
}
#ifdef _WIN32
VOID
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.
SetServiceStatus(SvcStatusHandle, &SvcStatus);
}
VOID FAR PASCAL
SvcCtrlHandler(DWORD dwCtrl)
{
// Handle the requested control code.
switch (dwCtrl)
{
case SERVICE_CONTROL_STOP:
ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0);
// Signal the service to stop.
handle_signal(SIGINT);
return;
case SERVICE_CONTROL_INTERROGATE:
break;
default:
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
VOID FAR PASCAL
win32_daemon_entry(DWORD argc, LPTSTR* argv)
{
// Register the handler function for the service
SvcStatusHandle = RegisterServiceCtrlHandler("lokinet", SvcCtrlHandler);
if (!SvcStatusHandle)
{
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] = "c:/programdata/.lokinet/lokinet.ini";
argv[2] = nullptr;
lokinet_main(argc, argv);
}
#endif