diff --git a/.gitignore b/.gitignore index a1d1593df..0b20f7ba5 100644 --- a/.gitignore +++ b/.gitignore @@ -43,7 +43,7 @@ testnet_tmp vsproject/ .vs -daemon.ini +*.ini .gradle/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 8c3b407d8..cc7bb6a91 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,8 +41,6 @@ if(APPLE) set(LOKINET_APPLE_BUILD 5) endif() -set(LOKINET_RELEASE_MOTTO "Anonymous, decentralized, IP-based overlay network" CACHE STRING "Release motto") - list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") set(DEFAULT_WITH_BOOTSTRAP ON) diff --git a/contrib/bootstrap/testnet.signed b/contrib/bootstrap/testnet.signed index 366c5c5a0..e80acd6cd 100644 Binary files a/contrib/bootstrap/testnet.signed and b/contrib/bootstrap/testnet.signed differ diff --git a/daemon/CMakeLists.txt b/daemon/CMakeLists.txt index 05685f44a..477e32e42 100644 --- a/daemon/CMakeLists.txt +++ b/daemon/CMakeLists.txt @@ -83,7 +83,7 @@ endforeach() target_link_libraries(lokinet PRIVATE CLI11) target_link_libraries(lokinet-vpn PRIVATE CLI11) -if(SETCAP) +if(WITH_SETCAP) install(CODE "execute_process(COMMAND ${SETCAP} cap_net_admin,cap_net_bind_service=+eip ${CMAKE_INSTALL_PREFIX}/bin/lokinet)") endif() diff --git a/daemon/lokinet-bootstrap.cpp b/daemon/lokinet-bootstrap.cpp index afdecec72..22ecb9d6f 100644 --- a/daemon/lokinet-bootstrap.cpp +++ b/daemon/lokinet-bootstrap.cpp @@ -46,10 +46,8 @@ int main(int argc, char* argv[]) { const std::unordered_map bootstrap_urls = { - {"mainnet", "https://seed.lokinet.org/lokinet.signed"}, {"lokinet", "https://seed.lokinet.org/lokinet.signed"}, - {"testnet", "https://seed.lokinet.org/testnet.signed"}, - {"gamma", "https://seed.lokinet.org/testnet.signed"}}; + {"testnet", "https://seed.lokinet.org/testnet.signed"}}; std::string bootstrap_url = bootstrap_urls.at("lokinet"); fs::path outputfile{llarp::GetDefaultBootstrap()}; diff --git a/daemon/lokinet.cpp b/daemon/lokinet.cpp index 89b3d155a..1e4b406f4 100644 --- a/daemon/lokinet.cpp +++ b/daemon/lokinet.cpp @@ -125,7 +125,7 @@ namespace if (!GetModuleFileName(nullptr, szPath.data(), MAX_PATH)) { - llarp::LogError("Cannot install service ", GetLastError()); + llarp::log::error(logcat, "Cannot install service {}", GetLastError()); return; } @@ -137,7 +137,7 @@ namespace if (nullptr == schSCManager) { - llarp::LogError("OpenSCManager failed ", GetLastError()); + llarp::log::error(logcat, "OpenSCManager failed {}", GetLastError()); return; } @@ -159,12 +159,12 @@ namespace if (schService == nullptr) { - llarp::LogError("CreateService failed ", GetLastError()); + llarp::log::error(logcat, "CreateService failed {}", GetLastError()); CloseServiceHandle(schSCManager); return; } else - llarp::LogInfo("Service installed successfully"); + llarp::log::info(logcat, "Service installed successfully"); CloseServiceHandle(schService); CloseServiceHandle(schSCManager); @@ -189,7 +189,7 @@ namespace if (nullptr == schSCManager) { - llarp::LogError("OpenSCManager failed ", GetLastError()); + llarp::log::error(logcat, "OpenSCManager failed {}", GetLastError()); return; } @@ -201,7 +201,7 @@ namespace if (schService == nullptr) { - llarp::LogError("OpenService failed ", GetLastError()); + llarp::log::error(logcat, "OpenService failed {}", GetLastError()); CloseServiceHandle(schSCManager); return; } @@ -214,10 +214,10 @@ namespace SERVICE_CONFIG_DESCRIPTION, // change: description &sd)) // new description { - llarp::LogError("ChangeServiceConfig2 failed"); + llarp::log::error(logcat, "ChangeServiceConfig2 failed"); } else - llarp::LogInfo("Service description updated successfully."); + llarp::log::info(log_cat, "Service description updated successfully."); CloseServiceHandle(schService); CloseServiceHandle(schSCManager); @@ -237,7 +237,7 @@ namespace if (nullptr == schSCManager) { - llarp::LogError("OpenSCManager failed ", GetLastError()); + llarp::log::error(logcat, "OpenSCManager failed {}", GetLastError()); return; } @@ -249,7 +249,7 @@ namespace if (schService == nullptr) { - llarp::LogError("OpenService failed ", GetLastError()); + llarp::log::error(logcat, "OpenService failed {}", GetLastError()); CloseServiceHandle(schSCManager); return; } @@ -257,10 +257,10 @@ namespace // Delete the service. if (!DeleteService(schService)) { - llarp::LogError("DeleteService failed ", GetLastError()); + llarp::log::error(logcat, "DeleteService failed {}", GetLastError()); } else - llarp::LogInfo("Service deleted successfully\n"); + llarp::log::info(logcat, "Service deleted successfully"); CloseServiceHandle(schService); CloseServiceHandle(schSCManager); @@ -337,7 +337,7 @@ namespace if (svc->handle == nullptr) { - llarp::LogError("failed to register daemon control handler"); + llarp::log::error(logcat, "failed to register daemon control handler"); return; } @@ -442,6 +442,9 @@ namespace cli.exit(e); }; + // TESTNET: + oxen::log::set_level("quic", oxen::log::Level::critical); + if (configFile.has_value()) { // when we have an explicit filepath @@ -454,7 +457,7 @@ namespace } catch (std::exception& ex) { - llarp::LogError("cannot generate config at ", *configFile, ": ", ex.what()); + llarp::log::error(logcat, "cannot generate config at {}: {}", *configFile, ex.what()); return 1; } } @@ -464,13 +467,13 @@ namespace { if (!fs::exists(*configFile)) { - llarp::LogError("Config file not found ", *configFile); + llarp::log::error(logcat, "Config file not found {}", *configFile); return 1; } } catch (std::exception& ex) { - llarp::LogError("cannot check if ", *configFile, " exists: ", ex.what()); + llarp::log::error(logcat, "cannot check if ", *configFile, " exists: ", ex.what()); return 1; } } @@ -487,7 +490,7 @@ namespace } catch (std::exception& ex) { - llarp::LogError("cannot ensure config: ", ex.what()); + llarp::log::error(logcat, "cannot ensure config: {}", ex.what()); return 1; } configFile = llarp::GetDefaultConfigPath(); @@ -548,14 +551,13 @@ namespace static void run_main_context(std::optional confFile, const llarp::RuntimeOptions opts) { - llarp::LogInfo(fmt::format( - "starting up {} {}", llarp::LOKINET_VERSION_FULL, llarp::LOKINET_RELEASE_MOTTO)); + llarp::log::info(logcat, "starting up {}", llarp::LOKINET_VERSION_FULL); try { std::shared_ptr conf; if (confFile) { - llarp::LogInfo("Using config file: ", *confFile); + llarp::log::info(logcat, "Using config file: {}", *confFile); conf = std::make_shared(confFile->parent_path()); } else @@ -564,7 +566,7 @@ namespace } if (not conf->load(confFile, opts.isSNode)) { - llarp::LogError("failed to parse configuration"); + llarp::log::error(logcat, "failed to parse configuration"); exit_code.set_value(1); return; } @@ -589,13 +591,13 @@ namespace } catch (llarp::util::bind_socket_error& ex) { - llarp::LogError(fmt::format("{}, is lokinet already running? 🤔", ex.what())); + llarp::log::error(logcat, "{}; is lokinet already running?", ex.what()); exit_code.set_value(1); return; } - catch (std::exception& ex) + catch (const std::exception& ex) { - llarp::LogError(fmt::format("failed to start up lokinet: {}", ex.what())); + llarp::log::error(logcat, "failed to start up lokinet: {}", ex.what()); exit_code.set_value(1); return; } @@ -604,14 +606,14 @@ namespace auto result = ctx->Run(opts); exit_code.set_value(result); } - catch (std::exception& e) + catch (const std::exception& e) { - llarp::LogError("Fatal: caught exception while running: ", e.what()); + llarp::log::error(logcat, "Fatal: caught exception while running: {}", e.what()); exit_code.set_exception(std::current_exception()); } catch (...) { - llarp::LogError("Fatal: caught non-standard exception while running"); + llarp::log::error(logcat, "Fatal: caught non-standard exception while running"); exit_code.set_exception(std::current_exception()); } } diff --git a/external/oxen-encoding b/external/oxen-encoding index f6172d58d..24fbdb794 160000 --- a/external/oxen-encoding +++ b/external/oxen-encoding @@ -1 +1 @@ -Subproject commit f6172d58d3358473a4c98d96270058a32e166d5f +Subproject commit 24fbdb794ef26bf5324d1b56d48d1da8de8a140c diff --git a/external/oxen-libquic b/external/oxen-libquic index 3ced484e8..73d4b6f94 160000 --- a/external/oxen-libquic +++ b/external/oxen-libquic @@ -1 +1 @@ -Subproject commit 3ced484e8cc543b90c5fc554ccc0ea2e54ec8d37 +Subproject commit 73d4b6f940c0790e6f7c3ec46e03c014f51be1fa diff --git a/llarp/CMakeLists.txt b/llarp/CMakeLists.txt index 657c20cb2..790c92351 100644 --- a/llarp/CMakeLists.txt +++ b/llarp/CMakeLists.txt @@ -178,10 +178,10 @@ lokinet_add_library(lokinet-nodedb set(BOOTSTRAP_FALLBACKS) foreach(bs IN ITEMS MAINNET TESTNET) if(BOOTSTRAP_FALLBACK_${bs}) - message(STATUS "Building with ${bs} fallback boostrap path \"${BOOTSTRAP_FALLBACK_${bs}}\"") + message(STATUS "Building with ${bs} fallback bootstrap path \"${BOOTSTRAP_FALLBACK_${bs}}\"") file(READ "${BOOTSTRAP_FALLBACK_${bs}}" bs_data HEX) if(bs STREQUAL TESTNET) - set(network "gamma") + set(network "testnet") elseif(bs STREQUAL MAINNET) set(network "lokinet") else() diff --git a/llarp/bootstrap.cpp b/llarp/bootstrap.cpp index 474052c30..061e3b3b3 100644 --- a/llarp/bootstrap.cpp +++ b/llarp/bootstrap.cpp @@ -9,12 +9,27 @@ namespace llarp bool BootstrapList::bt_decode(std::string_view buf) { - try + const auto& f = buf.front(); + + switch (f) { - oxenc::bt_list_consumer btlc{buf}; + case 'l': + return bt_decode(oxenc::bt_list_consumer{buf}); + case 'd': + return bt_decode(oxenc::bt_dict_consumer{buf}); + default: + log::critical(logcat, "Unable to parse bootstrap as bt list or dict!"); + return false; + } + } + bool + BootstrapList::bt_decode(oxenc::bt_list_consumer btlc) + { + try + { while (not btlc.is_finished()) - emplace(btlc.consume_dict_consumer()); + emplace(btlc.consume_dict_data()); } catch (...) { @@ -22,9 +37,45 @@ namespace llarp return false; } + _curr = begin(); return true; } + bool + BootstrapList::bt_decode(oxenc::bt_dict_consumer btdc) + { + try + { + emplace(btdc); + } + catch (const std::exception& e) + { + log::warning(logcat, "Unable to decode bootstrap RemoteRC: {}", e.what()); + return false; + } + + _curr = begin(); + return true; + } + + bool + BootstrapList::contains(const RouterID& rid) const + { + for (const auto& it : *this) + { + if (it.router_id() == rid) + return true; + } + + return false; + } + + bool + BootstrapList::contains(const RemoteRC& rc) const + { + return count(rc); + } + std::string_view BootstrapList::bt_encode() const { @@ -37,34 +88,82 @@ namespace llarp } void - BootstrapList::read_from_file(const fs::path& fpath) + BootstrapList::populate_bootstraps( + std::vector paths, const fs::path& def, bool load_fallbacks) { - bool isListFile = false; - - if (std::ifstream inf(fpath.c_str(), std::ios::binary); inf.is_open()) + for (const auto& f : paths) { - const char ch = inf.get(); - isListFile = ch == 'l'; + // TESTNET: TODO: revise fucked config + log::debug(logcat, "Loading BootstrapRC from file at path:{}", f); + if (not read_from_file(f)) + throw std::invalid_argument{"User-provided BootstrapRC is invalid!"}; } - if (isListFile) + if (empty()) { - auto content = util::file_to_string(fpath); + log::debug( + logcat, + "BootstrapRC list empty; looking for default BootstrapRC from file at path:{}", + def); + read_from_file(def); + } - if (not bt_decode(content)) + for (auto itr = begin(); itr != end(); ++itr) + { + if (RouterContact::is_obsolete(*itr)) // can move this into ::read_from_file { - throw std::runtime_error{fmt::format("failed to read bootstrap list file '{}'", fpath)}; + log::critical(logcat, "Deleting obsolete BootstrapRC (rid:{})", itr->router_id()); + itr = erase(itr); + continue; } } - else + + if (empty() and load_fallbacks) { - RemoteRC rc; - if (not rc.read(fpath)) + log::critical(logcat, "BootstrapRC list empty; loading fallbacks..."); + auto fallbacks = llarp::load_bootstrap_fallbacks(); + + if (auto itr = fallbacks.find(RouterContact::ACTIVE_NETID); itr != fallbacks.end()) { - throw std::runtime_error{ - fmt::format("failed to decode bootstrap RC, file='{}', rc={}", fpath, rc.to_string())}; + log::critical( + logcat, "Loading {} default fallback bootstrap router(s)!", itr->second.size()); + merge(itr->second); + } + + if (empty()) + { + log::error( + logcat, + "No Bootstrap routers were loaded. The default Bootstrap file {} does not exist, and " + "loading fallback Bootstrap RCs failed.", + def); + + throw std::runtime_error("No Bootstrap nodes available."); } - insert(rc); } + + log::critical(logcat, "We have {} Bootstrap router(s)!", size()); + _curr = begin(); + } + + bool + BootstrapList::read_from_file(const fs::path& fpath) + { + bool result = false; + + if (not fs::exists(fpath)) + { + log::critical(logcat, "Bootstrap RC file non-existant at path:{}", fpath); + return result; + } + + auto content = util::file_to_string(fpath); + result = bt_decode(content); + + log::critical( + logcat, "{}uccessfully loaded BootstrapRC file at path:{}", result ? "S" : "Un", fpath); + + _curr = begin(); + return result; } } // namespace llarp diff --git a/llarp/bootstrap.hpp b/llarp/bootstrap.hpp index 11e8286d3..86cf416f0 100644 --- a/llarp/bootstrap.hpp +++ b/llarp/bootstrap.hpp @@ -2,6 +2,7 @@ #include "router_contact.hpp" +#include #include #include @@ -11,15 +12,61 @@ namespace llarp { struct BootstrapList final : public std::set { + std::set::iterator _curr = begin(); + + const RemoteRC& + current() + { + return *_curr; + } + bool bt_decode(std::string_view buf); + bool + bt_decode(oxenc::bt_list_consumer btlc); + + bool + bt_decode(oxenc::bt_dict_consumer btdc); + std::string_view bt_encode() const; void + populate_bootstraps(std::vector paths, const fs::path& def, bool load_fallbacks); + + bool read_from_file(const fs::path& fpath); + bool + contains(const RouterID& rid) const; + + // returns a reference to the next index and a boolean that equals true if + // this is the front of the set + const RemoteRC& + next() + { + if (size() < 2) + return *_curr; + + ++_curr; + + if (_curr == this->end()) + _curr = this->begin(); + + return *_curr; + } + + bool + contains(const RemoteRC& rc) const; + + void + randomize() + { + if (size() > 1) + _curr = std::next(begin(), std::uniform_int_distribution{0, size() - 1}(csrng)); + } + void clear_list() { diff --git a/llarp/config/config.cpp b/llarp/config/config.cpp index 06e95594a..eeb2fecbf 100644 --- a/llarp/config/config.cpp +++ b/llarp/config/config.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -19,15 +20,6 @@ namespace llarp { - // constants for config file default values - constexpr int DefaultMinConnectionsForRouter = 6; - constexpr int DefaultMaxConnectionsForRouter = 60; - - constexpr int DefaultMinConnectionsForClient = 4; - constexpr int DefaultMaxConnectionsForClient = 6; - - constexpr int DefaultPublicPort = 1090; - using namespace config; namespace { @@ -61,8 +53,8 @@ namespace llarp "netid", Default{llarp::LOKINET_DEFAULT_NETID}, Comment{ - "Network ID; this is '"s + llarp::LOKINET_DEFAULT_NETID - + "' for mainnet, 'gamma' for testnet.", + "Network ID; this is '"s + llarp::LOKINET_DEFAULT_NETID + "' for mainnet, '"s + + llarp::LOKINET_TESTNET_NETID + "' for testnet."s, }, [this](std::string arg) { if (arg.size() > NETID_SIZE) @@ -72,38 +64,44 @@ namespace llarp net_id = std::move(arg); }); - int minConnections = - (params.is_relay ? DefaultMinConnectionsForRouter : DefaultMinConnectionsForClient); conf.define_option( "router", - "min-connections", - Default{minConnections}, + "relay-connections", + Default{CLIENT_ROUTER_CONNECTIONS}, + ClientOnly, Comment{ - "Minimum number of routers lokinet will attempt to maintain connections to.", + "Minimum number of routers lokinet client will attempt to maintain connections to.", }, [=](int arg) { - if (arg < minConnections) + if (arg < CLIENT_ROUTER_CONNECTIONS) throw std::invalid_argument{ - fmt::format("min-connections must be >= {}", minConnections)}; + fmt::format("Client relay connections must be >= {}", CLIENT_ROUTER_CONNECTIONS)}; - min_connected_routers = arg; + client_router_connections = arg; + }); + + conf.define_option( + "router", + "min-connections", + Deprecated, + Comment{ + "Minimum number of routers lokinet will attempt to maintain connections to.", + }, + [=](int) { + log::warning( + logcat, "Router min-connections is deprecated; use relay-connections for clients"); }); - int maxConnections = - (params.is_relay ? DefaultMaxConnectionsForRouter : DefaultMaxConnectionsForClient); conf.define_option( "router", "max-connections", - Default{maxConnections}, + Deprecated, Comment{ "Maximum number (hard limit) of routers lokinet will be connected to at any time.", }, - [=](int arg) { - if (arg < maxConnections) - throw std::invalid_argument{ - fmt::format("max-connections must be >= {}", maxConnections)}; - - max_connected_routers = arg; + [=](int) { + log::warning( + logcat, "Router max-connections is deprecated; use relay-connections for clients"); }); conf.define_option("router", "nickname", Deprecated); @@ -135,19 +133,7 @@ namespace llarp "this setting specifies the public IP at which this router is reachable. When", "provided the public-port option must also be specified.", }, - [this, net = params.Net_ptr()](std::string arg) { - if (arg.empty()) - return; - nuint32_t addr{}; - if (not addr.FromString(arg)) - throw std::invalid_argument{fmt::format("{} is not a valid IPv4 address", arg)}; - - if (net->IsBogonIP(addr)) - throw std::invalid_argument{ - fmt::format("{} is not a publicly routable ip address", addr)}; - - public_ip = addr; - }); + [this](std::string arg) { public_ip = std::move(arg); }); conf.define_option("router", "public-address", Hidden, [](std::string) { throw std::invalid_argument{ @@ -155,19 +141,18 @@ namespace llarp "[router]:public-port instead"}; }); - conf.define_option( + conf.define_option( "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) { + [this](uint16_t arg) { if (arg <= 0 || arg > std::numeric_limits::max()) throw std::invalid_argument("public-port must be >= 0 and <= 65536"); - public_port = ToNet(huint16_t{static_cast(arg)}); + public_port = arg; }); conf.define_option( @@ -290,7 +275,7 @@ namespace llarp MultiValue, [this](std::string value) { RouterID router; - if (not router.FromString(value)) + if (not router.from_string(value)) throw std::invalid_argument{"bad snode value: " + value}; if (not strict_connect.insert(router).second) throw std::invalid_argument{"duplicate strict connect snode: " + value}; @@ -710,7 +695,7 @@ namespace llarp }, [this](std::string arg) { RouterID id; - if (not id.FromString(arg)) + if (not id.from_string(arg)) throw std::invalid_argument{fmt::format("Invalid RouterID: {}", arg)}; auto itr = snode_blacklist.emplace(std::move(id)); @@ -913,66 +898,59 @@ namespace llarp conf.define_option( "bind", "public-ip", + Hidden, RelayOnly, Comment{ "The IP address to advertise to the network instead of the incoming= or auto-detected", "IP. This is typically required only when incoming= is used to listen on an internal", "private range IP address that received traffic forwarded from the public IP.", }, - [this](std::string_view arg) { - SockAddr pubaddr{arg}; - public_addr = pubaddr.getIP(); + [this](std::string arg) { + public_addr = std::move(arg); + log::warning( + logcat, + "Using deprecated option; pass this value to [Router]:public-ip instead PLEASE"); }); + conf.define_option( "bind", "public-port", + Hidden, RelayOnly, Comment{ "The port to advertise to the network instead of the incoming= (or default) port.", "This is typically required only when incoming= is used to listen on an internal", "private range IP address/port that received traffic forwarded from the public IP.", }, - [this](uint16_t arg) { public_port = net::port_t::from_host(arg); }); + [this](uint16_t arg) { + if (arg <= 0 || arg > std::numeric_limits::max()) + throw std::invalid_argument("public-port must be >= 0 and <= 65536"); + public_port = arg; + log::warning( + logcat, + "Using deprecated option; pass this value to [Router]:public-port instead PLEASE"); + }); auto parse_addr_for_link = [net_ptr](const std::string& arg) { std::optional maybe = std::nullopt; - std::string_view arg_v; + std::string_view arg_v{arg}, host; + uint16_t p{DEFAULT_LISTEN_PORT}; - // explicitly provided value - if (not arg.empty()) + if (auto pos = arg_v.find(':'); pos != arg_v.npos) { - arg_v = std::string_view{arg}; - } - - if (arg_v[0] == ':') - { - uint16_t res; - if (auto rv = llarp::parse_int(arg_v.substr(1), res); not rv) - res = DEFAULT_LISTEN_PORT; + host = arg_v.substr(0, pos); - maybe = oxen::quic::Address{""s, res}; + if (not llarp::parse_int(arg_v.substr(pos + 1), p)) + throw std::invalid_argument{"Failed to parse port in arg:{}"_format(arg)}; } - else if (auto pos = arg_v.find(':'); pos != arg_v.npos) - { - auto h = arg_v.substr(0, pos); - uint16_t p; - if (auto rv = llarp::parse_int(arg_v.substr(pos + 1), p); not rv) - p = DEFAULT_LISTEN_PORT; - maybe = oxen::quic::Address{std::string{h}, p}; + if (host.empty()) + maybe = net_ptr->get_best_public_address(true, p); + else + maybe = oxen::quic::Address{std::string{host}, p}; - if (maybe->is_loopback()) - throw std::invalid_argument{fmt::format("{} is a loopback address", arg)}; - } - if (not maybe) - { - // infer public address - if (auto maybe_ifname = net_ptr->GetBestNetIF()) - maybe = oxen::quic::Address{*maybe_ifname}; - } - - if (maybe && maybe->port() == 0) - maybe = oxen::quic::Address{maybe->host(), DEFAULT_LISTEN_PORT}; + if (maybe and maybe->is_loopback()) + throw std::invalid_argument{"{} is a loopback address"_format(arg)}; return maybe; }; @@ -980,10 +958,7 @@ namespace llarp conf.define_option( "bind", "listen", - Required, Comment{ - "********** NEW API OPTION (see note) **********", - "", "IP and/or port for lokinet to bind to for inbound/outbound connections.", "", "If IP is omitted then lokinet will search for a local network interface with a", @@ -1007,11 +982,13 @@ namespace llarp }, [this, parse_addr_for_link](const std::string& arg) { if (auto a = parse_addr_for_link(arg); a and a->is_addressable()) - addr = a; + { + listen_addr = *a; + using_user_value = true; + using_new_api = true; + } else - addr = oxen::quic::Address{""s, DEFAULT_LISTEN_PORT}; - - using_new_api = true; + throw std::invalid_argument{"Could not parse listen address!"}; }); conf.define_option( @@ -1020,86 +997,22 @@ namespace llarp RelayOnly, MultiValue, Hidden, - Comment{ - "********** DEPRECATED **********", - "Note: the new API dictates the lokinet bind address through the 'listen' config", - "parameter. Only ONE address will be read (no more lists of inbounds). Any address", - "passed to `listen` will supersede the", - "", - "IP and/or port to listen on for incoming connections.", - "", - "If IP is omitted then lokinet will search for a local network interface with a", - "public IP address and use that IP (and will exit with an error if no such IP is found", - "on the system). If port is omitted then lokinet defaults to 1090.", - "", - "Examples:", - " inbound=15.5.29.5:443", - " inbound=10.0.2.2", - " inbound=:1234", - "", - "Using a private range IP address (like the second example entry) will require using", - "the public-ip= and public-port= to specify the public IP address at which this", - "router can be reached.", - }, [this, parse_addr_for_link](const std::string& arg) { if (using_new_api) throw std::runtime_error{"USE THE NEW API -- SPECIFY LOCAL ADDRESS UNDER [LISTEN]"}; if (auto a = parse_addr_for_link(arg); a and a->is_addressable()) - addr = a; - else - addr = oxen::quic::Address{""s, DEFAULT_LISTEN_PORT}; + { + log::warning( + logcat, + "Loaded address from deprecated [inbound] options; update your config to use " + "[bind]:listen instead PLEASE"); + listen_addr = *a; + using_user_value = true; + } }); - conf.define_option( - "bind", - "outbound", - MultiValue, - params.is_relay ? Comment{ - "********** THIS PARAMETER IS DEPRECATED -- USE 'LISTEN' INSTEAD **********", - "", - "IP and/or port to use for outbound socket connections to other lokinet routers.", - "", - "If no outbound bind IP is configured, or the 0.0.0.0 wildcard IP is given, then", - "lokinet will bind to the same IP being used for inbound connections (either an", - "explicit inbound= provided IP, or the default). If no port is given, or port is", - "given as 0, then a random high port will be used.", - "", - "If using multiple inbound= addresses then you *must* provide an explicit oubound= IP.", - "", - "Examples:", - " outbound=1.2.3.4:5678", - " outbound=:9000", - " outbound=8.9.10.11", - "", - "The second example binds on the default incoming IP using port 9000; the third", - "example binds on the given IP address using a random high port.", - } : Comment{ - "********** DEPRECATED **********", - "", - "IP and/or port to use for outbound socket connections to lokinet routers.", - "", - "If no outbound bind IP is configured then lokinet will use a wildcard IP address", - "(equivalent to specifying 0.0.0.0). If no port is given then a random high port", - "will be used.", - "", - "Examples:", - " outbound=1.2.3.4:5678", - " outbound=:9000", - " outbound=8.9.10.11", - "", - "The second example binds on the wildcard address using port 9000; the third example", - "binds on the given IP address using a random high port.", - }, - [this, parse_addr_for_link](const std::string& arg) { - if (using_new_api) - throw std::runtime_error{"USE THE NEW API -- SPECIFY LOCAL ADDRESS UNDER [LISTEN]"}; - - if (auto a = parse_addr_for_link(arg); a and a->is_addressable()) - addr = a; - else - addr = oxen::quic::Address{""s, DEFAULT_LISTEN_PORT}; - }); + conf.define_option("bind", "outbound", MultiValue, Deprecated, Hidden); conf.add_undeclared_handler( "bind", [this](std::string_view, std::string_view key, std::string_view val) { @@ -1118,7 +1031,11 @@ namespace llarp // special case: wildcard for outbound if (key == "*") { - addr = oxen::quic::Address{port}; + log::warning( + logcat, + "Wildcat address referencing port {} is referencing deprecated outbound config " + "options; use [bind]:listen instead", + port); return; } @@ -1137,36 +1054,17 @@ namespace llarp e.what())}; } - if (temp.is_addressable()) + if (not temp.is_addressable()) { - addr = std::move(temp); - return; - } - - throw std::runtime_error{fmt::format( - "Invalid address: {}; stop using this deprecated handler, update your config to use " - "[bind]:listen instead PLEASE", - temp)}; - }); - } - - void - ConnectConfig::define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params) - { - (void)params; - - conf.add_undeclared_handler( - "connect", [this](std::string_view section, std::string_view name, std::string_view value) { - fs::path file{value.begin(), value.end()}; - if (not fs::exists(file)) throw std::runtime_error{fmt::format( - "Specified bootstrap file {} specified in [{}]:{} does not exist", - value, - section, - name)}; + "Invalid address: {}; stop using this deprecated handler, update your config to " + "use " + "[bind]:listen instead PLEASE", + temp)}; + } - routers.emplace_back(std::move(file)); - return true; + listen_addr = std::move(temp); + using_user_value = true; }); } @@ -1221,17 +1119,16 @@ namespace llarp LokidConfig::define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params) { (void)params; - - conf.define_option("lokid", "enabled", RelayOnly, Deprecated); - - conf.define_option("lokid", "jsonrpc", RelayOnly, [](std::string arg) { - if (arg.empty()) - return; - throw std::invalid_argument( - "the [lokid]:jsonrpc option is no longer supported; please use the [lokid]:rpc config " - "option instead with oxend's lmq-local-control address -- typically a value such as " - "rpc=ipc:///var/lib/oxen/oxend.sock or rpc=ipc:///home/snode/.oxen/oxend.sock"); - }); + conf.define_option( + "lokid", + "disable-testing", + Default{false}, + Hidden, + RelayOnly, + Comment{ + "Development option: set to true to disable reachability testing when using ", + "testnet"}, + assignment_acceptor(disable_testing)); conf.define_option( "lokid", @@ -1250,6 +1147,15 @@ namespace llarp [this](std::string arg) { rpc_addr = oxenmq::address(arg); }); // Deprecated options: + conf.define_option("lokid", "jsonrpc", RelayOnly, Hidden, [](std::string arg) { + if (arg.empty()) + return; + throw std::invalid_argument( + "the [lokid]:jsonrpc option is no longer supported; please use the [lokid]:rpc config " + "option instead with oxend's lmq-local-control address -- typically a value such as " + "rpc=ipc:///var/lib/oxen/oxend.sock or rpc=ipc:///home/snode/.oxen/oxend.sock"); + }); + conf.define_option("lokid", "enabled", RelayOnly, Deprecated); conf.define_option("lokid", "username", Deprecated); conf.define_option("lokid", "password", Deprecated); conf.define_option("lokid", "service-node-seed", Deprecated); @@ -1278,14 +1184,12 @@ namespace llarp }, [this](std::string arg) { if (arg.empty()) - { throw std::invalid_argument("cannot use empty filename as bootstrap"); - } + files.emplace_back(std::move(arg)); + if (not fs::exists(files.back())) - { throw std::invalid_argument("file does not exist: " + arg); - } }); } @@ -1437,22 +1341,19 @@ namespace llarp const auto overridesDir = GetOverridesDir(data_dir); if (fs::exists(overridesDir)) { - util::IterDir(overridesDir, [&](const fs::path& overrideFile) { - if (overrideFile.extension() == ".ini") - { - ConfigParser parser; - if (not parser.load_file(overrideFile)) - throw std::runtime_error{"cannot load '" + overrideFile.u8string() + "'"}; - - parser.iter_all_sections([&](std::string_view section, const SectionValues& values) { - for (const auto& pair : values) - { - conf.add_config_value(section, pair.first, pair.second); - } - }); - } - return true; - }); + for (const auto& f : fs::directory_iterator{overridesDir}) + { + if (not f.is_regular_file() or f.path().extension() != ".ini") + continue; + ConfigParser parser; + if (not parser.load_file(f.path())) + throw std::runtime_error{"cannot load '" + f.path().u8string() + "'"}; + + parser.iter_all_sections([&](std::string_view section, const SectionValues& values) { + for (const auto& [k, v] : values) + conf.add_config_value(section, k, v); + }); + } } } @@ -1537,7 +1438,6 @@ namespace llarp router.define_config_options(conf, params); network.define_config_options(conf, params); paths.define_config_options(conf, params); - connect.define_config_options(conf, params); dns.define_config_options(conf, params); links.define_config_options(conf, params); api.define_config_options(conf, params); diff --git a/llarp/config/config.hpp b/llarp/config/config.hpp index 624b22cb5..152da9345 100644 --- a/llarp/config/config.hpp +++ b/llarp/config/config.hpp @@ -34,7 +34,9 @@ namespace llarp using SectionValues = llarp::ConfigParser::SectionValues; using ConfigMap = llarp::ConfigParser::ConfigMap; - inline static constexpr uint16_t DEFAULT_LISTEN_PORT{1090}; + inline const std::string QUAD_ZERO{"0.0.0.0"}; + inline constexpr uint16_t DEFAULT_LISTEN_PORT{1090}; + inline constexpr int CLIENT_ROUTER_CONNECTIONS = 4; // TODO: don't use these maps. they're sloppy and difficult to follow /// Small struct to gather all parameters needed for config generation to reduce the number of @@ -57,8 +59,7 @@ namespace llarp struct RouterConfig { - size_t min_connected_routers = 0; - size_t max_connected_routers = 0; + int client_router_connections{CLIENT_ROUTER_CONNECTIONS}; std::string net_id; @@ -77,10 +78,9 @@ namespace llarp std::string transkey_file; bool is_relay = false; - /// deprecated - std::optional public_ip; - /// deprecated - std::optional public_port; + + std::optional public_ip; + std::optional public_port; void define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params); @@ -170,19 +170,15 @@ namespace llarp struct LinksConfig { - std::optional public_addr; - std::optional public_port; - - std::optional addr; - bool using_new_api = false; + // DEPRECATED -- use [Router]:public_addr + std::optional public_addr; + // DEPRECATED -- use [Router]:public_port + std::optional public_port; - void - define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params); - }; + std::optional listen_addr; - struct ConnectConfig - { - std::vector routers; + bool using_user_value = false; + bool using_new_api = false; void define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params); @@ -202,6 +198,7 @@ namespace llarp { fs::path id_keyfile; oxenmq::address rpc_addr; + bool disable_testing = true; void define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params); @@ -210,7 +207,6 @@ namespace llarp struct BootstrapConfig { std::vector files; - BootstrapList routers; bool seednode; void @@ -240,7 +236,6 @@ namespace llarp RouterConfig router; NetworkConfig network; PeerSelectionConfig paths; - ConnectConfig connect; DnsConfig dns; LinksConfig links; ApiConfig api; diff --git a/llarp/config/key_manager.cpp b/llarp/config/key_manager.cpp index d1da613c5..070a4ce86 100644 --- a/llarp/config/key_manager.cpp +++ b/llarp/config/key_manager.cpp @@ -10,20 +10,20 @@ namespace llarp { - KeyManager::KeyManager() : m_initialized(false), m_needBackup(false) + KeyManager::KeyManager() : is_initialized(false), backup_keys(false) {} bool - KeyManager::initialize(const llarp::Config& config, bool genIfAbsent, bool isSNode) + KeyManager::initialize(const llarp::Config& config, bool gen_if_absent, bool is_snode) { - if (m_initialized) + if (is_initialized) return false; - if (not isSNode) + if (not is_snode) { - crypto::identity_keygen(identityKey); - crypto::encryption_keygen(encryptionKey); - crypto::encryption_keygen(transportKey); + crypto::identity_keygen(identity_key); + crypto::encryption_keygen(encryption_key); + crypto::encryption_keygen(transport_key); return true; } @@ -46,80 +46,87 @@ namespace llarp } }; - m_rcPath = deriveFile(our_rc_filename, config.router.rc_file); - m_idKeyPath = deriveFile(our_identity_filename, config.router.idkey_file); - m_encKeyPath = deriveFile(our_enc_key_filename, config.router.enckey_file); - m_transportKeyPath = deriveFile(our_transport_key_filename, config.router.transkey_file); + rc_path = deriveFile(our_rc_filename, config.router.rc_file); + idkey_path = deriveFile(our_identity_filename, config.router.idkey_file); + enckey_path = deriveFile(our_enc_key_filename, config.router.enckey_file); + transkey_path = deriveFile(our_transport_key_filename, config.router.transkey_file); RemoteRC rc; - bool exists = rc.read(m_rcPath); - if (not exists and not genIfAbsent) - { - LogError("Could not read RouterContact at path ", m_rcPath); - return false; - } - // we need to back up keys if our self.signed doesn't appear to have a - // valid signature - m_needBackup = (isSNode and not rc.verify()); - - // if our RC file can't be verified, assume it is out of date (e.g. uses - // older encryption) and needs to be regenerated. before doing so, backup - // files that will be overwritten - if (exists and m_needBackup) + if (auto exists = rc.read(rc_path); not exists) { - if (!genIfAbsent) + if (not gen_if_absent) { - LogError("Our RouterContact ", m_rcPath, " is invalid or out of date"); + log::error(logcat, "Could not read RC at path {}", rc_path); return false; } - else + } + else + { + if (backup_keys = (is_snode and not rc.verify()); backup_keys) { - LogWarn( - "Our RouterContact ", - m_rcPath, - " seems out of date, backing up and regenerating private keys"); + auto err = "RC (path:{}) is invalid or out of date"_format(rc_path); - if (!backupKeyFilesByMoving()) + if (not gen_if_absent) { - LogError( - "Could not mv some key files, please ensure key files" - " are backed up if needed and remove"); + log::error(logcat, err); return false; } - } - } - if (not config.router.is_relay) - { - // load identity key or create if needed - auto identityKeygen = [](llarp::SecretKey& key) { - // TODO: handle generating from service node seed - llarp::crypto::identity_keygen(key); - }; - if (not loadOrCreateKey(m_idKeyPath, identityKey, identityKeygen)) - return false; + log::warning(logcat, "{}; backing up and regenerating private keys...", err); + + if (not copy_backup_keyfiles()) + { + log::error(logcat, "Failed to copy-backup key files"); + return false; + } + } } // load encryption key - auto encryptionKeygen = [](llarp::SecretKey& key) { llarp::crypto::encryption_keygen(key); }; - if (not loadOrCreateKey(m_encKeyPath, encryptionKey, encryptionKeygen)) + auto enckey_gen = [](llarp::SecretKey& key) { llarp::crypto::encryption_keygen(key); }; + if (not keygen(enckey_path, encryption_key, enckey_gen)) + { + log::critical( + logcat, "KeyManager::keygen failed to generate encryption key line:{}", __LINE__); return false; + } // TODO: transport key (currently done in LinkLayer) - auto transportKeygen = [](llarp::SecretKey& key) { + auto transkey_gen = [](llarp::SecretKey& key) { key.Zero(); crypto::encryption_keygen(key); }; - if (not loadOrCreateKey(m_transportKeyPath, transportKey, transportKeygen)) + + if (not keygen(transkey_path, transport_key, transkey_gen)) + { + log::critical( + logcat, "KeyManager::keygen failed to generate transport key line:{}", __LINE__); return false; + } - m_initialized = true; + if (not config.router.is_relay) + { + // load identity key or create if needed + auto idkey_gen = [](llarp::SecretKey& key) { + // TODO: handle generating from service node seed + llarp::crypto::identity_keygen(key); + }; + + if (not keygen(idkey_path, identity_key, idkey_gen)) + { + log::critical( + logcat, "KeyManager::keygen failed to generate identity key line:{}", __LINE__); + return false; + } + } + + is_initialized = true; return true; } bool - KeyManager::backupFileByMoving(const fs::path& filepath) + KeyManager::copy_backup_keyfile(const fs::path& filepath) { auto findFreeBackupFilename = [](const fs::path& filepath) { for (int i = 0; i < 9; i++) @@ -136,6 +143,7 @@ namespace llarp std::error_code ec; bool exists = fs::exists(filepath, ec); + if (ec) { LogError("Could not determine status of file ", filepath, ": ", ec.message()); @@ -168,13 +176,13 @@ namespace llarp } bool - KeyManager::backupKeyFilesByMoving() const + KeyManager::copy_backup_keyfiles() const { - std::vector files = {m_rcPath, m_idKeyPath, m_encKeyPath, m_transportKeyPath}; + std::vector files = {rc_path, idkey_path, enckey_path, transkey_path}; for (auto& filepath : files) { - if (not backupFileByMoving(filepath)) + if (not copy_backup_keyfile(filepath)) return false; } @@ -182,7 +190,7 @@ namespace llarp } bool - KeyManager::loadOrCreateKey( + KeyManager::keygen( fs::path path, llarp::SecretKey& key, std::function keygen) { if (not fs::exists(path)) diff --git a/llarp/config/key_manager.hpp b/llarp/config/key_manager.hpp index 20a45d0c9..9573fa128 100644 --- a/llarp/config/key_manager.hpp +++ b/llarp/config/key_manager.hpp @@ -29,15 +29,13 @@ namespace llarp /// @param filepath is the name of the original file to backup. /// @return true if the file could be moved or didn't exist, false otherwise static bool - backupFileByMoving(const fs::path& filepath); + copy_backup_keyfile(const fs::path& filepath); /// Constructor KeyManager(); - /// Initializes keys using the provided config, loading from disk - /// - /// NOTE: Must be called prior to obtaining any keys. - /// NOTE: blocks on I/O + /// Initializes keys using the provided config, loading from disk. Must be called + /// prior to obtaining any keys and blocks on I/O /// /// @param config should be a prepared config object /// @param genIfAbsent determines whether or not we will create files if they @@ -52,37 +50,37 @@ namespace llarp /// @param rc (out) will be modified to contian the RouterContact /// @return true on success, false otherwise bool - getRouterContact(llarp::RouterContact& rc) const; + gen_rc(llarp::RouterContact& rc) const; /// Return whether or not we need to backup keys as we load them bool - needBackup() const + needs_backup() const { - return m_needBackup; + return backup_keys; } - llarp::SecretKey identityKey; - llarp::SecretKey encryptionKey; - llarp::SecretKey transportKey; + llarp::SecretKey identity_key; + llarp::SecretKey encryption_key; + llarp::SecretKey transport_key; - fs::path m_rcPath; - fs::path m_idKeyPath; - fs::path m_encKeyPath; - fs::path m_transportKeyPath; + fs::path rc_path; + fs::path idkey_path; + fs::path enckey_path; + fs::path transkey_path; private: - std::atomic_bool m_initialized; - std::atomic_bool m_needBackup; + std::atomic_bool is_initialized; + std::atomic_bool backup_keys; /// Backup each key file (by copying, e.g. foo -> foo.bak) bool - backupKeyFilesByMoving() const; + copy_backup_keyfiles() const; /// Load the key at a given filepath or create it /// /// @param keygen is a function that will generate the key if needed static bool - loadOrCreateKey( + keygen( fs::path filepath, llarp::SecretKey& key, std::function keygen); diff --git a/llarp/consensus/reachability_testing.cpp b/llarp/consensus/reachability_testing.cpp index 1f5068255..9274bff2e 100644 --- a/llarp/consensus/reachability_testing.cpp +++ b/llarp/consensus/reachability_testing.cpp @@ -82,29 +82,34 @@ namespace llarp::consensus // Pull the next element off the queue, but skip ourself, any that are no longer registered, and // any that are currently known to be failing (those are queued for testing separately). - RouterID my_pk{router->pubkey()}; + auto local_pk = router->local_rid(); + while (!testing_queue.empty()) { auto& pk = testing_queue.back(); std::optional sn; - if (pk != my_pk && !failing.count(pk)) + + if (pk != local_pk && !failing.count(pk)) sn = pk; + testing_queue.pop_back(); + if (sn) return sn; } + if (!requeue) return std::nullopt; // FIXME: when a *new* node comes online we need to inject it into a random position in the SN // list with probability (L/N) [L = current list size, N = potential list size] // - // (FIXME: put this FIXME in a better place ;-) ) // We exhausted the queue so repopulate it and try again testing_queue.clear(); - const auto all = router->get_whitelist(); + const auto& all = router->get_whitelist(); + testing_queue.insert(testing_queue.begin(), all.begin(), all.end()); std::shuffle(testing_queue.begin(), testing_queue.end(), llarp::csrng); diff --git a/llarp/constants/time.hpp b/llarp/constants/time.hpp index 69866e2c8..79072418e 100644 --- a/llarp/constants/time.hpp +++ b/llarp/constants/time.hpp @@ -5,6 +5,5 @@ namespace llarp { using namespace std::literals; - /// how big of a time skip before we reset network state - constexpr auto TimeskipDetectedDuration = 1min; + } // namespace llarp diff --git a/llarp/constants/version.cpp.in b/llarp/constants/version.cpp.in index 60d3af6ae..e2e095147 100644 --- a/llarp/constants/version.cpp.in +++ b/llarp/constants/version.cpp.in @@ -4,11 +4,11 @@ namespace llarp { // clang-format off - const std::array LOKINET_VERSION{{@lokinet_VERSION_MAJOR@, @lokinet_VERSION_MINOR@, @lokinet_VERSION_PATCH@}}; + const std::array LOKINET_VERSION{{@lokinet_VERSION_MAJOR@, @lokinet_VERSION_MINOR@, @lokinet_VERSION_PATCH@}}; const char* const LOKINET_VERSION_TAG = "@VERSIONTAG@"; const char* const LOKINET_VERSION_FULL = "lokinet-@lokinet_VERSION_MAJOR@.@lokinet_VERSION_MINOR@.@lokinet_VERSION_PATCH@-@LOKINET_VERSION_TAG@"; - const char* const LOKINET_RELEASE_MOTTO = "@RELEASE_MOTTO@"; const char* const LOKINET_DEFAULT_NETID = "lokinet"; + const char* const LOKINET_TESTNET_NETID = "testnet"; // clang-format on } // namespace llarp diff --git a/llarp/constants/version.hpp b/llarp/constants/version.hpp index f79b842e2..bd83bdde6 100644 --- a/llarp/constants/version.hpp +++ b/llarp/constants/version.hpp @@ -6,10 +6,10 @@ namespace llarp { // Given a full lokinet version of: lokinet-1.2.3-abc these are: - extern const std::array LOKINET_VERSION; + extern const std::array LOKINET_VERSION; extern const char* const LOKINET_VERSION_TAG; extern const char* const LOKINET_VERSION_FULL; - extern const char* const LOKINET_RELEASE_MOTTO; extern const char* const LOKINET_DEFAULT_NETID; + extern const char* const LOKINET_TESTNET_NETID; } // namespace llarp diff --git a/llarp/context.cpp b/llarp/context.cpp index 378a5d9d6..fb2418fe6 100644 --- a/llarp/context.cpp +++ b/llarp/context.cpp @@ -57,8 +57,7 @@ namespace llarp throw std::runtime_error("Cannot call Setup() on context without a Config"); if (opts.showBanner) - llarp::LogInfo( - fmt::format("{} {}", llarp::LOKINET_VERSION_FULL, llarp::LOKINET_RELEASE_MOTTO)); + llarp::LogInfo(fmt::format("{}", llarp::LOKINET_VERSION_FULL)); if (!loop) { @@ -67,7 +66,6 @@ namespace llarp } router = makeRouter(loop); - nodedb = makeNodeDB(); if (!router->Configure(config, opts.isSNode, nodedb)) @@ -183,7 +181,7 @@ namespace llarp { if (router) { - llarp::log::debug(logcat, "Handling SIGINT"); + llarp::log::error(logcat, "Handling SIGINT"); /// async stop router on sigint router->Stop(); } diff --git a/llarp/crypto/types.hpp b/llarp/crypto/types.hpp index e62c0476a..0092adcfb 100644 --- a/llarp/crypto/types.hpp +++ b/llarp/crypto/types.hpp @@ -32,9 +32,11 @@ namespace llarp std::string ToString() const; + // FIXME: this is deceptively named: it should be "from_hex" since that's what it does. bool FromString(const std::string& str); + // FIXME: this is deceptively named: it should be "from_hex" since that's what it does. static PubKey from_string(const std::string& s); diff --git a/llarp/exit/context.cpp b/llarp/exit/context.cpp index aa3761cc9..ec03c38e4 100644 --- a/llarp/exit/context.cpp +++ b/llarp/exit/context.cpp @@ -13,19 +13,19 @@ namespace llarp::exit Context::Tick(llarp_time_t now) { { - auto itr = m_Exits.begin(); - while (itr != m_Exits.end()) + auto itr = _exits.begin(); + while (itr != _exits.end()) { itr->second->Tick(now); ++itr; } } { - auto itr = m_Closed.begin(); - while (itr != m_Closed.end()) + auto itr = _closed.begin(); + while (itr != _closed.end()) { if ((*itr)->ShouldRemove()) - itr = m_Closed.erase(itr); + itr = _closed.erase(itr); else ++itr; } @@ -33,14 +33,14 @@ namespace llarp::exit } void - Context::Stop() + Context::stop() { - auto itr = m_Exits.begin(); - while (itr != m_Exits.end()) + auto itr = _exits.begin(); + while (itr != _exits.end()) { itr->second->Stop(); - m_Closed.emplace_back(std::move(itr->second)); - itr = m_Exits.erase(itr); + _closed.emplace_back(std::move(itr->second)); + itr = _exits.erase(itr); } } @@ -48,8 +48,8 @@ namespace llarp::exit Context::ExtractStatus() const { util::StatusObject obj{}; - auto itr = m_Exits.begin(); - while (itr != m_Exits.end()) + auto itr = _exits.begin(); + while (itr != _exits.end()) { obj[itr->first] = itr->second->ExtractStatus(); ++itr; @@ -58,10 +58,10 @@ namespace llarp::exit } void - Context::CalculateExitTraffic(TrafficStats& stats) + Context::calculate_exit_traffic(TrafficStats& stats) { - auto itr = m_Exits.begin(); - while (itr != m_Exits.end()) + auto itr = _exits.begin(); + while (itr != _exits.end()) { itr->second->CalculateTrafficStats(stats); ++itr; @@ -69,10 +69,10 @@ namespace llarp::exit } exit::Endpoint* - Context::FindEndpointForPath(const PathID_t& path) const + Context::find_endpoint_for_path(const PathID_t& path) const { - auto itr = m_Exits.begin(); - while (itr != m_Exits.end()) + auto itr = _exits.begin(); + while (itr != _exits.end()) { auto ep = itr->second->FindEndpointByPath(path); if (ep) @@ -83,10 +83,10 @@ namespace llarp::exit } bool - Context::ObtainNewExit(const PubKey& pk, const PathID_t& path, bool permitInternet) + Context::obtain_new_exit(const PubKey& pk, const PathID_t& path, bool permitInternet) { - auto itr = m_Exits.begin(); - while (itr != m_Exits.end()) + auto itr = _exits.begin(); + while (itr != _exits.end()) { if (itr->second->AllocateNewExit(pk, path, permitInternet)) return true; @@ -96,9 +96,9 @@ namespace llarp::exit } std::shared_ptr - Context::GetExitEndpoint(std::string name) const + Context::get_exit_endpoint(std::string name) const { - if (auto itr = m_Exits.find(name); itr != m_Exits.end()) + if (auto itr = _exits.find(name); itr != _exits.end()) { return itr->second; } @@ -106,10 +106,10 @@ namespace llarp::exit } void - Context::AddExitEndpoint( + Context::add_exit_endpoint( const std::string& name, const NetworkConfig& networkConfig, const DnsConfig& dnsConfig) { - if (m_Exits.find(name) != m_Exits.end()) + if (_exits.find(name) != _exits.end()) throw std::invalid_argument{fmt::format("An exit with name {} already exists", name)}; auto endpoint = std::make_unique(name, router); @@ -119,7 +119,7 @@ namespace llarp::exit if (!endpoint->Start()) throw std::runtime_error{fmt::format("Failed to start endpoint {}", name)}; - m_Exits.emplace(name, std::move(endpoint)); + _exits.emplace(name, std::move(endpoint)); } } // namespace llarp::exit diff --git a/llarp/exit/context.hpp b/llarp/exit/context.hpp index 31a9e766f..6e52da3bc 100644 --- a/llarp/exit/context.hpp +++ b/llarp/exit/context.hpp @@ -18,37 +18,37 @@ namespace llarp::exit Tick(llarp_time_t now); void - ClearAllEndpoints(); + clear_all_endpoints(); util::StatusObject ExtractStatus() const; /// send close to all exit sessions and remove all sessions void - Stop(); + stop(); void - AddExitEndpoint( + add_exit_endpoint( const std::string& name, const NetworkConfig& networkConfig, const DnsConfig& dnsConfig); bool - ObtainNewExit(const PubKey& remote, const PathID_t& path, bool permitInternet); + obtain_new_exit(const PubKey& remote, const PathID_t& path, bool permitInternet); exit::Endpoint* - FindEndpointForPath(const PathID_t& path) const; + find_endpoint_for_path(const PathID_t& path) const; /// calculate (pk, tx, rx) for all exit traffic using TrafficStats = std::unordered_map>; void - CalculateExitTraffic(TrafficStats& stats); + calculate_exit_traffic(TrafficStats& stats); std::shared_ptr - GetExitEndpoint(std::string name) const; + get_exit_endpoint(std::string name) const; private: Router* router; - std::unordered_map> m_Exits; - std::list> m_Closed; + std::unordered_map> _exits; + std::list> _closed; }; } // namespace llarp::exit diff --git a/llarp/exit/session.cpp b/llarp/exit/session.cpp index 82dd4d44b..2802fd9ca 100644 --- a/llarp/exit/session.cpp +++ b/llarp/exit/session.cpp @@ -214,7 +214,7 @@ namespace llarp::exit BaseSession::HandleTrafficDrop(llarp::path::Path_ptr p, const PathID_t& path, uint64_t s) { llarp::LogError("dropped traffic on exit ", exit_router, " S=", s, " P=", path); - p->EnterState(path::ePathIgnore, router->now()); + p->EnterState(path::IGNORE, router->now()); return true; } @@ -238,7 +238,7 @@ namespace llarp::exit { if (BuildCooldownHit(now)) return false; - if (IsReady() and NumInStatus(path::ePathBuilding) < numDesiredPaths) + if (IsReady() and NumInStatus(path::BUILDING) < numDesiredPaths) return path::Builder::UrgentBuild(now); return false; } @@ -246,38 +246,38 @@ namespace llarp::exit bool BaseSession::FlushUpstream() { - auto now = router->now(); - auto path = PickEstablishedPath(llarp::path::ePathRoleExit); - if (path) - { - // for (auto& [i, queue] : m_Upstream) - // { - // while (queue.size()) - // { - // auto& msg = queue.front(); - // msg.sequence_number = path->NextSeqNo(); - // path->SendRoutingMessage(msg, router); - // queue.pop_front(); - // } - // } - } - else - { - // if (m_Upstream.size()) - // llarp::LogWarn("no path for exit session"); - // // discard upstream - // for (auto& [i, queue] : m_Upstream) - // queue.clear(); - // m_Upstream.clear(); - - if (numHops == 1) - { - if (const auto maybe = router->node_db()->get_rc(exit_router); maybe.has_value()) - router->connect_to(*maybe); - } - else if (UrgentBuild(now)) - BuildOneAlignedTo(exit_router); - } + // auto now = router->now(); + // auto path = PickEstablishedPath(llarp::path::ePathRoleExit); + // if (path) + // { + // for (auto& [i, queue] : m_Upstream) + // { + // while (queue.size()) + // { + // auto& msg = queue.front(); + // msg.sequence_number = path->NextSeqNo(); + // path->SendRoutingMessage(msg, router); + // queue.pop_front(); + // } + // } + // } + // else + // { + // if (m_Upstream.size()) + // llarp::LogWarn("no path for exit session"); + // // discard upstream + // for (auto& [i, queue] : m_Upstream) + // queue.clear(); + // m_Upstream.clear(); + + // if (numHops == 1) + // { + // if (const auto maybe = router->node_db()->get_rc(exit_router); maybe.has_value()) + // router->connect_to(*maybe); + // } + // else if (UrgentBuild(now)) + // BuildOneAlignedTo(exit_router); + // } return true; } diff --git a/llarp/handlers/exit.cpp b/llarp/handlers/exit.cpp index ffab0a37c..54555ba13 100644 --- a/llarp/handlers/exit.cpp +++ b/llarp/handlers/exit.cpp @@ -281,7 +281,7 @@ namespace llarp::handlers } // forward dns for snode RouterID r; - if (r.FromString(msg.questions[0].Name())) + if (r.from_string(msg.questions[0].Name())) { huint128_t ip; PubKey pubKey(r); diff --git a/llarp/handlers/tun.cpp b/llarp/handlers/tun.cpp index 15db0ebc7..6e1cc705b 100644 --- a/llarp/handlers/tun.cpp +++ b/llarp/handlers/tun.cpp @@ -535,7 +535,7 @@ namespace llarp::handlers if (auto saddr = service::Address(); saddr.FromString(name)) ReplyToLokiDNSWhenReady(saddr, msg, isV6); - if (auto rid = RouterID(); rid.FromString(name)) + if (auto rid = RouterID(); rid.from_string(name)) ReplyToSNodeDNSWhenReady(rid, msg, isV6); }; @@ -568,7 +568,7 @@ namespace llarp::handlers if (not qname) return false; RouterID addr; - if (not addr.FromString(*qname)) + if (not addr.from_string(*qname)) return false; auto replyMsg = std::make_shared(clear_dns_message(msg)); return ReplyToSNodeDNSWhenReady(addr, std::move(replyMsg), false); @@ -604,7 +604,7 @@ namespace llarp::handlers if (msg.questions[0].qtype == dns::qTypeTXT) { RouterID snode; - if (snode.FromString(qname)) + if (snode.from_string(qname)) { if (auto rc = router()->node_db()->get_rc(snode)) msg.AddTXTReply(std::string{rc->view()}); diff --git a/llarp/link/connection.cpp b/llarp/link/connection.cpp index dace3d299..27e3935a2 100644 --- a/llarp/link/connection.cpp +++ b/llarp/link/connection.cpp @@ -3,10 +3,10 @@ namespace llarp::link { Connection::Connection( - std::shared_ptr& c, - std::shared_ptr& s, - const RemoteRC& rc) - : conn{c}, control_stream{s}, remote_rc{std::move(rc)} + std::shared_ptr c, + std::shared_ptr s, + bool is_relay) + : conn{std::move(c)}, control_stream{std::move(s)}, remote_is_relay{is_relay} {} } // namespace llarp::link diff --git a/llarp/link/connection.hpp b/llarp/link/connection.hpp index 46aef9328..097303432 100644 --- a/llarp/link/connection.hpp +++ b/llarp/link/connection.hpp @@ -11,16 +11,19 @@ namespace llarp::link { std::shared_ptr conn; std::shared_ptr control_stream; - RemoteRC remote_rc; - // one side of a connection will be responsible for some things, e.g. heartbeat - bool inbound{false}; bool remote_is_relay{true}; + bool + is_inbound() const + { + return conn->is_inbound(); + } + Connection( - std::shared_ptr& c, - std::shared_ptr& s, - const RemoteRC& rc); + std::shared_ptr c, + std::shared_ptr s, + bool is_relay = true); }; } // namespace llarp::link diff --git a/llarp/link/contacts.cpp b/llarp/link/contacts.cpp index a31a231db..ffb78ad10 100644 --- a/llarp/link/contacts.cpp +++ b/llarp/link/contacts.cpp @@ -5,82 +5,38 @@ namespace llarp { - Contacts::Contacts(const dht::Key_t& k, Router& r) : _local_key{k}, _router{r} + Contacts::Contacts(Router& r) : _router{r}, _local_key{r.pubkey()} { timer_keepalive = std::make_shared(0); - _router.loop()->call_every(1s, timer_keepalive, [this]() { on_clean_contacts(); }); - _rc_nodes = std::make_unique>(_local_key, llarp::randint); _introset_nodes = std::make_unique>(_local_key, llarp::randint); } std::optional Contacts::get_introset_by_location(const dht::Key_t& key) const { - return _router.loop()->call_get([this, key]() -> std::optional { - auto& introsets = _introset_nodes->nodes; + std::optional enc = std::nullopt; - if (auto itr = introsets.find(key); itr != introsets.end()) - return itr->second.introset; + auto& introsets = _introset_nodes->nodes; - return std::nullopt; - }); - } - - void - Contacts::on_clean_contacts() - { - const auto now = llarp::time_now_ms(); - - if (_rc_nodes) - { - auto& nodes = _rc_nodes->nodes; - auto itr = nodes.begin(); - - while (itr != nodes.end()) - { - if (itr->second.rc.is_expired(now)) - itr = nodes.erase(itr); - else - ++itr; - } - } + if (auto itr = introsets.find(key); itr != introsets.end()) + enc = itr->second.introset; - if (_introset_nodes) - { - auto& svcs = _introset_nodes->nodes; - auto itr = svcs.begin(); - - while (itr != svcs.end()) - { - if (itr->second.introset.IsExpired(now)) - itr = svcs.erase(itr); - else - ++itr; - } - } + return enc; } util::StatusObject Contacts::ExtractStatus() const { util::StatusObject obj{ - {"nodes", _rc_nodes->ExtractStatus()}, - {"services", _introset_nodes->ExtractStatus()}, - {"local_key", _local_key.ToHex()}}; + {"services", _introset_nodes->ExtractStatus()}, {"local_key", _local_key.ToHex()}}; return obj; } void - Contacts::put_rc_node_async(const dht::RCNode& val) - { - _router.loop()->call([this, val]() { _rc_nodes->PutNode(val); }); - } - - void - Contacts::delete_rc_node_async(const dht::Key_t& val) + Contacts::put_intro(service::EncryptedIntroSet enc) { - _router.loop()->call([this, val]() { _rc_nodes->DelNode(val); }); + _introset_nodes->PutNode(std::move(enc)); } } // namespace llarp diff --git a/llarp/link/contacts.hpp b/llarp/link/contacts.hpp index 8423a346f..37056cbbc 100644 --- a/llarp/link/contacts.hpp +++ b/llarp/link/contacts.hpp @@ -15,28 +15,14 @@ namespace llarp private: // TODO: why was this a shared ptr in the original implementation? revisit this std::shared_ptr timer_keepalive; - const dht::Key_t& _local_key; Router& _router; - std::atomic transit_allowed{false}; + const dht::Key_t _local_key; - // holds router contacts - std::unique_ptr> _rc_nodes; // holds introsets for remote services std::unique_ptr> _introset_nodes; public: - Contacts(const dht::Key_t& local, Router& r); - - /// Sets the value of transit_allowed to the value of `b`. Returns false if the - /// value was already b, true otherwise - bool - set_transit_allowed(bool b) - { - return not transit_allowed.exchange(b) == b; - } - - void - on_clean_contacts(); + Contacts(Router& r); std::optional get_introset_by_location(const dht::Key_t& key) const; @@ -46,16 +32,7 @@ namespace llarp ExtractStatus() const; void - put_rc_node_async(const dht::RCNode& val); - - void - delete_rc_node_async(const dht::Key_t& val); - - dht::Bucket* - rc_nodes() const - { - return _rc_nodes.get(); - } + put_intro(service::EncryptedIntroSet enc); dht::Bucket* services() const diff --git a/llarp/link/link_manager.cpp b/llarp/link/link_manager.cpp index 77f699740..a1af33d64 100644 --- a/llarp/link/link_manager.cpp +++ b/llarp/link/link_manager.cpp @@ -5,9 +5,8 @@ #include #include +#include #include -#include -#include #include #include #include @@ -22,10 +21,16 @@ namespace llarp { namespace link { + Endpoint::Endpoint(std::shared_ptr ep, LinkManager& lm) + : endpoint{std::move(ep)} + , link_manager{lm} + , _is_service_node{link_manager.is_service_node()} + {} + std::shared_ptr - Endpoint::get_conn(const RemoteRC& rc) const + Endpoint::get_service_conn(const RouterID& rid) const { - if (auto itr = conns.find(rc.router_id()); itr != conns.end()) + if (auto itr = service_conns.find(rid); itr != service_conns.end()) return itr->second; return nullptr; @@ -34,98 +39,108 @@ namespace llarp std::shared_ptr Endpoint::get_conn(const RouterID& rid) const { - if (auto itr = conns.find(rid); itr != conns.end()) + if (auto itr = service_conns.find(rid); itr != service_conns.end()) return itr->second; + if (_is_service_node) + { + if (auto itr = client_conns.find(rid); itr != client_conns.end()) + return itr->second; + } + return nullptr; } bool - Endpoint::have_conn(const RouterID& remote, bool client_only) const + Endpoint::have_conn(const RouterID& remote) const { - if (auto itr = conns.find(remote); itr != conns.end()) - { - if (not(itr->second->remote_is_relay and client_only)) - return true; - } + return have_service_conn(remote) or have_client_conn(remote); + } - return false; + bool + Endpoint::have_client_conn(const RouterID& remote) const + { + return client_conns.count(remote); } bool - Endpoint::deregister_peer(RouterID _rid) + Endpoint::have_service_conn(const RouterID& remote) const { - if (auto itr = conns.find(_rid); itr != conns.end()) - { - auto& c = itr->second; - auto& _scid = c->conn->scid(); + return service_conns.count(remote); + } - link_manager._router.loop()->call([this, scid = _scid, rid = _rid]() { - endpoint->close_connection(scid); + std::pair + Endpoint::num_in_out() const + { + size_t in{0}, out{0}; - conns.erase(rid); - connid_map.erase(scid); - }); + for (const auto& c : service_conns) + { + if (c.second->is_inbound()) + ++in; + else + ++out; + } - return true; + for (const auto& c : client_conns) + { + if (c.second->is_inbound()) + ++in; + else + ++out; } - return false; + return {in, out}; } size_t - Endpoint::num_connected(bool clients_only) const + Endpoint::num_client_conns() const { - size_t count = 0; - - for (const auto& c : conns) - { - if (not(c.second->remote_is_relay and clients_only)) - count += 1; - } - - return count; + return client_conns.size(); } - bool - Endpoint::get_random_connection(RemoteRC& router) const + size_t + Endpoint::num_router_conns() const { - if (const auto size = conns.size(); size) - { - auto itr = conns.begin(); - std::advance(itr, randint() % size); - router = itr->second->remote_rc; - return true; - } - - log::warning(quic_cat, "Error: failed to fetch random connection"); - return false; + return service_conns.size(); } void Endpoint::for_each_connection(std::function func) { - for (const auto& [rid, conn] : conns) + for (const auto& [rid, conn] : service_conns) func(*conn); + + if (_is_service_node) + { + for (const auto& [rid, conn] : client_conns) + func(*conn); + } } void Endpoint::close_connection(RouterID _rid) { - if (auto itr = conns.find(_rid); itr != conns.end()) - { - auto& c = itr->second; - auto& _scid = c->conn->scid(); - - link_manager._router.loop()->call([this, scid = _scid, rid = _rid]() { - endpoint->close_connection(scid); - - conns.erase(rid); - connid_map.erase(scid); - }); - } + link_manager._router.loop()->call([this, rid = _rid]() { + if (auto itr = service_conns.find(rid); itr != service_conns.end()) + { + log::critical(logcat, "Closing connection to relay RID:{}", rid); + auto& conn = *itr->second->conn; + conn.close_connection(); + } + else if (_is_service_node) + { + if (auto itr = client_conns.find(rid); itr != client_conns.end()) + { + log::critical(logcat, "Closing connection to client RID:{}", rid); + auto& conn = *itr->second->conn; + conn.close_connection(); + } + } + else + log::critical(logcat, "Could not find connection to RID:{} to close!", rid); + }); } - } // namespace link using messages::serialize_response; @@ -140,39 +155,74 @@ namespace llarp } void - LinkManager::register_commands(std::shared_ptr& s) + LinkManager::register_commands( + std::shared_ptr& s, const RouterID& router_id, bool) { - assert(ep.connid_map.count(s->conn_id())); - const RouterID& rid = ep.connid_map[s->conn_id()]; + log::debug(logcat, "{} called", __PRETTY_FUNCTION__); + + s->register_handler("bfetch_rcs"s, [this](oxen::quic::message m) { + _router.loop()->call( + [this, msg = std::move(m)]() mutable { handle_fetch_bootstrap_rcs(std::move(msg)); }); + }); + + s->register_handler("fetch_rcs"s, [this](oxen::quic::message m) { + _router.loop()->call( + [this, msg = std::move(m)]() mutable { handle_fetch_rcs(std::move(msg)); }); + }); + + s->register_handler("fetch_rids"s, [this](oxen::quic::message m) { + _router.loop()->call( + [this, msg = std::move(m)]() mutable { handle_fetch_router_ids(std::move(msg)); }); + }); - s->register_command("path_build"s, [this, rid](oxen::quic::message m) { + s->register_handler("path_build"s, [this, rid = router_id](oxen::quic::message m) { _router.loop()->call( [this, &rid, msg = std::move(m)]() mutable { handle_path_build(std::move(msg), rid); }); }); - s->register_command("path_control"s, [this, rid](oxen::quic::message m) { + s->register_handler("path_control"s, [this, rid = router_id](oxen::quic::message m) { _router.loop()->call( [this, &rid, msg = std::move(m)]() mutable { handle_path_control(std::move(msg), rid); }); }); - s->register_command("gossip_rc"s, [this, rid](oxen::quic::message m) { + s->register_handler("gossip_rc"s, [this](oxen::quic::message m) { _router.loop()->call( [this, msg = std::move(m)]() mutable { handle_gossip_rc(std::move(msg)); }); }); for (auto& method : direct_requests) { - s->register_command( - std::string{method.first}, [this, func = method.second](oxen::quic::message m) { + s->register_handler( + std::string{method.first}, + [this, func = std::move(method.second)](oxen::quic::message m) { _router.loop()->call([this, msg = std::move(m), func = std::move(func)]() mutable { auto body = msg.body_str(); auto respond = [m = std::move(msg)](std::string response) mutable { - m.respond(std::move(response)); + m.respond(std::move(response), m.is_error()); }; std::invoke(func, this, body, std::move(respond)); }); }); } + + log::critical(logcat, "Registered all commands! (RID:{})", router_id); + } + + LinkManager::LinkManager(Router& r) + : _router{r} + , _is_service_node{_router.is_service_node()} + , quic{std::make_unique()} + , tls_creds{oxen::quic::GNUTLSCreds::make_from_ed_keys( + {reinterpret_cast(_router.identity().data()), size_t{32}}, + {reinterpret_cast(_router.identity().toPublic().data()), size_t{32}})} + , ep{startup_endpoint(), *this} + {} + + std::unique_ptr + LinkManager::make(Router& r) + { + std::unique_ptr p{new LinkManager(r)}; + return p; } std::shared_ptr @@ -184,68 +234,206 @@ namespace llarp - connection close callback - stream constructor callback - will return a BTRequestStream on the first call to get_new_stream + - bt stream construction contains a stream close callback that shuts down the connection + if the btstream closes unexpectedly */ - auto ep = quic->endpoint( - _router.public_ip(), + auto e = quic->endpoint( + _router.listen_addr(), [this](oxen::quic::connection_interface& ci) { return on_conn_open(ci); }, [this](oxen::quic::connection_interface& ci, uint64_t ec) { return on_conn_closed(ci, ec); }, - [this](oxen::quic::dgram_interface& di, bstring dgram) { recv_data_message(di, dgram); }); - ep->listen( - tls_creds, - [&](oxen::quic::Connection& c, - oxen::quic::Endpoint& e, - std::optional id) -> std::shared_ptr { - if (id && id == 0) - { - auto s = std::make_shared(c, e); - register_commands(s); - return s; - } - return std::make_shared(c, e); + [this](oxen::quic::dgram_interface& di, bstring dgram) { recv_data_message(di, dgram); }, + is_service_node() ? alpns::SERVICE_INBOUND : alpns::CLIENT_INBOUND, + is_service_node() ? alpns::SERVICE_OUTBOUND : alpns::CLIENT_OUTBOUND); + + // While only service nodes accept inbound connections, clients must have this key verify + // callback set. It will reject any attempted inbound connection to a lokinet client prior to + // handshake completion + tls_creds->set_key_verify_callback([this](const ustring_view& key, const ustring_view& alpn) { + RouterID other{key.data()}; + auto us = router().is_bootstrap_seed() ? "Bootstrap seed node"s : "Service node"s; + auto is_snode = is_service_node(); + + if (is_snode) + { + if (alpn == alpns::C_ALPNS) + { + log::critical(logcat, "{} node accepting client connection (remote ID:{})!", us, other); + ep.client_conns.emplace(other, nullptr); + return true; + } + if (alpn == alpns::SN_ALPNS) + { + // verify as service node! + bool result = node_db->registered_routers().count(other); + + log::critical( + logcat, + "{} node was {} to confirm remote (RID:{}) is registered; {} connection!", + us, + result ? "able" : "unable", + other, + result ? "allowing" : "rejecting"); + + if (result) + ep.service_conns.emplace(other, nullptr); + + return result; + } + + log::critical(logcat, "{} node received unknown ALPN; rejecting connection!", us); + return false; + } + + // TESTNET: change this to an error message later; just because someone tries to erroneously + // connect to a local lokinet client doesn't mean we should kill the program? + throw std::runtime_error{"Clients should not be validating inbound connections!"}; + }); + if (_router.is_service_node()) + { + e->listen(tls_creds); + } + return e; + } + + std::shared_ptr + LinkManager::make_control(oxen::quic::connection_interface& ci, const RouterID& rid) + { + auto control_stream = ci.queue_incoming_stream( + [this, rid = rid](oxen::quic::Stream&, uint64_t error_code) { + log::warning( + logcat, + "BTRequestStream closed unexpectedly (ec:{}); closing inbound connection...", + error_code); + ep.close_connection(rid); }); - return ep; + + log::critical(logcat, "Queued BTStream to be opened (ID:{})", control_stream->stream_id()); + assert(control_stream->stream_id() == 0); + register_commands(control_stream, rid); + + return control_stream; } - LinkManager::LinkManager(Router& r) - : _router{r} - , quic{std::make_unique()} - , tls_creds{oxen::quic::GNUTLSCreds::make_from_ed_keys( - {reinterpret_cast(_router.identity().data()), size_t{32}}, - {reinterpret_cast(_router.identity().toPublic().data()), size_t{32}})} - , ep{startup_endpoint(), *this} - {} + void + LinkManager::on_inbound_conn(oxen::quic::connection_interface& ci) + { + assert(_is_service_node); + RouterID rid{ci.remote_key()}; - bool - LinkManager::send_control_message( - const RouterID& remote, - std::string endpoint, - std::string body, - std::function func) + if (auto it = ep.service_conns.find(rid); it != ep.service_conns.end()) + { + log::critical(logcat, "Configuring inbound connection from relay RID:{}", rid); + it->second = std::make_shared(ci.shared_from_this(), make_control(ci, rid)); + } + else if (auto it = ep.client_conns.find(rid); it != ep.client_conns.end()) + { + log::critical(logcat, "Configuring inbound connection from client RID:{}", rid); + it->second = + std::make_shared(ci.shared_from_this(), make_control(ci, rid), false); + } + else + { + log::critical( + logcat, + "ERROR: connection accepted from RID:{} that was not logged in key verification!", + rid); + } + + log::critical(logcat, "Successfully configured inbound connection fom {}...", rid); + } + + void + LinkManager::on_outbound_conn(oxen::quic::connection_interface& ci) { - assert(func); // makes no sense to send control message and ignore response + RouterID rid{ci.remote_key()}; - if (func) + if (auto it = ep.service_conns.find(rid); it != ep.service_conns.end()) { - func = [this, f = std::move(func)](oxen::quic::message m) mutable { - _router.loop()->call([func = std::move(f), msg = std::move(m)]() mutable { func(msg); }); - }; + log::critical(logcat, "Fetched configured outbound connection to relay RID:{}", rid); } + else + { + log::critical( + logcat, + "ERROR: connection established to RID:{} that was not logged in key verifrication!", + rid); + } + } + + // TODO: should we add routes here now that Router::SessionOpen is gone? + void + LinkManager::on_conn_open(oxen::quic::connection_interface& ci) + { + _router.loop()->call([this, &conn_interface = ci, is_snode = _is_service_node]() { + const auto rid = RouterID{conn_interface.remote_key()}; + const auto& remote = conn_interface.remote(); + + log::critical( + logcat, + "{} (RID:{}) ESTABLISHED CONNECTION TO RID:{}", + is_snode ? "SERVICE NODE" : "CLIENT", + _router.local_rid(), + rid); + + if (conn_interface.is_inbound()) + { + log::critical(logcat, "Inbound connection from {} (remote:{})", rid, remote); + on_inbound_conn(conn_interface); + } + else + { + log::critical(logcat, "Outbound connection from {} (remote:{})", rid, remote); + on_outbound_conn(conn_interface); + } + }); + }; + + void + LinkManager::on_conn_closed(oxen::quic::connection_interface& ci, uint64_t ec) + { + _router.loop()->call( + [this, ref_id = ci.reference_id(), rid = RouterID{ci.remote_key()}, error_code = ec]() { + log::critical(quic_cat, "Purging quic connection {} (ec:{})", ref_id, error_code); - return send_control_message_impl(remote, std::move(endpoint), std::move(body), std::move(func)); + if (auto s_itr = ep.service_conns.find(rid); s_itr != ep.service_conns.end()) + { + log::critical(quic_cat, "Quic connection to relay RID:{} purged successfully", rid); + ep.service_conns.erase(s_itr); + } + else if (auto c_itr = ep.client_conns.find(rid); c_itr != ep.client_conns.end()) + { + log::critical(quic_cat, "Quic connection to client RID:{} purged successfully", rid); + ep.client_conns.erase(c_itr); + } + else + log::critical(quic_cat, "Nothing to purge for quic connection {}", ref_id); + }); } bool - LinkManager::send_control_message_impl( + LinkManager::send_control_message( const RouterID& remote, std::string endpoint, std::string body, std::function func) { + // DISCUSS: revisit if this assert makes sense. If so, there's no need to if (func) the + // next logic block + assert(func); // makes no sense to send control message and ignore response (maybe gossip?) + if (is_stopping) return false; + if (func) + { + func = [this, f = std::move(func)](oxen::quic::message m) mutable { + _router.loop()->call( + [func = std::move(f), msg = std::move(m)]() mutable { func(std::move(msg)); }); + }; + } + if (auto conn = ep.get_conn(remote); conn) { conn->control_stream->command(std::move(endpoint), std::move(body), std::move(func)); @@ -257,12 +445,7 @@ namespace llarp endpoint = std::move(endpoint), body = std::move(body), f = std::move(func)]() { - auto pending = PendingControlMessage(std::move(body), std::move(endpoint), f); - - auto [itr, b] = pending_conn_msg_queue.emplace(remote, MessageQueue()); - itr->second.push_back(std::move(pending)); - - connect_to(remote); + connect_and_send(remote, std::move(endpoint), std::move(body), std::move(f)); }); return false; @@ -274,19 +457,14 @@ namespace llarp if (is_stopping) return false; - if (auto conn = ep.get_conn(remote); conn) + if (auto conn = ep.get_service_conn(remote); conn) { conn->conn->send_datagram(std::move(body)); return true; } _router.loop()->call([this, body = std::move(body), remote]() { - auto pending = PendingDataMessage(body); - - auto [itr, b] = pending_conn_msg_queue.emplace(remote, MessageQueue()); - itr->second.push_back(std::move(pending)); - - connect_to(remote); + connect_and_send(remote, std::nullopt, std::move(body)); }); return false; @@ -299,23 +477,57 @@ namespace llarp } void - LinkManager::connect_to(const RouterID& rid) + LinkManager::test_reachability( + const RouterID& rid, conn_open_hook on_open, conn_closed_hook on_close) { - auto rc = node_db->get_rc(rid); - if (rc) + if (auto rc = node_db->get_rc(rid)) { - connect_to(*rc); + connect_to(*rc, std::move(on_open), std::move(on_close)); + } + else + log::warning(quic_cat, "Could not find RouterContact for connection to rid:{}", rid); + } + + void + LinkManager::connect_and_send( + const RouterID& router, + std::optional endpoint, + std::string body, + std::function func) + { + // by the time we have called this, we have already checked if we have a connection to this RID + // in ::send_control_message_impl, at which point we will dispatch on that stream + if (auto rc = node_db->get_rc(router)) + { + const auto& remote_addr = rc->addr(); + + if (auto rv = ep.establish_and_send( + oxen::quic::RemoteAddress{router.ToView(), remote_addr}, + *rc, + std::move(endpoint), + std::move(body), + std::move(func)); + rv) + { + log::info(quic_cat, "Begun establishing connection to {}", remote_addr); + return; + } + + log::warning(quic_cat, "Failed to begin establishing connection to {}", remote_addr); } else - log::warning(quic_cat, "Do something intelligent here for error handling"); + log::error( + quic_cat, "Error: Could not find RC for connection to rid:{}, message not sent!", router); } - // This function assumes the RC has already had its signature verified and connection is allowed. void - LinkManager::connect_to(const RemoteRC& rc) + LinkManager::connect_to(const RemoteRC& rc, conn_open_hook on_open, conn_closed_hook on_close) { - if (auto conn = ep.get_conn(rc.router_id()); conn) + const auto& rid = rc.router_id(); + + if (ep.have_service_conn(rid)) { + log::warning(logcat, "We already have a connection to {}!", rid); // TODO: should implement some connection failed logic, but not the same logic that // would be executed for another failure case return; @@ -323,462 +535,446 @@ namespace llarp const auto& remote_addr = rc.addr(); - // TODO: confirm remote end is using the expected pubkey (RouterID). - // TODO: ALPN for "client" vs "relay" (could just be set on endpoint creation) if (auto rv = ep.establish_connection( - oxen::quic::RemoteAddress{rc.router_id().ToView(), remote_addr}, rc); + oxen::quic::RemoteAddress{rid.ToView(), remote_addr}, + rc, + std::move(on_open), + std::move(on_close)); rv) { - log::info(quic_cat, "Connection to {} successfully established!", remote_addr); + log::info(quic_cat, "Begun establishing connection to {}", remote_addr); return; } - log::warning(quic_cat, "Connection to {} successfully established!", remote_addr); + log::warning(quic_cat, "Failed to begin establishing connection to {}", remote_addr); + } + + bool + LinkManager::have_connection_to(const RouterID& remote) const + { + return ep.have_conn(remote); + } + + bool + LinkManager::have_service_connection_to(const RouterID& remote) const + { + return ep.have_service_conn(remote); + } + + bool + LinkManager::have_client_connection_to(const RouterID& remote) const + { + return ep.have_client_conn(remote); } - // TODO: should we add routes here now that Router::SessionOpen is gone? void - LinkManager::on_conn_open(oxen::quic::connection_interface& ci) + LinkManager::stop() { - _router.loop()->call([this, &conn_interface = ci]() { - const auto& scid = conn_interface.scid(); - const auto& rid = ep.connid_map[scid]; + if (is_stopping) + { + return; + } - // check to see if this connection was established while we were attempting to queue - // messages to the remote - if (auto itr = pending_conn_msg_queue.find(rid); itr != pending_conn_msg_queue.end()) - { - auto& que = itr->second; + LogInfo("stopping links"); + is_stopping = true; - while (not que.empty()) - { - auto& m = que.front(); + quic.reset(); + } - if (m.is_control) - { - auto& msg = reinterpret_cast(m); - ep.conns[rid]->control_stream->command(msg.endpoint, msg.body, msg.func); - } - else - { - auto& msg = reinterpret_cast(m); - conn_interface.send_datagram(std::move(msg.body)); - } + void + LinkManager::set_conn_persist(const RouterID& remote, llarp_time_t until) + { + if (is_stopping) + return; - que.pop_front(); - } - } - }); - }; + persisting_conns[remote] = std::max(until, persisting_conns[remote]); + if (have_client_connection_to(remote)) + { + // mark this as a client so we don't try to back connect + clients.Upsert(remote); + } + } + + std::pair + LinkManager::num_in_out() const + { + return ep.num_in_out(); + } + size_t + LinkManager::get_num_connected_routers() const + { + return ep.num_router_conns(); + } + + size_t + LinkManager::get_num_connected_clients() const + { + return ep.num_client_conns(); + } + + bool + LinkManager::is_service_node() const + { + return _is_service_node; + } + + // TODO: this? perhaps no longer necessary in the same way? void - LinkManager::on_conn_closed(oxen::quic::connection_interface& ci, uint64_t ec) + LinkManager::check_persisting_conns(llarp_time_t) { - _router.loop()->call([this, &conn_interface = ci, error_code = ec]() { - const auto& scid = conn_interface.scid(); + if (is_stopping) + return; + } - log::debug(quic_cat, "Purging quic connection CID:{} (ec: {})", scid, error_code); + // TODO: this + util::StatusObject + LinkManager::extract_status() const + { + return {}; + } - if (const auto& c_itr = ep.connid_map.find(scid); c_itr != ep.connid_map.end()) - { - const auto& rid = c_itr->second; + void + LinkManager::init() + { + is_stopping = false; + node_db = _router.node_db(); + } - if (auto p_itr = pending_conn_msg_queue.find(rid); p_itr != pending_conn_msg_queue.end()) - pending_conn_msg_queue.erase(p_itr); + void + LinkManager::connect_to_random(int num_conns, bool client_only) + { + auto filter = [this, client_only](const RemoteRC& rc) -> bool { + const auto& rid = rc.router_id(); + auto res = client_only ? not ep.have_client_conn(rid) : not ep.have_conn(rid); - if (auto m_itr = ep.conns.find(rid); m_itr != ep.conns.end()) - ep.conns.erase(m_itr); + log::debug(logcat, "RID:{} {}", rid, res ? "ACCEPTED" : "REJECTED"); - ep.connid_map.erase(c_itr); + return res; + }; - log::debug(quic_cat, "Quic connection CID:{} purged successfully", scid); - } - }); + if (auto maybe = node_db->get_n_random_rcs_conditional(num_conns, filter)) + { + std::vector& rcs = *maybe; + + for (const auto& rc : rcs) + connect_to(rc); + } + else + log::warning( + logcat, "NodeDB query for {} random RCs for connection returned none", num_conns); } void - LinkManager::gossip_rc(const RouterID& rc_rid, std::string serialized_rc) + LinkManager::recv_data_message(oxen::quic::dgram_interface&, bstring) + { + // TODO: this + } + + void + LinkManager::gossip_rc(const RouterID& last_sender, const RemoteRC& rc) { - for (auto& [rid, conn] : ep.conns) + int count = 0; + const auto& gossip_src = rc.router_id(); + + for (auto& [rid, conn] : ep.service_conns) { - // don't send back to the owner... - if (rid == rc_rid) - continue; - // don't gossip RCs to clients - if (not conn->remote_is_relay) + // don't send back to the gossip source or the last sender + if (rid == gossip_src or rid == last_sender) continue; - send_control_message(rid, "gossip_rc", serialized_rc); + send_control_message( + rid, + "gossip_rc"s, + GossipRCMessage::serialize(last_sender, rc), + [](oxen::quic::message) mutable { + log::critical(logcat, "PLACEHOLDER FOR GOSSIP RC RESPONSE HANDLER"); + }); + ++count; } + + log::critical(logcat, "Dispatched {} GossipRC requests!", count); } void LinkManager::handle_gossip_rc(oxen::quic::message m) { + log::critical(logcat, "Handling GossipRC request..."); + + // RemoteRC constructor wraps deserialization in a try/catch + RemoteRC rc; + RouterID src, sender; + try { - RemoteRC rc{m.body()}; + oxenc::bt_dict_consumer btdc{m.body()}; - if (node_db->put_rc_if_newer(rc)) - { - log::info(link_cat, "Received updated RC, forwarding to relay peers."); - gossip_rc(rc.router_id(), m.body_str()); - } - else - log::debug(link_cat, "Received known or old RC, not storing or forwarding."); + btdc.required("rc"); + rc = RemoteRC{btdc.consume_dict_data()}; + src.from_string(btdc.require("sender")); } catch (const std::exception& e) { - log::info(link_cat, "Recieved invalid RC, dropping on the floor."); + log::info(link_cat, "Exception handling GossipRC request: {}", e.what()); + return; + } + + if (node_db->verify_store_gossip_rc(rc)) + { + log::critical(link_cat, "Received updated RC, forwarding to relay peers."); + gossip_rc(_router.local_rid(), rc); } + else + log::critical(link_cat, "Received known or old RC, not storing or forwarding."); } + // TODO: can probably use ::send_control_message instead. Need to discuss the potential difference + // in calling Endpoint::get_service_conn vs Endpoint::get_conn void - LinkManager::fetch_rcs( - const RouterID& source, rc_time since, const std::vector& explicit_ids) + LinkManager::fetch_bootstrap_rcs( + const RemoteRC& source, std::string payload, std::function func) { - send_control_message( - source, - "fetch_rcs", - RCFetchMessage::serialize(since, explicit_ids), - [this, source = source](oxen::quic::message m) { - if (m.timed_out) - { - // TODO: keep track of this failure for relay quality metrics? - log::info(link_cat, "RC Fetch to {} timed out", source); - return; - } - try - { - oxenc::bt_dict_consumer btdc{m.body()}; - if (not m) - { - auto reason = btdc.require(messages::STATUS_KEY); - log::info(link_cat, "RC Fetch to {} returned error: {}", source, reason); - return; - } + func = [this, f = std::move(func)](oxen::quic::message m) mutable { + _router.loop()->call( + [func = std::move(f), msg = std::move(m)]() mutable { func(std::move(msg)); }); + }; - auto btlc = btdc.require("rcs"sv); - auto timestamp = rc_time{std::chrono::seconds{btdc.require("time"sv)}}; + const auto& rid = source.router_id(); - std::vector rcs; - while (not btlc.is_finished()) - { - // TODO: maybe make RemoteRC constructor throw a bespoke exception type - // and catch it below so we know what about parsing failed? - rcs.emplace_back(btlc.consume_dict_consumer()); - } + if (auto conn = ep.get_service_conn(rid); conn) + { + conn->control_stream->command("bfetch_rcs"s, std::move(payload), std::move(func)); + log::critical(logcat, "Dispatched bootstrap fetch request!"); + return; + } - node_db->ingest_rcs(source, std::move(rcs), timestamp); - } - catch (const std::exception& e) - { - // TODO: Inform NodeDB of failure (perhaps just a call to rotate_rc_source()) - log::info(link_cat, "Failed to parse RC Fetch response from {}: {}", source, e.what()); - return; - } - }); + _router.loop()->call([this, source, payload, f = std::move(func), rid = rid]() mutable { + connect_and_send(rid, "bfetch_rcs"s, std::move(payload), std::move(f)); + }); } void - LinkManager::handle_fetch_rcs(oxen::quic::message m) + LinkManager::handle_fetch_bootstrap_rcs(oxen::quic::message m) { // this handler should not be registered for clients assert(_router.is_service_node()); + log::critical(logcat, "Handling fetch bootstrap fetch request..."); + + std::optional remote; + size_t quantity; - const auto& rcs = node_db->get_rcs(); - const auto now = - std::chrono::time_point_cast(std::chrono::system_clock::now()); try { oxenc::bt_dict_consumer btdc{m.body()}; + if (btdc.skip_until("local")) + remote.emplace(btdc.consume_dict_data()); - btdc.required("explicit_ids"); - auto explicit_ids = btdc.consume_list>(); - auto since_time = rc_time{std::chrono::seconds{btdc.require("since")}}; - - if (explicit_ids.size() > (rcs.size() / 4)) - { - log::info( - link_cat, "Remote requested too many relay IDs (greater than 1/4 of what we have)."); - m.respond(serialize_response({{messages::STATUS_KEY, RCFetchMessage::INVALID_REQUEST}})); - return; - } - - std::unordered_set explicit_relays; - for (auto& sv : explicit_ids) - { - if (sv.size() != RouterID::SIZE) - { - m.respond(serialize_response({{messages::STATUS_KEY, RCFetchMessage::INVALID_REQUEST}})); - return; - } - explicit_relays.emplace(reinterpret_cast(sv.data())); - } - - oxenc::bt_dict_producer resp; - - { - auto rc_bt_list = resp.append_list("rcs"); - - const auto& last_time = node_db->get_last_rc_update_times(); - - // if since_time isn't epoch start, subtract a bit for buffer - if (since_time != decltype(since_time)::min()) - since_time -= 5s; - - for (const auto& [_, rc] : rcs) - { - if (last_time.at(rc.router_id()) > since_time or explicit_relays.count(rc.router_id())) - rc_bt_list.append_encoded(rc.view()); - } - } - - resp.append("time", now.time_since_epoch().count()); - - m.respond(std::move(resp).str(), false); + quantity = btdc.require("quantity"); } catch (const std::exception& e) { log::info(link_cat, "Exception handling RC Fetch request: {}", e.what()); - m.respond(messages::ERROR_RESPONSE); - } - } - - void - LinkManager::fetch_router_ids(const RouterID& source) - { - if (ep.conns.empty()) - { - log::debug(link_cat, "Not attempting to fetch Router IDs: not connected to any relays."); + m.respond(messages::ERROR_RESPONSE, true); return; } - // TODO: randomize? Also, keep track of successful responses and drop this edge - // if not many come back successfully. - RouterID edge = ep.conns.begin()->first; - send_control_message( - edge, - "fetch_router_ids"s, - RouterIDFetch::serialize(source), - [this, source = source, edge = std::move(edge)](oxen::quic::message m) { - if (not m) - { - log::info( - link_cat, - "Error fetching RouterIDs from source \"{}\" via edge \"{}\"", - source, - edge); - node_db->ingest_router_ids(edge, {}); // empty response == failure - return; - } - try - { - oxenc::bt_dict_consumer btdc{m.body()}; - btdc.required("routers"); - auto router_id_strings = btdc.consume_list>(); - btdc.require_signature("signature", [&edge](ustring_view msg, ustring_view sig) { - if (sig.size() != 64) - throw std::runtime_error{"Invalid signature: not 64 bytes"}; - if (not crypto::verify(edge, msg, sig)) - throw std::runtime_error{ - "Failed to verify signature for fetch RouterIDs response."}; - }); - std::vector router_ids; - for (const auto& s : router_id_strings) - { - if (s.size() != RouterID::SIZE) - { - log::warning(link_cat, "Got bad RouterID from edge \"{}\".", edge); - return; - } - router_ids.emplace_back(s.data()); - } - node_db->ingest_router_ids(edge, std::move(router_ids)); - return; - } - catch (const std::exception& e) - { - log::info(link_cat, "Error handling fetch RouterIDs response: {}", e.what()); - } - node_db->ingest_router_ids(edge, {}); // empty response == failure - }); - } - void - LinkManager::handle_fetch_router_ids(oxen::quic::message m) - { - try + if (remote) { - oxenc::bt_dict_consumer btdc{m.body()}; + auto is_snode = _router.is_service_node(); + auto& rid = remote->router_id(); + + if (is_snode) + { + // we already insert the + auto& registered = node_db->registered_routers(); + + if (auto itr = registered.find(rid); itr != registered.end()) + { + log::critical( + logcat, + "Bootstrap node confirmed RID:{} is registered; approving fetch request and " + "saving RC!", + rid); + node_db->verify_gossip_bfetch_rc(*remote); + } + } + } - auto source = btdc.require("source"); + auto& src = node_db->get_known_rcs(); + auto count = src.size(); - // if bad request, silently fail - if (source.size() != RouterID::SIZE) - return; + // if quantity is 0, then the service node requesting this wants all the RC's; otherwise, + // send the amount requested in the message + quantity = quantity == 0 ? count : quantity; + + auto now = llarp::time_now_ms(); + size_t i = 0; + + oxenc::bt_dict_producer btdp; - const auto source_rid = RouterID{reinterpret_cast(source.data())}; - const auto our_rid = RouterID{router().pubkey()}; + { + auto sublist = btdp.append_list("rcs"); - if (source_rid == our_rid) + if (count == 0) + log::error(logcat, "No known RCs locally to send!"); + else { - oxenc::bt_dict_producer btdp; + for (const auto& rc : src) { - auto btlp = btdp.append_list("routers"); - for (const auto& relay : node_db->whitelist()) - { - btlp.append(relay.ToView()); - } - } - btdp.append_signature("signature", [this](ustring_view to_sign) { - std::array sig; - - if (!crypto::sign(const_cast(sig.data()), _router.identity(), to_sign)) - throw std::runtime_error{"Failed to sign fetch RouterIDs response"}; + if (not rc.is_expired(now)) + sublist.append_encoded(rc.view()); - return sig; - }); - m.respond(std::move(btdp).str()); - return; + if (++i >= quantity) + break; + } } - - send_control_message( - source_rid, - "fetch_router_ids"s, - m.body_str(), - [source_rid = std::move(source_rid), - orig_mess = std::move(m)](oxen::quic::message m) mutable { - if (not m.timed_out) - orig_mess.respond(m.body_str()); - // on timeout, just silently drop (as original requester will just time out anyway) - }); - } - catch (const std::exception& e) - { - log::info(link_cat, "Error fulfilling fetch RouterIDs request: {}", e.what()); } - } - bool - LinkManager::have_connection_to(const RouterID& remote, bool client_only) const - { - return ep.have_conn(remote, client_only); + m.respond(std::move(btdp).str(), count == 0); } - bool - LinkManager::have_client_connection_to(const RouterID& remote) const + void + LinkManager::fetch_rcs( + const RouterID& source, std::string payload, std::function func) { - return ep.have_conn(remote, true); + // this handler should not be registered for service nodes + assert(not _router.is_service_node()); + + send_control_message(source, "fetch_rcs", std::move(payload), std::move(func)); } void - LinkManager::deregister_peer(RouterID remote) + LinkManager::handle_fetch_rcs(oxen::quic::message m) { - if (auto rv = ep.deregister_peer(remote); rv) + log::critical(logcat, "Handling FetchRC request..."); + // this handler should not be registered for clients + assert(_router.is_service_node()); + + std::set explicit_ids; + rc_time since_time; + + try { - persisting_conns.erase(remote); - log::info(logcat, "Peer {} successfully de-registered", remote); - } - else - log::warning(logcat, "Peer {} not found for de-registration!", remote); - } + oxenc::bt_dict_consumer btdc{m.body()}; - void - LinkManager::stop() - { - if (is_stopping) + auto btlc = btdc.require("explicit_ids"); + + while (not btlc.is_finished()) + explicit_ids.emplace(btlc.consume().data()); + + since_time = rc_time{std::chrono::seconds{btdc.require("since")}}; + } + catch (const std::exception& e) { + log::critical(link_cat, "Exception handling RC Fetch request: {}", e.what()); + m.respond(messages::ERROR_RESPONSE, true); return; } - LogInfo("stopping links"); - is_stopping = true; - - quic.reset(); - } + const auto& rcs = node_db->get_rcs(); - void - LinkManager::set_conn_persist(const RouterID& remote, llarp_time_t until) - { - if (is_stopping) - return; + oxenc::bt_dict_producer btdp; + // const auto& last_time = node_db->get_last_rc_update_times(); - persisting_conns[remote] = std::max(until, persisting_conns[remote]); - if (have_client_connection_to(remote)) { - // mark this as a client so we don't try to back connect - clients.Upsert(remote); - } - } + auto sublist = btdp.append_list("rcs"); - size_t - LinkManager::get_num_connected(bool clients_only) const - { - return ep.num_connected(clients_only); - } + // if since_time isn't epoch start, subtract a bit for buffer + if (since_time != decltype(since_time)::min()) + since_time -= 5s; - size_t - LinkManager::get_num_connected_clients() const - { - return get_num_connected(true); + // Initial fetch: give me all the RC's + if (explicit_ids.empty()) + { + log::critical(logcat, "Returning ALL locally held RCs for initial FetchRC request..."); + for (const auto& rc : rcs) + { + sublist.append_encoded(rc.view()); + } + } + else + { + int count = 0; + for (const auto& rid : explicit_ids) + { + if (auto maybe_rc = node_db->get_rc_by_rid(rid)) + { + sublist.append_encoded(maybe_rc->view()); + ++count; + } + } + log::critical(logcat, "Returning {} RCs for FetchRC request...", count); + } + } + + m.respond(std::move(btdp).str()); } - bool - LinkManager::get_random_connected(RemoteRC& router) const + void + LinkManager::fetch_router_ids( + const RouterID& via, std::string payload, std::function func) { - return ep.get_random_connection(router); + send_control_message(via, "fetch_rids"s, std::move(payload), std::move(func)); } - // TODO: this? perhaps no longer necessary in the same way? void - LinkManager::check_persisting_conns(llarp_time_t) + LinkManager::handle_fetch_router_ids(oxen::quic::message m) { - if (is_stopping) + log::critical(logcat, "Handling FetchRIDs request..."); + + RouterID source; + RouterID local = router().local_rid(); + + try + { + oxenc::bt_dict_consumer btdc{m.body()}; + source = RouterID{btdc.require("source")}; + } + catch (const std::exception& e) + { + log::critical(link_cat, "Error fulfilling FetchRIDs request: {}", e.what()); + m.respond(messages::ERROR_RESPONSE, true); return; - } + } - // TODO: this - util::StatusObject - LinkManager::extract_status() const - { - return {}; - } + // if bad request, silently fail + if (source.size() != RouterID::SIZE) + return; - void - LinkManager::init() - { - is_stopping = false; - node_db = _router.node_db(); - } + if (source != local) + { + log::critical(logcat, "Relaying FetchRID request to intended target RID:{}", source); + send_control_message( + source, + "fetch_rids"s, + m.body_str(), + [source_rid = std::move(source), original = std::move(m)](oxen::quic::message m) mutable { + original.respond(m.body_str(), m.is_error()); + }); + return; + } - void - LinkManager::connect_to_random(int num_conns) - { - std::set exclude; - auto remainder = num_conns; + oxenc::bt_dict_producer btdp; - do { - auto filter = [exclude](const auto& rc) -> bool { - return exclude.count(rc.router_id()) == 0; - }; + auto btlp = btdp.append_list("routers"); - if (auto maybe_other = node_db->GetRandom(filter)) - { - exclude.insert(maybe_other->router_id()); + const auto& known_rids = node_db->get_known_rids(); - if (not node_db->is_connection_allowed(maybe_other->router_id())) - continue; + for (const auto& rid : known_rids) + btlp.append(rid.ToView()); + } - connect_to(*maybe_other); - --remainder; - } - } while (remainder > 0); - } + btdp.append_signature("signature", [this](ustring_view to_sign) { + std::array sig; - void - LinkManager::recv_data_message(oxen::quic::dgram_interface&, bstring) - { - // TODO: this + if (!crypto::sign(const_cast(sig.data()), _router.identity(), to_sign)) + throw std::runtime_error{"Failed to sign fetch RouterIDs response"}; + + return sig; + }); + + log::critical(logcat, "Returning ALL locally held RIDs to FetchRIDs request!"); + m.respond(std::move(btdp).str()); } void @@ -796,6 +992,7 @@ namespace llarp { log::warning(link_cat, "Exception: {}", e.what()); respond(messages::ERROR_RESPONSE); + return; } _router.rpc_client()->lookup_ons_hash( @@ -814,7 +1011,7 @@ namespace llarp { if (m.timed_out) { - log::info(link_cat, "FindNameMessage timed out!"); + log::info(link_cat, "FindNameMessage request timed out!"); return; } @@ -935,7 +1132,7 @@ namespace llarp "Received PublishIntroMessage in which we are peer index {}.. storing introset", relay_order); - _router.contacts()->services()->PutNode(dht::ISNode{std::move(enc)}); + _router.contacts().put_intro(std::move(enc)); respond(serialize_response({{messages::STATUS_KEY, ""}})); } else @@ -973,7 +1170,7 @@ namespace llarp { log::info(link_cat, "Received PublishIntroMessage for {} (TXID: {}); we are candidate {}"); - _router.contacts()->services()->PutNode(dht::ISNode{std::move(enc)}); + _router.contacts().put_intro(std::move(enc)); respond(serialize_response({{messages::STATUS_KEY, ""}})); } else @@ -983,6 +1180,9 @@ namespace llarp addr); } + // DISCUSS: I feel like ::handle_publish_intro_response should be the callback that handles the + // response to a relayed publish_intro (above line 1131-ish) + void LinkManager::handle_publish_intro_response(oxen::quic::message m) { @@ -1101,7 +1301,7 @@ namespace llarp } else { - if (auto maybe_intro = _router.contacts()->get_introset_by_location(addr)) + if (auto maybe_intro = _router.contacts().get_introset_by_location(addr)) respond(serialize_response({{"INTROSET", maybe_intro->bt_encode()}})); else { @@ -1139,7 +1339,7 @@ namespace llarp if (m) { service::EncryptedIntroSet enc{payload}; - _router.contacts()->services()->PutNode(std::move(enc)); + _router.contacts().put_intro(std::move(enc)); } else { @@ -1151,7 +1351,7 @@ namespace llarp void LinkManager::handle_path_build(oxen::quic::message m, const RouterID& from) { - if (!_router.path_context().AllowingTransit()) + if (!_router.path_context().is_transit_allowed()) { log::warning(link_cat, "got path build request when not permitting transit"); m.respond(serialize_response({{messages::STATUS_KEY, PathBuildMessage::NO_TRANSIT}}), true); @@ -1169,8 +1369,8 @@ namespace llarp } oxenc::bt_dict_consumer frame_info{payload_list.front()}; - auto hash = frame_info.require("HASH"); auto frame = frame_info.require("FRAME"); + auto hash = frame_info.require("HASH"); oxenc::bt_dict_consumer hop_dict{frame}; auto hop_payload = hop_dict.require("ENCRYPTED"); @@ -1237,7 +1437,7 @@ namespace llarp hop->info.upstream.from_string(upstream); - if (_router.path_context().HasTransitHop(hop->info)) + if (_router.path_context().has_transit_hop(hop->info)) { log::warning(link_cat, "Invalid PathID; PathIDs must be unique"); m.respond(serialize_response({{messages::STATUS_KEY, PathBuildMessage::BAD_PATHID}}), true); @@ -1274,7 +1474,7 @@ namespace llarp { hop->terminal_hop = true; // we are terminal hop and everything is okay - _router.path_context().PutTransitHop(hop); + _router.path_context().put_transit_hop(hop); m.respond(messages::OK_RESPONSE, false); return; } @@ -1307,14 +1507,14 @@ namespace llarp link_cat, "Upstream returned successful path build response; giving hop info to Router, " "then relaying response"); - _router.path_context().PutTransitHop(hop); + _router.path_context().put_transit_hop(hop); } if (m.timed_out) log::info(link_cat, "Upstream timed out on path build; relaying timeout"); else log::info(link_cat, "Upstream returned path build failure; relaying response"); - m.respond(m.body_str(), not m); + m.respond(m.body_str(), m.is_error()); }); } catch (const std::exception& e) @@ -1388,33 +1588,20 @@ namespace llarp void LinkManager::handle_obtain_exit(oxen::quic::message m) { + uint64_t flag; + ustring_view pubkey, sig; + std::string_view tx_id, dict_data; + try { - uint64_t flag; - ustring_view pubkey, sig; - std::string_view tx_id; - oxenc::bt_list_consumer btlc{m.body()}; - auto dict_data = btlc.consume_dict_data(); + dict_data = btlc.consume_dict_data(); oxenc::bt_dict_consumer btdc{dict_data}; sig = to_usv(btlc.consume_string_view()); flag = btdc.require("E"); pubkey = btdc.require("I"); tx_id = btdc.require("T"); - - RouterID target{pubkey.data()}; - auto transit_hop = - _router.path_context().GetTransitHop(target, PathID_t{to_usv(tx_id).data()}); - - const auto rx_id = transit_hop->info.rxID; - - auto success = - (crypto::verify(pubkey, to_usv(dict_data), sig) - and _router.exitContext().ObtainNewExit(PubKey{pubkey.data()}, rx_id, flag != 0)); - - m.respond( - ObtainExitMessage::sign_and_serialize_response(_router.identity(), tx_id), not success); } catch (const std::exception& e) { @@ -1422,6 +1609,18 @@ namespace llarp m.respond(messages::ERROR_RESPONSE, true); throw; } + + RouterID target{pubkey.data()}; + auto transit_hop = _router.path_context().GetTransitHop(target, PathID_t{to_usv(tx_id).data()}); + + const auto rx_id = transit_hop->info.rxID; + + auto success = + (crypto::verify(pubkey, to_usv(dict_data), sig) + and _router.exitContext().obtain_new_exit(PubKey{pubkey.data()}, rx_id, flag != 0)); + + m.respond( + ObtainExitMessage::sign_and_serialize_response(_router.identity(), tx_id), not success); } void @@ -1432,67 +1631,50 @@ namespace llarp log::info(link_cat, "ObtainExitMessage timed out!"); return; } - if (m.is_error) + if (m.is_error()) { // TODO: what to do here } + std::string_view tx_id, dict_data; + ustring_view sig; + try { - std::string_view tx_id; - ustring_view sig; - oxenc::bt_list_consumer btlc{m.body()}; - auto dict_data = btlc.consume_dict_data(); + dict_data = btlc.consume_dict_data(); oxenc::bt_dict_consumer btdc{dict_data}; sig = to_usv(btlc.consume_string_view()); tx_id = btdc.require("T"); - - auto path_ptr = _router.path_context().GetPath(PathID_t{to_usv(tx_id).data()}); - - if (crypto::verify(_router.pubkey(), to_usv(dict_data), sig)) - path_ptr->enable_exit_traffic(); } catch (const std::exception& e) { log::warning(link_cat, "Exception: {}", e.what()); throw; } + + auto path_ptr = _router.path_context().get_path(PathID_t{to_usv(tx_id).data()}); + + if (crypto::verify(_router.pubkey(), to_usv(dict_data), sig)) + path_ptr->enable_exit_traffic(); } void LinkManager::handle_update_exit(oxen::quic::message m) { + std::string_view path_id, tx_id, dict_data; + ustring_view sig; + try { - std::string_view path_id, tx_id; - ustring_view sig; - oxenc::bt_list_consumer btlc{m.body()}; - auto dict_data = btlc.consume_dict_data(); + dict_data = btlc.consume_dict_data(); oxenc::bt_dict_consumer btdc{dict_data}; sig = to_usv(btlc.consume_string_view()); path_id = btdc.require("P"); tx_id = btdc.require("T"); - - auto transit_hop = - _router.path_context().GetTransitHop(_router.pubkey(), PathID_t{to_usv(tx_id).data()}); - - if (auto exit_ep = - _router.exitContext().FindEndpointForPath(PathID_t{to_usv(path_id).data()})) - { - if (crypto::verify(exit_ep->PubKey().data(), to_usv(dict_data), sig)) - { - (exit_ep->UpdateLocalPath(transit_hop->info.rxID)) - ? m.respond(UpdateExitMessage::sign_and_serialize_response(_router.identity(), tx_id)) - : m.respond( - serialize_response({{messages::STATUS_KEY, UpdateExitMessage::UPDATE_FAILED}}), - true); - } - // If we fail to verify the message, no-op - } } catch (const std::exception& e) { @@ -1500,6 +1682,23 @@ namespace llarp m.respond(messages::ERROR_RESPONSE, true); return; } + + auto transit_hop = + _router.path_context().GetTransitHop(_router.pubkey(), PathID_t{to_usv(tx_id).data()}); + + if (auto exit_ep = + _router.exitContext().find_endpoint_for_path(PathID_t{to_usv(path_id).data()})) + { + if (crypto::verify(exit_ep->PubKey().data(), to_usv(dict_data), sig)) + { + (exit_ep->UpdateLocalPath(transit_hop->info.rxID)) + ? m.respond(UpdateExitMessage::sign_and_serialize_response(_router.identity(), tx_id)) + : m.respond( + serialize_response({{messages::STATUS_KEY, UpdateExitMessage::UPDATE_FAILED}}), + true); + } + // If we fail to verify the message, no-op + } } void @@ -1510,74 +1709,58 @@ namespace llarp log::info(link_cat, "UpdateExitMessage timed out!"); return; } - if (m.is_error) + if (m.is_error()) { // TODO: what to do here } + std::string tx_id; + std::string_view dict_data; + ustring_view sig; + try { - std::string tx_id; - ustring_view sig; - oxenc::bt_list_consumer btlc{m.body()}; - auto dict_data = btlc.consume_dict_data(); + dict_data = btlc.consume_dict_data(); oxenc::bt_dict_consumer btdc{dict_data}; sig = to_usv(btlc.consume_string_view()); tx_id = btdc.require("T"); - - auto path_ptr = _router.path_context().GetPath(PathID_t{to_usv(tx_id).data()}); - - if (crypto::verify(_router.pubkey(), to_usv(dict_data), sig)) - { - if (path_ptr->update_exit(std::stoul(tx_id))) - { - // TODO: talk to tom and Jason about how this stupid shit was a no-op originally - // see Path::HandleUpdateExitVerifyMessage - } - else - {} - } } catch (const std::exception& e) { log::warning(link_cat, "Exception: {}", e.what()); return; } + + auto path_ptr = _router.path_context().get_path(PathID_t{to_usv(tx_id).data()}); + + if (crypto::verify(_router.pubkey(), to_usv(dict_data), sig)) + { + if (path_ptr->update_exit(std::stoul(tx_id))) + { + // TODO: talk to tom and Jason about how this stupid shit was a no-op originally + // see Path::HandleUpdateExitVerifyMessage + } + else + {} + } } void LinkManager::handle_close_exit(oxen::quic::message m) { + std::string_view tx_id, dict_data; + ustring_view sig; + try { - std::string_view tx_id; - ustring_view sig; - oxenc::bt_list_consumer btlc{m.body()}; - auto dict_data = btlc.consume_dict_data(); + dict_data = btlc.consume_dict_data(); oxenc::bt_dict_consumer btdc{dict_data}; sig = to_usv(btlc.consume_string_view()); tx_id = btdc.require("T"); - - auto transit_hop = - _router.path_context().GetTransitHop(_router.pubkey(), PathID_t{to_usv(tx_id).data()}); - - const auto rx_id = transit_hop->info.rxID; - - if (auto exit_ep = router().exitContext().FindEndpointForPath(rx_id)) - { - if (crypto::verify(exit_ep->PubKey().data(), to_usv(dict_data), sig)) - { - exit_ep->Close(); - m.respond(CloseExitMessage::sign_and_serialize_response(_router.identity(), tx_id)); - } - } - - m.respond( - serialize_response({{messages::STATUS_KEY, CloseExitMessage::UPDATE_FAILED}}), true); } catch (const std::exception& e) { @@ -1585,6 +1768,22 @@ namespace llarp m.respond(messages::ERROR_RESPONSE, true); return; } + + auto transit_hop = + _router.path_context().GetTransitHop(_router.pubkey(), PathID_t{to_usv(tx_id).data()}); + + const auto rx_id = transit_hop->info.rxID; + + if (auto exit_ep = router().exitContext().find_endpoint_for_path(rx_id)) + { + if (crypto::verify(exit_ep->PubKey().data(), to_usv(dict_data), sig)) + { + exit_ep->Close(); + m.respond(CloseExitMessage::sign_and_serialize_response(_router.identity(), tx_id)); + } + } + + m.respond(serialize_response({{messages::STATUS_KEY, CloseExitMessage::UPDATE_FAILED}}), true); } void @@ -1595,100 +1794,132 @@ namespace llarp log::info(link_cat, "CloseExitMessage timed out!"); return; } - if (m.is_error) + if (m.is_error()) { // TODO: what to do here } + std::string_view nonce, tx_id, dict_data; + ustring_view sig; + try { - std::string_view nonce, tx_id; - ustring_view sig; - oxenc::bt_list_consumer btlc{m.body()}; - auto dict_data = btlc.consume_dict_data(); + dict_data = btlc.consume_dict_data(); oxenc::bt_dict_consumer btdc{dict_data}; sig = to_usv(btlc.consume_string_view()); tx_id = btdc.require("T"); nonce = btdc.require("Y"); - - auto path_ptr = _router.path_context().GetPath(PathID_t{to_usv(tx_id).data()}); - - if (path_ptr->SupportsAnyRoles(path::ePathRoleExit | path::ePathRoleSVC) - and crypto::verify(_router.pubkey(), to_usv(dict_data), sig)) - path_ptr->mark_exit_closed(); } catch (const std::exception& e) { log::warning(link_cat, "Exception: {}", e.what()); return; } + + auto path_ptr = _router.path_context().get_path(PathID_t{to_usv(tx_id).data()}); + + if (path_ptr->SupportsAnyRoles(path::ePathRoleExit | path::ePathRoleSVC) + and crypto::verify(_router.pubkey(), to_usv(dict_data), sig)) + path_ptr->mark_exit_closed(); } void LinkManager::handle_path_control(oxen::quic::message m, const RouterID& from) { + ustring_view nonce, path_id_str; + std::string payload; + try { oxenc::bt_dict_consumer btdc{m.body()}; - auto nonce = SymmNonce{btdc.require("NONCE").data()}; - auto path_id_str = btdc.require("PATHID"); - auto payload = btdc.require("PAYLOAD"); - auto path_id = PathID_t{path_id_str.data()}; - auto hop = _router.path_context().GetTransitHop(from, path_id); - - // TODO: use "path_control" for both directions? If not, drop message on - // floor if we don't have the path_id in question; if we decide to make this - // bidirectional, will need to check if we have a Path with path_id. - if (not hop) - return; - - // if terminal hop, payload should contain a request (e.g. "find_name"); handle and respond. - if (hop->terminal_hop) - { - hop->onion(payload, nonce, false); - handle_inner_request(std::move(m), std::move(payload), std::move(hop)); - return; - } - - auto next_id = path_id == hop->info.rxID ? hop->info.txID : hop->info.rxID; - auto next_router = path_id == hop->info.rxID ? hop->info.upstream : hop->info.downstream; - auto new_payload = hop->onion_and_payload(payload, next_id, nonce); - send_control_message( - next_router, - "path_control"s, - std::move(new_payload), - [hop_weak = hop->weak_from_this(), path_id, prev_message = std::move(m)]( - oxen::quic::message response) mutable { - auto hop = hop_weak.lock(); - if (not hop) - return; - - oxenc::bt_dict_consumer resp_btdc{response.body()}; - auto nonce = SymmNonce{resp_btdc.require("NONCE").data()}; - auto payload = resp_btdc.require("PAYLOAD"); - auto resp_payload = hop->onion_and_payload(payload, path_id, nonce); - prev_message.respond(std::move(resp_payload), false); - }); + nonce = btdc.require("NONCE"); + path_id_str = btdc.require("PATHID"); + payload = btdc.require("PAYLOAD"); } catch (const std::exception& e) { log::warning(link_cat, "Exception: {}", e.what()); return; } + + auto symnonce = SymmNonce{nonce.data()}; + auto path_id = PathID_t{path_id_str.data()}; + auto hop = _router.path_context().GetTransitHop(from, path_id); + + // TODO: use "path_control" for both directions? If not, drop message on + // floor if we don't have the path_id in question; if we decide to make this + // bidirectional, will need to check if we have a Path with path_id. + if (not hop) + return; + + // if terminal hop, payload should contain a request (e.g. "find_name"); handle and respond. + if (hop->terminal_hop) + { + hop->onion(payload, symnonce, false); + handle_inner_request(std::move(m), std::move(payload), std::move(hop)); + return; + } + + auto& next_id = path_id == hop->info.rxID ? hop->info.txID : hop->info.rxID; + auto& next_router = path_id == hop->info.rxID ? hop->info.upstream : hop->info.downstream; + + std::string new_payload = hop->onion_and_payload(payload, next_id, symnonce); + + send_control_message( + next_router, + "path_control"s, + std::move(new_payload), + [hop_weak = hop->weak_from_this(), path_id, prev_message = std::move(m)]( + oxen::quic::message response) mutable { + auto hop = hop_weak.lock(); + + if (not hop) + return; + + ustring_view nonce; + std::string payload, response_body; + + try + { + oxenc::bt_dict_consumer btdc{response.body()}; + nonce = btdc.require("NONCE"); + payload = btdc.require("PAYLOAD"); + } + catch (const std::exception& e) + { + log::warning(link_cat, "Exception: {}", e.what()); + return; + } + + auto symnonce = SymmNonce{nonce.data()}; + auto resp_payload = hop->onion_and_payload(payload, path_id, symnonce); + prev_message.respond(std::move(resp_payload), false); + }); } void LinkManager::handle_inner_request( oxen::quic::message m, std::string payload, std::shared_ptr hop) { - oxenc::bt_dict_consumer btdc{payload}; - auto body = btdc.require("BODY"); - auto method = btdc.require("METHOD"); + std::string_view body, method; + + try + { + oxenc::bt_dict_consumer btdc{payload}; + body = btdc.require("BODY"); + method = btdc.require("METHOD"); + } + catch (const std::exception& e) + { + log::warning(link_cat, "Exception: {}", e.what()); + return; + } // If a handler exists for "method", call it; else drop request on the floor. auto itr = path_requests.find(method); + if (itr == path_requests.end()) { log::info(link_cat, "Received path control request \"{}\", which has no handler.", method); @@ -1710,14 +1941,16 @@ namespace llarp void LinkManager::handle_convo_intro(oxen::quic::message m) { - if (m.timed_out) + if (not m) { log::info(link_cat, "Path control message timed out!"); return; } try - {} + { + // + } catch (const std::exception& e) { log::warning(link_cat, "Exception: {}", e.what()); diff --git a/llarp/link/link_manager.hpp b/llarp/link/link_manager.hpp index 899b25af4..c73662308 100644 --- a/llarp/link/link_manager.hpp +++ b/llarp/link/link_manager.hpp @@ -28,48 +28,94 @@ namespace llarp struct LinkManager; class NodeDB; + using conn_open_hook = oxen::quic::connection_established_callback; + using conn_closed_hook = oxen::quic::connection_closed_callback; + using stream_open_hook = oxen::quic::stream_open_callback; + using stream_closed_hook = oxen::quic::stream_close_callback; + + using keep_alive = oxen::quic::opt::keep_alive; + using inbound_alpns = oxen::quic::opt::inbound_alpns; + using outbound_alpns = oxen::quic::opt::outbound_alpns; + + inline const keep_alive ROUTER_KEEP_ALIVE{10s}; + inline const keep_alive CLIENT_KEEP_ALIVE{10s}; + + inline constexpr int MIN_CLIENT_ROUTER_CONNS{4}; + inline constexpr int MAX_CLIENT_ROUTER_CONNS{6}; + + namespace alpns + { + inline const auto SN_ALPNS = "SERVICE_NODE"_us; + inline const auto C_ALPNS = "CLIENT"_us; + + inline const inbound_alpns SERVICE_INBOUND{{SN_ALPNS, C_ALPNS}}; + inline const outbound_alpns SERVICE_OUTBOUND{{SN_ALPNS}}; + + inline const inbound_alpns CLIENT_INBOUND{}; + inline const outbound_alpns CLIENT_OUTBOUND{{C_ALPNS}}; + } // namespace alpns + namespace link { struct Connection; struct Endpoint { - Endpoint(std::shared_ptr ep, LinkManager& lm) - : endpoint{std::move(ep)}, link_manager{lm} - {} + Endpoint(std::shared_ptr ep, LinkManager& lm); std::shared_ptr endpoint; LinkManager& link_manager; - // for outgoing packets, we route via RouterID; map RouterID->Connection - // for incoming packets, we get a ConnectionID; map ConnectionID->RouterID - std::unordered_map> conns; - std::unordered_map connid_map; + /** Connection containers: + - service_conns: holds all connections where the remote (from the perspective + of the local lokinet instance) is a service node. This means all relay to + relay connections are held here; clients will also hold their connections to + relays here as well + - client_conns: holds all connections wehre the remote is a client. This is only + used by service nodes to store their client connections + */ + std::unordered_map> service_conns; + std::unordered_map> client_conns; - // TODO: see which of these is actually useful and delete the other std::shared_ptr - get_conn(const RemoteRC&) const; + get_conn(const RouterID&) const; std::shared_ptr - get_conn(const RouterID&) const; + get_service_conn(const RouterID&) const; + + bool + have_conn(const RouterID& remote) const; bool - have_conn(const RouterID& remote, bool client_only) const; + have_client_conn(const RouterID& remote) const; bool - deregister_peer(RouterID remote); + have_service_conn(const RouterID& remote) const; + + std::pair + num_in_out() const; size_t - num_connected(bool clients_only) const; + num_client_conns() const; - bool - get_random_connection(RemoteRC& router) const; + size_t + num_router_conns() const; template bool establish_connection( const oxen::quic::RemoteAddress& remote, const RemoteRC& rc, Opt&&... opts); + template + bool + establish_and_send( + const oxen::quic::RemoteAddress& remote, + const RemoteRC& rc, + std::optional endpoint, + std::string body, + std::function func = nullptr, + Opt&&... opts); + void for_each_connection(std::function func); @@ -77,6 +123,7 @@ namespace llarp close_connection(RouterID rid); private: + const bool _is_service_node; }; } // namespace link @@ -107,27 +154,18 @@ namespace llarp struct PendingMessage { std::string body; + std::optional endpoint = std::nullopt; + std::function func = nullptr; + RouterID rid; - bool is_control{false}; + bool is_control = false; - PendingMessage(std::string b, bool control = false) : body{std::move(b)}, is_control{control} + PendingMessage(std::string b) : body{std::move(b)} {} - }; - - struct PendingDataMessage : PendingMessage - { - PendingDataMessage(std::string b) : PendingMessage(b) - {} - }; - - struct PendingControlMessage : PendingMessage - { - std::string endpoint; - std::function func; - PendingControlMessage( - std::string b, std::string e, std::function f = nullptr) - : PendingMessage(b, true), endpoint{std::move(e)}, func{std::move(f)} + PendingMessage( + std::string b, std::string ep, std::function f = nullptr) + : body{std::move(b)}, endpoint{std::move(ep)}, func{std::move(f)}, is_control{true} {} }; @@ -138,7 +176,8 @@ namespace llarp struct LinkManager { public: - explicit LinkManager(Router& r); + static std::unique_ptr + make(Router& r); bool send_control_message( @@ -157,12 +196,7 @@ namespace llarp } private: - bool - send_control_message_impl( - const RouterID& remote, - std::string endpoint, - std::string body, - std::function = nullptr); + explicit LinkManager(Router& r); friend struct link::Endpoint; @@ -171,9 +205,6 @@ namespace llarp // sessions to persist -> timestamp to end persist at std::unordered_map persisting_conns; - // holds any messages we attempt to send while connections are establishing - std::unordered_map pending_conn_msg_queue; - util::DecayingHashSet clients{path::DEFAULT_LIFETIME}; std::shared_ptr node_db; @@ -182,6 +213,8 @@ namespace llarp Router& _router; + const bool _is_service_node; + // FIXME: Lokinet currently expects to be able to kill all network functionality before // finishing other shutdown things, including destroying this class, and that is all in // Network's destructor, so we need to be able to destroy it before this class. @@ -192,8 +225,14 @@ namespace llarp void recv_data_message(oxen::quic::dgram_interface& dgi, bstring dgram); + std::shared_ptr + make_control(oxen::quic::connection_interface& ci, const RouterID& rid); + void - recv_control_message(oxen::quic::message msg); + on_inbound_conn(oxen::quic::connection_interface& ci); + + void + on_outbound_conn(oxen::quic::connection_interface& ci); void on_conn_open(oxen::quic::connection_interface& ci); @@ -205,11 +244,14 @@ namespace llarp startup_endpoint(); void - register_commands(std::shared_ptr& s); + register_commands( + std::shared_ptr& s, + const RouterID& rid, + bool client_only = false); public: const link::Endpoint& - endpoint() + endpoint() const { return ep; } @@ -221,37 +263,57 @@ namespace llarp } void - gossip_rc(const RouterID& rc_rid, std::string serialized_rc); + gossip_rc(const RouterID& last_sender, const RemoteRC& rc); void handle_gossip_rc(oxen::quic::message m); void - fetch_rcs(const RouterID& source, rc_time since, const std::vector& explicit_ids); + fetch_rcs( + const RouterID& source, + std::string payload, + std::function func); void handle_fetch_rcs(oxen::quic::message m); void - fetch_router_ids(const RouterID& source); + fetch_router_ids( + const RouterID& via, std::string payload, std::function func); void handle_fetch_router_ids(oxen::quic::message m); + void + fetch_bootstrap_rcs( + const RemoteRC& source, + std::string payload, + std::function func); + + void + handle_fetch_bootstrap_rcs(oxen::quic::message m); + bool - have_connection_to(const RouterID& remote, bool client_only = false) const; + have_connection_to(const RouterID& remote) const; + + bool + have_service_connection_to(const RouterID& remote) const; bool have_client_connection_to(const RouterID& remote) const; void - deregister_peer(RouterID remote); + test_reachability(const RouterID& rid, conn_open_hook, conn_closed_hook); void - connect_to(const RouterID& router); + connect_to(const RemoteRC& rc, conn_open_hook = nullptr, conn_closed_hook = nullptr); void - connect_to(const RemoteRC& rc); + connect_and_send( + const RouterID& router, + std::optional endpoint, + std::string body, + std::function func = nullptr); void close_connection(RouterID rid); @@ -262,14 +324,17 @@ namespace llarp void set_conn_persist(const RouterID& remote, llarp_time_t until); + std::pair + num_in_out() const; + size_t - get_num_connected(bool clients_only = false) const; + get_num_connected_routers() const; size_t get_num_connected_clients() const; bool - get_random_connected(RemoteRC& router) const; + is_service_node() const; void check_persisting_conns(llarp_time_t now); @@ -289,13 +354,10 @@ namespace llarp // check if we already have a connection to any of the random set, as making // that thread safe would be slow...I think. void - connect_to_random(int num_conns); + connect_to_random(int num_conns, bool client_only = false); - // TODO: tune these (maybe even remove max?) now that we're switching to quic - /// always maintain this many connections to other routers - size_t min_connected_routers = 4; - /// hard upperbound limit on the number of router to router connections - size_t max_connected_routers = 6; + /// always maintain this many client connections to other routers + int client_router_connections = 4; private: // DHT messages @@ -330,18 +392,9 @@ namespace llarp {"find_name"sv, &LinkManager::handle_find_name}, {"publish_intro"sv, &LinkManager::handle_publish_intro}, {"find_intro"sv, &LinkManager::handle_find_intro}}; - /* - {"path_confirm", &LinkManager::handle_path_confirm}, - {"path_latency", &LinkManager::handle_path_latency}, - {"update_exit", &LinkManager::handle_update_exit}, - {"obtain_exit", &LinkManager::handle_obtain_exit}, - {"close_exit", &LinkManager::handle_close_exit}, - {"convo_intro", &LinkManager::handle_convo_intro}}; - */ // these requests are direct, i.e. not over a path; // the rest are relay->relay - // TODO: new RC fetch endpoint (which will be both client->relay and relay->relay) std::unordered_map< std::string_view, void (LinkManager::*)(std::string_view body, std::function respond)> @@ -370,18 +423,75 @@ namespace llarp void handle_obtain_exit_response(oxen::quic::message); void handle_update_exit_response(oxen::quic::message); void handle_close_exit_response(oxen::quic::message); - - std::unordered_map rpc_responses = { - {"find_name", &LinkManager::handle_find_name_response}, - {"publish_intro", &LinkManager::handle_publish_intro_response}, - {"find_intro", &LinkManager::handle_find_intro_response}, - {"update_exit", &LinkManager::handle_update_exit_response}, - {"obtain_exit", &LinkManager::handle_obtain_exit_response}, - {"close_exit", &LinkManager::handle_close_exit_response}}; }; namespace link { + template + bool + Endpoint::establish_and_send( + const oxen::quic::RemoteAddress& remote, + const RemoteRC& rc, + std::optional ep, + std::string body, + std::function func, + Opt&&... opts) + { + try + { + const auto& rid = rc.router_id(); + const auto& is_snode = _is_service_node; + const auto& is_control = ep.has_value(); + const auto us = is_snode ? "Relay"s : "Client"s; + + log::critical(logcat, "Establishing connection to RID:{}", rid); + // add to service conns + auto [itr, b] = service_conns.emplace(rid, nullptr); + + auto conn_interface = endpoint->connect( + remote, + link_manager.tls_creds, + is_snode ? ROUTER_KEEP_ALIVE : CLIENT_KEEP_ALIVE, + std::forward(opts)...); + + // auto + std::shared_ptr control_stream = + conn_interface->template open_stream( + [this, rid = rid](oxen::quic::Stream&, uint64_t error_code) { + log::warning( + logcat, + "BTRequestStream closed unexpectedly (ec:{}); closing outbound connection...", + error_code); + close_connection(rid); + }); + + if (is_snode) + link_manager.register_commands(control_stream, rid); + else + log::critical(logcat, "Client NOT registering BTStream commands!"); + + log::critical( + logcat, + "{} dispatching {} on outbound connection to remote (rid:{})", + us, + is_control ? "control message (ep:{})"_format(ep) : "data message", + rid); + + (is_control) ? control_stream->command(std::move(*ep), std::move(body), std::move(func)) + : conn_interface->send_datagram(std::move(body)); + + itr->second = std::make_shared(conn_interface, control_stream, true); + + log::critical(logcat, "Outbound connection to RID:{} added to service conns...", rid); + return true; + } + catch (...) + { + log::error(quic_cat, "Error: failed to establish connection to {}", remote); + return false; + } + } + template bool Endpoint::establish_connection( @@ -389,17 +499,35 @@ namespace llarp { try { - auto conn_interface = - endpoint->connect(remote, link_manager.tls_creds, std::forward(opts)...); - - // emplace immediately for connection open callback to find scid - connid_map.emplace(conn_interface->scid(), rc.router_id()); - auto [itr, b] = conns.emplace(rc.router_id(), nullptr); - - auto control_stream = - conn_interface->template get_new_stream(); - itr->second = std::make_shared(conn_interface, control_stream, rc); - + const auto& rid = rc.router_id(); + const auto& is_snode = _is_service_node; + + log::critical(logcat, "Establishing connection to RID:{}", rid); + // add to service conns + auto [itr, b] = service_conns.emplace(rid, nullptr); + + auto conn_interface = endpoint->connect( + remote, + link_manager.tls_creds, + is_snode ? ROUTER_KEEP_ALIVE : CLIENT_KEEP_ALIVE, + std::forward(opts)...); + + auto control_stream = conn_interface->template open_stream( + [this, rid = rid](oxen::quic::Stream&, uint64_t error_code) { + log::warning( + logcat, + "BTRequestStream closed unexpectedly (ec:{}); closing outbound connection...", + error_code); + close_connection(rid); + }); + + if (is_snode) + link_manager.register_commands(control_stream, rid); + else + log::critical(logcat, "Client NOT registering BTStream commands!"); + itr->second = std::make_shared(conn_interface, control_stream, true); + + log::critical(logcat, "Outbound connection to RID:{} added to service conns...", rid); return true; } catch (...) @@ -409,50 +537,4 @@ namespace llarp } } } // namespace link - } // namespace llarp - -/* -- Refactor RouterID to use gnutls info and maybe ConnectionID -- Combine routerID and connectionID to simplify mapping in llarp/link/endpoint.hpp -- Combine llarp/link/session.hpp into llarp/link/connection.hpp::Connection - -- Combine llarp/link/server.hpp::ILinkLayer into llarp/link/endpoint.hpp::Endpoint - - must maintain metadata storage, callbacks, etc - -- If: one endpoint for ipv4 and ipv6 - - Then: can potentially combine: - - llarp/link/endpoint.hpp - - llarp/link/link_manager.hpp - - llarp/link/outbound_message_handler.hpp - - llarp/link/outbound_session_maker.hpp - - -> Yields mega-combo endpoint managing object? - - Can avoid "kitchen sink" by greatly reducing complexity of implementation - - llarp/router/outbound_message_handler.hpp - - pendingsessionmessagequeue - - establish queue of messages to be sent on a connection we are creating - - upon creation, send these messages in the connection established callback - - if connection times out, flush queue - - TOCHECK: is priority used at all?? - - -std::unordered_map -rpc_commands = { - {"find_name", &handle_find_name}, - // ... -}; - -for (const auto& [name, mfn] : rpc_commands) - bparser.add_command(name, [this, mfn] (oxen::quic::message m) { - router->call([this, mfn, m=std::move(m)] mutable { - try { - std::invoke(mfn, this, std::move(m)); - } catch (const std::exception& e) { - m.respond("Error: "s + e.what(), true); - } - }); - }); - -*/ diff --git a/llarp/messages/common.hpp b/llarp/messages/common.hpp index f15a5184d..9d774aee2 100644 --- a/llarp/messages/common.hpp +++ b/llarp/messages/common.hpp @@ -20,7 +20,6 @@ namespace llarp { namespace messages { - inline std::string serialize_response(oxenc::bt_dict supplement = {}) { diff --git a/llarp/messages/dht.hpp b/llarp/messages/dht.hpp index 2a0e38805..2da262f3a 100644 --- a/llarp/messages/dht.hpp +++ b/llarp/messages/dht.hpp @@ -34,6 +34,7 @@ namespace llarp { inline auto NOT_FOUND = "NOT FOUND"sv; + // NOT USED inline static std::string serialize(dht::Key_t name_hash) { diff --git a/llarp/messages/fetch.hpp b/llarp/messages/fetch.hpp new file mode 100644 index 000000000..f8c194ff7 --- /dev/null +++ b/llarp/messages/fetch.hpp @@ -0,0 +1,123 @@ +#pragma once + +#include "common.hpp" + +#include + +namespace llarp +{ + namespace GossipRCMessage + { + inline static std::string + serialize(const RouterID& last_sender, const RemoteRC& rc) + { + oxenc::bt_dict_producer btdp; + + try + { + btdp.append_encoded("rc", rc.view()); + btdp.append("sender", last_sender.ToView()); + } + catch (...) + { + log::error(link_cat, "Error: GossipRCMessage failed to bt encode contents"); + } + + return std::move(btdp).str(); + } + } // namespace GossipRCMessage + + namespace FetchRCMessage + { + inline const auto INVALID_REQUEST = + messages::serialize_response({{messages::STATUS_KEY, "Invalid relay ID requested"}}); + + inline static std::string + serialize( + std::chrono::system_clock::time_point since, const std::vector& explicit_ids) + { + oxenc::bt_dict_producer btdp; + + try + { + { + auto sublist = btdp.append_list("explicit_ids"); + + for (const auto& rid : explicit_ids) + sublist.append(rid.ToView()); + } + + btdp.append("since", since.time_since_epoch() / 1s); + } + catch (...) + { + log::error(link_cat, "Error: RCFetchMessage failed to bt encode contents!"); + } + + return std::move(btdp).str(); + } + } // namespace FetchRCMessage + + namespace BootstrapFetchMessage + { + // the LocalRC is converted to a RemoteRC type to send to the bootstrap seed + inline static std::string + serialize(std::optional local_rc, size_t quantity) + { + oxenc::bt_dict_producer btdp; + + if (local_rc) + { + log::critical(logcat, "Serializing localRC: {}", oxenc::to_hex(local_rc->view())); + btdp.append_encoded("local", oxen::quic::to_sv(local_rc->view())); + } + + btdp.append("quantity", quantity); + + return std::move(btdp).str(); + } + + inline static std::string + serialize_response(const std::vector& explicit_ids) + { + oxenc::bt_dict_producer btdp; + + try + { + auto sublist = btdp.append_list("explicit_ids"); + + for (const auto& rid : explicit_ids) + sublist.append(rid.ToView()); + } + catch (...) + { + log::error(link_cat, "Error: BootstrapFetchMessage failed to bt encode contents!"); + } + + return std::move(btdp).str(); + } + } // namespace BootstrapFetchMessage + + namespace FetchRIDMessage + { + inline constexpr auto INVALID_REQUEST = "Invalid relay ID requested to relay response from."sv; + + inline static std::string + serialize(const RouterID& source) + { + oxenc::bt_dict_producer btdp; + + try + { + btdp.append("source", source.ToView()); + } + catch (...) + { + log::error(link_cat, "Error: FetchRIDMessage failed to bt encode contents!"); + } + + return std::move(btdp).str(); + } + } // namespace FetchRIDMessage + +} // namespace llarp diff --git a/llarp/messages/rc.hpp b/llarp/messages/rc.hpp deleted file mode 100644 index d9eb2ab82..000000000 --- a/llarp/messages/rc.hpp +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -#include "common.hpp" - -namespace llarp::RCFetchMessage -{ - inline constexpr auto INVALID_REQUEST = "Invalid relay ID requested."sv; - - inline static std::string - serialize(std::chrono::system_clock::time_point since, const std::vector& explicit_ids) - { - oxenc::bt_dict_producer btdp; - - try - { - btdp.append("since", since.time_since_epoch() / 1s); - { - auto id_list = btdp.append_list("explicit_ids"); - for (const auto& rid : explicit_ids) - id_list.append(rid.ToView()); - } - } - catch (...) - { - log::error(link_cat, "Error: RCFetchMessage failed to bt encode contents!"); - } - - return std::move(btdp).str(); - } -} // namespace llarp::RCFetchMessage diff --git a/llarp/messages/router_id.hpp b/llarp/messages/router_id.hpp deleted file mode 100644 index dbdd897de..000000000 --- a/llarp/messages/router_id.hpp +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include "common.hpp" - -namespace llarp::RouterIDFetch -{ - inline constexpr auto INVALID_REQUEST = "Invalid relay ID requested to relay response from."sv; - - inline static std::string - serialize(const RouterID& source) - { - // serialize_response is a bit weird here, and perhaps could have a sister function - // with the same purpose but as a request, but...it works. - return messages::serialize_response({{"source", source.ToView()}}); - } - -} // namespace llarp::RouterIDFetch diff --git a/llarp/net/net.hpp b/llarp/net/net.hpp index c821c2879..0074b4594 100644 --- a/llarp/net/net.hpp +++ b/llarp/net/net.hpp @@ -10,6 +10,8 @@ #include #include +#include + #include // for itoa #include #include @@ -121,8 +123,10 @@ namespace llarp return var::visit([](auto&& ip) { return not ip.n; }, ip); } - virtual std::optional - GetBestNetIF(int af = AF_INET) const = 0; + // Attempts to guess a good default public network address from the system's public IP + // addresses; the returned Address (if set) will have its port set to the given value. + virtual std::optional + get_best_public_address(bool ipv4, uint16_t port) const = 0; virtual std::optional FindFreeRange() const = 0; diff --git a/llarp/net/posix.cpp b/llarp/net/posix.cpp index d48fbb39e..f96088786 100644 --- a/llarp/net/posix.cpp +++ b/llarp/net/posix.cpp @@ -10,6 +10,8 @@ #include #endif +#include + #include namespace llarp::net @@ -50,19 +52,21 @@ namespace llarp::net return ifname; } - std::optional - GetBestNetIF(int af) const override + std::optional + get_best_public_address(bool ipv4, uint16_t port) const override { - std::optional found; + std::optional found; - iter_all([this, &found, af](auto i) { + iter_all([&found, ipv4, port](ifaddrs* i) { if (found) return; - if (i and i->ifa_addr and i->ifa_addr->sa_family == af) + if (i and i->ifa_addr and i->ifa_addr->sa_family == (ipv4 ? AF_INET : AF_INET6)) { - if (not IsBogon(*i->ifa_addr)) + oxen::quic::Address a{i->ifa_addr}; + if (a.is_public_ip()) { - found = i->ifa_addr; + a.set_port(port); + found = std::move(a); } } }); diff --git a/llarp/net/sock_addr.cpp b/llarp/net/sock_addr.cpp index a4a897ee3..e3630295e 100644 --- a/llarp/net/sock_addr.cpp +++ b/llarp/net/sock_addr.cpp @@ -232,8 +232,8 @@ namespace llarp return; } - // NOTE: this potentially involves multiple memory allocations, - // reimplement without split() if it is performance bottleneck + // TOFIX: This potentially involves multiple memory allocations, + // reimplement without split() if it is performance bottleneck auto splits = split(str, ":"); // TODO: having ":port" at the end makes this ambiguous with IPv6 diff --git a/llarp/net/win32.cpp b/llarp/net/win32.cpp index 88b011b20..37af7ad26 100644 --- a/llarp/net/win32.cpp +++ b/llarp/net/win32.cpp @@ -129,8 +129,8 @@ namespace llarp::net return "lokitun0"; } - std::optional - GetBestNetIF(int) const override + std::optional + get_best_public_address(bool, uint16_t) const override { // TODO: implement me ? return std::nullopt; diff --git a/llarp/nodedb.cpp b/llarp/nodedb.cpp index 7861b0041..5b9eb6bce 100644 --- a/llarp/nodedb.cpp +++ b/llarp/nodedb.cpp @@ -2,6 +2,7 @@ #include "crypto/types.hpp" #include "dht/kademlia.hpp" +#include "messages/fetch.hpp" #include "router_contact.hpp" #include "util/time.hpp" @@ -9,7 +10,6 @@ #include #include -static const char skiplist_subdirs[] = "0123456789abcdef"; static const std::string RC_FILE_EXT = ".signed"; namespace llarp @@ -30,51 +30,128 @@ namespace llarp if (not fs::is_directory(nodedbDir)) throw std::runtime_error{fmt::format("nodedb {} is not a directory", nodedbDir)}; + } + + NodeDB::NodeDB(fs::path root, std::function)> diskCaller, Router* r) + : _router{*r} + , _root{std::move(root)} + , _disk(std::move(diskCaller)) + , _next_flush_time{time_now_ms() + FLUSH_INTERVAL} + { + EnsureSkiplist(_root); + fetch_counters.clear(); + } + + std::optional + NodeDB::get_rc_by_rid(const RouterID& rid) + { + if (auto itr = rc_lookup.find(rid); itr != rc_lookup.end()) + return itr->second; + + return std::nullopt; + } + + std::optional + NodeDB::get_random_rc() const + { + std::optional rand = std::nullopt; + + std::sample(known_rcs.begin(), known_rcs.end(), &*rand, 1, csrng); + return rand; + } - for (const char& ch : skiplist_subdirs) + std::optional> + NodeDB::get_n_random_rcs(size_t n, bool exact) const + { + auto rand = std::make_optional>(); + rand->reserve(n); + + std::sample(known_rcs.begin(), known_rcs.end(), std::back_inserter(*rand), n, csrng); + if (rand->size() < (exact ? n : 1)) + rand.reset(); + return rand; + } + std::optional + NodeDB::get_random_rc_conditional(std::function hook) const + { + std::optional rand = get_random_rc(); + + if (rand and hook(*rand)) + return rand; + + size_t i = 0; + + for (const auto& rc : known_rcs) { - // this seems to be a problem on all targets - // perhaps cpp17::fs is just as screwed-up - // attempting to create a folder with no name - // what does this mean...? - if (!ch) + if (not hook(rc)) continue; - fs::path sub = nodedbDir / std::string(&ch, 1); - fs::create_directory(sub); + if (++i <= 1) + { + rand = rc; + continue; + } + + size_t x = csrng() % (i + 1); + if (x <= 1) + rand = rc; } - } - constexpr auto FlushInterval = 5min; + return rand; + } - NodeDB::NodeDB(fs::path root, std::function)> diskCaller, Router* r) - : router{*r} - , m_Root{std::move(root)} - , disk(std::move(diskCaller)) - , m_NextFlushAt{time_now_ms() + FlushInterval} + std::optional> + NodeDB::get_n_random_rcs_conditional( + size_t n, std::function hook, bool exact) const { - EnsureSkiplist(m_Root); + auto selected = std::make_optional>(); + selected->reserve(n); + + size_t i = 0; + + for (const auto& rc : known_rcs) + { + // ignore any RC's that do not pass the condition + if (not hook(rc)) + continue; + + // load the first n RC's that pass the condition into selected + if (++i <= n) + { + selected->push_back(rc); + continue; + } + + // replace selections with decreasing probability per iteration + size_t x = csrng() % (i + 1); + if (x < n) + (*selected)[x] = rc; + } + + if (selected->size() < (exact ? n : 1)) + selected.reset(); + return selected; } void NodeDB::Tick(llarp_time_t now) { - if (m_NextFlushAt == 0s) + if (_next_flush_time == 0s) return; - if (now > m_NextFlushAt) + if (now > _next_flush_time) { - router.loop()->call([this]() { - m_NextFlushAt += FlushInterval; + _router.loop()->call([this]() { + _next_flush_time += FLUSH_INTERVAL; // make copy of all rcs std::vector copy; - for (const auto& item : known_rcs) + for (const auto& item : rc_lookup) copy.push_back(item.second); // flush them to disk in one big job // TODO: split this up? idk maybe some day... - disk([this, data = std::move(copy)]() { + _disk([this, data = std::move(copy)]() { for (const auto& rc : data) rc.write(get_path_by_pubkey(rc.router_id())); }); @@ -83,310 +160,728 @@ namespace llarp } fs::path - NodeDB::get_path_by_pubkey(RouterID pubkey) const + NodeDB::get_path_by_pubkey(const RouterID& pubkey) const { - std::string hexString = oxenc::to_hex(pubkey.begin(), pubkey.end()); - std::string skiplistDir; - - const llarp::RouterID r{pubkey}; - std::string fname = r.ToString(); - - skiplistDir += hexString[0]; - fname += RC_FILE_EXT; - return m_Root / skiplistDir / fname; + return _root / (pubkey.ToString() + RC_FILE_EXT); } bool NodeDB::want_rc(const RouterID& rid) const { - if (not router.is_service_node()) + if (not _router.is_service_node()) return true; - return registered_routers.count(rid); + + return known_rids.count(rid); } void - NodeDB::set_bootstrap_routers(const std::set& rcs) + NodeDB::set_bootstrap_routers(BootstrapList& from_router) { - bootstraps.clear(); // this function really shouldn't be called more than once, but... - for (const auto& rc : rcs) + _bootstraps.merge(from_router); + _bootstraps.randomize(); + } + + bool + NodeDB::process_fetched_rcs(std::set& rcs) + { + std::set confirmed_set, unconfirmed_set; + + // the intersection of local RC's and received RC's is our confirmed set + std::set_intersection( + known_rcs.begin(), + known_rcs.end(), + rcs.begin(), + rcs.end(), + std::inserter(confirmed_set, confirmed_set.begin())); + + // the intersection of the confirmed set and received RC's is our unconfirmed set + std::set_intersection( + rcs.begin(), + rcs.end(), + confirmed_set.begin(), + confirmed_set.end(), + std::inserter(unconfirmed_set, unconfirmed_set.begin())); + + // the total number of rcs received + const auto num_received = static_cast(rcs.size()); + // the number of returned "good" rcs (that are also found locally) + const auto inter_size = confirmed_set.size(); + + const auto fetch_threshold = (double)inter_size / num_received; + + /** We are checking 2 things here: + 1) The number of "good" rcs is above MIN_GOOD_RC_FETCH_TOTAL + 2) The ratio of "good" rcs to total received is above MIN_GOOD_RC_FETCH_THRESHOLD + */ + bool success = false; + if (success = + inter_size > MIN_GOOD_RC_FETCH_TOTAL and fetch_threshold > MIN_GOOD_RC_FETCH_THRESHOLD; + success) { - bootstraps.emplace(rc.router_id(), rc); + // set rcs to be intersection set + rcs = std::move(confirmed_set); + + process_results(std::move(unconfirmed_set), unconfirmed_rcs, known_rcs); } + + return success; } - /// Called in normal operation when the relay we fetched RCs from gives either a "bad" - /// response or a timeout. Attempts to switch to a new relay as our RC source, using - /// existing connections if possible, and respecting pinned edges. - void - NodeDB::rotate_rc_source() + bool + NodeDB::ingest_fetched_rcs(std::set rcs) { - auto conn_count = router.link_manager().get_num_connected(); + // if we are not bootstrapping, we should check the rc's against the ones we currently hold + if (not _using_bootstrap_fallback) + { + log::critical(logcat, "Checking returned RCs against locally held..."); - // This function makes no sense to be called if we have no connections... - if (conn_count == 0) - throw std::runtime_error{"Called rotate_rc_source with no connections, does not make sense!"}; + auto success = process_fetched_rcs(rcs); - // We should not be in this function if client_known_routers isn't populated - if (client_known_routers.size() <= 1) - throw std::runtime_error{"Cannot rotate RC source without RC source(s) to rotate to!"}; + log::critical( + logcat, "RCs returned by FetchRC {} by trust model", success ? "approved" : "rejected"); + return success; + } - RemoteRC new_source{}; - router.link_manager().get_random_connected(new_source); - if (conn_count == 1) - { - // if we only have one connection, it must be current rc fetch source - assert(new_source.router_id() == rc_fetch_source); + while (!rcs.empty()) + put_rc_if_newer(std::move(rcs.extract(rcs.begin()).value())); - if (pinned_edges.size() == 1) - { - // only one pinned edge set, use it even though it gave unsatisfactory RCs - assert(rc_fetch_source == *(pinned_edges.begin())); - log::warning( - logcat, - "Single pinned edge {} gave bad RC response; still using it despite this.", - rc_fetch_source); - return; - } + return true; + } - // only one connection, choose a new relay to connect to for rc fetching + /** We only call into this function after ensuring two conditions: + 1) We have received all 12 responses from the queried RouterID sources, whether that + response was a timeout or not + 2) Of those responses, less than 4 were errors of any sorts + + Upon receiving each response from the rid fetch sources, the returned rid's are incremented + in fetch_counters. This greatly simplifies the analysis required by this function to the + determine success or failure: + - If the frequency of each rid is above a threshold, it is accepted + - If the number of accepted rids is below a certain amount, the set is rejected + + Logically, this function performs the following basic analysis of the returned RIDs: + 1) All responses are coalesced into a union set with no repetitions + 2) If we are bootstrapping: + - The routerID's returned + */ + bool + NodeDB::process_fetched_rids() + { + std::set union_set, confirmed_set, unconfirmed_set; - RouterID r = rc_fetch_source; - while (r == rc_fetch_source) - { - std::sample(client_known_routers.begin(), client_known_routers.end(), &r, 1, csrng); - } - rc_fetch_source = std::move(r); - return; + for (const auto& [rid, count] : fetch_counters) + { + if (count > MIN_RID_FETCH_FREQ) + union_set.insert(rid); + else + unconfirmed_set.insert(rid); } - // choose one of our other existing connections to use as the RC fetch source - while (new_source.router_id() == rc_fetch_source) + // get the intersection of accepted rids and local rids + std::set_intersection( + known_rids.begin(), + known_rids.end(), + union_set.begin(), + union_set.end(), + std::inserter(confirmed_set, confirmed_set.begin())); + + // the total number of rids received + const auto num_received = (double)fetch_counters.size(); + // the total number of received AND accepted rids + const auto union_size = union_set.size(); + + const auto fetch_threshold = (double)union_size / num_received; + + /** We are checking 2 things here: + 1) The ratio of received/accepted to total received is above GOOD_RID_FETCH_THRESHOLD. + This tells us how well the rid source's sets of rids "agree" with one another + 2) The total number received is above MIN_RID_FETCH_TOTAL. This ensures that we are + receiving a sufficient amount to make a comparison of any sorts + */ + bool success = false; + if (success = (fetch_threshold > GOOD_RID_FETCH_THRESHOLD) + and (union_size > MIN_GOOD_RID_FETCH_TOTAL); + success) { - router.link_manager().get_random_connected(new_source); + process_results(std::move(unconfirmed_set), unconfirmed_rids, known_rids); + + known_rids.merge(confirmed_set); } - rc_fetch_source = new_source.router_id(); + + return success; } - // TODO: trust model void - NodeDB::ingest_rcs(RouterID source, std::vector rcs, rc_time timestamp) + NodeDB::ingest_rid_fetch_responses(const RouterID& source, std::set rids) { - (void)source; - - // TODO: if we don't currently have a "trusted" relay we've been fetching from, - // this will be a full list of RCs. We need to first check if it aligns closely - // with our trusted RouterID list, then replace our RCs with the incoming set. - - for (auto& rc : rcs) - put_rc_if_newer(std::move(rc), timestamp); - - // TODO: if we have a "trusted" relay we've been fetching from, this will be - // an incremental update to the RC list, so *after* insertion we check if the - // RCs' RouterIDs closely match our trusted RouterID list. + if (rids.empty()) + { + fail_sources.insert(source); + return; + } - last_rc_update_relay_timestamp = timestamp; + for (const auto& rid : rids) + fetch_counters[rid] += 1; } - // TODO: trust model void - NodeDB::ingest_router_ids(RouterID source, std::vector ids) + NodeDB::fetch_initial(bool is_snode) { - router_id_fetch_responses[source] = std::move(ids); + auto sz = num_rcs(); - router_id_response_count++; - if (router_id_response_count == router_id_fetch_sources.size()) + if (sz < MIN_ACTIVE_RCS) { - // TODO: reconcile all the responses, for now just insert all - for (const auto& [rid, responses] : router_id_fetch_responses) - { - // TODO: empty == failure, handle that case - for (const auto& response : responses) - { - client_known_routers.insert(std::move(response)); - } - } - router_id_fetch_in_progress = false; + log::critical(logcat, "{}/{} RCs held locally... BOOTSTRAP TIME", sz, MIN_ACTIVE_RCS); + fallback_to_bootstrap(); + } + else if (is_snode) + { + // service nodes who have sufficient local RC's can bypass initial fetching + _needs_initial_fetch = false; + } + else + { + _fetching_initial = true; + // Set fetch source as random selection of known active client routers + fetch_source = *std::next(known_rids.begin(), csrng() % known_rids.size()); + fetch_rcs(true); } } void - NodeDB::fetch_rcs() + NodeDB::fetch_rcs(bool initial) { + auto& num_failures = fetch_failures; + + // base case; this function is called recursively + if (num_failures > MAX_FETCH_ATTEMPTS) + { + fetch_rcs_result(initial, true); + return; + } + std::vector needed; + const auto now = time_point_now(); - const auto now = - std::chrono::time_point_cast(std::chrono::system_clock::now()); - for (const auto& [rid, rc] : known_rcs) + if (not initial) { - if (now - rc.timestamp() > RouterContact::OUTDATED_AGE) - needed.push_back(rid); + for (const auto& [rid, rc] : rc_lookup) + { + if (now - rc.timestamp() > RouterContact::OUTDATED_AGE) + needed.push_back(rid); + } } - router.link_manager().fetch_rcs( - rc_fetch_source, last_rc_update_relay_timestamp, std::move(needed)); + RouterID& src = fetch_source; + log::critical( + logcat, + "Sending{} FetchRCs request to {} for {} RCs", + initial ? " initial" : "", + src, + initial ? "all of the" : std::to_string(needed.size())); + + if (initial) + _router.next_initial_fetch_attempt = now + INITIAL_ATTEMPT_INTERVAL; + + _router.last_rc_fetch = now; + + _router.link_manager().fetch_rcs( + src, + FetchRCMessage::serialize(_router.last_rc_fetch, needed), + [this, source = src, initial](oxen::quic::message m) mutable { + if (m.timed_out) + { + log::critical(logcat, "RC fetch to {} timed out!", source); + fetch_rcs_result(initial, m.timed_out); + return; + } + try + { + oxenc::bt_dict_consumer btdc{m.body()}; + // TODO: can this just combine with the above failure case...? + if (m.is_error()) + { + auto reason = btdc.require(messages::STATUS_KEY); + log::critical(logcat, "RC fetch to {} returned error: {}", source, reason); + fetch_rcs_result(initial, m.is_error()); + return; + } + + auto btlc = btdc.require("rcs"sv); + + std::set rcs; + + while (not btlc.is_finished()) + rcs.emplace(btlc.consume_dict_data()); + + // if process_fetched_rcs returns false, then the trust model rejected the fetched RC's + fetch_rcs_result(initial, not ingest_fetched_rcs(std::move(rcs))); + } + catch (const std::exception& e) + { + log::critical( + logcat, "Failed to parse RC fetch response from {}: {}", source, e.what()); + fetch_rcs_result(initial, true); + return; + } + }); } void - NodeDB::fetch_router_ids() + NodeDB::fetch_rids(bool initial) { - if (router_id_fetch_in_progress) + // base case; this function is called recursively + if (fetch_failures > MAX_FETCH_ATTEMPTS) + { + fetch_rids_result(initial); return; - if (router_id_fetch_sources.empty()) - select_router_id_sources(); + } - // if we *still* don't have fetch sources, we can't exactly fetch... - if (router_id_fetch_sources.empty()) + if (rid_sources.empty()) { - log::info(logcat, "Attempting to fetch RouterIDs, but have no source from which to do so."); + reselect_router_id_sources(rid_sources); + } + + if (not initial and rid_sources.empty()) + { + log::error(logcat, "Attempting to fetch RouterIDs, but have no source from which to do so."); + fallback_to_bootstrap(); return; } - router_id_fetch_in_progress = true; - router_id_response_count = 0; - router_id_fetch_responses.clear(); - for (const auto& rid : router_id_fetch_sources) - router.link_manager().fetch_router_ids(rid); + fetch_counters.clear(); + + RouterID& src = fetch_source; + _router.last_rid_fetch = llarp::time_point_now(); + + for (const auto& target : rid_sources) + { + log::critical(logcat, "Sending FetchRIDs request to {} via {}", target, src); + _router.link_manager().fetch_router_ids( + src, + FetchRIDMessage::serialize(target), + [this, source = src, target, initial](oxen::quic::message m) mutable { + if (m.is_error()) + { + auto err = "RID fetch from {} via {} {}"_format( + target, source, m.timed_out ? "timed out" : "failed"); + log::critical(link_cat, err); + ingest_rid_fetch_responses(target); + fetch_rids_result(initial); + return; + } + + try + { + oxenc::bt_dict_consumer btdc{m.body()}; + + btdc.required("routers"); + auto router_id_strings = btdc.consume_list>(); + + btdc.require_signature("signature", [&source](ustring_view msg, ustring_view sig) { + if (sig.size() != 64) + throw std::runtime_error{"Invalid signature: not 64 bytes"}; + if (not crypto::verify(source, msg, sig)) + throw std::runtime_error{ + "Failed to verify signature for fetch RouterIDs response."}; + }); + + std::set router_ids; + + for (const auto& s : router_id_strings) + { + if (s.size() != RouterID::SIZE) + { + log::critical( + link_cat, "RID fetch from {} via {} returned bad RouterID", target, source); + ingest_rid_fetch_responses(target); + fetch_rids_result(initial); + return; + } + + router_ids.emplace(s.data()); + } + + ingest_rid_fetch_responses(target, std::move(router_ids)); + fetch_rids_result(initial); // success + return; + } + catch (const std::exception& e) + { + log::critical(link_cat, "Error handling fetch RouterIDs response: {}", e.what()); + ingest_rid_fetch_responses(target); + fetch_rids_result(initial); + } + }); + } + } + + void + NodeDB::fetch_rcs_result(bool initial, bool error) + { + if (error) + { + if (++fetch_failures >= MAX_FETCH_ATTEMPTS) + { + log::critical( + logcat, + "RC fetching from {} reached failure threshold ({}); falling back to bootstrap...", + fetch_source, + MAX_FETCH_ATTEMPTS); + + fallback_to_bootstrap(); + return; + } + + if (initial) + _needs_initial_fetch = true; + + // If we have passed the last last conditional, then it means we are not bootstrapping + // and the current fetch_source has more attempts before being rotated. As a result, we + // find new non-bootstrap RC fetch source and try again buddy + fetch_source = (initial) ? *std::next(known_rids.begin(), csrng() % known_rids.size()) + : std::next(rc_lookup.begin(), csrng() % rc_lookup.size())->first; + + fetch_rcs(initial); + } + else + { + log::critical(logcat, "Successfully fetched RC's from {}", fetch_source); + post_fetch_rcs(initial); + } } void - NodeDB::select_router_id_sources(std::unordered_set excluded) + NodeDB::fetch_rids_result(bool initial) { - // TODO: bootstrapping should be finished before this is called, so this - // shouldn't happen; need to make sure that's the case. - if (client_known_routers.empty()) + if (fetch_failures >= MAX_FETCH_ATTEMPTS) + { + log::critical( + logcat, + "Failed {} attempts to fetch RID's from {}; reverting to bootstrap...", + MAX_FETCH_ATTEMPTS, + fetch_source); + + fallback_to_bootstrap(); return; + } - // keep using any we've been using, but remove `excluded` ones - for (const auto& r : excluded) - router_id_fetch_sources.erase(r); + auto n_responses = RID_SOURCE_COUNT - fail_sources.size(); - // only know so many routers, so no need to randomize - if (client_known_routers.size() <= (ROUTER_ID_SOURCE_COUNT + excluded.size())) + if (n_responses < RID_SOURCE_COUNT) { - for (const auto& r : client_known_routers) + log::critical(logcat, "Received {}/{} fetch RID requests", n_responses, RID_SOURCE_COUNT); + return; + } + + auto n_fails = fail_sources.size(); + + if (n_fails <= MAX_RID_ERRORS) + { + log::critical( + logcat, "RID fetching was successful ({}/{} acceptable errors)", n_fails, MAX_RID_ERRORS); + + // this is where the trust model will do verification based on the similarity of the sets + if (process_fetched_rids()) { - if (excluded.count(r)) - continue; - router_id_fetch_sources.insert(r); + log::critical(logcat, "Accumulated RID's accepted by trust model"); + post_fetch_rids(initial); + return; } + + log::critical( + logcat, "Accumulated RID's rejected by trust model, reselecting all RID sources..."); + reselect_router_id_sources(rid_sources); + ++fetch_failures; } + else + { + // we had 4 or more failed requests, so we will need to rotate our rid sources + log::critical( + logcat, "RID fetching found {} failures; reselecting failed RID sources...", n_fails); + ++fetch_failures; + reselect_router_id_sources(fail_sources); + } + + fetch_rids(initial); + } + + // This function is only called after a successful FetchRC request + void + NodeDB::post_fetch_rcs(bool initial) + { + _needs_rebootstrap = false; + _needs_initial_fetch = false; + _using_bootstrap_fallback = false; + fail_sources.clear(); + fetch_failures = 0; + + if (initial) + fetch_rids(initial); + } + + void + NodeDB::post_fetch_rids(bool initial) + { + fail_sources.clear(); + fetch_failures = 0; + fetch_counters.clear(); + _needs_rebootstrap = false; + _using_bootstrap_fallback = false; - // select at random until we have chosen enough - while (router_id_fetch_sources.size() < ROUTER_ID_SOURCE_COUNT) + if (initial) { - RouterID r; - std::sample(client_known_routers.begin(), client_known_routers.end(), &r, 1, csrng); - if (excluded.count(r) == 0) - router_id_fetch_sources.insert(r); + _needs_initial_fetch = false; + _fetching_initial = false; + _initial_completed = true; } } + void + NodeDB::fallback_to_bootstrap() + { + log::critical(logcat, "{} called", __PRETTY_FUNCTION__); + auto at_max_failures = bootstrap_attempts >= MAX_BOOTSTRAP_FETCH_ATTEMPTS; + + // base case: we have failed to query all bootstraps, or we received a sample of + // the network, but the sample was unusable or unreachable. We will also enter this + // if we are on our first fallback to bootstrap so we can set the fetch_source (by + // checking not using_bootstrap_fallback) + if (at_max_failures || not _using_bootstrap_fallback) + { + bootstrap_attempts = 0; + + // Fail case: if we have returned to the front of the bootstrap list, we're in a + // bad spot; we are unable to do anything + if (_using_bootstrap_fallback) + { + auto err = fmt::format( + "ERROR: ALL BOOTSTRAPS ARE BAD... REATTEMPTING IN {}...", BOOTSTRAP_COOLDOWN); + log::error(logcat, err); + + bootstrap_cooldown(); + return; + } + } + + auto& rc = (_using_bootstrap_fallback) ? _bootstraps.next() : _bootstraps.current(); + fetch_source = rc.router_id(); + + // By passing the last conditional, we ensure this is set to true + _using_bootstrap_fallback = true; + _needs_rebootstrap = false; + ++bootstrap_attempts; + + log::critical(logcat, "Dispatching BootstrapRC fetch request to {}", fetch_source); + + auto is_snode = _router.is_service_node(); + + auto num_needed = + is_snode ? SERVICE_NODE_BOOTSTRAP_SOURCE_COUNT : CLIENT_BOOTSTRAP_SOURCE_COUNT; + + _router.link_manager().fetch_bootstrap_rcs( + rc, + BootstrapFetchMessage::serialize( + is_snode ? std::make_optional(_router.router_contact) : std::nullopt, num_needed), + [this, is_snode = _router.is_service_node(), src = rc.router_id()]( + oxen::quic::message m) mutable { + log::critical(logcat, "Received response to BootstrapRC fetch request..."); + + if (not m) + { + log::warning( + logcat, + "BootstrapRC fetch request to {} failed (error {}/{})", + src, + bootstrap_attempts, + MAX_BOOTSTRAP_FETCH_ATTEMPTS); + fallback_to_bootstrap(); + return; + } + + size_t num = 0; + + try + { + oxenc::bt_dict_consumer btdc{m.body()}; + + { + auto btlc = btdc.require("rcs"sv); + + while (not btlc.is_finished()) + { + auto rc = RemoteRC{btlc.consume_dict_data()}; + put_rc(rc); + ++num; + } + } + } + catch (const std::exception& e) + { + log::warning( + logcat, + "Failed to parse BootstrapRC fetch response from {} (error {}/{}): {}", + src, + bootstrap_attempts, + MAX_BOOTSTRAP_FETCH_ATTEMPTS, + e.what()); + fallback_to_bootstrap(); + return; + } + + log::critical( + logcat, + "BootstrapRC fetch response from {} returned {}/{} needed RCs", + src, + num, + MIN_ACTIVE_RCS); + + if (not is_snode) + { + log::critical( + logcat, + "Client completed processing BootstrapRC fetch; proceeding to initial fetch"); + fetch_initial(); + } + else + { + log::critical(logcat, "Service node completed processing BootstrapRC fetch!"); + post_snode_bootstrap(); + } + }); + } + + void + NodeDB::post_snode_bootstrap() + { + _needs_rebootstrap = false; + _using_bootstrap_fallback = false; + _needs_initial_fetch = false; + } + + void + NodeDB::bootstrap_cooldown() + { + _needs_rebootstrap = true; + _using_bootstrap_fallback = false; + _router.next_bootstrap_attempt = llarp::time_point_now() + BOOTSTRAP_COOLDOWN; + } + + void + NodeDB::reselect_router_id_sources(std::set specific) + { + replace_subset(rid_sources, specific, known_rids, RID_SOURCE_COUNT, csrng); + } + void NodeDB::set_router_whitelist( const std::vector& whitelist, const std::vector& greylist, const std::vector& greenlist) { + log::critical( + logcat, + "Oxend provided {}/{}/{} (white/gray/green) routers", + whitelist.size(), + greylist.size(), + greenlist.size()); + if (whitelist.empty()) return; - registered_routers.clear(); - registered_routers.insert(whitelist.begin(), whitelist.end()); - registered_routers.insert(greylist.begin(), greylist.end()); - registered_routers.insert(greenlist.begin(), greenlist.end()); - - router_whitelist.clear(); - router_whitelist.insert(whitelist.begin(), whitelist.end()); - router_greylist.clear(); - router_greylist.insert(greylist.begin(), greylist.end()); - router_greenlist.clear(); - router_greenlist.insert(greenlist.begin(), greenlist.end()); - - log::info( - logcat, "lokinet service node list now has ", router_whitelist.size(), " active routers"); + _registered_routers.clear(); + _registered_routers.insert(whitelist.begin(), whitelist.end()); + _registered_routers.insert(greylist.begin(), greylist.end()); + _registered_routers.insert(greenlist.begin(), greenlist.end()); + + _router_whitelist.clear(); + _router_whitelist.insert(whitelist.begin(), whitelist.end()); + _router_greylist.clear(); + _router_greylist.insert(greylist.begin(), greylist.end()); + _router_greenlist.clear(); + _router_greenlist.insert(greenlist.begin(), greenlist.end()); + + log::critical( + logcat, + "Service node holding {}:{} (whitelist:registered) after oxend integration", + _router_whitelist.size(), + _registered_routers.size()); } std::optional NodeDB::get_random_whitelist_router() const { - const auto sz = router_whitelist.size(); - if (sz == 0) - return std::nullopt; - auto itr = router_whitelist.begin(); - if (sz > 1) - std::advance(itr, randint() % sz); - return *itr; + std::optional rand = std::nullopt; + + std::sample(_router_whitelist.begin(), _router_whitelist.end(), &*rand, 1, csrng); + return rand; } bool NodeDB::is_connection_allowed(const RouterID& remote) const { - if (pinned_edges.size() && pinned_edges.count(remote) == 0 && bootstraps.count(remote) == 0) + if (not _router.is_service_node()) { - return false; + if (_pinned_edges.size() && _pinned_edges.count(remote) == 0 + && not _bootstraps.contains(remote)) + return false; } - if (not router.is_service_node()) - return true; - - return router_whitelist.count(remote) or router_greylist.count(remote); + return known_rids.count(remote) or _router_greylist.count(remote); } bool NodeDB::is_first_hop_allowed(const RouterID& remote) const { - if (pinned_edges.size() && pinned_edges.count(remote) == 0) + if (_pinned_edges.size() && _pinned_edges.count(remote) == 0) return false; + return true; } + void + NodeDB::store_bootstraps() + { + if (_bootstraps.empty()) + return; + + for (const auto& rc : _bootstraps) + { + put_rc(rc); + } + + log::critical(logcat, "NodeDB stored {} bootstrap routers", _bootstraps.size()); + } + void NodeDB::load_from_disk() { - if (m_Root.empty()) + if (_root.empty()) return; - std::set purge; + std::vector purge; const auto now = time_now_ms(); - for (const char& ch : skiplist_subdirs) + for (const auto& f : fs::directory_iterator{_root}) { - if (!ch) + if (not f.is_regular_file() or f.path().extension() != RC_FILE_EXT) continue; - std::string p; - p += ch; - fs::path sub = m_Root / p; - llarp::util::IterDir(sub, [&](const fs::path& f) -> bool { - // skip files that are not suffixed with .signed - if (not(fs::is_regular_file(f) and f.extension() == RC_FILE_EXT)) - return true; + RemoteRC rc{}; - RemoteRC rc{}; - - if (not rc.read(f)) - { - // try loading it, purge it if it is junk - purge.emplace(f); - return true; - } - - if (rc.is_expired(now)) - { - // rc expired dont load it and purge it later - purge.emplace(f); - return true; - } + if (not rc.read(f) or rc.is_expired(now)) + { + // try loading it, purge it if it is junk or expired + purge.push_back(f); + continue; + } - known_rcs.emplace(rc.router_id(), rc); - // TODO: the list of relays should be maintained and stored separately from - // the RCs, as we keep older RCs around in case we go offline and need to - // bootstrap, but they shouldn't be in the "good relays" list. - client_known_routers.insert(rc.router_id()); + const auto& rid = rc.router_id(); - return true; - }); + auto [itr, b] = known_rcs.insert(std::move(rc)); + rc_lookup.emplace(rid, *itr); + known_rids.insert(rid); } if (not purge.empty()) @@ -401,37 +896,43 @@ namespace llarp void NodeDB::save_to_disk() const { - if (m_Root.empty()) + if (_root.empty()) return; - router.loop()->call([this]() { - for (const auto& item : known_rcs) - item.second.write(get_path_by_pubkey(item.first)); + _router.loop()->call([this]() { + for (const auto& rc : rc_lookup) + { + rc.second.write(get_path_by_pubkey(rc.first)); + } }); } bool - NodeDB::has_rc(RouterID pk) const + NodeDB::has_rc(const RemoteRC& rc) const { - return known_rcs.count(pk); + return known_rcs.count(rc); } - std::optional - NodeDB::get_rc(RouterID pk) const + bool + NodeDB::has_rc(const RouterID& pk) const { - const auto itr = known_rcs.find(pk); + return rc_lookup.count(pk); + } - if (itr == known_rcs.end()) - return std::nullopt; + std::optional + NodeDB::get_rc(const RouterID& pk) const + { + if (auto itr = rc_lookup.find(pk); itr != rc_lookup.end()) + return itr->second; - return itr->second; + return std::nullopt; } void NodeDB::remove_router(RouterID pk) { - router.loop()->call([this, pk]() { - known_rcs.erase(pk); + _router.loop()->call([this, pk]() { + rc_lookup.erase(pk); remove_many_from_disk_async({pk}); }); } @@ -439,15 +940,18 @@ namespace llarp void NodeDB::remove_stale_rcs() { - auto cutoff_time = - std::chrono::time_point_cast(std::chrono::system_clock::now()); - cutoff_time -= router.is_service_node() ? RouterContact::OUTDATED_AGE : RouterContact::LIFETIME; - for (auto itr = known_rcs.begin(); itr != known_rcs.end();) + auto cutoff_time = time_point_now(); + + cutoff_time -= + _router.is_service_node() ? RouterContact::OUTDATED_AGE : RouterContact::LIFETIME; + + for (auto itr = rc_lookup.begin(); itr != rc_lookup.end();) { if (cutoff_time > itr->second.timestamp()) { log::info(logcat, "Pruning RC for {}, as it is too old to keep.", itr->first); - known_rcs.erase(itr); + known_rcs.erase(itr->second); + rc_lookup.erase(itr); continue; } itr++; @@ -458,44 +962,85 @@ namespace llarp NodeDB::put_rc(RemoteRC rc, rc_time now) { const auto& rid = rc.router_id(); - if (not want_rc(rid)) + + if (rid == _router.local_rid()) return false; - known_rcs.erase(rid); - known_rcs.emplace(rid, std::move(rc)); + + known_rcs.erase(rc); + rc_lookup.erase(rid); + + auto [itr, b] = known_rcs.insert(std::move(rc)); + rc_lookup.emplace(rid, *itr); + known_rids.insert(rid); + last_rc_update_times[rid] = now; return true; } size_t - NodeDB::num_loaded() const + NodeDB::num_rcs() const { - return router.loop()->call_get([this]() { return known_rcs.size(); }); + return known_rcs.size(); + } + + size_t + NodeDB::num_rids() const + { + return known_rids.size(); } bool - NodeDB::put_rc_if_newer(RemoteRC rc, rc_time now) + NodeDB::verify_store_gossip_rc(const RemoteRC& rc) { - auto itr = known_rcs.find(rc.router_id()); - if (itr == known_rcs.end() or itr->second.other_is_newer(rc)) + if (registered_routers().count(rc.router_id())) + return put_rc_if_newer(rc); + + return false; + } + + void + NodeDB::verify_gossip_bfetch_rc(const RemoteRC& rc) + { + if (auto maybe = get_rc(rc.router_id())) { - return put_rc(std::move(rc), now); + if (maybe->other_is_newer(rc)) + put_rc(rc); + } + else + { + if (put_rc(rc)) + _router.link_manager().gossip_rc(_router.local_rid(), rc); } - return false; + } + + bool + NodeDB::put_rc_if_newer(RemoteRC rc) + { + if (auto maybe = get_rc(rc.router_id())) + { + if (maybe->other_is_newer(rc)) + return put_rc(rc); + + return false; + } + + return put_rc(rc); } void NodeDB::remove_many_from_disk_async(std::unordered_set remove) const { - if (m_Root.empty()) + if (_root.empty()) return; + // build file list std::set files; + for (auto id : remove) - { files.emplace(get_path_by_pubkey(std::move(id))); - } + // remove them from the disk via the diskio thread - disk([files]() { + _disk([files]() { for (auto fpath : files) fs::remove(fpath); }); @@ -504,11 +1049,11 @@ namespace llarp RemoteRC NodeDB::find_closest_to(llarp::dht::Key_t location) const { - return router.loop()->call_get([this, location]() -> RemoteRC { - RemoteRC rc; + return _router.loop()->call_get([this, location]() -> RemoteRC { + RemoteRC rc{}; const llarp::dht::XorMetric compare(location); - VisitAll([&rc, compare](const auto& otherRC) { + visit_all([&rc, compare](const auto& otherRC) { const auto& rid = rc.router_id(); if (rid.IsZero() || compare(dht::Key_t{otherRC.router_id()}, dht::Key_t{rid})) @@ -524,16 +1069,18 @@ namespace llarp std::vector NodeDB::find_many_closest_to(llarp::dht::Key_t location, uint32_t numRouters) const { - return router.loop()->call_get([this, location, numRouters]() -> std::vector { + return _router.loop()->call_get([this, location, numRouters]() -> std::vector { std::vector all; all.reserve(known_rcs.size()); - for (auto& entry : known_rcs) + + for (auto& entry : rc_lookup) { all.push_back(&entry.second); } auto it_mid = numRouters < all.size() ? all.begin() + numRouters : all.end(); + std::partial_sort( all.begin(), it_mid, all.end(), [compare = dht::XorMetric{location}](auto* a, auto* b) { return compare(*a, *b); diff --git a/llarp/nodedb.hpp b/llarp/nodedb.hpp index f4c3b629d..d9ac04a96 100644 --- a/llarp/nodedb.hpp +++ b/llarp/nodedb.hpp @@ -12,9 +12,9 @@ #include #include +#include #include #include -#include #include #include @@ -22,15 +22,164 @@ namespace llarp { struct Router; - class NodeDB + // TESTNET: the following constants have been shortened for testing purposes + + /* RC Fetch Constants */ + // fallback to bootstrap if we have less than this many RCs + inline constexpr size_t MIN_ACTIVE_RCS{6}; + // max number of attempts we make in non-bootstrap fetch requests + inline constexpr int MAX_FETCH_ATTEMPTS{10}; + // the total number of returned rcs that are held locally should be at least this + inline constexpr size_t MIN_GOOD_RC_FETCH_TOTAL{}; + // the ratio of returned rcs found locally to to total returned should be above this ratio + inline constexpr double MIN_GOOD_RC_FETCH_THRESHOLD{}; + + /* RID Fetch Constants */ + // the number of rid sources that we make rid fetch requests to + inline constexpr size_t RID_SOURCE_COUNT{8}; + // upper limit on how many rid fetch requests to rid sources can fail + inline constexpr size_t MAX_RID_ERRORS{2}; + // each returned rid must appear this number of times across all responses + inline constexpr int MIN_RID_FETCH_FREQ{RID_SOURCE_COUNT - MAX_RID_ERRORS - 1}; + // the total number of accepted returned rids should be above this number + inline constexpr size_t MIN_GOOD_RID_FETCH_TOTAL{}; + // the ratio of accepted:rejected rids must be above this ratio + inline constexpr double GOOD_RID_FETCH_THRESHOLD{}; + + /* Bootstrap Constants */ + // the number of rc's we query the bootstrap for; service nodes pass 0, which means + // gimme all dat RCs + inline constexpr size_t SERVICE_NODE_BOOTSTRAP_SOURCE_COUNT{0}; + inline constexpr size_t CLIENT_BOOTSTRAP_SOURCE_COUNT{10}; + // the maximum number of fetch requests we make across all bootstraps + inline constexpr int MAX_BOOTSTRAP_FETCH_ATTEMPTS{5}; + // if all bootstraps fail, router will trigger re-bootstrapping after this cooldown + inline constexpr auto BOOTSTRAP_COOLDOWN{1min}; + + /* Other Constants */ + // the maximum number of RC/RID fetches that can pass w/o an unconfirmed rc/rid appearing + inline constexpr int MAX_CONFIRMATION_ATTEMPTS{5}; + // threshold amount of verifications to promote an unconfirmed rc/rid + inline constexpr int CONFIRMATION_THRESHOLD{3}; + + inline constexpr auto FLUSH_INTERVAL{5min}; + + template < + typename ID_t, + std::enable_if_t || std::is_same_v, int> = 0> + struct Unconfirmed { - std::unordered_map known_rcs; + const ID_t id; + int attempts = 0; + int verifications = 0; + + Unconfirmed() = delete; + Unconfirmed(const ID_t& obj) : id{obj} + {} + Unconfirmed(ID_t&& obj) : id{std::move(obj)} + {} + + int + strikes() const + { + return attempts; + } - Router& router; - const fs::path m_Root; - const std::function)> disk; + operator bool() const + { + return verifications == CONFIRMATION_THRESHOLD; + } - llarp_time_t m_NextFlushAt; + bool + operator==(const Unconfirmed& other) const + { + return id == other.id; + } + + bool + operator<(const Unconfirmed& other) const + { + return id < other.id; + } + }; + + class NodeDB + { + Router& _router; + const fs::path _root; + const std::function)> _disk; + + llarp_time_t _next_flush_time; + + /******** RouterID/RouterContacts ********/ + + /** RouterID mappings + Both the following are populated in NodeDB startup with RouterID's stored on disk. + - known_rids: meant to persist between lokinet sessions, and is only + populated during startup and RouterID fetching. This is meant to represent the + client instance's most recent perspective of the network, and record which RouterID's + were recently "active" and connected to + - unconfirmed_rids: holds new rids returned in fetch requests to be verified by subsequent + fetch requests + - known_rcs: populated during startup and when RC's are updated both during gossip + and periodic RC fetching + - unconfirmed_rcs: holds new rcs to be verified by subsequent fetch requests, similar to + the unknown_rids container + - rc_lookup: holds all the same rc's as known_rcs, but can be used to look them up by + their rid + - bootstrap_seeds: if we are the seed node, we insert the rc's of bootstrap fetch requests + senders into this container to "introduce" them to each other + - _bootstraps: the standard container for bootstrap RemoteRCs + */ + std::set known_rids; + std::set> unconfirmed_rids; + + std::set known_rcs; + std::set> unconfirmed_rcs; + + std::map rc_lookup; + + BootstrapList _bootstraps{}; + + /** RouterID lists // TODO: get rid of all these, replace with better decom/not staked sets + - white: active routers + - gray: fully funded, but decommissioned routers + - green: registered, but not fully-staked routers + */ + std::set _router_whitelist{}; + std::set _router_greylist{}; + std::set _router_greenlist{}; + + // All registered relays (service nodes) + std::set _registered_routers; + // timing (note: Router holds the variables for last rc and rid request times) + std::unordered_map last_rc_update_times; + // if populated from a config file, lists specific exclusively used as path first-hops + std::set _pinned_edges; + // source of "truth" for RC updating. This relay will also mediate requests to the + // 12 selected active RID's for RID fetching + RouterID fetch_source; + // set of 12 randomly selected RID's from the client's set of routers + std::set rid_sources{}; + // logs the RID's that resulted in an error during RID fetching + std::set fail_sources{}; + // tracks the number of times each rid appears in the above responses + std::unordered_map fetch_counters{}; + + /** Failure counters: + - fetch_failures: tracks errors fetching RC's from the RC node and requesting RID's + from the 12 RID sources. Errors in the individual RID sets are NOT counted towards + this, their performance as a group is evaluated wholistically + - bootstrap_failures: tracks errors fetching both RC's from bootstrasps and RID requests + they mediate. This is a different counter as we only bootstrap in problematic cases + */ + std::atomic fetch_failures{0}, bootstrap_attempts{0}; + + std::atomic _using_bootstrap_fallback{false}, _needs_rebootstrap{false}, + _needs_initial_fetch{true}, _fetching_initial{false}, _initial_completed{false}; + + bool + want_rc(const RouterID& rid) const; /// asynchronously remove the files for a set of rcs on disk given their public ident key void @@ -38,99 +187,101 @@ namespace llarp /// get filename of an RC file given its public ident key fs::path - get_path_by_pubkey(RouterID pk) const; - - std::unordered_map bootstraps; - - // Router lists for snodes - // whitelist = active routers - std::unordered_set router_whitelist; - // greylist = fully funded, but decommissioned routers - std::unordered_set router_greylist; - // greenlist = registered but not fully-staked routers - std::unordered_set router_greenlist; - // all registered relays (snodes) - std::unordered_set registered_routers; - std::unordered_map last_rc_update_times; - - // Router list for clients - std::unordered_set client_known_routers; + get_path_by_pubkey(const RouterID& pk) const; - // only ever use to specific edges as path first-hops - std::unordered_set pinned_edges; - - // rc update info - RouterID rc_fetch_source; - rc_time last_rc_update_relay_timestamp; - static constexpr auto ROUTER_ID_SOURCE_COUNT = 12; - std::unordered_set router_id_fetch_sources; - std::unordered_map> router_id_fetch_responses; - // process responses once all are received (or failed/timed out) - size_t router_id_response_count{0}; - bool router_id_fetch_in_progress{false}; + public: + explicit NodeDB( + fs::path rootdir, std::function)> diskCaller, Router* r); - bool - want_rc(const RouterID& rid) const; + /// in memory nodedb + NodeDB(); - public: - void - set_bootstrap_routers(const std::set& rcs); + const std::set& + get_known_rids() const + { + return known_rids; + } - const std::unordered_set& - whitelist() const + const std::set& + get_known_rcs() const { - return router_whitelist; + return known_rcs; } - const std::unordered_set& - greylist() const + std::optional + get_rc_by_rid(const RouterID& rid); + + bool + is_initial_fetching() const { - return router_greylist; + return _fetching_initial; } - const std::unordered_set& - get_registered_routers() const + bool + initial_fetch_completed() const { - return registered_routers; + return _initial_completed; } - const std::unordered_map& - get_rcs() const + bool + needs_initial_fetch() const { - return known_rcs; + return _needs_initial_fetch; } - const std::unordered_map& - get_last_rc_update_times() const + bool + needs_rebootstrap() const { - return last_rc_update_times; + return _needs_rebootstrap; } - // If we receive a bad set of RCs from our current RC source relay, we consider - // that relay to be a bad source of RCs and we randomly choose a new one. - // - // When using a new RC fetch relay, we first re-fetch the full RC list and, if - // that aligns with our RouterID list, we go back to periodic updates from that relay. - // - // This will respect edge-pinning and attempt to use a relay we already have - // a connection with. void - rotate_rc_source(); + ingest_bootstrap_seed(); + + bool + ingest_fetched_rcs(std::set rcs); + + bool + process_fetched_rcs(std::set& rcs); void - ingest_rcs(RouterID source, std::vector rcs, rc_time timestamp); + ingest_rid_fetch_responses(const RouterID& source, std::set ids = {}); + + bool + process_fetched_rids(); void - ingest_router_ids(RouterID source, std::vector ids); + fetch_initial(bool is_snode = false); + // RouterContact fetching void - fetch_rcs(); + fetch_rcs(bool initial = false); + void + post_fetch_rcs(bool initial = false); + void + fetch_rcs_result(bool initial = false, bool error = false); + // RouterID fetching + void + fetch_rids(bool initial = false); void - fetch_router_ids(); + post_fetch_rids(bool initial = false); + void + fetch_rids_result(bool initial = false); + // Bootstrap fallback fetching + void + fallback_to_bootstrap(); void - select_router_id_sources(std::unordered_set excluded = {}); + post_snode_bootstrap(); + void + bootstrap_cooldown(); + + // Populate rid_sources with random sample from known_rids. A set of rids is passed + // if only specific RID's need to be re-selected; to re-select all, pass the member + // variable ::known_rids + void + reselect_router_id_sources(std::set specific); void set_router_whitelist( @@ -160,7 +311,7 @@ namespace llarp bool is_path_allowed(const RouterID& remote) const { - return router_whitelist.count(remote); + return known_rids.count(remote); } // if pinned edges were specified, the remote must be in that set, else any remote @@ -168,17 +319,83 @@ namespace llarp bool is_first_hop_allowed(const RouterID& remote) const; - const std::unordered_set& - get_pinned_edges() const + std::set& + pinned_edges() { - return pinned_edges; + return _pinned_edges; } - explicit NodeDB( - fs::path rootdir, std::function)> diskCaller, Router* r); + void + store_bootstraps(); - /// in memory nodedb - NodeDB(); + size_t + num_bootstraps() const + { + return _bootstraps.size(); + } + + bool + has_bootstraps() const + { + return _bootstraps.empty(); + } + + const BootstrapList& + bootstrap_list() const + { + return _bootstraps; + } + + BootstrapList& + bootstrap_list() + { + return _bootstraps; + } + + void + set_bootstrap_routers(BootstrapList& from_router); + + const std::set& + whitelist() const + { + return _router_whitelist; + } + + const std::set& + greylist() const + { + return _router_greylist; + } + + std::set& + registered_routers() + { + return _registered_routers; + } + + const std::set& + registered_routers() const + { + return _registered_routers; + } + + const std::set& + get_rcs() const + { + return known_rcs; + } + + // const std::unordered_map& + // get_rcs() const + // { + // return known_rcs; + // } + + const std::unordered_map& + get_last_rc_update_times() const + { + return last_rc_update_times; + } /// load all known_rcs from disk syncrhonously void @@ -190,7 +407,10 @@ namespace llarp /// the number of RCs that are loaded from disk size_t - num_loaded() const; + num_rcs() const; + + size_t + num_rids() const; /// do periodic tasks like flush to disk and expiration void @@ -206,41 +426,78 @@ namespace llarp /// return true if we have an rc by its ident pubkey bool - has_rc(RouterID pk) const; + has_rc(const RouterID& pk) const; + + bool + has_rc(const RemoteRC& rc) const; /// maybe get an rc by its ident pubkey std::optional - get_rc(RouterID pk) const; + get_rc(const RouterID& pk) const; - template std::optional - GetRandom(Filter visit) const - { - return router.loop()->call_get([visit]() -> std::optional { - std::vector known_rcs; - for (const auto& entry : known_rcs) - known_rcs.push_back(entry); + get_random_rc() const; - std::shuffle(known_rcs.begin(), known_rcs.end(), llarp::csrng); + // Get `n` random RCs from all RCs we know about. If `exact` is true then we require n matches + // (and otherwise return nullopt); otherwise we return whatever we found, or nullopt if we find + // nothing at all. + std::optional> + get_n_random_rcs(size_t n, bool exact = false) const; - for (const auto entry : known_rcs) - { - if (visit(entry->second)) - return entry->second; - } + /** The following random conditional functions utilize a simple implementation of reservoir + sampling to return either 1 or n random RC's using only one pass through the set of RC's. - return std::nullopt; - }); + Pseudocode: + - begin iterating through the set + - load the first n (or 1) that pass hook(n) into a list Selected[] + - for all that pass the hook, increment i, tracking the number seen thus far + - generate a random integer x from 0 to i + - x < n ? Selected[x] = current : continue; + */ + + std::optional + get_random_rc_conditional(std::function hook) const; + + std::optional> + get_n_random_rcs_conditional( + size_t n, std::function hook, bool exact = false) const; + + // Updates `current` to not contain any of the elements of `replace` and resamples (up to + // `target_size`) from population to refill it. + template + void + replace_subset( + std::set& current, + const std::set& replace, + std::set population, + size_t target_size, + RNG&& rng) + { + // Remove the ones we are replacing from current: + current.erase(replace.begin(), replace.end()); + + // Remove ones we are replacing, and ones we already have, from the population so that we + // won't reselect them: + population.erase(replace.begin(), replace.end()); + population.erase(current.begin(), current.end()); + + if (current.size() < target_size) + std::sample( + population.begin(), + population.end(), + std::inserter(current, current.end()), + target_size - current.size(), + rng); } /// visit all known_rcs template void - VisitAll(Visit visit) const + visit_all(Visit visit) const { - router.loop()->call([this, visit]() { + _router.loop()->call([this, visit]() { for (const auto& item : known_rcs) - visit(item.second); + visit(item); }); } @@ -251,26 +508,74 @@ namespace llarp /// remove an entry given a filter that inspects the rc template void - RemoveIf(Filter visit) + remove_if(Filter visit) { - router.loop()->call([this, visit]() { + _router.loop()->call([this, visit]() { std::unordered_set removed; - auto itr = known_rcs.begin(); - while (itr != known_rcs.end()) + + for (auto itr = rc_lookup.begin(); itr != rc_lookup.end();) { if (visit(itr->second)) { - removed.insert(itr->second.router_id()); - itr = known_rcs.erase(itr); + removed.insert(itr->first); + known_rcs.erase(itr->second); + itr = rc_lookup.erase(itr); } else ++itr; } + if (not removed.empty()) remove_many_from_disk_async(std::move(removed)); }); } + template < + typename ID_t, + std::enable_if_t || std::is_same_v, int> = 0> + void + process_results( + std::set unconfirmed, std::set>& container, std::set& known) + { + // before we add the unconfirmed set, we check to see if our local set of unconfirmed + // rcs/rids appeared in the latest unconfirmed set; if so, we will increment their number + // of verifications and reset the attempts counter. Once appearing in 3 different requests, + // the rc/rid will be "verified" and promoted to the known_{rcs,rids} container + for (auto itr = container.begin(); itr != container.end();) + { + auto& id = itr->id; + auto& count = const_cast(itr->attempts); + auto& verifications = const_cast(itr->verifications); + + if (auto found = unconfirmed.find(id); found != unconfirmed.end()) + { + if (++verifications >= CONFIRMATION_THRESHOLD) + { + if constexpr (std::is_same_v) + put_rc_if_newer(id); + else + known.emplace(id); + itr = container.erase(itr); + } + else + { + // reset attempt counter and continue + count = 0; + ++itr; + } + + unconfirmed.erase(found); + } + + itr = (++count >= MAX_CONFIRMATION_ATTEMPTS) ? container.erase(itr) : ++itr; + } + + for (auto& id : unconfirmed) + { + container.emplace(std::move(id)); + } + } + /// remove rcs that are older than we want to keep. For relays, this is when /// they become "outdated" (i.e. 12hrs). Clients will hang on to them until /// they are fully "expired" (i.e. 30 days), as the client may go offline for @@ -280,18 +585,29 @@ namespace llarp /// put (or replace) the RC if we consider it valid (want_rc). returns true if put. bool - put_rc( - RemoteRC rc, - rc_time now = - std::chrono::time_point_cast(std::chrono::system_clock::now())); + put_rc(RemoteRC rc, rc_time now = time_point_now()); /// if we consider it valid (want_rc), /// put this rc into the cache if it is not there or is newer than the one there already /// returns true if the rc was inserted bool - put_rc_if_newer( - RemoteRC rc, - rc_time now = - std::chrono::time_point_cast(std::chrono::system_clock::now())); + put_rc_if_newer(RemoteRC rc); + + void + verify_gossip_bfetch_rc(const RemoteRC& rc); + + bool + verify_store_gossip_rc(const RemoteRC& rc); }; } // namespace llarp + +namespace std +{ + template <> + struct hash> : public hash + {}; + + template <> + struct hash> : hash + {}; +} // namespace std diff --git a/llarp/path/path.cpp b/llarp/path/path.cpp index 73de6465f..8fd894d86 100644 --- a/llarp/path/path.cpp +++ b/llarp/path/path.cpp @@ -44,7 +44,7 @@ namespace llarp::path intro.router = hops[hsz - 1].rc.router_id(); intro.path_id = hops[hsz - 1].txID; if (auto parent = m_PathSet.lock()) - EnterState(ePathBuilding, parent->Now()); + EnterState(BUILDING, parent->Now()); } bool @@ -186,7 +186,7 @@ namespace llarp::path { if (Expired(llarp::time_now_ms())) return false; - return intro.latency > 0s && _status == ePathEstablished; + return intro.latency > 0s && _status == ESTABLISHED; } bool @@ -227,12 +227,12 @@ namespace llarp::path if (now == 0s) now = router.now(); - if (st == ePathFailed) + if (st == FAILED) { _status = st; return; } - if (st == ePathExpired && _status == ePathBuilding) + if (st == EXPIRED && _status == BUILDING) { _status = st; if (auto parent = m_PathSet.lock()) @@ -240,16 +240,16 @@ namespace llarp::path parent->HandlePathBuildTimeout(shared_from_this()); } } - else if (st == ePathBuilding) + else if (st == BUILDING) { LogInfo("path ", name(), " is building"); buildStarted = now; } - else if (st == ePathEstablished && _status == ePathBuilding) + else if (st == ESTABLISHED && _status == BUILDING) { LogInfo("path ", name(), " is built, took ", ToString(now - buildStarted)); } - else if (st == ePathTimeout && _status == ePathEstablished) + else if (st == TIMEOUT && _status == ESTABLISHED) { LogInfo("path ", name(), " died"); _status = st; @@ -258,11 +258,11 @@ namespace llarp::path parent->HandlePathDied(shared_from_this()); } } - else if (st == ePathEstablished && _status == ePathTimeout) + else if (st == ESTABLISHED && _status == TIMEOUT) { LogInfo("path ", name(), " reanimated"); } - else if (st == ePathIgnore) + else if (st == IGNORE) { LogInfo("path ", name(), " ignored"); } @@ -309,22 +309,22 @@ namespace llarp::path switch (_status) { - case ePathBuilding: + case BUILDING: obj["status"] = "building"; break; - case ePathEstablished: + case ESTABLISHED: obj["status"] = "established"; break; - case ePathTimeout: + case TIMEOUT: obj["status"] = "timeout"; break; - case ePathExpired: + case EXPIRED: obj["status"] = "expired"; break; - case ePathFailed: + case FAILED: obj["status"] = "failed"; break; - case ePathIgnore: + case IGNORE: obj["status"] = "ignored"; break; default: @@ -385,7 +385,7 @@ namespace llarp::path m_RXRate = 0; m_TXRate = 0; - if (_status == ePathBuilding) + if (_status == BUILDING) { if (buildStarted == 0s) return; @@ -396,13 +396,13 @@ namespace llarp::path { LogWarn(name(), " waited for ", ToString(dlt), " and no path was built"); r->router_profiling().MarkPathFail(this); - EnterState(ePathExpired, now); + EnterState(EXPIRED, now); return; } } } // check to see if this path is dead - if (_status == ePathEstablished) + if (_status == ESTABLISHED) { auto dlt = now - m_LastLatencyTestTime; if (dlt > path::LATENCY_INTERVAL && m_LastLatencyTestID == 0) @@ -420,13 +420,13 @@ namespace llarp::path { LogWarn(name(), " waited for ", ToString(dlt), " and path looks dead"); r->router_profiling().MarkPathFail(this); - EnterState(ePathTimeout, now); + EnterState(TIMEOUT, now); } } - if (_status == ePathIgnore and now - m_LastRecvMessage >= path::ALIVE_TIMEOUT) + if (_status == IGNORE and now - m_LastRecvMessage >= path::ALIVE_TIMEOUT) { // clean up this path as we dont use it anymore - EnterState(ePathExpired, now); + EnterState(EXPIRED, now); } } @@ -436,15 +436,15 @@ namespace llarp::path bool Path::Expired(llarp_time_t now) const { - if (_status == ePathFailed) + if (_status == FAILED) return true; - if (_status == ePathBuilding) + if (_status == BUILDING) return false; - if (_status == ePathTimeout) + if (_status == TIMEOUT) { return now >= m_LastRecvMessage + PathReanimationTimeout; } - if (_status == ePathEstablished or _status == ePathIgnore) + if (_status == ESTABLISHED or _status == IGNORE) { return now >= ExpireTime(); } diff --git a/llarp/path/path_context.cpp b/llarp/path/path_context.cpp index a8e30a5a9..b8d9224f4 100644 --- a/llarp/path/path_context.cpp +++ b/llarp/path/path_context.cpp @@ -13,19 +13,19 @@ namespace llarp::path {} void - PathContext::AllowTransit() + PathContext::allow_transit() { m_AllowTransit = true; } bool - PathContext::AllowingTransit() const + PathContext::is_transit_allowed() const { return m_AllowTransit; } bool - PathContext::CheckPathLimitHitByIP(const IpAddress& ip) + PathContext::check_path_limit_hit_by_ip(const IpAddress& ip) { #ifdef TESTNET return false; @@ -39,21 +39,6 @@ namespace llarp::path #endif } - bool - PathContext::CheckPathLimitHitByIP(const std::string& ip) - { -#ifdef TESTNET - return false; -#else - IpAddress remote{ip}; - // null out the port -- we don't care about it for path limiting purposes - remote.setPort(0); - // try inserting remote address by ip into decaying hash set - // if it cannot insert it has hit a limit - return not path_limits.Insert(remote); -#endif - } - const EventLoop_ptr& PathContext::loop() { @@ -102,7 +87,7 @@ namespace llarp::path } bool - PathContext::HasTransitHop(const TransitHopInfo& info) + PathContext::has_transit_hop(const TransitHopInfo& info) { TransitHopID downstream{info.downstream, info.rxID}; if (transit_hops.count(downstream)) @@ -125,7 +110,7 @@ namespace llarp::path } Path_ptr - PathContext::GetPath(const PathID_t& path_id) + PathContext::get_path(const PathID_t& path_id) { if (auto itr = own_paths.find(path_id); itr != own_paths.end()) return itr->second; @@ -186,7 +171,7 @@ namespace llarp::path } void - PathContext::PutTransitHop(std::shared_ptr hop) + PathContext::put_transit_hop(std::shared_ptr hop) { TransitHopID downstream{hop->info.downstream, hop->info.rxID}; TransitHopID upstream{hop->info.upstream, hop->info.txID}; diff --git a/llarp/path/path_context.hpp b/llarp/path/path_context.hpp index 3f6b4485c..87524951e 100644 --- a/llarp/path/path_context.hpp +++ b/llarp/path/path_context.hpp @@ -62,28 +62,25 @@ namespace llarp::path ExpirePaths(llarp_time_t now); void - AllowTransit(); + allow_transit(); void - RejectTransit(); + reject_transit(); bool - CheckPathLimitHitByIP(const IpAddress& ip); + check_path_limit_hit_by_ip(const IpAddress& ip); bool - CheckPathLimitHitByIP(const std::string& ip); + is_transit_allowed() const; bool - AllowingTransit() const; - - bool - HasTransitHop(const TransitHopInfo& info); + has_transit_hop(const TransitHopInfo& info); void - PutTransitHop(std::shared_ptr hop); + put_transit_hop(std::shared_ptr hop); Path_ptr - GetPath(const PathID_t& path_id); + get_path(const PathID_t& path_id); bool TransitHopPreviousIsRouter(const PathID_t& path, const RouterID& r); @@ -125,7 +122,7 @@ namespace llarp::path /// current number of paths we created in status uint64_t - CurrentOwnedPaths(path::PathStatus status = path::PathStatus::ePathEstablished); + CurrentOwnedPaths(path::PathStatus status = path::PathStatus::ESTABLISHED); Router* router() const diff --git a/llarp/path/pathbuilder.cpp b/llarp/path/pathbuilder.cpp index 49b080cd0..6eb1cc3b7 100644 --- a/llarp/path/pathbuilder.cpp +++ b/llarp/path/pathbuilder.cpp @@ -220,11 +220,10 @@ namespace llarp { std::optional found = std::nullopt; router->for_each_connection([&](link::Connection& conn) { - const auto& rc = conn.remote_rc; - const auto& rid = rc.router_id(); + RouterID rid{conn.conn->remote_key()}; #ifndef TESTNET - if (router->IsBootstrapNode(rid)) + if (router->is_bootstrap_node(rid)) return; #endif if (exclude.count(rid)) @@ -236,7 +235,7 @@ namespace llarp if (router->router_profiling().IsBadForPath(rid)) return; - found = rc; + found = router->node_db()->get_rc(rid); }); return found; } @@ -244,13 +243,13 @@ namespace llarp std::optional> Builder::GetHopsForBuild() { - auto filter = [r = router](const auto& rc) -> bool { + auto filter = [r = router](const RemoteRC& rc) -> bool { return not r->router_profiling().IsBadForPath(rc.router_id(), 1); }; - if (const auto maybe = router->node_db()->GetRandom(filter)) - { + + if (auto maybe = router->node_db()->get_random_rc_conditional(filter)) return GetHopsAlignedToForBuild(maybe->router_id()); - } + return std::nullopt; } @@ -262,7 +261,7 @@ namespace llarp const auto now = Now(); for (auto& item : m_Paths) { - item.second->EnterState(ePathIgnore, now); + item.second->EnterState(IGNORE, now); } return true; } @@ -276,7 +275,7 @@ namespace llarp bool Builder::ShouldRemove() const { - return IsStopped() and NumInStatus(ePathEstablished) == 0; + return IsStopped() and NumInStatus(ESTABLISHED) == 0; } bool @@ -359,6 +358,7 @@ namespace llarp if (r->router_profiling().IsBadForPath(rid, 1)) return false; + for (const auto& hop : hopsSet) { if (hop.router_id() == rid) @@ -373,7 +373,7 @@ namespace llarp return rc.router_id() != endpointRC.router_id(); }; - if (const auto maybe = router->node_db()->GetRandom(filter)) + if (auto maybe = router->node_db()->get_random_rc_conditional(filter)) hops.emplace_back(*maybe); else return std::nullopt; @@ -505,40 +505,40 @@ namespace llarp // be worth doing sooner rather than later. Leaving some TODOs below where fail // and success live. auto response_cb = [path](oxen::quic::message m) { + if (m) + { + // TODO: inform success (what this means needs revisiting, badly) + path->EnterState(path::ESTABLISHED); + return; + } + try { - if (m) - { - // TODO: inform success (what this means needs revisiting, badly) - path->EnterState(path::ePathEstablished); - return; - } + // TODO: inform failure (what this means needs revisiting, badly) if (m.timed_out) { - log::warning(path_cat, "Path build timed out"); + log::warning(path_cat, "Path build request timed out!"); + path->EnterState(path::TIMEOUT); } else { oxenc::bt_dict_consumer d{m.body()}; auto status = d.require(messages::STATUS_KEY); log::warning(path_cat, "Path build returned failure status: {}", status); + path->EnterState(path::FAILED); } } catch (const std::exception& e) { - log::warning(path_cat, "Failed parsing path build response."); + log::warning(path_cat, "Exception caught parsing path build response: {}", e.what()); } - - // TODO: inform failure (what this means needs revisiting, badly) - path->EnterState(path::ePathFailed); }; if (not router->send_control_message( path->upstream(), "path_build", std::move(frames).str(), std::move(response_cb))) { log::warning(path_cat, "Error sending path_build control message"); - // TODO: inform failure (what this means needs revisiting, badly) - path->EnterState(path::ePathFailed, router->now()); + path->EnterState(path::FAILED, router->now()); } } diff --git a/llarp/path/pathset.cpp b/llarp/path/pathset.cpp index 1b1012448..fc24acb51 100644 --- a/llarp/path/pathset.cpp +++ b/llarp/path/pathset.cpp @@ -13,10 +13,10 @@ namespace llarp::path PathSet::ShouldBuildMore(llarp_time_t now) const { (void)now; - const auto building = NumInStatus(ePathBuilding); + const auto building = NumInStatus(BUILDING); if (building >= numDesiredPaths) return false; - const auto established = NumInStatus(ePathEstablished); + const auto established = NumInStatus(ESTABLISHED); return established < numDesiredPaths; } @@ -255,7 +255,7 @@ namespace llarp::path auto itr = m_Paths.begin(); while (itr != m_Paths.end()) { - if (itr->second->Status() == ePathEstablished && itr->second->SupportsAnyRoles(roles)) + if (itr->second->Status() == ESTABLISHED && itr->second->SupportsAnyRoles(roles)) ++count; ++itr; } diff --git a/llarp/path/pathset.hpp b/llarp/path/pathset.hpp index c228823ac..a93608f43 100644 --- a/llarp/path/pathset.hpp +++ b/llarp/path/pathset.hpp @@ -47,12 +47,12 @@ namespace llarp /// status of a path enum PathStatus { - ePathBuilding, - ePathEstablished, - ePathTimeout, - ePathFailed, - ePathIgnore, - ePathExpired + BUILDING, + ESTABLISHED, + TIMEOUT, + FAILED, + IGNORE, + EXPIRED }; /// Stats about all our path builds diff --git a/llarp/router/route_poker.cpp b/llarp/router/route_poker.cpp index b2a9a1ed5..d578ff7d4 100644 --- a/llarp/router/route_poker.cpp +++ b/llarp/router/route_poker.cpp @@ -219,7 +219,7 @@ namespace llarp // explicit route pokes for first hops router.for_each_connection( - [this](link::Connection conn) { add_route(conn.remote_rc.addr()); }); + [this](link::Connection conn) { add_route(conn.conn->remote()); }); add_route(router.link_manager().local()); // add default route @@ -238,7 +238,7 @@ namespace llarp { // unpoke routes for first hops router.for_each_connection( - [this](link::Connection conn) { delete_route(conn.remote_rc.addr()); }); + [this](link::Connection conn) { delete_route(conn.conn->remote()); }); if (is_enabled() and is_up) { vpn::AbstractRouteManager& route = router.vpn_platform()->RouteManager(); diff --git a/llarp/router/router.cpp b/llarp/router/router.cpp index 7a23a3748..85e3b7ba3 100644 --- a/llarp/router/router.cpp +++ b/llarp/router/router.cpp @@ -45,7 +45,7 @@ namespace llarp , _disk_thread{_lmq->add_tagged_thread("disk")} , _rpc_server{nullptr} , _randomStartDelay{platform::is_simulation ? std::chrono::milliseconds{(llarp::randint() % 1250) + 2000} : 0s} - , _link_manager{*this} + // , _link_manager{*this} , _hidden_service_context{this} { _key_manager = std::make_shared(); @@ -58,14 +58,8 @@ namespace llarp loop_wakeup = _loop->make_waker([this]() { PumpLL(); }); } - Router::~Router() - { - _contacts.reset(); - } - // TODO: investigate changes needed for libquic integration // still needed at all? - // TODO: No. The answer is No. // TONUKE: EVERYTHING ABOUT THIS void @@ -78,7 +72,6 @@ namespace llarp llarp::LogTrace("Router::PumpLL() end"); } - // TOFIX: this util::StatusObject Router::ExtractStatus() const { @@ -87,12 +80,10 @@ namespace llarp return util::StatusObject{ {"running", true}, - {"numNodesKnown", _node_db->num_loaded()}, - {"contacts", _contacts->ExtractStatus()}, + {"numNodesKnown", _node_db->num_rcs()}, {"services", _hidden_service_context.ExtractStatus()}, {"exit", _exit_context.ExtractStatus()}, - {"links", _link_manager.extract_status()}, - /* {"outboundMessages", _outboundMessageHandler.ExtractStatus()} */}; + {"links", _link_manager->extract_status()}}; } // TODO: investigate changes needed for libquic integration @@ -104,7 +95,7 @@ namespace llarp auto services = _hidden_service_context.ExtractStatus(); - auto link_types = _link_manager.extract_status(); + auto link_types = _link_manager->extract_status(); uint64_t tx_rate = 0; uint64_t rx_rate = 0; @@ -177,7 +168,7 @@ namespace llarp {"uptime", to_json(Uptime())}, {"numPathsBuilt", pathsCount}, {"numPeersConnected", peers}, - {"numRoutersKnown", _node_db->num_loaded()}, + {"numRoutersKnown", _node_db->num_rcs()}, {"ratio", ratio}, {"txRate", tx_rate}, {"rxRate", rx_rate}, @@ -193,6 +184,18 @@ namespace llarp return stats; } + bool + Router::needs_initial_fetch() const + { + return _node_db->needs_initial_fetch(); + } + + bool + Router::needs_rebootstrap() const + { + return _node_db->needs_rebootstrap(); + } + void Router::Freeze() { @@ -211,34 +214,30 @@ namespace llarp std::unordered_set peer_pubkeys; - for_each_connection([&peer_pubkeys](link::Connection& conn) { - peer_pubkeys.emplace(conn.remote_rc.router_id()); - }); + for_each_connection( + [&peer_pubkeys](link::Connection& conn) { peer_pubkeys.emplace(conn.conn->remote_key()); }); loop()->call([this, &peer_pubkeys]() { for (auto& pk : peer_pubkeys) - _link_manager.close_connection(pk); + _link_manager->close_connection(pk); }); } void Router::persist_connection_until(const RouterID& remote, llarp_time_t until) { - _link_manager.set_conn_persist(remote, until); + _link_manager->set_conn_persist(remote, until); } std::optional Router::GetRandomGoodRouter() { if (is_service_node()) - { return node_db()->get_random_whitelist_router(); - } - if (auto maybe = node_db()->GetRandom([](const auto&) -> bool { return true; })) - { + if (auto maybe = node_db()->get_random_rc()) return maybe->router_id(); - } + return std::nullopt; } @@ -248,22 +247,10 @@ namespace llarp loop_wakeup->Trigger(); } - void - Router::connect_to(const RouterID& rid) - { - _link_manager.connect_to(rid); - } - - void - Router::connect_to(const RemoteRC& rc) - { - _link_manager.connect_to(rc); - } - bool Router::send_data_message(const RouterID& remote, std::string payload) { - return _link_manager.send_data_message(remote, std::move(payload)); + return _link_manager->send_data_message(remote, std::move(payload)); } bool @@ -273,20 +260,20 @@ namespace llarp std::string body, std::function func) { - return _link_manager.send_control_message( + return _link_manager->send_control_message( remote, std::move(ep), std::move(body), std::move(func)); } void Router::for_each_connection(std::function func) { - return _link_manager.for_each_connection(func); + return _link_manager->for_each_connection(func); } bool Router::EnsureIdentity() { - _encryption = _key_manager->encryptionKey; + _encryption = _key_manager->encryption_key; if (is_service_node()) { @@ -329,13 +316,19 @@ namespace llarp } else { - _identity = _key_manager->identityKey; + _identity = _key_manager->identity_key; } if (_identity.IsZero()) + { + log::critical(logcat, "FUCK @ line:{}", __LINE__); return false; + } if (_encryption.IsZero()) + { + log::critical(logcat, "FUCK @ line:{}", __LINE__); return false; + } return true; } @@ -348,16 +341,35 @@ namespace llarp _config = std::move(c); auto& conf = *_config; - // Do logging config as early as possible to get the configured log level applied + const auto& netid = conf.router.net_id; + + if (not netid.empty() and netid != llarp::LOKINET_DEFAULT_NETID) + { + _testnet = netid == llarp::LOKINET_TESTNET_NETID; + _testing_disabled = conf.lokid.disable_testing; + + RouterContact::ACTIVE_NETID = netid; + + if (_testing_disabled and not _testnet) + throw std::runtime_error{"Error: reachability testing can only be disabled on testnet!"}; + + auto err = "Lokinet network ID set to {}, NOT mainnet! {}"_format( + netid, + _testnet ? "Please ensure your local instance is configured to operate on testnet" + : "Local lokinet instance will attempt to run on the specified network"); + log::critical(logcat, err); + } // Backwards compat: before 0.9.10 we used `type=file` with `file=|-|stdout` for print mode auto log_type = conf.logging.type; + if (log_type == log::Type::File && (conf.logging.file == "stdout" || conf.logging.file == "-" || conf.logging.file.empty())) log_type = log::Type::Print; if (log::get_level_default() != log::Level::off) log::reset_level(conf.logging.level); + log::clear_sinks(); log::add_sink(log_type, log_type == log::Type::System ? "lokinet" : conf.logging.file); @@ -367,6 +379,9 @@ namespace llarp else llarp::logRingBuffer = nullptr; + // TESTNET: + oxen::log::set_level("quic", oxen::log::Level::critical); + log::debug(logcat, "Configuring router"); _is_service_node = conf.router.is_relay; @@ -393,15 +408,14 @@ namespace llarp logcat, _is_service_node ? "Running as a relay (service node)" : "Running as a client"); if (_is_service_node) - { _rpc_client->ConnectAsync(rpc_addr); - } log::debug(logcat, "Initializing key manager"); if (not _key_manager->initialize(conf, true, isSNode)) throw std::runtime_error("KeyManager failed to initialize"); log::debug(logcat, "Initializing from configuration"); + if (!from_config(conf)) throw std::runtime_error("FromConfig() failed"); @@ -421,7 +435,7 @@ namespace llarp Router::insufficient_peers() const { constexpr int KnownPeerWarningThreshold = 5; - return node_db()->num_loaded() < KnownPeerWarningThreshold; + return node_db()->num_rcs() < KnownPeerWarningThreshold; } std::optional @@ -429,7 +443,7 @@ namespace llarp { // If we're in the white or gray list then we *should* be establishing connections to other // routers, so if we have almost no peers then something is almost certainly wrong. - if (appears_funded() and insufficient_peers()) + if (appears_funded() and insufficient_peers() and not _testing_disabled) return "too few peer connections; lokinet is not adequately connected to the network"; return std::nullopt; } @@ -445,14 +459,6 @@ namespace llarp is_running.store(false); } - bool - Router::ParseRoutingMessageBuffer( - const llarp_buffer_t&, path::AbstractHopHandler&, const PathID_t&) - { - // TODO: will go away with the removal of flush upstream/downstream - return false; - } - bool Router::have_snode_whitelist() const { @@ -462,25 +468,27 @@ namespace llarp bool Router::appears_decommed() const { - return have_snode_whitelist() and node_db()->greylist().count(pubkey()); + return _is_service_node and have_snode_whitelist() and node_db()->greylist().count(pubkey()); } bool Router::appears_funded() const { - return have_snode_whitelist() and node_db()->is_connection_allowed(pubkey()); + return _is_service_node and have_snode_whitelist() + and node_db()->is_connection_allowed(pubkey()); } bool Router::appears_registered() const { - return have_snode_whitelist() and node_db()->get_registered_routers().count(pubkey()); + return _is_service_node and have_snode_whitelist() + and node_db()->registered_routers().count(pubkey()); } bool Router::can_test_routers() const { - return appears_funded(); + return appears_funded() and not _testing_disabled; } bool @@ -501,21 +509,22 @@ namespace llarp } size_t - Router::NumberOfConnectedRouters() const + Router::num_router_connections() const { - return _link_manager.get_num_connected(); + return _link_manager->get_num_connected_routers(); } size_t - Router::NumberOfConnectedClients() const + Router::num_client_connections() const { - return _link_manager.get_num_connected_clients(); + return _link_manager->get_num_connected_clients(); } void Router::save_rc() { - _node_db->put_rc(router_contact.view()); + // _node_db->put_rc(router_contact.view()); + log::info(logcat, "Saving RC file to {}", our_rc_file); queue_disk_io([&]() { router_contact.write(our_rc_file); }); } @@ -524,143 +533,73 @@ namespace llarp { // Set netid before anything else log::debug(logcat, "Network ID set to {}", conf.router.net_id); - if (!conf.router.net_id.empty() - && strcmp(conf.router.net_id.c_str(), llarp::LOKINET_DEFAULT_NETID) != 0) - { - const auto& netid = conf.router.net_id; - llarp::LogWarn( - "!!!! you have manually set netid to be '", - netid, - "' which does not equal '", - llarp::LOKINET_DEFAULT_NETID, - "' you will run as a different network, good luck " - "and don't forget: something something MUH traffic " - "shape correlation !!!!"); - } // Router config - _link_manager.max_connected_routers = conf.router.max_connected_routers; - _link_manager.min_connected_routers = conf.router.min_connected_routers; + client_router_connections = conf.router.client_router_connections; + + encryption_keyfile = _key_manager->enckey_path; + our_rc_file = _key_manager->rc_path; + transport_keyfile = _key_manager->transkey_path; + identity_keyfile = _key_manager->idkey_path; - encryption_keyfile = _key_manager->m_encKeyPath; - our_rc_file = _key_manager->m_rcPath; - transport_keyfile = _key_manager->m_transportKeyPath; - identity_keyfile = _key_manager->m_idKeyPath; + std::optional paddr = (conf.router.public_ip) ? conf.router.public_ip + : (conf.links.public_addr) ? conf.links.public_addr + : std::nullopt; + std::optional pport = (conf.router.public_port) ? conf.router.public_port + : (conf.links.public_port) ? conf.links.public_port + : std::nullopt; - if (auto maybe_ip = conf.links.public_addr) - _ourAddress = var::visit([](auto&& ip) { return SockAddr{ip}; }, *maybe_ip); - else if (auto maybe_ip = conf.router.public_ip) - _ourAddress = var::visit([](auto&& ip) { return SockAddr{ip}; }, *maybe_ip); + if (pport.has_value() and not paddr.has_value()) + throw std::runtime_error{"If public-port is specified, public-addr must be as well!"}; - if (_ourAddress) + if (conf.links.listen_addr) { - if (auto maybe_port = conf.links.public_port) - _ourAddress->setPort(*maybe_port); - else if (auto maybe_port = conf.router.public_port) - _ourAddress->setPort(*maybe_port); - else - throw std::runtime_error{"public ip provided without public port"}; - log::debug(logcat, "Using {} for our public address", *_ourAddress); + _listen_address = *conf.links.listen_addr; } else - log::debug(logcat, "No explicit public address given; will auto-detect during link setup"); + { + if (paddr or pport) + throw std::runtime_error{"Must specify [bind]:listen in config with public ip/addr!"}; + + if (auto maybe_addr = net().get_best_public_address(true, DEFAULT_LISTEN_PORT)) + _listen_address = std::move(*maybe_addr); + else + throw std::runtime_error{"Could not find net interface on current platform!"}; + } + + _public_address = (not paddr and not pport) + ? _listen_address + : oxen::quic::Address{*paddr, pport ? *pport : DEFAULT_LISTEN_PORT}; RouterContact::BLOCK_BOGONS = conf.router.block_bogons; auto& networkConfig = conf.network; /// build a set of strictConnectPubkeys - std::unordered_set strictConnectPubkeys; - if (not networkConfig.strict_connect.empty()) { const auto& val = networkConfig.strict_connect; + if (is_service_node()) throw std::runtime_error("cannot use strict-connect option as service node"); + if (val.size() < 2) throw std::runtime_error( "Must specify more than one strict-connect router if using strict-connect"); - strictConnectPubkeys.insert(val.begin(), val.end()); - log::debug(logcat, "{} strict-connect routers configured", val.size()); - } - - std::vector configRouters = conf.connect.routers; - configRouters.insert( - configRouters.end(), conf.bootstrap.files.begin(), conf.bootstrap.files.end()); - - // if our conf had no bootstrap files specified, try the default location of - // /bootstrap.signed. If this isn't present, leave a useful error message - // TODO: use constant - fs::path defaultBootstrapFile = conf.router.data_dir / "bootstrap.signed"; - if (configRouters.empty() and conf.bootstrap.routers.empty()) - { - if (fs::exists(defaultBootstrapFile)) - { - configRouters.push_back(defaultBootstrapFile); - } - } - - bootstrap_rc_list.clear(); - for (const auto& router : configRouters) - { - log::debug(logcat, "Loading bootstrap router list from {}", defaultBootstrapFile); - bootstrap_rc_list.read_from_file(router); - } - - for (const auto& rc : conf.bootstrap.routers) - { - bootstrap_rc_list.emplace(rc); - } - if (bootstrap_rc_list.empty() and not conf.bootstrap.seednode) - { - auto fallbacks = llarp::load_bootstrap_fallbacks(); - - if (bootstrap_rc_list.empty() and not conf.bootstrap.seednode) - { - // empty after trying fallback, if set - log::error( - logcat, - "No bootstrap routers were loaded. The default bootstrap file {} does not exist, and " - "loading fallback bootstrap RCs failed.", - defaultBootstrapFile); - throw std::runtime_error("No bootstrap nodes available."); - } + _node_db->pinned_edges().insert(val.begin(), val.end()); + log::debug(logcat, "{} strict-connect routers configured", val.size()); } - // in case someone has an old bootstrap file and is trying to use a bootstrap - // that no longer exists - for (auto it = bootstrap_rc_list.begin(); it != bootstrap_rc_list.end();) - { - if (it->is_obsolete_bootstrap()) - log::warning(logcat, "ignoring obsolete boostrap RC: {}", it->router_id()); - else if (not it->verify()) - log::warning(logcat, "ignoring invalid bootstrap RC: {}", it->router_id()); - else - { - ++it; - continue; - } - - // we are in one of the above error cases that we warned about: - it = bootstrap_rc_list.erase(it); - } + _bootstrap_seed = conf.bootstrap.seednode; - node_db()->set_bootstrap_routers(bootstrap_rc_list); + std::vector bootstrap_paths{std::move(conf.bootstrap.files)}; - if (conf.bootstrap.seednode) - LogInfo("we are a seed node"); - else - LogInfo("Loaded ", bootstrap_rc_list.size(), " bootstrap routers"); + fs::path default_bootstrap = conf.router.data_dir / "bootstrap.signed"; - // Init components after relevant config settings loaded - _link_manager.init(); + auto& bootstrap = _node_db->bootstrap_list(); - // FIXME: kludge for now, will be part of larger cleanup effort. - if (_is_service_node) - InitInboundLinks(); - else - InitOutboundLinks(); + bootstrap.populate_bootstraps(bootstrap_paths, default_bootstrap, not _bootstrap_seed); // profiling _profile_file = conf.router.data_dir / "profiles.dat"; @@ -695,45 +634,52 @@ namespace llarp } bool - Router::IsBootstrapNode(const RouterID r) const + Router::is_bootstrap_node(const RouterID r) const { - return std::count_if( - bootstrap_rc_list.begin(), - bootstrap_rc_list.end(), - [r](const RemoteRC& rc) -> bool { return rc.router_id() == r; }) - > 0; + return _node_db->has_bootstraps() ? _node_db->bootstrap_list().contains(r) : false; } bool Router::should_report_stats(llarp_time_t now) const { - static constexpr auto ReportStatsInterval = 1h; - return now - _last_stats_report > ReportStatsInterval; + return now - _last_stats_report > REPORT_STATS_INTERVAL; } void Router::report_stats() { const auto now = llarp::time_now_ms(); - log::info( - logcat, - "{} RCs loaded with {} bootstrap peers and {} router connections!", - node_db()->num_loaded(), - bootstrap_rc_list.size(), - NumberOfConnectedRouters()); - if (is_service_node()) + auto [in, out] = _link_manager->num_in_out(); + auto num_bootstraps = _node_db->num_bootstraps(); + auto num_rids = _node_db->num_rids(); + auto num_rcs = _node_db->num_rcs(); + auto num_router_conns = num_router_connections(); + + log::critical( + logcat, + "Local {} has {} RCs, {} RIDs, {} bootstrap peers, {}:{} (inbound:outbound) " + "conns ({} router, {} client)", + is_service_node() ? "Service Node" : "Client", + num_rcs, + num_rids, + num_bootstraps, + in, + out, + num_router_conns, + num_client_connections()); + + if (is_service_node() and num_router_connections() >= num_rcs) { - log::info( - logcat, - "Local service node has {} client connections since last RC update ({} to expiry)", - NumberOfConnectedClients(), - router_contact.age(now), - router_contact.time_to_expiry(now)); + log::critical(logcat, "SERVICE NODE IS FULLY MESHED"); } + if (_last_stats_report > 0s) log::info(logcat, "Last reported stats time {}", now - _last_stats_report); + _last_stats_report = now; + + oxen::log::flush(); } std::string @@ -747,9 +693,9 @@ namespace llarp fmt::format_to( out, " snode | known/svc/clients: {}/{}/{}", - node_db()->num_loaded(), - NumberOfConnectedRouters(), - NumberOfConnectedClients()); + node_db()->num_rcs(), + num_router_connections(), + num_client_connections()); fmt::format_to( out, " | {} active paths | block {} ", @@ -765,10 +711,7 @@ namespace llarp else { fmt::format_to( - out, - " client | known/connected: {}/{}", - node_db()->num_loaded(), - NumberOfConnectedRouters()); + out, " client | known/connected: {}/{}", node_db()->num_rcs(), num_router_connections()); if (auto ep = hidden_service_context().GetDefault()) { @@ -793,12 +736,28 @@ namespace llarp { if (is_stopping) return; - // LogDebug("tick router"); + + const bool is_snode = is_service_node(); + const bool is_decommed = appears_decommed(); + + const auto& local = local_rid(); + + if (is_snode and not node_db()->registered_routers().count(local)) + { + log::critical(logcat, "We are NOT registered router, figure it out!"); + // update tick timestamp + _last_tick = llarp::time_now_ms(); + return; + } + const auto now = llarp::time_now_ms(); - if (const auto delta = now - _last_tick; _last_tick != 0s and delta > TimeskipDetectedDuration) + auto now_timepoint = std::chrono::system_clock::time_point(now); + + if (const auto delta = now - _last_tick; + _last_tick != 0s and delta > NETWORK_RESET_SKIP_INTERVAL) { // we detected a time skip into the futre, thaw the network - LogWarn("Timeskip of ", ToString(delta), " detected. Resetting network state"); + log::error(logcat, "Timeskip of {} detected, resetting network state!", delta.count()); Thaw(); } @@ -813,66 +772,79 @@ namespace llarp report_stats(); } - const bool is_snode = is_service_node(); - const bool is_decommed = appears_decommed(); - // (relay-only) if we have fetched the relay list from oxend and // we are registered and funded, we want to gossip our RC periodically - auto now_timepoint = std::chrono::system_clock::time_point(now); - if (is_snode and appears_funded() and (now_timepoint > next_rc_gossip)) + if (is_snode) { - log::info(logcat, "regenerating and gossiping RC"); - router_contact.resign(); - save_rc(); - auto view = router_contact.view(); - _link_manager.gossip_rc( - pubkey(), std::string{reinterpret_cast(view.data()), view.size()}); - last_rc_gossip = now_timepoint; - - // 1min to 5min before "stale time" is next gossip time - auto random_delta = - std::chrono::seconds{std::uniform_int_distribution{60, 300}(llarp::csrng)}; - next_rc_gossip = now_timepoint + RouterContact::STALE_AGE - random_delta; + if (now_timepoint > next_rc_gossip) + { + log::critical(logcat, "Regenerating and gossiping RC..."); + + router_contact.resign(); + save_rc(); + + _link_manager->gossip_rc(local_rid(), router_contact.to_remote()); + + last_rc_gossip = now_timepoint; + + // TESTNET: 1 to 5 minutes before testnet gossip interval + auto delta = + std::chrono::seconds{std::uniform_int_distribution{60, 300}(llarp::csrng)}; + + next_rc_gossip = now_timepoint + TESTNET_GOSSIP_INTERVAL - delta; + } } - if (not is_snode) + if (needs_rebootstrap() and now_timepoint > next_bootstrap_attempt) + { + node_db()->fallback_to_bootstrap(); + } + else if (needs_initial_fetch() and now_timepoint > next_initial_fetch_attempt) + { + if (not _config->bootstrap.seednode) + node_db()->fetch_initial(is_service_node()); + } + else if (not is_snode and node_db()->initial_fetch_completed()) { // (client-only) periodically fetch updated RCs if (now_timepoint - last_rc_fetch > RC_UPDATE_INTERVAL) { + log::critical(logcat, "Time to fetch RCs!"); node_db()->fetch_rcs(); - last_rc_fetch = now_timepoint; } // (client-only) periodically fetch updated RouterID list - if (now_timepoint - last_routerid_fetch > ROUTERID_UPDATE_INTERVAL) + if (now_timepoint - last_rid_fetch > ROUTERID_UPDATE_INTERVAL) { - node_db()->fetch_router_ids(); - last_routerid_fetch = now_timepoint; + log::critical(logcat, "Time to fetch RIDs!"); + node_db()->fetch_rids(); } } // remove RCs for nodes that are no longer allowed by network policy - node_db()->RemoveIf([&](const RemoteRC& rc) -> bool { + node_db()->remove_if([&](const RemoteRC& rc) -> bool { // don't purge bootstrap nodes from nodedb - if (IsBootstrapNode(rc.router_id())) + if (is_bootstrap_node(rc.router_id())) { log::trace(logcat, "Not removing {}: is bootstrap node", rc.router_id()); return false; } + // if for some reason we stored an RC that isn't a valid router // purge this entry - if (not rc.is_public_router()) + if (not rc.is_public_addressable()) { log::debug(logcat, "Removing {}: not a valid router", rc.router_id()); return true; } - /// clear out a fully expired RC + + // clear out a fully expired RC if (rc.is_expired(now)) { log::debug(logcat, "Removing {}: RC is expired", rc.router_id()); return true; } + // clients have no notion of a whilelist // we short circuit logic here so we dont remove // routers that are not whitelisted for first hops @@ -900,74 +872,71 @@ namespace llarp return false; }); - /* TODO: this behavior seems incorrect, but fixing it will require discussion - * - if (not is_snode or not whitelist_received) - { - // find all deregistered relays - std::unordered_set close_peers; - - for_each_connection([this, &close_peers](link::Connection& conn) { - const auto& pk = conn.remote_rc.router_id(); - - if (conn.remote_is_relay and not _rc_lookup_handler.is_session_allowed(pk)) - close_peers.insert(pk); - }); - - // mark peers as de-registered - for (auto& peer : close_peers) - _link_manager.deregister_peer(peer); - } - */ - - _link_manager.check_persisting_conns(now); - - size_t connected = NumberOfConnectedRouters(); + _link_manager->check_persisting_conns(now); - size_t connectToNum = _link_manager.min_connected_routers; - const auto& pinned_edges = _node_db->get_pinned_edges(); - const auto pinned_count = pinned_edges.size(); - if (pinned_count > 0 && connectToNum > pinned_count) - { - connectToNum = pinned_count; - } + auto num_router_conns = num_router_connections(); + auto num_rcs = node_db()->num_rcs(); - if (is_snode and now >= _next_decomm_warning) + if (is_snode) { - constexpr auto DecommissionWarnInterval = 5min; - if (auto registered = appears_registered(), funded = appears_funded(); - not(registered and funded and not is_decommed)) + if (now >= _next_decomm_warning) { - // complain about being deregistered/decommed/unfunded - log::error( - logcat, - "We are running as a service node but we seem to be {}", - not registered ? "deregistered" - : is_decommed ? "decommissioned" - : "not fully staked"); - _next_decomm_warning = now + DecommissionWarnInterval; + if (auto registered = appears_registered(), funded = appears_funded(); + not(registered and funded and not is_decommed)) + { + // complain about being deregistered/decommed/unfunded + log::error( + logcat, + "We are running as a service node but we seem to be {}", + not registered ? "deregistered" + : is_decommed ? "decommissioned" + : "not fully staked"); + _next_decomm_warning = now + DECOMM_WARNING_INTERVAL; + } + else if (insufficient_peers()) + { + log::error( + logcat, + "We appear to be an active service node, but have only {} known peers.", + node_db()->num_rcs()); + _next_decomm_warning = now + DECOMM_WARNING_INTERVAL; + } } - else if (insufficient_peers()) + + if (num_router_conns < num_rcs) { - log::error( + log::critical( logcat, - "We appear to be an active service node, but have only {} known peers.", - node_db()->num_loaded()); - _next_decomm_warning = now + DecommissionWarnInterval; + "Service Node connecting to {} random routers to achieve full mesh", + FULL_MESH_ITERATION); + _link_manager->connect_to_random(FULL_MESH_ITERATION); } } - - // if we need more sessions to routers and we are not a service node kicked from the network or - // we are a client we shall connect out to others - if (connected < connectToNum and (appears_funded() or not is_snode)) + else { - size_t dlt = connectToNum - connected; - LogDebug("connecting to ", dlt, " random routers to keep alive"); - _link_manager.connect_to_random(dlt); - } + size_t min_client_conns = MIN_CLIENT_ROUTER_CONNS; + const auto& pinned_edges = _node_db->pinned_edges(); + const auto pinned_count = pinned_edges.size(); - _hidden_service_context.Tick(now); - _exit_context.Tick(now); + if (pinned_count > 0 && min_client_conns > pinned_count) + min_client_conns = pinned_count; + + // if we need more sessions to routers and we are not a service node kicked from the network + // or we are a client we shall connect out to others + if (num_router_conns < min_client_conns) + { + size_t needed = min_client_conns - num_router_conns; + log::critical(logcat, "Client connecting to {} random routers to keep alive", needed); + _link_manager->connect_to_random(needed); + } + else + { + // log::critical( + // logcat, "Client skipping hidden service exit tick or whatever the fuck that means"); + // _hidden_service_context.Tick(now); + // _exit_context.Tick(now); + } + } // save profiles if (router_profiling().ShouldSave(now) and _config->network.save_profiles) @@ -983,13 +952,7 @@ namespace llarp _last_tick = llarp::time_now_ms(); } - bool - Router::GetRandomConnectedRouter(RemoteRC& result) const - { - return _link_manager.get_random_connected(result); - } - - const std::unordered_set& + const std::set& Router::get_whitelist() const { return _node_db->whitelist(); @@ -1017,14 +980,22 @@ namespace llarp bool Router::Run() { + log::critical(logcat, "{} called", __PRETTY_FUNCTION__); + if (is_running || is_stopping) return false; - router_contact = LocalRC::make(identity(), public_ip()); + router_contact = LocalRC::make( + identity(), _is_service_node and _public_address ? *_public_address : _listen_address); - if (is_service_node() and not router_contact.is_public_router()) + _link_manager = LinkManager::make(*this); + + // Init components after relevant config settings loaded + _link_manager->init(); + + if (is_service_node()) { - if (not router_contact.is_public_router()) + if (not router_contact.is_public_addressable()) { log::error(logcat, "Router is configured as relay but has no reachable addresses!"); return false; @@ -1039,7 +1010,7 @@ namespace llarp } log::info(logcat, "Router initialized as service node!"); - const RouterID us = pubkey(); + // relays do not use profiling router_profiling().Disable(); } @@ -1061,24 +1032,20 @@ namespace llarp log::info(logcat, "Loading NodeDB from disk..."); _node_db->load_from_disk(); + _node_db->store_bootstraps(); - _contacts = std::make_shared(llarp::dht::Key_t(pubkey()), *this); - - for (const auto& rc : bootstrap_rc_list) - { - node_db()->put_rc(rc); - _contacts->rc_nodes()->PutNode(rc); - log::info(logcat, "Added bootstrap node (rid: {})", rc.router_id()); - } - - log::info(logcat, "Router populated NodeDB with {} routers", _node_db->num_loaded()); + log::info(logcat, "Creating Introset Contacts..."); + _contacts = std::make_unique(*this); _loop->call_every(ROUTER_TICK_INTERVAL, weak_from_this(), [this] { Tick(); }); + _route_poker->start(); + is_running.store(true); + _started_at = now(); - if (is_service_node()) + if (is_service_node() and not _testing_disabled) { // do service node testing if we are in service node whitelist mode _loop->call_every(consensus::REACHABILITY_TESTING_TIMER_INTERVAL, weak_from_this(), [this] { @@ -1090,7 +1057,9 @@ namespace llarp // yet when we expect to have one. if (not can_test_routers()) return; + auto tests = router_testing.get_failing(); + if (auto maybe = router_testing.next_random(this)) { tests.emplace_back(*maybe, 0); @@ -1111,54 +1080,34 @@ namespace llarp // try to make a session to this random router // this will do a dht lookup if needed - _link_manager.connect_to(router); - - /* - * TODO: container of pending snode test routers to be queried on - * connection success/failure, then do this stuff there. - _outboundSessionMaker.CreateSessionTo( - router, [previous_fails = fails, this](const auto& router, const auto result) { - auto rpc = RpcClient(); - - if (result != SessionResult::Establish) - { - // failed connection mark it as so - m_routerTesting.add_failing_node(router, previous_fails); - LogInfo( - "FAILED SN connection test to ", - router, - " (", - previous_fails + 1, - " consecutive failures) result=", - result); - } - else - { - m_routerTesting.remove_node_from_failing(router); - if (previous_fails > 0) - { - LogInfo( - "Successful SN connection test to ", - router, - " after ", - previous_fails, - " failures"); - } - else - { - LogDebug("Successful SN connection test to ", router); - } - } - if (rpc) + _link_manager->test_reachability( + router, + [this, rid = router, previous = fails](oxen::quic::connection_interface& conn) { + log::info( + logcat, + "Successful SN reachability test to {}{}", + rid, + previous ? "after {} previous failures"_format(previous) : ""); + router_testing.remove_node_from_failing(rid); + _rpc_client->InformConnection(rid, true); + conn.close_connection(); + }, + [this, rid = router, previous = fails]( + oxen::quic::connection_interface&, uint64_t ec) { + if (ec != 0) { - // inform as needed - rpc->InformConnection(router, result == SessionResult::Establish); + log::info( + logcat, + "Unsuccessful SN reachability test to {} after {} previous failures", + rid, + previous); + router_testing.add_failing_node(rid, previous); } }); - */ } }); } + llarp::sys::service_manager->ready(); return is_running; } @@ -1201,7 +1150,7 @@ namespace llarp void Router::StopLinks() { - _link_manager.stop(); + _link_manager->stop(); } void @@ -1218,7 +1167,7 @@ namespace llarp LogWarn("stopping router hard"); llarp::sys::service_manager->stopping(); hidden_service_context().StopAll(); - _exit_context.Stop(); + _exit_context.stop(); StopLinks(); Close(); } @@ -1238,27 +1187,21 @@ namespace llarp } is_stopping.store(true); + if (auto level = log::get_level_default(); level > log::Level::info and level != log::Level::off) log::reset_level(log::Level::info); - log::info(logcat, "stopping"); + + log::info(logcat, "stopping service manager..."); llarp::sys::service_manager->stopping(); - log::debug(logcat, "stopping hidden service context"); + + log::debug(logcat, "stopping hidden service context..."); hidden_service_context().StopAll(); - llarp::sys::service_manager->stopping(); - log::debug(logcat, "stopping exit context"); - _exit_context.Stop(); - llarp::sys::service_manager->stopping(); - log::debug(logcat, "final upstream pump"); - llarp::sys::service_manager->stopping(); - log::debug(logcat, "final links pump"); - _loop->call_later(200ms, [this] { AfterStopIssued(); }); - } - bool - Router::HasSessionTo(const RouterID& remote) const - { - return _link_manager.have_connection_to(remote); + log::debug(logcat, "stopping exit context..."); + _exit_context.stop(); + + _loop->call_later(200ms, [this] { AfterStopIssued(); }); } std::string @@ -1277,19 +1220,20 @@ namespace llarp Router::ConnectToRandomRouters(int _want) { const size_t want = _want; - auto connected = NumberOfConnectedRouters(); + auto connected = num_router_connections(); + if (connected >= want) return; - _link_manager.connect_to_random(want); + + _link_manager->connect_to_random(want); } bool Router::init_service_node() { - LogInfo("accepting transit traffic"); - paths.AllowTransit(); - _contacts->set_transit_allowed(true); - _exit_context.AddExitEndpoint("default", _config->network, _config->dns); + log::info(logcat, "Router accepting transit traffic..."); + paths.allow_transit(); + _exit_context.add_exit_endpoint("default", _config->network, _config->dns); return true; } @@ -1315,13 +1259,13 @@ namespace llarp } oxen::quic::Address - Router::public_ip() const + Router::listen_addr() const { - return _local_addr; + return _listen_address; } void - Router::InitInboundLinks() + Router::init_inbounds() { // auto addrs = _config->links.InboundListenAddrs; // if (is_service_node and addrs.empty()) @@ -1370,7 +1314,7 @@ namespace llarp } void - Router::InitOutboundLinks() + Router::init_outbounds() { // auto addrs = config()->links.OutboundLinks; // if (addrs.empty()) diff --git a/llarp/router/router.hpp b/llarp/router/router.hpp index cdd6da7ab..ea2774e15 100644 --- a/llarp/router/router.hpp +++ b/llarp/router/router.hpp @@ -37,41 +37,45 @@ #include #include -/* - TONUKE: - - hidden_service_context - - TODO: - - router should hold DHT nodes container? in either a class or a map - - -*/ - namespace llarp { /// number of routers to publish to - static constexpr size_t INTROSET_RELAY_REDUNDANCY = 2; + inline constexpr size_t INTROSET_RELAY_REDUNDANCY{2}; /// number of dht locations handled per relay - static constexpr size_t INTROSET_REQS_PER_RELAY = 2; + inline constexpr size_t INTROSET_REQS_PER_RELAY{2}; + + inline constexpr size_t INTROSET_STORAGE_REDUNDANCY{ + (INTROSET_RELAY_REDUNDANCY * INTROSET_REQS_PER_RELAY)}; + + // TESTNET: these constants are shortened for testing purposes + inline constexpr std::chrono::milliseconds TESTNET_GOSSIP_INTERVAL{10min}; + inline constexpr std::chrono::milliseconds RC_UPDATE_INTERVAL{5min}; + inline constexpr std::chrono::milliseconds INITIAL_ATTEMPT_INTERVAL{30s}; + // as we advance towards full mesh, we try to connect to this number per tick + inline constexpr int FULL_MESH_ITERATION{1}; + inline constexpr std::chrono::milliseconds ROUTERID_UPDATE_INTERVAL{1h}; - static constexpr size_t INTROSET_STORAGE_REDUNDANCY = - (INTROSET_RELAY_REDUNDANCY * INTROSET_REQS_PER_RELAY); + // DISCUSS: ask tom and jason about this + // how big of a time skip before we reset network state + inline constexpr std::chrono::milliseconds NETWORK_RESET_SKIP_INTERVAL{1min}; - static const std::chrono::seconds RC_UPDATE_INTERVAL = 5min; - static const std::chrono::seconds ROUTERID_UPDATE_INTERVAL = 1h; + inline constexpr std::chrono::milliseconds REPORT_STATS_INTERVAL{10s}; + + inline constexpr std::chrono::milliseconds DECOMM_WARNING_INTERVAL{5min}; struct Contacts; struct Router : std::enable_shared_from_this { + friend class NodeDB; + explicit Router(EventLoop_ptr loop, std::shared_ptr vpnPlatform); - ~Router(); + ~Router() = default; private: std::shared_ptr _route_poker; - /// bootstrap RCs - BootstrapList bootstrap_rc_list; std::chrono::steady_clock::time_point _next_explore_at; llarp_time_t last_pump = 0s; // transient iwp encryption key @@ -95,8 +99,14 @@ namespace llarp int _outbound_udp_socket = -1; bool _is_service_node = false; - std::optional _ourAddress; - oxen::quic::Address _local_addr; + bool _testnet = false; + bool _testing_disabled = false; + bool _bootstrap_seed = false; + + consensus::reachability_testing router_testing; + + std::optional _public_address; // public addr for relays + oxen::quic::Address _listen_address; EventLoop_ptr _loop; std::shared_ptr _vpn; @@ -108,7 +118,6 @@ namespace llarp std::shared_ptr _node_db; llarp_time_t _started_at; const oxenmq::TaggedThreadID _disk_thread; - oxen::quic::Network _net; llarp_time_t _last_stats_report = 0s; llarp_time_t _next_decomm_warning = time_now_ms() + 15s; @@ -126,24 +135,15 @@ namespace llarp oxenmq::address rpc_addr; Profiling _router_profiling; fs::path _profile_file; - LinkManager _link_manager{*this}; - std::chrono::system_clock::time_point last_rc_gossip{ - std::chrono::system_clock::time_point::min()}; - std::chrono::system_clock::time_point next_rc_gossip{ - std::chrono::system_clock::time_point::min()}; - std::chrono::system_clock::time_point last_rc_fetch{ - std::chrono::system_clock::time_point::min()}; - std::chrono::system_clock::time_point last_routerid_fetch{ - std::chrono::system_clock::time_point::min()}; + std::unique_ptr _link_manager; + int client_router_connections; // should we be sending padded messages every interval? bool send_padding = false; service::Context _hidden_service_context; - consensus::reachability_testing router_testing; - bool should_report_stats(llarp_time_t now) const; @@ -159,20 +159,59 @@ namespace llarp bool insufficient_peers() const; + protected: + std::chrono::system_clock::time_point last_rc_gossip{ + std::chrono::system_clock::time_point::min()}; + std::chrono::system_clock::time_point next_rc_gossip{last_rc_gossip}; + std::chrono::system_clock::time_point next_initial_fetch_attempt{last_rc_gossip}; + std::chrono::system_clock::time_point last_rc_fetch{last_rc_gossip}; + std::chrono::system_clock::time_point last_rid_fetch{last_rc_gossip}; + std::chrono::system_clock::time_point next_bootstrap_attempt{last_rc_gossip}; + public: - void - for_each_connection(std::function func); + bool + testnet() const + { + return _testnet; + } - void - connect_to(const RouterID& rid); + bool + is_bootstrap_seed() const + { + return _bootstrap_seed; + } + + int + required_num_client_conns() const + { + return client_router_connections; + } + + const RouterID& + local_rid() const + { + return router_contact.router_id(); + } + + bool + needs_initial_fetch() const; + + bool + needs_rebootstrap() const; void - connect_to(const RemoteRC& rc); + for_each_connection(std::function func); - Contacts* + const Contacts& contacts() const { - return _contacts.get(); + return *_contacts; + } + + Contacts& + contacts() + { + return *_contacts; } std::shared_ptr @@ -205,10 +244,16 @@ namespace llarp LinkManager& link_manager() { - return _link_manager; + return *_link_manager; + } + + const LinkManager& + link_manager() const + { + return *_link_manager; } - inline int + int outbound_udp_socket() const { return _outbound_udp_socket; @@ -275,7 +320,7 @@ namespace llarp } oxen::quic::Address - public_ip() const; + listen_addr() const; util::StatusObject ExtractStatus() const; @@ -283,7 +328,7 @@ namespace llarp util::StatusObject ExtractSummaryStatus() const; - const std::unordered_set& + const std::set& get_whitelist() const; void @@ -369,10 +414,10 @@ namespace llarp status_line(); void - InitInboundLinks(); + init_inbounds(); void - InitOutboundLinks(); + init_outbounds(); std::optional GetRandomGoodRouter(); @@ -466,7 +511,7 @@ namespace llarp std::string body, std::function func = nullptr); - bool IsBootstrapNode(RouterID) const; + bool is_bootstrap_node(RouterID) const; /// call internal router ticker void @@ -478,29 +523,16 @@ namespace llarp return llarp::time_now_ms(); } - /// parse a routing message in a buffer and handle it with a handler if - /// successful parsing return true on parse and handle success otherwise - /// return false - bool - ParseRoutingMessageBuffer( - const llarp_buffer_t& buf, path::AbstractHopHandler& p, const PathID_t& rxid); - void ConnectToRandomRouters(int N); /// count the number of unique service nodes connected via pubkey size_t - NumberOfConnectedRouters() const; + num_router_connections() const; /// count the number of unique clients connected by pubkey size_t - NumberOfConnectedClients() const; - - bool - GetRandomConnectedRouter(RemoteRC& result) const; - - bool - HasSessionTo(const RouterID& remote) const; + num_client_connections() const; std::string ShortName() const; diff --git a/llarp/router_contact.cpp b/llarp/router_contact.cpp index ab13328ca..2f3f5f606 100644 --- a/llarp/router_contact.cpp +++ b/llarp/router_contact.cpp @@ -6,19 +6,48 @@ #include "util/bencode.hpp" #include "util/buffer.hpp" #include "util/file.hpp" -#include "util/time.hpp" #include namespace llarp { void - RouterContact::bt_load(oxenc::bt_dict_consumer& data) + RouterContact::bt_verify(oxenc::bt_dict_consumer& btdc, bool reject_expired) const { - if (int rc_ver = data.require(""); rc_ver != RC_VERSION) + btdc.require_signature("~", [this, reject_expired](ustring_view msg, ustring_view sig) { + if (sig.size() != 64) + throw std::runtime_error{"Invalid signature: not 64 bytes"}; + + if (reject_expired and is_expired(time_now_ms())) + throw std::runtime_error{"Rejecting expired RemoteRC!"}; + + // TODO: revisit if this is needed; detail from previous implementation + const auto* net = net::Platform::Default_ptr(); + + if (net->IsBogon(addr().in4()) and BLOCK_BOGONS) + { + auto err = "Unable to verify expired RemoteRC address!"; + log::info(logcat, err); + throw std::runtime_error{err}; + } + + if (not crypto::verify(router_id(), msg, sig)) + throw std::runtime_error{"Failed to verify RemoteRC signature"}; + }); + + if (not btdc.is_finished()) + throw std::runtime_error{"RemoteRC has some fucked up shit at the end"}; + + btdc.finish(); + } + + void + RouterContact::bt_load(oxenc::bt_dict_consumer& btdc) + { + if (int rc_ver = btdc.require(""); rc_ver != RC_VERSION) throw std::runtime_error{"Invalid RC: do not know how to parse v{} RCs"_format(rc_ver)}; - auto ipv4_port = data.require("4"); + auto ipv4_port = btdc.require("4"); if (ipv4_port.size() != 6) throw std::runtime_error{ @@ -35,7 +64,7 @@ namespace llarp if (!_addr.is_public()) throw std::runtime_error{"Invalid RC: IPv4 address is not a publicly routable IP"}; - if (auto ipv6_port = data.maybe("6")) + if (auto ipv6_port = btdc.maybe("6")) { if (ipv6_port->size() != 18) throw std::runtime_error{ @@ -56,22 +85,22 @@ namespace llarp _addr6.reset(); } - auto netid = data.maybe("i").value_or(llarp::LOKINET_DEFAULT_NETID); + auto netid = btdc.maybe("i").value_or(llarp::LOKINET_DEFAULT_NETID); + if (netid != ACTIVE_NETID) throw std::runtime_error{ "Invalid RC netid: expected {}, got {}; this is an RC for a different network!"_format( ACTIVE_NETID, netid)}; - auto pk = data.require("p"); - - if (pk.size() != RouterID::SIZE) - throw std::runtime_error{"Invalid RC: router id has invalid size {}"_format(pk.size())}; - - std::memcpy(_router_id.data(), pk.data(), RouterID::SIZE); + auto pubkey = btdc.require("p"); + if (pubkey.size() != 32) + throw std::runtime_error{ + "Invalid RC pubkey: expected 32 bytes, got {}"_format(pubkey.size())}; + std::memcpy(_router_id.data(), pubkey.data(), 32); - _timestamp = rc_time{std::chrono::seconds{data.require("t")}}; + _timestamp = rc_time{std::chrono::seconds{btdc.require("t")}}; - auto ver = data.require("v"); + auto ver = btdc.require("v"); if (ver.size() != 3) throw std::runtime_error{ @@ -103,7 +132,7 @@ namespace llarp { util::StatusObject obj{ {"lastUpdated", _timestamp.time_since_epoch().count()}, - {"publicRouter", is_public_router()}, + {"publicRouter", is_public_addressable()}, {"identity", _router_id.ToString()}, {"address", _addr.to_string()}}; @@ -122,110 +151,11 @@ namespace llarp } bool - RouterContact::BDecode(llarp_buffer_t* buf) - { - // TODO: unfuck all of this - - (void)buf; - - // clear(); - - // if (*buf->cur == 'd') // old format - // { - // return DecodeVersion_0(buf); - // } - // else if (*buf->cur != 'l') // if not dict, should be new format and start with list - // { - // return false; - // } - - // try - // { - // std::string_view buf_view(reinterpret_cast(buf->cur), buf->size_left()); - // oxenc::bt_list_consumer btlist(buf_view); - - // uint64_t outer_version = btlist.consume_integer(); - - // if (outer_version == 1) - // { - // bool decode_result = DecodeVersion_1(btlist); - - // // advance the llarp_buffer_t since lokimq serialization is unaware of it. - // // FIXME: this is broken (current_buffer got dropped), but the whole thing is getting - // // replaced. - // // buf->cur += btlist. - // // current_buffer().data() - buf_view.data() + 1; - - // return decode_result; - // } - // else - // { - // log::warning(logcat, "Received RouterContact with unkown version ({})", outer_version); - // return false; - // } - // } - // catch (const std::exception& e) - // { - // log::debug(logcat, "RouterContact::BDecode failed: {}", e.what()); - // } - - return false; - } - - bool - RouterContact::decode_key(const llarp_buffer_t& key, llarp_buffer_t* buf) - { - bool read = false; - (void)key; - - // TOFIX: fuck everything about llarp_buffer_t - - // if (!BEncodeMaybeReadDictEntry("a", addr, read, key, buf)) - // return false; - - // if (!BEncodeMaybeReadDictEntry("i", netID, read, key, buf)) - // return false; - - // if (!BEncodeMaybeReadDictEntry("k", _router_id, read, key, buf)) - // return false; - - // if (key.startswith("r")) - // { - // RouterVersion r; - // if (not r.BDecode(buf)) - // return false; - // routerVersion = r; - // return true; - // } - - // if (not BEncodeMaybeReadDictList("s", srvRecords, read, key, buf)) - // return false; - - // if (!BEncodeMaybeReadDictEntry("p", enckey, read, key, buf)) - // return false; - - // if (!BEncodeMaybeReadDictInt("u", _timestamp, read, key, buf)) - // return false; - - // if (!BEncodeMaybeReadDictInt("v", version, read, key, buf)) - // return false; - - // if (key.startswith("x") and serializeExit) - // { - // return bencode_discard(buf); - // } - - // if (!BEncodeMaybeReadDictEntry("z", signature, read, key, buf)) - // return false; - - return read or bencode_discard(buf); - } - - bool - RouterContact::is_public_router() const + RouterContact::is_public_addressable() const { if (_router_version.empty()) return false; + return _addr.is_addressable(); } @@ -255,7 +185,7 @@ namespace llarp return time_to_expiry(now) <= dlt; } - static constexpr std::array obsolete_bootstraps = { + static const std::set obsolete_bootstraps{ "7a16ac0b85290bcf69b2f3b52456d7e989ac8913b4afbb980614e249a3723218"sv, "e6b3a6fe5e32c379b64212c72232d65b0b88ddf9bbaed4997409d329f8519e0b"sv, }; @@ -270,4 +200,12 @@ namespace llarp } return false; } + + bool + RouterContact::is_obsolete(const RouterContact& rc) + { + const auto& hex = rc._router_id.ToHex(); + + return obsolete_bootstraps.count(hex); + } } // namespace llarp diff --git a/llarp/router_contact.hpp b/llarp/router_contact.hpp index b65aa2c76..76f62c31f 100644 --- a/llarp/router_contact.hpp +++ b/llarp/router_contact.hpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -21,8 +22,6 @@ namespace llarp { static auto logcat = log::Cat("RC"); - using rc_time = std::chrono::time_point; - static inline constexpr size_t NETID_SIZE{8}; /// On the wire we encode the data as a dict containing: @@ -34,7 +33,7 @@ namespace llarp /// "6" -- optional 18 byte IPv6 address & port: 16 byte raw IPv6 address followed by 2 bytes /// of port in network order. /// "i" -- optional network ID string of up to 8 bytes; this is omitted for the default network - /// ID ("lokinet") but included for others (such as "gamma" for testnet). + /// ID ("lokinet") but included for others (such as "testnet" for testnet). /// "p" -- 32-byte router pubkey /// "t" -- timestamp when this RC record was created (which also implicitly determines when it /// goes stale and when it expires). @@ -54,8 +53,6 @@ namespace llarp static inline constexpr size_t MAX_RC_SIZE = 1024; - /// Timespans for RCs: - /// How long (from its signing time) before an RC is considered "stale". Relays republish /// their RCs slightly more frequently than this so that ideally this won't happen. static constexpr auto STALE_AGE = 6h; @@ -171,13 +168,7 @@ namespace llarp {} bool - BDecode(llarp_buffer_t* buf); - - bool - decode_key(const llarp_buffer_t& k, llarp_buffer_t* buf); - - bool - is_public_router() const; + is_public_addressable() const; /// does this RC expire soon? default delta is 1 minute bool @@ -204,10 +195,18 @@ namespace llarp bool is_obsolete_bootstrap() const; + static bool + is_obsolete(const RouterContact& rc); + + void + bt_verify(oxenc::bt_dict_consumer& data, bool reject_expired = false) const; + void bt_load(oxenc::bt_dict_consumer& data); }; + struct RemoteRC; + /// Extension of RouterContact used to store a local "RC," and inserts a RouterContact by /// re-parsing and sending it out. This sub-class contains a pubkey and all the other attributes /// required for signing and serialization @@ -234,9 +233,11 @@ namespace llarp public: LocalRC() = default; - explicit LocalRC(std::string payload, const SecretKey sk); ~LocalRC() = default; + RemoteRC + to_remote(); + void resign(); @@ -298,8 +299,7 @@ namespace llarp void set_systime_timestamp() { - set_timestamp( - std::chrono::time_point_cast(std::chrono::system_clock::now())); + set_timestamp(time_point_now()); } }; @@ -307,15 +307,13 @@ namespace llarp /// the data in the constructor, eliminating the need for a ::verify method/ struct RemoteRC final : public RouterContact { - private: - void - bt_verify(oxenc::bt_dict_consumer& data, bool reject_expired = false) const; - public: RemoteRC() = default; - RemoteRC(std::string_view data) : RemoteRC{oxenc::bt_dict_consumer{data}} - {} - RemoteRC(ustring_view data) : RemoteRC{oxenc::bt_dict_consumer{data}} + explicit RemoteRC(std::string_view data) : RemoteRC{oxenc::bt_dict_consumer{data}} + { + _payload = {reinterpret_cast(data.data()), data.size()}; + } + explicit RemoteRC(ustring_view data) : RemoteRC{oxenc::bt_dict_consumer{data}} { _payload = data; } @@ -360,10 +358,18 @@ namespace std template <> struct hash { - size_t + virtual size_t operator()(const llarp::RouterContact& r) const { return std::hash{}(r.router_id()); } }; + + template <> + struct hash : public hash + {}; + + template <> + struct hash final : public hash + {}; } // namespace std diff --git a/llarp/router_contact_local.cpp b/llarp/router_contact_local.cpp index 9b82551cc..82520daa4 100644 --- a/llarp/router_contact_local.cpp +++ b/llarp/router_contact_local.cpp @@ -22,45 +22,16 @@ namespace llarp { _router_id = llarp::seckey_to_pubkey(_secret_key); _addr = std::move(local); - _addr6.emplace(&_addr.in6()); + if (_addr.is_ipv6()) + _addr6.emplace(&_addr.in6()); resign(); } - LocalRC::LocalRC(std::string payload, const SecretKey sk) : _secret_key{std::move(sk)} + RemoteRC + LocalRC::to_remote() { - _router_id = llarp::seckey_to_pubkey(_secret_key); - - try - { - oxenc::bt_dict_consumer btdc{payload}; - bt_load(btdc); - - btdc.require_signature("~", [this](ustring_view msg, ustring_view sig) { - if (sig.size() != 64) - throw std::runtime_error{"Invalid signature: not 64 bytes"}; - - if (is_expired(time_now_ms())) - throw std::runtime_error{"Unable to verify expired RemoteRC!"}; - - // TODO: revisit if this is needed; detail from previous implementation - const auto* net = net::Platform::Default_ptr(); - - if (net->IsBogon(addr().in4()) and BLOCK_BOGONS) - { - auto err = "Unable to verify expired RemoteRC!"; - log::info(logcat, err); - throw std::runtime_error{err}; - } - - if (not crypto::verify(router_id(), msg, sig)) - throw std::runtime_error{"Failed to verify RemoteRC"}; - }); - } - catch (const std::exception& e) - { - log::warning(logcat, "Failed to parse LocalRC: {}", e.what()); - throw; - } + resign(); + return RemoteRC{view()}; } void @@ -78,7 +49,7 @@ namespace llarp return sig; }); - _payload = btdp.view(); + _payload = ustring{btdp.view()}; } void @@ -123,8 +94,6 @@ namespace llarp static_assert(llarp::LOKINET_VERSION.size() == 3); btdp.append( "v", std::string_view{reinterpret_cast(llarp::LOKINET_VERSION.data()), 3}); - - bt_sign(btdp); } void diff --git a/llarp/router_contact_remote.cpp b/llarp/router_contact_remote.cpp index 0d7eeb05b..141c6fc84 100644 --- a/llarp/router_contact_remote.cpp +++ b/llarp/router_contact_remote.cpp @@ -16,79 +16,33 @@ namespace llarp try { bt_load(btdc); - - btdc.require_signature("~", [this](ustring_view msg, ustring_view sig) { - if (sig.size() != 64) - throw std::runtime_error{"Invalid signature: not 64 bytes"}; - - if (is_expired(time_now_ms())) - throw std::runtime_error{"Unable to verify expired RemoteRC!"}; - - // TODO: revisit if this is needed; detail from previous implementation - const auto* net = net::Platform::Default_ptr(); - - if (net->IsBogon(addr().in4()) and BLOCK_BOGONS) - { - auto err = "Unable to verify expired RemoteRC!"; - log::info(logcat, err); - throw std::runtime_error{err}; - } - - if (not crypto::verify(router_id(), msg, sig)) - throw std::runtime_error{"Failed to verify RemoteRC"}; - }); + bt_verify(btdc, /*reject_expired=*/true); } catch (const std::exception& e) { - log::warning(logcat, "Failed to parse RemoteRC: {}", e.what()); - throw; + auto err = "Exception caught parsing RemoteRC: {}"_format(e.what()); + log::warning(logcat, err); + throw std::runtime_error{err}; } } - void - RemoteRC::bt_verify(oxenc::bt_dict_consumer& data, bool reject_expired) const - { - data.require_signature("~", [this, reject_expired](ustring_view msg, ustring_view sig) { - if (sig.size() != 64) - throw std::runtime_error{"Invalid signature: not 64 bytes"}; - - if (reject_expired and is_expired(time_now_ms())) - throw std::runtime_error{"Rejecting expired RemoteRC!"}; - - // TODO: revisit if this is needed; detail from previous implementation - const auto* net = net::Platform::Default_ptr(); - - if (net->IsBogon(addr().in4()) and BLOCK_BOGONS) - { - auto err = "Unable to verify expired RemoteRC!"; - log::info(logcat, err); - throw std::runtime_error{err}; - } - - if (not crypto::verify(router_id(), msg, sig)) - throw std::runtime_error{"Failed to verify RemoteRC"}; - }); - } - bool RemoteRC::read(const fs::path& fname) { - ustring buf; - buf.reserve(MAX_RC_SIZE); + _payload.resize(MAX_RC_SIZE); try { - util::file_to_buffer(fname, buf.data(), MAX_RC_SIZE); + auto nread = util::file_to_buffer(fname, _payload.data(), _payload.size()); + _payload.resize(nread); - oxenc::bt_dict_consumer btdc{buf}; + oxenc::bt_dict_consumer btdc{_payload}; bt_load(btdc); bt_verify(btdc); - - _payload = buf; } catch (const std::exception& e) { - log::error(logcat, "Failed to read or validate RC from {}: {}", fname, e.what()); + log::warning(logcat, "Failed to read or validate RC from {}: {}", fname, e.what()); return false; } diff --git a/llarp/router_id.cpp b/llarp/router_id.cpp index d8f1e1581..a046f0aee 100644 --- a/llarp/router_id.cpp +++ b/llarp/router_id.cpp @@ -29,7 +29,7 @@ namespace llarp } bool - RouterID::FromString(std::string_view str) + RouterID::from_string(std::string_view str) { auto pos = str.find(SNODE_TLD); if (pos != str.size() - SNODE_TLD.size()) diff --git a/llarp/router_id.hpp b/llarp/router_id.hpp index 8a82f924d..f82f78402 100644 --- a/llarp/router_id.hpp +++ b/llarp/router_id.hpp @@ -21,6 +21,12 @@ namespace llarp RouterID(const Data& data) : PubKey(data) {} + RouterID(ustring_view data) : PubKey(data.data()) + {} + + RouterID(std::string_view data) : RouterID(to_usv(data)) + {} + util::StatusObject ExtractStatus() const; @@ -30,8 +36,11 @@ namespace llarp std::string ShortString() const; + // FIXME: this is deceptively named: it parses something base32z formatted with .snode on the + // end, so should probably be called "from_snode_address" or "from_base32z" or something that + // doesn't sound exactly like the other (different) from_strings of its base classes. bool - FromString(std::string_view str); + from_string(std::string_view str); RouterID& operator=(const byte_t* ptr) @@ -49,7 +58,6 @@ namespace llarp template <> constexpr inline bool IsToStringFormattable = true; - } // namespace llarp namespace std diff --git a/llarp/rpc/lokid_rpc_client.cpp b/llarp/rpc/lokid_rpc_client.cpp index aa4e3f3ad..6f41b8c4e 100644 --- a/llarp/rpc/lokid_rpc_client.cpp +++ b/llarp/rpc/lokid_rpc_client.cpp @@ -97,7 +97,7 @@ namespace llarp::rpc return; // bail } - LogDebug("new block at height ", m_BlockHeight); + log::trace(logcat, "new block at height {}", m_BlockHeight); // don't upadate on block notification if an update is pending if (not m_UpdatingList) UpdateServiceNodeList(); @@ -138,7 +138,7 @@ namespace llarp::rpc throw std::runtime_error{"get_service_nodes did not return 'OK' status"}; if (auto it = json.find("unchanged"); it != json.end() and it->is_boolean() and it->get()) - LogDebug("service node list unchanged"); + log::trace(logcat, "service node list unchanged"); else { self->HandleNewServiceNodeList(json.at("service_node_states")); @@ -269,6 +269,7 @@ namespace llarp::rpc keymap = std::move(keymap), router = std::move(router)]() mutable { m_KeyMap = std::move(keymap); + router->set_router_whitelist(active, decomm, unfunded); }); } diff --git a/llarp/rpc/rpc_server.cpp b/llarp/rpc/rpc_server.cpp index b5854d940..c6873ff0f 100644 --- a/llarp/rpc/rpc_server.cpp +++ b/llarp/rpc/rpc_server.cpp @@ -76,7 +76,7 @@ namespace llarp::rpc { if (r.is_service_node()) { - return r.exitContext().GetExitEndpoint(name); + return r.exitContext().get_exit_endpoint(name); } return r.hidden_service_context().GetEndpointByName(name); @@ -322,14 +322,14 @@ namespace llarp::rpc return; } - if (not routerID.FromString(lookupsnode.request.routerID)) + if (not routerID.from_string(lookupsnode.request.routerID)) { SetJSONError("Invalid remote: " + lookupsnode.request.routerID, lookupsnode.response); return; } m_Router.loop()->call([&]() { - auto endpoint = m_Router.exitContext().GetExitEndpoint("default"); + auto endpoint = m_Router.exitContext().get_exit_endpoint("default"); if (endpoint == nullptr) { diff --git a/llarp/service/address.cpp b/llarp/service/address.cpp index 2f18907de..cd37138a4 100644 --- a/llarp/service/address.cpp +++ b/llarp/service/address.cpp @@ -79,7 +79,7 @@ namespace llarp::service { RouterID router{}; service::Address addr{}; - if (router.FromString(lokinet_addr)) + if (router.from_string(lokinet_addr)) return router; if (addr.FromString(lokinet_addr)) return addr; diff --git a/llarp/service/endpoint.cpp b/llarp/service/endpoint.cpp index dabc654da..a9eebe4c9 100644 --- a/llarp/service/endpoint.cpp +++ b/llarp/service/endpoint.cpp @@ -299,7 +299,7 @@ namespace llarp::service // expire convotags EndpointUtil::ExpireConvoSessions(now, Sessions()); - if (NumInStatus(path::ePathEstablished) > 1) + if (NumInStatus(path::ESTABLISHED) > 1) { for (const auto& item : _startup_ons_mappings) { @@ -516,7 +516,7 @@ namespace llarp::service const auto& keyfile = _state->key_file; if (!keyfile.empty()) { - _identity.EnsureKeys(keyfile, router()->key_manager()->needBackup()); + _identity.EnsureKeys(keyfile, router()->key_manager()->needs_backup()); } else { @@ -697,14 +697,16 @@ namespace llarp::service { std::unordered_set exclude; ForEachPath([&exclude](auto path) { exclude.insert(path->Endpoint()); }); - const auto maybe = - router()->node_db()->GetRandom([exclude, r = router()](const RemoteRC& rc) -> bool { - const auto& rid = rc.router_id(); - return exclude.count(rid) == 0 and not r->router_profiling().IsBadForPath(rid); - }); - if (not maybe.has_value()) - return std::nullopt; - return GetHopsForBuildWithEndpoint(maybe->router_id()); + + auto hook = [exclude, r = router()](const RemoteRC& rc) -> bool { + const auto& rid = rc.router_id(); + return not(exclude.count(rid) || r->router_profiling().IsBadForPath(rid)); + }; + + if (auto maybe = router()->node_db()->get_random_rc_conditional(hook)) + return GetHopsForBuildWithEndpoint(maybe->router_id()); + + return std::nullopt; } std::optional> @@ -1308,7 +1310,7 @@ namespace llarp::service // TODO: if all requests fail, call callback with failure? for (const auto& path : paths) { - path->find_intro(location, false, 0, [this, hook, got_it](std::string resp) mutable { + path->find_intro(location, false, 0, [hook, got_it, this](std::string resp) mutable { // asking many, use only first successful if (*got_it) return; @@ -1333,7 +1335,7 @@ namespace llarp::service } service::EncryptedIntroSet enc{introset}; - router()->contacts()->services()->PutNode(std::move(enc)); + router()->contacts().put_intro(std::move(enc)); // TODO: finish this /* @@ -1396,7 +1398,7 @@ namespace llarp::service while (not _inbound_queue.empty()) { - // succ it out + // suck it out queue.emplace(std::move(*_inbound_queue.popFront())); } @@ -1537,7 +1539,7 @@ namespace llarp::service if (BuildCooldownHit(now)) return false; const auto requiredPaths = std::max(numDesiredPaths, path::MIN_INTRO_PATHS); - if (NumInStatus(path::ePathBuilding) >= requiredPaths) + if (NumInStatus(path::BUILDING) >= requiredPaths) return false; return NumPathsExistingAt(now + (path::DEFAULT_LIFETIME - path::INTRO_PATH_SPREAD)) < requiredPaths; diff --git a/llarp/service/endpoint.hpp b/llarp/service/endpoint.hpp index 266c0bf1f..0ceb16df3 100644 --- a/llarp/service/endpoint.hpp +++ b/llarp/service/endpoint.hpp @@ -178,7 +178,7 @@ namespace llarp EgresPacketRouter() { return nullptr; - }; + } virtual vpn::NetworkInterface* GetVPNInterface() diff --git a/llarp/service/identity.cpp b/llarp/service/identity.cpp index a79c6ae97..63ba3c5d6 100644 --- a/llarp/service/identity.cpp +++ b/llarp/service/identity.cpp @@ -97,7 +97,7 @@ namespace llarp::service if (exists and needBackup) { - KeyManager::backupFileByMoving(fname); + KeyManager::copy_backup_keyfile(fname); exists = false; } diff --git a/llarp/service/intro.cpp b/llarp/service/intro.cpp index 5e25e6b62..2c8bb5596 100644 --- a/llarp/service/intro.cpp +++ b/llarp/service/intro.cpp @@ -39,7 +39,7 @@ namespace llarp::service { oxenc::bt_dict_consumer btdc{std::move(buf)}; - router.FromString(btdc.require("k")); + router.from_string(btdc.require("k")); latency = std::chrono::milliseconds{btdc.require("l")}; path_id.from_string(btdc.require("p")); expiry = std::chrono::milliseconds{btdc.require("x")}; diff --git a/llarp/service/outbound_context.cpp b/llarp/service/outbound_context.cpp index fe0218416..c025168b5 100644 --- a/llarp/service/outbound_context.cpp +++ b/llarp/service/outbound_context.cpp @@ -143,7 +143,7 @@ namespace llarp::service { // ignore new path if we are marked dead LogInfo(Name(), " marked bad, ignoring new path"); - p->EnterState(path::ePathIgnore, Now()); + p->EnterState(path::IGNORE, Now()); } else if (p->Endpoint() == next_intro.router) { @@ -375,7 +375,7 @@ namespace llarp::service if (marked_bad or path::Builder::BuildCooldownHit(now)) return false; - if (NumInStatus(path::ePathBuilding) >= std::max(numDesiredPaths / size_t{2}, size_t{1})) + if (NumInStatus(path::BUILDING) >= std::max(numDesiredPaths / size_t{2}, size_t{1})) return false; size_t numValidPaths = 0; diff --git a/llarp/util/buffer.hpp b/llarp/util/buffer.hpp index 2cfa8f3a6..ca688f127 100644 --- a/llarp/util/buffer.hpp +++ b/llarp/util/buffer.hpp @@ -24,6 +24,11 @@ namespace llarp using bstring = std::basic_string; using bstring_view = std::basic_string_view; + inline ustring operator""_us(const char* str, size_t len) noexcept + { + return {reinterpret_cast(str), len}; + } + // Helper function to switch between string_view and ustring_view inline ustring_view to_usv(std::string_view v) diff --git a/llarp/util/file.hpp b/llarp/util/file.hpp index 39c140673..8629a9d2c 100644 --- a/llarp/util/file.hpp +++ b/llarp/util/file.hpp @@ -25,7 +25,7 @@ namespace llarp::util template < typename Char, std::enable_if_t, int> = 1> - inline size_t + size_t file_to_buffer(const fs::path& filename, Char* buffer, size_t buffer_size) { return file_to_buffer(filename, reinterpret_cast(buffer), buffer_size); @@ -38,7 +38,7 @@ namespace llarp::util /// Same as above, but works via char-like buffer template = 0> - inline void + void buffer_to_file(const fs::path& filename, const Char* buffer, size_t buffer_size) { return buffer_to_file( @@ -73,31 +73,4 @@ namespace llarp::util return std::make_optional(pathname, mode); } - template - static void - IterDir(const fs::path& path, PathVisitor visit) - { - DIR* d = opendir(path.string().c_str()); - if (d == nullptr) - return; - struct dirent* ent = nullptr; - std::set entries; - do - { - ent = readdir(d); - if (not ent) - break; - if (ent->d_name[0] == '.') - continue; - entries.emplace(path / fs::path{ent->d_name}); - } while (ent); - closedir(d); - - for (const auto& p : entries) - { - if (not visit(p)) - return; - } - } - } // namespace llarp::util diff --git a/llarp/util/time.cpp b/llarp/util/time.cpp index af36a55dd..39fc6e268 100644 --- a/llarp/util/time.cpp +++ b/llarp/util/time.cpp @@ -34,6 +34,12 @@ namespace llarp std::chrono::steady_clock::now() - started_at_steady); } + rc_time + time_point_now() + { + return std::chrono::time_point_cast(std::chrono::system_clock::now()); + } + Duration_t time_now_ms() { diff --git a/llarp/util/time.hpp b/llarp/util/time.hpp index 2a312090d..bba3cf636 100644 --- a/llarp/util/time.hpp +++ b/llarp/util/time.hpp @@ -12,6 +12,11 @@ using namespace std::chrono_literals; namespace llarp { + using rc_time = std::chrono::time_point; + + rc_time + time_point_now(); + /// get time right now as milliseconds, this is monotonic Duration_t time_now_ms();