From 68148e098f4ed742581e9505f9cc29acd7645e1a Mon Sep 17 00:00:00 2001 From: Jeff Date: Sat, 9 Jul 2022 11:05:52 -0400 Subject: [PATCH] * add mockable network functions * add unit tests with ability to pretend to be different network setups --- CMakeLists.txt | 2 +- jni/lokinet_daemon.cpp | 4 +- llarp/apple/context_wrapper.cpp | 2 +- llarp/config/config.cpp | 340 +++++---- llarp/config/config.hpp | 46 +- llarp/config/definition.cpp | 18 +- llarp/config/definition.hpp | 33 +- llarp/constants/link_layer.hpp | 5 + llarp/ev/ev_libuv.hpp | 13 +- llarp/ev/vpn.hpp | 9 + llarp/handlers/exit.cpp | 4 +- llarp/handlers/tun.cpp | 6 +- llarp/link/server.cpp | 75 +- llarp/link/server.hpp | 11 +- llarp/net/ip_range.hpp | 16 + llarp/net/net.cpp | 807 +++++++++------------ llarp/net/net.hpp | 129 ++-- llarp/net/net_bits.hpp | 10 + llarp/net/net_int.cpp | 72 +- llarp/net/net_int.hpp | 102 ++- llarp/net/sock_addr.hpp | 10 + llarp/router/abstractrouter.hpp | 8 + llarp/router/router.cpp | 220 +++--- llarp/router/router.hpp | 17 +- llarp/vpn/linux.hpp | 11 +- llarp/vpn/platform.cpp | 6 + llarp/win32/exception.hpp | 31 + pybind/llarp/config.cpp | 20 +- readme.md | 4 +- test/CMakeLists.txt | 4 +- test/config/test_llarp_config_values.cpp | 286 ++++++++ test/mocks/mock_context.hpp | 40 + test/mocks/mock_network.hpp | 192 +++++ test/mocks/mock_router.hpp | 25 + test/mocks/mock_vpn.hpp | 93 +++ test/regress/2020-06-08-key-backup-bug.cpp | 77 -- 36 files changed, 1691 insertions(+), 1057 deletions(-) create mode 100644 llarp/win32/exception.hpp create mode 100644 test/config/test_llarp_config_values.cpp create mode 100644 test/mocks/mock_context.hpp create mode 100644 test/mocks/mock_network.hpp create mode 100644 test/mocks/mock_router.hpp create mode 100644 test/mocks/mock_vpn.hpp delete mode 100644 test/regress/2020-06-08-key-backup-bug.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 0758f6c1a..8969f0eca 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -170,7 +170,7 @@ if(NOT TARGET sodium) endif() if(NOT APPLE) - add_compile_options(-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 -Wall -Wextra -Wno-unknown-pragmas -Wno-unused-function -Wno-deprecated-declarations -Werror=vla) + add_compile_options(-Wall -Wextra -Wno-unknown-pragmas -Wno-unused-function -Wno-deprecated-declarations -Werror=vla) if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") add_compile_options(-Wno-unknown-warning-option) endif() diff --git a/jni/lokinet_daemon.cpp b/jni/lokinet_daemon.cpp index 8de704ae8..bec31c147 100644 --- a/jni/lokinet_daemon.cpp +++ b/jni/lokinet_daemon.cpp @@ -87,14 +87,14 @@ extern "C" { auto ptr = GetImpl(env, self); - return ptr->GetUDPSocket(); + return ptr->router.m_OutboundUDPSocket; } JNIEXPORT jstring JNICALL Java_network_loki_lokinet_LokinetDaemon_DetectFreeRange(JNIEnv* env, jclass) { std::string rangestr{}; - if (auto maybe = llarp::FindFreeRange()) + if (auto maybe = llarp::net::Platform::Default().FindFreeRange()) { rangestr = maybe->ToString(); } diff --git a/llarp/apple/context_wrapper.cpp b/llarp/apple/context_wrapper.cpp index 31768f3f3..3c91cdb06 100644 --- a/llarp/apple/context_wrapper.cpp +++ b/llarp/apple/context_wrapper.cpp @@ -54,7 +54,7 @@ llarp_apple_init(llarp_apple_config* appleconf) auto& range = config->network.m_ifaddr; if (!range.addr.h) { - if (auto maybe = llarp::FindFreeRange()) + if (auto maybe = llarp::net::Platform::Default().FindFreeRange()) range = *maybe; else throw std::runtime_error{"Could not find any free IP range"}; diff --git a/llarp/config/config.cpp b/llarp/config/config.cpp index 4ca1625c1..db195e707 100644 --- a/llarp/config/config.cpp +++ b/llarp/config/config.cpp @@ -35,6 +35,17 @@ namespace llarp constexpr int DefaultPublicPort = 1090; using namespace config; + namespace + { + struct ConfigGenParameters_impl : public ConfigGenParameters + { + const llarp::net::Platform* + Net_ptr() const + { + return llarp::net::Platform::Default_ptr(); + } + }; + } // namespace void RouterConfig::defineConfigOptions(ConfigDefinition& conf, const ConfigGenParameters& params) @@ -140,7 +151,7 @@ namespace llarp throw std::invalid_argument{ fmt::format("{} is not a publicly routable ip address", addr)}; - m_PublicIP = addr; + PublicIP = addr; }); conf.defineOption("router", "public-address", Hidden, [](std::string) { @@ -161,7 +172,7 @@ namespace llarp [this](int arg) { if (arg <= 0 || arg > std::numeric_limits::max()) throw std::invalid_argument("public-port must be >= 0 and <= 65536"); - m_PublicPort = ToNet(huint16_t{static_cast(arg)}); + PublicPort = ToNet(huint16_t{static_cast(arg)}); }); conf.defineOption( @@ -403,7 +414,7 @@ namespace llarp ReachableDefault, AssignmentAcceptor(m_reachable), Comment{ - "Determines whether we will publish our snapp's introset to the DHT.", + "Determines whether we will pubish our snapp's introset to the DHT.", }); conf.defineOption( @@ -838,111 +849,166 @@ namespace llarp }); } - LinksConfig::LinkInfo - LinksConfig::LinkInfoFromINIValues(std::string_view name, std::string_view value) - { - // we treat the INI k:v pair as: - // k: interface name, * indicating outbound - // v: a comma-separated list of values, an int indicating port (everything else ignored) - // this is somewhat of a backwards- and forwards-compatibility thing - - LinkInfo info; - info.port = 0; - info.addressFamily = AF_INET; - - if (name == "address") - { - const IpAddress addr{value}; - if (not addr.hasPort()) - throw std::invalid_argument("no port provided in link address"); - info.m_interface = addr.toHost(); - info.port = *addr.getPort(); - } - else - { - info.m_interface = std::string{name}; - - std::vector splits = split(value, ","); - for (std::string_view str : splits) - { - int asNum = std::atoi(str.data()); - if (asNum > 0) - info.port = asNum; - - // otherwise, ignore ("future-proofing") - } - } - - return info; - } - void LinksConfig::defineConfigOptions(ConfigDefinition& conf, const ConfigGenParameters& params) { - constexpr Default DefaultOutboundLinkValue{"0"}; - conf.addSectionComments( "bind", { - "This section specifies network interface names and/or IPs as keys, and", - "ports as values to control the address(es) on which Lokinet listens for", - "incoming data.", + "Typically this section can be left blank, but can be used to specify which sockets to " + "bind on for inbound and outbound traffic.", + "", + "If no inbound bind addresses are configured then lokinet will search for a local ", + "network interface with a public IP address and use that IP with port 1090.", + "If no outbound bind addresses are configured then lokinet will use a wildcard " + "address.", "", "Examples:", "", - " eth0=1090", - " 0.0.0.0=1090", - " 1.2.3.4=1090", + " inbound=15.5.29.5:443", + " inbound=10.0.2.2", + " outbound=0.0.0.0:9000", "", - "The first bind to port 1090 on the network interface 'eth0'; the second binds", - "to port 1090 on all local network interfaces; and the third example binds to", - "port 1090 on the given IP address.", + "The first binds an inbound socket on local ip 15.5.29.5 with port 443; and the " + "second binds an inbound socket on local ip 10.0.2.2 with the default port, 1090; and " + "the third example binds an outbound socket on all interfaces with a pinned outbound " + "port on port 9000.", "", - "If a private range IP address (or an interface with a private IP) is given, or", - "if the 0.0.0.0 all-address IP is given then you must also specify the", - "public-ip= and public-port= settings in the [router] section with a public", - "address at which this router can be reached.", + "Inbound sockets with a wildcard address or private range IP address (like the second " + "example entry) will require setting the public-ip= and public-port= settings with a " + "public address at which this router can be reached.", + "Inbound sockets can NOT have ports explicitly set to be 0.", "", - "Typically this section can be left blank: if no inbound bind addresses are", - "configured then lokinet will search for a local network interface with a public", - "IP address and use that (with port 1090).", + "On setups with multiple public ip addresses on a network interface, the first ip will " + "be used as a default or when a wildcard is provided, unless explicitly set in config.", + "Setting the IP for both inbound and outbound sockets on machines with multiple public " + "ip addresses is highly recommended.", }); + const auto* net_ptr = params.Net_ptr(); + + static constexpr Default DefaultInboundPort{uint16_t{1090}}; + static constexpr Default DefaultOutboundPort{uint16_t{0}}; + conf.defineOption( "bind", - "*", - DefaultOutboundLinkValue, - Comment{ - "Specify a source port for **outgoing** Lokinet traffic, for example if you want to", - "set up custom firewall rules based on the originating port. Typically this should", - "be left unset to automatically choose random source ports.", - }, - [this](std::string arg) { m_OutboundLink = LinkInfoFromINIValues("*", arg); }); - - if (params.isRelay) - { - if (std::string best_if; GetBestNetIF(best_if)) - m_InboundLinks.push_back(LinkInfoFromINIValues(best_if, std::to_string(DefaultPublicPort))); - } - conf.addUndeclaredHandler( + "public-ip", + RelayOnly, + Comment{"set our public ip if it is different than the one we detect or if we are unable " + "to detect it"}, + [this](std::string_view arg) { + SockAddr pubaddr{arg}; + PublicAddress = pubaddr.getIP(); + }); + conf.defineOption( "bind", - [&, defaulted = true]( - std::string_view, std::string_view name, std::string_view value) mutable { - if (defaulted) - { - m_InboundLinks.clear(); // Clear the default - defaulted = false; - } + "public-port", + RelayOnly, + Comment{"set our public port if it is different than the one we detect or if we are unable " + "to detect it"}, + [this](uint16_t arg) { PublicPort = net::port_t::from_host(arg); }); + + auto parse_addr_for_link = [net_ptr](const std::string& arg, net::port_t default_port) { + std::optional addr = std::nullopt; + // explicitly provided value + if (not arg.empty()) + { + if (arg[0] == ':') + { + // port only case + auto port = net::port_t::from_string(arg.substr(1)); + addr = net_ptr->WildcardWithPort(port); + } + else + { + addr = SockAddr{arg}; + if (net_ptr->IsLoopbackAddress(addr->getIP())) + throw std::invalid_argument{fmt::format("{} is a loopback address", arg)}; + } + } + if (not addr) + { + // infer public address + if (auto maybe_ifname = net_ptr->GetBestNetIF()) + addr = net_ptr->GetInterfaceAddr(*maybe_ifname); + } - LinkInfo info = LinkInfoFromINIValues(name, value); + if (addr) + { + // set port if not explicitly provided + if (addr->getPort() == 0) + addr->setPort(default_port); + } + return addr; + }; - if (info.port <= 0) - throw std::invalid_argument{ - fmt::format("Invalid [bind] port specified on interface {}", name)}; + conf.defineOption( + "bind", + "inbound", + RelayOnly, + MultiValue, + Comment{""}, + [this, parse_addr_for_link](const std::string& arg) { + auto default_port = net::port_t::from_host(DefaultInboundPort.val); + if (auto addr = parse_addr_for_link(arg, default_port)) + InboundListenAddrs.emplace_back(std::move(*addr)); + }); + + conf.defineOption( + "bind", + "outbound", + MultiValue, + Comment{""}, + [this, net_ptr, parse_addr_for_link](const std::string& arg) { + auto default_port = net::port_t::from_host(DefaultOutboundPort.val); + auto addr = parse_addr_for_link(arg, default_port); + if (not addr) + addr = net_ptr->WildcardWithPort(default_port); + OutboundLinks.emplace_back(std::move(*addr)); + }); - assert(name != "*"); // handled by defineOption("bind", "*", ...) above + conf.addUndeclaredHandler( + "bind", [this, net_ptr](std::string_view, std::string_view key, std::string_view val) { + LogError( + "using the [bind] section without inbound= or outbound= is deprecated and will stop " + "working in a future release"); + std::optional addr; + // special case: wildcard for outbound + if (key == "*") + { + addr = net_ptr->Wildcard(); + // set port, zero is acceptable here. + if (auto port = std::stoi(std::string{val}); + port < std::numeric_limits::max()) + { + addr->setPort(port); + } + else + throw std::invalid_argument{fmt::format("invalid port value: '{}'", val)}; + OutboundLinks.emplace_back(std::move(*addr)); + return; + } + // try as interface name first + addr = net_ptr->GetInterfaceAddr(key, AF_INET); + if (addr and net_ptr->IsLoopbackAddress(addr->getIP())) + throw std::invalid_argument{fmt::format("{} is a loopback interface", key)}; + // try as ip address next, throws if unable to parse + if (not addr) + { + addr = SockAddr{key, huint16_t{0}}; + if (net_ptr->IsLoopbackAddress(addr->getIP())) + throw std::invalid_argument{fmt::format("{} is a loopback address", key)}; + } + // parse port and set if acceptable non zero value + if (auto port = std::stoi(std::string{val}); + port and port < std::numeric_limits::max()) + { + addr->setPort(port); + } + else + throw std::invalid_argument{fmt::format("invalid port value: '{}'", val)}; - m_InboundLinks.emplace_back(std::move(info)); + InboundListenAddrs.emplace_back(std::move(*addr)); }); } @@ -1199,8 +1265,14 @@ namespace llarp return true; } - Config::Config(fs::path datadir) - : m_DataDir(datadir.empty() ? fs::current_path() : std::move(datadir)) + std::unique_ptr + Config::MakeGenParams() const + { + return std::make_unique(); + } + + Config::Config(std::optional datadir) + : m_DataDir{datadir ? std::move(*datadir) : fs::current_path()} {} constexpr auto GetOverridesDir = [](auto datadir) -> fs::path { return datadir / "conf.d"; }; @@ -1242,6 +1314,31 @@ namespace llarp m_Additional.emplace_back(std::array{section, key, val}); } + bool + Config::LoadString(std::string_view ini, bool isRelay) + { + auto params = MakeGenParams(); + params->isRelay = isRelay; + params->defaultDataDir = m_DataDir; + ConfigDefinition conf{isRelay}; + initializeConfig(conf, *params); + + m_Parser.Clear(); + if (not m_Parser.LoadFromStr(ini)) + return false; + + m_Parser.IterAll([&](std::string_view section, const SectionValues_t& values) { + for (const auto& pair : values) + { + conf.addConfigValue(section, pair.first, pair.second); + } + }); + + conf.process(); + + return true; + } + bool Config::Load(std::optional fname, bool isRelay) { @@ -1249,13 +1346,12 @@ namespace llarp return LoadDefault(isRelay); try { - ConfigGenParameters params; - params.isRelay = isRelay; - params.defaultDataDir = m_DataDir; + auto params = MakeGenParams(); + params->isRelay = isRelay; + params->defaultDataDir = m_DataDir; ConfigDefinition conf{isRelay}; - initializeConfig(conf, params); - addBackwardsCompatibleConfigOptions(conf); + initializeConfig(conf, *params); m_Parser.Clear(); if (!m_Parser.LoadFile(*fname)) { @@ -1269,9 +1365,7 @@ namespace llarp conf.addConfigValue(section, pair.first, pair.second); } }); - - conf.acceptAllOptions(); - + conf.process(); return true; } catch (const std::exception& e) @@ -1284,39 +1378,7 @@ namespace llarp bool Config::LoadDefault(bool isRelay) { - try - { - ConfigGenParameters params; - params.isRelay = isRelay; - params.defaultDataDir = m_DataDir; - ConfigDefinition conf{isRelay}; - initializeConfig(conf, params); - - m_Parser.Clear(); - LoadOverrides(); - - /// load additional config options added - for (const auto& [sect, key, val] : m_Additional) - { - conf.addConfigValue(sect, key, val); - } - - m_Parser.IterAll([&](std::string_view section, const SectionValues_t& values) { - for (const auto& pair : values) - { - conf.addConfigValue(section, pair.first, pair.second); - } - }); - - conf.acceptAllOptions(); - - return true; - } - catch (const std::exception& e) - { - LogError("Error trying to init default config: ", e.what()); - return false; - } + return LoadString("", isRelay); } void @@ -1439,12 +1501,12 @@ namespace llarp std::string Config::generateBaseClientConfig() { - ConfigGenParameters params; - params.isRelay = false; - params.defaultDataDir = m_DataDir; + auto params = MakeGenParams(); + params->isRelay = false; + params->defaultDataDir = m_DataDir; llarp::ConfigDefinition def{false}; - initializeConfig(def, params); + initializeConfig(def, *params); generateCommonConfigComments(def); def.addSectionComments( "paths", @@ -1464,12 +1526,12 @@ namespace llarp std::string Config::generateBaseRouterConfig() { - ConfigGenParameters params; - params.isRelay = true; - params.defaultDataDir = m_DataDir; + auto params = MakeGenParams(); + params->isRelay = true; + params->defaultDataDir = m_DataDir; llarp::ConfigDefinition def{true}; - initializeConfig(def, params); + initializeConfig(def, *params); generateCommonConfigComments(def); // lokid @@ -1485,7 +1547,7 @@ namespace llarp std::shared_ptr Config::EmbeddedConfig() { - auto config = std::make_shared(fs::path{}); + auto config = std::make_shared(); config->Load(); config->logging.m_logLevel = log::Level::off; config->api.m_enableRPCServer = false; diff --git a/llarp/config/config.hpp b/llarp/config/config.hpp index 22385086b..0d7a6856d 100644 --- a/llarp/config/config.hpp +++ b/llarp/config/config.hpp @@ -39,8 +39,18 @@ namespace llarp /// parameters that need to be passed around. struct ConfigGenParameters { + ConfigGenParameters() = default; + virtual ~ConfigGenParameters() = default; + + ConfigGenParameters(const ConfigGenParameters&) = delete; + ConfigGenParameters(ConfigGenParameters&&) = delete; + bool isRelay = false; fs::path defaultDataDir; + + /// get network platform (virtual for unit test mocks) + virtual const llarp::net::Platform* + Net_ptr() const = 0; }; struct RouterConfig @@ -55,9 +65,6 @@ namespace llarp bool m_blockBogons = false; - std::optional m_PublicIP; - nuint16_t m_PublicPort; - int m_workerThreads = -1; int m_numNetThreads = -1; @@ -69,6 +76,10 @@ namespace llarp std::string m_transportKeyFile; bool m_isRelay = false; + /// deprecated + std::optional PublicIP; + /// deprecated + std::optional PublicPort; void defineConfigOptions(ConfigDefinition& conf, const ConfigGenParameters& params); @@ -154,19 +165,10 @@ namespace llarp struct LinksConfig { - struct LinkInfo - { - std::string m_interface; - int addressFamily = -1; - uint16_t port = -1; - }; - /// Create a LinkInfo from the given string. - /// @throws if str does not represent a LinkInfo. - LinkInfo - LinkInfoFromINIValues(std::string_view name, std::string_view value); - - LinkInfo m_OutboundLink; - std::vector m_InboundLinks; + std::optional PublicAddress; + std::optional PublicPort; + std::vector OutboundLinks; + std::vector InboundListenAddrs; void defineConfigOptions(ConfigDefinition& conf, const ConfigGenParameters& params); @@ -220,9 +222,13 @@ namespace llarp struct Config { - explicit Config(fs::path datadir); + explicit Config(std::optional datadir = std::nullopt); - ~Config() = default; + virtual ~Config() = default; + + /// create generation params (virtual for unit test mock) + virtual std::unique_ptr + MakeGenParams() const; RouterConfig router; NetworkConfig network; @@ -250,6 +256,10 @@ namespace llarp bool Load(std::optional fname = std::nullopt, bool isRelay = false); + // Load a config from a string of ini, same effects as Config::Load + bool + LoadString(std::string_view ini, bool isRelay = false); + std::string generateBaseClientConfig(); diff --git a/llarp/config/definition.cpp b/llarp/config/definition.cpp index 9156acaee..ccf885c15 100644 --- a/llarp/config/definition.cpp +++ b/llarp/config/definition.cpp @@ -89,18 +89,18 @@ namespace llarp // fall back to undeclared handler if needed auto& sectionDefinitions = secItr->second; auto defItr = sectionDefinitions.find(std::string(name)); - if (defItr == sectionDefinitions.end()) + if (defItr != sectionDefinitions.end()) { - if (not haveUndeclaredHandler) - throw std::invalid_argument{fmt::format("unrecognized option [{}]:{}", section, name)}; - auto& handler = undItr->second; - handler(section, name, value); + OptionDefinition_ptr& definition = defItr->second; + definition->parseValue(std::string(value)); return *this; } - OptionDefinition_ptr& definition = defItr->second; - definition->parseValue(std::string(value)); + if (not haveUndeclaredHandler) + throw std::invalid_argument{fmt::format("unrecognized option [{}]: {}", section, name)}; + auto& handler = undItr->second; + handler(section, name, value); return *this; } @@ -142,9 +142,9 @@ namespace llarp void ConfigDefinition::acceptAllOptions() { - visitSections([&](const std::string& section, const DefinitionMap&) { + visitSections([this](const std::string& section, const DefinitionMap&) { visitDefinitions( - section, [&](const std::string&, const OptionDefinition_ptr& def) { def->tryAccept(); }); + section, [](const std::string&, const OptionDefinition_ptr& def) { def->tryAccept(); }); }); } diff --git a/llarp/config/definition.hpp b/llarp/config/definition.hpp index 07e448c01..b3f9cd0ab 100644 --- a/llarp/config/definition.hpp +++ b/llarp/config/definition.hpp @@ -243,12 +243,9 @@ namespace llarp std::optional getValue() const { - if (parsedValues.size()) - return parsedValues[0]; - else if (not required and not multiValued) - return defaultValue; - else - return std::nullopt; + if (parsedValues.empty()) + return required ? std::nullopt : defaultValue; + return parsedValues.front(); } /// Returns the value at the given index. @@ -340,7 +337,7 @@ namespace llarp void tryAccept() const override { - if (required and parsedValues.size() == 0) + if (required and parsedValues.empty()) { throw std::runtime_error{fmt::format( "cannot call tryAccept() on [{}]:{} when required but no value available", @@ -348,14 +345,14 @@ namespace llarp name)}; } - // don't use default value if we are multi-valued and have no value - if (multiValued and parsedValues.size() == 0) - return; - if (acceptor) { if (multiValued) { + // add default value in multi value mode + if (defaultValue and parsedValues.empty()) + acceptor(*defaultValue); + for (auto value : parsedValues) { acceptor(value); @@ -365,13 +362,7 @@ namespace llarp { auto maybe = getValue(); if (maybe) - { acceptor(*maybe); - } - else - { - assert(not defaultValue); // maybe should have a value if defaultValue does - } } } } @@ -510,6 +501,14 @@ namespace llarp void acceptAllOptions(); + /// validates and accept all parsed options + inline void + process() + { + validateRequiredFields(); + acceptAllOptions(); + } + /// Add comments for a given section. Comments are replayed in-order during config file /// generation. A proper comment prefix will automatically be applied, and the entire comment /// will otherwise be used verbatim (no automatic line separation, etc.). diff --git a/llarp/constants/link_layer.hpp b/llarp/constants/link_layer.hpp index 5da4d97f6..39eb38449 100644 --- a/llarp/constants/link_layer.hpp +++ b/llarp/constants/link_layer.hpp @@ -8,3 +8,8 @@ constexpr size_t MAX_LINK_MSG_SIZE = 8192; static constexpr auto DefaultLinkSessionLifetime = 5min; constexpr size_t MaxSendQueueSize = 1024 * 16; static constexpr auto LinkLayerConnectTimeout = 5s; + +namespace llarp::constants +{ + static constexpr auto DefaultInboundIWPPort = uint16_t{1090}; +} diff --git a/llarp/ev/ev_libuv.hpp b/llarp/ev/ev_libuv.hpp index 62c3d5a4c..32e8010ca 100644 --- a/llarp/ev/ev_libuv.hpp +++ b/llarp/ev/ev_libuv.hpp @@ -18,14 +18,14 @@ namespace llarp::uv class UVWakeup; class UVRepeater; - class Loop final : public llarp::EventLoop + class Loop : public llarp::EventLoop { public: using Callback = std::function; Loop(size_t queue_size); - void + virtual void run() override; bool @@ -63,7 +63,7 @@ namespace llarp::uv std::shared_ptr make_repeater() override; - std::shared_ptr + virtual std::shared_ptr make_udp(UDPReceiveFunc on_recv) override; void @@ -75,8 +75,11 @@ namespace llarp::uv bool inEventLoop() const override; - private: + protected: std::shared_ptr m_Impl; + std::optional m_EventLoopThreadID; + + private: std::shared_ptr m_WakeUp; std::atomic m_Run; using AtomicQueue_t = llarp::thread::Queue>; @@ -92,8 +95,6 @@ namespace llarp::uv std::unordered_map> m_Polls; - std::optional m_EventLoopThreadID; - void wakeup() override; }; diff --git a/llarp/ev/vpn.hpp b/llarp/ev/vpn.hpp index 64da9f50d..4488010eb 100644 --- a/llarp/ev/vpn.hpp +++ b/llarp/ev/vpn.hpp @@ -76,6 +76,15 @@ namespace llarp::vpn IRouteManager(IRouteManager&&) = delete; virtual ~IRouteManager() = default; + virtual const llarp::net::Platform* + Net_ptr() const; + + inline const llarp::net::Platform& + Net() const + { + return *Net_ptr(); + } + virtual void AddRoute(IPVariant_t ip, IPVariant_t gateway) = 0; diff --git a/llarp/handlers/exit.cpp b/llarp/handlers/exit.cpp index 73902ac04..336eb68d9 100644 --- a/llarp/handlers/exit.cpp +++ b/llarp/handlers/exit.cpp @@ -710,7 +710,7 @@ namespace llarp m_OurRange = networkConfig.m_ifaddr; if (!m_OurRange.addr.h) { - const auto maybe = llarp::FindFreeRange(); + const auto maybe = m_Router->Net().FindFreeRange(); if (not maybe.has_value()) throw std::runtime_error("cannot find free interface range"); m_OurRange = *maybe; @@ -725,7 +725,7 @@ namespace llarp m_ifname = networkConfig.m_ifname; if (m_ifname.empty()) { - const auto maybe = llarp::FindFreeTun(); + const auto maybe = m_Router->Net().FindFreeTun(); if (not maybe.has_value()) throw std::runtime_error("cannot find free interface name"); m_ifname = *maybe; diff --git a/llarp/handlers/tun.cpp b/llarp/handlers/tun.cpp index 66dcee92b..1cb4f5cf3 100644 --- a/llarp/handlers/tun.cpp +++ b/llarp/handlers/tun.cpp @@ -222,7 +222,7 @@ namespace llarp m_IfName = conf.m_ifname; if (m_IfName.empty()) { - const auto maybe = llarp::FindFreeTun(); + const auto maybe = m_router->Net().FindFreeTun(); if (not maybe.has_value()) throw std::runtime_error("cannot find free interface name"); m_IfName = *maybe; @@ -231,7 +231,7 @@ namespace llarp m_OurRange = conf.m_ifaddr; if (!m_OurRange.addr.h) { - const auto maybe = llarp::FindFreeRange(); + const auto maybe = m_router->Net().FindFreeRange(); if (not maybe.has_value()) { throw std::runtime_error("cannot find free address range"); @@ -938,7 +938,7 @@ namespace llarp m_OurIPv6 = llarp::huint128_t{ llarp::uint128_t{0xfd2e'6c6f'6b69'0000, llarp::net::TruncateV6(m_OurRange.addr).h}}; #else - const auto maybe = GetInterfaceIPv6Address(m_IfName); + const auto maybe = m_router->Net().GetInterfaceIPv6Address(m_IfName); if (maybe.has_value()) { m_OurIPv6 = *maybe; diff --git a/llarp/link/server.cpp b/llarp/link/server.cpp index c22b0b863..58924e4ad 100644 --- a/llarp/link/server.cpp +++ b/llarp/link/server.cpp @@ -129,9 +129,12 @@ namespace llarp visit(s.get()); } - bool - ILinkLayer::Configure(AbstractRouter* router, std::string ifname, int af, uint16_t port) + void + ILinkLayer::Bind(AbstractRouter* router, SockAddr bind_addr) { + if (router->Net().IsLoopbackAddress(bind_addr.getIP())) + throw std::runtime_error{"cannot udp bind socket on loopback"}; + m_ourAddr = bind_addr; m_Router = router; m_udp = m_Router->loop()->make_udp( [this]([[maybe_unused]] UDPHandle& udp, const SockAddr& from, llarp_buffer_t buf) { @@ -141,71 +144,11 @@ namespace llarp RecvFrom(from, std::move(pkt)); }); - if (ifname == "*") - { - if (router->IsServiceNode()) - { - if (auto maybe = router->OurPublicIP()) - { - auto addr = var::visit([](auto&& addr) { return SockAddr{addr}; }, *maybe); - // service node outbound link - if (HasInterfaceAddress(addr.getIP())) - { - // we have our ip claimed on a local net interface - m_ourAddr = addr; - } - else if (auto maybe = net::AllInterfaces(addr)) - { - // we do not have our claimed ip, nat or something? - m_ourAddr = *maybe; - } - else if (auto maybe = net::AllInterfaces(SockAddr{"0.0.0.0"})) - { - // one last fallback - m_ourAddr = *maybe; - } - else - return false; // the ultimate failure case - } - else - return false; - } - else if (auto maybe = net::AllInterfaces(SockAddr{"0.0.0.0"})) - { - // client outbound link - m_ourAddr = *maybe; - } - else - return false; - } - else - { - if (ifname == "0.0.0.0" and not GetBestNetIF(ifname)) - throw std::invalid_argument{ - "0.0.0.0 provided and we cannot find a valid ip to use, please set one " - "explicitly instead in the bind section instead of 0.0.0.0"}; - if (const auto maybe = GetInterfaceAddr(ifname, af)) - { - m_ourAddr = *maybe; - } - else - { - try - { - m_ourAddr = SockAddr{ifname + ":0"}; - } - catch (const std::exception& ex) - { - LogError("Could not use ifname ", ifname, " to configure ILinkLayer: ", ex.what()); - throw ex; - } - } - } - m_ourAddr.setPort(port); - if (not m_udp->listen(m_ourAddr)) - return false; + if (m_udp->listen(m_ourAddr)) + return; - return true; + throw std::runtime_error{ + fmt::format("failed to listen {} udp socket on {}", Name(), m_ourAddr)}; } void diff --git a/llarp/link/server.hpp b/llarp/link/server.hpp index 02cc17f83..0e7c86c89 100644 --- a/llarp/link/server.hpp +++ b/llarp/link/server.hpp @@ -107,8 +107,8 @@ namespace llarp void SendTo_LL(const SockAddr& to, const llarp_buffer_t& pkt); - virtual bool - Configure(AbstractRouter* loop, std::string ifname, int af, uint16_t port); + void + Bind(AbstractRouter* router, SockAddr addr); virtual std::shared_ptr NewOutboundSession(const RouterContact& rc, const AddressInfo& ai) = 0; @@ -233,6 +233,13 @@ namespace llarp return m_Router; } + /// Get the local sock addr we are bound on + const SockAddr& + LocalSocketAddr() const + { + return m_ourAddr; + } + private: const SecretKey& m_RouterEncSecret; diff --git a/llarp/net/ip_range.hpp b/llarp/net/ip_range.hpp index 31aee44d6..e89e54865 100644 --- a/llarp/net/ip_range.hpp +++ b/llarp/net/ip_range.hpp @@ -31,6 +31,13 @@ namespace llarp return IPRange{net::ExpandV4(ipaddr_ipv4_bits(a, b, c, d)), netmask_ipv6_bits(mask + 96)}; } + static inline IPRange + FromIPv4(net::ipv4addr_t addr, net::ipv4addr_t netmask) + { + return IPRange{ + net::ExpandV4(ToHost(addr)), netmask_ipv6_bits(bits::count_bits(netmask) + 96)}; + } + /// return true if this iprange is in the IPv4 mapping range for containing ipv4 addresses constexpr bool IsV4() const @@ -39,6 +46,15 @@ namespace llarp return ipv4_map.Contains(addr); } + /// get address family + constexpr int + Family() const + { + if (IsV4()) + return AF_INET; + return AF_INET6; + } + /// return true if we intersect with a bogon range bool BogonRange() const diff --git a/llarp/net/net.cpp b/llarp/net/net.cpp index 09877e944..ad066b24c 100644 --- a/llarp/net/net.cpp +++ b/llarp/net/net.cpp @@ -2,6 +2,7 @@ #include "net_if.hpp" #include +#include #ifdef ANDROID #include @@ -22,13 +23,17 @@ #ifdef ANDROID #include #else -#ifndef _WIN32 +#ifdef _WIN32 +#include +#include +#else #include #endif #endif #include #include +#include bool operator==(const sockaddr& a, const sockaddr& b) @@ -76,559 +81,408 @@ operator==(const sockaddr_in6& a, const sockaddr_in6& b) return a.sin6_port == b.sin6_port && a.sin6_addr == b.sin6_addr; } -#ifdef _WIN32 -#include -#include -#include -#include - -// current strategy: mingw 32-bit builds call an inlined version of the function -// microsoft c++ and mingw 64-bit builds call the normal function -#define DEFAULT_BUFFER_SIZE 15000 - -// in any case, we still need to implement some form of -// getifaddrs(3) with compatible semantics on NT... -// daemon.ini section [bind] will have something like -// [bind] -// Ethernet=1090 -// inside, since that's what we use in windows to refer to -// network interfaces -struct llarp_nt_ifaddrs_t -{ - struct llarp_nt_ifaddrs_t* ifa_next; /* Pointer to the next structure. */ - char* ifa_name; /* Name of this network interface. */ - unsigned int ifa_flags; /* Flags as from SIOCGIFFLAGS ioctl. */ - struct sockaddr* ifa_addr; /* Network address of this interface. */ - struct sockaddr* ifa_netmask; /* Netmask of this interface. */ -}; - -// internal struct -struct _llarp_nt_ifaddrs_t -{ - struct llarp_nt_ifaddrs_t _ifa; - char _name[256]; - struct sockaddr_storage _addr; - struct sockaddr_storage _netmask; -}; - -static inline void* -_llarp_nt_heap_alloc(const size_t n_bytes) +namespace llarp::net { - /* Does not appear very safe with re-entrant calls on XP */ - return HeapAlloc(GetProcessHeap(), HEAP_GENERATE_EXCEPTIONS, n_bytes); -} - -static inline void -_llarp_nt_heap_free(void* mem) -{ - HeapFree(GetProcessHeap(), 0, mem); -} -#define llarp_nt_new0(struct_type, n_structs) \ - ((struct_type*)malloc((size_t)sizeof(struct_type) * (size_t)(n_structs))) - -int -llarp_nt_sockaddr_pton(const char* src, struct sockaddr* dst) -{ - struct addrinfo hints; - struct addrinfo* result = nullptr; - memset(&hints, 0, sizeof(struct addrinfo)); - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_DGRAM; - hints.ai_protocol = IPPROTO_TCP; - hints.ai_flags = AI_NUMERICHOST; - const int status = getaddrinfo(src, nullptr, &hints, &result); - if (!status) + class Platform_Base : public llarp::net::Platform { - memcpy(dst, result->ai_addr, result->ai_addrlen); - freeaddrinfo(result); - return 1; - } - return 0; -} - -/* NB: IP_ADAPTER_INFO size varies size due to sizeof (time_t), the API assumes - * 4-byte datatype whilst compiler uses an 8-byte datatype. Size can be forced - * with -D_USE_32BIT_TIME_T with side effects to everything else. - * - * Only supports IPv4 addressing similar to SIOCGIFCONF socket option. - * - * Interfaces that are not "operationally up" will return the address 0.0.0.0, - * this includes adapters with static IP addresses but with disconnected cable. - * This is documented under the GetIpAddrTable API. Interface status can only - * be determined by the address, a separate flag is introduced with the - * GetAdapterAddresses API. - * - * The IPv4 loopback interface is not included. - * - * Available in Windows 2000 and Wine 1.0. - */ -static bool -_llarp_nt_getadaptersinfo(struct llarp_nt_ifaddrs_t** ifap) -{ - DWORD dwRet; - ULONG ulOutBufLen = DEFAULT_BUFFER_SIZE; - PIP_ADAPTER_INFO pAdapterInfo = nullptr; - PIP_ADAPTER_INFO pAdapter = nullptr; - - /* loop to handle interfaces coming online causing a buffer overflow - * between first call to list buffer length and second call to enumerate. - */ - for (unsigned i = 3; i; i--) - { -#ifdef DEBUG - fprintf(stderr, "IP_ADAPTER_INFO buffer length %lu bytes.\n", ulOutBufLen); -#endif - pAdapterInfo = (IP_ADAPTER_INFO*)_llarp_nt_heap_alloc(ulOutBufLen); - dwRet = GetAdaptersInfo(pAdapterInfo, &ulOutBufLen); - if (ERROR_BUFFER_OVERFLOW == dwRet) + public: + bool + IsLoopbackAddress(ipaddr_t ip) const override { - _llarp_nt_heap_free(pAdapterInfo); - pAdapterInfo = nullptr; + return var::visit( + [loopback6 = IPRange{huint128_t{uint128_t{0UL, 1UL}}, netmask_ipv6_bits(128)}, + loopback4 = IPRange::FromIPv4(127, 0, 0, 0, 8)](auto&& ip) { + const auto h_ip = ToHost(ip); + return loopback4.Contains(h_ip) or loopback6.Contains(h_ip); + }, + ip); } - else + + SockAddr + Wildcard(int af) const override { - break; + if (af == AF_INET) + { + sockaddr_in addr{}; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_ANY); + addr.sin_port = htons(0); + return SockAddr{addr}; + } + if (af == AF_INET6) + { + sockaddr_in6 addr6{}; + addr6.sin6_family = AF_INET6; + addr6.sin6_port = htons(0); + addr6.sin6_addr = IN6ADDR_ANY_INIT; + return SockAddr{addr6}; + } + throw std::invalid_argument{fmt::format("{} is not a valid address family")}; } - } - switch (dwRet) - { - case ERROR_SUCCESS: /* NO_ERROR */ - break; - case ERROR_BUFFER_OVERFLOW: - errno = ENOBUFS; - if (pAdapterInfo) - _llarp_nt_heap_free(pAdapterInfo); - return false; - default: - errno = dwRet; -#ifdef DEBUG - fprintf(stderr, "system call failed: %lu\n", GetLastError()); -#endif - if (pAdapterInfo) - _llarp_nt_heap_free(pAdapterInfo); - return false; - } - - /* count valid adapters */ - int n = 0, k = 0; - for (pAdapter = pAdapterInfo; pAdapter; pAdapter = pAdapter->Next) - { - for (IP_ADDR_STRING* pIPAddr = &pAdapter->IpAddressList; pIPAddr; pIPAddr = pIPAddr->Next) + bool + IsBogon(const llarp::SockAddr& addr) const override { - /* skip null adapters */ - if (strlen(pIPAddr->IpAddress.String) == 0) - continue; - ++n; + return llarp::IsBogon(addr.asIPv6()); } - } -#ifdef DEBUG - fprintf(stderr, "GetAdaptersInfo() discovered %d interfaces.\n", n); -#endif + bool + IsWildcardAddress(ipaddr_t ip) const override + { + return var::visit([](auto&& ip) { return not ip.n; }, ip); + } + }; - /* contiguous block for adapter list */ - struct _llarp_nt_ifaddrs_t* ifa = llarp_nt_new0(struct _llarp_nt_ifaddrs_t, n); - struct _llarp_nt_ifaddrs_t* ift = ifa; - int val = 0; - /* now populate list */ - for (pAdapter = pAdapterInfo; pAdapter; pAdapter = pAdapter->Next) +#ifdef _WIN32 + class Platform_Impl : public Platform_Base { - for (IP_ADDR_STRING* pIPAddr = &pAdapter->IpAddressList; pIPAddr; pIPAddr = pIPAddr->Next) + /// visit all adapters (not addresses). windows serves net info per adapter unlink posix which + /// gives a list of all distinct addresses. + template + void + iter_adapters(Visit_t&& visit) const { - /* skip null adapters */ - if (strlen(pIPAddr->IpAddress.String) == 0) - continue; - - /* address */ - ift->_ifa.ifa_addr = (struct sockaddr*)&ift->_addr; - val = llarp_nt_sockaddr_pton(pIPAddr->IpAddress.String, ift->_ifa.ifa_addr); - assert(1 == val); - - /* name */ -#ifdef DEBUG - fprintf(stderr, "name:%s IPv4 index:%lu\n", pAdapter->AdapterName, pAdapter->Index); -#endif - ift->_ifa.ifa_name = ift->_name; - StringCchCopyN(ift->_ifa.ifa_name, 128, pAdapter->AdapterName, 128); + constexpr auto flags = GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST + | GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_INCLUDE_PREFIX | GAA_FLAG_INCLUDE_GATEWAYS + | GAA_FLAG_INCLUDE_ALL_INTERFACES; - /* flags: assume up, broadcast and multicast */ - ift->_ifa.ifa_flags = IFF_UP | IFF_BROADCAST | IFF_MULTICAST; - if (pAdapter->Type == MIB_IF_TYPE_LOOPBACK) - ift->_ifa.ifa_flags |= IFF_LOOPBACK; + ULONG sz{}; + GetAdaptersAddresses(AF_UNSPEC, flags, nullptr, nullptr, &sz); + auto* ptr = new uint8_t[sz]; + auto* addrs = reinterpret_cast(ptr); - /* netmask */ - ift->_ifa.ifa_netmask = (sockaddr*)&ift->_netmask; - val = llarp_nt_sockaddr_pton(pIPAddr->IpMask.String, ift->_ifa.ifa_netmask); - assert(1 == val); + if (auto err = GetAdaptersAddresses(AF_UNSPEC, flags, nullptr, addrs, &sz); + err != ERROR_SUCCESS) + throw llarp::win32::error{err, "GetAdaptersAddresses()"}; + + for (auto* addr = addrs; addr and addr->Next; addr = addr->Next) + visit(addr); + + delete[] ptr; + } - /* next */ - if (k++ < (n - 1)) + template + bool + adapter_has_ip(adapter_t* a, ipaddr_t ip) const + { + for (auto* addr = a->FirstUnicastAddress; addr and addr->Next; addr = addr->Next) { - ift->_ifa.ifa_next = (struct llarp_nt_ifaddrs_t*)(ift + 1); - ift = (struct _llarp_nt_ifaddrs_t*)(ift->_ifa.ifa_next); + SockAddr saddr{*addr->Address.lpSockaddr}; + if (saddr.getIP() == ip) + return true; } - else + return false; + } + + template + bool + adapter_has_fam(adapter_t* a, int af) const + { + for (auto* addr = a->FirstUnicastAddress; addr and addr->Next; addr = addr->Next) { - ift->_ifa.ifa_next = nullptr; + SockAddr saddr{*addr->Address.lpSockaddr}; + if (saddr.Family() == af) + return true; } + return false; } - } - - if (pAdapterInfo) - _llarp_nt_heap_free(pAdapterInfo); - *ifap = (struct llarp_nt_ifaddrs_t*)ifa; - return true; -} - -// an implementation of if_nametoindex(3) based on GetAdapterIndex(2) -// with a fallback to GetAdaptersAddresses(2) commented out for now -// unless it becomes evident that the first codepath fails in certain -// edge cases? -static unsigned -_llarp_nt_nametoindex(const char* ifname) -{ - ULONG ifIndex; - DWORD dwRet; - char szAdapterName[256]; - - if (!ifname) - return 0; - StringCchCopyN(szAdapterName, sizeof(szAdapterName), ifname, 256); - dwRet = GetAdapterIndex((LPWSTR)szAdapterName, &ifIndex); - - if (!dwRet) - return ifIndex; - else - return 0; -} - -// the emulated getifaddrs(3) itself. -static bool -llarp_nt_getifaddrs(struct llarp_nt_ifaddrs_t** ifap) -{ - assert(nullptr != ifap); -#ifdef DEBUG - fprintf(stderr, "llarp_nt_getifaddrs (ifap:%p error:%p)\n", (void*)ifap, (void*)errno); -#endif - return _llarp_nt_getadaptersinfo(ifap); -} + public: + std::optional + GetInterfaceIndex(ipaddr_t ip) const override + { + std::optional found; + iter_adapters([&found, ip, this](auto* adapter) { + if (found) + return; + if (adapter_has_ip(adapter, ip)) + found = adapter->IfIndex; + }); + return found; + } -static void -llarp_nt_freeifaddrs(struct llarp_nt_ifaddrs_t* ifa) -{ - if (!ifa) - return; - free(ifa); -} + std::optional + GetInterfaceAddr(std::string_view name, int af) const override + { + std::optional found; + iter_adapters([name = std::string{name}, af, &found, this](auto* a) { + if (found) + return; + if (std::string{a->AdapterName} != name) + return; -// emulated if_nametoindex(3) -static unsigned -llarp_nt_if_nametoindex(const char* ifname) -{ - if (!ifname) - return 0; - return _llarp_nt_nametoindex(ifname); -} + if (adapter_has_fam(a, af)) + found = SockAddr{*a->FirstUnicastAddress->Address.lpSockaddr}; + }); + return found; + } -// fix up names for win32 -#define ifaddrs llarp_nt_ifaddrs_t -#define getifaddrs llarp_nt_getifaddrs -#define freeifaddrs llarp_nt_freeifaddrs -#define if_nametoindex llarp_nt_if_nametoindex -#endif + std::optional + AllInterfaces(SockAddr fallback) const override + { + // windows seems to not give a shit about source address + return fallback.isIPv6() ? SockAddr{"[::]"} : SockAddr{"0.0.0.0"}; + } -// jeff's original code -bool -llarp_getifaddr(const char* ifname, int af, struct sockaddr* addr) -{ - ifaddrs* ifa = nullptr; - bool found = false; - socklen_t sl = sizeof(sockaddr_in6); - if (af == AF_INET) - sl = sizeof(sockaddr_in); + std::optional + FindFreeTun() const override + { + // TODO: implement me ? + return std::nullopt; + } -#ifndef _WIN32 - if (getifaddrs(&ifa) == -1) -#else - if (!strcmp(ifname, "lo") || !strcmp(ifname, "lo0")) - { - if (addr) + std::optional + GetBestNetIF(int) const override { - sockaddr_in* lo = (sockaddr_in*)addr; - lo->sin_family = af; - lo->sin_port = 0; - inet_pton(af, "127.0.0.1", &lo->sin_addr); + // TODO: implement me ? + return std::nullopt; } - return true; - } - if (!getifaddrs(&ifa)) -#endif - return false; - ifaddrs* i = ifa; - while (i) - { - if (i->ifa_addr) + + std::optional + FindFreeRange() const override { - // llarp::LogInfo(__FILE__, "scanning ", i->ifa_name, " af: ", - // std::to_string(i->ifa_addr->sa_family)); - if (std::string_view{i->ifa_name} == std::string_view{ifname} && i->ifa_addr->sa_family == af) - { - // can't do this here - // llarp::Addr a(*i->ifa_addr); - // if(!a.isPrivate()) - //{ - // llarp::LogInfo(__FILE__, "found ", ifname, " af: ", af); - if (addr) + std::list currentRanges; + iter_adapters([¤tRanges](auto* i) { + for (auto* addr = i->FirstUnicastAddress; addr and addr->Next; addr = addr->Next) { - memcpy(addr, i->ifa_addr, sl); - if (af == AF_INET6) - { - // set scope id - auto* ip6addr = (sockaddr_in6*)addr; - ip6addr->sin6_scope_id = if_nametoindex(ifname); - ip6addr->sin6_flowinfo = 0; - } + SockAddr saddr{*addr->Address.lpSockaddr}; + currentRanges.emplace_back( + saddr.asIPv6(), + ipaddr_netmask_bits(addr->OnLinkPrefixLength, addr->Address.lpSockaddr->sa_family)); + } + }); + + auto ownsRange = [¤tRanges](const IPRange& range) -> bool { + for (const auto& ownRange : currentRanges) + { + if (ownRange * range) + return true; } - found = true; - break; + return false; + }; + // generate possible ranges to in order of attempts + std::list possibleRanges; + for (byte_t oct = 16; oct < 32; ++oct) + { + possibleRanges.emplace_back(IPRange::FromIPv4(172, oct, 0, 1, 16)); + } + for (byte_t oct = 0; oct < 255; ++oct) + { + possibleRanges.emplace_back(IPRange::FromIPv4(10, oct, 0, 1, 16)); + } + for (byte_t oct = 0; oct < 255; ++oct) + { + possibleRanges.emplace_back(IPRange::FromIPv4(192, 168, oct, 1, 24)); } - //} + // for each possible range pick the first one we don't own + for (const auto& range : possibleRanges) + { + if (not ownsRange(range)) + return range; + } + return std::nullopt; } - i = i->ifa_next; - } - if (ifa) - freeifaddrs(ifa); - return found; -} + std::string + LoopbackInterfaceName() const override + { + // todo: implement me? does windows even have a loopback? + return ""; + } + bool + HasInterfaceAddress(ipaddr_t ip) const override + { + return GetInterfaceIndex(ip) != std::nullopt; + } + }; -namespace llarp -{ - static void - IterAllNetworkInterfaces(std::function visit) - { - ifaddrs* ifa = nullptr; -#ifndef _WIN32 - if (getifaddrs(&ifa) == -1) #else - if (!getifaddrs(&ifa)) -#endif - return; - ifaddrs* i = ifa; - while (i) + class Platform_Impl : public Platform_Base + { + template + void + iter_all(Visit_t&& visit) const { - visit(i); - i = i->ifa_next; + ifaddrs* addrs{nullptr}; + if (getifaddrs(&addrs)) + throw std::runtime_error{fmt::format("getifaddrs(): {}", strerror(errno))}; + + for (auto next = addrs; addrs and addrs->ifa_next; addrs = addrs->ifa_next) + visit(next); + + freeifaddrs(addrs); } - if (ifa) - freeifaddrs(ifa); - } - namespace net - { + public: std::string - LoopbackInterfaceName() + LoopbackInterfaceName() const override { - const auto loopback = IPRange::FromIPv4(127, 0, 0, 0, 8); std::string ifname; - IterAllNetworkInterfaces([&ifname, loopback](ifaddrs* const i) { - if (i->ifa_addr and i->ifa_addr->sa_family == AF_INET) + iter_all([this, &ifname](auto i) { + if (i and i->ifa_addr and i->ifa_addr->sa_family == AF_INET) { - llarp::nuint32_t addr{((sockaddr_in*)i->ifa_addr)->sin_addr.s_addr}; - if (loopback.Contains(xntohl(addr))) + const SockAddr addr{*i->ifa_addr}; + if (IsLoopbackAddress(addr.getIP())) { ifname = i->ifa_name; } } }); if (ifname.empty()) - { - throw std::runtime_error( - "we have no ipv4 loopback interface for some ungodly reason, yeah idk fam"); - } + throw std::runtime_error{"we have no ipv4 loopback interface for some ungodly reason"}; return ifname; } - } // namespace net - bool - GetBestNetIF(std::string& ifname, int af) - { - bool found = false; - IterAllNetworkInterfaces([&](ifaddrs* i) { - if (found) - return; - if (i->ifa_addr) - { - if (i->ifa_addr->sa_family == af) + std::optional + GetBestNetIF(int af) const override + { + std::optional found; + iter_all([this, &found, af](auto i) { + if (found) + return; + if (i and i->ifa_addr and i->ifa_addr->sa_family == af) { - llarp::SockAddr a(*i->ifa_addr); - llarp::IpAddress ip(a); - - if (!ip.isBogon()) + if (not IsBogon(*i->ifa_addr)) { - ifname = i->ifa_name; - found = true; + found = i->ifa_name; } } - } - }); - return found; - } + }); - // TODO: ipv6? - std::optional - FindFreeRange() - { - std::list currentRanges; - IterAllNetworkInterfaces([&](ifaddrs* i) { - if (i && i->ifa_addr) + return found; + } + + std::optional + FindFreeRange() const override + { + std::list currentRanges; + iter_all([¤tRanges](auto i) { + if (i and i->ifa_addr and i->ifa_addr->sa_family == AF_INET) + { + ipv4addr_t addr{reinterpret_cast(i->ifa_addr)->sin_addr.s_addr}; + ipv4addr_t mask{reinterpret_cast(i->ifa_netmask)->sin_addr.s_addr}; + currentRanges.emplace_back(IPRange::FromIPv4(addr, mask)); + } + }); + + auto ownsRange = [¤tRanges](const IPRange& range) -> bool { + for (const auto& ownRange : currentRanges) + { + if (ownRange * range) + return true; + } + return false; + }; + // generate possible ranges to in order of attempts + std::list possibleRanges; + for (byte_t oct = 16; oct < 32; ++oct) { - const auto fam = i->ifa_addr->sa_family; - if (fam != AF_INET) - return; - auto* addr = (sockaddr_in*)i->ifa_addr; - auto* mask = (sockaddr_in*)i->ifa_netmask; - nuint32_t ifaddr{addr->sin_addr.s_addr}; - nuint32_t ifmask{mask->sin_addr.s_addr}; -#ifdef _WIN32 - // do not delete, otherwise GCC will do horrible things to this lambda - LogDebug("found ", ifaddr, " with mask ", ifmask); -#endif - if (addr->sin_addr.s_addr) - // skip unconfig'd adapters (windows passes these through the unix-y - // wrapper) - currentRanges.emplace_back( - IPRange{net::ExpandV4(xntohl(ifaddr)), net::ExpandV4(xntohl(ifmask))}); + possibleRanges.emplace_back(IPRange::FromIPv4(172, oct, 0, 1, 16)); } - }); - auto ownsRange = [¤tRanges](const IPRange& range) -> bool { - for (const auto& ownRange : currentRanges) + for (byte_t oct = 0; oct < 255; ++oct) { - if (ownRange * range) - return true; + possibleRanges.emplace_back(IPRange::FromIPv4(10, oct, 0, 1, 16)); } - return false; - }; - // generate possible ranges to in order of attempts - std::list possibleRanges; - for (byte_t oct = 16; oct < 32; ++oct) - { - possibleRanges.emplace_back(IPRange::FromIPv4(172, oct, 0, 1, 16)); - } - for (byte_t oct = 0; oct < 255; ++oct) - { - possibleRanges.emplace_back(IPRange::FromIPv4(10, oct, 0, 1, 16)); - } - for (byte_t oct = 0; oct < 255; ++oct) - { - possibleRanges.emplace_back(IPRange::FromIPv4(192, 168, oct, 1, 24)); + for (byte_t oct = 0; oct < 255; ++oct) + { + possibleRanges.emplace_back(IPRange::FromIPv4(192, 168, oct, 1, 24)); + } + // for each possible range pick the first one we don't own + for (const auto& range : possibleRanges) + { + if (not ownsRange(range)) + return range; + } + return std::nullopt; } - // for each possible range pick the first one we don't own - for (const auto& range : possibleRanges) + + std::optional GetInterfaceIndex(ipaddr_t) const override { - if (not ownsRange(range)) - return range; + // todo: implement me + return std::nullopt; } - return std::nullopt; - } - std::optional - FindFreeTun() - { - int num = 0; - while (num < 255) + std::optional + FindFreeTun() const override { - std::string iftestname = fmt::format("lokitun{}", num); - bool found = llarp_getifaddr(iftestname.c_str(), AF_INET, nullptr); - if (!found) + int num = 0; + while (num < 255) { - return iftestname; + std::string ifname = fmt::format("lokitun{}", num); + if (GetInterfaceAddr(ifname, AF_INET)) + return ifname; + num++; } - num++; - } - return std::nullopt; - } - - std::optional - GetInterfaceAddr(const std::string& ifname, int af) - { - sockaddr_storage s; - sockaddr* sptr = (sockaddr*)&s; - sptr->sa_family = af; - if (!llarp_getifaddr(ifname.c_str(), af, sptr)) - return std::nullopt; - return SockAddr{*sptr}; - } - - std::optional - GetInterfaceIPv6Address(std::string ifname) - { - sockaddr_storage s; - sockaddr* sptr = (sockaddr*)&s; - sptr->sa_family = AF_INET6; - if (!llarp_getifaddr(ifname.c_str(), AF_INET6, sptr)) return std::nullopt; - llarp::SockAddr addr{*sptr}; - return addr.asIPv6(); - } + } - namespace net - { - namespace + std::optional + GetInterfaceAddr(std::string_view ifname, int af) const override { - SockAddr - All(int af) - { - if (af == AF_INET) - { - sockaddr_in addr{}; - addr.sin_family = AF_INET; - addr.sin_addr.s_addr = htonl(INADDR_ANY); - addr.sin_port = htons(0); - return SockAddr{addr}; - } - if (af == AF_INET6) - { - sockaddr_in6 addr6{}; - addr6.sin6_family = AF_INET6; - addr6.sin6_port = htons(0); - addr6.sin6_addr = IN6ADDR_ANY_INIT; - return SockAddr{addr6}; - } - throw std::invalid_argument{fmt::format("{} is not a valid address family", af)}; - } - } // namespace + std::optional addr; + iter_all([&addr, af, ifname = std::string{ifname}](auto i) { + if (addr) + return; + if (i and i->ifa_addr and i->ifa_addr->sa_family == af and i->ifa_name == ifname) + addr = llarp::SockAddr{*i->ifa_addr}; + }); + return addr; + } std::optional - AllInterfaces(SockAddr pub) + AllInterfaces(SockAddr fallback) const override { std::optional found; - IterAllNetworkInterfaces([pub, &found](auto* ifa) { + iter_all([fallback, &found](auto i) { if (found) return; - if (auto ifa_addr = ifa->ifa_addr) - { - if (ifa_addr->sa_family != pub.Family()) - return; - - SockAddr addr{*ifa->ifa_addr}; - - if (addr == pub) - found = addr; - } + if (i == nullptr or i->ifa_addr == nullptr) + return; + if (i->ifa_addr->sa_family != fallback.Family()) + return; + SockAddr addr{*i->ifa_addr}; + if (addr == fallback) + found = addr; }); // 0.0.0.0 is used in our compat shim as our public ip so we check for that special case const auto zero = IPRange::FromIPv4(0, 0, 0, 0, 8); - // when we cannot find an address but we are looking for 0.0.0.0 just default to the old style - if (not found and (pub.isIPv4() and zero.Contains(pub.asIPv4()))) - found = All(pub.Family()); + // when we cannot find an address but we are looking for 0.0.0.0 just default to the old + // style + if (not found and (fallback.isIPv4() and zero.Contains(fallback.asIPv4()))) + found = Wildcard(fallback.Family()); + return found; + } + + bool + HasInterfaceAddress(ipaddr_t ip) const override + { + bool found{false}; + iter_all([&found, ip](auto i) { + if (found) + return; + + if (not(i and i->ifa_addr)) + return; + const SockAddr addr{*i->ifa_addr}; + found = addr.getIP() == ip; + }); return found; } - } // namespace net + }; +#endif + + const Platform_Impl g_plat{}; + + const Platform* + Platform::Default_ptr() + { + return &g_plat; + } +} // namespace llarp::net +namespace llarp +{ #if !defined(TESTNET) static constexpr std::array bogonRanges_v6 = { // zero @@ -721,20 +575,5 @@ namespace llarp return false; } #endif - bool - HasInterfaceAddress(std::variant ip) - { - bool found{false}; - IterAllNetworkInterfaces([ip, &found](const auto* iface) { - if (found or iface == nullptr) - return; - if (auto addr = iface->ifa_addr; - addr and (addr->sa_family == AF_INET or addr->sa_family == AF_INET6)) - { - found = SockAddr{*iface->ifa_addr}.getIP() == ip; - } - }); - return found; - } } // namespace llarp diff --git a/llarp/net/net.hpp b/llarp/net/net.hpp index 7c40617bc..d8bd77aaa 100644 --- a/llarp/net/net.hpp +++ b/llarp/net/net.hpp @@ -67,56 +67,89 @@ namespace llarp bool IsBogonRange(const in6_addr& host, const in6_addr& mask); - /// get a sock addr we can use for all interfaces given our public address namespace net { - std::optional - AllInterfaces(SockAddr pubaddr); - } - - /// compat shim - // TODO: remove me - inline bool - AllInterfaces(int af, SockAddr& addr) - { - if (auto maybe = net::AllInterfaces(SockAddr{af == AF_INET ? "0.0.0.0" : "::"})) + /// network platform (all methods virtual so it can be mocked by unit tests) + class Platform { - addr = *maybe; - return true; - } - return false; - } - - /// get first network interface with public address - bool - GetBestNetIF(std::string& ifname, int af = AF_INET); - - /// look at adapter ranges and find a free one - std::optional - FindFreeRange(); - - /// look at adapter names and find a free one - std::optional - FindFreeTun(); - - /// get network interface address for network interface with ifname - std::optional - GetInterfaceAddr(const std::string& ifname, int af = AF_INET); - - /// get an interface's ip6 address - std::optional - GetInterfaceIPv6Address(std::string ifname); - -#ifdef _WIN32 - namespace net - { - std::optional - GetInterfaceIndex(huint32_t ip); - } -#endif - - /// return true if we have a network interface with this ip - bool - HasInterfaceAddress(std::variant ip); + public: + Platform() = default; + virtual ~Platform() = default; + Platform(const Platform&) = delete; + Platform(Platform&&) = delete; + + /// get a pointer to our signleton instance used by main lokinet + /// unit test mocks will not call this + static const Platform* + Default_ptr(); + + virtual std::optional + AllInterfaces(SockAddr pubaddr) const = 0; + + virtual SockAddr + Wildcard(int af = AF_INET) const = 0; + + inline SockAddr + WildcardWithPort(port_t port, int af = AF_INET) const + { + auto addr = Wildcard(af); + addr.setPort(port); + return addr; + } + + virtual std::string + LoopbackInterfaceName() const = 0; + + virtual bool + HasInterfaceAddress(ipaddr_t ip) const = 0; + + /// return true if ip is considered a loopback address + virtual bool + IsLoopbackAddress(ipaddr_t ip) const = 0; + + /// return true if ip is considered a wildcard address + virtual bool + IsWildcardAddress(ipaddr_t ip) const = 0; + + virtual std::optional + GetBestNetIF(int af = AF_INET) const = 0; + + inline std::optional + MaybeInferPublicAddr(port_t default_port, int af = AF_INET) const + { + std::optional maybe_addr; + if (auto maybe_ifname = GetBestNetIF(af)) + maybe_addr = GetInterfaceAddr(*maybe_ifname, af); + + if (maybe_addr) + maybe_addr->setPort(default_port); + return maybe_addr; + } + + virtual std::optional + FindFreeRange() const = 0; + + virtual std::optional + FindFreeTun() const = 0; + + virtual std::optional + GetInterfaceAddr(std::string_view ifname, int af = AF_INET) const = 0; + + inline std::optional + GetInterfaceIPv6Address(std::string_view ifname) const + { + if (auto maybe_addr = GetInterfaceAddr(ifname, AF_INET6)) + return maybe_addr->asIPv6(); + return std::nullopt; + } + + virtual bool + IsBogon(const SockAddr& addr) const = 0; + + virtual std::optional + GetInterfaceIndex(ipaddr_t ip) const = 0; + }; + + } // namespace net } // namespace llarp diff --git a/llarp/net/net_bits.hpp b/llarp/net/net_bits.hpp index 155c80b97..c9c2c9c97 100644 --- a/llarp/net/net_bits.hpp +++ b/llarp/net/net_bits.hpp @@ -48,4 +48,14 @@ namespace llarp return false; return true; } + namespace net + { + inline auto + ipaddr_netmask_bits(uint32_t bits, int af) + { + if (af == AF_INET6) + return netmask_ipv6_bits(bits); + return ExpandV4(netmask_ipv4_bits(bits)); + }; + } // namespace net } // namespace llarp diff --git a/llarp/net/net_int.cpp b/llarp/net/net_int.cpp index 566e11b70..1affa42e5 100644 --- a/llarp/net/net_int.cpp +++ b/llarp/net/net_int.cpp @@ -6,41 +6,44 @@ namespace llarp { - huint16_t - ToHost(nuint16_t n) + namespace net { - return xntohs(n); - } + huint16_t + ToHost(port_t x) + { + return huint16_t{oxenc::big_to_host(x.n)}; + } - huint32_t - ToHost(nuint32_t n) - { - return xntohl(n); - } + huint32_t + ToHost(ipv4addr_t x) + { + return huint32_t{oxenc::big_to_host(x.n)}; + } - huint128_t - ToHost(nuint128_t n) - { - return {ntoh128(n.n)}; - } + huint128_t + ToHost(ipv6addr_t x) + { + return {ntoh128(x.n)}; + } - nuint16_t - ToNet(huint16_t h) - { - return xhtons(h); - } + port_t + ToNet(huint16_t x) + { + return port_t{oxenc::host_to_big(x.h)}; + } - nuint32_t - ToNet(huint32_t h) - { - return xhtonl(h); - } + ipv4addr_t + ToNet(huint32_t x) + { + return ipv4addr_t{oxenc::host_to_big(x.h)}; + } - nuint128_t - ToNet(huint128_t h) - { - return {hton128(h.h)}; - } + ipv6addr_t + ToNet(huint128_t x) + { + return ipv6addr_t{hton128(x.h)}; + } + } // namespace net template <> void @@ -128,6 +131,17 @@ namespace llarp return ""; return tmp; } + + template <> + std::string + nuint128_t::ToString() const + { + char tmp[INET6_ADDRSTRLEN] = {0}; + if (!inet_ntop(AF_INET6, (void*)&n, tmp, sizeof(tmp))) + return ""; + return tmp; + } + template <> std::string huint16_t::ToString() const diff --git a/llarp/net/net_int.hpp b/llarp/net/net_int.hpp index 051d8c665..1fd263c7f 100644 --- a/llarp/net/net_int.hpp +++ b/llarp/net/net_int.hpp @@ -18,6 +18,7 @@ #include #include +#include #include "uint128.hpp" @@ -105,7 +106,7 @@ namespace llarp } using V6Container = std::vector; - void + [[deprecated]] void ToV6(V6Container& c); std::string @@ -174,7 +175,7 @@ namespace llarp } using V6Container = std::vector; - void + [[deprecated]] void ToV6(V6Container& c); std::string @@ -189,49 +190,86 @@ namespace llarp *this = ToNet(x); return true; } - }; - - template - inline constexpr bool IsToStringFormattable> = true; - template - inline constexpr bool IsToStringFormattable> = true; + inline static nuint_t + from_string(const std::string& str) + { + nuint_t x{}; + if (not x.FromString(str)) + throw std::invalid_argument{fmt::format("{} is not a valid value")}; + return x; + } - using nuint32_t = nuint_t; - using nuint16_t = nuint_t; - using nuint128_t = nuint_t; + template + inline static nuint_t + from_host(Args_t&&... args) + { + return ToNet(huint_t{std::forward(args)...}); + } + }; - static inline nuint32_t - xhtonl(huint32_t x) + namespace net { - return nuint32_t{htonl(x.h)}; - } + /// hides the nuint types used with net_port_t / net_ipv4addr_t / net_ipv6addr_t + namespace + { + using n_uint16_t = llarp::nuint_t; + using n_uint32_t = llarp::nuint_t; + using n_uint128_t = llarp::nuint_t; + } // namespace + + using port_t = n_uint16_t; + using ipv4addr_t = n_uint32_t; + using flowlabel_t = n_uint32_t; + using ipv6addr_t = n_uint128_t; + using ipaddr_t = std::variant; + + huint16_t ToHost(port_t); + huint32_t ToHost(ipv4addr_t); + huint128_t ToHost(ipv6addr_t); + + port_t ToNet(huint16_t); + ipv4addr_t ToNet(huint32_t); + ipv6addr_t ToNet(huint128_t); + + } // namespace net + + template <> + inline constexpr bool IsToStringFormattable = true; + template <> + inline constexpr bool IsToStringFormattable = true; + template <> + inline constexpr bool IsToStringFormattable = true; + template <> + inline constexpr bool IsToStringFormattable = true; + template <> + inline constexpr bool IsToStringFormattable = true; + template <> + inline constexpr bool IsToStringFormattable = true; + + using nuint16_t [[deprecated("use llarp::net::port_t instead")]] = llarp::net::port_t; + using nuint32_t [[deprecated("use llarp::net::ipv4addr_t instead")]] = llarp::net::ipv4addr_t; + using nuint128_t [[deprecated("use llarp::net::ipv6addr_t instead")]] = llarp::net::ipv6addr_t; - static inline huint32_t - xntohl(nuint32_t x) + template + [[deprecated("use llarp::net::ToNet instead")]] inline llarp::nuint_t + ToNet(llarp::huint_t x) { - return huint32_t{ntohl(x.n)}; + return llarp::net::ToNet(x); } - static inline nuint16_t - xhtons(huint16_t x) + template + [[deprecated("use llarp::net::ToHost instead")]] inline llarp::huint_t + ToHost(llarp::nuint_t x) { - return nuint16_t{htons(x.h)}; + return llarp::net::ToHost(x); } - static inline huint16_t - xntohs(nuint16_t x) + [[deprecated("use llarp::net::ToHost instead")]] inline net::ipv4addr_t + xhtonl(huint32_t x) { - return huint16_t{ntohs(x.n)}; + return ToNet(x); } - - huint16_t ToHost(nuint16_t); - huint32_t ToHost(nuint32_t); - huint128_t ToHost(nuint128_t); - - nuint16_t ToNet(huint16_t); - nuint32_t ToNet(huint32_t); - nuint128_t ToNet(huint128_t); } // namespace llarp namespace std diff --git a/llarp/net/sock_addr.hpp b/llarp/net/sock_addr.hpp index df9838ac0..447c4771e 100644 --- a/llarp/net/sock_addr.hpp +++ b/llarp/net/sock_addr.hpp @@ -101,6 +101,15 @@ namespace llarp void setIPv4(uint8_t a, uint8_t b, uint8_t c, uint8_t d); + inline void + setIP(std::variant ip) + { + if (auto* v4 = std::get_if(&ip)) + setIPv4(*v4); + if (auto* v6 = std::get_if(&ip)) + setIPv6(*v6); + } + void setIPv4(nuint32_t ip); @@ -143,6 +152,7 @@ namespace llarp getIPv6() const; nuint32_t getIPv4() const; + std::variant getIP() const; diff --git a/llarp/router/abstractrouter.hpp b/llarp/router/abstractrouter.hpp index a0e761b78..6a3cd2c3b 100644 --- a/llarp/router/abstractrouter.hpp +++ b/llarp/router/abstractrouter.hpp @@ -45,6 +45,11 @@ namespace llarp struct I_RCLookupHandler; struct RoutePoker; + namespace net + { + class Platform; + } + namespace exit { struct Context; @@ -93,6 +98,9 @@ namespace llarp virtual bool HandleRecvLinkMessageBuffer(ILinkSession* from, const llarp_buffer_t& msg) = 0; + virtual const net::Platform& + Net() const = 0; + virtual const LMQ_ptr& lmq() const = 0; diff --git a/llarp/router/router.cpp b/llarp/router/router.cpp index c74a25b5e..e8a175d64 100644 --- a/llarp/router/router.cpp +++ b/llarp/router/router.cpp @@ -37,6 +37,8 @@ #include #endif +#include + #include static constexpr std::chrono::milliseconds ROUTER_TICK_INTERVAL = 250ms; @@ -580,8 +582,6 @@ namespace llarp _rc.netID = llarp::NetID(); } - // IWP config - m_OutboundPort = conf.links.m_OutboundLink.port; // Router config _rc.SetNick(conf.router.m_nickname); _outboundSessionMaker.maxConnectedRouters = conf.router.m_maxConnectedRouters; @@ -592,8 +592,20 @@ namespace llarp transport_keyfile = m_keyManager->m_transportKeyPath; ident_keyfile = m_keyManager->m_idKeyPath; - if (auto maybe = conf.router.m_PublicIP) - _ourAddress = SockAddr{*maybe, conf.router.m_PublicPort}; + if (auto maybe_ip = conf.links.PublicAddress) + _ourAddress = var::visit([](auto&& ip) { return SockAddr{ip}; }, *maybe_ip); + else if (auto maybe_ip = conf.router.PublicIP) + _ourAddress = var::visit([](auto&& ip) { return SockAddr{ip}; }, *maybe_ip); + + if (_ourAddress) + { + if (auto maybe_port = conf.links.PublicPort) + _ourAddress->setPort(*maybe_port); + else if (auto maybe_port = conf.router.PublicPort) + _ourAddress->setPort(*maybe_port); + else + throw std::runtime_error{"public ip provided without public port"}; + } RouterContact::BlockBogons = conf.router.m_blockBogons; @@ -720,48 +732,10 @@ namespace llarp whitelistRouters, m_isServiceNode); - std::vector inboundLinks = conf.links.m_InboundLinks; - - if (inboundLinks.empty() and m_isServiceNode) - { - if (_ourAddress) - { - inboundLinks.push_back(LinksConfig::LinkInfo{ - _ourAddress->hostString(), _ourAddress->Family(), _ourAddress->getPort()}); - } - else - throw std::runtime_error{ - "service node enabled but could not find a public IP to bind to; you need to set the " - "public-ip= and public-port= options"}; - } - - // create inbound links, if we are a service node - for (const LinksConfig::LinkInfo& serverConfig : inboundLinks) - { - auto server = iwp::NewInboundLink( - m_keyManager, - loop(), - util::memFn(&AbstractRouter::rc, this), - util::memFn(&AbstractRouter::HandleRecvLinkMessageBuffer, this), - util::memFn(&AbstractRouter::Sign, this), - nullptr, - util::memFn(&Router::ConnectionEstablished, this), - util::memFn(&AbstractRouter::CheckRenegotiateValid, this), - util::memFn(&Router::ConnectionTimedOut, this), - util::memFn(&AbstractRouter::SessionClosed, this), - util::memFn(&AbstractRouter::TriggerPump, this), - util::memFn(&AbstractRouter::QueueWork, this)); - - const std::string& key = serverConfig.m_interface; - int af = serverConfig.addressFamily; - uint16_t port = serverConfig.port; - if (!server->Configure(this, key, af, port)) - { - throw std::runtime_error{ - fmt::format("failed to bind inbound link on {} port {}", key, port)}; - } - _linkManager.AddLink(std::move(server), true); - } + // inbound links + InitInboundLinks(); + // outbound links + InitOutboundLinks(); // profiling _profilesFile = conf.router.m_dataDir / "profiles.dat"; @@ -1234,13 +1208,20 @@ namespace llarp AddressInfo ai; if (link->GetOurAddressInfo(ai)) { - // override ip and port + // override ip and port as needed if (_ourAddress) { + if (not Net().IsBogon(ai.ip)) + throw std::runtime_error{"cannot override public ip, it is already set"}; ai.fromSockAddr(*_ourAddress); } if (RouterContact::BlockBogons && IsBogon(ai.ip)) - return; + throw std::runtime_error{var::visit( + [](auto&& ip) { + return "cannot use " + ip.ToString() + + " as a public ip as it is in a non routable ip range"; + }, + ai.IP())}; LogInfo("adding address: ", ai); _rc.addrs.push_back(ai); } @@ -1268,12 +1249,6 @@ namespace llarp return false; } - if (not InitOutboundLinks()) - { - LogError("failed to init outbound links"); - return false; - } - if (IsServiceNode()) { if (!SaveRC()) @@ -1603,47 +1578,116 @@ namespace llarp return found; } - bool - Router::InitOutboundLinks() + void + Router::InitInboundLinks() { - auto link = iwp::NewOutboundLink( - m_keyManager, - loop(), - util::memFn(&AbstractRouter::rc, this), - util::memFn(&AbstractRouter::HandleRecvLinkMessageBuffer, this), - util::memFn(&AbstractRouter::Sign, this), - [&](llarp::RouterContact rc) { - if (IsServiceNode()) - return; - llarp::LogTrace( - "Before connect, outbound link adding route to (", - rc.addrs[0].toIpAddress().toIP(), - ") via gateway."); - m_RoutePoker.AddRoute(rc.addrs[0].toIpAddress().toIP()); - }, - util::memFn(&Router::ConnectionEstablished, this), - util::memFn(&AbstractRouter::CheckRenegotiateValid, this), - util::memFn(&Router::ConnectionTimedOut, this), - util::memFn(&AbstractRouter::SessionClosed, this), - util::memFn(&AbstractRouter::TriggerPump, this), - util::memFn(&AbstractRouter::QueueWork, this)); + auto addrs = m_Config->links.InboundListenAddrs; + if (m_isServiceNode and addrs.empty()) + { + LogInfo("Inferring Public Address"); + + auto maybe_port = m_Config->links.PublicPort; + if (m_Config->router.PublicPort and not maybe_port) + maybe_port = m_Config->router.PublicPort; + if (not maybe_port) + maybe_port = net::port_t::from_host(constants::DefaultInboundIWPPort); - if (!link) - throw std::runtime_error("NewOutboundLink() failed to provide a link"); + if (auto maybe_addr = Net().MaybeInferPublicAddr(*maybe_port)) + { + LogInfo("Public Address looks to be ", *maybe_addr); + addrs.emplace_back(std::move(*maybe_addr)); + } + } + if (m_isServiceNode and addrs.empty()) + throw std::runtime_error{"we are a service node and we have no inbound links configured"}; - for (const auto af : {AF_INET, AF_INET6}) + // create inbound links, if we are a service node + for (auto bind_addr : addrs) { - if (not link->Configure(this, "*", af, m_OutboundPort)) - continue; + if (bind_addr.getPort() == 0) + throw std::invalid_argument{"inbound link cannot use port 0"}; + + if (Net().IsWildcardAddress(bind_addr.getIP())) + { + if (auto maybe_ip = OurPublicIP()) + bind_addr.setIP(*maybe_ip); + else + throw std::runtime_error{"no public ip provided for inbound socket"}; + } + + auto server = iwp::NewInboundLink( + m_keyManager, + loop(), + util::memFn(&AbstractRouter::rc, this), + util::memFn(&AbstractRouter::HandleRecvLinkMessageBuffer, this), + util::memFn(&AbstractRouter::Sign, this), + nullptr, + util::memFn(&Router::ConnectionEstablished, this), + util::memFn(&AbstractRouter::CheckRenegotiateValid, this), + util::memFn(&Router::ConnectionTimedOut, this), + util::memFn(&AbstractRouter::SessionClosed, this), + util::memFn(&AbstractRouter::TriggerPump, this), + util::memFn(&AbstractRouter::QueueWork, this)); + + server->Bind(this, bind_addr); + _linkManager.AddLink(std::move(server), true); + } + } + + void + Router::InitOutboundLinks() + { + auto addrs = m_Config->links.OutboundLinks; + if (addrs.empty()) + addrs.emplace_back(Net().Wildcard()); + + for (auto bind_addr : addrs) + { + auto link = iwp::NewOutboundLink( + m_keyManager, + loop(), + util::memFn(&AbstractRouter::rc, this), + util::memFn(&AbstractRouter::HandleRecvLinkMessageBuffer, this), + util::memFn(&AbstractRouter::Sign, this), + [this](llarp::RouterContact rc) { + if (IsServiceNode()) + return; + llarp::LogTrace( + "Before connect, outbound link adding route to (", + rc.addrs[0].toIpAddress().toIP(), + ") via gateway."); + m_RoutePoker.AddRoute(rc.addrs[0].toIpAddress().toIP()); + }, + util::memFn(&Router::ConnectionEstablished, this), + util::memFn(&AbstractRouter::CheckRenegotiateValid, this), + util::memFn(&Router::ConnectionTimedOut, this), + util::memFn(&AbstractRouter::SessionClosed, this), + util::memFn(&AbstractRouter::TriggerPump, this), + util::memFn(&AbstractRouter::QueueWork, this)); + + const auto& net = Net(); + + // try to use a public address if we have one set on our inbound links + _linkManager.ForEachInboundLink([&bind_addr, &net](const auto& link) { + if (not net.IsBogon(bind_addr)) + return; + if (auto addr = link->LocalSocketAddr(); not net.IsBogon(addr)) + bind_addr.setIP(addr.getIP()); + }); + + link->Bind(this, bind_addr); + + if constexpr (llarp::platform::is_android) + m_OutboundUDPSocket = link->GetUDPFD().value_or(-1); -#if defined(ANDROID) - m_OutboundUDPSocket = link->GetUDPFD().value_or(-1); -#endif _linkManager.AddLink(std::move(link), false); - return true; } - throw std::runtime_error{ - fmt::format("Failed to init AF_INET and AF_INET6 on port {}", m_OutboundPort)}; + } + + const llarp::net::Platform& + Router::Net() const + { + return *llarp::net::Platform::Default_ptr(); } void diff --git a/llarp/router/router.hpp b/llarp/router/router.hpp index f8578c024..dc4b901a1 100644 --- a/llarp/router/router.hpp +++ b/llarp/router/router.hpp @@ -84,6 +84,9 @@ namespace llarp return m_PathBuildLimiter; } + const llarp::net::Platform& + Net() const override; + const LMQ_ptr& lmq() const override { @@ -227,7 +230,6 @@ namespace llarp bool Sign(Signature& sig, const llarp_buffer_t& buf) const override; - uint16_t m_OutboundPort = 0; /// how often do we resign our RC? milliseconds. // TODO: make configurable llarp_time_t rcRegenInterval = 1h; @@ -363,7 +365,10 @@ namespace llarp bool HandleRecvLinkMessageBuffer(ILinkSession* from, const llarp_buffer_t& msg) override; - bool + void + InitInboundLinks(); + + void InitOutboundLinks(); bool @@ -541,16 +546,8 @@ namespace llarp return m_Config; } -#if defined(ANDROID) int m_OutboundUDPSocket = -1; - int - GetOutboundUDPSocket() const override - { - return m_OutboundUDPSocket; - } -#endif - private: std::atomic _stopping; std::atomic _running; diff --git a/llarp/vpn/linux.hpp b/llarp/vpn/linux.hpp index cad48fe91..44b5ae220 100644 --- a/llarp/vpn/linux.hpp +++ b/llarp/vpn/linux.hpp @@ -21,6 +21,9 @@ #include +#include +#include + namespace llarp::vpn { struct in6_ifreq @@ -290,7 +293,7 @@ namespace llarp::vpn DefaultRouteViaInterface(std::string ifname, int cmd, int flags) { int if_idx = if_nametoindex(ifname.c_str()); - const auto maybe = GetInterfaceAddr(ifname); + const auto maybe = Net().GetInterfaceAddr(ifname); if (not maybe) throw std::runtime_error{"we dont have our own network interface?"}; @@ -301,7 +304,7 @@ namespace llarp::vpn Route(cmd, flags, lower, gateway, GatewayMode::eLowerDefault, if_idx); Route(cmd, flags, upper, gateway, GatewayMode::eUpperDefault, if_idx); - if (const auto maybe6 = GetInterfaceIPv6Address(ifname)) + if (const auto maybe6 = Net().GetInterfaceIPv6Address(ifname)) { const _inet_addr gateway6{*maybe6, 128}; for (const std::string str : {"::", "4000::", "8000::", "c000::"}) @@ -320,7 +323,7 @@ namespace llarp::vpn int if_idx = if_nametoindex(ifname.c_str()); if (range.IsV4()) { - const auto maybe = GetInterfaceAddr(ifname); + const auto maybe = Net().GetInterfaceAddr(ifname); if (not maybe) throw std::runtime_error{"we dont have our own network interface?"}; @@ -333,7 +336,7 @@ namespace llarp::vpn } else { - const auto maybe = GetInterfaceIPv6Address(ifname); + const auto maybe = Net().GetInterfaceIPv6Address(ifname); if (not maybe) throw std::runtime_error{"we dont have our own network interface?"}; const _inet_addr gateway{*maybe, 128}; diff --git a/llarp/vpn/platform.cpp b/llarp/vpn/platform.cpp index 341e28fcb..ef5a30295 100644 --- a/llarp/vpn/platform.cpp +++ b/llarp/vpn/platform.cpp @@ -16,6 +16,12 @@ namespace llarp::vpn { + const llarp::net::Platform* + IRouteManager::Net_ptr() const + { + return llarp::net::Platform::Default_ptr(); + } + std::shared_ptr MakeNativePlatform(llarp::Context* ctx) { diff --git a/llarp/win32/exception.hpp b/llarp/win32/exception.hpp new file mode 100644 index 000000000..893fcc741 --- /dev/null +++ b/llarp/win32/exception.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include +#include + +#include + +namespace llarp::win32 +{ + namespace + { + inline std::string + error_to_string(DWORD err) + { + std::array buffer{}; + + FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, &err, 0, 0, buffer.data(), buffer.size(), nullptr); + return std::string{buffer.data()}; + } + } // namespace + + class error : public std::runtime_error + { + public: + error(DWORD err, std::string msg) + : std::runtime_error{fmt::format("{}: {}", msg, error_to_string(err))} + {} + }; +} // namespace llarp::win32 diff --git a/pybind/llarp/config.cpp b/pybind/llarp/config.cpp index 9083f213d..9b705bc5a 100644 --- a/pybind/llarp/config.cpp +++ b/pybind/llarp/config.cpp @@ -70,22 +70,12 @@ namespace llarp .def(py::init<>()) .def( "setOutboundLink", - [](LinksConfig& self, std::string _interface, int family, uint16_t port) { - LinksConfig::LinkInfo info; - info.m_interface = std::move(_interface); - info.addressFamily = family; - info.port = port; - self.m_OutboundLink = std::move(info); + [](LinksConfig& self, std::string addr) { + self.OutboundLinks.emplace_back(std::move(addr)); }) - .def( - "addInboundLink", - [](LinksConfig& self, std::string _interface, int family, uint16_t port) { - LinksConfig::LinkInfo info; - info.m_interface = std::move(_interface); - info.addressFamily = family; - info.port = port; - self.m_InboundLinks.push_back(info); - }); + .def("addInboundLink", [](LinksConfig& self, std::string addr) { + self.InboundListenAddrs.emplace_back(std::move(addr)); + }); py::class_(mod, "ApiConfig") .def(py::init<>()) diff --git a/readme.md b/readme.md index 0a5c4ced0..fde25a89d 100644 --- a/readme.md +++ b/readme.md @@ -16,11 +16,11 @@ Tier 1: * [Linux](#linux-install) * [Android](#apk-install) +* [Windows](#windows-install) +* [MacOS](#mac-install) Tier 2: -* [Windows](#windows-install) -* [MacOS](#mac-install) * [FreeBSD](#freebsd-install) Currently Unsupported Platforms: (maintainers welcome) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index fe0cc9480..af0f45bf2 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -18,7 +18,8 @@ add_executable(testAll config/test_llarp_config_definition.cpp config/test_llarp_config_ini.cpp config/test_llarp_config_output.cpp - crypto/test_llarp_crypto_types.cpp + config/test_llarp_config_values.cpp + crypto/test_llarp_crypto_types.cpp crypto/test_llarp_crypto.cpp crypto/test_llarp_key_manager.cpp dns/test_llarp_dns_dns.cpp @@ -29,7 +30,6 @@ add_executable(testAll path/test_path.cpp peerstats/test_peer_db.cpp peerstats/test_peer_types.cpp - regress/2020-06-08-key-backup-bug.cpp router/test_llarp_router_version.cpp routing/test_llarp_routing_transfer_traffic.cpp routing/test_llarp_routing_obtainexitmessage.cpp diff --git a/test/config/test_llarp_config_values.cpp b/test/config/test_llarp_config_values.cpp new file mode 100644 index 000000000..242f5d14d --- /dev/null +++ b/test/config/test_llarp_config_values.cpp @@ -0,0 +1,286 @@ +#include + +#include +#include "mocks/mock_context.hpp" + +using namespace std::literals; + +struct UnitTestConfigGenParameters : public llarp::ConfigGenParameters +{ + const mocks::Network* const _plat; + UnitTestConfigGenParameters(const mocks::Network* plat) + : llarp::ConfigGenParameters{}, _plat{plat} + {} + + const llarp::net::Platform* + Net_ptr() const override + { + return _plat; + } +}; + +struct UnitTestConfig : public llarp::Config +{ + const mocks::Network* const _plat; + + explicit UnitTestConfig(const mocks::Network* plat) : llarp::Config{std::nullopt}, _plat{plat} + {} + + std::unique_ptr + MakeGenParams() const override + { + return std::make_unique(_plat); + } +}; + +std::shared_ptr +make_config_for_test(const mocks::Network* env, std::string_view ini_str = "") +{ + auto conf = std::make_shared(env); + conf->LoadString(ini_str, true); + conf->lokid.whitelistRouters = false; + conf->bootstrap.seednode = true; + conf->bootstrap.files.clear(); + return conf; +} + +std::shared_ptr +make_config(mocks::Network env, std::string_view ini_str = "") +{ + auto conf = std::make_shared(&env); + conf->LoadString(ini_str, true); + conf->lokid.whitelistRouters = false; + conf->bootstrap.seednode = true; + conf->bootstrap.files.clear(); + return conf; +} + +void +run_config_test(mocks::Network env, std::string_view ini_str) +{ + auto conf = make_config_for_test(&env, ini_str); + const auto opts = env.Opts(); + auto context = std::make_shared(env); + + context->Configure(conf); + context->Setup(opts); + int ib_links{}; + int ob_links{}; + + context->router->linkManager().ForEachInboundLink([&ib_links](auto) { ib_links++; }); + context->router->linkManager().ForEachOutboundLink([&ob_links](auto) { ob_links++; }); + REQUIRE(ib_links == 1); + REQUIRE(ob_links == 1); + if (context->Run(opts)) + throw std::runtime_error{"non zero return"}; +} + +TEST_CASE("service node bind section on valid network", "[config]") +{ + std::unordered_multimap env{ + {"mock0", llarp::IPRange::FromIPv4(1, 1, 1, 1, 32)}, + {"lo", llarp::IPRange::FromIPv4(127, 0, 0, 1, 8)}, + }; + + SECTION("mock network is sane") + { + mocks::Network mock_net{env}; + REQUIRE(mock_net.GetInterfaceAddr("mock0"sv, AF_INET6) == std::nullopt); + auto maybe_addr = mock_net.GetInterfaceAddr("mock0"sv, AF_INET); + REQUIRE(maybe_addr != std::nullopt); + REQUIRE(maybe_addr->hostString() == "1.1.1.1"); + REQUIRE(not mock_net.IsBogon(*maybe_addr)); + } + + SECTION("empty config") + { + std::string_view ini_str = ""; + REQUIRE_NOTHROW(run_config_test(env, ini_str)); + } + + SECTION("explicit bind via ifname") + { + std::string_view ini_str = R"( +[bind] +mock0=443 +)"; + run_config_test(env, ini_str); + } + SECTION("explicit bind via ip address") + { + std::string_view ini_str = R"( +[bind] +inbound=1.1.1.1:443 +)"; + REQUIRE_NOTHROW(run_config_test(env, ini_str)); + } + SECTION("explicit bind via ip address with old syntax") + { + std::string_view ini_str = R"( +[bind] +1.1.1.1=443 +)"; + + REQUIRE_NOTHROW(run_config_test(env, ini_str)); + } + SECTION("ip spoof fails") + { + std::string_view ini_str = R"( +[router] +public-ip=8.8.8.8 +public-port=443 +[bind] +inbound=1.1.1.1:443 +)"; + REQUIRE_THROWS(run_config_test(env, ini_str)); + } + SECTION("explicit bind via ifname but fails from non existing ifname") + { + std::string_view ini_str = R"( +[bind] +ligma0=443 +)"; + REQUIRE_THROWS(make_config(env, ini_str)); + } + + SECTION("explicit bind via ifname but fails from using loopback") + { + std::string_view ini_str = R"( +[bind] +lo=443 +)"; + REQUIRE_THROWS(make_config(env, ini_str)); + } + + SECTION("explicit bind via explicit loopback") + { + std::string_view ini_str = R"( +[bind] +inbound=127.0.0.1:443 +)"; + REQUIRE_THROWS(make_config(env, ini_str)); + } +} + +TEST_CASE("service node bind section on nat network", "[config]") +{ + std::unordered_multimap env{ + {"mock0", llarp::IPRange::FromIPv4(10, 1, 1, 1, 32)}, + {"lo", llarp::IPRange::FromIPv4(127, 0, 0, 1, 8)}, + }; + SECTION("no public ip set should fail") + { + std::string_view ini_str = ""; + REQUIRE_THROWS(run_config_test(env, ini_str)); + } + + SECTION("public ip provided via inbound directive") + { + std::string_view ini_str = R"( +[router] +public-ip=1.1.1.1 +public-port=443 + +[bind] +inbound=10.1.1.1:443 +)"; + REQUIRE_NOTHROW(run_config_test(env, ini_str)); + } + + SECTION("public ip provided with bind via ifname") + { + std::string_view ini_str = R"( +[router] +public-ip=1.1.1.1 +public-port=443 + +[bind] +mock0=443 +)"; + REQUIRE_NOTHROW(run_config_test(env, ini_str)); + } + + SECTION("public ip provided bind via wildcard ip") + { + std::string_view ini_str = R"( +[router] +public-ip=1.1.1.1 +public-port=443 + +[bind] +inbound=0.0.0.0:443 +)"; + REQUIRE_THROWS(run_config_test(env, ini_str)); + } +} + +TEST_CASE("service node bind section with multiple public ip", "[config]") +{ + std::unordered_multimap env{ + {"mock0", llarp::IPRange::FromIPv4(1, 1, 1, 1, 32)}, + {"mock0", llarp::IPRange::FromIPv4(2, 1, 1, 1, 32)}, + {"lo", llarp::IPRange::FromIPv4(127, 0, 0, 1, 8)}, + }; + SECTION("empty config") + { + std::string_view ini_str = ""; + REQUIRE_NOTHROW(run_config_test(env, ini_str)); + } + SECTION("with old style wildcard for inbound and no public ip") + { + std::string_view ini_str = R"( +[bind] +0.0.0.0=443 +)"; + REQUIRE_THROWS(run_config_test(env, ini_str)); + } + SECTION("with old style wildcard for outbound") + { + std::string_view ini_str = R"( +[bind] +*=1443 +)"; + REQUIRE_NOTHROW(run_config_test(env, ini_str)); + } + SECTION("with wildcard via inbound directive no public ip given, fails") + { + std::string_view ini_str = R"( +[bind] +inbound=0.0.0.0:443 +)"; + + REQUIRE_THROWS(run_config_test(env, ini_str)); + } + SECTION("with wildcard via inbound directive primary public ip given") + { + std::string_view ini_str = R"( +[router] +public-ip=1.1.1.1 +public-port=443 +[bind] +inbound=0.0.0.0:443 +)"; + + REQUIRE_THROWS(run_config_test(env, ini_str)); + } + SECTION("with wildcard via inbound directive secondary public ip given") + { + std::string_view ini_str = R"( +[router] +public-ip=2.1.1.1 +public-port=443 +[bind] +inbound=0.0.0.0:443 +)"; + + REQUIRE_THROWS(run_config_test(env, ini_str)); + } + SECTION("with bind via interface name") + { + std::string_view ini_str = R"( +[bind] +mock0=443 +)"; + REQUIRE_NOTHROW(run_config_test(env, ini_str)); + } +} diff --git a/test/mocks/mock_context.hpp b/test/mocks/mock_context.hpp new file mode 100644 index 000000000..b24e929a3 --- /dev/null +++ b/test/mocks/mock_context.hpp @@ -0,0 +1,40 @@ +#pragma once +#include + +#include "mock_network.hpp" +#include "mock_router.hpp" +#include "mock_vpn.hpp" + +namespace mocks +{ + class MockContext : public llarp::Context + { + const Network& _net; + + public: + MockContext(const Network& net) : llarp::Context{}, _net{net} + { + loop = std::shared_ptr{const_cast(&_net), [](Network*) {}}; + } + + std::shared_ptr + makeRouter(const std::shared_ptr&) override + { + return std::static_pointer_cast( + std::make_shared(_net, makeVPNPlatform())); + } + + std::shared_ptr + makeVPNPlatform() override + { + return std::static_pointer_cast(std::make_shared(_net)); + } + + std::shared_ptr + makeNodeDB() override + { + return std::make_shared(); + } + }; + +} // namespace mocks diff --git a/test/mocks/mock_network.hpp b/test/mocks/mock_network.hpp new file mode 100644 index 000000000..f5a47548e --- /dev/null +++ b/test/mocks/mock_network.hpp @@ -0,0 +1,192 @@ +#pragma once + +#include +#include +#include +#include + +namespace mocks +{ + class Network; + + class MockUDPHandle : public llarp::UDPHandle + { + Network* const _net; + + public: + MockUDPHandle(Network* net, llarp::UDPHandle::ReceiveFunc recv) + : llarp::UDPHandle{recv}, _net{net} + {} + + bool + listen(const llarp::SockAddr& addr) override; + + bool + send(const llarp::SockAddr&, const llarp_buffer_t&) override + { + return true; + }; + + void + close() override{}; + }; + + class Network : public llarp::net::Platform, public llarp::uv::Loop + { + std::unordered_multimap _network_interfaces; + bool _snode; + + const Platform* const m_Default{Platform::Default_ptr()}; + + public: + Network( + std::unordered_multimap network_interfaces, bool snode = true) + : llarp::net::Platform{} + , llarp::uv::Loop{1024} + , _network_interfaces{std::move(network_interfaces)} + , _snode{snode} + {} + + void + run() override + { + m_EventLoopThreadID = std::this_thread::get_id(); + m_Impl->run(); + m_Impl->close(); + // reset the event loop for reuse + m_Impl = uvw::Loop::create(); + }; + + llarp::RuntimeOptions + Opts() const + { + return llarp::RuntimeOptions{false, false, _snode}; + } + + std::shared_ptr + make_udp(UDPReceiveFunc recv) override + { + return std::make_shared(this, recv); + } + + std::optional + GetBestNetIF(int af) const override + { + for (const auto& [k, range] : _network_interfaces) + if (range.Family() == af and not range.BogonRange()) + return k; + return std::nullopt; + } + + std::optional + FindFreeTun() const override + { + return "mocktun0"; + } + + std::optional + GetInterfaceAddr(std::string_view ifname, int af) const override + { + for (const auto& [name, range] : _network_interfaces) + if (range.Family() == af and name == ifname) + return llarp::SockAddr{range.addr}; + return std::nullopt; + } + + bool + HasInterfaceAddress(llarp::net::ipaddr_t ip) const override + { + for (const auto& item : _network_interfaces) + if (var::visit([range = item.second](auto&& ip) { return range.Contains(ToHost(ip)); }, ip)) + return true; + // check for wildcard + return IsWildcardAddress(ip); + } + + std::optional + AllInterfaces(llarp::SockAddr fallback) const override + { + return m_Default->AllInterfaces(fallback); + } + + llarp::SockAddr + Wildcard(int af) const override + { + return m_Default->Wildcard(af); + } + + bool + IsBogon(const llarp::SockAddr& addr) const override + { + return m_Default->IsBogon(addr); + } + bool + IsLoopbackAddress(llarp::net::ipaddr_t ip) const override + { + return m_Default->IsLoopbackAddress(ip); + } + + bool + IsWildcardAddress(llarp::net::ipaddr_t ip) const override + { + return m_Default->IsWildcardAddress(ip); + } + + std::optional + GetInterfaceIndex(llarp::net::ipaddr_t ip) const override + { + return m_Default->GetInterfaceIndex(ip); + } + + std::optional + FindFreeRange() const override + { + auto ownsRange = [this](const auto& range) { + for (const auto& [name, ownRange] : _network_interfaces) + { + if (ownRange * range) + return true; + } + return false; + }; + using namespace llarp; + // generate possible ranges to in order of attempts + std::list possibleRanges; + for (byte_t oct = 16; oct < 32; ++oct) + { + possibleRanges.emplace_back(IPRange::FromIPv4(172, oct, 0, 1, 16)); + } + for (byte_t oct = 0; oct < 255; ++oct) + { + possibleRanges.emplace_back(IPRange::FromIPv4(10, oct, 0, 1, 16)); + } + for (byte_t oct = 0; oct < 255; ++oct) + { + possibleRanges.emplace_back(IPRange::FromIPv4(192, 168, oct, 1, 24)); + } + // for each possible range pick the first one we don't own + for (const auto& range : possibleRanges) + { + if (not ownsRange(range)) + return range; + } + return std::nullopt; + } + + std::string + LoopbackInterfaceName() const override + { + for (const auto& [name, range] : _network_interfaces) + if (IsLoopbackAddress(ToNet(range.addr))) + return name; + throw std::runtime_error{"no loopback interface?"}; + } + }; + + bool + MockUDPHandle::listen(const llarp::SockAddr& addr) + { + return _net->HasInterfaceAddress(addr.getIP()); + } + +} // namespace mocks diff --git a/test/mocks/mock_router.hpp b/test/mocks/mock_router.hpp new file mode 100644 index 000000000..1d8c73401 --- /dev/null +++ b/test/mocks/mock_router.hpp @@ -0,0 +1,25 @@ +#pragma once +#include + +#include "mock_network.hpp" + +namespace mocks +{ + class MockRouter : public llarp::Router + { + const Network& _net; + + public: + explicit MockRouter(const Network& net, std::shared_ptr vpnPlatform) + : llarp:: + Router{std::shared_ptr{const_cast(&net), [](Network*) {}}, vpnPlatform} + , _net{net} + {} + + const llarp::net::Platform& + Net() const override + { + return _net; + }; + }; +} // namespace mocks diff --git a/test/mocks/mock_vpn.hpp b/test/mocks/mock_vpn.hpp new file mode 100644 index 000000000..538102226 --- /dev/null +++ b/test/mocks/mock_vpn.hpp @@ -0,0 +1,93 @@ +#pragma once +#include +#include "mock_network.hpp" + +namespace mocks +{ + class MockInterface : public llarp::vpn::NetworkInterface + { + int _pipes[2]; + + public: + MockInterface(llarp::vpn::InterfaceInfo) : llarp::vpn::NetworkInterface{} + { + if (pipe(_pipes)) + throw std::runtime_error{strerror(errno)}; + } + + virtual ~MockInterface() + { + close(_pipes[1]); + } + + int + PollFD() const override + { + return _pipes[0]; + }; + + std::string + IfName() const override + { + return "ligma"; + }; + + llarp::net::IPPacket + ReadNextPacket() override + { + return llarp::net::IPPacket{}; + }; + + bool WritePacket(llarp::net::IPPacket) override + { + return true; + } + }; + + class MockVPN : public llarp::vpn::Platform, public llarp::vpn::IRouteManager + { + const Network& _net; + + public: + MockVPN(const Network& net) : llarp::vpn::Platform{}, llarp::vpn::IRouteManager{}, _net{net} + {} + + virtual std::shared_ptr + ObtainInterface(llarp::vpn::InterfaceInfo info, llarp::AbstractRouter*) override + { + return std::make_shared(info); + }; + + const llarp::net::Platform* + Net_ptr() const override + { + return &_net; + }; + + void AddRoute(IPVariant_t, IPVariant_t) override{}; + + void DelRoute(IPVariant_t, IPVariant_t) override{}; + + void AddDefaultRouteViaInterface(std::string) override{}; + + void DelDefaultRouteViaInterface(std::string) override{}; + + void + AddRouteViaInterface(llarp::vpn::NetworkInterface&, llarp::IPRange) override{}; + + void + DelRouteViaInterface(llarp::vpn::NetworkInterface&, llarp::IPRange) override{}; + + std::vector GetGatewaysNotOnInterface(std::string) override + { + return std::vector{}; + }; + + /// get owned ip route manager for managing routing table + virtual llarp::vpn::IRouteManager& + RouteManager() override + { + return *this; + }; + }; +} // namespace mocks diff --git a/test/regress/2020-06-08-key-backup-bug.cpp b/test/regress/2020-06-08-key-backup-bug.cpp deleted file mode 100644 index 669c1aca8..000000000 --- a/test/regress/2020-06-08-key-backup-bug.cpp +++ /dev/null @@ -1,77 +0,0 @@ -#include -#include -#include -#include -#include - -llarp::RuntimeOptions opts = {false, false, false}; - -/// make a context with 1 endpoint that specifies a keyfile -static std::shared_ptr -make_context(std::optional keyfile) -{ - auto conf = std::make_shared(fs::current_path()); - conf->Load(std::nullopt, opts.isSNode); - conf->network.m_endpointType = "null"; - conf->network.m_keyfile = keyfile; - conf->bootstrap.seednode = true; - conf->api.m_enableRPCServer = false; - - auto context = std::make_shared(); - REQUIRE_NOTHROW(context->Configure(std::move(conf))); - - return context; -} - -/// test that we dont back up all keys when self.signed is missing or invalid as client -TEST_CASE("key backup bug regression test", "[regress]") -{ - // kill logging, this code is noisy - // test 2 explicitly provided keyfiles, empty keyfile and no keyfile - for (std::optional path : {std::optional{"regress-1.private"}, - std::optional{"regress-2.private"}, - std::optional{""}, - {std::nullopt}}) - { - llarp::service::Address endpointAddress{}; - // try 10 start up and shut downs and see if our key changes or not - for (size_t index = 0; index < 10; index++) - { - auto ctx = make_context(path); - REQUIRE_NOTHROW(ctx->Setup(opts)); - ctx->CallSafe([ctx, index, &endpointAddress, &path]() { - auto ep = ctx->router->hiddenServiceContext().GetDefault(); - REQUIRE(ep != nullptr); - if (index == 0) - { - REQUIRE(endpointAddress.IsZero()); - // first iteration, we are getting our identity that we start with - endpointAddress = ep->GetIdentity().pub.Addr(); - REQUIRE(not endpointAddress.IsZero()); - } - else - { - REQUIRE(not endpointAddress.IsZero()); - if (path.has_value() and not path->empty()) - { - // we have a keyfile provided - // after the first iteration we expect the keys to stay the same - REQUIRE(endpointAddress == ep->GetIdentity().pub.Addr()); - } - else - { - // we want the keys to shift because no keyfile was provided - REQUIRE(endpointAddress != ep->GetIdentity().pub.Addr()); - } - } - // close the router right away - ctx->router->Die(); - }); - REQUIRE(ctx->Run({}) == 0); - ctx.reset(); - } - // remove keys if provied - if (path.has_value() and not path->empty()) - fs::remove(*path); - } -}