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