* 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()
if(NOT APPLE)
add_compile_options(-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 -Wall -Wextra -Wno-unknown-pragmas -Wno-unused-function -Wno-deprecated-declarations -Werror=vla)
add_compile_options(-Wall -Wextra -Wno-unknown-pragmas -Wno-unused-function -Wno-deprecated-declarations -Werror=vla)
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wno-unknown-warning-option)
endif()

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

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

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

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

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

@ -243,12 +243,9 @@ namespace llarp
std::optional<T>
getValue() const
{
if (parsedValues.size())
return parsedValues[0];
else if (not required and not multiValued)
return defaultValue;
else
return std::nullopt;
if (parsedValues.empty())
return required ? std::nullopt : defaultValue;
return parsedValues.front();
}
/// Returns the value at the given index.
@ -340,7 +337,7 @@ namespace llarp
void
tryAccept() const override
{
if (required and parsedValues.size() == 0)
if (required and parsedValues.empty())
{
throw std::runtime_error{fmt::format(
"cannot call tryAccept() on [{}]:{} when required but no value available",
@ -348,14 +345,14 @@ namespace llarp
name)};
}
// don't use default value if we are multi-valued and have no value
if (multiValued and parsedValues.size() == 0)
return;
if (acceptor)
{
if (multiValued)
{
// add default value in multi value mode
if (defaultValue and parsedValues.empty())
acceptor(*defaultValue);
for (auto value : parsedValues)
{
acceptor(value);
@ -365,13 +362,7 @@ namespace llarp
{
auto maybe = getValue();
if (maybe)
{
acceptor(*maybe);
}
else
{
assert(not defaultValue); // maybe should have a value if defaultValue does
}
}
}
}
@ -510,6 +501,14 @@ namespace llarp
void
acceptAllOptions();
/// validates and accept all parsed options
inline void
process()
{
validateRequiredFields();
acceptAllOptions();
}
/// Add comments for a given section. Comments are replayed in-order during config file
/// generation. A proper comment prefix will automatically be applied, and the entire comment
/// will otherwise be used verbatim (no automatic line separation, etc.).

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

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

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

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

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

@ -129,9 +129,12 @@ namespace llarp
visit(s.get());
}
bool
ILinkLayer::Configure(AbstractRouter* router, std::string ifname, int af, uint16_t port)
void
ILinkLayer::Bind(AbstractRouter* router, SockAddr bind_addr)
{
if (router->Net().IsLoopbackAddress(bind_addr.getIP()))
throw std::runtime_error{"cannot udp bind socket on loopback"};
m_ourAddr = bind_addr;
m_Router = router;
m_udp = m_Router->loop()->make_udp(
[this]([[maybe_unused]] UDPHandle& udp, const SockAddr& from, llarp_buffer_t buf) {
@ -141,71 +144,11 @@ namespace llarp
RecvFrom(from, std::move(pkt));
});
if (ifname == "*")
{
if (router->IsServiceNode())
{
if (auto maybe = router->OurPublicIP())
{
auto addr = var::visit([](auto&& addr) { return SockAddr{addr}; }, *maybe);
// service node outbound link
if (HasInterfaceAddress(addr.getIP()))
{
// we have our ip claimed on a local net interface
m_ourAddr = addr;
}
else if (auto maybe = net::AllInterfaces(addr))
{
// we do not have our claimed ip, nat or something?
m_ourAddr = *maybe;
}
else if (auto maybe = net::AllInterfaces(SockAddr{"0.0.0.0"}))
{
// one last fallback
m_ourAddr = *maybe;
}
else
return false; // the ultimate failure case
}
else
return false;
}
else if (auto maybe = net::AllInterfaces(SockAddr{"0.0.0.0"}))
{
// client outbound link
m_ourAddr = *maybe;
}
else
return false;
}
else
{
if (ifname == "0.0.0.0" and not GetBestNetIF(ifname))
throw std::invalid_argument{
"0.0.0.0 provided and we cannot find a valid ip to use, please set one "
"explicitly instead in the bind section instead of 0.0.0.0"};
if (const auto maybe = GetInterfaceAddr(ifname, af))
{
m_ourAddr = *maybe;
}
else
{
try
{
m_ourAddr = SockAddr{ifname + ":0"};
}
catch (const std::exception& ex)
{
LogError("Could not use ifname ", ifname, " to configure ILinkLayer: ", ex.what());
throw ex;
}
}
}
m_ourAddr.setPort(port);
if (not m_udp->listen(m_ourAddr))
return false;
if (m_udp->listen(m_ourAddr))
return;
return true;
throw std::runtime_error{
fmt::format("failed to listen {} udp socket on {}", Name(), m_ourAddr)};
}
void

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

@ -31,6 +31,13 @@ namespace llarp
return IPRange{net::ExpandV4(ipaddr_ipv4_bits(a, b, c, d)), netmask_ipv6_bits(mask + 96)};
}
static inline IPRange
FromIPv4(net::ipv4addr_t addr, net::ipv4addr_t netmask)
{
return IPRange{
net::ExpandV4(ToHost(addr)), netmask_ipv6_bits(bits::count_bits(netmask) + 96)};
}
/// return true if this iprange is in the IPv4 mapping range for containing ipv4 addresses
constexpr bool
IsV4() const
@ -39,6 +46,15 @@ namespace llarp
return ipv4_map.Contains(addr);
}
/// get address family
constexpr int
Family() const
{
if (IsV4())
return AF_INET;
return AF_INET6;
}
/// return true if we intersect with a bogon range
bool
BogonRange() const

