* add mockable network functions

* add unit tests with ability to pretend to be different network setups
dev
Jeff 3 months ago
parent 12653d4ac2
commit 68148e098f
Signed by: jeff
GPG Key ID: 025C02EE3A092F2D
  1. 2
      CMakeLists.txt
  2. 4
      jni/lokinet_daemon.cpp
  3. 2
      llarp/apple/context_wrapper.cpp
  4. 340
      llarp/config/config.cpp
  5. 46
      llarp/config/config.hpp
  6. 18
      llarp/config/definition.cpp
  7. 33
      llarp/config/definition.hpp
  8. 5
      llarp/constants/link_layer.hpp
  9. 13
      llarp/ev/ev_libuv.hpp
  10. 9
      llarp/ev/vpn.hpp
  11. 4
      llarp/handlers/exit.cpp
  12. 6
      llarp/handlers/tun.cpp
  13. 75
      llarp/link/server.cpp
  14. 11
      llarp/link/server.hpp
  15. 16
      llarp/net/ip_range.hpp
  16. 807
      llarp/net/net.cpp
  17. 129
      llarp/net/net.hpp
  18. 10
      llarp/net/net_bits.hpp
  19. 72
      llarp/net/net_int.cpp
  20. 102
      llarp/net/net_int.hpp
  21. 10
      llarp/net/sock_addr.hpp
  22. 8
      llarp/router/abstractrouter.hpp
  23. 220
      llarp/router/router.cpp
  24. 17
      llarp/router/router.hpp
  25. 11
      llarp/vpn/linux.hpp
  26. 6
      llarp/vpn/platform.cpp
  27. 31
      llarp/win32/exception.hpp
  28. 20
      pybind/llarp/config.cpp
  29. 4
      readme.md
  30. 4
      test/CMakeLists.txt
  31. 286
      test/config/test_llarp_config_values.cpp
  32. 40
      test/mocks/mock_context.hpp
  33. 192
      test/mocks/mock_network.hpp
  34. 25
      test/mocks/mock_router.hpp
  35. 93
      test/mocks/mock_vpn.hpp
  36. 77
      test/regress/2020-06-08-key-backup-bug.cpp

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