From d507e4435042284a014cdc9802a4ffa484f8e4d8 Mon Sep 17 00:00:00 2001 From: Jason Rhinelander Date: Sat, 3 Oct 2020 16:23:13 -0300 Subject: [PATCH 01/50] Don't use -march=native for static linux build (#1390) --- .drone.jsonnet | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.drone.jsonnet b/.drone.jsonnet index 00821e6b7..98e83ce6e 100644 --- a/.drone.jsonnet +++ b/.drone.jsonnet @@ -216,7 +216,8 @@ local mac_builder(name, build_type='Release', werror=true, cmake_extra='', extra // Static build (on bionic) which gets uploaded to builds.lokinet.dev: debian_pipeline("Static (bionic amd64)", "ubuntu:bionic", deps='g++-8 python3-dev', lto=true, cmake_extra='-DBUILD_STATIC_DEPS=ON -DBUILD_SHARED_LIBS=OFF -DSTATIC_LINK=ON -DCMAKE_C_COMPILER=gcc-8 -DCMAKE_CXX_COMPILER=g++-8 ' + - '-DDOWNLOAD_SODIUM=ON -DDOWNLOAD_CURL=ON -DDOWNLOAD_UV=ON -DWITH_SYSTEMD=OFF', + '-DCMAKE_CXX_FLAGS="-march=x86-64 -mtune=haswell" -DCMAKE_C_FLAGS="-march=x86-64 -mtune=haswell" -DNATIVE_BUILD=OFF ' + + '-DDOWNLOAD_UV=ON -DWITH_SYSTEMD=OFF', extra_cmds=[ '../contrib/ci/drone-check-static-libs.sh', '../contrib/ci/drone-static-upload.sh' From 03d6f191d1f5efb4061150ea89ebe9dad95999bf Mon Sep 17 00:00:00 2001 From: Jeff Date: Mon, 5 Oct 2020 11:50:59 -0400 Subject: [PATCH 02/50] add status command to lokinet-vpn (#1393) --- daemon/lokinet-vpn.cpp | 86 +++++++++++++++++++++--------------------- 1 file changed, 44 insertions(+), 42 deletions(-) diff --git a/daemon/lokinet-vpn.cpp b/daemon/lokinet-vpn.cpp index 966d329ea..51ac591bc 100644 --- a/daemon/lokinet-vpn.cpp +++ b/daemon/lokinet-vpn.cpp @@ -55,14 +55,20 @@ main(int argc, char* argv[]) { cxxopts::Options opts("lokinet-vpn", "LokiNET vpn control utility"); - opts.add_options()("v,verbose", "Verbose", cxxopts::value())( - "h,help", "help", cxxopts::value())("up", "put vpn up", cxxopts::value())( - "down", "put vpn down", cxxopts::value())( - "exit", "specify exit node address", cxxopts::value())( - "rpc", "rpc url for lokinet", cxxopts::value())( - "endpoint", "endpoint to use", cxxopts::value())( - "token", "exit auth token to use", cxxopts::value()); - + // clang-format off + opts.add_options() + ("v,verbose", "Verbose", cxxopts::value()) + ("h,help", "help", cxxopts::value()) + ("up", "put vpn up", cxxopts::value()) + ("down", "put vpn down", cxxopts::value()) + ("exit", "specify exit node address", cxxopts::value()) + ("rpc", "rpc url for lokinet", cxxopts::value()) + ("endpoint", "endpoint to use", cxxopts::value()) + ("token", "exit auth token to use", cxxopts::value()) + ("auth", "exit auth token to use", cxxopts::value()) + ("status", "print status and exit", cxxopts::value()) + ; + // clang-format on lokimq::address rpcURL("tcp://127.0.0.1:1190"); std::string exitAddress; std::string endpoint = "default"; @@ -70,6 +76,7 @@ main(int argc, char* argv[]) lokimq::LogLevel logLevel = lokimq::LogLevel::warn; bool goUp = false; bool goDown = false; + bool printStatus = false; try { const auto result = opts.parse(argc, argv); @@ -94,6 +101,7 @@ main(int argc, char* argv[]) } goUp = result.count("up") > 0; goDown = result.count("down") > 0; + printStatus = result.count("status") > 0; if (result.count("endpoint") > 0) { @@ -103,6 +111,10 @@ main(int argc, char* argv[]) { token = result["token"].as(); } + if (result.count("auth") > 0) + { + token = result["auth"].as(); + } } catch (const cxxopts::option_not_exists_exception& ex) { @@ -115,7 +127,7 @@ main(int argc, char* argv[]) std::cout << ex.what() << std::endl; return 1; } - if ((not goUp) and (not goDown)) + if ((not goUp) and (not goDown) and (not printStatus)) { std::cout << opts.help() << std::endl; return 1; @@ -149,47 +161,37 @@ main(int argc, char* argv[]) return 1; } - std::vector firstHops; - std::string ifname; - - const auto maybe_status = LMQ_Request(lmq, connID, "llarp.status"); - if (not maybe_status.has_value()) + if (printStatus) { - std::cout << "call to llarp.status failed" << std::endl; - return 1; - } + const auto maybe_status = LMQ_Request(lmq, connID, "llarp.status"); + if (not maybe_status.has_value()) + { + std::cout << "call to llarp.status failed" << std::endl; + return 1; + } - try - { - // extract first hops - const auto& links = maybe_status->at("result")["links"]["outbound"]; - for (const auto& link : links) + try { - const auto& sessions = link["sessions"]["established"]; - for (const auto& session : sessions) + const auto& ep = maybe_status->at("result").at("services").at(endpoint); + const auto exitMap = ep.at("exitMap"); + if (exitMap.empty()) { - std::string addr = session["remoteAddr"]; - const auto pos = addr.find(":"); - firstHops.push_back(addr.substr(0, pos)); + std::cout << "no exits" << std::endl; + } + else + { + for (const auto& [range, exit] : exitMap.items()) + { + std::cout << range << " via " << exit.get() << std::endl; + } } } - // get interface name -#ifdef _WIN32 - // strip off the "::ffff." - ifname = maybe_status->at("result")["services"][endpoint]["ifaddr"]; - const auto pos = ifname.find("/"); - if (pos != std::string::npos) + catch (std::exception& ex) { - ifname = ifname.substr(0, pos); + std::cout << "failed to parse result: " << ex.what() << std::endl; + return 1; } -#else - ifname = maybe_status->at("result")["services"][endpoint]["ifname"]; -#endif - } - catch (std::exception& ex) - { - std::cout << "failed to parse result: " << ex.what() << std::endl; - return 1; + return 0; } if (goUp) { From ff231068528f8fb478de8150b1eb44b8ad098a40 Mon Sep 17 00:00:00 2001 From: Jeff Date: Mon, 5 Oct 2020 12:55:19 -0400 Subject: [PATCH 03/50] don't allow running lokid-rpc as client (#1394) --- llarp/rpc/lokid_rpc_client.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/llarp/rpc/lokid_rpc_client.cpp b/llarp/rpc/lokid_rpc_client.cpp index 3b6417a84..fd841ff09 100644 --- a/llarp/rpc/lokid_rpc_client.cpp +++ b/llarp/rpc/lokid_rpc_client.cpp @@ -47,6 +47,10 @@ namespace llarp void LokidRpcClient::ConnectAsync(lokimq::address url) { + if (not m_Router->IsServiceNode()) + { + throw std::runtime_error("we cannot talk to lokid while not a service node"); + } LogInfo("connecting to lokid via LMQ at ", url); m_Connection = m_lokiMQ->connect_remote( url, From b0088b3298132dc6de5d12b484a6d460d498d891 Mon Sep 17 00:00:00 2001 From: Jeff Date: Tue, 6 Oct 2020 09:44:51 -0400 Subject: [PATCH 04/50] dont segfault on exit (#1396) * dont segfault on exit * initialize m_isServiceNode earlier. --- llarp/router/router.cpp | 18 +++++++++++++++--- llarp/router/router.hpp | 10 ++-------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/llarp/router/router.cpp b/llarp/router/router.cpp index f7c15b5c3..bd4d611c0 100644 --- a/llarp/router/router.cpp +++ b/llarp/router/router.cpp @@ -283,6 +283,8 @@ namespace llarp _nodedb = nodedb; + m_isServiceNode = conf.router.m_isRelay; + if (whitelistRouters) { m_lokidRpcClient->ConnectAsync(lokidRPCAddr); @@ -455,8 +457,6 @@ namespace llarp if (usingSNSeed) ident_keyfile = conf.lokid.ident_keyfile; - m_isServiceNode = conf.router.m_isRelay; - networkConfig = conf.network; /// build a set of strictConnectPubkeys ( @@ -1120,6 +1120,7 @@ namespace llarp Router::AfterStopLinks() { Close(); + m_lmq.reset(); } void @@ -1128,7 +1129,6 @@ namespace llarp StopLinks(); nodedb()->AsyncFlushToDisk(); _logic->call_later(200ms, std::bind(&Router::AfterStopLinks, this)); - m_lmq.reset(); } void @@ -1240,6 +1240,18 @@ namespace llarp return true; } + void + Router::QueueWork(std::function func) + { + m_lmq->job(std::move(func)); + } + + void + Router::QueueDiskIO(std::function func) + { + m_lmq->job(std::move(func), m_DiskThread); + } + bool Router::HasClientExit() const { diff --git a/llarp/router/router.hpp b/llarp/router/router.hpp index 6e295568c..1ae81603c 100644 --- a/llarp/router/router.hpp +++ b/llarp/router/router.hpp @@ -170,16 +170,10 @@ namespace llarp } void - QueueWork(std::function func) override - { - m_lmq->job(std::move(func)); - } + QueueWork(std::function func) override; void - QueueDiskIO(std::function func) override - { - m_lmq->job(std::move(func), m_DiskThread); - } + QueueDiskIO(std::function func) override; IpAddress _ourAddress; From 62a90f4c8223dee26e72358d266f0bb73d63989a Mon Sep 17 00:00:00 2001 From: Jason Rhinelander Date: Wed, 7 Oct 2020 12:21:07 -0300 Subject: [PATCH 05/50] macos package fixes for 0.3.0 - bumps deployment target to 10.15 because earlier versions don't support C++17. - remove double-include of installer.cmake - use new static dep lokinet build system - replace lokinetctl with lokinet-vpn --- CMakeLists.txt | 6 +----- cmake/macos_installer_deps.cmake | 11 +++++------ 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e96dc26bd..0c3dadfc8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.10) # bionic's cmake version # Has to be set before `project()`, and ignored on non-macos: -set(CMAKE_OSX_DEPLOYMENT_TARGET 10.14 CACHE STRING "macOS deployment target (Apple clang only)") +set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15 CACHE STRING "macOS deployment target (Apple clang only)") find_program(CCACHE_PROGRAM ccache) if(CCACHE_PROGRAM) @@ -377,10 +377,6 @@ if(NOT TARGET uninstall) endif() -if(BUILD_PACKAGE) - include(cmake/installer.cmake) -endif() - if(BUILD_PACKAGE) include(cmake/installer.cmake) endif() diff --git a/cmake/macos_installer_deps.cmake b/cmake/macos_installer_deps.cmake index 0f045dd56..217433d5d 100644 --- a/cmake/macos_installer_deps.cmake +++ b/cmake/macos_installer_deps.cmake @@ -26,7 +26,7 @@ ExternalProject_Add(lokinet-gui GIT_REPOSITORY "${LOKINET_GUI_REPO}" GIT_TAG "${LOKINET_GUI_CHECKOUT}" CMAKE_ARGS -DMACOS_APP=ON -DCMAKE_INSTALL_PREFIX=${PROJECT_BINARY_DIR} -DMACOS_SIGN=${MACOS_SIGN_APP} - -DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH} + -DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH} -DBUILD_STATIC_DEPS=ON -DBUILD_SHARED_LIBS=OFF ) @@ -60,16 +60,15 @@ set(CPACK_GENERATOR "productbuild") set(CPACK_PACKAGING_INSTALL_PREFIX "/opt/lokinet") set(CPACK_POSTFLIGHT_LOKINET_SCRIPT ${CMAKE_SOURCE_DIR}/contrib/macos/postinstall) -# The GUI is GPLv3, and so the bundled core+GUI must be as well: -set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/contrib/gpl-3.0.txt") +set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE.txt") set(CPACK_PRODUCTBUILD_IDENTITY_NAME "${MACOS_SIGN_PKG}") if(MACOS_SIGN_APP) add_custom_target(sign ALL - echo "Signing lokinet and lokinetctl binaries" - COMMAND codesign -s "${MACOS_SIGN_APP}" --strict --options runtime --force -vvv $ $ - DEPENDS lokinet lokinetctl + echo "Signing lokinet and lokinet-vpn binaries" + COMMAND codesign -s "${MACOS_SIGN_APP}" --strict --options runtime --force -vvv $ $ + DEPENDS lokinet lokinet-vpn ) endif() From af6caf776a4a7a8580d96d5e72cb9e037708e3c4 Mon Sep 17 00:00:00 2001 From: Jason Rhinelander Date: Wed, 7 Oct 2020 19:22:58 -0300 Subject: [PATCH 06/50] 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("a", "b", false, true, 123, [] { ... }); you now write: conf.defineOption("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 --- .clang-format | 18 +- daemon/main.cpp | 3 +- jni/lokinet_jni_vpnio.hpp | 9 +- llarp/config/config.cpp | 1221 +++++++++-------- llarp/config/config.hpp | 3 - llarp/config/definition.cpp | 106 +- llarp/config/definition.hpp | 217 ++- llarp/config/ini.cpp | 70 +- llarp/config/key_manager.cpp | 3 +- llarp/context.cpp | 3 +- llarp/crypto/encrypted.hpp | 3 +- llarp/crypto/encrypted_frame.hpp | 9 +- llarp/crypto/types.hpp | 24 +- llarp/dht/bucket.hpp | 3 +- llarp/dht/explorenetworkjob.hpp | 3 +- llarp/dht/kademlia.hpp | 3 +- llarp/dht/key.hpp | 12 +- llarp/dht/localrouterlookup.cpp | 3 +- llarp/dht/localserviceaddresslookup.cpp | 3 +- llarp/dht/localtaglookup.cpp | 3 +- llarp/dht/message.cpp | 6 +- llarp/dht/message.hpp | 3 +- llarp/dht/messages/findintro.hpp | 3 +- llarp/dht/messages/findname.cpp | 3 +- llarp/dht/messages/findrouter.hpp | 9 +- llarp/dht/messages/gotintro.cpp | 3 +- llarp/dht/messages/gotintro.hpp | 9 +- llarp/dht/messages/gotrouter.hpp | 12 +- llarp/dht/messages/pubintro.hpp | 6 +- llarp/dht/node.hpp | 3 +- llarp/dht/publishservicejob.cpp | 6 +- llarp/dht/taglookup.hpp | 3 +- llarp/dht/tx.hpp | 3 +- llarp/dht/txowner.hpp | 3 +- llarp/dns/message.cpp | 6 +- llarp/dns/question.cpp | 6 +- llarp/dns/rr.cpp | 6 +- llarp/dns/server.cpp | 6 +- llarp/dns/unbound_resolver.cpp | 3 +- llarp/ev/ev.hpp | 36 +- llarp/ev/ev_libuv.cpp | 3 +- llarp/ev/ev_win32.hpp | 3 +- llarp/ev/pipe.cpp | 3 +- llarp/ev/vpnio.hpp | 9 +- llarp/exit/context.cpp | 3 +- llarp/exit/endpoint.hpp | 3 +- llarp/exit/exit_messages.hpp | 3 +- llarp/exit/session.hpp | 3 +- llarp/handlers/null.hpp | 3 +- llarp/iwp/linklayer.cpp | 3 +- llarp/iwp/message_buffer.cpp | 3 +- llarp/link/server.cpp | 3 +- llarp/messages/discard.hpp | 3 +- llarp/messages/link_intro.hpp | 3 +- llarp/messages/link_message_parser.cpp | 3 +- llarp/messages/relay_commit.hpp | 3 +- llarp/messages/relay_status.cpp | 3 +- llarp/messages/relay_status.hpp | 3 +- llarp/net/exit_info.hpp | 3 +- llarp/net/ip_address.cpp | 3 +- llarp/net/ip_packet.hpp | 9 +- llarp/net/uint128.hpp | 9 +- llarp/nodedb.cpp | 3 +- llarp/nodedb.hpp | 3 +- llarp/path/path_context.cpp | 6 +- llarp/path/pathset.cpp | 3 +- llarp/path/transit_hop.cpp | 3 +- llarp/peerstats/orm.hpp | 6 +- llarp/peerstats/peer_db.cpp | 3 - llarp/peerstats/types.cpp | 3 +- llarp/profiling.cpp | 3 +- llarp/router/outbound_message_handler.cpp | 3 +- llarp/router/outbound_session_maker.cpp | 3 +- llarp/router/rc_gossiper.cpp | 3 +- llarp/router/router.cpp | 27 +- llarp/router/router.hpp | 3 - llarp/router_contact.cpp | 3 +- llarp/router_id.hpp | 9 +- llarp/router_version.cpp | 3 +- llarp/routing/message_parser.cpp | 3 +- llarp/routing/path_confirm_message.cpp | 3 +- llarp/rpc/endpoint_rpc.cpp | 3 +- llarp/rpc/rpc_server.cpp | 3 +- llarp/service/address.hpp | 12 +- llarp/service/context.cpp | 3 +- llarp/service/endpoint.cpp | 3 +- .../service/hidden_service_address_lookup.cpp | 3 +- llarp/service/protocol.cpp | 6 +- llarp/service/router_lookup_job.cpp | 3 +- llarp/service/sendcontext.cpp | 3 +- llarp/service/tag.hpp | 6 +- llarp/simulation/sim_context.cpp | 6 +- llarp/tooling/dht_event.hpp | 12 +- llarp/tooling/hive_context.cpp | 3 +- llarp/tooling/hive_router.cpp | 3 +- llarp/tooling/path_event.hpp | 12 +- llarp/tooling/peer_stats_event.hpp | 6 +- llarp/tooling/rc_event.hpp | 6 +- llarp/tooling/router_event.hpp | 3 +- llarp/util/buffer.hpp | 15 +- llarp/util/codel.hpp | 3 +- llarp/util/decaying_hashset.hpp | 3 +- llarp/util/decaying_hashtable.hpp | 3 +- llarp/util/logging/android_logger.cpp | 6 +- llarp/util/logging/json_logger.hpp | 3 +- llarp/util/logging/logger.cpp | 17 +- llarp/util/logging/logger_internal.hpp | 3 +- llarp/util/logging/logger_syslog.hpp | 6 +- llarp/util/logging/ostream_logger.cpp | 3 +- llarp/util/logging/ostream_logger.hpp | 3 +- llarp/util/logging/syslog_logger.cpp | 3 +- llarp/util/meta/traits.hpp | 18 +- llarp/util/printer.hpp | 3 +- llarp/util/thread/barrier.hpp | 3 +- llarp/util/thread/queue.hpp | 6 +- llarp/util/thread/threading.hpp | 9 +- llarp/win32/win32_intrnl.c | 3 +- pybind/llarp/config.cpp | 5 +- pybind/llarp/handlers/pyhandler.hpp | 3 +- pybind/llarp/tooling/peer_stats_event.hpp | 3 +- test/config/test_llarp_config_definition.cpp | 118 +- test/config/test_llarp_config_output.cpp | 102 +- test/exit/test_llarp_exit_context.cpp | 1 - 123 files changed, 1256 insertions(+), 1213 deletions(-) diff --git a/.clang-format b/.clang-format index f510ba4a1..7109a2362 100644 --- a/.clang-format +++ b/.clang-format @@ -14,7 +14,23 @@ AlwaysBreakAfterDefinitionReturnType: All AlwaysBreakAfterReturnType: All AlwaysBreakTemplateDeclarations: 'true' BreakBeforeBinaryOperators: NonAssignment -BreakBeforeBraces: Allman +BreakBeforeBraces: Custom +BraceWrapping: + AfterCaseLabel: true + AfterClass: true + AfterControlStatement: true + AfterEnum: true + AfterFunction: true + AfterNamespace: true + AfterObjCDeclaration: true + AfterStruct: true + AfterUnion: true + AfterExternBlock: true + BeforeCatch: true + BeforeElse: true + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: false BreakBeforeTernaryOperators: 'true' BreakConstructorInitializersBeforeComma: 'true' Cpp11BracedListStyle: 'true' diff --git a/daemon/main.cpp b/daemon/main.cpp index 8db322a74..bd4951619 100644 --- a/daemon/main.cpp +++ b/daemon/main.cpp @@ -259,7 +259,8 @@ run_main_context(const fs::path confFile, const llarp::RuntimeOptions opts) llarp::LogInfo("Using config file: ", confFile); llarp::Config conf; - conf.Load(confFile, opts.isRouter, confFile.parent_path()); + if (!conf.Load(confFile, opts.isRouter, confFile.parent_path())) + throw std::runtime_error{"Config file parsing failed"}; ctx = std::make_shared(); ctx->Configure(conf); diff --git a/jni/lokinet_jni_vpnio.hpp b/jni/lokinet_jni_vpnio.hpp index fd2f374c7..ce9cde065 100644 --- a/jni/lokinet_jni_vpnio.hpp +++ b/jni/lokinet_jni_vpnio.hpp @@ -133,18 +133,15 @@ struct lokinet_jni_vpnio : public lokinet::VPNIO { void InjectSuccess() override - { - } + {} void InjectFail() override - { - } + {} void Tick() override - { - } + {} }; #endif \ No newline at end of file diff --git a/llarp/config/config.cpp b/llarp/config/config.cpp index d108438fc..2e7501137 100644 --- a/llarp/config/config.cpp +++ b/llarp/config/config.cpp @@ -17,6 +17,7 @@ #include #include #include +#include "constants/version.hpp" namespace llarp { @@ -29,51 +30,81 @@ namespace llarp constexpr int DefaultPublicPort = 1090; + using namespace config; + void RouterConfig::defineConfigOptions(ConfigDefinition& conf, const ConfigGenParameters& params) { - constexpr int DefaultJobQueueSize = 1024 * 8; - constexpr auto DefaultNetId = "lokinet"; - constexpr int DefaultWorkerThreads = 1; - constexpr int DefaultNetThreads = 1; - constexpr bool DefaultBlockBogons = true; + constexpr Default DefaultJobQueueSize{1024 * 8}; + constexpr Default DefaultWorkerThreads{0}; + constexpr Default DefaultBlockBogons{true}; - conf.defineOption("router", "job-queue-size", false, DefaultJobQueueSize, [this](int arg) { - if (arg < 1024) - throw std::invalid_argument("job-queue-size must be 1024 or greater"); + conf.defineOption( + "router", "job-queue-size", DefaultJobQueueSize, Hidden, [this](int arg) { + if (arg < 1024) + throw std::invalid_argument("job-queue-size must be 1024 or greater"); - m_JobQueueSize = arg; - }); + m_JobQueueSize = arg; + }); - conf.defineOption("router", "netid", false, DefaultNetId, [this](std::string arg) { - if (arg.size() > NetID::size()) - throw std::invalid_argument(stringify("netid is too long, max length is ", NetID::size())); + conf.defineOption( + "router", + "netid", + Default{llarp::DEFAULT_NETID}, + Comment{ + "Network ID; this is '"s + llarp::DEFAULT_NETID + "' for mainnet, 'gamma' for testnet.", + }, + [this](std::string arg) { + if (arg.size() > NetID::size()) + throw std::invalid_argument( + stringify("netid is too long, max length is ", NetID::size())); - m_netId = std::move(arg); - }); + m_netId = std::move(arg); + }); int minConnections = (params.isRelay ? DefaultMinConnectionsForRouter : DefaultMinConnectionsForClient); - conf.defineOption("router", "min-connections", false, minConnections, [=](int arg) { - if (arg < minConnections) - throw std::invalid_argument(stringify("min-connections must be >= ", minConnections)); + conf.defineOption( + "router", + "min-connections", + Default{minConnections}, + Comment{ + "Minimum number of routers lokinet will attempt to maintain connections to.", + }, + [=](int arg) { + if (arg < minConnections) + throw std::invalid_argument(stringify("min-connections must be >= ", minConnections)); - m_minConnectedRouters = arg; - }); + m_minConnectedRouters = arg; + }); int maxConnections = (params.isRelay ? DefaultMaxConnectionsForRouter : DefaultMaxConnectionsForClient); - conf.defineOption("router", "max-connections", false, maxConnections, [=](int arg) { - if (arg < maxConnections) - throw std::invalid_argument(stringify("max-connections must be >= ", maxConnections)); + conf.defineOption( + "router", + "max-connections", + Default{maxConnections}, + Comment{ + "Maximum number (hard limit) of routers lokinet will be connected to at any time.", + }, + [=](int arg) { + if (arg < maxConnections) + throw std::invalid_argument(stringify("max-connections must be >= ", maxConnections)); - m_maxConnectedRouters = arg; - }); + m_maxConnectedRouters = arg; + }); - conf.defineOption("router", "nickname", false, "", AssignmentAcceptor(m_nickname)); + conf.defineOption("router", "nickname", Hidden, AssignmentAcceptor(m_nickname)); conf.defineOption( - "router", "data-dir", false, params.defaultDataDir, [this](fs::path arg) { + "router", + "data-dir", + Default{params.defaultDataDir}, + Comment{ + "Optional directory for containing lokinet runtime data. This includes generated", + "private keys.", + }, + [this](fs::path arg) { if (not fs::exists(arg)) throw std::runtime_error( stringify("Specified [router]:data-dir ", arg, " does not exist")); @@ -81,19 +112,28 @@ namespace llarp m_dataDir = std::move(arg); }); - conf.defineOption("router", "public-ip", false, "", [this](std::string arg) { - if (not arg.empty()) - { - llarp::LogInfo("public ip ", arg, " size ", arg.size()); + conf.defineOption( + "router", + "public-ip", + RelayOnly, + Comment{ + "For complex network configurations where the detected IP is incorrect or non-public", + "this setting specifies the public IP at which this router is reachable. When", + "provided the public-port option must also be specified.", + }, + [this](std::string arg) { + if (not arg.empty()) + { + llarp::LogInfo("public ip ", arg, " size ", arg.size()); - if (arg.size() > 15) - throw std::invalid_argument(stringify("Not a valid IPv4 addr: ", arg)); + if (arg.size() > 15) + throw std::invalid_argument(stringify("Not a valid IPv4 addr: ", arg)); - m_publicAddress.setAddress(arg); - } - }); + m_publicAddress.setAddress(arg); + } + }); - conf.defineOption("router", "public-address", false, "", [this](std::string arg) { + conf.defineOption("router", "public-address", Hidden, [this](std::string arg) { if (not arg.empty()) { llarp::LogWarn( @@ -101,7 +141,7 @@ namespace llarp arg, " is deprecated, use public-ip=", arg, - " instead."); + " instead to avoid this warning and avoid future configuration problems."); if (arg.size() > 15) throw std::invalid_argument(stringify("Not a valid IPv4 addr: ", arg)); @@ -110,49 +150,101 @@ namespace llarp } }); - conf.defineOption("router", "public-port", false, DefaultPublicPort, [this](int arg) { - if (arg <= 0 || arg > std::numeric_limits::max()) - throw std::invalid_argument("public-port must be >= 0 and <= 65536"); + conf.defineOption( + "router", + "public-port", + RelayOnly, + Default{DefaultPublicPort}, + Comment{ + "When specifying public-ip=, this specifies the public UDP port at which this lokinet", + "router is reachable. Required when public-ip is used.", + }, + [this](int arg) { + if (arg <= 0 || arg > std::numeric_limits::max()) + throw std::invalid_argument("public-port must be >= 0 and <= 65536"); - m_publicAddress.setPort(arg); - }); + m_publicAddress.setPort(arg); + }); conf.defineOption( - "router", "worker-threads", false, DefaultWorkerThreads, [this](int arg) { - if (arg <= 0) - throw std::invalid_argument("worker-threads must be > 0"); + "router", + "worker-threads", + DefaultWorkerThreads, + Comment{ + "The number of threads available for performing cryptographic functions.", + "The minimum is one thread, but network performance may increase with more.", + "threads. Should not exceed the number of logical CPU cores.", + "0 means use the number of logical CPU cores detected at startup.", + }, + [this](int arg) { + if (arg < 0) + throw std::invalid_argument("worker-threads must be >= 0"); m_workerThreads = arg; }); - conf.defineOption("router", "net-threads", false, DefaultNetThreads, [this](int arg) { - if (arg <= 0) - throw std::invalid_argument("net-threads must be > 0"); - - m_numNetThreads = arg; - }); - + // Hidden option because this isn't something that should ever be turned off occasionally when + // doing dev/testing work. conf.defineOption( - "router", "block-bogons", false, DefaultBlockBogons, AssignmentAcceptor(m_blockBogons)); + "router", "block-bogons", DefaultBlockBogons, Hidden, AssignmentAcceptor(m_blockBogons)); - conf.defineOption( - "router", "contact-file", false, "", AssignmentAcceptor(m_routerContactFile)); + constexpr auto relative_to_datadir = + "An absolute path is used as-is, otherwise relative to 'data-dir'."; conf.defineOption( - "router", "encryption-privkey", false, "", AssignmentAcceptor(m_encryptionKeyFile)); + "router", + "contact-file", + RelayOnly, + Default{llarp::our_rc_filename}, + AssignmentAcceptor(m_routerContactFile), + Comment{ + "Filename in which to store the router contact file", + relative_to_datadir, + }); conf.defineOption( - "router", "ident-privkey", false, "", AssignmentAcceptor(m_identityKeyFile)); + "router", + "encryption-privkey", + RelayOnly, + Default{llarp::our_enc_key_filename}, + AssignmentAcceptor(m_encryptionKeyFile), + Comment{ + "Filename in which to store the encryption private key", + relative_to_datadir, + }); conf.defineOption( - "router", "transport-privkey", false, "", AssignmentAcceptor(m_transportKeyFile)); + "router", + "ident-privkey", + RelayOnly, + Default{llarp::our_identity_filename}, + AssignmentAcceptor(m_identityKeyFile), + Comment{ + "Filename in which to store the identity private key", + relative_to_datadir, + }); - conf.defineOption( + conf.defineOption( "router", - "enable-peer-stats", - false, - params.isRelay, - AssignmentAcceptor(m_enablePeerStats)); + "transport-privkey", + RelayOnly, + Default{llarp::our_transport_key_filename}, + AssignmentAcceptor(m_transportKeyFile), + Comment{ + "Filename in which to store the transport private key.", + relative_to_datadir, + }); + + // Deprecated options: + + // these weren't even ever used! + conf.defineOption("router", "max-routers", Deprecated); + conf.defineOption("router", "min-routers", Deprecated); + + // TODO: this may have been a synonym for [router]worker-threads + conf.defineOption("router", "threads", Deprecated); + conf.defineOption("router", "net-threads", Deprecated); + m_isRelay = params.isRelay; } @@ -161,53 +253,91 @@ namespace llarp { (void)params; - constexpr bool DefaultProfilingValue = true; - static constexpr bool ReachableDefault = true; - static constexpr int HopsDefault = 4; - static constexpr int PathsDefault = 6; + static constexpr Default ProfilingValueDefault{false}; + static constexpr Default ReachableDefault{true}; + static constexpr Default HopsDefault{4}; + static constexpr Default PathsDefault{6}; conf.defineOption( - "network", "type", false, "tun", AssignmentAcceptor(m_endpointType)); - - conf.defineOption("network", "exit", false, false, AssignmentAcceptor(m_AllowExit)); + "network", "type", Default{"tun"}, Hidden, AssignmentAcceptor(m_endpointType)); conf.defineOption( "network", "profiling", - false, - DefaultProfilingValue, + ProfilingValueDefault, + Hidden, AssignmentAcceptor(m_enableProfiling)); - // TODO: this should be implied from [router]:data-dir conf.defineOption( "network", - "profiles", - false, - m_routerProfilesFile, - AssignmentAcceptor(m_routerProfilesFile)); + "strict-connect", + ClientOnly, + AssignmentAcceptor(m_strictConnect), + Comment{ + "Public key of a router which will act as sole first-hop. This may be used to", + "provide a trusted router (consider that you are not fully anonymous with your", + "first hop).", + }); conf.defineOption( - "network", "strict-connect", false, "", AssignmentAcceptor(m_strictConnect)); - - conf.defineOption("network", "keyfile", false, "", AssignmentAcceptor(m_keyfile)); + "network", + "keyfile", + ClientOnly, + AssignmentAcceptor(m_keyfile), + Comment{ + "The private key to persist address with. If not specified the address will be", + "ephemeral.", + }); - conf.defineOption("network", "auth", false, "", [this](std::string arg) { - if (arg.empty()) - return; - m_AuthType = service::ParseAuthType(arg); - }); + conf.defineOption( + "network", + "auth", + ClientOnly, + Comment{ + "Set the endpoint authentication mechanism.", + "none/whitelist/lmq", + }, + [this](std::string arg) { + if (arg.empty()) + return; + m_AuthType = service::ParseAuthType(arg); + }); - conf.defineOption("network", "auth-lmq", false, "", AssignmentAcceptor(m_AuthUrl)); + conf.defineOption( + "network", + "auth-lmq", + ClientOnly, + AssignmentAcceptor(m_AuthUrl), + Comment{ + "lmq endpoint to talk to for authenticating new sessions", + "ipc:///var/lib/lokinet/auth.socket", + "tcp://127.0.0.1:5555", + }); conf.defineOption( - "network", "auth-lmq-method", false, "llarp.auth", [this](std::string arg) { + "network", + "auth-lmq-method", + ClientOnly, + Default{"llarp.auth"}, + Comment{ + "lmq function to call for authenticating new sessions", + "llarp.auth", + }, + [this](std::string arg) { if (arg.empty()) return; m_AuthMethod = std::move(arg); }); conf.defineOption( - "network", "auth-whitelist", false, true, "", [this](std::string arg) { + "network", + "auth-whitelist", + ClientOnly, + MultiValue, + Comment{ + "manually add a remote endpoint by .loki address to the access whitelist", + }, + [this](std::string arg) { service::Address addr; if (not addr.FromString(arg)) throw std::invalid_argument(stringify("bad loki address: ", arg)); @@ -215,125 +345,230 @@ namespace llarp }); conf.defineOption( - "network", "reachable", false, ReachableDefault, AssignmentAcceptor(m_reachable)); + "network", + "reachable", + ClientOnly, + ReachableDefault, + AssignmentAcceptor(m_reachable), + Comment{ + "Determines whether we will publish our snapp's introset to the DHT.", + }); - conf.defineOption("network", "hops", false, HopsDefault, [this](int arg) { - if (arg < 1 or arg > 8) - throw std::invalid_argument("[endpoint]:hops must be >= 1 and <= 8"); - m_Hops = arg; - }); + conf.defineOption( + "network", + "hops", + HopsDefault, + Comment{ + "Number of hops in a path. Min 1, max 8.", + }, + [this](int arg) { + if (arg < 1 or arg > 8) + throw std::invalid_argument("[endpoint]:hops must be >= 1 and <= 8"); + m_Hops = arg; + }); - conf.defineOption("network", "paths", false, PathsDefault, [this](int arg) { - if (arg < 2 or arg > 8) - throw std::invalid_argument("[endpoint]:paths must be >= 2 and <= 8"); - m_Paths = arg; - }); + conf.defineOption( + "network", + "paths", + ClientOnly, + PathsDefault, + Comment{ + "Number of paths to maintain at any given time.", + }, + [this](int arg) { + if (arg < 2 or arg > 8) + throw std::invalid_argument("[endpoint]:paths must be >= 2 and <= 8"); + m_Paths = arg; + }); - conf.defineOption("network", "exit-auth", false, "", [this](std::string arg) { - if (arg.empty()) - return; - service::Address exit; - service::AuthInfo auth; - const auto pos = arg.find(":"); - if (pos == std::string::npos) - { - throw std::invalid_argument( - "[network]:exit-auth invalid format, expects exit-address.loki:auth-code-goes-here"); - } - const auto exit_str = arg.substr(0, pos); - auth.token = arg.substr(pos + 1); - if (not exit.FromString(exit_str)) - { - throw std::invalid_argument("[network]:exit-auth invalid exit address"); - } - m_ExitAuths.emplace(exit, auth); - }); + conf.defineOption( + "network", + "exit", + ClientOnly, + Default{false}, + AssignmentAcceptor(m_AllowExit), + Comment{ + "Whether or not we should act as an exit node. Beware that this increases demand", + "on the server and may pose liability concerns. Enable at your own risk.", + }); - conf.defineOption("network", "exit-node", false, "", [this](std::string arg) { - if (arg.empty()) - return; - service::Address exit; - IPRange range; - const auto pos = arg.find(":"); - if (pos == std::string::npos) - { - range.FromString("0.0.0.0/0"); - } - else if (not range.FromString(arg.substr(pos + 1))) - { - throw std::invalid_argument("[network]:exit-node invalid ip range for exit provided"); - } - if (pos != std::string::npos) - { - arg = arg.substr(0, pos); - } - if (not exit.FromString(arg)) - { - throw std::invalid_argument(stringify("[network]:exit-node bad address: ", arg)); - } - m_ExitMap.Insert(range, exit); - }); + // TODO: not implemented yet! + // TODO: define the order of precedence (e.g. is whitelist applied before blacklist?) + // additionally, what's default? What if I don't whitelist anything? + /* + conf.defineOption("network", "exit-whitelist", MultiValue, Comment{ + "List of destination protocol:port pairs to whitelist, example: udp:*", + "or tcp:80. Multiple values supported.", + }, FIXME-acceptor); - conf.defineOption("network", "mapaddr", false, true, "", [this](std::string arg) { - if (arg.empty()) - return; - huint128_t ip; - service::Address addr; - const auto pos = arg.find(":"); - if (pos == std::string::npos) - { - throw std::invalid_argument(stringify("[endpoint]:mapaddr invalid entry: ", arg)); - } - std::string addrstr = arg.substr(0, pos); - std::string ipstr = arg.substr(pos + 1); - if (not ip.FromString(ipstr)) - { - huint32_t ipv4; - if (not ipv4.FromString(ipstr)) - { - throw std::invalid_argument(stringify("[endpoint]:mapaddr invalid ip: ", ipstr)); - } - ip = net::ExpandV4(ipv4); - } - if (not addr.FromString(addrstr)) - { - throw std::invalid_argument(stringify("[endpoint]:mapaddr invalid addresss: ", addrstr)); - } - if (m_mapAddrs.find(ip) != m_mapAddrs.end()) - { - throw std::invalid_argument(stringify("[endpoint]:mapaddr ip already mapped: ", ipstr)); - } - m_mapAddrs[ip] = addr; - }); + conf.defineOption("network", "exit-blacklist", MultiValue, Comment{ + "Blacklist of destinations (same format as whitelist).", + }, FIXME-acceptor); + */ - conf.defineOption("network", "ifaddr", false, "", [this](std::string arg) { - if (arg.empty()) - { - const auto maybe = llarp::FindFreeRange(); - if (not maybe) - throw std::invalid_argument("cannot determine free ip range"); - m_ifaddr = *maybe; - return; - } - if (not m_ifaddr.FromString(arg)) - { - throw std::invalid_argument(stringify("[network]:ifaddr invalid value: ", arg)); - } - }); + conf.defineOption( + "network", + "exit-node", + ClientOnly, + Comment{ + "Specify a `.loki` address and an optional ip range to use as an exit broker.", + "Example:", + "exit-node=whatever.loki # maps all exit traffic to whatever.loki", + "exit-node=stuff.loki:100.0.0.0/24 # maps 100.0.0.0/24 to stuff.loki", + }, + [this](std::string arg) { + if (arg.empty()) + return; + service::Address exit; + IPRange range; + const auto pos = arg.find(":"); + if (pos == std::string::npos) + { + range.FromString("0.0.0.0/0"); + } + else if (not range.FromString(arg.substr(pos + 1))) + { + throw std::invalid_argument("[network]:exit-node invalid ip range for exit provided"); + } + if (pos != std::string::npos) + { + arg = arg.substr(0, pos); + } + if (not exit.FromString(arg)) + { + throw std::invalid_argument(stringify("[network]:exit-node bad address: ", arg)); + } + m_ExitMap.Insert(range, exit); + }); - conf.defineOption("network", "ifname", false, "", [this](std::string arg) { - if (arg.empty()) - { - const auto maybe = llarp::FindFreeTun(); - if (not maybe) - throw std::invalid_argument("cannot determine free interface name"); - arg = *maybe; - } - m_ifname = arg; - }); + conf.defineOption( + "network", + "exit-auth", + ClientOnly, + Comment{ + "Specify an optional authentication code required to use a non-public exit node.", + "For example:", + " exit-auth=myfavouriteexit.loki:abc", + "uses the authentication code `abc` whenever myfavouriteexit.loki is accessed.", + "Can be specified multiple time to store codes for different exit nodes.", + }, + [this](std::string arg) { + if (arg.empty()) + return; + service::Address exit; + service::AuthInfo auth; + const auto pos = arg.find(":"); + if (pos == std::string::npos) + { + throw std::invalid_argument( + "[network]:exit-auth invalid format, expects " + "exit-address.loki:auth-code-goes-here"); + } + const auto exit_str = arg.substr(0, pos); + auth.token = arg.substr(pos + 1); + if (not exit.FromString(exit_str)) + { + throw std::invalid_argument("[network]:exit-auth invalid exit address"); + } + m_ExitAuths.emplace(exit, auth); + }); + + conf.defineOption( + "network", + "ifname", + Comment{ + "Interface name for lokinet traffic. If unset lokinet will look for a free name", + "lokinetN, starting at 0 (e.g. lokinet0, lokinet1, ...).", + }, + [this](std::string arg) { + if (arg.empty()) + { + const auto maybe = llarp::FindFreeTun(); + if (not maybe) + throw std::invalid_argument("cannot determine free interface name"); + arg = *maybe; + } + m_ifname = arg; + }); conf.defineOption( - "network", "blacklist-snode", false, true, "", [this](std::string arg) { + "network", + "ifaddr", + Comment{ + "Local IP and range for lokinet traffic. For example, 172.16.0.1/16 to use", + "172.16.0.1 for this machine and 172.16.x.y for remote peers. If omitted then", + "lokinet will attempt to find an unused private range.", + }, + [this](std::string arg) { + if (arg.empty()) + { + const auto maybe = llarp::FindFreeRange(); + if (not maybe) + throw std::invalid_argument("cannot determine free ip range"); + m_ifaddr = *maybe; + return; + } + if (not m_ifaddr.FromString(arg)) + { + throw std::invalid_argument(stringify("[network]:ifaddr invalid value: ", arg)); + } + }); + + // TODO: could be useful for snodes in the future, but currently only implemented for clients: + conf.defineOption( + "network", + "mapaddr", + ClientOnly, + MultiValue, + Comment{ + "Map a remote `.loki` address to always use a fixed local IP. For example:", + " mapaddr=whatever.loki:172.16.0.10", + "maps `whatever.loki` to `172.16.0.10` instead of using the next available IP.", + "The given IP address must be inside the range configured by ifaddr=", + }, + [this](std::string arg) { + if (arg.empty()) + return; + huint128_t ip; + service::Address addr; + const auto pos = arg.find(":"); + if (pos == std::string::npos) + { + throw std::invalid_argument(stringify("[endpoint]:mapaddr invalid entry: ", arg)); + } + std::string addrstr = arg.substr(0, pos); + std::string ipstr = arg.substr(pos + 1); + if (not ip.FromString(ipstr)) + { + huint32_t ipv4; + if (not ipv4.FromString(ipstr)) + { + throw std::invalid_argument(stringify("[endpoint]:mapaddr invalid ip: ", ipstr)); + } + ip = net::ExpandV4(ipv4); + } + if (not addr.FromString(addrstr)) + { + throw std::invalid_argument( + stringify("[endpoint]:mapaddr invalid addresss: ", addrstr)); + } + if (m_mapAddrs.find(ip) != m_mapAddrs.end()) + { + throw std::invalid_argument(stringify("[endpoint]:mapaddr ip already mapped: ", ipstr)); + } + m_mapAddrs[ip] = addr; + }); + + conf.defineOption( + "network", + "blacklist-snode", + ClientOnly, + MultiValue, + Comment{ + "Adds a lokinet relay `.snode` address to the list of relays to avoid when", + "building paths. Can be specified multiple times.", + }, + [this](std::string arg) { RouterID id; if (not id.FromString(arg)) throw std::invalid_argument(stringify("Invalid RouterID: ", arg)); @@ -343,13 +578,27 @@ namespace llarp throw std::invalid_argument(stringify("Duplicate blacklist-snode: ", arg)); }); - conf.defineOption("network", "srv", false, true, "", [this](std::string arg) { - llarp::dns::SRVData newSRV; - if (not newSRV.fromString(arg)) - throw std::invalid_argument(stringify("Invalid SRV Record string: ", arg)); + // TODO: support SRV records for routers, but for now client only + conf.defineOption( + "network", + "srv", + ClientOnly, + MultiValue, + Comment{ + "Specify SRV Records for services hosted on the SNApp", + "for more info see https://docs.loki.network/Lokinet/Guides/HostingSNApps/", + "srv=_service._protocol priority weight port target.loki", + }, + [this](std::string arg) { + llarp::dns::SRVData newSRV; + if (not newSRV.fromString(arg)) + throw std::invalid_argument(stringify("Invalid SRV Record string: ", arg)); + + m_SRVRecords.push_back(std::move(newSRV)); + }); - m_SRVRecords.push_back(std::move(newSRV)); - }); + // Deprecated options: + conf.defineOption("network", "enabled", Deprecated); } void @@ -357,16 +606,28 @@ namespace llarp { (void)params; + // Most non-linux platforms have loopback as 127.0.0.1/32, but linux uses 127.0.0.1/8 so that we + // can bind to other 127.* IPs to avoid conflicting with something else that may be listening on + // 127.0.0.1:53. +#ifdef __linux__ + constexpr Default DefaultDNSBind{"127.3.2.1:53"}; +#else + constexpr Default DefaultDNSBind{"127.0.0.1:53"}; +#endif + // Default, but if we get any upstream (including upstream=, i.e. empty string) we clear it - constexpr auto DefaultUpstreamDNS = "1.1.1.1"; - m_upstreamDNS.emplace_back(DefaultUpstreamDNS); + constexpr Default DefaultUpstreamDNS{"1.1.1.1"}; + m_upstreamDNS.emplace_back(DefaultUpstreamDNS.val); conf.defineOption( "dns", "upstream", - false, - true, DefaultUpstreamDNS, + MultiValue, + Comment{ + "Upstream resolver(s) to use as fallback for non-loki addresses.", + "Multiple values accepted.", + }, [=, first = true](std::string arg) mutable { if (first) { @@ -384,14 +645,29 @@ namespace llarp } }); - conf.defineOption("dns", "bind", false, "127.3.2.1:53", [=](std::string arg) { - m_bind = IpAddress{std::move(arg)}; - if (!m_bind.getPort()) - m_bind.setPort(53); - }); + conf.defineOption( + "dns", + "bind", + DefaultDNSBind, + Comment{ + "Address to bind to for handling DNS requests.", + }, + [=](std::string arg) { + m_bind = IpAddress{std::move(arg)}; + if (!m_bind.getPort()) + m_bind.setPort(53); + }); // Ignored option (used by the systemd service file to disable resolvconf configuration). - conf.defineOption("dns", "no-resolvconf", false, false); + conf.defineOption( + "dns", + "no-resolvconf", + ClientOnly, + Comment{ + "Can be uncommented and set to 1 to disable resolvconf configuration of lokinet DNS.", + "(This is not used directly by lokinet itself, but by the lokinet init scripts", + "on systems which use resolveconf)", + }); } LinksConfig::LinkInfo @@ -437,13 +713,46 @@ namespace llarp { (void)params; - constexpr auto DefaultOutboundLinkValue = "0"; + constexpr Default DefaultOutboundLinkValue{"0"}; - conf.defineOption( - "bind", "*", false, false, DefaultOutboundLinkValue, [this](std::string arg) { - m_OutboundLink = LinkInfoFromINIValues("*", arg); + conf.addSectionComments( + "bind", + { + "This section specifies network interface names and/or IPs as keys, and", + "ports as values to control the address(es) on which Lokinet listens for", + "incoming data.", + "", + "Examples:", + "", + " eth0=1090", + " 0.0.0.0=1090", + " 1.2.3.4=1090", + "", + "The first bind to port 1090 on the network interface 'eth0'; the second binds", + "to port 1090 on all local network interfaces; and the third example binds to", + "port 1090 on the given IP address.", + "", + "If a private range IP address (or an interface with a private IP) is given, or", + "if the 0.0.0.0 all-address IP is given then you must also specify the", + "public-ip= and public-port= settings in the [router] section with a public", + "address at which this router can be reached.", + "" + "Typically this section can be left blank: if no inbound bind addresses are", + "configured then lokinet will search for a local network interface with a public", + "IP address and use that (with port 1090).", }); + conf.defineOption( + "bind", + "*", + DefaultOutboundLinkValue, + Comment{ + "Specify a source port for **outgoing** Lokinet traffic, for example if you want to", + "set up custom firewall rules based on the originating port. Typically this should", + "be left unset to automatically choose random source ports.", + }, + [this](std::string arg) { m_OutboundLink = LinkInfoFromINIValues("*", arg); }); + if (std::string best_if; GetBestNetIF(best_if)) m_InboundLinks.push_back(LinkInfoFromINIValues(best_if, std::to_string(DefaultPublicPort))); @@ -495,24 +804,39 @@ namespace llarp void ApiConfig::defineConfigOptions(ConfigDefinition& conf, const ConfigGenParameters& params) { - constexpr auto DefaultRPCBindAddr = "tcp://127.0.0.1:1190"; + constexpr Default DefaultRPCBindAddr{"tcp://127.0.0.1:1190"}; conf.defineOption( - "api", "enabled", false, not params.isRelay, AssignmentAcceptor(m_enableRPCServer)); + "api", + "enabled", + Default{not params.isRelay}, + AssignmentAcceptor(m_enableRPCServer), + Comment{ + "Determines whether or not the LMQ JSON API is enabled. Defaults ", + }); conf.defineOption( - "api", "bind", false, DefaultRPCBindAddr, [this](std::string arg) { + "api", + "bind", + DefaultRPCBindAddr, + [this](std::string arg) { if (arg.empty()) { - arg = DefaultRPCBindAddr; + arg = DefaultRPCBindAddr.val; } if (arg.find("://") == std::string::npos) { arg = "tcp://" + arg; } m_rpcBindAddr = std::move(arg); + }, + Comment{ + "IP address and port to bind to.", + "Recommend localhost-only for security purposes.", }); + conf.defineOption("api", "authkey", Deprecated); + // TODO: this was from pre-refactor: // TODO: add pubkey to whitelist } @@ -522,33 +846,44 @@ namespace llarp { (void)params; - constexpr auto DefaultLokidRPCAddr = "tcp://127.0.0.1:22023"; - - conf.defineOption( - "lokid", "service-node-seed", false, our_identity_filename, [this](std::string arg) { - if (not arg.empty()) - { - usingSNSeed = true; - ident_keyfile = std::move(arg); - } - }); - conf.defineOption( - "lokid", "enabled", false, params.isRelay, AssignmentAcceptor(whitelistRouters)); + "lokid", + "enabled", + RelayOnly, + Default{true}, + Comment{ + "Whether or not we should talk to lokid. Must be enabled for staked routers.", + }, + AssignmentAcceptor(whitelistRouters)); - conf.defineOption("lokid", "jsonrpc", false, "", [](std::string arg) { + conf.defineOption("lokid", "jsonrpc", RelayOnly, [](std::string arg) { if (arg.empty()) return; throw std::invalid_argument( - "the [lokid]::jsonrpc option is deprecated please use the " - "[lokid]::rpc " - "config option instead"); + "the [lokid]:jsonrpc option is no longer supported; please use the [lokid]:rpc config " + "option instead with lokid's lmq-local-control address -- typically a value such as " + "rpc=ipc:///var/lib/loki/lokid.sock or rpc=ipc:///home/snode/.loki/lokid.sock"); }); conf.defineOption( - "lokid", "rpc", false, DefaultLokidRPCAddr, [this](std::string arg) { - lokidRPCAddr = lokimq::address(arg); - }); + "lokid", + "rpc", + RelayOnly, + Comment{ + "lokimq control address for for communicating with lokid. Depends on lokid's", + "lmq-local-control configuration option. By default this value should be", + "ipc://LOKID-DATA-DIRECTORY/lokid.sock, such as:", + " rpc=ipc:///var/lib/loki/lokid.sock", + " rpc=ipc:///home/USER/.loki/lokid.sock", + "but can use (non-default) TCP if lokid is configured that way:", + " rpc=tcp://127.0.0.1:5678", + }, + [this](std::string arg) { lokidRPCAddr = lokimq::address(arg); }); + + // Deprecated options: + conf.defineOption("lokid", "username", Deprecated); + conf.defineOption("lokid", "password", Deprecated); + conf.defineOption("lokid", "service-node-seed", Deprecated); } void @@ -557,7 +892,14 @@ namespace llarp (void)params; conf.defineOption( - "bootstrap", "add-node", false, true, "", [this](std::string arg) { + "bootstrap", + "add-node", + MultiValue, + Comment{ + "Specify a bootstrap file containing a signed RouterContact of a service node", + "which can act as a bootstrap. Can be specified multiple times.", + }, + [this](std::string arg) { if (arg.empty()) { throw std::invalid_argument("cannot use empty filename as bootstrap"); @@ -575,30 +917,59 @@ namespace llarp { (void)params; - constexpr auto DefaultLogType = "file"; - constexpr auto DefaultLogFile = "stdout"; - constexpr auto DefaultLogLevel = "info"; + constexpr Default DefaultLogType{"file"}; + constexpr Default DefaultLogFile{""}; + constexpr Default DefaultLogLevel{"info"}; conf.defineOption( - "logging", "type", false, DefaultLogType, [this](std::string arg) { + "logging", + "type", + DefaultLogType, + [this](std::string arg) { LogType type = LogTypeFromString(arg); if (type == LogType::Unknown) throw std::invalid_argument(stringify("invalid log type: ", arg)); m_logType = type; + }, + Comment{ + "Log type (format). Valid options are:", + " file - plaintext formatting", + " json - json-formatted log statements", + " syslog - logs directed to syslog", }); conf.defineOption( - "logging", "level", false, DefaultLogLevel, [this](std::string arg) { + "logging", + "level", + DefaultLogLevel, + [this](std::string arg) { std::optional level = LogLevelFromString(arg); if (not level) throw std::invalid_argument(stringify("invalid log level value: ", arg)); m_logLevel = *level; + }, + Comment{ + "Minimum log level to print. Logging below this level will be ignored.", + "Valid log levels, in ascending order, are:", + " trace", + " debug", + " info", + " warn", + " error", }); conf.defineOption( - "logging", "file", false, DefaultLogFile, AssignmentAcceptor(m_logFile)); + "logging", + "file", + DefaultLogFile, + AssignmentAcceptor(m_logFile), + Comment{ + "When using type=file this is the output filename. If given the value 'stdout' or", + "left empty then logging is printed as standard output rather than written to a", + "file.", + }); } void @@ -622,7 +993,7 @@ namespace llarp params.isRelay = isRelay; params.defaultDataDir = std::move(defaultDataDir); - ConfigDefinition conf; + ConfigDefinition conf{isRelay}; initializeConfig(conf, params); addBackwardsCompatibleConfigOptions(conf); m_Parser.Clear(); @@ -662,7 +1033,7 @@ namespace llarp params.isRelay = isRelay; params.defaultDataDir = std::move(dataDir); - ConfigDefinition conf; + ConfigDefinition conf{isRelay}; initializeConfig(conf, params); conf.acceptAllOptions(); @@ -693,34 +1064,15 @@ namespace llarp void Config::addBackwardsCompatibleConfigOptions(ConfigDefinition& conf) { - auto addIgnoreOption = [&](const std::string& section, const std::string& name) { - conf.defineOption(section, name, false, true, "", [=](std::string arg) { - (void)arg; - LogWarn("*** WARNING: The config option [", section, "]:", name, " is deprecated"); - }); - }; - - addIgnoreOption("system", "user"); - addIgnoreOption("system", "group"); - addIgnoreOption("system", "pidfile"); - - addIgnoreOption("api", "authkey"); - - addIgnoreOption("netdb", "dir"); - - // these weren't even ever used! - addIgnoreOption("router", "max-routers"); - addIgnoreOption("router", "min-routers"); - - // TODO: this may have been a synonym for [router]worker-threads - addIgnoreOption("router", "threads"); + // These config sections don't exist anymore: - addIgnoreOption("metrics", "json-metrics-path"); + conf.defineOption("system", "user", Deprecated); + conf.defineOption("system", "group", Deprecated); + conf.defineOption("system", "pidfile", Deprecated); - addIgnoreOption("network", "enabled"); + conf.defineOption("netdb", "dir", Deprecated); - addIgnoreOption("lokid", "username"); - addIgnoreOption("lokid", "password"); + conf.defineOption("metrics", "json-metrics-path", Deprecated); } void @@ -778,57 +1130,6 @@ namespace llarp "Configuration for routing activity.", }); - def.addOptionComments( - "router", - "threads", - { - "The number of threads available for performing cryptographic functions.", - "The minimum is one thread, but network performance may increase with more.", - "threads. Should not exceed the number of logical CPU cores.", - }); - - def.addOptionComments( - "router", - "data-dir", - { - "Optional directory for containing lokinet runtime data. This includes generated", - "private keys.", - }); - - // TODO: why did Kee want this, and/or what does it really do? Something about logs? - def.addOptionComments("router", "nickname", {"Router nickname. Kee wanted it."}); - - def.addOptionComments( - "router", - "min-connections", - { - "Minimum number of routers lokinet will attempt to maintain connections to.", - }); - - def.addOptionComments( - "router", - "max-connections", - { - "Maximum number (hard limit) of routers lokinet will be connected to at any time.", - }); - - def.addOptionComments( - "router", - "public-ip", - { - "For complex network configurations where the detected IP is incorrect or non-public", - "this setting specifies the public IP at which this router is reachable. When", - "provided the public-port option must also be specified.", - }); - - def.addOptionComments( - "router", - "public-port", - { - "When specifying public-ip=, this specifies the public UDP port at which this lokinet", - "router is reachable. Required when public-ip is used.", - }); - // logging def.addSectionComments( "logging", @@ -836,29 +1137,6 @@ namespace llarp "logging settings", }); - def.addOptionComments( - "logging", - "level", - { - "Minimum log level to print. Logging below this level will be ignored.", - "Valid log levels, in ascending order, are:", - " trace", - " debug", - " info", - " warn", - " error", - }); - - def.addOptionComments( - "logging", - "type", - { - "Log type (format). Valid options are:", - " file - plaintext formatting", - " json - json-formatted log statements", - " syslog - logs directed to syslog", - }); - // api def.addSectionComments( "api", @@ -866,21 +1144,6 @@ namespace llarp "JSON API settings", }); - def.addOptionComments( - "api", - "enabled", - { - "Determines whether or not the JSON API is enabled.", - }); - - def.addOptionComments( - "api", - "bind", - { - "IP address and port to bind to.", - "Recommend localhost-only for security purposes.", - }); - // dns def.addSectionComments( "dns", @@ -888,31 +1151,6 @@ namespace llarp "DNS configuration", }); - def.addOptionComments( - "dns", - "upstream", - { - "Upstream resolver(s) to use as fallback for non-loki addresses.", - "Multiple values accepted.", - }); - - def.addOptionComments( - "dns", - "bind", - { - "Address to bind to for handling DNS requests.", - "Multiple values accepted.", - }); - - def.addOptionComments( - "dns", - "no-resolvconf", - { - "Can be uncommented and set to 1 to disable resolvconf configuration of lokinet DNS.", - "(This is not used directly by lokinet itself, but by the lokinet init scripts", - "on systems which use resolveconf)", - }); - // bootstrap def.addSectionComments( "bootstrap", @@ -920,57 +1158,12 @@ namespace llarp "Configure nodes that will bootstrap us onto the network", }); - def.addOptionComments( - "bootstrap", - "add-node", - { - "Specify a bootstrap file containing a signed RouterContact of a service node", - "which can act as a bootstrap. Accepts multiple values.", - }); - // network def.addSectionComments( "network", { "Network settings", }); - - def.addOptionComments( - "network", - "profiles", - { - "File to contain router profiles.", - }); - - def.addOptionComments( - "network", - "strict-connect", - { - "Public key of a router which will act as sole first-hop. This may be used to", - "provide a trusted router (consider that you are not fully anonymous with your", - "first hop).", - }); - - def.addOptionComments( - "network", - "exit-node", - { - "Public key of an exit-node.", - }); - - def.addOptionComments( - "network", - "ifname", - { - "Interface name for lokinet traffic.", - }); - - def.addOptionComments( - "network", - "ifaddr", - { - "Local IP address for lokinet traffic.", - }); } std::string @@ -980,7 +1173,7 @@ namespace llarp params.isRelay = false; params.defaultDataDir = std::move(defaultDataDir); - llarp::ConfigDefinition def; + llarp::ConfigDefinition def{false}; initializeConfig(def, params); generateCommonConfigComments(def); @@ -990,102 +1183,6 @@ namespace llarp "Snapp settings", }); - def.addOptionComments( - "network", - "keyfile", - { - "The private key to persist address with. If not specified the address will be", - "ephemeral.", - }); - - // TODO: is this redundant with / should be merged with basic client config? - def.addOptionComments( - "network", - "reachable", - { - "Determines whether we will publish our snapp's introset to the DHT.", - }); - - def.addOptionComments( - "network", - "hops", - { - "Number of hops in a path. Min 1, max 8.", - }); - - def.addOptionComments( - "network", - "paths", - { - "Number of paths to maintain at any given time.", - }); - - def.addOptionComments( - "network", - "blacklist-snode", - { - "Adds a `.snode` address to the blacklist.", - }); - - def.addOptionComments( - "network", - "exit-node", - { - "Specify a `.loki` address and an optional ip range to use as an exit broker.", - "Example:", - "exit-node=whatever.loki # maps all exit traffic to whatever.loki", - "exit-node=stuff.loki:100.0.0.0/24 # maps 100.0.0.0/24 to stuff.loki", - }); - - def.addOptionComments( - "network", - "mapaddr", - { - "Permanently map a `.loki` address to an IP owned by the snapp. Example:", - "mapaddr=whatever.loki:10.0.10.10 # maps `whatever.loki` to `10.0.10.10`.", - }); - // extra [network] options - // TODO: probably better to create an [exit] section and only allow it for routers - def.addOptionComments( - "network", - "exit", - { - "Whether or not we should act as an exit node. Beware that this increases demand", - "on the server and may pose liability concerns. Enable at your own risk.", - }); - - def.addOptionComments( - "network", - "auth", - { - "Set the endpoint authentication mechanism.", - "none/whitelist/lmq", - }); - - def.addOptionComments( - "network", - "auth-lmq", - { - "lmq endpoint to talk to for authenticating new sessions", - "ipc:///var/lib/lokinet/auth.socket", - "tcp://127.0.0.1:5555", - }); - - def.addOptionComments( - "network", - "auth-lmq-method", - { - "lmq function to call for authenticating new sessions", - "llarp.auth", - }); - - def.addOptionComments( - "network", - "auth-whitelist", - { - "manually add a remote endpoint by .loki address to the access whitelist", - }); - return def.generateINIConfig(true); } @@ -1096,7 +1193,7 @@ namespace llarp params.isRelay = true; params.defaultDataDir = std::move(defaultDataDir); - llarp::ConfigDefinition def; + llarp::ConfigDefinition def{true}; initializeConfig(def, params); generateCommonConfigComments(def); @@ -1104,63 +1201,7 @@ namespace llarp def.addSectionComments( "lokid", { - "Lokid configuration (settings for talking to lokid", - }); - - def.addOptionComments( - "lokid", - "enabled", - { - "Whether or not we should talk to lokid. Must be enabled for staked routers.", - }); - - def.addOptionComments( - "lokid", - "rpc", - { - "Host and port of running lokid's rpc that we should talk to.", - }); - - // TODO: doesn't appear to be used in the codebase - def.addOptionComments( - "lokid", - "service-node-seed", - { - "File containing service node's seed.", - }); - - // extra [network] options - // TODO: probably better to create an [exit] section and only allow it for routers - def.addOptionComments( - "network", - "exit", - { - "Whether or not we should act as an exit node. Beware that this increases demand", - "on the server and may pose liability concerns. Enable at your own risk.", - }); - - // TODO: define the order of precedence (e.g. is whitelist applied before blacklist?) - // additionally, what's default? What if I don't whitelist anything? - def.addOptionComments( - "network", - "exit-whitelist", - { - "List of destination protocol:port pairs to whitelist, example: udp:*", - "or tcp:80. Multiple values supported.", - }); - - def.addOptionComments( - "network", - "exit-blacklist", - { - "Blacklist of destinations (same format as whitelist).", - }); - - def.addOptionComments( - "router", - "enable-peer-stats", - { - "Enable collection of SNode peer stats", + "Settings for communicating with lokid", }); return def.generateINIConfig(true); diff --git a/llarp/config/config.hpp b/llarp/config/config.hpp index a960bea8b..e2387ee63 100644 --- a/llarp/config/config.hpp +++ b/llarp/config/config.hpp @@ -63,7 +63,6 @@ namespace llarp std::string m_identityKeyFile; std::string m_transportKeyFile; - bool m_enablePeerStats = false; bool m_isRelay = false; void @@ -73,7 +72,6 @@ namespace llarp struct NetworkConfig { std::optional m_enableProfiling; - std::string m_routerProfilesFile; std::string m_strictConnect; std::string m_ifname; IPRange m_ifaddr; @@ -153,7 +151,6 @@ namespace llarp struct LokidConfig { - bool usingSNSeed = false; bool whitelistRouters = false; fs::path ident_keyfile; lokimq::address lokidRPCAddr; diff --git a/llarp/config/definition.cpp b/llarp/config/definition.cpp index 7ad93c901..6ad34b885 100644 --- a/llarp/config/definition.cpp +++ b/llarp/config/definition.cpp @@ -1,22 +1,13 @@ #include +#include +#include #include #include #include namespace llarp { - OptionDefinitionBase::OptionDefinitionBase( - std::string section_, std::string name_, bool required_) - : section(section_), name(name_), required(required_) - { - } - OptionDefinitionBase::OptionDefinitionBase( - std::string section_, std::string name_, bool required_, bool multiValued_) - : section(section_), name(name_), required(required_), multiValued(multiValued_) - { - } - template <> bool OptionDefinition::fromString(const std::string& input) @@ -32,17 +23,43 @@ namespace llarp ConfigDefinition& ConfigDefinition::defineOption(OptionDefinition_ptr def) { - auto sectionItr = m_definitions.find(def->section); - if (sectionItr == m_definitions.end()) + using namespace config; + // If explicitly deprecated or is a {client,relay} option in a {relay,client} config then add a + // dummy, warning option instead of this one. + if (def->deprecated || (relay ? def->clientOnly : def->relayOnly)) + { + return defineOption( + def->section, + def->name, + MultiValue, + Hidden, + [deprecated = def->deprecated, + relay = relay, + opt = "[" + def->section + "]:" + def->name](std::string_view) { + LogWarn( + "*** WARNING: The config option ", + opt, + (deprecated ? " is deprecated" + : relay ? " is not valid in service node configuration files" + : " is not valid in client configuration files"), + " and has been ignored."); + }); + } + + auto [sectionItr, newSect] = m_definitions.try_emplace(def->section); + if (newSect) m_sectionOrdering.push_back(def->section); + auto& section = sectionItr->first; - auto& sectionDefinitions = m_definitions[def->section]; - if (sectionDefinitions.find(def->name) != sectionDefinitions.end()) + auto [it, added] = m_definitions[section].try_emplace(std::string{def->name}, std::move(def)); + if (!added) throw std::invalid_argument( stringify("definition for [", def->section, "]:", def->name, " already exists")); - m_definitionOrdering[def->section].push_back(def->name); - sectionDefinitions[def->name] = std::move(def); + m_definitionOrdering[section].push_back(it->first); + + if (!it->second->comments.empty()) + addOptionComments(section, it->first, std::move(it->second->comments)); return *this; } @@ -153,10 +170,13 @@ namespace llarp const std::string& section, const std::string& name, std::vector comments) { auto& defComments = m_definitionComments[section][name]; - for (size_t i = 0; i < comments.size(); ++i) - { - defComments.emplace_back(std::move(comments[i])); - } + if (defComments.empty()) + defComments = std::move(comments); + else + defComments.insert( + defComments.end(), + std::make_move_iterator(comments.begin()), + std::make_move_iterator(comments.end())); } std::string @@ -167,41 +187,49 @@ namespace llarp int sectionsVisited = 0; visitSections([&](const std::string& section, const DefinitionMap&) { - if (sectionsVisited > 0) - oss << "\n\n"; - - // TODO: this will create empty objects as a side effect of map's operator[] - // TODO: this also won't handle sections which have no definition - for (const std::string& comment : m_sectionComments[section]) - { - oss << "# " << comment << "\n"; - } - - oss << "[" << section << "]\n"; + std::ostringstream sect_out; visitDefinitions(section, [&](const std::string& name, const OptionDefinition_ptr& def) { - oss << "\n"; - + bool has_comment = false; // TODO: as above, this will create empty objects // TODO: as above (but more important): this won't handle definitions with no entries // (i.e. those handled by UndeclaredValueHandler's) for (const std::string& comment : m_definitionComments[section][name]) { - oss << "# " << comment << "\n"; + sect_out << "\n# " << comment; + has_comment = true; } if (useValues and def->getNumberFound() > 0) { - oss << name << "=" << def->valueAsString(false) << "\n"; + sect_out << "\n" << name << "=" << def->valueAsString(false) << "\n"; } - else + else if (not(def->hidden and not has_comment)) { + sect_out << "\n"; if (not def->required) - oss << "#"; - oss << name << "=" << def->defaultValueAsString() << "\n"; + sect_out << "#"; + sect_out << name << "=" << def->defaultValueAsString() << "\n"; } }); + auto sect_str = sect_out.str(); + if (sect_str.empty()) + return; // Skip sections with no options + + if (sectionsVisited > 0) + oss << "\n\n"; + + oss << "[" << section << "]\n"; + + // TODO: this will create empty objects as a side effect of map's operator[] + // TODO: this also won't handle sections which have no definition + for (const std::string& comment : m_sectionComments[section]) + { + oss << "# " << comment << "\n"; + } + oss << "\n" << sect_str; + sectionsVisited++; }); diff --git a/llarp/config/definition.hpp b/llarp/config/definition.hpp index 749389507..1f13f987a 100644 --- a/llarp/config/definition.hpp +++ b/llarp/config/definition.hpp @@ -1,6 +1,9 @@ #pragma once +#include +#include #include +#include #include #include @@ -15,19 +18,111 @@ namespace llarp { + namespace config + { + // Base class for the following option flag types + struct option_flag + {}; + + struct Required_t : option_flag + {}; + struct Hidden_t : option_flag + {}; + struct MultiValue_t : option_flag + {}; + struct RelayOnly_t : option_flag + {}; + struct ClientOnly_t : option_flag + {}; + struct Deprecated_t : option_flag + {}; + + /// Value to pass for an OptionDefinition to indicate that the option is required + inline constexpr Required_t Required{}; + /// Value to pass for an OptionDefinition to indicate that the option should be hidden from the + /// generate config file if it is unset (and has no comment). Typically for deprecated, renamed + /// options that still do something, and for internal dev options that aren't usefully exposed. + /// (For do-nothing deprecated options use Deprecated instead). + inline constexpr Hidden_t Hidden{}; + /// Value to pass for an OptionDefinition to indicate that the option takes multiple values + inline constexpr MultiValue_t MultiValue{}; + /// Value to pass for an option that should only be set for relay configs. If found in a client + /// config it be ignored (but will produce a warning). + inline constexpr RelayOnly_t RelayOnly{}; + /// Value to pass for an option that should only be set for client configs. If found in a relay + /// config it will be ignored (but will produce a warning). + inline constexpr ClientOnly_t ClientOnly{}; + /// Value to pass for an option that is deprecated and does nothing and should be ignored (with + /// a deprecation warning) if specified. Note that Deprecated implies Hidden, and that + /// {client,relay}-only options in a {relay,client} config are also considered Deprecated. + inline constexpr Deprecated_t Deprecated{}; + + /// Wrapper to specify a default value to an OptionDefinition + template + struct Default + { + T val; + constexpr explicit Default(T val) : val{std::move(val)} + {} + }; + + /// Adds one or more comment lines to the option definition. + struct Comment + { + std::vector comments; + explicit Comment(std::initializer_list comments) : comments{std::move(comments)} + {} + }; + + /// A convenience function that returns an acceptor which assigns to a reference. + /// + /// Note that this holds on to the reference; it must only be used when this is safe to do. In + /// particular, a reference to a local variable may be problematic. + template + auto + AssignmentAcceptor(T& ref) + { + return [&ref](T arg) { ref = std::move(arg); }; + } + + // C++20 backport: + template + using remove_cvref_t = std::remove_cv_t>; + + template + constexpr bool is_default = false; + template + constexpr bool is_default> = true; + template + constexpr bool is_default = is_default>; + + template + constexpr bool is_option = + std::is_base_of_v< + option_flag, + remove_cvref_t< + Option>> or std::is_same_v or is_default