* add mockable network functions

* add unit tests with ability to pretend to be different network setups
pull/1930/head
Jeff 2 years ago
parent 12653d4ac2
commit 68148e098f
No known key found for this signature in database
GPG Key ID: 025C02EE3A092F2D

@ -170,7 +170,7 @@ if(NOT TARGET sodium)
endif() endif()
if(NOT APPLE) 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") if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wno-unknown-warning-option) add_compile_options(-Wno-unknown-warning-option)
endif() endif()

@ -87,14 +87,14 @@ extern "C"
{ {
auto ptr = GetImpl<llarp::Context>(env, self); auto ptr = GetImpl<llarp::Context>(env, self);
return ptr->GetUDPSocket(); return ptr->router.m_OutboundUDPSocket;
} }
JNIEXPORT jstring JNICALL JNIEXPORT jstring JNICALL
Java_network_loki_lokinet_LokinetDaemon_DetectFreeRange(JNIEnv* env, jclass) Java_network_loki_lokinet_LokinetDaemon_DetectFreeRange(JNIEnv* env, jclass)
{ {
std::string rangestr{}; std::string rangestr{};
if (auto maybe = llarp::FindFreeRange()) if (auto maybe = llarp::net::Platform::Default().FindFreeRange())
{ {
rangestr = maybe->ToString(); rangestr = maybe->ToString();
} }

@ -54,7 +54,7 @@ llarp_apple_init(llarp_apple_config* appleconf)
auto& range = config->network.m_ifaddr; auto& range = config->network.m_ifaddr;
if (!range.addr.h) if (!range.addr.h)
{ {
if (auto maybe = llarp::FindFreeRange()) if (auto maybe = llarp::net::Platform::Default().FindFreeRange())
range = *maybe; range = *maybe;
else else
throw std::runtime_error{"Could not find any free IP range"}; throw std::runtime_error{"Could not find any free IP range"};

@ -35,6 +35,17 @@ namespace llarp
constexpr int DefaultPublicPort = 1090; constexpr int DefaultPublicPort = 1090;
using namespace config; using namespace config;
namespace
{
struct ConfigGenParameters_impl : public ConfigGenParameters
{
const llarp::net::Platform*
Net_ptr() const
{
return llarp::net::Platform::Default_ptr();
}
};
} // namespace
void void
RouterConfig::defineConfigOptions(ConfigDefinition& conf, const ConfigGenParameters& params) RouterConfig::defineConfigOptions(ConfigDefinition& conf, const ConfigGenParameters& params)
@ -140,7 +151,7 @@ namespace llarp
throw std::invalid_argument{ throw std::invalid_argument{
fmt::format("{} is not a publicly routable ip address", addr)}; fmt::format("{} is not a publicly routable ip address", addr)};
m_PublicIP = addr; PublicIP = addr;
}); });
conf.defineOption<std::string>("router", "public-address", Hidden, [](std::string) { conf.defineOption<std::string>("router", "public-address", Hidden, [](std::string) {
@ -161,7 +172,7 @@ namespace llarp
[this](int arg) { [this](int arg) {
if (arg <= 0 || arg > std::numeric_limits<uint16_t>::max()) if (arg <= 0 || arg > std::numeric_limits<uint16_t>::max())
throw std::invalid_argument("public-port must be >= 0 and <= 65536"); throw std::invalid_argument("public-port must be >= 0 and <= 65536");
m_PublicPort = ToNet(huint16_t{static_cast<uint16_t>(arg)}); PublicPort = ToNet(huint16_t{static_cast<uint16_t>(arg)});
}); });
conf.defineOption<int>( conf.defineOption<int>(
@ -403,7 +414,7 @@ namespace llarp
ReachableDefault, ReachableDefault,
AssignmentAcceptor(m_reachable), AssignmentAcceptor(m_reachable),
Comment{ 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<int>( conf.defineOption<int>(
@ -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<std::string_view> 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 void
LinksConfig::defineConfigOptions(ConfigDefinition& conf, const ConfigGenParameters& params) LinksConfig::defineConfigOptions(ConfigDefinition& conf, const ConfigGenParameters& params)
{ {
constexpr Default DefaultOutboundLinkValue{"0"};
conf.addSectionComments( conf.addSectionComments(
"bind", "bind",
{ {
"This section specifies network interface names and/or IPs as keys, and", "Typically this section can be left blank, but can be used to specify which sockets to "
"ports as values to control the address(es) on which Lokinet listens for", "bind on for inbound and outbound traffic.",
"incoming data.", "",
"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:", "Examples:",
"", "",
" eth0=1090", " inbound=15.5.29.5:443",
" 0.0.0.0=1090", " inbound=10.0.2.2",
" 1.2.3.4=1090", " outbound=0.0.0.0:9000",
"", "",
"The first bind to port 1090 on the network interface 'eth0'; the second binds", "The first binds an inbound socket on local ip 15.5.29.5 with port 443; and the "
"to port 1090 on all local network interfaces; and the third example binds to", "second binds an inbound socket on local ip 10.0.2.2 with the default port, 1090; and "
"port 1090 on the given IP address.", "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", "Inbound sockets with a wildcard address or private range IP address (like the second "
"if the 0.0.0.0 all-address IP is given then you must also specify the", "example entry) will require setting the public-ip= and public-port= settings with a "
"public-ip= and public-port= settings in the [router] section with a public", "public address at which this router can be reached.",
"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", "On setups with multiple public ip addresses on a network interface, the first ip will "
"configured then lokinet will search for a local network interface with a public", "be used as a default or when a wildcard is provided, unless explicitly set in config.",
"IP address and use that (with port 1090).", "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<std::string>( conf.defineOption<std::string>(
"bind", "bind",
"*", "public-ip",
DefaultOutboundLinkValue, RelayOnly,
Comment{ Comment{"set our public ip if it is different than the one we detect or if we are unable "
"Specify a source port for **outgoing** Lokinet traffic, for example if you want to", "to detect it"},
"set up custom firewall rules based on the originating port. Typically this should", [this](std::string_view arg) {
"be left unset to automatically choose random source ports.", SockAddr pubaddr{arg};
}, PublicAddress = pubaddr.getIP();
[this](std::string arg) { m_OutboundLink = LinkInfoFromINIValues("*", arg); }); });
conf.defineOption<uint16_t>(
"bind",
"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); });
if (params.isRelay) auto parse_addr_for_link = [net_ptr](const std::string& arg, net::port_t default_port) {
std::optional<SockAddr> addr = std::nullopt;
// explicitly provided value
if (not arg.empty())
{
if (arg[0] == ':')
{ {
if (std::string best_if; GetBestNetIF(best_if)) // port only case
m_InboundLinks.push_back(LinkInfoFromINIValues(best_if, std::to_string(DefaultPublicPort))); auto port = net::port_t::from_string(arg.substr(1));
addr = net_ptr->WildcardWithPort(port);
} }
conf.addUndeclaredHandler( else
"bind", {
[&, defaulted = true]( addr = SockAddr{arg};
std::string_view, std::string_view name, std::string_view value) mutable { if (net_ptr->IsLoopbackAddress(addr->getIP()))
if (defaulted) throw std::invalid_argument{fmt::format("{} is a loopback address", arg)};
}
}
if (not addr)
{ {
m_InboundLinks.clear(); // Clear the default // infer public address
defaulted = false; 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) conf.defineOption<std::string>(
throw std::invalid_argument{ "bind",
fmt::format("Invalid [bind] port specified on interface {}", name)}; "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));
});
assert(name != "*"); // handled by defineOption("bind", "*", ...) above conf.defineOption<std::string>(
"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));
});
m_InboundLinks.emplace_back(std::move(info)); 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<SockAddr> 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<uint16_t>::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<uint16_t>::max())
{
addr->setPort(port);
}
else
throw std::invalid_argument{fmt::format("invalid port value: '{}'", val)};
InboundListenAddrs.emplace_back(std::move(*addr));
}); });
} }
@ -1199,8 +1265,14 @@ namespace llarp
return true; return true;
} }
Config::Config(fs::path datadir) std::unique_ptr<ConfigGenParameters>
: m_DataDir(datadir.empty() ? fs::current_path() : std::move(datadir)) Config::MakeGenParams() const
{
return std::make_unique<ConfigGenParameters_impl>();
}
Config::Config(std::optional<fs::path> datadir)
: m_DataDir{datadir ? std::move(*datadir) : fs::current_path()}
{} {}
constexpr auto GetOverridesDir = [](auto datadir) -> fs::path { return datadir / "conf.d"; }; constexpr auto GetOverridesDir = [](auto datadir) -> fs::path { return datadir / "conf.d"; };
@ -1243,25 +1315,17 @@ namespace llarp
} }
bool bool
Config::Load(std::optional<fs::path> fname, bool isRelay) Config::LoadString(std::string_view ini, bool isRelay)
{
if (not fname.has_value())
return LoadDefault(isRelay);
try
{ {
ConfigGenParameters params; auto params = MakeGenParams();
params.isRelay = isRelay; params->isRelay = isRelay;
params.defaultDataDir = m_DataDir; params->defaultDataDir = m_DataDir;
ConfigDefinition conf{isRelay}; ConfigDefinition conf{isRelay};
initializeConfig(conf, params); initializeConfig(conf, *params);
addBackwardsCompatibleConfigOptions(conf);
m_Parser.Clear(); m_Parser.Clear();
if (!m_Parser.LoadFile(*fname)) if (not m_Parser.LoadFromStr(ini))
{
return false; return false;
}
LoadOverrides();
m_Parser.IterAll([&](std::string_view section, const SectionValues_t& values) { m_Parser.IterAll([&](std::string_view section, const SectionValues_t& values) {
for (const auto& pair : values) for (const auto& pair : values)
@ -1270,36 +1334,30 @@ namespace llarp
} }
}); });
conf.acceptAllOptions(); conf.process();
return true; return true;
} }
catch (const std::exception& e)
{
LogError("Error trying to init and parse config from file: ", e.what());
return false;
}
}
bool bool
Config::LoadDefault(bool isRelay) Config::Load(std::optional<fs::path> fname, bool isRelay)
{ {
if (not fname.has_value())
return LoadDefault(isRelay);
try try
{ {
ConfigGenParameters params; auto params = MakeGenParams();
params.isRelay = isRelay; params->isRelay = isRelay;
params.defaultDataDir = m_DataDir; params->defaultDataDir = m_DataDir;
ConfigDefinition conf{isRelay};
initializeConfig(conf, params);
ConfigDefinition conf{isRelay};
initializeConfig(conf, *params);
m_Parser.Clear(); m_Parser.Clear();
LoadOverrides(); if (!m_Parser.LoadFile(*fname))
/// load additional config options added
for (const auto& [sect, key, val] : m_Additional)
{ {
conf.addConfigValue(sect, key, val); return false;
} }
LoadOverrides();
m_Parser.IterAll([&](std::string_view section, const SectionValues_t& values) { m_Parser.IterAll([&](std::string_view section, const SectionValues_t& values) {
for (const auto& pair : values) for (const auto& pair : values)
@ -1307,18 +1365,22 @@ namespace llarp
conf.addConfigValue(section, pair.first, pair.second); conf.addConfigValue(section, pair.first, pair.second);
} }
}); });
conf.process();
conf.acceptAllOptions();
return true; return true;
} }
catch (const std::exception& e) catch (const std::exception& e)
{ {
LogError("Error trying to init default config: ", e.what()); LogError("Error trying to init and parse config from file: ", e.what());
return false; return false;
} }
} }
bool
Config::LoadDefault(bool isRelay)
{
return LoadString("", isRelay);
}
void void
Config::initializeConfig(ConfigDefinition& conf, const ConfigGenParameters& params) Config::initializeConfig(ConfigDefinition& conf, const ConfigGenParameters& params)
{ {
@ -1439,12 +1501,12 @@ namespace llarp
std::string std::string
Config::generateBaseClientConfig() Config::generateBaseClientConfig()
{ {
ConfigGenParameters params; auto params = MakeGenParams();
params.isRelay = false; params->isRelay = false;
params.defaultDataDir = m_DataDir; params->defaultDataDir = m_DataDir;
llarp::ConfigDefinition def{false}; llarp::ConfigDefinition def{false};
initializeConfig(def, params); initializeConfig(def, *params);
generateCommonConfigComments(def); generateCommonConfigComments(def);
def.addSectionComments( def.addSectionComments(
"paths", "paths",
@ -1464,12 +1526,12 @@ namespace llarp
std::string std::string
Config::generateBaseRouterConfig() Config::generateBaseRouterConfig()
{ {
ConfigGenParameters params; auto params = MakeGenParams();
params.isRelay = true; params->isRelay = true;
params.defaultDataDir = m_DataDir; params->defaultDataDir = m_DataDir;
llarp::ConfigDefinition def{true}; llarp::ConfigDefinition def{true};
initializeConfig(def, params); initializeConfig(def, *params);
generateCommonConfigComments(def); generateCommonConfigComments(def);
// lokid // lokid
@ -1485,7 +1547,7 @@ namespace llarp
std::shared_ptr<Config> std::shared_ptr<Config>
Config::EmbeddedConfig() Config::EmbeddedConfig()
{ {
auto config = std::make_shared<Config>(fs::path{}); auto config = std::make_shared<Config>();
config->Load(); config->Load();
config->logging.m_logLevel = log::Level::off; config->logging.m_logLevel = log::Level::off;
config->api.m_enableRPCServer = false; config->api.m_enableRPCServer = false;

@ -39,8 +39,18 @@ namespace llarp
/// parameters that need to be passed around. /// parameters that need to be passed around.
struct ConfigGenParameters struct ConfigGenParameters
{ {
ConfigGenParameters() = default;
virtual ~ConfigGenParameters() = default;
ConfigGenParameters(const ConfigGenParameters&) = delete;
ConfigGenParameters(ConfigGenParameters&&) = delete;
bool isRelay = false; bool isRelay = false;
fs::path defaultDataDir; fs::path defaultDataDir;
/// get network platform (virtual for unit test mocks)
virtual const llarp::net::Platform*
Net_ptr() const = 0;
}; };
struct RouterConfig struct RouterConfig
@ -55,9 +65,6 @@ namespace llarp
bool m_blockBogons = false; bool m_blockBogons = false;
std::optional<nuint32_t> m_PublicIP;
nuint16_t m_PublicPort;
int m_workerThreads = -1; int m_workerThreads = -1;
int m_numNetThreads = -1; int m_numNetThreads = -1;
@ -69,6 +76,10 @@ namespace llarp
std::string m_transportKeyFile; std::string m_transportKeyFile;
bool m_isRelay = false; bool m_isRelay = false;
/// deprecated
std::optional<net::ipaddr_t> PublicIP;
/// deprecated
std::optional<net::port_t> PublicPort;
void void
defineConfigOptions(ConfigDefinition& conf, const ConfigGenParameters& params); defineConfigOptions(ConfigDefinition& conf, const ConfigGenParameters& params);
@ -154,19 +165,10 @@ namespace llarp
struct LinksConfig struct LinksConfig
{ {
struct LinkInfo std::optional<net::ipaddr_t> PublicAddress;
{ std::optional<net::port_t> PublicPort;
std::string m_interface; std::vector<SockAddr> OutboundLinks;
int addressFamily = -1; std::vector<SockAddr> InboundListenAddrs;
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<LinkInfo> m_InboundLinks;
void void
defineConfigOptions(ConfigDefinition& conf, const ConfigGenParameters& params); defineConfigOptions(ConfigDefinition& conf, const ConfigGenParameters& params);
@ -220,9 +222,13 @@ namespace llarp
struct Config struct Config
{ {
explicit Config(fs::path datadir); explicit Config(std::optional<fs::path> datadir = std::nullopt);
virtual ~Config() = default;
~Config() = default; /// create generation params (virtual for unit test mock)
virtual std::unique_ptr<ConfigGenParameters>
MakeGenParams() const;
RouterConfig router; RouterConfig router;
NetworkConfig network; NetworkConfig network;
@ -250,6 +256,10 @@ namespace llarp
bool bool
Load(std::optional<fs::path> fname = std::nullopt, bool isRelay = false); Load(std::optional<fs::path> 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 std::string
generateBaseClientConfig(); generateBaseClientConfig();

@ -89,18 +89,18 @@ namespace llarp
// fall back to undeclared handler if needed // fall back to undeclared handler if needed
auto& sectionDefinitions = secItr->second; auto& sectionDefinitions = secItr->second;
auto defItr = sectionDefinitions.find(std::string(name)); auto defItr = sectionDefinitions.find(std::string(name));
if (defItr == sectionDefinitions.end()) if (defItr != sectionDefinitions.end())
{ {
if (not haveUndeclaredHandler) OptionDefinition_ptr& definition = defItr->second;
throw std::invalid_argument{fmt::format("unrecognized option [{}]:{}", section, name)}; definition->parseValue(std::string(value));
auto& handler = undItr->second;
handler(section, name, value);
return *this; return *this;
} }
OptionDefinition_ptr& definition = defItr->second; if (not haveUndeclaredHandler)
definition->parseValue(std::string(value)); throw std::invalid_argument{fmt::format("unrecognized option [{}]: {}", section, name)};
auto& handler = undItr->second;
handler(section, name, value);
return *this; return *this;
} }
@ -142,9 +142,9 @@ namespace llarp
void void
ConfigDefinition::acceptAllOptions() ConfigDefinition::acceptAllOptions()
{ {
visitSections([&](const std::string& section, const DefinitionMap&) { visitSections([this](const std::string& section, const DefinitionMap&) {
visitDefinitions( visitDefinitions(
section, [&](const std::string&, const OptionDefinition_ptr& def) { def->tryAccept(); }); section, [](const std::string&, const OptionDefinition_ptr& def) { def->tryAccept(); });
}); });
} }

@ -243,12 +243,9 @@ namespace llarp
std::optional<T> std::optional<T>
getValue() const getValue() const
{ {
if (parsedValues.size()) if (parsedValues.empty())
return parsedValues[0]; return required ? std::nullopt : defaultValue;
else if (not required and not multiValued) return parsedValues.front();
return defaultValue;
else
return std::nullopt;
} }
/// Returns the value at the given index. /// Returns the value at the given index.
@ -340,7 +337,7 @@ namespace llarp
void void
tryAccept() const override tryAccept() const override
{ {
if (required and parsedValues.size() == 0) if (required and parsedValues.empty())
{ {
throw std::runtime_error{fmt::format( throw std::runtime_error{fmt::format(
"cannot call tryAccept() on [{}]:{} when required but no value available", "cannot call tryAccept() on [{}]:{} when required but no value available",
@ -348,14 +345,14 @@ namespace llarp
name)}; 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 (acceptor)
{ {
if (multiValued) if (multiValued)
{ {
// add default value in multi value mode
if (defaultValue and parsedValues.empty())
acceptor(*defaultValue);
for (auto value : parsedValues) for (auto value : parsedValues)
{ {
acceptor(value); acceptor(value);
@ -365,14 +362,8 @@ namespace llarp
{ {
auto maybe = getValue(); auto maybe = getValue();
if (maybe) if (maybe)
{
acceptor(*maybe); acceptor(*maybe);
} }
else
{
assert(not defaultValue); // maybe should have a value if defaultValue does
}
}
} }
} }
@ -510,6 +501,14 @@ namespace llarp
void void
acceptAllOptions(); 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 /// 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 /// generation. A proper comment prefix will automatically be applied, and the entire comment
/// will otherwise be used verbatim (no automatic line separation, etc.). /// will otherwise be used verbatim (no automatic line separation, etc.).

@ -8,3 +8,8 @@ constexpr size_t MAX_LINK_MSG_SIZE = 8192;
static constexpr auto DefaultLinkSessionLifetime = 5min; static constexpr auto DefaultLinkSessionLifetime = 5min;
constexpr size_t MaxSendQueueSize = 1024 * 16; constexpr size_t MaxSendQueueSize = 1024 * 16;
static constexpr auto LinkLayerConnectTimeout = 5s; static constexpr auto LinkLayerConnectTimeout = 5s;
namespace llarp::constants
{
static constexpr auto DefaultInboundIWPPort = uint16_t{1090};
}

@ -18,14 +18,14 @@ namespace llarp::uv
class UVWakeup; class UVWakeup;
class UVRepeater; class UVRepeater;
class Loop final : public llarp::EventLoop class Loop : public llarp::EventLoop
{ {
public: public:
using Callback = std::function<void()>; using Callback = std::function<void()>;
Loop(size_t queue_size); Loop(size_t queue_size);
void virtual void
run() override; run() override;
bool bool
@ -63,7 +63,7 @@ namespace llarp::uv
std::shared_ptr<EventLoopRepeater> std::shared_ptr<EventLoopRepeater>
make_repeater() override; make_repeater() override;
std::shared_ptr<llarp::UDPHandle> virtual std::shared_ptr<llarp::UDPHandle>
make_udp(UDPReceiveFunc on_recv) override; make_udp(UDPReceiveFunc on_recv) override;
void void
@ -75,8 +75,11 @@ namespace llarp::uv
bool bool
inEventLoop() const override; inEventLoop() const override;
private: protected:
std::shared_ptr<uvw::Loop> m_Impl; std::shared_ptr<uvw::Loop> m_Impl;
std::optional<std::thread::id> m_EventLoopThreadID;
private:
std::shared_ptr<uvw::AsyncHandle> m_WakeUp; std::shared_ptr<uvw::AsyncHandle> m_WakeUp;
std::atomic<bool> m_Run; std::atomic<bool> m_Run;
using AtomicQueue_t = llarp::thread::Queue<std::function<void(void)>>; using AtomicQueue_t = llarp::thread::Queue<std::function<void(void)>>;
@ -92,8 +95,6 @@ namespace llarp::uv
std::unordered_map<int, std::shared_ptr<uvw::PollHandle>> m_Polls; std::unordered_map<int, std::shared_ptr<uvw::PollHandle>> m_Polls;
std::optional<std::thread::id> m_EventLoopThreadID;
void void
wakeup() override; wakeup() override;
}; };

@ -76,6 +76,15 @@ namespace llarp::vpn
IRouteManager(IRouteManager&&) = delete; IRouteManager(IRouteManager&&) = delete;
virtual ~IRouteManager() = default; virtual ~IRouteManager() = default;
virtual const llarp::net::Platform*
Net_ptr() const;
inline const llarp::net::Platform&
Net() const
{
return *Net_ptr();
}
virtual void virtual void
AddRoute(IPVariant_t ip, IPVariant_t gateway) = 0; AddRoute(IPVariant_t ip, IPVariant_t gateway) = 0;

@ -710,7 +710,7 @@ namespace llarp
m_OurRange = networkConfig.m_ifaddr; m_OurRange = networkConfig.m_ifaddr;
if (!m_OurRange.addr.h) if (!m_OurRange.addr.h)
{ {
const auto maybe = llarp::FindFreeRange(); const auto maybe = m_Router->Net().FindFreeRange();
if (not maybe.has_value()) if (not maybe.has_value())
throw std::runtime_error("cannot find free interface range"); throw std::runtime_error("cannot find free interface range");
m_OurRange = *maybe; m_OurRange = *maybe;
@ -725,7 +725,7 @@ namespace llarp
m_ifname = networkConfig.m_ifname; m_ifname = networkConfig.m_ifname;
if (m_ifname.empty()) if (m_ifname.empty())
{ {
const auto maybe = llarp::FindFreeTun(); const auto maybe = m_Router->Net().FindFreeTun();
if (not maybe.has_value()) if (not maybe.has_value())
throw std::runtime_error("cannot find free interface name"); throw std::runtime_error("cannot find free interface name");
m_ifname = *maybe; m_ifname = *maybe;

@ -222,7 +222,7 @@ namespace llarp
m_IfName = conf.m_ifname; m_IfName = conf.m_ifname;
if (m_IfName.empty()) if (m_IfName.empty())
{ {
const auto maybe = llarp::FindFreeTun(); const auto maybe = m_router->Net().FindFreeTun();
if (not maybe.has_value()) if (not maybe.has_value())
throw std::runtime_error("cannot find free interface name"); throw std::runtime_error("cannot find free interface name");
m_IfName = *maybe; m_IfName = *maybe;
@ -231,7 +231,7 @@ namespace llarp
m_OurRange = conf.m_ifaddr; m_OurRange = conf.m_ifaddr;
if (!m_OurRange.addr.h) if (!m_OurRange.addr.h)
{ {
const auto maybe = llarp::FindFreeRange(); const auto maybe = m_router->Net().FindFreeRange();
if (not maybe.has_value()) if (not maybe.has_value())
{ {
throw std::runtime_error("cannot find free address range"); throw std::runtime_error("cannot find free address range");
@ -938,7 +938,7 @@ namespace llarp
m_OurIPv6 = llarp::huint128_t{ m_OurIPv6 = llarp::huint128_t{
llarp::uint128_t{0xfd2e'6c6f'6b69'0000, llarp::net::TruncateV6(m_OurRange.addr).h}}; llarp::uint128_t{0xfd2e'6c6f'6b69'0000, llarp::net::TruncateV6(m_OurRange.addr).h}};
#else #else
const auto maybe = GetInterfaceIPv6Address(m_IfName); const auto maybe = m_router->Net().GetInterfaceIPv6Address(m_IfName);
if (maybe.has_value()) if (maybe.has_value())
{ {
m_OurIPv6 = *maybe; m_OurIPv6 = *maybe;

@ -129,9 +129,12 @@ namespace llarp
visit(s.get()); visit(s.get());
} }
bool void
ILinkLayer::Configure(AbstractRouter* router, std::string ifname, int af, uint16_t port) 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_Router = router;
m_udp = m_Router->loop()->make_udp( m_udp = m_Router->loop()->make_udp(
[this]([[maybe_unused]] UDPHandle& udp, const SockAddr& from, llarp_buffer_t buf) { [this]([[maybe_unused]] UDPHandle& udp, const SockAddr& from, llarp_buffer_t buf) {
@ -141,71 +144,11 @@ namespace llarp
RecvFrom(from, std::move(pkt)); RecvFrom(from, std::move(pkt));
}); });
if (ifname == "*") if (m_udp->listen(m_ourAddr))
{ return;
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;
return true; throw std::runtime_error{
fmt::format("failed to listen {} udp socket on {}", Name(), m_ourAddr)};
} }
void void

@ -107,8 +107,8 @@ namespace llarp
void void
SendTo_LL(const SockAddr& to, const llarp_buffer_t& pkt); SendTo_LL(const SockAddr& to, const llarp_buffer_t& pkt);
virtual bool void
Configure(AbstractRouter* loop, std::string ifname, int af, uint16_t port); Bind(AbstractRouter* router, SockAddr addr);
virtual std::shared_ptr<ILinkSession> virtual std::shared_ptr<ILinkSession>
NewOutboundSession(const RouterContact& rc, const AddressInfo& ai) = 0; NewOutboundSession(const RouterContact& rc, const AddressInfo& ai) = 0;
@ -233,6 +233,13 @@ namespace llarp
return m_Router; return m_Router;
} }
/// Get the local sock addr we are bound on
const SockAddr&
LocalSocketAddr() const
{
return m_ourAddr;
}
private: private:
const SecretKey& m_RouterEncSecret; const SecretKey& m_RouterEncSecret;

@ -31,6 +31,13 @@ namespace llarp
return IPRange{net::ExpandV4(ipaddr_ipv4_bits(a, b, c, d)), netmask_ipv6_bits(mask + 96)}; 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 /// return true if this iprange is in the IPv4 mapping range for containing ipv4 addresses
constexpr bool constexpr bool
IsV4() const IsV4() const
@ -39,6 +46,15 @@ namespace llarp
return ipv4_map.Contains(addr); 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 /// return true if we intersect with a bogon range
bool bool
BogonRange() const BogonRange() const

@ -2,6 +2,7 @@
#include "net_if.hpp" #include "net_if.hpp"
#include <stdexcept> #include <stdexcept>
#include <llarp/constants/platform.hpp>
#ifdef ANDROID #ifdef ANDROID
#include <llarp/android/ifaddrs.h> #include <llarp/android/ifaddrs.h>
@ -22,13 +23,17 @@
#ifdef ANDROID #ifdef ANDROID
#include <llarp/android/ifaddrs.h> #include <llarp/android/ifaddrs.h>
#else #else
#ifndef _WIN32 #ifdef _WIN32
#include <iphlpapi.h>
#include <llarp/win32/exception.hpp>
#else
#include <ifaddrs.h> #include <ifaddrs.h>
#endif #endif
#endif #endif
#include <cstdio> #include <cstdio>
#include <list> #include <list>
#include <type_traits>
bool bool
operator==(const sockaddr& a, const sockaddr& b) operator==(const sockaddr& a, const sockaddr& b)
@ -76,433 +81,290 @@ operator==(const sockaddr_in6& a, const sockaddr_in6& b)
return a.sin6_port == b.sin6_port && a.sin6_addr == b.sin6_addr; return a.sin6_port == b.sin6_port && a.sin6_addr == b.sin6_addr;
} }
#ifdef _WIN32 namespace llarp::net
#include <assert.h>
#include <errno.h>
#include <iphlpapi.h>
#include <strsafe.h>
// 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; class Platform_Base : public llarp::net::Platform
char _name[256]; {
struct sockaddr_storage _addr; public:
struct sockaddr_storage _netmask; bool
}; IsLoopbackAddress(ipaddr_t ip) const override
{
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);
}
static inline void* SockAddr
_llarp_nt_heap_alloc(const size_t n_bytes) Wildcard(int af) const override
{ {
/* Does not appear very safe with re-entrant calls on XP */ if (af == AF_INET)
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)
{
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)
{ {
_llarp_nt_heap_free(pAdapterInfo); sockaddr_in addr{};
pAdapterInfo = nullptr; addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(0);
return SockAddr{addr};
} }
else if (af == AF_INET6)
{ {
break; 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) bool
IsBogon(const llarp::SockAddr& addr) const override
{ {
case ERROR_SUCCESS: /* NO_ERROR */ return llarp::IsBogon(addr.asIPv6());
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 */ bool
int n = 0, k = 0; IsWildcardAddress(ipaddr_t ip) const override
for (pAdapter = pAdapterInfo; pAdapter; pAdapter = pAdapter->Next)
{
for (IP_ADDR_STRING* pIPAddr = &pAdapter->IpAddressList; pIPAddr; pIPAddr = pIPAddr->Next)
{ {
/* skip null adapters */ return var::visit([](auto&& ip) { return not ip.n; }, ip);
if (strlen(pIPAddr->IpAddress.String) == 0)
continue;
++n;
}
} }
};
#ifdef DEBUG #ifdef _WIN32
fprintf(stderr, "GetAdaptersInfo() discovered %d interfaces.\n", n); class Platform_Impl : public Platform_Base
#endif
/* 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)
{ {
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 <typename Visit_t>
void
iter_adapters(Visit_t&& visit) const
{ {
/* skip null adapters */ constexpr auto flags = GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST
if (strlen(pIPAddr->IpAddress.String) == 0) | GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_INCLUDE_PREFIX | GAA_FLAG_INCLUDE_GATEWAYS
continue; | GAA_FLAG_INCLUDE_ALL_INTERFACES;
/* address */ ULONG sz{};
ift->_ifa.ifa_addr = (struct sockaddr*)&ift->_addr; GetAdaptersAddresses(AF_UNSPEC, flags, nullptr, nullptr, &sz);
val = llarp_nt_sockaddr_pton(pIPAddr->IpAddress.String, ift->_ifa.ifa_addr); auto* ptr = new uint8_t[sz];
assert(1 == val); auto* addrs = reinterpret_cast<PIP_ADAPTER_ADDRESSES>(ptr);
/* name */ if (auto err = GetAdaptersAddresses(AF_UNSPEC, flags, nullptr, addrs, &sz);
#ifdef DEBUG err != ERROR_SUCCESS)
fprintf(stderr, "name:%s IPv4 index:%lu\n", pAdapter->AdapterName, pAdapter->Index); throw llarp::win32::error{err, "GetAdaptersAddresses()"};
#endif
ift->_ifa.ifa_name = ift->_name;
StringCchCopyN(ift->_ifa.ifa_name, 128, pAdapter->AdapterName, 128);
/* flags: assume up, broadcast and multicast */ for (auto* addr = addrs; addr and addr->Next; addr = addr->Next)
ift->_ifa.ifa_flags = IFF_UP | IFF_BROADCAST | IFF_MULTICAST; visit(addr);
if (pAdapter->Type == MIB_IF_TYPE_LOOPBACK)
ift->_ifa.ifa_flags |= IFF_LOOPBACK;
/* netmask */ delete[] ptr;
ift->_ifa.ifa_netmask = (sockaddr*)&ift->_netmask; }
val = llarp_nt_sockaddr_pton(pIPAddr->IpMask.String, ift->_ifa.ifa_netmask);
assert(1 == val);
/* next */ template <typename adapter_t>
if (k++ < (n - 1)) bool
adapter_has_ip(adapter_t* a, ipaddr_t ip) const
{ {
ift->_ifa.ifa_next = (struct llarp_nt_ifaddrs_t*)(ift + 1); for (auto* addr = a->FirstUnicastAddress; addr and addr->Next; addr = addr->Next)
ift = (struct _llarp_nt_ifaddrs_t*)(ift->_ifa.ifa_next);
}
else
{ {
ift->_ifa.ifa_next = nullptr; SockAddr saddr{*addr->Address.lpSockaddr};
} if (saddr.getIP() == ip)
return true;
} }
return false;
} }
if (pAdapterInfo) template <typename adapter_t>
_llarp_nt_heap_free(pAdapterInfo); bool
*ifap = (struct llarp_nt_ifaddrs_t*)ifa; adapter_has_fam(adapter_t* a, int af) const
{
for (auto* addr = a->FirstUnicastAddress; addr and addr->Next; addr = addr->Next)
{
SockAddr saddr{*addr->Address.lpSockaddr};
if (saddr.Family() == af)
return true; return true;
} }
return false;
// 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. public:
static bool std::optional<int>
llarp_nt_getifaddrs(struct llarp_nt_ifaddrs_t** ifap) GetInterfaceIndex(ipaddr_t ip) const override
{ {
assert(nullptr != ifap); std::optional<int> found;
#ifdef DEBUG iter_adapters([&found, ip, this](auto* adapter) {
fprintf(stderr, "llarp_nt_getifaddrs (ifap:%p error:%p)\n", (void*)ifap, (void*)errno); if (found)
#endif return;
return _llarp_nt_getadaptersinfo(ifap); if (adapter_has_ip(adapter, ip))
found = adapter->IfIndex;
});
return found;
} }
static void std::optional<llarp::SockAddr>
llarp_nt_freeifaddrs(struct llarp_nt_ifaddrs_t* ifa) GetInterfaceAddr(std::string_view name, int af) const override
{ {
if (!ifa) std::optional<SockAddr> found;
iter_adapters([name = std::string{name}, af, &found, this](auto* a) {
if (found)
return; return;
free(ifa); if (std::string{a->AdapterName} != name)
return;
if (adapter_has_fam(a, af))
found = SockAddr{*a->FirstUnicastAddress->Address.lpSockaddr};
});
return found;
} }
// emulated if_nametoindex(3) std::optional<SockAddr>
static unsigned AllInterfaces(SockAddr fallback) const override
llarp_nt_if_nametoindex(const char* ifname)
{ {
if (!ifname) // windows seems to not give a shit about source address
return 0; return fallback.isIPv6() ? SockAddr{"[::]"} : SockAddr{"0.0.0.0"};
return _llarp_nt_nametoindex(ifname);
} }
// fix up names for win32 std::optional<std::string>
#define ifaddrs llarp_nt_ifaddrs_t FindFreeTun() const override
#define getifaddrs llarp_nt_getifaddrs {
#define freeifaddrs llarp_nt_freeifaddrs // TODO: implement me ?
#define if_nametoindex llarp_nt_if_nametoindex return std::nullopt;
#endif }
// jeff's original code std::optional<std::string>
bool GetBestNetIF(int) const override
llarp_getifaddr(const char* ifname, int af, struct sockaddr* addr)
{ {
ifaddrs* ifa = nullptr; // TODO: implement me ?
bool found = false; return std::nullopt;
socklen_t sl = sizeof(sockaddr_in6); }
if (af == AF_INET)
sl = sizeof(sockaddr_in);
#ifndef _WIN32 std::optional<IPRange>
if (getifaddrs(&ifa) == -1) FindFreeRange() const override
#else
if (!strcmp(ifname, "lo") || !strcmp(ifname, "lo0"))
{ {
if (addr) std::list<IPRange> currentRanges;
iter_adapters([&currentRanges](auto* i) {
for (auto* addr = i->FirstUnicastAddress; addr and addr->Next; addr = addr->Next)
{ {
sockaddr_in* lo = (sockaddr_in*)addr; SockAddr saddr{*addr->Address.lpSockaddr};
lo->sin_family = af; currentRanges.emplace_back(
lo->sin_port = 0; saddr.asIPv6(),
inet_pton(af, "127.0.0.1", &lo->sin_addr); ipaddr_netmask_bits(addr->OnLinkPrefixLength, addr->Address.lpSockaddr->sa_family));
} }
});
auto ownsRange = [&currentRanges](const IPRange& range) -> bool {
for (const auto& ownRange : currentRanges)
{
if (ownRange * range)
return true; return true;
} }
if (!getifaddrs(&ifa))
#endif
return false; return false;
ifaddrs* i = ifa; };
while (i) // generate possible ranges to in order of attempts
{ std::list<IPRange> possibleRanges;
if (i->ifa_addr) for (byte_t oct = 16; oct < 32; ++oct)
{
// 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)
{ {
memcpy(addr, i->ifa_addr, sl); possibleRanges.emplace_back(IPRange::FromIPv4(172, oct, 0, 1, 16));
if (af == AF_INET6) }
for (byte_t oct = 0; oct < 255; ++oct)
{ {
// set scope id possibleRanges.emplace_back(IPRange::FromIPv4(10, oct, 0, 1, 16));
auto* ip6addr = (sockaddr_in6*)addr;
ip6addr->sin6_scope_id = if_nametoindex(ifname);
ip6addr->sin6_flowinfo = 0;
} }
for (byte_t oct = 0; oct < 255; ++oct)
{
possibleRanges.emplace_back(IPRange::FromIPv4(192, 168, oct, 1, 24));
} }
found = true; // for each possible range pick the first one we don't own
break; for (const auto& range : possibleRanges)
{
if (not ownsRange(range))
return range;
} }
//} return std::nullopt;
} }
i = i->ifa_next; std::string
LoopbackInterfaceName() const override
{
// todo: implement me? does windows even have a loopback?
return "";
} }
if (ifa) bool
freeifaddrs(ifa); HasInterfaceAddress(ipaddr_t ip) const override
return found; {
return GetInterfaceIndex(ip) != std::nullopt;
} }
};
namespace llarp
{
static void
IterAllNetworkInterfaces(std::function<void(ifaddrs* const)> visit)
{
ifaddrs* ifa = nullptr;
#ifndef _WIN32
if (getifaddrs(&ifa) == -1)
#else #else
if (!getifaddrs(&ifa))
#endif
return;
ifaddrs* i = ifa; class Platform_Impl : public Platform_Base
while (i)
{ {
visit(i); template <typename Visit_t>
i = i->ifa_next; void
} iter_all(Visit_t&& visit) const
{
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);
if (ifa) freeifaddrs(addrs);
freeifaddrs(ifa);
} }
namespace net
{ public:
std::string std::string
LoopbackInterfaceName() LoopbackInterfaceName() const override
{ {
const auto loopback = IPRange::FromIPv4(127, 0, 0, 0, 8);
std::string ifname; std::string ifname;
IterAllNetworkInterfaces([&ifname, loopback](ifaddrs* const i) { iter_all([this, &ifname](auto i) {
if (i->ifa_addr and i->ifa_addr->sa_family == AF_INET) 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}; const SockAddr addr{*i->ifa_addr};
if (loopback.Contains(xntohl(addr))) if (IsLoopbackAddress(addr.getIP()))
{ {
ifname = i->ifa_name; ifname = i->ifa_name;
} }
} }
}); });
if (ifname.empty()) if (ifname.empty())
{ throw std::runtime_error{"we have no ipv4 loopback interface for some ungodly reason"};
throw std::runtime_error(
"we have no ipv4 loopback interface for some ungodly reason, yeah idk fam");
}
return ifname; return ifname;
} }
} // namespace net
bool std::optional<std::string>
GetBestNetIF(std::string& ifname, int af) GetBestNetIF(int af) const override
{ {
bool found = false; std::optional<std::string> found;
IterAllNetworkInterfaces([&](ifaddrs* i) { iter_all([this, &found, af](auto i) {
if (found) if (found)
return; return;
if (i->ifa_addr) if (i and i->ifa_addr and i->ifa_addr->sa_family == af)
{ {
if (i->ifa_addr->sa_family == af) if (not IsBogon(*i->ifa_addr))
{ {
llarp::SockAddr a(*i->ifa_addr); found = i->ifa_name;
llarp::IpAddress ip(a);
if (!ip.isBogon())
{
ifname = i->ifa_name;
found = true;
}
} }
} }
}); });
return found; return found;
} }
// TODO: ipv6?
std::optional<IPRange> std::optional<IPRange>
FindFreeRange() FindFreeRange() const override
{ {
std::list<IPRange> currentRanges; std::list<IPRange> currentRanges;
IterAllNetworkInterfaces([&](ifaddrs* i) { iter_all([&currentRanges](auto i) {
if (i && i->ifa_addr) if (i and i->ifa_addr and i->ifa_addr->sa_family == AF_INET)
{ {
const auto fam = i->ifa_addr->sa_family; ipv4addr_t addr{reinterpret_cast<sockaddr_in*>(i->ifa_addr)->sin_addr.s_addr};
if (fam != AF_INET) ipv4addr_t mask{reinterpret_cast<sockaddr_in*>(i->ifa_netmask)->sin_addr.s_addr};
return; currentRanges.emplace_back(IPRange::FromIPv4(addr, mask));
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))});
} }
}); });
auto ownsRange = [&currentRanges](const IPRange& range) -> bool { auto ownsRange = [&currentRanges](const IPRange& range) -> bool {
for (const auto& ownRange : currentRanges) for (const auto& ownRange : currentRanges)
{ {
@ -534,101 +396,93 @@ namespace llarp
return std::nullopt; return std::nullopt;
} }
std::optional<int> GetInterfaceIndex(ipaddr_t) const override
{
// todo: implement me
return std::nullopt;
}
std::optional<std::string> std::optional<std::string>
FindFreeTun() FindFreeTun() const override
{ {
int num = 0; int num = 0;
while (num < 255) while (num < 255)
{ {
std::string iftestname = fmt::format("lokitun{}", num); std::string ifname = fmt::format("lokitun{}", num);
bool found = llarp_getifaddr(iftestname.c_str(), AF_INET, nullptr); if (GetInterfaceAddr(ifname, AF_INET))
if (!found) return ifname;
{
return iftestname;
}
num++; num++;
} }
return std::nullopt; return std::nullopt;
} }
std::optional<SockAddr> std::optional<SockAddr>
GetInterfaceAddr(const std::string& ifname, int af) GetInterfaceAddr(std::string_view ifname, int af) const override
{
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<huint128_t>
GetInterfaceIPv6Address(std::string ifname)
{ {
sockaddr_storage s; std::optional<SockAddr> addr;
sockaddr* sptr = (sockaddr*)&s; iter_all([&addr, af, ifname = std::string{ifname}](auto i) {
sptr->sa_family = AF_INET6; if (addr)
if (!llarp_getifaddr(ifname.c_str(), AF_INET6, sptr)) return;
return std::nullopt; if (i and i->ifa_addr and i->ifa_addr->sa_family == af and i->ifa_name == ifname)
llarp::SockAddr addr{*sptr}; addr = llarp::SockAddr{*i->ifa_addr};
return addr.asIPv6(); });
} return addr;
namespace net
{
namespace
{
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<SockAddr> std::optional<SockAddr>
AllInterfaces(SockAddr pub) AllInterfaces(SockAddr fallback) const override
{ {
std::optional<SockAddr> found; std::optional<SockAddr> found;
IterAllNetworkInterfaces([pub, &found](auto* ifa) { iter_all([fallback, &found](auto i) {
if (found) if (found)
return; return;
if (auto ifa_addr = ifa->ifa_addr) if (i == nullptr or i->ifa_addr == nullptr)
{
if (ifa_addr->sa_family != pub.Family())
return; return;
if (i->ifa_addr->sa_family != fallback.Family())
SockAddr addr{*ifa->ifa_addr}; return;
SockAddr addr{*i->ifa_addr};
if (addr == pub) if (addr == fallback)
found = addr; found = addr;
}
}); });
// 0.0.0.0 is used in our compat shim as our public ip so we check for that special case // 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); 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 // when we cannot find an address but we are looking for 0.0.0.0 just default to the old
if (not found and (pub.isIPv4() and zero.Contains(pub.asIPv4()))) // style
found = All(pub.Family()); if (not found and (fallback.isIPv4() and zero.Contains(fallback.asIPv4())))
found = Wildcard(fallback.Family());
return found; return found;
} }
} // namespace net
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;
}
};
#endif
const Platform_Impl g_plat{};
const Platform*
Platform::Default_ptr()
{
return &g_plat;
}
} // namespace llarp::net
namespace llarp
{
#if !defined(TESTNET) #if !defined(TESTNET)
static constexpr std::array bogonRanges_v6 = { static constexpr std::array bogonRanges_v6 = {
// zero // zero
@ -721,20 +575,5 @@ namespace llarp
return false; return false;
} }
#endif #endif
bool
HasInterfaceAddress(std::variant<nuint32_t, nuint128_t> 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 } // namespace llarp

@ -67,56 +67,89 @@ namespace llarp
bool bool
IsBogonRange(const in6_addr& host, const in6_addr& mask); 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 namespace net
{ {
std::optional<SockAddr> /// network platform (all methods virtual so it can be mocked by unit tests)
AllInterfaces(SockAddr pubaddr); class Platform
}
/// 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" : "::"})) 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<SockAddr>
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
{ {
addr = *maybe; auto addr = Wildcard(af);
return true; addr.setPort(port);
} return addr;
return false;
} }
/// get first network interface with public address virtual std::string
bool LoopbackInterfaceName() const = 0;
GetBestNetIF(std::string& ifname, int af = AF_INET);
/// look at adapter ranges and find a free one virtual bool
std::optional<IPRange> HasInterfaceAddress(ipaddr_t ip) const = 0;
FindFreeRange();
/// look at adapter names and find a free one /// return true if ip is considered a loopback address
std::optional<std::string> virtual bool
FindFreeTun(); IsLoopbackAddress(ipaddr_t ip) const = 0;
/// get network interface address for network interface with ifname /// return true if ip is considered a wildcard address
std::optional<SockAddr> virtual bool
GetInterfaceAddr(const std::string& ifname, int af = AF_INET); IsWildcardAddress(ipaddr_t ip) const = 0;
/// get an interface's ip6 address virtual std::optional<std::string>
std::optional<huint128_t> GetBestNetIF(int af = AF_INET) const = 0;
GetInterfaceIPv6Address(std::string ifname);
#ifdef _WIN32 inline std::optional<SockAddr>
namespace net MaybeInferPublicAddr(port_t default_port, int af = AF_INET) const
{ {
std::optional<int> std::optional<SockAddr> maybe_addr;
GetInterfaceIndex(huint32_t ip); if (auto maybe_ifname = GetBestNetIF(af))
maybe_addr = GetInterfaceAddr(*maybe_ifname, af);
if (maybe_addr)
maybe_addr->setPort(default_port);
return maybe_addr;
} }
#endif
/// return true if we have a network interface with this ip virtual std::optional<IPRange>
bool FindFreeRange() const = 0;
HasInterfaceAddress(std::variant<nuint32_t, nuint128_t> ip);
virtual std::optional<std::string>
FindFreeTun() const = 0;
virtual std::optional<SockAddr>
GetInterfaceAddr(std::string_view ifname, int af = AF_INET) const = 0;
inline std::optional<huint128_t>
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<int>
GetInterfaceIndex(ipaddr_t ip) const = 0;
};
} // namespace net
} // namespace llarp } // namespace llarp

@ -48,4 +48,14 @@ namespace llarp
return false; return false;
return true; 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 } // namespace llarp

@ -5,42 +5,45 @@
#include <oxenc/endian.h> #include <oxenc/endian.h>
namespace llarp namespace llarp
{
namespace net
{ {
huint16_t huint16_t
ToHost(nuint16_t n) ToHost(port_t x)
{ {
return xntohs(n); return huint16_t{oxenc::big_to_host(x.n)};
} }
huint32_t huint32_t
ToHost(nuint32_t n) ToHost(ipv4addr_t x)
{ {
return xntohl(n); return huint32_t{oxenc::big_to_host(x.n)};
} }
huint128_t huint128_t
ToHost(nuint128_t n) ToHost(ipv6addr_t x)
{ {
return {ntoh128(n.n)}; return {ntoh128(x.n)};
} }
nuint16_t port_t
ToNet(huint16_t h) ToNet(huint16_t x)
{ {
return xhtons(h); return port_t{oxenc::host_to_big(x.h)};
} }
nuint32_t ipv4addr_t
ToNet(huint32_t h) ToNet(huint32_t x)
{ {
return xhtonl(h); return ipv4addr_t{oxenc::host_to_big(x.h)};
} }
nuint128_t ipv6addr_t
ToNet(huint128_t h) ToNet(huint128_t x)
{ {
return {hton128(h.h)}; return ipv6addr_t{hton128(x.h)};
} }
} // namespace net
template <> template <>
void void
@ -128,6 +131,17 @@ namespace llarp
return ""; return "";
return tmp; 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 <> template <>
std::string std::string
huint16_t::ToString() const huint16_t::ToString() const

@ -18,6 +18,7 @@
#include <vector> #include <vector>
#include <llarp/util/formattable.hpp> #include <llarp/util/formattable.hpp>
#include <oxenc/variant.h>
#include "uint128.hpp" #include "uint128.hpp"
@ -105,7 +106,7 @@ namespace llarp
} }
using V6Container = std::vector<uint8_t>; using V6Container = std::vector<uint8_t>;
void [[deprecated]] void
ToV6(V6Container& c); ToV6(V6Container& c);
std::string std::string
@ -174,7 +175,7 @@ namespace llarp
} }
using V6Container = std::vector<uint8_t>; using V6Container = std::vector<uint8_t>;
void [[deprecated]] void
ToV6(V6Container& c); ToV6(V6Container& c);
std::string std::string
@ -189,49 +190,86 @@ namespace llarp
*this = ToNet(x); *this = ToNet(x);
return true; return true;
} }
};
template <typename UInt_t>
inline constexpr bool IsToStringFormattable<huint_t<UInt_t>> = true;
template <typename UInt_t>
inline constexpr bool IsToStringFormattable<nuint_t<UInt_t>> = true;
using nuint32_t = nuint_t<uint32_t>;
using nuint16_t = nuint_t<uint16_t>;
using nuint128_t = nuint_t<llarp::uint128_t>;
static inline nuint32_t inline static nuint_t<UInt_t>
xhtonl(huint32_t x) from_string(const std::string& str)
{ {
return nuint32_t{htonl(x.h)}; nuint_t<UInt_t> x{};
if (not x.FromString(str))
throw std::invalid_argument{fmt::format("{} is not a valid value")};
return x;
} }
static inline huint32_t template <typename... Args_t>
xntohl(nuint32_t x) inline static nuint_t<UInt_t>
from_host(Args_t&&... args)
{ {
return huint32_t{ntohl(x.n)}; return ToNet(huint_t<UInt_t>{std::forward<Args_t>(args)...});
} }
};
static inline nuint16_t namespace net
xhtons(huint16_t x) {
/// hides the nuint types used with net_port_t / net_ipv4addr_t / net_ipv6addr_t
namespace
{
using n_uint16_t = llarp::nuint_t<uint16_t>;
using n_uint32_t = llarp::nuint_t<uint32_t>;
using n_uint128_t = llarp::nuint_t<llarp::uint128_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<ipv4addr_t, ipv6addr_t>;
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<huint128_t> = true;
template <>
inline constexpr bool IsToStringFormattable<huint32_t> = true;
template <>
inline constexpr bool IsToStringFormattable<huint16_t> = true;
template <>
inline constexpr bool IsToStringFormattable<net::ipv6addr_t> = true;
template <>
inline constexpr bool IsToStringFormattable<net::ipv4addr_t> = true;
template <>
inline constexpr bool IsToStringFormattable<net::port_t> = 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;
template <typename UInt_t>
[[deprecated("use llarp::net::ToNet instead")]] inline llarp::nuint_t<UInt_t>
ToNet(llarp::huint_t<UInt_t> x)
{ {
return nuint16_t{htons(x.h)}; return llarp::net::ToNet(x);
} }
static inline huint16_t template <typename UInt_t>
xntohs(nuint16_t x) [[deprecated("use llarp::net::ToHost instead")]] inline llarp::huint_t<UInt_t>
ToHost(llarp::nuint_t<UInt_t> x)
{ {
return huint16_t{ntohs(x.n)}; return llarp::net::ToHost(x);
} }
huint16_t ToHost(nuint16_t); [[deprecated("use llarp::net::ToHost instead")]] inline net::ipv4addr_t
huint32_t ToHost(nuint32_t); xhtonl(huint32_t x)
huint128_t ToHost(nuint128_t); {
return ToNet(x);
nuint16_t ToNet(huint16_t); }
nuint32_t ToNet(huint32_t);
nuint128_t ToNet(huint128_t);
} // namespace llarp } // namespace llarp
namespace std namespace std

@ -101,6 +101,15 @@ namespace llarp
void void
setIPv4(uint8_t a, uint8_t b, uint8_t c, uint8_t d); setIPv4(uint8_t a, uint8_t b, uint8_t c, uint8_t d);
inline void
setIP(std::variant<nuint32_t, nuint128_t> ip)
{
if (auto* v4 = std::get_if<nuint32_t>(&ip))
setIPv4(*v4);
if (auto* v6 = std::get_if<nuint128_t>(&ip))
setIPv6(*v6);
}
void void
setIPv4(nuint32_t ip); setIPv4(nuint32_t ip);
@ -143,6 +152,7 @@ namespace llarp
getIPv6() const; getIPv6() const;
nuint32_t nuint32_t
getIPv4() const; getIPv4() const;
std::variant<nuint32_t, nuint128_t> std::variant<nuint32_t, nuint128_t>
getIP() const; getIP() const;

@ -45,6 +45,11 @@ namespace llarp
struct I_RCLookupHandler; struct I_RCLookupHandler;
struct RoutePoker; struct RoutePoker;
namespace net
{
class Platform;
}
namespace exit namespace exit
{ {
struct Context; struct Context;
@ -93,6 +98,9 @@ namespace llarp
virtual bool virtual bool
HandleRecvLinkMessageBuffer(ILinkSession* from, const llarp_buffer_t& msg) = 0; HandleRecvLinkMessageBuffer(ILinkSession* from, const llarp_buffer_t& msg) = 0;
virtual const net::Platform&
Net() const = 0;
virtual const LMQ_ptr& virtual const LMQ_ptr&
lmq() const = 0; lmq() const = 0;

@ -37,6 +37,8 @@
#include <systemd/sd-daemon.h> #include <systemd/sd-daemon.h>
#endif #endif
#include <llarp/constants/platform.hpp>
#include <oxenmq/oxenmq.h> #include <oxenmq/oxenmq.h>
static constexpr std::chrono::milliseconds ROUTER_TICK_INTERVAL = 250ms; static constexpr std::chrono::milliseconds ROUTER_TICK_INTERVAL = 250ms;
@ -580,8 +582,6 @@ namespace llarp
_rc.netID = llarp::NetID(); _rc.netID = llarp::NetID();
} }
// IWP config
m_OutboundPort = conf.links.m_OutboundLink.port;
// Router config // Router config
_rc.SetNick(conf.router.m_nickname); _rc.SetNick(conf.router.m_nickname);
_outboundSessionMaker.maxConnectedRouters = conf.router.m_maxConnectedRouters; _outboundSessionMaker.maxConnectedRouters = conf.router.m_maxConnectedRouters;
@ -592,8 +592,20 @@ namespace llarp
transport_keyfile = m_keyManager->m_transportKeyPath; transport_keyfile = m_keyManager->m_transportKeyPath;
ident_keyfile = m_keyManager->m_idKeyPath; ident_keyfile = m_keyManager->m_idKeyPath;
if (auto maybe = conf.router.m_PublicIP) if (auto maybe_ip = conf.links.PublicAddress)
_ourAddress = SockAddr{*maybe, conf.router.m_PublicPort}; _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; RouterContact::BlockBogons = conf.router.m_blockBogons;
@ -720,48 +732,10 @@ namespace llarp
whitelistRouters, whitelistRouters,
m_isServiceNode); m_isServiceNode);
std::vector<LinksConfig::LinkInfo> inboundLinks = conf.links.m_InboundLinks; // inbound links
InitInboundLinks();
if (inboundLinks.empty() and m_isServiceNode) // outbound links
{ InitOutboundLinks();
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);
}
// profiling // profiling
_profilesFile = conf.router.m_dataDir / "profiles.dat"; _profilesFile = conf.router.m_dataDir / "profiles.dat";
@ -1234,13 +1208,20 @@ namespace llarp
AddressInfo ai; AddressInfo ai;
if (link->GetOurAddressInfo(ai)) if (link->GetOurAddressInfo(ai))
{ {
// override ip and port // override ip and port as needed
if (_ourAddress) if (_ourAddress)
{ {
if (not Net().IsBogon(ai.ip))
throw std::runtime_error{"cannot override public ip, it is already set"};
ai.fromSockAddr(*_ourAddress); ai.fromSockAddr(*_ourAddress);
} }
if (RouterContact::BlockBogons && IsBogon(ai.ip)) 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); LogInfo("adding address: ", ai);
_rc.addrs.push_back(ai); _rc.addrs.push_back(ai);
} }
@ -1268,12 +1249,6 @@ namespace llarp
return false; return false;
} }
if (not InitOutboundLinks())
{
LogError("failed to init outbound links");
return false;
}
if (IsServiceNode()) if (IsServiceNode())
{ {
if (!SaveRC()) if (!SaveRC())
@ -1603,8 +1578,70 @@ namespace llarp
return found; return found;
} }
bool void
Router::InitInboundLinks()
{
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 (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"};
// create inbound links, if we are a service node
for (auto bind_addr : addrs)
{
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() 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( auto link = iwp::NewOutboundLink(
m_keyManager, m_keyManager,
@ -1612,7 +1649,7 @@ namespace llarp
util::memFn(&AbstractRouter::rc, this), util::memFn(&AbstractRouter::rc, this),
util::memFn(&AbstractRouter::HandleRecvLinkMessageBuffer, this), util::memFn(&AbstractRouter::HandleRecvLinkMessageBuffer, this),
util::memFn(&AbstractRouter::Sign, this), util::memFn(&AbstractRouter::Sign, this),
[&](llarp::RouterContact rc) { [this](llarp::RouterContact rc) {
if (IsServiceNode()) if (IsServiceNode())
return; return;
llarp::LogTrace( llarp::LogTrace(
@ -1628,22 +1665,29 @@ namespace llarp
util::memFn(&AbstractRouter::TriggerPump, this), util::memFn(&AbstractRouter::TriggerPump, this),
util::memFn(&AbstractRouter::QueueWork, this)); util::memFn(&AbstractRouter::QueueWork, this));
if (!link) const auto& net = Net();
throw std::runtime_error("NewOutboundLink() failed to provide a link");
for (const auto af : {AF_INET, AF_INET6}) // 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 link->Configure(this, "*", af, m_OutboundPort)) if (not net.IsBogon(bind_addr))
continue; return;
if (auto addr = link->LocalSocketAddr(); not net.IsBogon(addr))
bind_addr.setIP(addr.getIP());
});
#if defined(ANDROID) link->Bind(this, bind_addr);
if constexpr (llarp::platform::is_android)
m_OutboundUDPSocket = link->GetUDPFD().value_or(-1); m_OutboundUDPSocket = link->GetUDPFD().value_or(-1);
#endif
_linkManager.AddLink(std::move(link), false); _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 void

@ -84,6 +84,9 @@ namespace llarp
return m_PathBuildLimiter; return m_PathBuildLimiter;
} }
const llarp::net::Platform&
Net() const override;
const LMQ_ptr& const LMQ_ptr&
lmq() const override lmq() const override
{ {
@ -227,7 +230,6 @@ namespace llarp
bool bool
Sign(Signature& sig, const llarp_buffer_t& buf) const override; Sign(Signature& sig, const llarp_buffer_t& buf) const override;
uint16_t m_OutboundPort = 0;
/// how often do we resign our RC? milliseconds. /// how often do we resign our RC? milliseconds.
// TODO: make configurable // TODO: make configurable
llarp_time_t rcRegenInterval = 1h; llarp_time_t rcRegenInterval = 1h;
@ -363,7 +365,10 @@ namespace llarp
bool bool
HandleRecvLinkMessageBuffer(ILinkSession* from, const llarp_buffer_t& msg) override; HandleRecvLinkMessageBuffer(ILinkSession* from, const llarp_buffer_t& msg) override;
bool void
InitInboundLinks();
void
InitOutboundLinks(); InitOutboundLinks();
bool bool
@ -541,16 +546,8 @@ namespace llarp
return m_Config; return m_Config;
} }
#if defined(ANDROID)
int m_OutboundUDPSocket = -1; int m_OutboundUDPSocket = -1;
int
GetOutboundUDPSocket() const override
{
return m_OutboundUDPSocket;
}
#endif
private: private:
std::atomic<bool> _stopping; std::atomic<bool> _stopping;
std::atomic<bool> _running; std::atomic<bool> _running;

@ -21,6 +21,9 @@
#include <oxenc/endian.h> #include <oxenc/endian.h>
#include <llarp/router/abstractrouter.hpp>
#include <llarp.hpp>
namespace llarp::vpn namespace llarp::vpn
{ {
struct in6_ifreq struct in6_ifreq
@ -290,7 +293,7 @@ namespace llarp::vpn
DefaultRouteViaInterface(std::string ifname, int cmd, int flags) DefaultRouteViaInterface(std::string ifname, int cmd, int flags)
{ {
int if_idx = if_nametoindex(ifname.c_str()); int if_idx = if_nametoindex(ifname.c_str());
const auto maybe = GetInterfaceAddr(ifname); const auto maybe = Net().GetInterfaceAddr(ifname);
if (not maybe) if (not maybe)
throw std::runtime_error{"we dont have our own network interface?"}; 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, lower, gateway, GatewayMode::eLowerDefault, if_idx);
Route(cmd, flags, upper, gateway, GatewayMode::eUpperDefault, 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}; const _inet_addr gateway6{*maybe6, 128};
for (const std::string str : {"::", "4000::", "8000::", "c000::"}) for (const std::string str : {"::", "4000::", "8000::", "c000::"})
@ -320,7 +323,7 @@ namespace llarp::vpn
int if_idx = if_nametoindex(ifname.c_str()); int if_idx = if_nametoindex(ifname.c_str());
if (range.IsV4()) if (range.IsV4())
{ {
const auto maybe = GetInterfaceAddr(ifname); const auto maybe = Net().GetInterfaceAddr(ifname);
if (not maybe) if (not maybe)
throw std::runtime_error{"we dont have our own network interface?"}; throw std::runtime_error{"we dont have our own network interface?"};
@ -333,7 +336,7 @@ namespace llarp::vpn
} }
else else
{ {
const auto maybe = GetInterfaceIPv6Address(ifname); const auto maybe = Net().GetInterfaceIPv6Address(ifname);
if (not maybe) if (not maybe)
throw std::runtime_error{"we dont have our own network interface?"}; throw std::runtime_error{"we dont have our own network interface?"};
const _inet_addr gateway{*maybe, 128}; const _inet_addr gateway{*maybe, 128};

@ -16,6 +16,12 @@
namespace llarp::vpn namespace llarp::vpn
{ {
const llarp::net::Platform*
IRouteManager::Net_ptr() const
{
return llarp::net::Platform::Default_ptr();
}
std::shared_ptr<Platform> std::shared_ptr<Platform>
MakeNativePlatform(llarp::Context* ctx) MakeNativePlatform(llarp::Context* ctx)
{ {

@ -0,0 +1,31 @@
#pragma once
#include <array>
#include <string>
#include <windows.h>
#include <stdexcept>
#include <llarp/util/str.hpp>
namespace llarp::win32
{
namespace
{
inline std::string
error_to_string(DWORD err)
{
std::array<CHAR, 512> 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

@ -70,21 +70,11 @@ namespace llarp
.def(py::init<>()) .def(py::init<>())
.def( .def(
"setOutboundLink", "setOutboundLink",
[](LinksConfig& self, std::string _interface, int family, uint16_t port) { [](LinksConfig& self, std::string addr) {
LinksConfig::LinkInfo info; self.OutboundLinks.emplace_back(std::move(addr));
info.m_interface = std::move(_interface);
info.addressFamily = family;
info.port = port;
self.m_OutboundLink = std::move(info);
}) })
.def( .def("addInboundLink", [](LinksConfig& self, std::string addr) {
"addInboundLink", self.InboundListenAddrs.emplace_back(std::move(addr));
[](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);
}); });
py::class_<ApiConfig>(mod, "ApiConfig") py::class_<ApiConfig>(mod, "ApiConfig")

@ -16,11 +16,11 @@ Tier 1:
* [Linux](#linux-install) * [Linux](#linux-install)
* [Android](#apk-install) * [Android](#apk-install)
* [Windows](#windows-install)
* [MacOS](#mac-install)
Tier 2: Tier 2:
* [Windows](#windows-install)
* [MacOS](#mac-install)
* [FreeBSD](#freebsd-install) * [FreeBSD](#freebsd-install)
Currently Unsupported Platforms: (maintainers welcome) Currently Unsupported Platforms: (maintainers welcome)

@ -18,6 +18,7 @@ add_executable(testAll
config/test_llarp_config_definition.cpp config/test_llarp_config_definition.cpp
config/test_llarp_config_ini.cpp config/test_llarp_config_ini.cpp
config/test_llarp_config_output.cpp config/test_llarp_config_output.cpp
config/test_llarp_config_values.cpp
crypto/test_llarp_crypto_types.cpp crypto/test_llarp_crypto_types.cpp
crypto/test_llarp_crypto.cpp crypto/test_llarp_crypto.cpp
crypto/test_llarp_key_manager.cpp crypto/test_llarp_key_manager.cpp
@ -29,7 +30,6 @@ add_executable(testAll
path/test_path.cpp path/test_path.cpp
peerstats/test_peer_db.cpp peerstats/test_peer_db.cpp
peerstats/test_peer_types.cpp peerstats/test_peer_types.cpp
regress/2020-06-08-key-backup-bug.cpp
router/test_llarp_router_version.cpp router/test_llarp_router_version.cpp
routing/test_llarp_routing_transfer_traffic.cpp routing/test_llarp_routing_transfer_traffic.cpp
routing/test_llarp_routing_obtainexitmessage.cpp routing/test_llarp_routing_obtainexitmessage.cpp

@ -0,0 +1,286 @@
#include <llarp/config/config.hpp>
#include <catch2/catch.hpp>
#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<llarp::ConfigGenParameters>
MakeGenParams() const override
{
return std::make_unique<UnitTestConfigGenParameters>(_plat);
}
};
std::shared_ptr<UnitTestConfig>
make_config_for_test(const mocks::Network* env, std::string_view ini_str = "")
{
auto conf = std::make_shared<UnitTestConfig>(env);
conf->LoadString(ini_str, true);
conf->lokid.whitelistRouters = false;
conf->bootstrap.seednode = true;
conf->bootstrap.files.clear();
return conf;
}
std::shared_ptr<UnitTestConfig>
make_config(mocks::Network env, std::string_view ini_str = "")
{
auto conf = std::make_shared<UnitTestConfig>(&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<mocks::MockContext>(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<std::string, llarp::IPRange> 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<std::string, llarp::IPRange> 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<std::string, llarp::IPRange> 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));
}
}

@ -0,0 +1,40 @@
#pragma once
#include <llarp.hpp>
#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<llarp::EventLoop>{const_cast<Network*>(&_net), [](Network*) {}};
}
std::shared_ptr<llarp::AbstractRouter>
makeRouter(const std::shared_ptr<llarp::EventLoop>&) override
{
return std::static_pointer_cast<llarp::AbstractRouter>(
std::make_shared<MockRouter>(_net, makeVPNPlatform()));
}
std::shared_ptr<llarp::vpn::Platform>
makeVPNPlatform() override
{
return std::static_pointer_cast<llarp::vpn::Platform>(std::make_shared<MockVPN>(_net));
}
std::shared_ptr<llarp::NodeDB>
makeNodeDB() override
{
return std::make_shared<llarp::NodeDB>();
}
};
} // namespace mocks

@ -0,0 +1,192 @@
#pragma once
#include <unordered_map>
#include <llarp/net/net.hpp>
#include <llarp/ev/ev_libuv.hpp>
#include <oxenc/variant.h>
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<std::string, llarp::IPRange> _network_interfaces;
bool _snode;
const Platform* const m_Default{Platform::Default_ptr()};
public:
Network(
std::unordered_multimap<std::string, llarp::IPRange> 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<uvw::Loop::Mode::ONCE>();
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<llarp::UDPHandle>
make_udp(UDPReceiveFunc recv) override
{
return std::make_shared<MockUDPHandle>(this, recv);
}
std::optional<std::string>
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<std::string>
FindFreeTun() const override
{
return "mocktun0";
}
std::optional<llarp::SockAddr>
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<llarp::SockAddr>
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<int>
GetInterfaceIndex(llarp::net::ipaddr_t ip) const override
{
return m_Default->GetInterfaceIndex(ip);
}
std::optional<llarp::IPRange>
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<IPRange> 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

@ -0,0 +1,25 @@
#pragma once
#include <llarp/router/router.hpp>
#include "mock_network.hpp"
namespace mocks
{
class MockRouter : public llarp::Router
{
const Network& _net;
public:
explicit MockRouter(const Network& net, std::shared_ptr<llarp::vpn::Platform> vpnPlatform)
: llarp::
Router{std::shared_ptr<llarp::EventLoop>{const_cast<Network*>(&net), [](Network*) {}}, vpnPlatform}
, _net{net}
{}
const llarp::net::Platform&
Net() const override
{
return _net;
};
};
} // namespace mocks

@ -0,0 +1,93 @@
#pragma once
#include <llarp/ev/vpn.hpp>
#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<llarp::vpn::NetworkInterface>
ObtainInterface(llarp::vpn::InterfaceInfo info, llarp::AbstractRouter*) override
{
return std::make_shared<MockInterface>(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<IPVariant_t> GetGatewaysNotOnInterface(std::string) override
{
return std::vector<IPVariant_t>{};
};
/// get owned ip route manager for managing routing table
virtual llarp::vpn::IRouteManager&
RouteManager() override
{
return *this;
};
};
} // namespace mocks

@ -1,77 +0,0 @@
#include <llarp.hpp>
#include <config/config.hpp>
#include <router/abstractrouter.hpp>
#include <service/context.hpp>
#include <catch2/catch.hpp>
llarp::RuntimeOptions opts = {false, false, false};
/// make a context with 1 endpoint that specifies a keyfile
static std::shared_ptr<llarp::Context>
make_context(std::optional<fs::path> keyfile)
{
auto conf = std::make_shared<llarp::Config>(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<llarp::Context>();
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<fs::path> path : {std::optional<fs::path>{"regress-1.private"},
std::optional<fs::path>{"regress-2.private"},
std::optional<fs::path>{""},
{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);
}
}
Loading…
Cancel
Save