From af6caf776a4a7a8580d96d5e72cb9e037708e3c4 Mon Sep 17 00:00:00 2001 From: Jason Rhinelander Date: Wed, 7 Oct 2020 19:22:58 -0300 Subject: [PATCH] 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