@ -2,6 +2,7 @@
#include "net_if.hpp"
#include <stdexcept>
#include <llarp/constants/platform.hpp>
#ifdef ANDROID
#include <llarp/android/ifaddrs.h>
@ -22,13 +23,17 @@
#ifdef ANDROID
#include <llarp/android/ifaddrs.h>
#else
#ifndef _WIN32
#ifdef _WIN32
#include <iphlpapi.h>
#include <llarp/win32/exception.hpp>
#else
#include <ifaddrs.h>
#endif
#endif
#include <cstdio>
#include <list>
#include <type_traits>
bool
operator==(const sockaddr& a, const sockaddr& b)
@ -76,559 +81,408 @@ operator==(const sockaddr_in6& a, const sockaddr_in6& b)
return a.sin6_port == b.sin6_port && a.sin6_addr == b.sin6_addr;
}
#ifdef _WIN32
#include <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;
char _name[256];
struct sockaddr_storage _addr;
struct sockaddr_storage _netmask;
};
static inline void*
_llarp_nt_heap_alloc(const size_t n_bytes)
namespace llarp::net
{
/* Does not appear very safe with re-entrant calls on XP */
return HeapAlloc(GetProcessHeap(), HEAP_GENERATE_EXCEPTIONS, n_bytes);
}
static inline void
_llarp_nt_heap_free(void* mem)
{
HeapFree(GetProcessHeap(), 0, mem);
}
#define llarp_nt_new0(struct_type, n_structs) \
((struct_type*)malloc((size_t)sizeof(struct_type) * (size_t)(n_structs)))
int
llarp_nt_sockaddr_pton(const char* src, struct sockaddr* dst)
{
struct addrinfo hints;
struct addrinfo* result = nullptr;
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_NUMERICHOST;
const int status = getaddrinfo(src, nullptr, &hints, &result);
if (!status)
class Platform_Base : public llarp::net::Platform
{
memcpy(dst, result->ai_addr, result->ai_addrlen);
freeaddrinfo(result);
return 1;
}
return 0;
}
/* NB: IP_ADAPTER_INFO size varies size due to sizeof (time_t), the API assumes
* 4-byte datatype whilst compiler uses an 8-byte datatype. Size can be forced
* with -D_USE_32BIT_TIME_T with side effects to everything else.
*
* Only supports IPv4 addressing similar to SIOCGIFCONF socket option.
*
* Interfaces that are not "operationally up" will return the address 0.0.0.0,
* this includes adapters with static IP addresses but with disconnected cable.
* This is documented under the GetIpAddrTable API. Interface status can only
* be determined by the address, a separate flag is introduced with the
* GetAdapterAddresses API.
*
* The IPv4 loopback interface is not included.
*
* Available in Windows 2000 and Wine 1.0.
*/
static bool
_llarp_nt_getadaptersinfo(struct llarp_nt_ifaddrs_t** ifap)
{
DWORD dwRet;
ULONG ulOutBufLen = DEFAULT_BUFFER_SIZE;
PIP_ADAPTER_INFO pAdapterInfo = nullptr;
PIP_ADAPTER_INFO pAdapter = nullptr;
/* loop to handle interfaces coming online causing a buffer overflow
* between first call to list buffer length and second call to enumerate.
*/
for (unsigned i = 3; i; i--)
{
#ifdef DEBUG
fprintf(stderr, "IP_ADAPTER_INFO buffer length %lu bytes.\n", ulOutBufLen);
#endif
pAdapterInfo = (IP_ADAPTER_INFO*)_llarp_nt_heap_alloc(ulOutBufLen);
dwRet = GetAdaptersInfo(pAdapterInfo, &ulOutBufLen);
if (ERROR_BUFFER_OVERFLOW == dwRet)
public:
bool
IsLoopbackAddress(ipaddr_t ip) const override
{
_llarp_nt_heap_free(pAdapterInfo);
pAdapterInfo = nullptr;
return var::visit(
[loopback6 = IPRange{huint128_t{uint128_t{0UL, 1UL}}, netmask_ipv6_bits(128)},
loopback4 = IPRange::FromIPv4(127, 0, 0, 0, 8)](auto&& ip) {
const auto h_ip = ToHost(ip);
return loopback4.Contains(h_ip) or loopback6.Contains(h_ip);
},
ip);
}
else
SockAddr
Wildcard(int af) const override
{
break;
if (af == AF_INET)
{
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(0);
return SockAddr{addr};
}
if (af == AF_INET6)
{
sockaddr_in6 addr6{};
addr6.sin6_family = AF_INET6;
addr6.sin6_port = htons(0);
addr6.sin6_addr = IN6ADDR_ANY_INIT;
return SockAddr{addr6};
}
throw std::invalid_argument{fmt::format("{} is not a valid address family")};
}
}
switch (dwRet)
{
case ERROR_SUCCESS: /* NO_ERROR */
break;
case ERROR_BUFFER_OVERFLOW:
errno = ENOBUFS;
if (pAdapterInfo)
_llarp_nt_heap_free(pAdapterInfo);
return false;
default:
errno = dwRet;
#ifdef DEBUG
fprintf(stderr, "system call failed: %lu\n", GetLastError());
#endif
if (pAdapterInfo)
_llarp_nt_heap_free(pAdapterInfo);
return false;
}
/* count valid adapters */
int n = 0, k = 0;
for (pAdapter = pAdapterInfo; pAdapter; pAdapter = pAdapter->Next)
{
for (IP_ADDR_STRING* pIPAddr = &pAdapter->IpAddressList; pIPAddr; pIPAddr = pIPAddr->Next)
bool
IsBogon(const llarp::SockAddr& addr) const override
{
/* skip null adapters */
if (strlen(pIPAddr->IpAddress.String) == 0)
continue;
++n;
return llarp::IsBogon(addr.asIPv6());
}
}
#ifdef DEBUG
fprintf(stderr, "GetAdaptersInfo() discovered %d interfaces.\n", n);
#endif
bool
IsWildcardAddress(ipaddr_t ip) const override
{
return var::visit([](auto&& ip) { return not ip.n; }, ip);
}
};
/* contiguous block for adapter list */
struct _llarp_nt_ifaddrs_t* ifa = llarp_nt_new0(struct _llarp_nt_ifaddrs_t, n);
struct _llarp_nt_ifaddrs_t* ift = ifa;
int val = 0;
/* now populate list */
for (pAdapter = pAdapterInfo; pAdapter; pAdapter = pAdapter->Next)
#ifdef _WIN32
class Platform_Impl : public Platform_Base
{
for (IP_ADDR_STRING* pIPAddr = &pAdapter->IpAddressList; pIPAddr; pIPAddr = pIPAddr->Next)
/// visit all adapters (not addresses). windows serves net info per adapter unlink posix which
/// gives a list of all distinct addresses.
template <typename Visit_t>
void
iter_adapters(Visit_t&& visit) const
{
/* skip null adapters */
if (strlen(pIPAddr->IpAddress.String) == 0)
continue;
/* address */
ift->_ifa.ifa_addr = (struct sockaddr*)&ift->_addr;
val = llarp_nt_sockaddr_pton(pIPAddr->IpAddress.String, ift->_ifa.ifa_addr);
assert(1 == val);
/* name */
#ifdef DEBUG
fprintf(stderr, "name:%s IPv4 index:%lu\n", pAdapter->AdapterName, pAdapter->Index);
#endif
ift->_ifa.ifa_name = ift->_name;
StringCchCopyN(ift->_ifa.ifa_name, 128, pAdapter->AdapterName, 128);
constexpr auto flags = GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST
| GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_INCLUDE_PREFIX | GAA_FLAG_INCLUDE_GATEWAYS
| GAA_FLAG_INCLUDE_ALL_INTERFACES;
/* flags: assume up, broadcast and multicast */
ift->_ifa.ifa_flags = IFF_UP | IFF_BROADCAST | IFF_MULTICAST;
if (pAdapter->Type == MIB_IF_TYPE_LOOPBACK)
ift->_ifa.ifa_flags |= IFF_LOOPBACK;
ULONG sz{};
GetAdaptersAddresses(AF_UNSPEC, flags, nullptr, nullptr, &sz);
auto* ptr = new uint8_t[sz];
auto* addrs = reinterpret_cast<PIP_ADAPTER_ADDRESSES>(ptr);
/* netmask */
ift->_ifa.ifa_netmask = (sockaddr*)&ift->_netmask;
val = llarp_nt_sockaddr_pton(pIPAddr->IpMask.String, ift->_ifa.ifa_netmask);
assert(1 == val);
if (auto err = GetAdaptersAddresses(AF_UNSPEC, flags, nullptr, addrs, &sz);
err != ERROR_SUCCESS)
throw llarp::win32::error{err, "GetAdaptersAddresses()"};
for (auto* addr = addrs; addr and addr->Next; addr = addr->Next)
visit(addr);
delete[] ptr;
}
/* next */
if (k++ < (n - 1))
template <typename adapter_t>
bool
adapter_has_ip(adapter_t* a, ipaddr_t ip) const
{
for (auto* addr = a->FirstUnicastAddress; addr and addr->Next; addr = addr->Next)
{
ift->_ifa.ifa_next = (struct llarp_nt_ifaddrs_t*)(ift + 1);
ift = (struct _llarp_nt_ifaddrs_t*)(ift->_ifa.ifa_next);
SockAddr saddr{*addr->Address.lpSockaddr};
if (saddr.getIP() == ip)
return true;
}
else
return false;
}
template <typename adapter_t>
bool
adapter_has_fam(adapter_t* a, int af) const
{
for (auto* addr = a->FirstUnicastAddress; addr and addr->Next; addr = addr->Next)
{
ift->_ifa.ifa_next = nullptr;
SockAddr saddr{*addr->Address.lpSockaddr};
if (saddr.Family() == af)
return true;
}
return false;
}
}
if (pAdapterInfo)
_llarp_nt_heap_free(pAdapterInfo);
*ifap = (struct llarp_nt_ifaddrs_t*)ifa;
return true;
}
// an implementation of if_nametoindex(3) based on GetAdapterIndex(2)
// with a fallback to GetAdaptersAddresses(2) commented out for now
// unless it becomes evident that the first codepath fails in certain
// edge cases?
static unsigned
_llarp_nt_nametoindex(const char* ifname)
{
ULONG ifIndex;
DWORD dwRet;
char szAdapterName[256];
if (!ifname)
return 0;
StringCchCopyN(szAdapterName, sizeof(szAdapterName), ifname, 256);
dwRet = GetAdapterIndex((LPWSTR)szAdapterName, &ifIndex);
if (!dwRet)
return ifIndex;
else
return 0;
}
// the emulated getifaddrs(3) itself.
static bool
llarp_nt_getifaddrs(struct llarp_nt_ifaddrs_t** ifap)
{
assert(nullptr != ifap);
#ifdef DEBUG
fprintf(stderr, "llarp_nt_getifaddrs (ifap:%p error:%p)\n", (void*)ifap, (void*)errno);
#endif
return _llarp_nt_getadaptersinfo(ifap);
}
public:
std::optional<int>
GetInterfaceIndex(ipaddr_t ip) const override
{
std::optional<int> found;
iter_adapters([&found, ip, this](auto* adapter) {
if (found)
return;
if (adapter_has_ip(adapter, ip))
found = adapter->IfIndex;
});
return found;
}
static void
llarp_nt_freeifaddrs(struct llarp_nt_ifaddrs_t* ifa)
{
if (!ifa)
return;
free(ifa);
}
std::optional<llarp::SockAddr>
GetInterfaceAddr(std::string_view name, int af) const override
{
std::optional<SockAddr> found;
iter_adapters([name = std::string{name}, af, &found, this](auto* a) {
if (found)
return;
if (std::string{a->AdapterName} != name)
return;
// emulated if_nametoindex(3)
static unsigned
llarp_nt_if_nametoindex(const char* ifname)
{
if (!ifname)
return 0;
return _llarp_nt_nametoindex(ifname);
}
if (adapter_has_fam(a, af))
found = SockAddr{*a->FirstUnicastAddress->Address.lpSockaddr};
});
return found;
}
// fix up names for win32
#define ifaddrs llarp_nt_ifaddrs_t
#define getifaddrs llarp_nt_getifaddrs
#define freeifaddrs llarp_nt_freeifaddrs
#define if_nametoindex llarp_nt_if_nametoindex
#endif
std::optional<SockAddr>
AllInterfaces(SockAddr fallback) const override
{
// windows seems to not give a shit about source address
return fallback.isIPv6() ? SockAddr{"[::]"} : SockAddr{"0.0.0.0"};
}
// jeff's original code
bool
llarp_getifaddr(const char* ifname, int af, struct sockaddr* addr)
{
ifaddrs* ifa = nullptr;
bool found = false;
socklen_t sl = sizeof(sockaddr_in6);
if (af == AF_INET)
sl = sizeof(sockaddr_in);
std::optional<std::string>
FindFreeTun() const override
{
// TODO: implement me ?
return std::nullopt;
}
#ifndef _WIN32
if (getifaddrs(&ifa) == -1)
#else
if (!strcmp(ifname, "lo") || !strcmp(ifname, "lo0"))
{
if (addr)
std::optional<std::string>
GetBestNetIF(int) const override
{
sockaddr_in* lo = (sockaddr_in*)addr;
lo->sin_family = af;
lo->sin_port = 0;
inet_pton(af, "127.0.0.1", &lo->sin_addr);
// TODO: implement me ?
return std::nullopt;
}
return true;
}
if (!getifaddrs(&ifa))
#endif
return false;
ifaddrs* i = ifa;
while (i)
{
if (i->ifa_addr)
std::optional<IPRange>
FindFreeRange() const override
{
// llarp::LogInfo(__FILE__, "scanning ", i->ifa_name, " af: ",
// std::to_string(i->ifa_addr->sa_family));
if (std::string_view{i->ifa_name} == std::string_view{ifname} && i->ifa_addr->sa_family == af)
{
// can't do this here
// llarp::Addr a(*i->ifa_addr);
// if(!a.isPrivate())
//{
// llarp::LogInfo(__FILE__, "found ", ifname, " af: ", af);
if (addr)
std::list<IPRange> currentRanges;
iter_adapters([&currentRanges](auto* i) {
for (auto* addr = i->FirstUnicastAddress; addr and addr->Next; addr = addr->Next)
{
memcpy(addr, i->ifa_addr, sl);
if (af == AF_INET6)
{
// set scope id
auto* ip6addr = (sockaddr_in6*)addr;
ip6addr->sin6_scope_id = if_nametoindex(ifname);
ip6addr->sin6_flowinfo = 0;
}
SockAddr saddr{*addr->Address.lpSockaddr};
currentRanges.emplace_back(
saddr.asIPv6(),
ipaddr_netmask_bits(addr->OnLinkPrefixLength, addr->Address.lpSockaddr->sa_family));
}
});
auto ownsRange = [&currentRanges](const IPRange& range) -> bool {
for (const auto& ownRange : currentRanges)
{
if (ownRange * range)
return true;
}
found = true;
break;
return false;
};
// generate possible ranges to in order of attempts
std::list<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;
}
i = i->ifa_next;
}
if (ifa)
freeifaddrs(ifa);
return found;
}
std::string
LoopbackInterfaceName() const override
{
// todo: implement me? does windows even have a loopback?
return "";
}
bool
HasInterfaceAddress(ipaddr_t ip) const override
{
return GetInterfaceIndex(ip) != std::nullopt;
}
};
namespace llarp
{
static void
IterAllNetworkInterfaces(std::function<void(ifaddrs* const)> visit)
{
ifaddrs* ifa = nullptr;
#ifndef _WIN32
if (getifaddrs(&ifa) == -1)
#else
if (!getifaddrs(&ifa))
#endif
return;
ifaddrs* i = ifa;
while (i)
class Platform_Impl : public Platform_Base
{
template <typename Visit_t>
void
iter_all(Visit_t&& visit) const
{
visit(i);
i = i->ifa_next;
ifaddrs* addrs{nullptr};
if (getifaddrs(&addrs))
throw std::runtime_error{fmt::format("getifaddrs(): {}", strerror(errno))};
for (auto next = addrs; addrs and addrs->ifa_next; addrs = addrs->ifa_next)
visit(next);
freeifaddrs(addrs);
}
if (ifa)
freeifaddrs(ifa);
}
namespace net
{
public:
std::string
LoopbackInterfaceName()
LoopbackInterfaceName() const override
{
const auto loopback = IPRange::FromIPv4(127, 0, 0, 0, 8);
std::string ifname;
IterAllNetworkInterfaces([&ifname, loopback](ifaddrs* const i) {
if (i->ifa_addr and i->ifa_addr->sa_family == AF_INET)
iter_all([this, &ifname](auto i) {
if (i and i->ifa_addr and i->ifa_addr->sa_family == AF_INET)
{
llarp::nuint32_t addr{((sockaddr_in*)i->ifa_addr)->sin_addr.s_addr};
if (loopback.Contains(xntohl(addr)))
const SockAddr addr{*i->ifa_addr};
if (IsLoopbackAddress(addr.getIP()))
{
ifname = i->ifa_name;
}
}
});
if (ifname.empty())
{
throw std::runtime_error(
"we have no ipv4 loopback interface for some ungodly reason, yeah idk fam");
}
throw std::runtime_error{"we have no ipv4 loopback interface for some ungodly reason"};
return ifname;
}
} // namespace net
bool
GetBestNetIF(std::string& ifname, int af)
{
bool found = false;
IterAllNetworkInterfaces([&](ifaddrs* i) {
if (found)
return;
if (i->ifa_addr)
{
if (i->ifa_addr->sa_family == af)
std::optional<std::string>
GetBestNetIF(int af) const override
{
std::optional<std::string> found;
iter_all([this, &found, af](auto i) {
if (found)
return;
if (i and i->ifa_addr and i->ifa_addr->sa_family == af)
{
llarp::SockAddr a(*i->ifa_addr);
llarp::IpAddress ip(a);
if (!ip.isBogon())
if (not IsBogon(*i->ifa_addr))
{
ifname = i->ifa_name;
found = true;
found = i->ifa_name;
}
}
}
});
return found;
}
});
// TODO: ipv6?
std::optional<IPRange>
FindFreeRange()
{
std::list<IPRange> currentRanges;
IterAllNetworkInterfaces([&](ifaddrs* i) {
if (i && i->ifa_addr)
return found;
}
std::optional<IPRange>
FindFreeRange() const override
{
std::list<IPRange> currentRanges;
iter_all([&currentRanges](auto i) {
if (i and i->ifa_addr and i->ifa_addr->sa_family == AF_INET)
{
ipv4addr_t addr{reinterpret_cast<sockaddr_in*>(i->ifa_addr)->sin_addr.s_addr};
ipv4addr_t mask{reinterpret_cast<sockaddr_in*>(i->ifa_netmask)->sin_addr.s_addr};
currentRanges.emplace_back(IPRange::FromIPv4(addr, mask));
}
});
auto ownsRange = [&currentRanges](const IPRange& range) -> bool {
for (const auto& ownRange : currentRanges)
{
if (ownRange * range)
return true;
}
return false;
};
// generate possible ranges to in order of attempts
std::list<IPRange> possibleRanges;
for (byte_t oct = 16; oct < 32; ++oct)
{
const auto fam = i->ifa_addr->sa_family;
if (fam != AF_INET)
return;
auto* addr = (sockaddr_in*)i->ifa_addr;
auto* mask = (sockaddr_in*)i->ifa_netmask;
nuint32_t ifaddr{addr->sin_addr.s_addr};
nuint32_t ifmask{mask->sin_addr.s_addr};
#ifdef _WIN32
// do not delete, otherwise GCC will do horrible things to this lambda
LogDebug("found ", ifaddr, " with mask ", ifmask);
#endif
if (addr->sin_addr.s_addr)
// skip unconfig'd adapters (windows passes these through the unix-y
// wrapper)
currentRanges.emplace_back(
IPRange{net::ExpandV4(xntohl(ifaddr)), net::ExpandV4(xntohl(ifmask))});
possibleRanges.emplace_back(IPRange::FromIPv4(172, oct, 0, 1, 16));
}
});
auto ownsRange = [&currentRanges](const IPRange& range) -> bool {
for (const auto& ownRange : currentRanges)
for (byte_t oct = 0; oct < 255; ++oct)
{
if (ownRange * range)
return true;
possibleRanges.emplace_back(IPRange::FromIPv4(10, oct, 0, 1, 16));
}
return false;
};
// generate possible ranges to in order of attempts
std::list<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 (byte_t oct = 0; oct < 255; ++oct)
{
possibleRanges.emplace_back(IPRange::FromIPv4(192, 168, oct, 1, 24));
}
// for each possible range pick the first one we don't own
for (const auto& range : possibleRanges)
{
if (not ownsRange(range))
return range;
}
return std::nullopt;
}
// for each possible range pick the first one we don't own
for (const auto& range : possibleRanges)
std::optional<int> GetInterfaceIndex(ipaddr_t) const override
{
if (not ownsRange(range))
return range;
// todo: implement me
return std::nullopt;
}
return std::nullopt;
}
std::optional<std::string>
FindFreeTun()
{
int num = 0;
while (num < 255)
std::optional<std::string>
FindFreeTun() const override
{
std::string iftestname = fmt::format("lokitun{}", num);
bool found = llarp_getifaddr(iftestname.c_str(), AF_INET, nullptr);
if (!found)
int num = 0;
while (num < 255)
{
return iftestname;
std::string ifname = fmt::format("lokitun{}", num);
if (GetInterfaceAddr(ifname, AF_INET))
return ifname;
num++;
}
num++;
}
return std::nullopt;
}
std::optional<SockAddr>
GetInterfaceAddr(const std::string& ifname, int af)
{
sockaddr_storage s;
sockaddr* sptr = (sockaddr*)&s;
sptr->sa_family = af;
if (!llarp_getifaddr(ifname.c_str(), af, sptr))
return std::nullopt;
return SockAddr{*sptr};
}
std::optional<huint128_t>
GetInterfaceIPv6Address(std::string ifname)
{
sockaddr_storage s;
sockaddr* sptr = (sockaddr*)&s;
sptr->sa_family = AF_INET6;
if (!llarp_getifaddr(ifname.c_str(), AF_INET6, sptr))
return std::nullopt;
llarp::SockAddr addr{*sptr};
return addr.asIPv6();
}
}
namespace net
{
namespace
std::optional<SockAddr>
GetInterfaceAddr(std::string_view ifname, int af) const override
{
SockAddr
All(int af)
{
if (af == AF_INET)
{
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(0);
return SockAddr{addr};
}
if (af == AF_INET6)
{
sockaddr_in6 addr6{};
addr6.sin6_family = AF_INET6;
addr6.sin6_port = htons(0);
addr6.sin6_addr = IN6ADDR_ANY_INIT;
return SockAddr{addr6};
}
throw std::invalid_argument{fmt::format("{} is not a valid address family", af)};
}
} // namespace
std::optional<SockAddr> addr;
iter_all([&addr, af, ifname = std::string{ifname}](auto i) {
if (addr)
return;
if (i and i->ifa_addr and i->ifa_addr->sa_family == af and i->ifa_name == ifname)
addr = llarp::SockAddr{*i->ifa_addr};
});
return addr;
}
std::optional<SockAddr>
AllInterfaces(SockAddr pub)
AllInterfaces(SockAddr fallback) const override
{
std::optional<SockAddr> found;
IterAllNetworkInterfaces([pub, &found](auto* ifa) {
iter_all([fallback, &found](auto i) {
if (found)
return;
if (auto ifa_addr = ifa->ifa_addr)
{
if (ifa_addr->sa_family != pub.Family())
return;
SockAddr addr{*ifa->ifa_addr};
if (addr == pub)
found = addr;
}
if (i == nullptr or i->ifa_addr == nullptr)
return;
if (i->ifa_addr->sa_family != fallback.Family())
return;
SockAddr addr{*i->ifa_addr};
if (addr == fallback)
found = addr;
});
// 0.0.0.0 is used in our compat shim as our public ip so we check for that special case
const auto zero = IPRange::FromIPv4(0, 0, 0, 0, 8);
// when we cannot find an address but we are looking for 0.0.0.0 just default to the old style
if (not found and (pub.isIPv4() and zero.Contains(pub.asIPv4())))
found = All(pub.Family());
// when we cannot find an address but we are looking for 0.0.0.0 just default to the old
// style
if (not found and (fallback.isIPv4() and zero.Contains(fallback.asIPv4())))
found = Wildcard(fallback.Family());
return found;
}
bool
HasInterfaceAddress(ipaddr_t ip) const override
{
bool found{false};
iter_all([&found, ip](auto i) {
if (found)
return;
if (not(i and i->ifa_addr))
return;
const SockAddr addr{*i->ifa_addr};
found = addr.getIP() == ip;
});
return found;
}
} // namespace net
};
#endif
const Platform_Impl g_plat{};
const Platform*
Platform::Default_ptr()
{
return &g_plat;
}
} // namespace llarp::net
namespace llarp
{
#if !defined(TESTNET)
static constexpr std::array bogonRanges_v6 = {
// zero
@ -721,20 +575,5 @@ namespace llarp
return false;
}
#endif
bool
HasInterfaceAddress(std::variant<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

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

@ -48,4 +48,14 @@ namespace llarp
return false;
return true;
}
namespace net
{
inline auto
ipaddr_netmask_bits(uint32_t bits, int af)
{
if (af == AF_INET6)
return netmask_ipv6_bits(bits);
return ExpandV4(netmask_ipv4_bits(bits));
};
} // namespace net
} // namespace llarp

@ -6,41 +6,44 @@
namespace llarp
{
huint16_t
ToHost(nuint16_t n)
namespace net
{
return xntohs(n);
}
huint16_t
ToHost(port_t x)
{
return huint16_t{oxenc::big_to_host(x.n)};
}
huint32_t
ToHost(nuint32_t n)
{
return xntohl(n);
}
huint32_t
ToHost(ipv4addr_t x)
{
return huint32_t{oxenc::big_to_host(x.n)};
}
huint128_t
ToHost(nuint128_t n)
{
return {ntoh128(n.n)};
}
huint128_t
ToHost(ipv6addr_t x)
{
return {ntoh128(x.n)};
}
nuint16_t
ToNet(huint16_t h)
{
return xhtons(h);
}
port_t
ToNet(huint16_t x)
{
return port_t{oxenc::host_to_big(x.h)};
}
nuint32_t
ToNet(huint32_t h)
{
return xhtonl(h);
}
ipv4addr_t
ToNet(huint32_t x)
{
return ipv4addr_t{oxenc::host_to_big(x.h)};
}
nuint128_t
ToNet(huint128_t h)
{
return {hton128(h.h)};
}
ipv6addr_t
ToNet(huint128_t x)
{
return ipv6addr_t{hton128(x.h)};
}
} // namespace net
template <>
void
@ -128,6 +131,17 @@ namespace llarp
return "";
return tmp;
}
template <>
std::string
nuint128_t::ToString() const
{
char tmp[INET6_ADDRSTRLEN] = {0};
if (!inet_ntop(AF_INET6, (void*)&n, tmp, sizeof(tmp)))
return "";
return tmp;
}
template <>
std::string
huint16_t::ToString() const

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

@ -101,6 +101,15 @@ namespace llarp
void
setIPv4(uint8_t a, uint8_t b, uint8_t c, uint8_t d);
inline void
setIP(std::variant<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
setIPv4(nuint32_t ip);
@ -143,6 +152,7 @@ namespace llarp
getIPv6() const;
nuint32_t
getIPv4() const;
std::variant<nuint32_t, nuint128_t>
getIP() const;

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

@ -37,6 +37,8 @@
#include <systemd/sd-daemon.h>
#endif
#include <llarp/constants/platform.hpp>
#include <oxenmq/oxenmq.h>
static constexpr std::chrono::milliseconds ROUTER_TICK_INTERVAL = 250ms;
@ -580,8 +582,6 @@ namespace llarp
_rc.netID = llarp::NetID();
}
// IWP config
m_OutboundPort = conf.links.m_OutboundLink.port;
// Router config
_rc.SetNick(conf.router.m_nickname);
_outboundSessionMaker.maxConnectedRouters = conf.router.m_maxConnectedRouters;
@ -592,8 +592,20 @@ namespace llarp
transport_keyfile = m_keyManager->m_transportKeyPath;
ident_keyfile = m_keyManager->m_idKeyPath;
if (auto maybe = conf.router.m_PublicIP)
_ourAddress = SockAddr{*maybe, conf.router.m_PublicPort};
if (auto maybe_ip = conf.links.PublicAddress)
_ourAddress = var::visit([](auto&& ip) { return SockAddr{ip}; }, *maybe_ip);
else if (auto maybe_ip = conf.router.PublicIP)
_ourAddress = var::visit([](auto&& ip) { return SockAddr{ip}; }, *maybe_ip);
if (_ourAddress)
{
if (auto maybe_port = conf.links.PublicPort)
_ourAddress->setPort(*maybe_port);
else if (auto maybe_port = conf.router.PublicPort)
_ourAddress->setPort(*maybe_port);
else
throw std::runtime_error{"public ip provided without public port"};
}
RouterContact::BlockBogons = conf.router.m_blockBogons;
@ -720,48 +732,10 @@ namespace llarp
whitelistRouters,
m_isServiceNode);
std::vector<LinksConfig::LinkInfo> inboundLinks = conf.links.m_InboundLinks;
if (inboundLinks.empty() and m_isServiceNode)
{
if (_ourAddress)
{
inboundLinks.push_back(LinksConfig::LinkInfo{
_ourAddress->hostString(), _ourAddress->Family(), _ourAddress->getPort()});
}
else
throw std::runtime_error{
"service node enabled but could not find a public IP to bind to; you need to set the "
"public-ip= and public-port= options"};
}
// create inbound links, if we are a service node
for (const LinksConfig::LinkInfo& serverConfig : inboundLinks)
{
auto server = iwp::NewInboundLink(
m_keyManager,
loop(),
util::memFn(&AbstractRouter::rc, this),
util::memFn(&AbstractRouter::HandleRecvLinkMessageBuffer, this),
util::memFn(&AbstractRouter::Sign, this),
nullptr,
util::memFn(&Router::ConnectionEstablished, this),
util::memFn(&AbstractRouter::CheckRenegotiateValid, this),
util::memFn(&Router::ConnectionTimedOut, this),
util::memFn(&AbstractRouter::SessionClosed, this),
util::memFn(&AbstractRouter::TriggerPump, this),
util::memFn(&AbstractRouter::QueueWork, this));
const std::string& key = serverConfig.m_interface;
int af = serverConfig.addressFamily;
uint16_t port = serverConfig.port;
if (!server->Configure(this, key, af, port))
{
throw std::runtime_error{
fmt::format("failed to bind inbound link on {} port {}", key, port)};
}
_linkManager.AddLink(std::move(server), true);
}
// inbound links
InitInboundLinks();
// outbound links
InitOutboundLinks();
// profiling
_profilesFile = conf.router.m_dataDir / "profiles.dat";
@ -1234,13 +1208,20 @@ namespace llarp
AddressInfo ai;
if (link->GetOurAddressInfo(ai))
{
// override ip and port
// override ip and port as needed
if (_ourAddress)
{
if (not Net().IsBogon(ai.ip))
throw std::runtime_error{"cannot override public ip, it is already set"};
ai.fromSockAddr(*_ourAddress);
}
if (RouterContact::BlockBogons && IsBogon(ai.ip))
return;
throw std::runtime_error{var::visit(
[](auto&& ip) {
return "cannot use " + ip.ToString()
+ " as a public ip as it is in a non routable ip range";
},
ai.IP())};
LogInfo("adding address: ", ai);
_rc.addrs.push_back(ai);
}
@ -1268,12 +1249,6 @@ namespace llarp
return false;
}
if (not InitOutboundLinks())
{
LogError("failed to init outbound links");
return false;
}
if (IsServiceNode())
{
if (!SaveRC())
@ -1603,47 +1578,116 @@ namespace llarp
return found;
}
bool
Router::InitOutboundLinks()
void
Router::InitInboundLinks()
{
auto link = iwp::NewOutboundLink(
m_keyManager,
loop(),
util::memFn(&AbstractRouter::rc, this),
util::memFn(&AbstractRouter::HandleRecvLinkMessageBuffer, this),
util::memFn(&AbstractRouter::Sign, this),
[&](llarp::RouterContact rc) {
if (IsServiceNode())
return;
llarp::LogTrace(
"Before connect, outbound link adding route to (",
rc.addrs[0].toIpAddress().toIP(),
") via gateway.");
m_RoutePoker.AddRoute(rc.addrs[0].toIpAddress().toIP());
},
util::memFn(&Router::ConnectionEstablished, this),
util::memFn(&AbstractRouter::CheckRenegotiateValid, this),
util::memFn(&Router::ConnectionTimedOut, this),
util::memFn(&AbstractRouter::SessionClosed, this),
util::memFn(&AbstractRouter::TriggerPump, this),
util::memFn(&AbstractRouter::QueueWork, this));
auto addrs = m_Config->links.InboundListenAddrs;
if (m_isServiceNode and addrs.empty())
{
LogInfo("Inferring Public Address");
auto maybe_port = m_Config->links.PublicPort;
if (m_Config->router.PublicPort and not maybe_port)
maybe_port = m_Config->router.PublicPort;
if (not maybe_port)
maybe_port = net::port_t::from_host(constants::DefaultInboundIWPPort);
if (!link)
throw std::runtime_error("NewOutboundLink() failed to provide a link");
if (auto maybe_addr = Net().MaybeInferPublicAddr(*maybe_port))
{
LogInfo("Public Address looks to be ", *maybe_addr);
addrs.emplace_back(std::move(*maybe_addr));
}
}
if (m_isServiceNode and addrs.empty())
throw std::runtime_error{"we are a service node and we have no inbound links configured"};
for (const auto af : {AF_INET, AF_INET6})
// create inbound links, if we are a service node
for (auto bind_addr : addrs)
{
if (not link->Configure(this, "*", af, m_OutboundPort))
continue;
if (bind_addr.getPort() == 0)
throw std::invalid_argument{"inbound link cannot use port 0"};
if (Net().IsWildcardAddress(bind_addr.getIP()))
{
if (auto maybe_ip = OurPublicIP())
bind_addr.setIP(*maybe_ip);
else
throw std::runtime_error{"no public ip provided for inbound socket"};
}
auto server = iwp::NewInboundLink(
m_keyManager,
loop(),
util::memFn(&AbstractRouter::rc, this),
util::memFn(&AbstractRouter::HandleRecvLinkMessageBuffer, this),
util::memFn(&AbstractRouter::Sign, this),
nullptr,
util::memFn(&Router::ConnectionEstablished, this),
util::memFn(&AbstractRouter::CheckRenegotiateValid, this),
util::memFn(&Router::ConnectionTimedOut, this),
util::memFn(&AbstractRouter::SessionClosed, this),
util::memFn(&AbstractRouter::TriggerPump, this),
util::memFn(&AbstractRouter::QueueWork, this));
server->Bind(this, bind_addr);
_linkManager.AddLink(std::move(server), true);
}
}
void
Router::InitOutboundLinks()
{
auto addrs = m_Config->links.OutboundLinks;
if (addrs.empty())
addrs.emplace_back(Net().Wildcard());
for (auto bind_addr : addrs)
{
auto link = iwp::NewOutboundLink(
m_keyManager,
loop(),
util::memFn(&AbstractRouter::rc, this),
util::memFn(&AbstractRouter::HandleRecvLinkMessageBuffer, this),
util::memFn(&AbstractRouter::Sign, this),
[this](llarp::RouterContact rc) {
if (IsServiceNode())
return;
llarp::LogTrace(
"Before connect, outbound link adding route to (",
rc.addrs[0].toIpAddress().toIP(),
") via gateway.");
m_RoutePoker.AddRoute(rc.addrs[0].toIpAddress().toIP());
},
util::memFn(&Router::ConnectionEstablished, this),
util::memFn(&AbstractRouter::CheckRenegotiateValid, this),
util::memFn(&Router::ConnectionTimedOut, this),
util::memFn(&AbstractRouter::SessionClosed, this),
util::memFn(&AbstractRouter::TriggerPump, this),
util::memFn(&AbstractRouter::QueueWork, this));
const auto& net = Net();
// try to use a public address if we have one set on our inbound links
_linkManager.ForEachInboundLink([&bind_addr, &net](const auto& link) {
if (not net.IsBogon(bind_addr))
return;
if (auto addr = link->LocalSocketAddr(); not net.IsBogon(addr))
bind_addr.setIP(addr.getIP());
});
link->Bind(this, bind_addr);
if constexpr (llarp::platform::is_android)
m_OutboundUDPSocket = link->GetUDPFD().value_or(-1);
#if defined(ANDROID)
m_OutboundUDPSocket = link->GetUDPFD().value_or(-1);
#endif
_linkManager.AddLink(std::move(link), false);
return true;
}
throw std::runtime_error{
fmt::format("Failed to init AF_INET and AF_INET6 on port {}", m_OutboundPort)};
}
const llarp::net::Platform&
Router::Net() const
{
return *llarp::net::Platform::Default_ptr();
}
void

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

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

@ -16,6 +16,12 @@
namespace llarp::vpn
{
const llarp::net::Platform*
IRouteManager::Net_ptr() const
{
return llarp::net::Platform::Default_ptr();
}
std::shared_ptr<Platform>
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,22 +70,12 @@ namespace llarp
.def(py::init<>())
.def(
"setOutboundLink",
[](LinksConfig& self, std::string _interface, int family, uint16_t port) {
LinksConfig::LinkInfo info;
info.m_interface = std::move(_interface);
info.addressFamily = family;
info.port = port;
self.m_OutboundLink = std::move(info);
[](LinksConfig& self, std::string addr) {
self.OutboundLinks.emplace_back(std::move(addr));
})
.def(
"addInboundLink",
[](LinksConfig& self, std::string _interface, int family, uint16_t port) {
LinksConfig::LinkInfo info;
info.m_interface = std::move(_interface);
info.addressFamily = family;
info.port = port;
self.m_InboundLinks.push_back(info);
});
.def("addInboundLink", [](LinksConfig& self, std::string addr) {
self.InboundListenAddrs.emplace_back(std::move(addr));
});
py::class_<ApiConfig>(mod, "ApiConfig")
.def(py::init<>())

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

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

@ -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