formatting

pull/2232/head
dr7ana 4 months ago
parent 861d573e6a
commit 5f8e1ada15

@ -10,11 +10,20 @@ AllowShortCaseLabelsOnASingleLine: 'false'
AllowShortFunctionsOnASingleLine: None
AllowShortIfStatementsOnASingleLine: 'false'
AllowShortLoopsOnASingleLine: 'false'
AlwaysBreakAfterDefinitionReturnType: All
AlwaysBreakAfterReturnType: All
AlwaysBreakTemplateDeclarations: 'true'
BreakBeforeBinaryOperators: NonAssignment
BreakBeforeBraces: Custom
ColumnLimit: 100
KeepEmptyLinesAtTheStartOfBlocks: 'false'
NamespaceIndentation: All
PenaltyBreakString: '3'
SortIncludes: CaseInsensitive
SpaceBeforeParens: ControlStatements
SpacesInAngles: 'false'
SpacesInContainerLiterals: 'false'
SpacesInParentheses: 'false'
SpacesInSquareBrackets: 'false'
Standard: Cpp11
UseTab: Never
# bracing
BraceWrapping:
AfterCaseLabel: true
AfterClass: true
@ -31,27 +40,27 @@ BraceWrapping:
SplitEmptyFunction: false
SplitEmptyRecord: false
SplitEmptyNamespace: false
# breaking
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakTemplateDeclarations: 'true'
BreakBeforeBinaryOperators: NonAssignment
BreakBeforeBraces: Custom
BreakBeforeTernaryOperators: 'true'
BreakConstructorInitializersBeforeComma: 'true'
ColumnLimit: 100
Cpp11BracedListStyle: 'true'
KeepEmptyLinesAtTheStartOfBlocks: 'false'
NamespaceIndentation: All
PenaltyBreakString: '3'
SortIncludes: CaseInsensitive
SpaceBeforeParens: ControlStatements
SpacesInAngles: 'false'
SpacesInContainerLiterals: 'false'
SpacesInParentheses: 'false'
SpacesInSquareBrackets: 'false'
Standard: Cpp11
UseTab: Never
BreakConstructorInitializers: BeforeColon
# indent width
IndentWidth: 4
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
# treat pointers and reference declarations as if part of the type
DerivePointerAlignment: false
PointerAlignment: Left
# when wrapping function calls/declarations, force each parameter to have its own line
PackConstructorInitializers: NextLine
BinPackParameters: 'false'
BinPackArguments: 'false'

@ -14,100 +14,97 @@
namespace
{
int
fail(std::string msg)
{
std::cout << msg << std::endl;
return 1;
}
int
print_help(std::string exe)
{
std::cout << R"(Lokinet bootstrap.signed fetchy program thing
int fail(std::string msg)
{
std::cout << msg << std::endl;
return 1;
}
int print_help(std::string exe)
{
std::cout << R"(Lokinet bootstrap.signed fetchy program thing
Downloads the initial bootstrap.signed for lokinet into a local file from a
default or user defined server via the reachable network.
Usage: )" << exe
<< R"( [bootstrap_url [output_file]]
Usage: )" << exe << R"( [bootstrap_url [output_file]]
bootstrap_url can be specified as a full URL, or a special named value
("mainnet" or "testnet") to download from the pre-defined mainnet or testnet
bootstrap URLs.
)";
return 0;
}
return 0;
}
} // namespace
int
main(int argc, char* argv[])
int main(int argc, char* argv[])
{
const std::unordered_map<std::string, std::string> bootstrap_urls = {
{"lokinet", "https://seed.lokinet.org/lokinet.signed"},
{"testnet", "https://seed.lokinet.org/testnet.signed"}};
const std::unordered_map<std::string, std::string> bootstrap_urls = {
{"lokinet", "https://seed.lokinet.org/lokinet.signed"},
{"testnet", "https://seed.lokinet.org/testnet.signed"}};
std::string bootstrap_url = bootstrap_urls.at("lokinet");
fs::path outputfile{llarp::GetDefaultBootstrap()};
std::string bootstrap_url = bootstrap_urls.at("lokinet");
fs::path outputfile{llarp::GetDefaultBootstrap()};
const std::unordered_set<std::string> help_args = {"-h", "--help"};
const std::unordered_set<std::string> help_args = {"-h", "--help"};
for (int idx = 1; idx < argc; idx++)
{
const std::string arg{argv[idx]};
if (help_args.count(arg))
return print_help(argv[0]);
}
for (int idx = 1; idx < argc; idx++)
{
const std::string arg{argv[idx]};
if (help_args.count(arg))
return print_help(argv[0]);
}
if (argc > 1)
{
if (auto itr = bootstrap_urls.find(argv[1]); itr != bootstrap_urls.end())
if (argc > 1)
{
bootstrap_url = itr->second;
if (auto itr = bootstrap_urls.find(argv[1]); itr != bootstrap_urls.end())
{
bootstrap_url = itr->second;
}
else
{
bootstrap_url = argv[1];
}
}
else
if (argc > 2)
{
bootstrap_url = argv[1];
outputfile = fs::path{argv[2]};
}
}
if (argc > 2)
{
outputfile = fs::path{argv[2]};
}
std::cout << "fetching " << bootstrap_url << std::endl;
cpr::Response resp =
std::cout << "fetching " << bootstrap_url << std::endl;
cpr::Response resp =
#ifdef _WIN32
cpr::Get(
cpr::Url{bootstrap_url}, cpr::Header{{"User-Agent", std::string{llarp::VERSION_FULL}}});
cpr::Get(
cpr::Url{bootstrap_url}, cpr::Header{{"User-Agent", std::string{llarp::VERSION_FULL}}});
#else
cpr::Get(
cpr::Url{bootstrap_url},
cpr::Header{{"User-Agent", std::string{llarp::LOKINET_VERSION_FULL}}},
cpr::Ssl(cpr::ssl::CaPath{X509_get_default_cert_dir()}));
cpr::Get(
cpr::Url{bootstrap_url},
cpr::Header{{"User-Agent", std::string{llarp::LOKINET_VERSION_FULL}}},
cpr::Ssl(cpr::ssl::CaPath{X509_get_default_cert_dir()}));
#endif
if (resp.status_code != 200)
{
return fail("failed to fetch '" + bootstrap_url + "' HTTP " + std::to_string(resp.status_code));
}
const auto& data = resp.text;
if (data[0] == 'l' or data[0] == 'd')
{
try
if (resp.status_code != 200)
{
std::cout << "writing bootstrap file to: " << outputfile << std::endl;
fs::ofstream ofs{outputfile, std::ios::binary};
ofs.exceptions(fs::ofstream::failbit);
ofs << data;
return 0;
return fail(
"failed to fetch '" + bootstrap_url + "' HTTP " + std::to_string(resp.status_code));
}
catch (std::exception& ex)
const auto& data = resp.text;
if (data[0] == 'l' or data[0] == 'd')
{
return fail(std::string{"failed to write bootstrap file: "} + ex.what());
try
{
std::cout << "writing bootstrap file to: " << outputfile << std::endl;
fs::ofstream ofs{outputfile, std::ios::binary};
ofs.exceptions(fs::ofstream::failbit);
ofs << data;
return 0;
}
catch (std::exception& ex)
{
return fail(std::string{"failed to write bootstrap file: "} + ex.what());
}
}
}
return fail("got invalid bootstrap file content");
return fail("got invalid bootstrap file content");
}

@ -19,276 +19,279 @@
/// do a oxenmq request on an omq instance blocking style
/// returns a json object parsed from the result
std::optional<nlohmann::json>
OMQ_Request(
std::optional<nlohmann::json> OMQ_Request(
oxenmq::OxenMQ& omq,
const oxenmq::ConnectionID& id,
std::string_view method,
std::optional<nlohmann::json> args = std::nullopt)
{
std::promise<std::optional<std::string>> result_promise;
std::promise<std::optional<std::string>> result_promise;
auto handleRequest = [&result_promise](bool success, std::vector<std::string> result) {
if ((not success) or result.empty())
auto handleRequest = [&result_promise](bool success, std::vector<std::string> result) {
if ((not success) or result.empty())
{
result_promise.set_value(std::nullopt);
return;
}
result_promise.set_value(result[0]);
};
if (args.has_value())
{
omq.request(id, method, handleRequest, args->dump());
}
else
{
result_promise.set_value(std::nullopt);
return;
omq.request(id, method, handleRequest);
}
result_promise.set_value(result[0]);
};
if (args.has_value())
{
omq.request(id, method, handleRequest, args->dump());
}
else
{
omq.request(id, method, handleRequest);
}
auto ftr = result_promise.get_future();
const auto str = ftr.get();
if (str.has_value())
return nlohmann::json::parse(*str);
return std::nullopt;
auto ftr = result_promise.get_future();
const auto str = ftr.get();
if (str.has_value())
return nlohmann::json::parse(*str);
return std::nullopt;
}
namespace
{
struct command_line_options
{
// bool options
bool verbose = false;
bool help = false;
bool vpnUp = false;
bool vpnDown = false;
bool swap = false;
bool printStatus = false;
bool killDaemon = false;
// string options
std::string exitAddress;
std::string rpc;
std::string endpoint = "default";
std::string token;
std::optional<std::string> range;
std::vector<std::string> swapExits;
// oxenmq
oxenmq::address rpcURL{};
oxenmq::LogLevel logLevel = oxenmq::LogLevel::warn;
};
// Takes a code, prints a message, and returns the code. Intended use is:
// return exit_error(1, "blah: {}", 42);
// from within main().
template <typename... T>
[[nodiscard]] int
exit_error(int code, const std::string& format, T&&... args)
{
fmt::print(format, std::forward<T>(args)...);
fmt::print("\n");
return code;
}
// Same as above, but with code omitted (uses exit code 1)
template <typename... T>
[[nodiscard]] int
exit_error(const std::string& format, T&&... args)
{
return exit_error(1, format, std::forward<T>(args)...);
}
struct command_line_options
{
// bool options
bool verbose = false;
bool help = false;
bool vpnUp = false;
bool vpnDown = false;
bool swap = false;
bool printStatus = false;
bool killDaemon = false;
// string options
std::string exitAddress;
std::string rpc;
std::string endpoint = "default";
std::string token;
std::optional<std::string> range;
std::vector<std::string> swapExits;
// oxenmq
oxenmq::address rpcURL{};
oxenmq::LogLevel logLevel = oxenmq::LogLevel::warn;
};
// Takes a code, prints a message, and returns the code. Intended use is:
// return exit_error(1, "blah: {}", 42);
// from within main().
template <typename... T>
[[nodiscard]] int exit_error(int code, const std::string& format, T&&... args)
{
fmt::print(format, std::forward<T>(args)...);
fmt::print("\n");
return code;
}
// Same as above, but with code omitted (uses exit code 1)
template <typename... T>
[[nodiscard]] int exit_error(const std::string& format, T&&... args)
{
return exit_error(1, format, std::forward<T>(args)...);
}
} // namespace
int
main(int argc, char* argv[])
int main(int argc, char* argv[])
{
CLI::App cli{"lokiNET vpn control utility", "lokinet-vpn"};
command_line_options options{};
// flags: boolean values in command_line_options struct
cli.add_flag("-v,--verbose", options.verbose, "Verbose");
cli.add_flag("--add,--up", options.vpnUp, "Map VPN connection to exit node [--up is deprecated]");
cli.add_flag(
"--remove,--down",
options.vpnDown,
"Unmap VPN connection to exit node [--down is deprecated]");
cli.add_flag("--status", options.printStatus, "Print VPN status and exit");
cli.add_flag("-k,--kill", options.killDaemon, "Kill lokinet daemon");
// options: string values in command_line_options struct
cli.add_option("--exit", options.exitAddress, "Specify exit node address")->capture_default_str();
cli.add_option("--endpoint", options.endpoint, "Endpoint to use")->capture_default_str();
cli.add_option("--token,--auth", options.token, "Exit auth token to use")->capture_default_str();
cli.add_option("--range", options.range, "IP range to map exit to")->capture_default_str();
cli.add_option(
"--swap", options.swapExits, "Exit addresses to swap mapped connection to [old] [new]")
->expected(2)
->capture_default_str();
// options: oxenmq values in command_line_options struct
cli.add_option("--rpc", options.rpc, "Specify RPC URL for lokinet")->capture_default_str();
cli.add_option(
"--log-level", options.logLevel, "Log verbosity level, see log levels for accepted values")
->type_name("LEVEL")
->capture_default_str();
try
{
cli.parse(argc, argv);
}
catch (const CLI::ParseError& e)
{
return cli.exit(e);
}
try
{
if (options.verbose)
options.logLevel = oxenmq::LogLevel::debug;
}
catch (const CLI::OptionNotFound& e)
{
cli.exit(e);
}
catch (const CLI::Error& e)
{
cli.exit(e);
};
int numCommands = options.vpnUp + options.vpnDown + options.printStatus + options.killDaemon
+ (not options.swapExits.empty());
switch (numCommands)
{
case 0:
return exit_error(3, "One of --add/--remove/--swap/--status/--kill must be specified");
case 1:
break;
default:
return exit_error(3, "Only one of --add/--remove/--swap/--status/--kill may be specified");
}
if (options.vpnUp and options.exitAddress.empty())
return exit_error("No exit address provided, must specify --exit <address>");
oxenmq::OxenMQ omq{
[](oxenmq::LogLevel lvl, const char* file, int line, std::string msg) {
std::cout << lvl << " [" << file << ":" << line << "] " << msg << std::endl;
},
options.logLevel};
options.rpcURL = oxenmq::address{(options.rpc.empty()) ? "tcp://127.0.0.1:1190" : options.rpc};
omq.start();
std::promise<bool> connectPromise;
const auto connectionID = omq.connect_remote(
options.rpcURL,
[&connectPromise](auto) { connectPromise.set_value(true); },
[&connectPromise](auto, std::string_view msg) {
std::cout << "Failed to connect to lokinet RPC: " << msg << std::endl;
connectPromise.set_value(false);
});
auto ftr = connectPromise.get_future();
if (not ftr.get())
return 1;
if (options.killDaemon)
{
auto maybe_halt = OMQ_Request(omq, connectionID, "llarp.halt");
if (not maybe_halt)
return exit_error("Call to llarp.halt failed");
if (auto err_it = maybe_halt->find("error");
err_it != maybe_halt->end() and not err_it.value().is_null())
CLI::App cli{"lokiNET vpn control utility", "lokinet-vpn"};
command_line_options options{};
// flags: boolean values in command_line_options struct
cli.add_flag("-v,--verbose", options.verbose, "Verbose");
cli.add_flag(
"--add,--up", options.vpnUp, "Map VPN connection to exit node [--up is deprecated]");
cli.add_flag(
"--remove,--down",
options.vpnDown,
"Unmap VPN connection to exit node [--down is deprecated]");
cli.add_flag("--status", options.printStatus, "Print VPN status and exit");
cli.add_flag("-k,--kill", options.killDaemon, "Kill lokinet daemon");
// options: string values in command_line_options struct
cli.add_option("--exit", options.exitAddress, "Specify exit node address")
->capture_default_str();
cli.add_option("--endpoint", options.endpoint, "Endpoint to use")->capture_default_str();
cli.add_option("--token,--auth", options.token, "Exit auth token to use")
->capture_default_str();
cli.add_option("--range", options.range, "IP range to map exit to")->capture_default_str();
cli.add_option(
"--swap", options.swapExits, "Exit addresses to swap mapped connection to [old] [new]")
->expected(2)
->capture_default_str();
// options: oxenmq values in command_line_options struct
cli.add_option("--rpc", options.rpc, "Specify RPC URL for lokinet")->capture_default_str();
cli.add_option(
"--log-level",
options.logLevel,
"Log verbosity level, see log levels for accepted values")
->type_name("LEVEL")
->capture_default_str();
try
{
return exit_error("{}", err_it.value().dump());
cli.parse(argc, argv);
}
catch (const CLI::ParseError& e)
{
return cli.exit(e);
}
}
if (options.printStatus)
{
const auto maybe_status = OMQ_Request(omq, connectionID, "llarp.status");
if (not maybe_status)
return exit_error("Call to llarp.status failed");
try
{
const auto& ep = maybe_status->at("result").at("services").at(options.endpoint).at("exitMap");
if (ep.empty())
{
std::cout << "No exits found" << std::endl;
}
else
{
for (const auto& [range, exit] : ep.items())
{
std::cout << range << " via " << exit.get<std::string>() << std::endl;
}
}
if (options.verbose)
options.logLevel = oxenmq::LogLevel::debug;
}
catch (std::exception& ex)
catch (const CLI::OptionNotFound& e)
{
return exit_error("Failed to parse result: {}", ex.what());
cli.exit(e);
}
return 0;
}
catch (const CLI::Error& e)
{
cli.exit(e);
};
int numCommands = options.vpnUp + options.vpnDown + options.printStatus + options.killDaemon
+ (not options.swapExits.empty());
switch (numCommands)
{
case 0:
return exit_error(3, "One of --add/--remove/--swap/--status/--kill must be specified");
case 1:
break;
default:
return exit_error(
3, "Only one of --add/--remove/--swap/--status/--kill may be specified");
}
if (options.vpnUp and options.exitAddress.empty())
return exit_error("No exit address provided, must specify --exit <address>");
oxenmq::OxenMQ omq{
[](oxenmq::LogLevel lvl, const char* file, int line, std::string msg) {
std::cout << lvl << " [" << file << ":" << line << "] " << msg << std::endl;
},
options.logLevel};
options.rpcURL = oxenmq::address{(options.rpc.empty()) ? "tcp://127.0.0.1:1190" : options.rpc};
if (not options.swapExits.empty())
{
nlohmann::json opts{{"exit_addresses", std::move(options.swapExits)}};
omq.start();
auto maybe_swap = OMQ_Request(omq, connectionID, "llarp.swap_exits", std::move(opts));
std::promise<bool> connectPromise;
if (not maybe_swap)
return exit_error("Failed to swap exit node connections");
const auto connectionID = omq.connect_remote(
options.rpcURL,
[&connectPromise](auto) { connectPromise.set_value(true); },
[&connectPromise](auto, std::string_view msg) {
std::cout << "Failed to connect to lokinet RPC: " << msg << std::endl;
connectPromise.set_value(false);
});
if (auto err_it = maybe_swap->find("error");
err_it != maybe_swap->end() and not err_it.value().is_null())
auto ftr = connectPromise.get_future();
if (not ftr.get())
return 1;
if (options.killDaemon)
{
return exit_error("{}", err_it.value().dump());
auto maybe_halt = OMQ_Request(omq, connectionID, "llarp.halt");
if (not maybe_halt)
return exit_error("Call to llarp.halt failed");
if (auto err_it = maybe_halt->find("error");
err_it != maybe_halt->end() and not err_it.value().is_null())
{
return exit_error("{}", err_it.value().dump());
}
}
}
if (options.vpnUp)
{
nlohmann::json opts{{"address", options.exitAddress}, {"token", options.token}};
if (options.range)
opts["ip_range"] = *options.range;
if (options.printStatus)
{
const auto maybe_status = OMQ_Request(omq, connectionID, "llarp.status");
auto maybe_result = OMQ_Request(omq, connectionID, "llarp.map_exit", std::move(opts));
if (not maybe_status)
return exit_error("Call to llarp.status failed");
if (not maybe_result)
return exit_error("Could not add exit");
try
{
const auto& ep =
maybe_status->at("result").at("services").at(options.endpoint).at("exitMap");
if (ep.empty())
{
std::cout << "No exits found" << std::endl;
}
else
{
for (const auto& [range, exit] : ep.items())
{
std::cout << range << " via " << exit.get<std::string>() << std::endl;
}
}
}
catch (std::exception& ex)
{
return exit_error("Failed to parse result: {}", ex.what());
}
return 0;
}
if (auto err_it = maybe_result->find("error");
err_it != maybe_result->end() and not err_it.value().is_null())
if (not options.swapExits.empty())
{
return exit_error("{}", err_it.value().dump());
nlohmann::json opts{{"exit_addresses", std::move(options.swapExits)}};
auto maybe_swap = OMQ_Request(omq, connectionID, "llarp.swap_exits", std::move(opts));
if (not maybe_swap)
return exit_error("Failed to swap exit node connections");
if (auto err_it = maybe_swap->find("error");
err_it != maybe_swap->end() and not err_it.value().is_null())
{
return exit_error("{}", err_it.value().dump());
}
}
}
if (options.vpnDown)
{
nlohmann::json opts{{"unmap_exit", true}};
if (options.range)
opts["ip_range"] = *options.range;
auto maybe_down = OMQ_Request(omq, connectionID, "llarp.unmap_exit", std::move(opts));
if (options.vpnUp)
{
nlohmann::json opts{{"address", options.exitAddress}, {"token", options.token}};
if (options.range)
opts["ip_range"] = *options.range;
auto maybe_result = OMQ_Request(omq, connectionID, "llarp.map_exit", std::move(opts));
if (not maybe_down)
return exit_error("Failed to unmap exit node connection");
if (not maybe_result)
return exit_error("Could not add exit");
if (auto err_it = maybe_down->find("error");
err_it != maybe_down->end() and not err_it.value().is_null())
if (auto err_it = maybe_result->find("error");
err_it != maybe_result->end() and not err_it.value().is_null())
{
return exit_error("{}", err_it.value().dump());
}
}
if (options.vpnDown)
{
return exit_error("{}", err_it.value().dump());
nlohmann::json opts{{"unmap_exit", true}};
if (options.range)
opts["ip_range"] = *options.range;
auto maybe_down = OMQ_Request(omq, connectionID, "llarp.unmap_exit", std::move(opts));
if (not maybe_down)
return exit_error("Failed to unmap exit node connection");
if (auto err_it = maybe_down->find("error");
err_it != maybe_down->end() and not err_it.value().is_null())
{
return exit_error("{}", err_it.value().dump());
}
}
}
return 0;
return 0;
}

File diff suppressed because it is too large Load Diff

@ -1 +1 @@
Subproject commit ec2e92cd4ace40708f595fb2c608a063f63f3cd2
Subproject commit 28ae47f5bf5f59cd71a82979bbc96660ca70e8c4

@ -10,106 +10,90 @@
namespace llarp
{
namespace vpn
{
class Platform;
}
class EventLoop;
struct Config;
struct RouterContact;
struct Config;
struct Router;
class NodeDB;
namespace thread
{
class ThreadPool;
}
struct RuntimeOptions
{
bool showBanner = true;
bool debug = false;
bool isSNode = false;
};
struct Context
{
std::shared_ptr<Router> router = nullptr;
std::shared_ptr<EventLoop> loop = nullptr;
std::shared_ptr<NodeDB> nodedb = nullptr;
Context();
virtual ~Context() = default;
void
Setup(const RuntimeOptions& opts);
int
Run(const RuntimeOptions& opts);
void
HandleSignal(int sig);
/// Configure given the specified config.
void
Configure(std::shared_ptr<Config> conf);
/// handle SIGHUP
void
Reload();
bool
IsUp() const;
bool
LooksAlive() const;
bool
IsStopping() const;
/// close async
void
CloseAsync();
/// wait until closed and done
void
Wait();
/// call a function in logic thread
/// return true if queued for calling
/// return false if not queued for calling
bool
CallSafe(std::function<void(void)> f);
/// Creates a router. Can be overridden to allow a different class of router
/// to be created instead. Defaults to llarp::Router.
virtual std::shared_ptr<Router>
makeRouter(const std::shared_ptr<EventLoop>& loop);
/// create the nodedb given our current configs
virtual std::shared_ptr<NodeDB>
makeNodeDB();
/// create the vpn platform for use in creating network interfaces
virtual std::shared_ptr<llarp::vpn::Platform>
makeVPNPlatform();
int androidFD = -1;
protected:
std::shared_ptr<Config> config = nullptr;
private:
void
SigINT();
void
Close();
std::unique_ptr<std::promise<void>> closeWaiter;
};
namespace vpn
{
class Platform;
}
class EventLoop;
struct Config;
struct RouterContact;
struct Config;
struct Router;
class NodeDB;
namespace thread
{
class ThreadPool;
}
struct RuntimeOptions
{
bool showBanner = true;
bool debug = false;
bool isSNode = false;
};
struct Context
{
std::shared_ptr<Router> router = nullptr;
std::shared_ptr<EventLoop> loop = nullptr;
std::shared_ptr<NodeDB> nodedb = nullptr;
Context();
virtual ~Context() = default;
void Setup(const RuntimeOptions& opts);
int Run(const RuntimeOptions& opts);
void HandleSignal(int sig);
/// Configure given the specified config.
void Configure(std::shared_ptr<Config> conf);
/// handle SIGHUP
void Reload();
bool IsUp() const;
bool LooksAlive() const;
bool IsStopping() const;
/// close async
void CloseAsync();
/// wait until closed and done
void Wait();
/// call a function in logic thread
/// return true if queued for calling
/// return false if not queued for calling
bool CallSafe(std::function<void(void)> f);
/// Creates a router. Can be overridden to allow a different class of router
/// to be created instead. Defaults to llarp::Router.
virtual std::shared_ptr<Router> makeRouter(const std::shared_ptr<EventLoop>& loop);
/// create the nodedb given our current configs
virtual std::shared_ptr<NodeDB> makeNodeDB();
/// create the vpn platform for use in creating network interfaces
virtual std::shared_ptr<llarp::vpn::Platform> makeVPNPlatform();
int androidFD = -1;
protected:
std::shared_ptr<Config> config = nullptr;
private:
void SigINT();
void Close();
std::unique_ptr<std::promise<void>> closeWaiter;
};
} // namespace llarp
#endif

@ -6,10 +6,9 @@ extern "C"
{
#endif
/// get a free()-able null terminated string that holds our .loki address
/// returns NULL if we dont have one right now
char* EXPORT
lokinet_address(struct lokinet_context*);
/// get a free()-able null terminated string that holds our .loki address
/// returns NULL if we dont have one right now
char* EXPORT lokinet_address(struct lokinet_context*);
#ifdef __cplusplus
}
#endif

@ -11,44 +11,37 @@ extern "C"
{
#endif
struct lokinet_context;
/// allocate a new lokinet context
struct lokinet_context* EXPORT
lokinet_context_new();
/// free a context allocated by lokinet_context_new
void EXPORT
lokinet_context_free(struct lokinet_context*);
/// spawn all the threads needed for operation and start running
/// return 0 on success
/// return non zero on fail
int EXPORT
lokinet_context_start(struct lokinet_context*);
/// return 0 if we our endpoint has published on the network and is ready to send
/// return -1 if we don't have enough paths ready
/// retrun -2 if we look deadlocked
/// retrun -3 if context was null or not started yet
int EXPORT
lokinet_status(struct lokinet_context*);
/// wait at most N milliseconds for lokinet to build paths and get ready
/// return 0 if we are ready
/// return nonzero if we are not ready
int EXPORT
lokinet_wait_for_ready(int N, struct lokinet_context*);
/// stop all operations on this lokinet context
void EXPORT
lokinet_context_stop(struct lokinet_context*);
/// load a bootstrap RC from memory
/// return 0 on success
/// return non zero on fail
int EXPORT
lokinet_add_bootstrap_rc(const char*, size_t, struct lokinet_context*);
struct lokinet_context;
/// allocate a new lokinet context
struct lokinet_context* EXPORT lokinet_context_new();
/// free a context allocated by lokinet_context_new
void EXPORT lokinet_context_free(struct lokinet_context*);
/// spawn all the threads needed for operation and start running
/// return 0 on success
/// return non zero on fail
int EXPORT lokinet_context_start(struct lokinet_context*);
/// return 0 if we our endpoint has published on the network and is ready to send
/// return -1 if we don't have enough paths ready
/// retrun -2 if we look deadlocked
/// retrun -3 if context was null or not started yet
int EXPORT lokinet_status(struct lokinet_context*);
/// wait at most N milliseconds for lokinet to build paths and get ready
/// return 0 if we are ready
/// return nonzero if we are not ready
int EXPORT lokinet_wait_for_ready(int N, struct lokinet_context*);
/// stop all operations on this lokinet context
void EXPORT lokinet_context_stop(struct lokinet_context*);
/// load a bootstrap RC from memory
/// return 0 on success
/// return non zero on fail
int EXPORT lokinet_add_bootstrap_rc(const char*, size_t, struct lokinet_context*);
#ifdef __cplusplus
}

@ -5,42 +5,37 @@ extern "C"
{
#endif
/// change our network id globally across all contexts
void EXPORT
lokinet_set_netid(const char* netid);
/// get our current netid
/// must be free()'d after use
const char* EXPORT
lokinet_get_netid();
/// set log level
/// possible values: trace, debug, info, warn, error, critical, none
/// return 0 on success
/// return non zero on fail
int EXPORT
lokinet_log_level(const char* level);
/// Function pointer to invoke with lokinet log messages
typedef void (*lokinet_logger_func)(const char* message, void* context);
/// Optional function to call when flushing lokinet log messages; can be NULL if flushing is not
/// meaningful for the logging system.
typedef void (*lokinet_logger_sync)(void* context);
/// set a custom logger function; it is safe (and often desirable) to call this before calling
/// initializing lokinet via lokinet_context_new.
void EXPORT
lokinet_set_syncing_logger(lokinet_logger_func func, lokinet_logger_sync sync, void* context);
/// shortcut for calling `lokinet_set_syncing_logger` with a NULL sync
void EXPORT
lokinet_set_logger(lokinet_logger_func func, void* context);
/// @brief take in hex and turn it into base32z
/// @return value must be free()'d later
char* EXPORT
lokinet_hex_to_base32z(const char* hex);
/// change our network id globally across all contexts
void EXPORT lokinet_set_netid(const char* netid);
/// get our current netid
/// must be free()'d after use
const char* EXPORT lokinet_get_netid();
/// set log level
/// possible values: trace, debug, info, warn, error, critical, none
/// return 0 on success
/// return non zero on fail
int EXPORT lokinet_log_level(const char* level);
/// Function pointer to invoke with lokinet log messages
typedef void (*lokinet_logger_func)(const char* message, void* context);
/// Optional function to call when flushing lokinet log messages; can be NULL if flushing is not
/// meaningful for the logging system.
typedef void (*lokinet_logger_sync)(void* context);
/// set a custom logger function; it is safe (and often desirable) to call this before calling
/// initializing lokinet via lokinet_context_new.
void EXPORT
lokinet_set_syncing_logger(lokinet_logger_func func, lokinet_logger_sync sync, void* context);
/// shortcut for calling `lokinet_set_syncing_logger` with a NULL sync
void EXPORT lokinet_set_logger(lokinet_logger_func func, void* context);
/// @brief take in hex and turn it into base32z
/// @return value must be free()'d later
char* EXPORT lokinet_hex_to_base32z(const char* hex);
#ifdef __cplusplus
}

@ -8,15 +8,13 @@ extern "C"
{
#endif
/// poll many sockets for activity
/// each pollfd.fd should be set to the socket id
/// returns 0 on sucess
int EXPORT
lokinet_poll(struct pollfd* poll, nfds_t numsockets, struct lokinet_context* ctx);
/// poll many sockets for activity
/// each pollfd.fd should be set to the socket id
/// returns 0 on sucess
int EXPORT lokinet_poll(struct pollfd* poll, nfds_t numsockets, struct lokinet_context* ctx);
/// close a udp socket or a stream socket by its id
void EXPORT
lokinet_close_socket(int id, struct lokinet_context* ctx);
/// close a udp socket or a stream socket by its id
void EXPORT lokinet_close_socket(int id, struct lokinet_context* ctx);
#ifdef __cplusplus
}

@ -7,57 +7,54 @@ extern "C"
{
#endif
// a single srv record
struct lokinet_srv_record
{
/// the srv priority of the record
uint16_t priority;
/// the weight of this record
uint16_t weight;
/// null terminated string of the hostname
char target[256];
/// the port to use
int port;
};
/// private members of a srv lookup
struct lokinet_srv_lookup_private;
/// the result of an srv lookup
struct lokinet_srv_lookup_result
{
/// set to zero on success otherwise is the error code
int error;
/// pointer to internal members
/// dont touch me
struct lokinet_srv_lookup_private* internal;
};
/// do a srv lookup on host for service
/// caller MUST call lokinet_srv_lookup_done when they are done handling the result
int EXPORT
lokinet_srv_lookup(
char* host,
char* service,
struct lokinet_srv_lookup_result* result,
struct lokinet_context* ctx);
/// a hook function to handle each srv record in a srv lookup result
/// passes in NULL when we are at the end of iteration
/// passes in void * user data
/// hook should NOT free the record
typedef void (*lokinet_srv_record_iterator)(struct lokinet_srv_record*, void*);
/// iterate over each srv record in a lookup result
/// user is passes into hook and called for each result and then with NULL as the result on the
/// end of iteration
void EXPORT
lokinet_for_each_srv_record(
struct lokinet_srv_lookup_result* result, lokinet_srv_record_iterator iter, void* user);
/// free internal members of a srv lookup result after use of the result
void EXPORT
lokinet_srv_lookup_done(struct lokinet_srv_lookup_result* result);
// a single srv record
struct lokinet_srv_record
{
/// the srv priority of the record
uint16_t priority;
/// the weight of this record
uint16_t weight;
/// null terminated string of the hostname
char target[256];
/// the port to use
int port;
};
/// private members of a srv lookup
struct lokinet_srv_lookup_private;
/// the result of an srv lookup
struct lokinet_srv_lookup_result
{
/// set to zero on success otherwise is the error code
int error;
/// pointer to internal members
/// dont touch me
struct lokinet_srv_lookup_private* internal;
};
/// do a srv lookup on host for service
/// caller MUST call lokinet_srv_lookup_done when they are done handling the result
int EXPORT lokinet_srv_lookup(
char* host,
char* service,
struct lokinet_srv_lookup_result* result,
struct lokinet_context* ctx);
/// a hook function to handle each srv record in a srv lookup result
/// passes in NULL when we are at the end of iteration
/// passes in void * user data
/// hook should NOT free the record
typedef void (*lokinet_srv_record_iterator)(struct lokinet_srv_record*, void*);
/// iterate over each srv record in a lookup result
/// user is passes into hook and called for each result and then with NULL as the result on the
/// end of iteration
void EXPORT lokinet_for_each_srv_record(
struct lokinet_srv_lookup_result* result, lokinet_srv_record_iterator iter, void* user);
/// free internal members of a srv lookup result after use of the result
void EXPORT lokinet_srv_lookup_done(struct lokinet_srv_lookup_result* result);
#ifdef __cplusplus
}

@ -7,52 +7,48 @@ extern "C"
{
#endif
/// the result of a lokinet stream mapping attempt
struct lokinet_stream_result
{
/// set to zero on success otherwise the error that happened
/// use strerror(3) to get printable string of this error
int error;
/// the local ip address we mapped the remote endpoint to
/// null terminated
char local_address[256];
/// the local port we mapped the remote endpoint to
int local_port;
/// the id of the stream we created
int stream_id;
};
/// connect out to a remote endpoint
/// remoteAddr is in the form of "name:port"
/// localAddr is either NULL for any or in the form of "ip:port" to bind to an explicit address
void EXPORT
lokinet_outbound_stream(
struct lokinet_stream_result* result,
const char* remoteAddr,
const char* localAddr,
struct lokinet_context* context);
/// stream accept filter determines if we should accept a stream or not
/// return 0 to accept
/// return -1 to explicitly reject
/// return -2 to silently drop
typedef int (*lokinet_stream_filter)(const char* remote, uint16_t port, void* userdata);
/// set stream accepter filter
/// passes user parameter into stream filter as void *
/// returns stream id
int EXPORT
lokinet_inbound_stream_filter(
lokinet_stream_filter acceptFilter, void* user, struct lokinet_context* context);
/// simple stream acceptor
/// simple variant of lokinet_inbound_stream_filter that maps port to localhost:port
int EXPORT
lokinet_inbound_stream(uint16_t port, struct lokinet_context* context);
void EXPORT
lokinet_close_stream(int stream_id, struct lokinet_context* context);
/// the result of a lokinet stream mapping attempt
struct lokinet_stream_result
{
/// set to zero on success otherwise the error that happened
/// use strerror(3) to get printable string of this error
int error;
/// the local ip address we mapped the remote endpoint to
/// null terminated
char local_address[256];
/// the local port we mapped the remote endpoint to
int local_port;
/// the id of the stream we created
int stream_id;
};
/// connect out to a remote endpoint
/// remoteAddr is in the form of "name:port"
/// localAddr is either NULL for any or in the form of "ip:port" to bind to an explicit address
void EXPORT lokinet_outbound_stream(
struct lokinet_stream_result* result,
const char* remoteAddr,
const char* localAddr,
struct lokinet_context* context);
/// stream accept filter determines if we should accept a stream or not
/// return 0 to accept
/// return -1 to explicitly reject
/// return -2 to silently drop
typedef int (*lokinet_stream_filter)(const char* remote, uint16_t port, void* userdata);
/// set stream accepter filter
/// passes user parameter into stream filter as void *
/// returns stream id
int EXPORT lokinet_inbound_stream_filter(
lokinet_stream_filter acceptFilter, void* user, struct lokinet_context* context);
/// simple stream acceptor
/// simple variant of lokinet_inbound_stream_filter that maps port to localhost:port
int EXPORT lokinet_inbound_stream(uint16_t port, struct lokinet_context* context);
void EXPORT lokinet_close_stream(int stream_id, struct lokinet_context* context);
#ifdef __cplusplus
}

@ -7,115 +7,111 @@ extern "C"
{
#endif
/// information about a udp flow
struct lokinet_udp_flowinfo
{
/// remote endpoint's .loki or .snode address
char remote_host[256];
/// remote endpont's port
uint16_t remote_port;
/// the socket id for this flow used for i/o purposes and closing this socket
int socket_id;
};
/// information about a udp flow
struct lokinet_udp_flowinfo
{
/// remote endpoint's .loki or .snode address
char remote_host[256];
/// remote endpont's port
uint16_t remote_port;
/// the socket id for this flow used for i/o purposes and closing this socket
int socket_id;
};
/// a result from a lokinet_udp_bind call
struct lokinet_udp_bind_result
{
/// a socket id used to close a lokinet udp socket
int socket_id;
};
/// a result from a lokinet_udp_bind call
struct lokinet_udp_bind_result
{
/// a socket id used to close a lokinet udp socket
int socket_id;
};
/// flow acceptor hook, return 0 success, return nonzero with errno on failure
typedef int (*lokinet_udp_flow_filter)(
void* userdata,
const struct lokinet_udp_flowinfo* remote_address,
void** flow_userdata,
int* timeout_seconds);
/// flow acceptor hook, return 0 success, return nonzero with errno on failure
typedef int (*lokinet_udp_flow_filter)(
void* userdata,
const struct lokinet_udp_flowinfo* remote_address,
void** flow_userdata,
int* timeout_seconds);
/// callback to make a new outbound flow
typedef void(lokinet_udp_create_flow_func)(
void* userdata, void** flow_userdata, int* timeout_seconds);
/// callback to make a new outbound flow
typedef void(lokinet_udp_create_flow_func)(
void* userdata, void** flow_userdata, int* timeout_seconds);
/// hook function for handling packets
typedef void (*lokinet_udp_flow_recv_func)(
const struct lokinet_udp_flowinfo* remote_address,
const char* pkt_data,
size_t pkt_length,
void* flow_userdata);
/// hook function for handling packets
typedef void (*lokinet_udp_flow_recv_func)(
const struct lokinet_udp_flowinfo* remote_address,
const char* pkt_data,
size_t pkt_length,
void* flow_userdata);
/// hook function for flow timeout
typedef void (*lokinet_udp_flow_timeout_func)(
const struct lokinet_udp_flowinfo* remote_address, void* flow_userdata);
/// hook function for flow timeout
typedef void (*lokinet_udp_flow_timeout_func)(
const struct lokinet_udp_flowinfo* remote_address, void* flow_userdata);
/// inbound listen udp socket
/// expose udp port exposePort to the void
////
/// @param filter MUST be non null, pointing to a flow filter for accepting new udp flows, called
/// with user data
///
/// @param recv MUST be non null, pointing to a packet handler function for each flow, called
/// with per flow user data provided by filter function if accepted
///
/// @param timeout MUST be non null,
/// pointing to a cleanup function to clean up a stale flow, staleness determined by the value
/// given by the filter function returns 0 on success
///
/// @returns nonzero on error in which it is an errno value
int EXPORT
lokinet_udp_bind(
uint16_t exposedPort,
lokinet_udp_flow_filter filter,
lokinet_udp_flow_recv_func recv,
lokinet_udp_flow_timeout_func timeout,
void* user,
struct lokinet_udp_bind_result* result,
struct lokinet_context* ctx);
/// inbound listen udp socket
/// expose udp port exposePort to the void
////
/// @param filter MUST be non null, pointing to a flow filter for accepting new udp flows,
/// called with user data
///
/// @param recv MUST be non null, pointing to a packet handler function for each flow, called
/// with per flow user data provided by filter function if accepted
///
/// @param timeout MUST be non null,
/// pointing to a cleanup function to clean up a stale flow, staleness determined by the value
/// given by the filter function returns 0 on success
///
/// @returns nonzero on error in which it is an errno value
int EXPORT lokinet_udp_bind(
uint16_t exposedPort,
lokinet_udp_flow_filter filter,
lokinet_udp_flow_recv_func recv,
lokinet_udp_flow_timeout_func timeout,
void* user,
struct lokinet_udp_bind_result* result,
struct lokinet_context* ctx);
/// @brief establish a udp flow to remote endpoint
///
/// @param create_flow the callback to create the new flow if we establish one
///
/// @param user passed to new_flow as user data
///
/// @param remote the remote address to establish to
///
/// @param ctx the lokinet context to use
///
/// @return 0 on success, non zero errno on fail
int EXPORT
lokinet_udp_establish(
lokinet_udp_create_flow_func create_flow,
void* user,
const struct lokinet_udp_flowinfo* remote,
struct lokinet_context* ctx);
/// @brief establish a udp flow to remote endpoint
///
/// @param create_flow the callback to create the new flow if we establish one
///
/// @param user passed to new_flow as user data
///
/// @param remote the remote address to establish to
///
/// @param ctx the lokinet context to use
///
/// @return 0 on success, non zero errno on fail
int EXPORT lokinet_udp_establish(
lokinet_udp_create_flow_func create_flow,
void* user,
const struct lokinet_udp_flowinfo* remote,
struct lokinet_context* ctx);
/// @brief send on an established flow to remote endpoint
/// blocks until we have sent the packet
///
/// @param flowinfo remote flow to use for sending
///
/// @param ptr pointer to data to send
///
/// @param len the length of the data
///
/// @param ctx the lokinet context to use
///
/// @returns 0 on success and non zero errno on fail
int EXPORT
lokinet_udp_flow_send(
const struct lokinet_udp_flowinfo* remote,
const void* ptr,
size_t len,
struct lokinet_context* ctx);
/// @brief send on an established flow to remote endpoint
/// blocks until we have sent the packet
///
/// @param flowinfo remote flow to use for sending
///
/// @param ptr pointer to data to send
///
/// @param len the length of the data
///
/// @param ctx the lokinet context to use
///
/// @returns 0 on success and non zero errno on fail
int EXPORT lokinet_udp_flow_send(
const struct lokinet_udp_flowinfo* remote,
const void* ptr,
size_t len,
struct lokinet_context* ctx);
/// @brief close a bound udp socket
/// closes all flows immediately
///
/// @param socket_id the bound udp socket's id
///
/// @param ctx lokinet context
void EXPORT
lokinet_udp_close(int socket_id, struct lokinet_context* ctx);
/// @brief close a bound udp socket
/// closes all flows immediately
///
/// @param socket_id the bound udp socket's id
///
/// @param ctx lokinet context
void EXPORT lokinet_udp_close(int socket_id, struct lokinet_context* ctx);
#ifdef __cplusplus
}

@ -6,70 +6,69 @@
extern "C"
{
JNIEXPORT jobject JNICALL
Java_network_loki_lokinet_LokinetConfig_Obtain(JNIEnv* env, jclass, jstring dataDir)
{
auto conf = VisitStringAsStringView<llarp::Config*>(
env, dataDir, [](std::string_view val) -> llarp::Config* {
return new llarp::Config{val};
});
if (conf == nullptr)
return nullptr;
return env->NewDirectByteBuffer(conf, sizeof(llarp::Config));
}
JNIEXPORT jobject JNICALL
Java_network_loki_lokinet_LokinetConfig_Obtain(JNIEnv* env, jclass, jstring dataDir)
{
auto conf = VisitStringAsStringView<llarp::Config*>(
env, dataDir, [](std::string_view val) -> llarp::Config* {
return new llarp::Config{val};
});
JNIEXPORT void JNICALL
Java_network_loki_lokinet_LokinetConfig_Free(JNIEnv* env, jclass, jobject buf)
{
auto ptr = FromBuffer<llarp::Config>(env, buf);
delete ptr;
}
if (conf == nullptr)
return nullptr;
return env->NewDirectByteBuffer(conf, sizeof(llarp::Config));
}
JNIEXPORT jboolean JNICALL
Java_network_loki_lokinet_LokinetConfig_Load(JNIEnv* env, jobject self)
{
auto conf = GetImpl<llarp::Config>(env, self);
if (conf == nullptr)
return JNI_FALSE;
if (conf->Load())
JNIEXPORT void JNICALL
Java_network_loki_lokinet_LokinetConfig_Free(JNIEnv* env, jclass, jobject buf)
{
return JNI_TRUE;
auto ptr = FromBuffer<llarp::Config>(env, buf);
delete ptr;
}
return JNI_FALSE;
}
JNIEXPORT jboolean JNICALL
Java_network_loki_lokinet_LokinetConfig_Save(JNIEnv* env, jobject self)
{
auto conf = GetImpl<llarp::Config>(env, self);
if (conf == nullptr)
return JNI_FALSE;
try
JNIEXPORT jboolean JNICALL
Java_network_loki_lokinet_LokinetConfig_Load(JNIEnv* env, jobject self)
{
conf->Save();
auto conf = GetImpl<llarp::Config>(env, self);
if (conf == nullptr)
return JNI_FALSE;
if (conf->Load())
{
return JNI_TRUE;
}
return JNI_FALSE;
}
catch (...)
JNIEXPORT jboolean JNICALL
Java_network_loki_lokinet_LokinetConfig_Save(JNIEnv* env, jobject self)
{
return JNI_FALSE;
auto conf = GetImpl<llarp::Config>(env, self);
if (conf == nullptr)
return JNI_FALSE;
try
{
conf->Save();
}
catch (...)
{
return JNI_FALSE;
}
return JNI_TRUE;
}
return JNI_TRUE;
}
JNIEXPORT void JNICALL
Java_network_loki_lokinet_LokinetConfig_AddDefaultValue(
JNIEnv* env, jobject self, jstring section, jstring key, jstring value)
{
auto convert = [](std::string_view str) -> std::string { return std::string{str}; };
JNIEXPORT void JNICALL Java_network_loki_lokinet_LokinetConfig_AddDefaultValue(
JNIEnv* env, jobject self, jstring section, jstring key, jstring value)
{
auto convert = [](std::string_view str) -> std::string { return std::string{str}; };
const auto sect = VisitStringAsStringView<std::string>(env, section, convert);
const auto k = VisitStringAsStringView<std::string>(env, key, convert);
const auto v = VisitStringAsStringView<std::string>(env, value, convert);
const auto sect = VisitStringAsStringView<std::string>(env, section, convert);
const auto k = VisitStringAsStringView<std::string>(env, key, convert);
const auto v = VisitStringAsStringView<std::string>(env, value, convert);
auto conf = GetImpl<llarp::Config>(env, self);
if (conf)
{
conf->AddDefault(sect, k, v);
auto conf = GetImpl<llarp::Config>(env, self);
if (conf)
{
conf->AddDefault(sect, k, v);
}
}
}
}

@ -7,116 +7,115 @@
extern "C"
{
JNIEXPORT jobject JNICALL
Java_network_loki_lokinet_LokinetDaemon_Obtain(JNIEnv* env, jclass)
{
auto* ptr = new llarp::Context();
if (ptr == nullptr)
return nullptr;
return env->NewDirectByteBuffer(ptr, sizeof(llarp::Context));
}
JNIEXPORT jobject JNICALL Java_network_loki_lokinet_LokinetDaemon_Obtain(JNIEnv* env, jclass)
{
auto* ptr = new llarp::Context();
if (ptr == nullptr)
return nullptr;
return env->NewDirectByteBuffer(ptr, sizeof(llarp::Context));
}
JNIEXPORT void JNICALL
Java_network_loki_lokinet_LokinetDaemon_Free(JNIEnv* env, jclass, jobject buf)
{
auto ptr = FromBuffer<llarp::Context>(env, buf);
delete ptr;
}
JNIEXPORT void JNICALL
Java_network_loki_lokinet_LokinetDaemon_Free(JNIEnv* env, jclass, jobject buf)
{
auto ptr = FromBuffer<llarp::Context>(env, buf);
delete ptr;
}
JNIEXPORT jboolean JNICALL
Java_network_loki_lokinet_LokinetDaemon_Configure(JNIEnv* env, jobject self, jobject conf)
{
auto ptr = GetImpl<llarp::Context>(env, self);
auto config = GetImpl<llarp::Config>(env, conf);
if (ptr == nullptr || config == nullptr)
return JNI_FALSE;
try
JNIEXPORT jboolean JNICALL
Java_network_loki_lokinet_LokinetDaemon_Configure(JNIEnv* env, jobject self, jobject conf)
{
llarp::RuntimeOptions opts{};
auto ptr = GetImpl<llarp::Context>(env, self);
auto config = GetImpl<llarp::Config>(env, conf);
if (ptr == nullptr || config == nullptr)
return JNI_FALSE;
try
{
llarp::RuntimeOptions opts{};
// janky make_shared deep copy because jni + shared pointer = scary
ptr->Configure(std::make_shared<llarp::Config>(*config));
ptr->Setup(opts);
// janky make_shared deep copy because jni + shared pointer = scary
ptr->Configure(std::make_shared<llarp::Config>(*config));
ptr->Setup(opts);
}
catch (...)
{
return JNI_FALSE;
}
return JNI_TRUE;
}
catch (...)
JNIEXPORT jint JNICALL
Java_network_loki_lokinet_LokinetDaemon_Mainloop(JNIEnv* env, jobject self)
{
return JNI_FALSE;
auto ptr = GetImpl<llarp::Context>(env, self);
if (ptr == nullptr)
return -1;
llarp::RuntimeOptions opts{};
return ptr->Run(opts);
}
return JNI_TRUE;
}
JNIEXPORT jint JNICALL
Java_network_loki_lokinet_LokinetDaemon_Mainloop(JNIEnv* env, jobject self)
{
auto ptr = GetImpl<llarp::Context>(env, self);
if (ptr == nullptr)
return -1;
llarp::RuntimeOptions opts{};
return ptr->Run(opts);
}
JNIEXPORT jboolean JNICALL
Java_network_loki_lokinet_LokinetDaemon_IsRunning(JNIEnv* env, jobject self)
{
auto ptr = GetImpl<llarp::Context>(env, self);
return (ptr != nullptr && ptr->IsUp()) ? JNI_TRUE : JNI_FALSE;
}
JNIEXPORT jboolean JNICALL
Java_network_loki_lokinet_LokinetDaemon_IsRunning(JNIEnv* env, jobject self)
{
auto ptr = GetImpl<llarp::Context>(env, self);
return (ptr != nullptr && ptr->IsUp()) ? JNI_TRUE : JNI_FALSE;
}
JNIEXPORT jboolean JNICALL
Java_network_loki_lokinet_LokinetDaemon_Stop(JNIEnv* env, jobject self)
{
auto ptr = GetImpl<llarp::Context>(env, self);
if (ptr == nullptr)
return JNI_FALSE;
if (not ptr->IsUp())
return JNI_FALSE;
ptr->CloseAsync();
ptr->Wait();
return ptr->IsUp() ? JNI_FALSE : JNI_TRUE;
}
JNIEXPORT jboolean JNICALL
Java_network_loki_lokinet_LokinetDaemon_Stop(JNIEnv* env, jobject self)
{
auto ptr = GetImpl<llarp::Context>(env, self);
if (ptr == nullptr)
return JNI_FALSE;
if (not ptr->IsUp())
return JNI_FALSE;
ptr->CloseAsync();
ptr->Wait();
return ptr->IsUp() ? JNI_FALSE : JNI_TRUE;
}
JNIEXPORT void JNICALL
Java_network_loki_lokinet_LokinetDaemon_InjectVPNFD(JNIEnv* env, jobject self)
{
if (auto ptr = GetImpl<llarp::Context>(env, self))
ptr->androidFD = GetObjectMemberAsInt<int>(env, self, "m_FD");
}
JNIEXPORT void JNICALL
Java_network_loki_lokinet_LokinetDaemon_InjectVPNFD(JNIEnv* env, jobject self)
{
if (auto ptr = GetImpl<llarp::Context>(env, self))
ptr->androidFD = GetObjectMemberAsInt<int>(env, self, "m_FD");
}
JNIEXPORT jint JNICALL
Java_network_loki_lokinet_LokinetDaemon_GetUDPSocket(JNIEnv* env, jobject self)
{
if (auto ptr = GetImpl<llarp::Context>(env, self); ptr and ptr->router)
return ptr->router->outbound_socket();
return -1;
}
JNIEXPORT jint JNICALL
Java_network_loki_lokinet_LokinetDaemon_GetUDPSocket(JNIEnv* env, jobject self)
{
if (auto ptr = GetImpl<llarp::Context>(env, self); ptr and ptr->router)
return ptr->router->outbound_socket();
return -1;
}
JNIEXPORT jstring JNICALL
Java_network_loki_lokinet_LokinetDaemon_DetectFreeRange(JNIEnv* env, jclass)
{
std::string rangestr{};
if (auto maybe = llarp::net::Platform::Default_ptr()->FindFreeRange())
JNIEXPORT jstring JNICALL
Java_network_loki_lokinet_LokinetDaemon_DetectFreeRange(JNIEnv* env, jclass)
{
rangestr = maybe->ToString();
std::string rangestr{};
if (auto maybe = llarp::net::Platform::Default_ptr()->FindFreeRange())
{
rangestr = maybe->ToString();
}
return env->NewStringUTF(rangestr.c_str());
}
return env->NewStringUTF(rangestr.c_str());
}
JNIEXPORT jstring JNICALL
Java_network_loki_lokinet_LokinetDaemon_DumpStatus(JNIEnv* env, jobject self)
{
std::string status{};
if (auto ptr = GetImpl<llarp::Context>(env, self))
JNIEXPORT jstring JNICALL
Java_network_loki_lokinet_LokinetDaemon_DumpStatus(JNIEnv* env, jobject self)
{
if (ptr->IsUp())
{
std::promise<std::string> result;
ptr->CallSafe([&result, router = ptr->router]() {
const auto status = router->ExtractStatus();
result.set_value(status.dump());
});
status = result.get_future().get();
}
std::string status{};
if (auto ptr = GetImpl<llarp::Context>(env, self))
{
if (ptr->IsUp())
{
std::promise<std::string> result;
ptr->CallSafe([&result, router = ptr->router]() {
const auto status = router->ExtractStatus();
result.set_value(status.dump());
});
status = result.get_future().get();
}
}
return env->NewStringUTF(status.c_str());
}
return env->NewStringUTF(status.c_str());
}
}

@ -8,73 +8,67 @@
/// visit string as native bytes
/// jvm uses some unholy encoding internally so we convert it to utf-8
template <typename T, typename V>
static T
VisitStringAsStringView(JNIEnv* env, jobject str, V visit)
static T VisitStringAsStringView(JNIEnv* env, jobject str, V visit)
{
const jclass stringClass = env->GetObjectClass(str);
const jmethodID getBytes = env->GetMethodID(stringClass, "getBytes", "(Ljava/lang/String;)[B");
const jclass stringClass = env->GetObjectClass(str);
const jmethodID getBytes = env->GetMethodID(stringClass, "getBytes", "(Ljava/lang/String;)[B");
const jstring charsetName = env->NewStringUTF("UTF-8");
const jbyteArray stringJbytes = (jbyteArray)env->CallObjectMethod(str, getBytes, charsetName);
env->DeleteLocalRef(charsetName);
const jstring charsetName = env->NewStringUTF("UTF-8");
const jbyteArray stringJbytes = (jbyteArray)env->CallObjectMethod(str, getBytes, charsetName);
env->DeleteLocalRef(charsetName);
const size_t length = env->GetArrayLength(stringJbytes);
jbyte* pBytes = env->GetByteArrayElements(stringJbytes, NULL);
const size_t length = env->GetArrayLength(stringJbytes);
jbyte* pBytes = env->GetByteArrayElements(stringJbytes, NULL);
T result = visit(std::string_view((const char*)pBytes, length));
T result = visit(std::string_view((const char*)pBytes, length));
env->ReleaseByteArrayElements(stringJbytes, pBytes, JNI_ABORT);
env->DeleteLocalRef(stringJbytes);
env->ReleaseByteArrayElements(stringJbytes, pBytes, JNI_ABORT);
env->DeleteLocalRef(stringJbytes);
return result;
return result;
}
/// cast jni buffer to T *
template <typename T>
static T*
FromBuffer(JNIEnv* env, jobject o)
static T* FromBuffer(JNIEnv* env, jobject o)
{
if (o == nullptr)
return nullptr;
return static_cast<T*>(env->GetDirectBufferAddress(o));
if (o == nullptr)
return nullptr;
return static_cast<T*>(env->GetDirectBufferAddress(o));
}
/// get T * from object member called membername
template <typename T>
static T*
FromObjectMember(JNIEnv* env, jobject self, const char* membername)
static T* FromObjectMember(JNIEnv* env, jobject self, const char* membername)
{
jclass cl = env->GetObjectClass(self);
jfieldID name = env->GetFieldID(cl, membername, "Ljava/nio/ByteBuffer;");
jobject buffer = env->GetObjectField(self, name);
return FromBuffer<T>(env, buffer);
jclass cl = env->GetObjectClass(self);
jfieldID name = env->GetFieldID(cl, membername, "Ljava/nio/ByteBuffer;");
jobject buffer = env->GetObjectField(self, name);
return FromBuffer<T>(env, buffer);
}
/// visit object string member called membername as bytes
template <typename T, typename V>
static T
VisitObjectMemberStringAsStringView(JNIEnv* env, jobject self, const char* membername, V v)
static T VisitObjectMemberStringAsStringView(JNIEnv* env, jobject self, const char* membername, V v)
{
jclass cl = env->GetObjectClass(self);
jfieldID name = env->GetFieldID(cl, membername, "Ljava/lang/String;");
jobject str = env->GetObjectField(self, name);
return VisitStringAsStringView<T, V>(env, str, v);
jclass cl = env->GetObjectClass(self);
jfieldID name = env->GetFieldID(cl, membername, "Ljava/lang/String;");
jobject str = env->GetObjectField(self, name);
return VisitStringAsStringView<T, V>(env, str, v);
}
/// get object member int called membername
template <typename Int_t>
Int_t
GetObjectMemberAsInt(JNIEnv* env, jobject self, const char* membername)
Int_t GetObjectMemberAsInt(JNIEnv* env, jobject self, const char* membername)
{
jclass cl = env->GetObjectClass(self);
jfieldID name = env->GetFieldID(cl, membername, "I");
return env->GetIntField(self, name);
jclass cl = env->GetObjectClass(self);
jfieldID name = env->GetFieldID(cl, membername, "I");
return env->GetIntField(self, name);
}
/// get implementation on jni type
template <typename T>
T*
GetImpl(JNIEnv* env, jobject self)
T* GetImpl(JNIEnv* env, jobject self)
{
return FromObjectMember<T>(env, self, "impl");
return FromObjectMember<T>(env, self, "impl");
}

@ -8,46 +8,42 @@
extern "C"
{
#endif
/*
* Class: network_loki_lokinet_LokinetConfig
* Method: Obtain
* Signature: (Ljava/lang/String;)Ljava/nio/ByteBuffer;
*/
JNIEXPORT jobject JNICALL
Java_network_loki_lokinet_LokinetConfig_Obtain(JNIEnv*, jclass, jstring);
/*
* Class: network_loki_lokinet_LokinetConfig
* Method: Obtain
* Signature: (Ljava/lang/String;)Ljava/nio/ByteBuffer;
*/
JNIEXPORT jobject JNICALL
Java_network_loki_lokinet_LokinetConfig_Obtain(JNIEnv*, jclass, jstring);
/*
* Class: network_loki_lokinet_LokinetConfig
* Method: Free
* Signature: (Ljava/nio/ByteBuffer;)V
*/
JNIEXPORT void JNICALL
Java_network_loki_lokinet_LokinetConfig_Free(JNIEnv*, jclass, jobject);
/*
* Class: network_loki_lokinet_LokinetConfig
* Method: Free
* Signature: (Ljava/nio/ByteBuffer;)V
*/
JNIEXPORT void JNICALL Java_network_loki_lokinet_LokinetConfig_Free(JNIEnv*, jclass, jobject);
/*
* Class: network_loki_lokinet_LokinetConfig
* Method: Load
* Signature: ()Z
*/
JNIEXPORT jboolean JNICALL
Java_network_loki_lokinet_LokinetConfig_Load(JNIEnv*, jobject);
/*
* Class: network_loki_lokinet_LokinetConfig
* Method: Load
* Signature: ()Z
*/
JNIEXPORT jboolean JNICALL Java_network_loki_lokinet_LokinetConfig_Load(JNIEnv*, jobject);
/*
* Class: network_loki_lokinet_LokinetConfig
* Method: Save
* Signature: ()Z
*/
JNIEXPORT jboolean JNICALL
Java_network_loki_lokinet_LokinetConfig_Save(JNIEnv*, jobject);
/*
* Class: network_loki_lokinet_LokinetConfig
* Method: Save
* Signature: ()Z
*/
JNIEXPORT jboolean JNICALL Java_network_loki_lokinet_LokinetConfig_Save(JNIEnv*, jobject);
/*
* Class: network_loki_lokinet_LokinetConfig
* Method: AddDefaultValue
* Signature: (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL
Java_network_loki_lokinet_LokinetConfig_AddDefaultValue(
JNIEnv*, jobject, jstring, jstring, jstring);
/*
* Class: network_loki_lokinet_LokinetConfig
* Method: AddDefaultValue
* Signature: (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_network_loki_lokinet_LokinetConfig_AddDefaultValue(
JNIEnv*, jobject, jstring, jstring, jstring);
#ifdef __cplusplus
}

@ -8,85 +8,77 @@
extern "C"
{
#endif
/*
* Class: network_loki_lokinet_LokinetDaemon
* Method: Obtain
* Signature: ()Ljava/nio/ByteBuffer;
*/
JNIEXPORT jobject JNICALL
Java_network_loki_lokinet_LokinetDaemon_Obtain(JNIEnv*, jclass);
/*
* Class: network_loki_lokinet_LokinetDaemon
* Method: Obtain
* Signature: ()Ljava/nio/ByteBuffer;
*/
JNIEXPORT jobject JNICALL Java_network_loki_lokinet_LokinetDaemon_Obtain(JNIEnv*, jclass);
/*
* Class: network_loki_lokinet_LokinetDaemon
* Method: Free
* Signature: (Ljava/nio/ByteBuffer;)V
*/
JNIEXPORT void JNICALL
Java_network_loki_lokinet_LokinetDaemon_Free(JNIEnv*, jclass, jobject);
/*
* Class: network_loki_lokinet_LokinetDaemon
* Method: Free
* Signature: (Ljava/nio/ByteBuffer;)V
*/
JNIEXPORT void JNICALL Java_network_loki_lokinet_LokinetDaemon_Free(JNIEnv*, jclass, jobject);
/*
* Class: network_loki_lokinet_LokinetDaemon
* Method: Configure
* Signature: (Lnetwork/loki/lokinet/LokinetConfig;)Z
*/
JNIEXPORT jboolean JNICALL
Java_network_loki_lokinet_LokinetDaemon_Configure(JNIEnv*, jobject, jobject);
/*
* Class: network_loki_lokinet_LokinetDaemon
* Method: Configure
* Signature: (Lnetwork/loki/lokinet/LokinetConfig;)Z
*/
JNIEXPORT jboolean JNICALL
Java_network_loki_lokinet_LokinetDaemon_Configure(JNIEnv*, jobject, jobject);
/*
* Class: network_loki_lokinet_LokinetDaemon
* Method: Mainloop
* Signature: ()I
*/
JNIEXPORT jint JNICALL
Java_network_loki_lokinet_LokinetDaemon_Mainloop(JNIEnv*, jobject);
/*
* Class: network_loki_lokinet_LokinetDaemon
* Method: Mainloop
* Signature: ()I
*/
JNIEXPORT jint JNICALL Java_network_loki_lokinet_LokinetDaemon_Mainloop(JNIEnv*, jobject);
/*
* Class: network_loki_lokinet_LokinetDaemon
* Method: IsRunning
* Signature: ()Z
*/
JNIEXPORT jboolean JNICALL
Java_network_loki_lokinet_LokinetDaemon_IsRunning(JNIEnv*, jobject);
/*
* Class: network_loki_lokinet_LokinetDaemon
* Method: IsRunning
* Signature: ()Z
*/
JNIEXPORT jboolean JNICALL Java_network_loki_lokinet_LokinetDaemon_IsRunning(JNIEnv*, jobject);
/*
* Class: network_loki_lokinet_LokinetDaemon
* Method: Stop
* Signature: ()Z
*/
JNIEXPORT jboolean JNICALL
Java_network_loki_lokinet_LokinetDaemon_Stop(JNIEnv*, jobject);
/*
* Class: network_loki_lokinet_LokinetDaemon
* Method: Stop
* Signature: ()Z
*/
JNIEXPORT jboolean JNICALL Java_network_loki_lokinet_LokinetDaemon_Stop(JNIEnv*, jobject);
/*
* Class: network_loki_lokinet_LokinetDaemon
* Method: InjectVPNFD
* Signature: ()V
*/
JNIEXPORT void JNICALL
Java_network_loki_lokinet_LokinetDaemon_InjectVPNFD(JNIEnv*, jobject);
/*
* Class: network_loki_lokinet_LokinetDaemon
* Method: InjectVPNFD
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_network_loki_lokinet_LokinetDaemon_InjectVPNFD(JNIEnv*, jobject);
/*
* Class: network_loki_lokinet_LokinetDaemon
* Method: GetUDPSocket
* Signature: ()I
*/
JNIEXPORT jint JNICALL
Java_network_loki_lokinet_LokinetDaemon_GetUDPSocket(JNIEnv*, jobject);
/*
* Class: network_loki_lokinet_LokinetDaemon
* Method: GetUDPSocket
* Signature: ()I
*/
JNIEXPORT jint JNICALL Java_network_loki_lokinet_LokinetDaemon_GetUDPSocket(JNIEnv*, jobject);
/*
* Class: network_loki_lokinet_LokinetDaemon
* Method: DetectFreeRange
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL
Java_network_loki_lokinet_LokinetDaemon_DetectFreeRange(JNIEnv*, jclass);
/*
* Class: network_loki_lokinet_LokinetDaemon
* Method: DetectFreeRange
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL
Java_network_loki_lokinet_LokinetDaemon_DetectFreeRange(JNIEnv*, jclass);
/*
* Class: network_loki_lokinet_LokinetDaemon
* Method: DumpStatus
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL
Java_network_loki_lokinet_LokinetDaemon_DumpStatus(JNIEnv*, jobject);
/*
* Class: network_loki_lokinet_LokinetDaemon
* Method: DumpStatus
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_network_loki_lokinet_LokinetDaemon_DumpStatus(JNIEnv*, jobject);
#ifdef __cplusplus
}

@ -8,61 +8,55 @@
extern "C"
{
#endif
/*
* Class: network_loki_lokinet_LokinetVPN
* Method: PacketSize
* Signature: ()I
*/
JNIEXPORT jint JNICALL
Java_network_loki_lokinet_LokinetVPN_PacketSize(JNIEnv*, jclass);
/*
* Class: network_loki_lokinet_LokinetVPN
* Method: PacketSize
* Signature: ()I
*/
JNIEXPORT jint JNICALL Java_network_loki_lokinet_LokinetVPN_PacketSize(JNIEnv*, jclass);
/*
* Class: network_loki_lokinet_LokinetVPN
* Method: Alloc
* Signature: ()Ljava/nio/Buffer;
*/
JNIEXPORT jobject JNICALL
Java_network_loki_lokinet_LokinetVPN_Alloc(JNIEnv*, jclass);
/*
* Class: network_loki_lokinet_LokinetVPN
* Method: Alloc
* Signature: ()Ljava/nio/Buffer;
*/
JNIEXPORT jobject JNICALL Java_network_loki_lokinet_LokinetVPN_Alloc(JNIEnv*, jclass);
/*
* Class: network_loki_lokinet_LokinetVPN
* Method: Free
* Signature: (Ljava/nio/Buffer;)V
*/
JNIEXPORT void JNICALL
Java_network_loki_lokinet_LokinetVPN_Free(JNIEnv*, jclass, jobject);
/*
* Class: network_loki_lokinet_LokinetVPN
* Method: Free
* Signature: (Ljava/nio/Buffer;)V
*/
JNIEXPORT void JNICALL Java_network_loki_lokinet_LokinetVPN_Free(JNIEnv*, jclass, jobject);
/*
* Class: network_loki_lokinet_LokinetVPN
* Method: Stop
* Signature: ()V
*/
JNIEXPORT void JNICALL
Java_network_loki_lokinet_LokinetVPN_Stop(JNIEnv*, jobject);
/*
* Class: network_loki_lokinet_LokinetVPN
* Method: Stop
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_network_loki_lokinet_LokinetVPN_Stop(JNIEnv*, jobject);
/*
* Class: network_loki_lokinet_LokinetVPN
* Method: ReadPkt
* Signature: (Ljava/nio/ByteBuffer;)I
*/
JNIEXPORT jint JNICALL
Java_network_loki_lokinet_LokinetVPN_ReadPkt(JNIEnv*, jobject, jobject);
/*
* Class: network_loki_lokinet_LokinetVPN
* Method: ReadPkt
* Signature: (Ljava/nio/ByteBuffer;)I
*/
JNIEXPORT jint JNICALL Java_network_loki_lokinet_LokinetVPN_ReadPkt(JNIEnv*, jobject, jobject);
/*
* Class: network_loki_lokinet_LokinetVPN
* Method: WritePkt
* Signature: (Ljava/nio/ByteBuffer;)Z
*/
JNIEXPORT jboolean JNICALL
Java_network_loki_lokinet_LokinetVPN_WritePkt(JNIEnv*, jobject, jobject);
/*
* Class: network_loki_lokinet_LokinetVPN
* Method: WritePkt
* Signature: (Ljava/nio/ByteBuffer;)Z
*/
JNIEXPORT jboolean JNICALL
Java_network_loki_lokinet_LokinetVPN_WritePkt(JNIEnv*, jobject, jobject);
/*
* Class: network_loki_lokinet_LokinetVPN
* Method: SetInfo
* Signature: (Lnetwork/loki/lokinet/LokinetVPN/VPNInfo;)V
*/
JNIEXPORT void JNICALL
Java_network_loki_lokinet_LokinetVPN_SetInfo(JNIEnv*, jobject, jobject);
/*
* Class: network_loki_lokinet_LokinetVPN
* Method: SetInfo
* Signature: (Lnetwork/loki/lokinet/LokinetVPN/VPNInfo;)V
*/
JNIEXPORT void JNICALL Java_network_loki_lokinet_LokinetVPN_SetInfo(JNIEnv*, jobject, jobject);
#ifdef __cplusplus
}
#endif

@ -8,46 +8,43 @@
extern "C"
{
#endif
/*
* Class: network_loki_lokinet_Lokinet_JNI
* Method: getABICompiledWith
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL
Java_network_loki_lokinet_Lokinet_1JNI_getABICompiledWith(JNIEnv*, jclass);
/*
* Class: network_loki_lokinet_Lokinet_JNI
* Method: startLokinet
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL
Java_network_loki_lokinet_Lokinet_1JNI_startLokinet(JNIEnv*, jclass, jstring);
JNIEXPORT jstring JNICALL
Java_network_loki_lokinet_Lokinet_1JNI_getIfAddr(JNIEnv*, jclass);
JNIEXPORT jint JNICALL
Java_network_loki_lokinet_Lokinet_1JNI_getIfRange(JNIEnv*, jclass);
/*
* Class: network_loki_lokinet_Lokinet_JNI
* Method: stopLokinet
* Signature: ()V
*/
JNIEXPORT void JNICALL
Java_network_loki_lokinet_Lokinet_1JNI_stopLokinet(JNIEnv*, jclass);
JNIEXPORT void JNICALL
Java_network_loki_lokinet_Lokinet_1JNI_setVPNFileDescriptor(JNIEnv*, jclass, jint, jint);
/*
* Class: network_loki_lokinet_Lokinet_JNI
* Method: onNetworkStateChanged
* Signature: (Z)V
*/
JNIEXPORT void JNICALL
Java_network_loki_lokinet_Lokinet_1JNI_onNetworkStateChanged(JNIEnv*, jclass, jboolean);
/*
* Class: network_loki_lokinet_Lokinet_JNI
* Method: getABICompiledWith
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL
Java_network_loki_lokinet_Lokinet_1JNI_getABICompiledWith(JNIEnv*, jclass);
/*
* Class: network_loki_lokinet_Lokinet_JNI
* Method: startLokinet
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL
Java_network_loki_lokinet_Lokinet_1JNI_startLokinet(JNIEnv*, jclass, jstring);
JNIEXPORT jstring JNICALL Java_network_loki_lokinet_Lokinet_1JNI_getIfAddr(JNIEnv*, jclass);
JNIEXPORT jint JNICALL Java_network_loki_lokinet_Lokinet_1JNI_getIfRange(JNIEnv*, jclass);
/*
* Class: network_loki_lokinet_Lokinet_JNI
* Method: stopLokinet
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_network_loki_lokinet_Lokinet_1JNI_stopLokinet(JNIEnv*, jclass);
JNIEXPORT void JNICALL
Java_network_loki_lokinet_Lokinet_1JNI_setVPNFileDescriptor(JNIEnv*, jclass, jint, jint);
/*
* Class: network_loki_lokinet_Lokinet_JNI
* Method: onNetworkStateChanged
* Signature: (Z)V
*/
JNIEXPORT void JNICALL
Java_network_loki_lokinet_Lokinet_1JNI_onNetworkStateChanged(JNIEnv*, jclass, jboolean);
#ifdef __cplusplus
}

File diff suppressed because it is too large Load Diff

@ -27,13 +27,13 @@
struct ifaddrs
{
struct ifaddrs* ifa_next;
char* ifa_name;
unsigned int ifa_flags;
struct sockaddr* ifa_addr;
struct sockaddr* ifa_netmask;
struct sockaddr* ifa_dstaddr;
void* ifa_data;
struct ifaddrs* ifa_next;
char* ifa_name;
unsigned int ifa_flags;
struct sockaddr* ifa_addr;
struct sockaddr* ifa_netmask;
struct sockaddr* ifa_dstaddr;
void* ifa_data;
};
/*
@ -47,8 +47,6 @@ struct ifaddrs
#include <sys/cdefs.h>
__BEGIN_DECLS
extern int
getifaddrs(struct ifaddrs** ifap);
extern void
freeifaddrs(struct ifaddrs* ifa);
extern int getifaddrs(struct ifaddrs** ifap);
extern void freeifaddrs(struct ifaddrs* ifa);
__END_DECLS

@ -22,23 +22,24 @@ extern NSString* error_domain;
*/
@interface LLARPDNSTrampoline : NSObject
{
// The socket libunbound talks with:
uv_udp_t request_socket;
// The reply address. This is a bit hacky: we configure libunbound to just use single address
// (rather than a range) so that we don't have to worry about tracking different reply addresses.
@public
struct sockaddr reply_addr;
// UDP "session" aimed at the upstream DNS
@public
NWUDPSession* upstream;
// Apple docs say writes could take time *and* the crappy Apple datagram write methods aren't
// callable again until the previous write finishes. Deal with this garbage API by queuing
// everything than using a uv_async to process the queue.
@public
int write_ready;
@public
NSMutableArray<NSData*>* pending_writes;
uv_async_t write_trigger;
// The socket libunbound talks with:
uv_udp_t request_socket;
// The reply address. This is a bit hacky: we configure libunbound to just use single address
// (rather than a range) so that we don't have to worry about tracking different reply
// addresses.
@public
struct sockaddr reply_addr;
// UDP "session" aimed at the upstream DNS
@public
NWUDPSession* upstream;
// Apple docs say writes could take time *and* the crappy Apple datagram write methods aren't
// callable again until the previous write finishes. Deal with this garbage API by queuing
// everything than using a uv_async to process the queue.
@public
int write_ready;
@public
NSMutableArray<NSData*>* pending_writes;
uv_async_t write_trigger;
}
- (void)startWithUpstreamDns:(NWUDPSession*)dns
listenIp:(NSString*)listenIp

@ -6,80 +6,76 @@ NSString* error_domain = @"org.lokinet";
// Receiving an incoming packet, presumably from libunbound. NB: this is called from the libuv
// event loop.
static void
on_request(
static void on_request(
uv_udp_t* socket,
ssize_t nread,
const uv_buf_t* buf,
const struct sockaddr* addr,
unsigned flags)
{
(void)flags;
if (nread < 0)
{
NSLog(@"Read error: %s", uv_strerror(nread));
free(buf->base);
return;
}
if (nread == 0 || !addr)
{
if (buf)
free(buf->base);
return;
}
LLARPDNSTrampoline* t = (__bridge LLARPDNSTrampoline*)socket->data;
// We configure libunbound to use just one single port so we'll just send replies to the last port
// to talk to us. (And we're only listening on localhost in the first place).
t->reply_addr = *addr;
// NSData takes care of calling free(buf->base) for us with this constructor:
[t->pending_writes addObject:[NSData dataWithBytesNoCopy:buf->base length:nread]];
[t flushWrites];
(void)flags;
if (nread < 0)
{
NSLog(@"Read error: %s", uv_strerror(nread));
free(buf->base);
return;
}
if (nread == 0 || !addr)
{
if (buf)
free(buf->base);
return;
}
LLARPDNSTrampoline* t = (__bridge LLARPDNSTrampoline*)socket->data;
// We configure libunbound to use just one single port so we'll just send replies to the last
// port to talk to us. (And we're only listening on localhost in the first place).
t->reply_addr = *addr;
// NSData takes care of calling free(buf->base) for us with this constructor:
[t->pending_writes addObject:[NSData dataWithBytesNoCopy:buf->base length:nread]];
[t flushWrites];
}
static void
on_sent(uv_udp_send_t* req, int status)
static void on_sent(uv_udp_send_t* req, int status)
{
(void)status;
NSArray<NSData*>* datagrams = (__bridge_transfer NSArray<NSData*>*)req->data;
(void)datagrams;
free(req);
(void)status;
NSArray<NSData*>* datagrams = (__bridge_transfer NSArray<NSData*>*)req->data;
(void)datagrams;
free(req);
}
// NB: called from the libuv event loop (so we don't have to worry about the above and this one
// running at once from different threads).
static void
write_flusher(uv_async_t* async)
static void write_flusher(uv_async_t* async)
{
LLARPDNSTrampoline* t = (__bridge LLARPDNSTrampoline*)async->data;
if (t->pending_writes.count == 0)
return;
NSArray<NSData*>* data = [NSArray<NSData*> arrayWithArray:t->pending_writes];
[t->pending_writes removeAllObjects];
__weak LLARPDNSTrampoline* weakSelf = t;
[t->upstream writeMultipleDatagrams:data
completionHandler:^(NSError* error) {
if (error)
NSLog(@"Failed to send request to upstream DNS: %@", error);
// Trigger another flush in case anything built up while Apple was doing its
// things. Just call it unconditionally (rather than checking the queue)
// because this handler is probably running in some other thread.
[weakSelf flushWrites];
}];
LLARPDNSTrampoline* t = (__bridge LLARPDNSTrampoline*)async->data;
if (t->pending_writes.count == 0)
return;
NSArray<NSData*>* data = [NSArray<NSData*> arrayWithArray:t->pending_writes];
[t->pending_writes removeAllObjects];
__weak LLARPDNSTrampoline* weakSelf = t;
[t->upstream writeMultipleDatagrams:data
completionHandler:^(NSError* error) {
if (error)
NSLog(@"Failed to send request to upstream DNS: %@", error);
// Trigger another flush in case anything built up while Apple was doing its
// things. Just call it unconditionally (rather than checking the queue)
// because this handler is probably running in some other thread.
[weakSelf flushWrites];
}];
}
static void
alloc_buffer(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf)
static void alloc_buffer(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf)
{
(void)handle;
buf->base = malloc(suggested_size);
buf->len = suggested_size;
(void)handle;
buf->base = malloc(suggested_size);
buf->len = suggested_size;
}
@implementation LLARPDNSTrampoline
@ -90,78 +86,78 @@ alloc_buffer(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf)
uvLoop:(uv_loop_t*)loop
completionHandler:(void (^)(NSError* error))completionHandler
{
NSLog(@"Setting up trampoline");
pending_writes = [[NSMutableArray<NSData*> alloc] init];
write_trigger.data = (__bridge void*)self;
uv_async_init(loop, &write_trigger, write_flusher);
request_socket.data = (__bridge void*)self;
uv_udp_init(loop, &request_socket);
struct sockaddr_in recv_addr;
uv_ip4_addr(listenIp.UTF8String, listenPort, &recv_addr);
int ret = uv_udp_bind(&request_socket, (const struct sockaddr*)&recv_addr, UV_UDP_REUSEADDR);
if (ret < 0)
{
NSString* errstr =
[NSString stringWithFormat:@"Failed to start DNS trampoline: %s", uv_strerror(ret)];
NSError* err = [NSError errorWithDomain:error_domain code:ret userInfo:@{@"Error": errstr}];
NSLog(@"%@", err);
return completionHandler(err);
}
uv_udp_recv_start(&request_socket, alloc_buffer, on_request);
NSLog(@"Starting DNS trampoline");
upstream = dns;
__weak LLARPDNSTrampoline* weakSelf = self;
[upstream
setReadHandler:^(NSArray<NSData*>* datagrams, NSError* error) {
// Reading a reply back from the UDP socket used to talk to upstream
if (error)
{
NSLog(@"Reader handler failed: %@", error);
return;
NSLog(@"Setting up trampoline");
pending_writes = [[NSMutableArray<NSData*> alloc] init];
write_trigger.data = (__bridge void*)self;
uv_async_init(loop, &write_trigger, write_flusher);
request_socket.data = (__bridge void*)self;
uv_udp_init(loop, &request_socket);
struct sockaddr_in recv_addr;
uv_ip4_addr(listenIp.UTF8String, listenPort, &recv_addr);
int ret = uv_udp_bind(&request_socket, (const struct sockaddr*)&recv_addr, UV_UDP_REUSEADDR);
if (ret < 0)
{
NSString* errstr =
[NSString stringWithFormat:@"Failed to start DNS trampoline: %s", uv_strerror(ret)];
NSError* err = [NSError errorWithDomain:error_domain code:ret userInfo:@{@"Error": errstr}];
NSLog(@"%@", err);
return completionHandler(err);
}
uv_udp_recv_start(&request_socket, alloc_buffer, on_request);
NSLog(@"Starting DNS trampoline");
upstream = dns;
__weak LLARPDNSTrampoline* weakSelf = self;
[upstream
setReadHandler:^(NSArray<NSData*>* datagrams, NSError* error) {
// Reading a reply back from the UDP socket used to talk to upstream
if (error)
{
NSLog(@"Reader handler failed: %@", error);
return;
}
LLARPDNSTrampoline* strongSelf = weakSelf;
if (!strongSelf || datagrams.count == 0)
return;
uv_buf_t* buffers = malloc(datagrams.count * sizeof(uv_buf_t));
size_t buf_count = 0;
for (NSData* packet in datagrams)
{
buffers[buf_count].base = (void*)packet.bytes;
buffers[buf_count].len = packet.length;
buf_count++;
}
uv_udp_send_t* uvsend = malloc(sizeof(uv_udp_send_t));
uvsend->data = (__bridge_retained void*)datagrams;
int ret = uv_udp_send(
uvsend,
&strongSelf->request_socket,
buffers,
buf_count,
&strongSelf->reply_addr,
on_sent);
free(buffers);
if (ret < 0)
NSLog(@"Error returning DNS responses to unbound: %s", uv_strerror(ret));
}
LLARPDNSTrampoline* strongSelf = weakSelf;
if (!strongSelf || datagrams.count == 0)
return;
uv_buf_t* buffers = malloc(datagrams.count * sizeof(uv_buf_t));
size_t buf_count = 0;
for (NSData* packet in datagrams)
{
buffers[buf_count].base = (void*)packet.bytes;
buffers[buf_count].len = packet.length;
buf_count++;
}
uv_udp_send_t* uvsend = malloc(sizeof(uv_udp_send_t));
uvsend->data = (__bridge_retained void*)datagrams;
int ret = uv_udp_send(
uvsend,
&strongSelf->request_socket,
buffers,
buf_count,
&strongSelf->reply_addr,
on_sent);
free(buffers);
if (ret < 0)
NSLog(@"Error returning DNS responses to unbound: %s", uv_strerror(ret));
}
maxDatagrams:NSUIntegerMax];
completionHandler(nil);
maxDatagrams:NSUIntegerMax];
completionHandler(nil);
}
- (void)flushWrites
{
uv_async_send(&write_trigger);
uv_async_send(&write_trigger);
}
- (void)dealloc
{
NSLog(@"Stopping DNS trampoline");
uv_close((uv_handle_t*)&request_socket, NULL);
uv_close((uv_handle_t*)&write_trigger, NULL);
NSLog(@"Stopping DNS trampoline");
uv_close((uv_handle_t*)&request_socket, NULL);
uv_close((uv_handle_t*)&write_trigger, NULL);
}
@end

@ -8,15 +8,15 @@
@interface LLARPPacketTunnel : NEPacketTunnelProvider
{
void* lokinet;
llarp_incoming_packet packet_buf[LLARP_APPLE_PACKET_BUF_SIZE];
@public
NEPacketTunnelNetworkSettings* settings;
@public
NEIPv4Route* tun_route4;
@public
NEIPv6Route* tun_route6;
LLARPDNSTrampoline* dns_tramp;
void* lokinet;
llarp_incoming_packet packet_buf[LLARP_APPLE_PACKET_BUF_SIZE];
@public
NEPacketTunnelNetworkSettings* settings;
@public
NEIPv4Route* tun_route4;
@public
NEIPv6Route* tun_route6;
LLARPDNSTrampoline* dns_tramp;
}
- (void)startTunnelWithOptions:(NSDictionary<NSString*, NSObject*>*)options
@ -34,368 +34,364 @@
@end
static void
nslogger(const char* msg)
static void nslogger(const char* msg)
{
NSLog(@"%s", msg);
NSLog(@"%s", msg);
}
static void
packet_writer(int af, const void* data, size_t size, void* ctx)
static void packet_writer(int af, const void* data, size_t size, void* ctx)
{
if (ctx == nil || data == nil)
return;
if (ctx == nil || data == nil)
return;
NSData* buf = [NSData dataWithBytesNoCopy:(void*)data length:size freeWhenDone:NO];
LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*)ctx;
[t.packetFlow writePackets:@[buf] withProtocols:@[[NSNumber numberWithInt:af]]];
NSData* buf = [NSData dataWithBytesNoCopy:(void*)data length:size freeWhenDone:NO];
LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*)ctx;
[t.packetFlow writePackets:@[buf] withProtocols:@[[NSNumber numberWithInt:af]]];
}
static void
start_packet_reader(void* ctx)
static void start_packet_reader(void* ctx)
{
if (ctx == nil)
return;
if (ctx == nil)
return;
LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*)ctx;
[t readPackets];
LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*)ctx;
[t readPackets];
}
static void
add_ipv4_route(const char* addr, const char* netmask, void* ctx)
static void add_ipv4_route(const char* addr, const char* netmask, void* ctx)
{
NSLog(@"Adding IPv4 route %s:%s to packet tunnel", addr, netmask);
NEIPv4Route* route =
[[NEIPv4Route alloc] initWithDestinationAddress:[NSString stringWithUTF8String:addr]
subnetMask:[NSString stringWithUTF8String:netmask]];
NSLog(@"Adding IPv4 route %s:%s to packet tunnel", addr, netmask);
NEIPv4Route* route =
[[NEIPv4Route alloc] initWithDestinationAddress:[NSString stringWithUTF8String:addr]
subnetMask:[NSString stringWithUTF8String:netmask]];
LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*)ctx;
for (NEIPv4Route* r in t->settings.IPv4Settings.includedRoutes)
if ([r.destinationAddress isEqualToString:route.destinationAddress] &&
[r.destinationSubnetMask isEqualToString:route.destinationSubnetMask])
return; // Already in the settings, nothing to add.
LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*)ctx;
for (NEIPv4Route* r in t->settings.IPv4Settings.includedRoutes)
if ([r.destinationAddress isEqualToString:route.destinationAddress] &&
[r.destinationSubnetMask isEqualToString:route.destinationSubnetMask])
return; // Already in the settings, nothing to add.
t->settings.IPv4Settings.includedRoutes =
[t->settings.IPv4Settings.includedRoutes arrayByAddingObject:route];
t->settings.IPv4Settings.includedRoutes =
[t->settings.IPv4Settings.includedRoutes arrayByAddingObject:route];
[t updateNetworkSettings];
[t updateNetworkSettings];
}
static void
del_ipv4_route(const char* addr, const char* netmask, void* ctx)
static void del_ipv4_route(const char* addr, const char* netmask, void* ctx)
{
NSLog(@"Removing IPv4 route %s:%s to packet tunnel", addr, netmask);
NEIPv4Route* route =
[[NEIPv4Route alloc] initWithDestinationAddress:[NSString stringWithUTF8String:addr]
subnetMask:[NSString stringWithUTF8String:netmask]];
LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*)ctx;
NSMutableArray<NEIPv4Route*>* routes =
[NSMutableArray arrayWithArray:t->settings.IPv4Settings.includedRoutes];
for (size_t i = 0; i < routes.count; i++)
{
if ([routes[i].destinationAddress isEqualToString:route.destinationAddress] &&
[routes[i].destinationSubnetMask isEqualToString:route.destinationSubnetMask])
NSLog(@"Removing IPv4 route %s:%s to packet tunnel", addr, netmask);
NEIPv4Route* route =
[[NEIPv4Route alloc] initWithDestinationAddress:[NSString stringWithUTF8String:addr]
subnetMask:[NSString stringWithUTF8String:netmask]];
LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*)ctx;
NSMutableArray<NEIPv4Route*>* routes =
[NSMutableArray arrayWithArray:t->settings.IPv4Settings.includedRoutes];
for (size_t i = 0; i < routes.count; i++)
{
[routes removeObjectAtIndex:i];
i--;
if ([routes[i].destinationAddress isEqualToString:route.destinationAddress] &&
[routes[i].destinationSubnetMask isEqualToString:route.destinationSubnetMask])
{
[routes removeObjectAtIndex:i];
i--;
}
}
}
if (routes.count != t->settings.IPv4Settings.includedRoutes.count)
{
t->settings.IPv4Settings.includedRoutes = routes;
[t updateNetworkSettings];
}
if (routes.count != t->settings.IPv4Settings.includedRoutes.count)
{
t->settings.IPv4Settings.includedRoutes = routes;
[t updateNetworkSettings];
}
}
static void
add_ipv6_route(const char* addr, int prefix, void* ctx)
static void add_ipv6_route(const char* addr, int prefix, void* ctx)
{
NEIPv6Route* route =
[[NEIPv6Route alloc] initWithDestinationAddress:[NSString stringWithUTF8String:addr]
networkPrefixLength:[NSNumber numberWithInt:prefix]];
NEIPv6Route* route =
[[NEIPv6Route alloc] initWithDestinationAddress:[NSString stringWithUTF8String:addr]
networkPrefixLength:[NSNumber numberWithInt:prefix]];
LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*)ctx;
for (NEIPv6Route* r in t->settings.IPv6Settings.includedRoutes)
if ([r.destinationAddress isEqualToString:route.destinationAddress] &&
[r.destinationNetworkPrefixLength isEqualToNumber:route.destinationNetworkPrefixLength])
return; // Already in the settings, nothing to add.
LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*)ctx;
for (NEIPv6Route* r in t->settings.IPv6Settings.includedRoutes)
if ([r.destinationAddress isEqualToString:route.destinationAddress] &&
[r.destinationNetworkPrefixLength isEqualToNumber:route.destinationNetworkPrefixLength])
return; // Already in the settings, nothing to add.
t->settings.IPv6Settings.includedRoutes =
[t->settings.IPv6Settings.includedRoutes arrayByAddingObject:route];
t->settings.IPv6Settings.includedRoutes =
[t->settings.IPv6Settings.includedRoutes arrayByAddingObject:route];
[t updateNetworkSettings];
[t updateNetworkSettings];
}
static void
del_ipv6_route(const char* addr, int prefix, void* ctx)
static void del_ipv6_route(const char* addr, int prefix, void* ctx)
{
NEIPv6Route* route =
[[NEIPv6Route alloc] initWithDestinationAddress:[NSString stringWithUTF8String:addr]
networkPrefixLength:[NSNumber numberWithInt:prefix]];
LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*)ctx;
NSMutableArray<NEIPv6Route*>* routes =
[NSMutableArray arrayWithArray:t->settings.IPv6Settings.includedRoutes];
for (size_t i = 0; i < routes.count; i++)
{
if ([routes[i].destinationAddress isEqualToString:route.destinationAddress] &&
[routes[i].destinationNetworkPrefixLength
isEqualToNumber:route.destinationNetworkPrefixLength])
NEIPv6Route* route =
[[NEIPv6Route alloc] initWithDestinationAddress:[NSString stringWithUTF8String:addr]
networkPrefixLength:[NSNumber numberWithInt:prefix]];
LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*)ctx;
NSMutableArray<NEIPv6Route*>* routes =
[NSMutableArray arrayWithArray:t->settings.IPv6Settings.includedRoutes];
for (size_t i = 0; i < routes.count; i++)
{
[routes removeObjectAtIndex:i];
i--;
if ([routes[i].destinationAddress isEqualToString:route.destinationAddress] &&
[routes[i].destinationNetworkPrefixLength
isEqualToNumber:route.destinationNetworkPrefixLength])
{
[routes removeObjectAtIndex:i];
i--;
}
}
}
if (routes.count != t->settings.IPv6Settings.includedRoutes.count)
{
t->settings.IPv6Settings.includedRoutes = routes;
[t updateNetworkSettings];
}
if (routes.count != t->settings.IPv6Settings.includedRoutes.count)
{
t->settings.IPv6Settings.includedRoutes = routes;
[t updateNetworkSettings];
}
}
static void
add_default_route(void* ctx)
static void add_default_route(void* ctx)
{
NSLog(@"Making the tunnel the default route");
LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*)ctx;
NSLog(@"Making the tunnel the default route");
LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*)ctx;
t->settings.IPv4Settings.includedRoutes = @[NEIPv4Route.defaultRoute];
t->settings.IPv6Settings.includedRoutes = @[NEIPv6Route.defaultRoute];
t->settings.IPv4Settings.includedRoutes = @[NEIPv4Route.defaultRoute];
t->settings.IPv6Settings.includedRoutes = @[NEIPv6Route.defaultRoute];
[t updateNetworkSettings];
[t updateNetworkSettings];
}
static void
del_default_route(void* ctx)
static void del_default_route(void* ctx)
{
NSLog(@"Removing default route from tunnel");
LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*)ctx;
NSLog(@"Removing default route from tunnel");
LLARPPacketTunnel* t = (__bridge LLARPPacketTunnel*)ctx;
t->settings.IPv4Settings.includedRoutes = @[t->tun_route4];
t->settings.IPv6Settings.includedRoutes = @[t->tun_route6];
t->settings.IPv4Settings.includedRoutes = @[t->tun_route4];
t->settings.IPv6Settings.includedRoutes = @[t->tun_route6];
[t updateNetworkSettings];
[t updateNetworkSettings];
}
@implementation LLARPPacketTunnel
- (void)readPackets
{
[self.packetFlow readPacketObjectsWithCompletionHandler:^(NSArray<NEPacket*>* packets) {
if (lokinet == nil)
return;
[self.packetFlow readPacketObjectsWithCompletionHandler:^(NSArray<NEPacket*>* packets) {
if (lokinet == nil)
return;
size_t size = 0;
for (NEPacket* p in packets)
{
packet_buf[size].bytes = p.data.bytes;
packet_buf[size].size = p.data.length;
size++;
if (size >= LLARP_APPLE_PACKET_BUF_SIZE)
size_t size = 0;
for (NEPacket* p in packets)
{
llarp_apple_incoming(lokinet, packet_buf, size);
size = 0;
packet_buf[size].bytes = p.data.bytes;
packet_buf[size].size = p.data.length;
size++;
if (size >= LLARP_APPLE_PACKET_BUF_SIZE)
{
llarp_apple_incoming(lokinet, packet_buf, size);
size = 0;
}
}
}
if (size > 0)
llarp_apple_incoming(lokinet, packet_buf, size);
if (size > 0)
llarp_apple_incoming(lokinet, packet_buf, size);
[self readPackets];
}];
[self readPackets];
}];
}
- (void)startTunnelWithOptions:(NSDictionary<NSString*, NSObject*>*)options
completionHandler:(void (^)(NSError*))completionHandler
{
NSString* default_bootstrap = [NSBundle.mainBundle pathForResource:@"bootstrap" ofType:@"signed"];
NSString* home = NSHomeDirectory();
llarp_apple_config conf = {
.config_dir = home.UTF8String,
.default_bootstrap = default_bootstrap.UTF8String,
.ns_logger = nslogger,
.packet_writer = packet_writer,
.start_reading = start_packet_reader,
.route_callbacks =
{.add_ipv4_route = add_ipv4_route,
.del_ipv4_route = del_ipv4_route,
.add_ipv6_route = add_ipv6_route,
.del_ipv6_route = del_ipv6_route,
.add_default_route = add_default_route,
.del_default_route = del_default_route},
};
lokinet = llarp_apple_init(&conf);
if (!lokinet)
{
NSError* init_failure = [NSError errorWithDomain:error_domain
code:500
userInfo:@{@"Error": @"Failed to initialize lokinet"}];
NSLog(@"%@", [init_failure localizedDescription]);
return completionHandler(init_failure);
}
NSString* ip = [NSString stringWithUTF8String:conf.tunnel_ipv4_ip];
NSString* mask = [NSString stringWithUTF8String:conf.tunnel_ipv4_netmask];
// We don't have a fixed address so just stick some bogus value here:
settings = [[NEPacketTunnelNetworkSettings alloc] initWithTunnelRemoteAddress:@"127.3.2.1"];
NSString* default_bootstrap = [NSBundle.mainBundle pathForResource:@"bootstrap"
ofType:@"signed"];
NSString* home = NSHomeDirectory();
llarp_apple_config conf = {
.config_dir = home.UTF8String,
.default_bootstrap = default_bootstrap.UTF8String,
.ns_logger = nslogger,
.packet_writer = packet_writer,
.start_reading = start_packet_reader,
.route_callbacks =
{.add_ipv4_route = add_ipv4_route,
.del_ipv4_route = del_ipv4_route,
.add_ipv6_route = add_ipv6_route,
.del_ipv6_route = del_ipv6_route,
.add_default_route = add_default_route,
.del_default_route = del_default_route},
};
lokinet = llarp_apple_init(&conf);
if (!lokinet)
{
NSError* init_failure =
[NSError errorWithDomain:error_domain
code:500
userInfo:@{@"Error": @"Failed to initialize lokinet"}];
NSLog(@"%@", [init_failure localizedDescription]);
return completionHandler(init_failure);
}
NSString* ip = [NSString stringWithUTF8String:conf.tunnel_ipv4_ip];
NSString* mask = [NSString stringWithUTF8String:conf.tunnel_ipv4_netmask];
// We don't have a fixed address so just stick some bogus value here:
settings = [[NEPacketTunnelNetworkSettings alloc] initWithTunnelRemoteAddress:@"127.3.2.1"];
#ifdef MACOS_SYSTEM_EXTENSION
NSString* dns_ip = [NSString stringWithUTF8String:conf.dns_bind_ip];
NSString* dns_ip = [NSString stringWithUTF8String:conf.dns_bind_ip];
#else
// TODO: placeholder
NSString* dns_ip = ip;
// TODO: placeholder
NSString* dns_ip = ip;
#endif
NSLog(@"setting dns to %@", dns_ip);
NEDNSSettings* dns = [[NEDNSSettings alloc] initWithServers:@[dns_ip]];
dns.domainName = @"localhost.loki";
dns.matchDomains = @[@""];
// In theory, matchDomains is supposed to be set to DNS suffixes that we resolve. This seems
// highly unreliable, though: often it just doesn't work at all (perhaps only if we make ourselves
// the default route?), and even when it does work, it seems there are secret reasons that some
// domains (such as instagram.com) still won't work because there's some magic sauce in the OS
// that Apple engineers don't want to disclose ("This is what I expected, actually. Although I
// will not comment on what I believe is happening here", from
// https://developer.apple.com/forums/thread/685410).
//
// So the documentation sucks and the feature doesn't appear to work, so as much as it would be
// nice to capture only .loki and .snode when not in exit mode, we can't, so capture everything
// and use our default upstream.
dns.matchDomains = @[@""];
dns.matchDomainsNoSearch = true;
dns.searchDomains = @[];
settings.DNSSettings = dns;
NWHostEndpoint* upstreamdns_ep;
if (strlen(conf.upstream_dns))
upstreamdns_ep =
[NWHostEndpoint endpointWithHostname:[NSString stringWithUTF8String:conf.upstream_dns]
port:@(conf.upstream_dns_port).stringValue];
NEIPv4Settings* ipv4 = [[NEIPv4Settings alloc] initWithAddresses:@[ip] subnetMasks:@[mask]];
tun_route4 = [[NEIPv4Route alloc] initWithDestinationAddress:ip subnetMask:mask];
ipv4.includedRoutes = @[tun_route4];
settings.IPv4Settings = ipv4;
NSString* ip6 = [NSString stringWithUTF8String:conf.tunnel_ipv6_ip];
NSNumber* ip6_prefix = [NSNumber numberWithUnsignedInt:conf.tunnel_ipv6_prefix];
NEIPv6Settings* ipv6 = [[NEIPv6Settings alloc] initWithAddresses:@[ip6]
networkPrefixLengths:@[ip6_prefix]];
tun_route6 = [[NEIPv6Route alloc] initWithDestinationAddress:ip6 networkPrefixLength:ip6_prefix];
ipv6.includedRoutes = @[tun_route6];
settings.IPv6Settings = ipv6;
__weak LLARPPacketTunnel* weakSelf = self;
[self setTunnelNetworkSettings:settings
completionHandler:^(NSError* err) {
if (err)
{
NSLog(@"Failed to configure lokinet tunnel: %@", err);
return completionHandler(err);
}
LLARPPacketTunnel* strongSelf = weakSelf;
if (!strongSelf)
return completionHandler(nil);
int start_ret = llarp_apple_start(strongSelf->lokinet, (__bridge void*)strongSelf);
if (start_ret != 0)
{
NSError* start_failure =
[NSError errorWithDomain:error_domain
code:start_ret
userInfo:@{@"Error": @"Failed to start lokinet"}];
NSLog(@"%@", start_failure);
lokinet = nil;
return completionHandler(start_failure);
}
NSString* dns_tramp_ip = @"127.0.0.1";
NSLog(
@"Starting DNS exit mode trampoline to %@ on %@:%d",
upstreamdns_ep,
dns_tramp_ip,
dns_trampoline_port);
NWUDPSession* upstreamdns =
[strongSelf createUDPSessionThroughTunnelToEndpoint:upstreamdns_ep
fromEndpoint:nil];
strongSelf->dns_tramp = [LLARPDNSTrampoline alloc];
[strongSelf->dns_tramp
startWithUpstreamDns:upstreamdns
listenIp:dns_tramp_ip
listenPort:dns_trampoline_port
uvLoop:llarp_apple_get_uv_loop(strongSelf->lokinet)
completionHandler:^(NSError* error) {
if (error)
NSLog(@"Error starting dns trampoline: %@", error);
return completionHandler(error);
}];
}];
NSLog(@"setting dns to %@", dns_ip);
NEDNSSettings* dns = [[NEDNSSettings alloc] initWithServers:@[dns_ip]];
dns.domainName = @"localhost.loki";
dns.matchDomains = @[@""];
// In theory, matchDomains is supposed to be set to DNS suffixes that we resolve. This seems
// highly unreliable, though: often it just doesn't work at all (perhaps only if we make
// ourselves the default route?), and even when it does work, it seems there are secret reasons
// that some domains (such as instagram.com) still won't work because there's some magic sauce
// in the OS that Apple engineers don't want to disclose ("This is what I expected, actually.
// Although I will not comment on what I believe is happening here", from
// https://developer.apple.com/forums/thread/685410).
//
// So the documentation sucks and the feature doesn't appear to work, so as much as it would be
// nice to capture only .loki and .snode when not in exit mode, we can't, so capture everything
// and use our default upstream.
dns.matchDomains = @[@""];
dns.matchDomainsNoSearch = true;
dns.searchDomains = @[];
settings.DNSSettings = dns;
NWHostEndpoint* upstreamdns_ep;
if (strlen(conf.upstream_dns))
upstreamdns_ep =
[NWHostEndpoint endpointWithHostname:[NSString stringWithUTF8String:conf.upstream_dns]
port:@(conf.upstream_dns_port).stringValue];
NEIPv4Settings* ipv4 = [[NEIPv4Settings alloc] initWithAddresses:@[ip] subnetMasks:@[mask]];
tun_route4 = [[NEIPv4Route alloc] initWithDestinationAddress:ip subnetMask:mask];
ipv4.includedRoutes = @[tun_route4];
settings.IPv4Settings = ipv4;
NSString* ip6 = [NSString stringWithUTF8String:conf.tunnel_ipv6_ip];
NSNumber* ip6_prefix = [NSNumber numberWithUnsignedInt:conf.tunnel_ipv6_prefix];
NEIPv6Settings* ipv6 = [[NEIPv6Settings alloc] initWithAddresses:@[ip6]
networkPrefixLengths:@[ip6_prefix]];
tun_route6 = [[NEIPv6Route alloc] initWithDestinationAddress:ip6
networkPrefixLength:ip6_prefix];
ipv6.includedRoutes = @[tun_route6];
settings.IPv6Settings = ipv6;
__weak LLARPPacketTunnel* weakSelf = self;
[self setTunnelNetworkSettings:settings
completionHandler:^(NSError* err) {
if (err)
{
NSLog(@"Failed to configure lokinet tunnel: %@", err);
return completionHandler(err);
}
LLARPPacketTunnel* strongSelf = weakSelf;
if (!strongSelf)
return completionHandler(nil);
int start_ret =
llarp_apple_start(strongSelf->lokinet, (__bridge void*)strongSelf);
if (start_ret != 0)
{
NSError* start_failure =
[NSError errorWithDomain:error_domain
code:start_ret
userInfo:@{@"Error": @"Failed to start lokinet"}];
NSLog(@"%@", start_failure);
lokinet = nil;
return completionHandler(start_failure);
}
NSString* dns_tramp_ip = @"127.0.0.1";
NSLog(
@"Starting DNS exit mode trampoline to %@ on %@:%d",
upstreamdns_ep,
dns_tramp_ip,
dns_trampoline_port);
NWUDPSession* upstreamdns =
[strongSelf createUDPSessionThroughTunnelToEndpoint:upstreamdns_ep
fromEndpoint:nil];
strongSelf->dns_tramp = [LLARPDNSTrampoline alloc];
[strongSelf->dns_tramp
startWithUpstreamDns:upstreamdns
listenIp:dns_tramp_ip
listenPort:dns_trampoline_port
uvLoop:llarp_apple_get_uv_loop(strongSelf->lokinet)
completionHandler:^(NSError* error) {
if (error)
NSLog(@"Error starting dns trampoline: %@", error);
return completionHandler(error);
}];
}];
}
- (void)stopTunnelWithReason:(NEProviderStopReason)reason
completionHandler:(void (^)(void))completionHandler
{
if (lokinet)
{
llarp_apple_shutdown(lokinet);
lokinet = nil;
}
completionHandler();
if (lokinet)
{
llarp_apple_shutdown(lokinet);
lokinet = nil;
}
completionHandler();
}
- (void)handleAppMessage:(NSData*)messageData
completionHandler:(void (^)(NSData* responseData))completionHandler
{
NSData* response = [NSData dataWithBytesNoCopy:"ok" length:3 freeWhenDone:NO];
completionHandler(response);
NSData* response = [NSData dataWithBytesNoCopy:"ok" length:3 freeWhenDone:NO];
completionHandler(response);
}
- (void)updateNetworkSettings
{
self.reasserting = YES;
__weak LLARPPacketTunnel* weakSelf = self;
// Apple documentation says that setting network settings to nil isn't required before setting it
// to a new value. Apple lies: both end up with a routing table that looks exactly the same (from
// both `netstat -rn` and from everything that happens in `route -n monitor`), but if we don't
// call with nil first then everything fails to route to either lokinet *and* clearnet through the
// exit, so there is apparently some special magic internal Apple state that actually *does*
// require the tunnel settings being reset with nil first.
//
// Thanks for the accurate documentation, Apple.
//
[self setTunnelNetworkSettings:nil
completionHandler:^(NSError* err) {
if (err)
NSLog(@"Failed to clear lokinet tunnel settings: %@", err);
LLARPPacketTunnel* strongSelf = weakSelf;
if (strongSelf)
{
[weakSelf
setTunnelNetworkSettings:strongSelf->settings
completionHandler:^(NSError* err) {
LLARPPacketTunnel* strongSelf = weakSelf;
if (strongSelf)
strongSelf.reasserting = NO;
if (err)
NSLog(@"Failed to reconfigure lokinet tunnel settings: %@", err);
}];
}
}];
self.reasserting = YES;
__weak LLARPPacketTunnel* weakSelf = self;
// Apple documentation says that setting network settings to nil isn't required before setting
// it to a new value. Apple lies: both end up with a routing table that looks exactly the same
// (from both `netstat -rn` and from everything that happens in `route -n monitor`), but if we
// don't call with nil first then everything fails to route to either lokinet *and* clearnet
// through the exit, so there is apparently some special magic internal Apple state that
// actually *does* require the tunnel settings being reset with nil first.
//
// Thanks for the accurate documentation, Apple.
//
[self setTunnelNetworkSettings:nil
completionHandler:^(NSError* err) {
if (err)
NSLog(@"Failed to clear lokinet tunnel settings: %@", err);
LLARPPacketTunnel* strongSelf = weakSelf;
if (strongSelf)
{
[weakSelf setTunnelNetworkSettings:strongSelf->settings
completionHandler:^(NSError* err) {
LLARPPacketTunnel* strongSelf = weakSelf;
if (strongSelf)
strongSelf.reasserting = NO;
if (err)
NSLog(
@"Failed to reconfigure lokinet tunnel settings: "
@"%@",
err);
}];
}
}];
}
@end
#ifdef MACOS_SYSTEM_EXTENSION
int
main()
int main()
{
[NEProvider startSystemExtensionMode];
dispatch_main();
[NEProvider startSystemExtensionMode];
dispatch_main();
}
#endif

@ -7,22 +7,21 @@
namespace llarp::apple
{
struct Context : public llarp::Context
{
std::shared_ptr<vpn::Platform>
makeVPNPlatform() override
struct Context : public llarp::Context
{
return std::make_shared<VPNPlatform>(
*this, m_PacketWriter, m_OnReadable, route_callbacks, callback_context);
}
std::shared_ptr<vpn::Platform> makeVPNPlatform() override
{
return std::make_shared<VPNPlatform>(
*this, m_PacketWriter, m_OnReadable, route_callbacks, callback_context);
}
// Callbacks that must be set for packet handling *before* calling Setup/Configure/Run; the main
// point of these is to get passed through to VPNInterface, which will be called during setup,
// after construction.
VPNInterface::packet_write_callback m_PacketWriter;
VPNInterface::on_readable_callback m_OnReadable;
llarp_route_callbacks route_callbacks{};
void* callback_context = nullptr;
};
// Callbacks that must be set for packet handling *before* calling Setup/Configure/Run; the
// main point of these is to get passed through to VPNInterface, which will be called during
// setup, after construction.
VPNInterface::packet_write_callback m_PacketWriter;
VPNInterface::on_readable_callback m_OnReadable;
llarp_route_callbacks route_callbacks{};
void* callback_context = nullptr;
};
} // namespace llarp::apple

@ -20,192 +20,188 @@
namespace
{
struct instance_data
{
llarp::apple::Context context;
std::thread runner;
packet_writer_callback packet_writer;
start_reading_callback start_reading;
struct instance_data
{
llarp::apple::Context context;
std::thread runner;
packet_writer_callback packet_writer;
start_reading_callback start_reading;
std::weak_ptr<llarp::apple::VPNInterface> iface;
};
std::weak_ptr<llarp::apple::VPNInterface> iface;
};
} // namespace
// Expose this with C linkage so that objective-c can use it
extern "C" const uint16_t dns_trampoline_port = llarp::apple::dns_trampoline_port;
void*
llarp_apple_init(llarp_apple_config* appleconf)
void* llarp_apple_init(llarp_apple_config* appleconf)
{
llarp::log::clear_sinks();
llarp::log::add_sink(std::make_shared<llarp::logging::CallbackSink_mt>(
[](const char* msg, void* nslog) { reinterpret_cast<ns_logger_callback>(nslog)(msg); },
nullptr,
reinterpret_cast<void*>(appleconf->ns_logger)));
llarp::logRingBuffer = std::make_shared<llarp::log::RingBufferSink>(100);
llarp::log::add_sink(llarp::logRingBuffer, llarp::log::DEFAULT_PATTERN_MONO);
try
{
auto config_dir = fs::u8path(appleconf->config_dir);
auto config = std::make_shared<llarp::Config>(config_dir);
fs::path config_path = config_dir / "lokinet.ini";
if (!fs::exists(config_path))
llarp::ensure_config(config_dir, config_path, /*overwrite=*/false, /*asRouter=*/false);
config->load(config_path);
// If no range is specified then go look for a free one, set that in the config, and then return
// it to the caller via the char* parameters.
auto& range = config->network.if_addr;
if (!range.addr.h)
{
if (auto maybe = llarp::net::Platform::Default_ptr()->FindFreeRange())
range = *maybe;
else
throw std::runtime_error{"Could not find any free IP range"};
}
auto addr = llarp::net::TruncateV6(range.addr).ToString();
auto mask = llarp::net::TruncateV6(range.netmask_bits).ToString();
if (addr.size() > 15 || mask.size() > 15)
throw std::runtime_error{"Unexpected non-IPv4 tunnel range configured"};
std::strncpy(appleconf->tunnel_ipv4_ip, addr.c_str(), sizeof(appleconf->tunnel_ipv4_ip));
std::strncpy(
appleconf->tunnel_ipv4_netmask, mask.c_str(), sizeof(appleconf->tunnel_ipv4_netmask));
// TODO: in the future we want to do this properly with our pubkey (see issue #1705), but that's
// going to take a bit more work because we currently can't *get* the (usually) ephemeral pubkey
// at this stage of lokinet configuration. So for now we just stick our IPv4 address into it
// until #1705 gets implemented.
llarp::huint128_t ipv6{
llarp::uint128_t{0xfd2e'6c6f'6b69'0000, llarp::net::TruncateV6(range.addr).h}};
std::strncpy(
appleconf->tunnel_ipv6_ip, ipv6.ToString().c_str(), sizeof(appleconf->tunnel_ipv6_ip));
appleconf->tunnel_ipv6_prefix = 48;
appleconf->upstream_dns[0] = '\0';
for (auto& upstream : config->dns.upstream_dns)
llarp::log::clear_sinks();
llarp::log::add_sink(std::make_shared<llarp::logging::CallbackSink_mt>(
[](const char* msg, void* nslog) { reinterpret_cast<ns_logger_callback>(nslog)(msg); },
nullptr,
reinterpret_cast<void*>(appleconf->ns_logger)));
llarp::logRingBuffer = std::make_shared<llarp::log::RingBufferSink>(100);
llarp::log::add_sink(llarp::logRingBuffer, llarp::log::DEFAULT_PATTERN_MONO);
try
{
if (upstream.isIPv4())
{
std::strcpy(appleconf->upstream_dns, upstream.hostString().c_str());
appleconf->upstream_dns_port = upstream.getPort();
break;
}
}
auto config_dir = fs::u8path(appleconf->config_dir);
auto config = std::make_shared<llarp::Config>(config_dir);
fs::path config_path = config_dir / "lokinet.ini";
if (!fs::exists(config_path))
llarp::ensure_config(config_dir, config_path, /*overwrite=*/false, /*asRouter=*/false);
config->load(config_path);
// If no range is specified then go look for a free one, set that in the config, and then
// return it to the caller via the char* parameters.
auto& range = config->network.if_addr;
if (!range.addr.h)
{
if (auto maybe = llarp::net::Platform::Default_ptr()->FindFreeRange())
range = *maybe;
else
throw std::runtime_error{"Could not find any free IP range"};
}
auto addr = llarp::net::TruncateV6(range.addr).ToString();
auto mask = llarp::net::TruncateV6(range.netmask_bits).ToString();
if (addr.size() > 15 || mask.size() > 15)
throw std::runtime_error{"Unexpected non-IPv4 tunnel range configured"};
std::strncpy(appleconf->tunnel_ipv4_ip, addr.c_str(), sizeof(appleconf->tunnel_ipv4_ip));
std::strncpy(
appleconf->tunnel_ipv4_netmask, mask.c_str(), sizeof(appleconf->tunnel_ipv4_netmask));
// TODO: in the future we want to do this properly with our pubkey (see issue #1705), but
// that's going to take a bit more work because we currently can't *get* the (usually)
// ephemeral pubkey at this stage of lokinet configuration. So for now we just stick our
// IPv4 address into it until #1705 gets implemented.
llarp::huint128_t ipv6{
llarp::uint128_t{0xfd2e'6c6f'6b69'0000, llarp::net::TruncateV6(range.addr).h}};
std::strncpy(
appleconf->tunnel_ipv6_ip, ipv6.ToString().c_str(), sizeof(appleconf->tunnel_ipv6_ip));
appleconf->tunnel_ipv6_prefix = 48;
appleconf->upstream_dns[0] = '\0';
for (auto& upstream : config->dns.upstream_dns)
{
if (upstream.isIPv4())
{
std::strcpy(appleconf->upstream_dns, upstream.hostString().c_str());
appleconf->upstream_dns_port = upstream.getPort();
break;
}
}
#ifdef MACOS_SYSTEM_EXTENSION
std::strncpy(
appleconf->dns_bind_ip,
config->dns.m_bind.front().hostString().c_str(),
sizeof(appleconf->dns_bind_ip));
std::strncpy(
appleconf->dns_bind_ip,
config->dns.m_bind.front().hostString().c_str(),
sizeof(appleconf->dns_bind_ip));
#endif
// If no explicit bootstrap then set the system default one included with the app bundle
if (config->bootstrap.files.empty())
config->bootstrap.files.push_back(fs::u8path(appleconf->default_bootstrap));
// If no explicit bootstrap then set the system default one included with the app bundle
if (config->bootstrap.files.empty())
config->bootstrap.files.push_back(fs::u8path(appleconf->default_bootstrap));
auto inst = std::make_unique<instance_data>();
inst->context.Configure(std::move(config));
inst->context.route_callbacks = appleconf->route_callbacks;
auto inst = std::make_unique<instance_data>();
inst->context.Configure(std::move(config));
inst->context.route_callbacks = appleconf->route_callbacks;
inst->packet_writer = appleconf->packet_writer;
inst->start_reading = appleconf->start_reading;
inst->packet_writer = appleconf->packet_writer;
inst->start_reading = appleconf->start_reading;
return inst.release();
}
catch (const std::exception& e)
{
llarp::LogError("Failed to initialize lokinet from config: ", e.what());
}
return nullptr;
return inst.release();
}
catch (const std::exception& e)
{
llarp::LogError("Failed to initialize lokinet from config: ", e.what());
}
return nullptr;
}
int
llarp_apple_start(void* lokinet, void* callback_context)
int llarp_apple_start(void* lokinet, void* callback_context)
{
auto* inst = static_cast<instance_data*>(lokinet);
auto* inst = static_cast<instance_data*>(lokinet);
inst->context.callback_context = callback_context;
inst->context.m_PacketWriter = [inst, callback_context](
int af_family, void* data, size_t size) {
inst->packet_writer(af_family, data, size, callback_context);
return true;
};
inst->context.m_OnReadable = [inst, callback_context](llarp::apple::VPNInterface& iface) {
inst->iface = iface.weak_from_this();
inst->start_reading(callback_context);
};
std::promise<void> result;
inst->runner = std::thread{[inst, &result] {
const llarp::RuntimeOptions opts{};
try
{
inst->context.Setup(opts);
}
catch (...)
{
result.set_exception(std::current_exception());
return;
}
result.set_value();
inst->context.Run(opts);
}};
inst->context.callback_context = callback_context;
inst->context.m_PacketWriter = [inst, callback_context](int af_family, void* data, size_t size) {
inst->packet_writer(af_family, data, size, callback_context);
return true;
};
inst->context.m_OnReadable = [inst, callback_context](llarp::apple::VPNInterface& iface) {
inst->iface = iface.weak_from_this();
inst->start_reading(callback_context);
};
std::promise<void> result;
inst->runner = std::thread{[inst, &result] {
const llarp::RuntimeOptions opts{};
try
{
inst->context.Setup(opts);
result.get_future().get();
}
catch (...)
catch (const std::exception& e)
{
result.set_exception(std::current_exception());
return;
llarp::LogError("Failed to initialize lokinet: ", e.what());
return -1;
}
result.set_value();
inst->context.Run(opts);
}};
try
{
result.get_future().get();
}
catch (const std::exception& e)
{
llarp::LogError("Failed to initialize lokinet: ", e.what());
return -1;
}
return 0;
return 0;
}
uv_loop_t*
llarp_apple_get_uv_loop(void* lokinet)
uv_loop_t* llarp_apple_get_uv_loop(void* lokinet)
{
auto& inst = *static_cast<instance_data*>(lokinet);
auto uvw = inst.context.loop->MaybeGetUVWLoop();
assert(uvw);
return uvw->raw();
auto& inst = *static_cast<instance_data*>(lokinet);
auto uvw = inst.context.loop->MaybeGetUVWLoop();
assert(uvw);
return uvw->raw();
}
int
llarp_apple_incoming(void* lokinet, const llarp_incoming_packet* packets, size_t size)
int llarp_apple_incoming(void* lokinet, const llarp_incoming_packet* packets, size_t size)
{
auto& inst = *static_cast<instance_data*>(lokinet);
auto iface = inst.iface.lock();
if (!iface)
return -1;
int count = 0;
for (size_t i = 0; i < size; i++)
{
llarp_buffer_t buf{static_cast<const uint8_t*>(packets[i].bytes), packets[i].size};
if (iface->OfferReadPacket(buf))
count++;
else
llarp::LogError("invalid IP packet: ", llarp::buffer_printer(buf));
}
iface->MaybeWakeUpperLayers();
return count;
auto& inst = *static_cast<instance_data*>(lokinet);
auto iface = inst.iface.lock();
if (!iface)
return -1;
int count = 0;
for (size_t i = 0; i < size; i++)
{
llarp_buffer_t buf{static_cast<const uint8_t*>(packets[i].bytes), packets[i].size};
if (iface->OfferReadPacket(buf))
count++;
else
llarp::LogError("invalid IP packet: ", llarp::buffer_printer(buf));
}
iface->MaybeWakeUpperLayers();
return count;
}
void
llarp_apple_shutdown(void* lokinet)
void llarp_apple_shutdown(void* lokinet)
{
auto* inst = static_cast<instance_data*>(lokinet);
auto* inst = static_cast<instance_data*>(lokinet);
inst->context.CloseAsync();
inst->context.Wait();
inst->runner.join();
delete inst;
inst->context.CloseAsync();
inst->context.Wait();
inst->runner.join();
delete inst;
}

@ -12,157 +12,153 @@ extern "C"
#include <unistd.h>
#include <uv.h>
// Port (on localhost) for our DNS trampoline for bouncing DNS requests through the exit route
// when in exit mode.
extern const uint16_t dns_trampoline_port;
/// C callback function for us to invoke when we need to write a packet
typedef void (*packet_writer_callback)(int af, const void* data, size_t size, void* ctx);
/// C callback function to invoke once we are ready to start receiving packets
typedef void (*start_reading_callback)(void* ctx);
/// C callback that bridges things into NSLog
typedef void (*ns_logger_callback)(const char* msg);
/// C callbacks to add/remove specific and default routes to the tunnel
typedef void (*llarp_route_ipv4_callback)(const char* addr, const char* netmask, void* ctx);
typedef void (*llarp_route_ipv6_callback)(const char* addr, int prefix, void* ctx);
typedef void (*llarp_default_route_callback)(void* ctx);
typedef struct llarp_route_callbacks
{
/// Callback invoked to set up an IPv4 range that should be routed through the tunnel
/// interface. Called with the address and netmask.
llarp_route_ipv4_callback add_ipv4_route;
/// Callback invoked to set the tunnel as the default IPv4 route.
llarp_default_route_callback add_ipv4_default_route;
/// Callback invoked to remove a specific range from the tunnel IPv4 routes. Called with the
/// address and netmask.
llarp_route_ipv4_callback del_ipv4_route;
/// Callback invoked to set up an IPv6 range that should be routed through the tunnel
/// interface. Called with the address and netmask.
llarp_route_ipv6_callback add_ipv6_route;
/// Callback invoked to remove a specific range from the tunnel IPv6 routes. Called with the
/// address and netmask.
llarp_route_ipv6_callback del_ipv6_route;
/// Callback invoked to set the tunnel as the default IPv4/IPv6 route.
llarp_default_route_callback add_default_route;
/// Callback invoked to remove the tunnel as the default IPv4/IPv6 route.
llarp_default_route_callback del_default_route;
} llarp_route_callbacks;
/// Pack of crap to be passed into llarp_apple_init to initialize
typedef struct llarp_apple_config
{
/// lokinet configuration directory, expected to be the application-specific "home" directory,
/// which is where state files are stored and the lokinet.ini will be loaded (or created if it
/// doesn't exist).
const char* config_dir;
/// path to the default bootstrap.signed file included in installation, which will be used by
/// default when no specific bootstrap is in the config file.
const char* default_bootstrap;
/// llarp_apple_init writes the IP address for the primary tunnel IP address here,
/// null-terminated.
char tunnel_ipv4_ip[INET_ADDRSTRLEN];
/// llarp_apple_init writes the netmask of the tunnel address here, null-terminated.
char tunnel_ipv4_netmask[INET_ADDRSTRLEN];
/// Writes the IPv6 address for the tunnel here, null-terminated.
char tunnel_ipv6_ip[INET6_ADDRSTRLEN];
/// IPv6 address prefix.
uint16_t tunnel_ipv6_prefix;
/// The first upstream DNS server's IPv4 address the OS should use when in exit mode.
/// (Currently on mac in exit mode we only support querying the first such configured server).
char upstream_dns[INET_ADDRSTRLEN];
uint16_t upstream_dns_port;
// Port (on localhost) for our DNS trampoline for bouncing DNS requests through the exit route
// when in exit mode.
extern const uint16_t dns_trampoline_port;
/// C callback function for us to invoke when we need to write a packet
typedef void (*packet_writer_callback)(int af, const void* data, size_t size, void* ctx);
/// C callback function to invoke once we are ready to start receiving packets
typedef void (*start_reading_callback)(void* ctx);
/// C callback that bridges things into NSLog
typedef void (*ns_logger_callback)(const char* msg);
/// C callbacks to add/remove specific and default routes to the tunnel
typedef void (*llarp_route_ipv4_callback)(const char* addr, const char* netmask, void* ctx);
typedef void (*llarp_route_ipv6_callback)(const char* addr, int prefix, void* ctx);
typedef void (*llarp_default_route_callback)(void* ctx);
typedef struct llarp_route_callbacks
{
/// Callback invoked to set up an IPv4 range that should be routed through the tunnel
/// interface. Called with the address and netmask.
llarp_route_ipv4_callback add_ipv4_route;
/// Callback invoked to set the tunnel as the default IPv4 route.
llarp_default_route_callback add_ipv4_default_route;
/// Callback invoked to remove a specific range from the tunnel IPv4 routes. Called with
/// the address and netmask.
llarp_route_ipv4_callback del_ipv4_route;
/// Callback invoked to set up an IPv6 range that should be routed through the tunnel
/// interface. Called with the address and netmask.
llarp_route_ipv6_callback add_ipv6_route;
/// Callback invoked to remove a specific range from the tunnel IPv6 routes. Called with
/// the address and netmask.
llarp_route_ipv6_callback del_ipv6_route;
/// Callback invoked to set the tunnel as the default IPv4/IPv6 route.
llarp_default_route_callback add_default_route;
/// Callback invoked to remove the tunnel as the default IPv4/IPv6 route.
llarp_default_route_callback del_default_route;
} llarp_route_callbacks;
/// Pack of crap to be passed into llarp_apple_init to initialize
typedef struct llarp_apple_config
{
/// lokinet configuration directory, expected to be the application-specific "home"
/// directory, which is where state files are stored and the lokinet.ini will be loaded (or
/// created if it doesn't exist).
const char* config_dir;
/// path to the default bootstrap.signed file included in installation, which will be used
/// by default when no specific bootstrap is in the config file.
const char* default_bootstrap;
/// llarp_apple_init writes the IP address for the primary tunnel IP address here,
/// null-terminated.
char tunnel_ipv4_ip[INET_ADDRSTRLEN];
/// llarp_apple_init writes the netmask of the tunnel address here, null-terminated.
char tunnel_ipv4_netmask[INET_ADDRSTRLEN];
/// Writes the IPv6 address for the tunnel here, null-terminated.
char tunnel_ipv6_ip[INET6_ADDRSTRLEN];
/// IPv6 address prefix.
uint16_t tunnel_ipv6_prefix;
/// The first upstream DNS server's IPv4 address the OS should use when in exit mode.
/// (Currently on mac in exit mode we only support querying the first such configured
/// server).
char upstream_dns[INET_ADDRSTRLEN];
uint16_t upstream_dns_port;
#ifdef MACOS_SYSTEM_EXTENSION
/// DNS bind IP; llarp_apple_init writes the lokinet config value here so that we know (in Apple
/// API code) what to set DNS to when lokinet gets turned on. Null terminated.
char dns_bind_ip[INET_ADDRSTRLEN];
/// DNS bind IP; llarp_apple_init writes the lokinet config value here so that we know (in
/// Apple API code) what to set DNS to when lokinet gets turned on. Null terminated.
char dns_bind_ip[INET_ADDRSTRLEN];
#endif
/// \defgroup callbacks Callbacks
/// Callbacks we invoke for various operations that require glue into the Apple network
/// extension APIs. All of these except for ns_logger are passed the pointer provided to
/// llarp_apple_start when invoked.
/// @{
/// simple wrapper around NSLog for lokinet message logging
ns_logger_callback ns_logger;
/// C function callback that will be called when we need to write a packet to the packet
/// tunnel. Will be passed AF_INET or AF_INET6, a void pointer to the data, and the size of
/// the data in bytes.
packet_writer_callback packet_writer;
/// C function callback that will be called when lokinet is setup and ready to start receiving
/// packets from the packet tunnel. This should set up the read handler to deliver packets
/// via llarp_apple_incoming.
start_reading_callback start_reading;
/// Callbacks invoked to add/remove routes to the tunnel.
llarp_route_callbacks route_callbacks;
/// @}
} llarp_apple_config;
/// Initializes a lokinet instance by initializing various objects and loading the configuration
/// (if <config_dir>/lokinet.ini exists). Does not actually start lokinet (call llarp_apple_start
/// for that).
///
/// Returns NULL if there was a problem initializing/loading the configuration, otherwise returns
/// an opaque void pointer that should be passed into the other llarp_apple_* functions.
///
/// \param config pointer to a llarp_apple_config where we get the various settings needed
/// and return the ip/mask/dns fields needed for the tunnel.
void*
llarp_apple_init(llarp_apple_config* config);
/// Starts the lokinet instance in a new thread.
///
/// \param lokinet the void pointer returned by llarp_apple_init
///
/// \param callback_context Opaque pointer that is passed into the various callbacks provided to
/// llarp_apple_init. This code does nothing with this pointer aside from passing it through to
/// callbacks.
///
/// \returns 0 on succesful startup, -1 on failure.
int
llarp_apple_start(void* lokinet, void* callback_context);
/// Returns a pointer to the uv event loop. Must have called llarp_apple_start already.
uv_loop_t*
llarp_apple_get_uv_loop(void* lokinet);
/// Struct of packet data; a C array of tests gets passed to llarp_apple_incoming
typedef struct llarp_incoming_packet
{
const void* bytes;
size_t size;
} llarp_incoming_packet;
/// Called to deliver one or more incoming packets from the apple layer into lokinet. Takes a C
/// array of `llarp_incoming_packets` with pointers/sizes set to the individual new packets that
/// have arrived.
///
/// Returns the number of valid packets on success (which can be less than the number of provided
/// packets, if some failed to parse), or -1 if there is no current active VPNInterface associated
/// with the lokinet instance (which generally means llarp_apple_start wasn't called or failed, or
/// lokinet is in the process of shutting down).
int
llarp_apple_incoming(void* lokinet, const llarp_incoming_packet* packets, size_t size);
/// Stops a lokinet instance created with `llarp_apple_initialize`. This waits for lokinet to
/// shut down and rejoins the thread. After this call the given pointer is no longer valid.
void
llarp_apple_shutdown(void* lokinet);
/// \defgroup callbacks Callbacks
/// Callbacks we invoke for various operations that require glue into the Apple network
/// extension APIs. All of these except for ns_logger are passed the pointer provided to
/// llarp_apple_start when invoked.
/// @{
/// simple wrapper around NSLog for lokinet message logging
ns_logger_callback ns_logger;
/// C function callback that will be called when we need to write a packet to the packet
/// tunnel. Will be passed AF_INET or AF_INET6, a void pointer to the data, and the size of
/// the data in bytes.
packet_writer_callback packet_writer;
/// C function callback that will be called when lokinet is setup and ready to start
/// receiving packets from the packet tunnel. This should set up the read handler to
/// deliver packets via llarp_apple_incoming.
start_reading_callback start_reading;
/// Callbacks invoked to add/remove routes to the tunnel.
llarp_route_callbacks route_callbacks;
/// @}
} llarp_apple_config;
/// Initializes a lokinet instance by initializing various objects and loading the configuration
/// (if <config_dir>/lokinet.ini exists). Does not actually start lokinet (call
/// llarp_apple_start for that).
///
/// Returns NULL if there was a problem initializing/loading the configuration, otherwise
/// returns an opaque void pointer that should be passed into the other llarp_apple_* functions.
///
/// \param config pointer to a llarp_apple_config where we get the various settings needed
/// and return the ip/mask/dns fields needed for the tunnel.
void* llarp_apple_init(llarp_apple_config* config);
/// Starts the lokinet instance in a new thread.
///
/// \param lokinet the void pointer returned by llarp_apple_init
///
/// \param callback_context Opaque pointer that is passed into the various callbacks provided to
/// llarp_apple_init. This code does nothing with this pointer aside from passing it through to
/// callbacks.
///
/// \returns 0 on succesful startup, -1 on failure.
int llarp_apple_start(void* lokinet, void* callback_context);
/// Returns a pointer to the uv event loop. Must have called llarp_apple_start already.
uv_loop_t* llarp_apple_get_uv_loop(void* lokinet);
/// Struct of packet data; a C array of tests gets passed to llarp_apple_incoming
typedef struct llarp_incoming_packet
{
const void* bytes;
size_t size;
} llarp_incoming_packet;
/// Called to deliver one or more incoming packets from the apple layer into lokinet. Takes a C
/// array of `llarp_incoming_packets` with pointers/sizes set to the individual new packets that
/// have arrived.
///
/// Returns the number of valid packets on success (which can be less than the number of
/// provided packets, if some failed to parse), or -1 if there is no current active VPNInterface
/// associated with the lokinet instance (which generally means llarp_apple_start wasn't called
/// or failed, or lokinet is in the process of shutting down).
int llarp_apple_incoming(void* lokinet, const llarp_incoming_packet* packets, size_t size);
/// Stops a lokinet instance created with `llarp_apple_initialize`. This waits for lokinet to
/// shut down and rejoins the thread. After this call the given pointer is no longer valid.
void llarp_apple_shutdown(void* lokinet);
#ifdef __cplusplus
} // extern "C"

@ -8,98 +8,94 @@
namespace llarp::apple
{
void
RouteManager::check_trampoline(bool enable)
{
if (trampoline_active == enable)
return;
auto router = context.router;
if (!router)
void RouteManager::check_trampoline(bool enable)
{
LogError("Cannot reconfigure to use DNS trampoline: no router");
return;
}
if (trampoline_active == enable)
return;
auto router = context.router;
if (!router)
{
LogError("Cannot reconfigure to use DNS trampoline: no router");
return;
}
std::shared_ptr<llarp::handlers::TunEndpoint> tun;
router->hidden_service_context().ForEachService([&tun](const auto& /*name*/, const auto ep) {
tun = std::dynamic_pointer_cast<llarp::handlers::TunEndpoint>(ep);
return !tun;
});
std::shared_ptr<llarp::handlers::TunEndpoint> tun;
router->hidden_service_context().ForEachService(
[&tun](const auto& /*name*/, const auto ep) {
tun = std::dynamic_pointer_cast<llarp::handlers::TunEndpoint>(ep);
return !tun;
});
if (!tun)
{
LogError("Cannot reconfigure to use DNS trampoline: no tun endpoint found (!?)");
return;
}
if (!tun)
{
LogError("Cannot reconfigure to use DNS trampoline: no tun endpoint found (!?)");
return;
}
if (enable)
tun->ReconfigureDNS({SockAddr{127, 0, 0, 1, {dns_trampoline_port}}});
else
tun->ReconfigureDNS(router->config()->dns.upstream_dns);
if (enable)
tun->ReconfigureDNS({SockAddr{127, 0, 0, 1, {dns_trampoline_port}}});
else
tun->ReconfigureDNS(router->config()->dns.upstream_dns);
trampoline_active = enable;
}
trampoline_active = enable;
}
void
RouteManager::add_default_route_via_interface(vpn::NetworkInterface&)
{
check_trampoline(true);
if (callback_context and route_callbacks.add_default_route)
route_callbacks.add_default_route(callback_context);
}
void RouteManager::add_default_route_via_interface(vpn::NetworkInterface&)
{
check_trampoline(true);
if (callback_context and route_callbacks.add_default_route)
route_callbacks.add_default_route(callback_context);
}
void
RouteManager::delete_default_route_via_interface(vpn::NetworkInterface&)
{
check_trampoline(false);
if (callback_context and route_callbacks.del_default_route)
route_callbacks.del_default_route(callback_context);
}
void RouteManager::delete_default_route_via_interface(vpn::NetworkInterface&)
{
check_trampoline(false);
if (callback_context and route_callbacks.del_default_route)
route_callbacks.del_default_route(callback_context);
}
void
RouteManager::add_route_via_interface(vpn::NetworkInterface&, IPRange range)
{
check_trampoline(true);
if (callback_context)
void RouteManager::add_route_via_interface(vpn::NetworkInterface&, IPRange range)
{
if (range.IsV4())
{
if (route_callbacks.add_ipv4_route)
route_callbacks.add_ipv4_route(
range.BaseAddressString().c_str(),
net::TruncateV6(range.netmask_bits).ToString().c_str(),
callback_context);
}
else
{
if (route_callbacks.add_ipv6_route)
route_callbacks.add_ipv6_route(
range.BaseAddressString().c_str(), range.HostmaskBits(), callback_context);
}
check_trampoline(true);
if (callback_context)
{
if (range.IsV4())
{
if (route_callbacks.add_ipv4_route)
route_callbacks.add_ipv4_route(
range.BaseAddressString().c_str(),
net::TruncateV6(range.netmask_bits).ToString().c_str(),
callback_context);
}
else
{
if (route_callbacks.add_ipv6_route)
route_callbacks.add_ipv6_route(
range.BaseAddressString().c_str(), range.HostmaskBits(), callback_context);
}
}
}
}
void
RouteManager::delete_route_via_interface(vpn::NetworkInterface&, IPRange range)
{
check_trampoline(false);
if (callback_context)
void RouteManager::delete_route_via_interface(vpn::NetworkInterface&, IPRange range)
{
if (range.IsV4())
{
if (route_callbacks.del_ipv4_route)
route_callbacks.del_ipv4_route(
range.BaseAddressString().c_str(),
net::TruncateV6(range.netmask_bits).ToString().c_str(),
callback_context);
}
else
{
if (route_callbacks.del_ipv6_route)
route_callbacks.del_ipv6_route(
range.BaseAddressString().c_str(), range.HostmaskBits(), callback_context);
}
check_trampoline(false);
if (callback_context)
{
if (range.IsV4())
{
if (route_callbacks.del_ipv4_route)
route_callbacks.del_ipv4_route(
range.BaseAddressString().c_str(),
net::TruncateV6(range.netmask_bits).ToString().c_str(),
callback_context);
}
else
{
if (route_callbacks.del_ipv6_route)
route_callbacks.del_ipv6_route(
range.BaseAddressString().c_str(), range.HostmaskBits(), callback_context);
}
}
}
}
} // namespace llarp::apple

@ -7,51 +7,44 @@
namespace llarp::apple
{
class RouteManager final : public llarp::vpn::AbstractRouteManager
{
public:
RouteManager(llarp::Context& ctx, llarp_route_callbacks rcs, void* callback_context)
: context{ctx}, callback_context{callback_context}, route_callbacks{std::move(rcs)}
{}
class RouteManager final : public llarp::vpn::AbstractRouteManager
{
public:
RouteManager(llarp::Context& ctx, llarp_route_callbacks rcs, void* callback_context)
: context{ctx}, callback_context{callback_context}, route_callbacks{std::move(rcs)}
{}
/// These are called for poking route holes, but we don't have to do that at all on macos
/// because the appex isn't subject to its own rules.
void
add_route(oxen::quic::Address /*ip*/, oxen::quic::Address /*gateway*/) override
{}
/// These are called for poking route holes, but we don't have to do that at all on macos
/// because the appex isn't subject to its own rules.
void add_route(oxen::quic::Address /*ip*/, oxen::quic::Address /*gateway*/) override
{}
void
delete_route(oxen::quic::Address /*ip*/, oxen::quic::Address /*gateway*/) override
{}
void delete_route(oxen::quic::Address /*ip*/, oxen::quic::Address /*gateway*/) override
{}
void
add_default_route_via_interface(vpn::NetworkInterface& vpn) override;
void add_default_route_via_interface(vpn::NetworkInterface& vpn) override;
void
delete_default_route_via_interface(vpn::NetworkInterface& vpn) override;
void delete_default_route_via_interface(vpn::NetworkInterface& vpn) override;
void
add_route_via_interface(vpn::NetworkInterface& vpn, IPRange range) override;
void add_route_via_interface(vpn::NetworkInterface& vpn, IPRange range) override;
void
delete_route_via_interface(vpn::NetworkInterface& vpn, IPRange range) override;
void delete_route_via_interface(vpn::NetworkInterface& vpn, IPRange range) override;
std::vector<oxen::quic::Address>
get_non_interface_gateways(vpn::NetworkInterface& /*vpn*/) override
{
// We can't get this on mac from our sandbox, but we don't actually need it because we
// ignore the gateway for AddRoute/DelRoute anyway, so just return a zero IP.
return std::vector<oxen::quic::Address>{};
}
private:
llarp::Context& context;
bool trampoline_active = false;
void
check_trampoline(bool enable);
void* callback_context = nullptr;
llarp_route_callbacks route_callbacks;
};
std::vector<oxen::quic::Address> get_non_interface_gateways(
vpn::NetworkInterface& /*vpn*/) override
{
// We can't get this on mac from our sandbox, but we don't actually need it because we
// ignore the gateway for AddRoute/DelRoute anyway, so just return a zero IP.
return std::vector<oxen::quic::Address>{};
}
private:
llarp::Context& context;
bool trampoline_active = false;
void check_trampoline(bool enable);
void* callback_context = nullptr;
llarp_route_callbacks route_callbacks;
};
} // namespace llarp::apple

@ -7,55 +7,50 @@
namespace llarp::apple
{
VPNInterface::VPNInterface(
Context& ctx,
packet_write_callback packet_writer,
on_readable_callback on_readable,
Router* router)
: vpn::NetworkInterface{{}}
, m_PacketWriter{std::move(packet_writer)}
, m_OnReadable{std::move(on_readable)}
, _router{router}
{
ctx.loop->call_soon([this] { m_OnReadable(*this); });
}
bool
VPNInterface::OfferReadPacket(const llarp_buffer_t& buf)
{
llarp::net::IPPacket pkt;
if (!pkt.Load(buf))
return false;
m_ReadQueue.tryPushBack(std::move(pkt));
return true;
}
void
VPNInterface::MaybeWakeUpperLayers() const
{
_router->TriggerPump();
}
int
VPNInterface::PollFD() const
{
return -1;
}
net::IPPacket
VPNInterface::ReadNextPacket()
{
net::IPPacket pkt{};
if (not m_ReadQueue.empty())
pkt = m_ReadQueue.popFront();
return pkt;
}
bool
VPNInterface::WritePacket(net::IPPacket pkt)
{
int af_family = pkt.IsV6() ? AF_INET6 : AF_INET;
return m_PacketWriter(af_family, pkt.data(), pkt.size());
}
VPNInterface::VPNInterface(
Context& ctx,
packet_write_callback packet_writer,
on_readable_callback on_readable,
Router* router)
: vpn::NetworkInterface{{}},
m_PacketWriter{std::move(packet_writer)},
m_OnReadable{std::move(on_readable)},
_router{router}
{
ctx.loop->call_soon([this] { m_OnReadable(*this); });
}
bool VPNInterface::OfferReadPacket(const llarp_buffer_t& buf)
{
llarp::net::IPPacket pkt;
if (!pkt.Load(buf))
return false;
m_ReadQueue.tryPushBack(std::move(pkt));
return true;
}
void VPNInterface::MaybeWakeUpperLayers() const
{
_router->TriggerPump();
}
int VPNInterface::PollFD() const
{
return -1;
}
net::IPPacket VPNInterface::ReadNextPacket()
{
net::IPPacket pkt{};
if (not m_ReadQueue.empty())
pkt = m_ReadQueue.popFront();
return pkt;
}
bool VPNInterface::WritePacket(net::IPPacket pkt)
{
int af_family = pkt.IsV6() ? AF_INET6 : AF_INET;
return m_PacketWriter(af_family, pkt.data(), pkt.size());
}
} // namespace llarp::apple

@ -8,50 +8,45 @@
namespace llarp::apple
{
struct Context;
struct Context;
class VPNInterface final : public vpn::NetworkInterface,
public std::enable_shared_from_this<VPNInterface>
{
public:
using packet_write_callback = std::function<bool(int af_family, void* data, int size)>;
using on_readable_callback = std::function<void(VPNInterface&)>;
class VPNInterface final : public vpn::NetworkInterface,
public std::enable_shared_from_this<VPNInterface>
{
public:
using packet_write_callback = std::function<bool(int af_family, void* data, int size)>;
using on_readable_callback = std::function<void(VPNInterface&)>;
explicit VPNInterface(
Context& ctx,
packet_write_callback packet_writer,
on_readable_callback on_readable,
Router* router);
explicit VPNInterface(
Context& ctx,
packet_write_callback packet_writer,
on_readable_callback on_readable,
Router* router);
// Method to call when a packet has arrived to deliver the packet to lokinet
bool
OfferReadPacket(const llarp_buffer_t& buf);
// Method to call when a packet has arrived to deliver the packet to lokinet
bool OfferReadPacket(const llarp_buffer_t& buf);
int
PollFD() const override;
int PollFD() const override;
net::IPPacket
ReadNextPacket() override;
net::IPPacket ReadNextPacket() override;
bool
WritePacket(net::IPPacket pkt) override;
bool WritePacket(net::IPPacket pkt) override;
void
MaybeWakeUpperLayers() const override;
void MaybeWakeUpperLayers() const override;
private:
// Function for us to call when we have a packet to emit. Should return true if the packet was
// handed off to the OS successfully.
packet_write_callback m_PacketWriter;
private:
// Function for us to call when we have a packet to emit. Should return true if the packet
// was handed off to the OS successfully.
packet_write_callback m_PacketWriter;
// Called when we are ready to start reading packets
on_readable_callback m_OnReadable;
// Called when we are ready to start reading packets
on_readable_callback m_OnReadable;
static inline constexpr auto PacketQueueSize = 1024;
static inline constexpr auto PacketQueueSize = 1024;
thread::Queue<net::IPPacket> m_ReadQueue{PacketQueueSize};
thread::Queue<net::IPPacket> m_ReadQueue{PacketQueueSize};
Router* const _router;
};
Router* const _router;
};
} // namespace llarp::apple

@ -4,21 +4,21 @@
namespace llarp::apple
{
VPNPlatform::VPNPlatform(
Context& ctx,
VPNInterface::packet_write_callback packet_writer,
VPNInterface::on_readable_callback on_readable,
llarp_route_callbacks route_callbacks,
void* callback_context)
: _context{ctx}
, _route_manager{ctx, std::move(route_callbacks), callback_context}
, _packet_writer{std::move(packet_writer)}
, _read_cb{std::move(on_readable)}
{}
VPNPlatform::VPNPlatform(
Context& ctx,
VPNInterface::packet_write_callback packet_writer,
VPNInterface::on_readable_callback on_readable,
llarp_route_callbacks route_callbacks,
void* callback_context)
: _context{ctx},
_route_manager{ctx, std::move(route_callbacks), callback_context},
_packet_writer{std::move(packet_writer)},
_read_cb{std::move(on_readable)}
{}
std::shared_ptr<vpn::NetworkInterface>
VPNPlatform::ObtainInterface(vpn::InterfaceInfo, Router* router)
{
return std::make_shared<VPNInterface>(_context, _packet_writer, _read_cb, router);
}
std::shared_ptr<vpn::NetworkInterface> VPNPlatform::ObtainInterface(
vpn::InterfaceInfo, Router* router)
{
return std::make_shared<VPNInterface>(_context, _packet_writer, _read_cb, router);
}
} // namespace llarp::apple

@ -7,30 +7,29 @@
namespace llarp::apple
{
class VPNPlatform final : public vpn::Platform
{
public:
explicit VPNPlatform(
Context& ctx,
VPNInterface::packet_write_callback packet_writer,
VPNInterface::on_readable_callback on_readable,
llarp_route_callbacks route_callbacks,
void* callback_context);
class VPNPlatform final : public vpn::Platform
{
public:
explicit VPNPlatform(
Context& ctx,
VPNInterface::packet_write_callback packet_writer,
VPNInterface::on_readable_callback on_readable,
llarp_route_callbacks route_callbacks,
void* callback_context);
std::shared_ptr<vpn::NetworkInterface>
ObtainInterface(vpn::InterfaceInfo, Router*) override;
std::shared_ptr<vpn::NetworkInterface> ObtainInterface(
vpn::InterfaceInfo, Router*) override;
vpn::AbstractRouteManager&
RouteManager() override
{
return _route_manager;
}
vpn::AbstractRouteManager& RouteManager() override
{
return _route_manager;
}
private:
Context& _context;
apple::RouteManager _route_manager;
VPNInterface::packet_write_callback _packet_writer;
VPNInterface::on_readable_callback _read_cb;
};
private:
Context& _context;
apple::RouteManager _route_manager;
VPNInterface::packet_write_callback _packet_writer;
VPNInterface::on_readable_callback _read_cb;
};
} // namespace llarp::apple

@ -6,164 +6,157 @@
namespace llarp
{
bool
BootstrapList::bt_decode(std::string_view buf)
{
const auto& f = buf.front();
switch (f)
{
case 'l':
return bt_decode(oxenc::bt_list_consumer{buf});
case 'd':
return bt_decode(oxenc::bt_dict_consumer{buf});
default:
log::critical(logcat, "Unable to parse bootstrap as bt list or dict!");
return false;
}
}
bool
BootstrapList::bt_decode(oxenc::bt_list_consumer btlc)
{
try
{
while (not btlc.is_finished())
emplace(btlc.consume_dict_data());
}
catch (...)
bool BootstrapList::bt_decode(std::string_view buf)
{
log::warning(logcat, "Unable to decode bootstrap RemoteRC");
return false;
const auto& f = buf.front();
switch (f)
{
case 'l':
return bt_decode(oxenc::bt_list_consumer{buf});
case 'd':
return bt_decode(oxenc::bt_dict_consumer{buf});
default:
log::critical(logcat, "Unable to parse bootstrap as bt list or dict!");
return false;
}
}
_curr = begin();
return true;
}
bool
BootstrapList::bt_decode(oxenc::bt_dict_consumer btdc)
{
try
{
emplace(btdc);
}
catch (const std::exception& e)
bool BootstrapList::bt_decode(oxenc::bt_list_consumer btlc)
{
log::warning(logcat, "Unable to decode bootstrap RemoteRC: {}", e.what());
return false;
try
{
while (not btlc.is_finished())
emplace(btlc.consume_dict_data());
}
catch (...)
{
log::warning(logcat, "Unable to decode bootstrap RemoteRC");
return false;
}
_curr = begin();
return true;
}
_curr = begin();
return true;
}
bool
BootstrapList::contains(const RouterID& rid) const
{
for (const auto& it : *this)
bool BootstrapList::bt_decode(oxenc::bt_dict_consumer btdc)
{
if (it.router_id() == rid)
try
{
emplace(btdc);
}
catch (const std::exception& e)
{
log::warning(logcat, "Unable to decode bootstrap RemoteRC: {}", e.what());
return false;
}
_curr = begin();
return true;
}
return false;
}
bool
BootstrapList::contains(const RemoteRC& rc) const
{
return count(rc);
}
std::string_view
BootstrapList::bt_encode() const
{
oxenc::bt_list_producer btlp{};
for (const auto& it : *this)
btlp.append(it.view());
bool BootstrapList::contains(const RouterID& rid) const
{
for (const auto& it : *this)
{
if (it.router_id() == rid)
return true;
}
return btlp.view();
}
return false;
}
void
BootstrapList::populate_bootstraps(
std::vector<fs::path> paths, const fs::path& def, bool load_fallbacks)
{
for (const auto& f : paths)
bool BootstrapList::contains(const RemoteRC& rc) const
{
// TESTNET: TODO: revise fucked config
log::debug(logcat, "Loading BootstrapRC from file at path:{}", f);
if (not read_from_file(f))
throw std::invalid_argument{"User-provided BootstrapRC is invalid!"};
return count(rc);
}
if (empty())
std::string_view BootstrapList::bt_encode() const
{
log::debug(
logcat,
"BootstrapRC list empty; looking for default BootstrapRC from file at path:{}",
def);
read_from_file(def);
oxenc::bt_list_producer btlp{};
for (const auto& it : *this)
btlp.append(it.view());
return btlp.view();
}
for (auto itr = begin(); itr != end(); ++itr)
void BootstrapList::populate_bootstraps(
std::vector<fs::path> paths, const fs::path& def, bool load_fallbacks)
{
if (RouterContact::is_obsolete(*itr)) // can move this into ::read_from_file
{
log::critical(logcat, "Deleting obsolete BootstrapRC (rid:{})", itr->router_id());
itr = erase(itr);
continue;
}
for (const auto& f : paths)
{
// TESTNET: TODO: revise fucked config
log::debug(logcat, "Loading BootstrapRC from file at path:{}", f);
if (not read_from_file(f))
throw std::invalid_argument{"User-provided BootstrapRC is invalid!"};
}
if (empty())
{
log::debug(
logcat,
"BootstrapRC list empty; looking for default BootstrapRC from file at path:{}",
def);
read_from_file(def);
}
for (auto itr = begin(); itr != end(); ++itr)
{
if (RouterContact::is_obsolete(*itr)) // can move this into ::read_from_file
{
log::critical(logcat, "Deleting obsolete BootstrapRC (rid:{})", itr->router_id());
itr = erase(itr);
continue;
}
}
if (empty() and load_fallbacks)
{
log::critical(logcat, "BootstrapRC list empty; loading fallbacks...");
auto fallbacks = llarp::load_bootstrap_fallbacks();
if (auto itr = fallbacks.find(RouterContact::ACTIVE_NETID); itr != fallbacks.end())
{
log::critical(
logcat, "Loading {} default fallback bootstrap router(s)!", itr->second.size());
merge(itr->second);
}
if (empty())
{
log::error(
logcat,
"No Bootstrap routers were loaded. The default Bootstrap file {} does not "
"exist, and "
"loading fallback Bootstrap RCs failed.",
def);
throw std::runtime_error("No Bootstrap nodes available.");
}
}
log::critical(logcat, "We have {} Bootstrap router(s)!", size());
_curr = begin();
}
if (empty() and load_fallbacks)
bool BootstrapList::read_from_file(const fs::path& fpath)
{
log::critical(logcat, "BootstrapRC list empty; loading fallbacks...");
auto fallbacks = llarp::load_bootstrap_fallbacks();
bool result = false;
if (auto itr = fallbacks.find(RouterContact::ACTIVE_NETID); itr != fallbacks.end())
{
log::critical(
logcat, "Loading {} default fallback bootstrap router(s)!", itr->second.size());
merge(itr->second);
}
if (empty())
{
log::error(
logcat,
"No Bootstrap routers were loaded. The default Bootstrap file {} does not exist, and "
"loading fallback Bootstrap RCs failed.",
def);
throw std::runtime_error("No Bootstrap nodes available.");
}
}
if (not fs::exists(fpath))
{
log::critical(logcat, "Bootstrap RC file non-existant at path:{}", fpath);
return result;
}
log::critical(logcat, "We have {} Bootstrap router(s)!", size());
_curr = begin();
}
auto content = util::file_to_string(fpath);
result = bt_decode(content);
bool
BootstrapList::read_from_file(const fs::path& fpath)
{
bool result = false;
log::critical(
logcat, "{}uccessfully loaded BootstrapRC file at path:{}", result ? "S" : "Un", fpath);
if (not fs::exists(fpath))
{
log::critical(logcat, "Bootstrap RC file non-existant at path:{}", fpath);
return result;
_curr = begin();
return result;
}
auto content = util::file_to_string(fpath);
result = bt_decode(content);
log::critical(
logcat, "{}uccessfully loaded BootstrapRC file at path:{}", result ? "S" : "Un", fpath);
_curr = begin();
return result;
}
} // namespace llarp

@ -10,71 +10,60 @@
namespace llarp
{
struct BootstrapList final : public std::set<RemoteRC>
{
std::set<RemoteRC>::iterator _curr = begin();
const RemoteRC&
current()
struct BootstrapList final : public std::set<RemoteRC>
{
return *_curr;
}
std::set<RemoteRC>::iterator _curr = begin();
bool
bt_decode(std::string_view buf);
const RemoteRC& current()
{
return *_curr;
}
bool
bt_decode(oxenc::bt_list_consumer btlc);
bool bt_decode(std::string_view buf);
bool
bt_decode(oxenc::bt_dict_consumer btdc);
bool bt_decode(oxenc::bt_list_consumer btlc);
std::string_view
bt_encode() const;
bool bt_decode(oxenc::bt_dict_consumer btdc);
void
populate_bootstraps(std::vector<fs::path> paths, const fs::path& def, bool load_fallbacks);
std::string_view bt_encode() const;
bool
read_from_file(const fs::path& fpath);
void populate_bootstraps(
std::vector<fs::path> paths, const fs::path& def, bool load_fallbacks);
bool
contains(const RouterID& rid) const;
bool read_from_file(const fs::path& fpath);
// returns a reference to the next index and a boolean that equals true if
// this is the front of the set
const RemoteRC&
next()
{
if (size() < 2)
return *_curr;
bool contains(const RouterID& rid) const;
++_curr;
// returns a reference to the next index and a boolean that equals true if
// this is the front of the set
const RemoteRC& next()
{
if (size() < 2)
return *_curr;
if (_curr == this->end())
_curr = this->begin();
++_curr;
return *_curr;
}
if (_curr == this->end())
_curr = this->begin();
bool
contains(const RemoteRC& rc) const;
return *_curr;
}
void
randomize()
{
if (size() > 1)
_curr = std::next(begin(), std::uniform_int_distribution<size_t>{0, size() - 1}(csrng));
}
bool contains(const RemoteRC& rc) const;
void
clear_list()
{
clear();
}
};
void randomize()
{
if (size() > 1)
_curr =
std::next(begin(), std::uniform_int_distribution<size_t>{0, size() - 1}(csrng));
}
void clear_list()
{
clear();
}
};
std::unordered_map<std::string, BootstrapList>
load_bootstrap_fallbacks();
std::unordered_map<std::string, BootstrapList> load_bootstrap_fallbacks();
} // namespace llarp

@ -4,25 +4,24 @@
namespace llarp
{
using namespace std::literals;
using namespace std::literals;
std::unordered_map<std::string, BootstrapList>
load_bootstrap_fallbacks()
{
std::unordered_map<std::string, BootstrapList> fallbacks;
for (const auto& [network, bootstrap] :
std::initializer_list<std::pair<std::string, std::string_view>>{
//
})
std::unordered_map<std::string, BootstrapList> load_bootstrap_fallbacks()
{
if (network != RouterContact::ACTIVE_NETID)
continue;
std::unordered_map<std::string, BootstrapList> fallbacks;
auto& bsl = fallbacks[network];
bsl.bt_decode(bootstrap);
}
for (const auto& [network, bootstrap] :
std::initializer_list<std::pair<std::string, std::string_view>>{
//
})
{
if (network != RouterContact::ACTIVE_NETID)
continue;
return fallbacks;
}
auto& bsl = fallbacks[network];
bsl.bt_decode(bootstrap);
}
return fallbacks;
}
} // namespace llarp

File diff suppressed because it is too large Load Diff

@ -31,284 +31,261 @@
namespace llarp
{
using SectionValues = llarp::ConfigParser::SectionValues;
using ConfigMap = llarp::ConfigParser::ConfigMap;
inline const std::string QUAD_ZERO{"0.0.0.0"};
inline constexpr uint16_t DEFAULT_LISTEN_PORT{1090};
inline constexpr int CLIENT_ROUTER_CONNECTIONS = 4;
using SectionValues = llarp::ConfigParser::SectionValues;
using ConfigMap = llarp::ConfigParser::ConfigMap;
// TODO: don't use these maps. they're sloppy and difficult to follow
/// Small struct to gather all parameters needed for config generation to reduce the number of
/// parameters that need to be passed around.
struct ConfigGenParameters
{
ConfigGenParameters() = default;
virtual ~ConfigGenParameters() = default;
inline const std::string QUAD_ZERO{"0.0.0.0"};
inline constexpr uint16_t DEFAULT_LISTEN_PORT{1090};
inline constexpr int CLIENT_ROUTER_CONNECTIONS = 4;
ConfigGenParameters(const ConfigGenParameters&) = delete;
ConfigGenParameters(ConfigGenParameters&&) = delete;
// TODO: don't use these maps. they're sloppy and difficult to follow
/// Small struct to gather all parameters needed for config generation to reduce the number of
/// parameters that need to be passed around.
struct ConfigGenParameters
{
ConfigGenParameters() = default;
virtual ~ConfigGenParameters() = default;
bool is_relay = false;
fs::path default_data_dir;
ConfigGenParameters(const ConfigGenParameters&) = delete;
ConfigGenParameters(ConfigGenParameters&&) = delete;
/// get network platform (virtual for unit test mocks)
virtual const llarp::net::Platform*
Net_ptr() const = 0;
};
bool is_relay = false;
fs::path default_data_dir;
struct RouterConfig
{
int client_router_connections{CLIENT_ROUTER_CONNECTIONS};
/// get network platform (virtual for unit test mocks)
virtual const llarp::net::Platform* Net_ptr() const = 0;
};
std::string net_id;
struct RouterConfig
{
int client_router_connections{CLIENT_ROUTER_CONNECTIONS};
fs::path data_dir;
std::string net_id;
bool block_bogons = false;
fs::path data_dir;
int worker_threads = -1;
int net_threads = -1;
bool block_bogons = false;
size_t job_que_size = 0;
int worker_threads = -1;
int net_threads = -1;
std::string rc_file;
std::string enckey_file;
std::string idkey_file;
std::string transkey_file;
size_t job_que_size = 0;
bool is_relay = false;
std::string rc_file;
std::string enckey_file;
std::string idkey_file;
std::string transkey_file;
std::optional<std::string> public_ip;
std::optional<uint16_t> public_port;
bool is_relay = false;
void
define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params);
};
std::optional<std::string> public_ip;
std::optional<uint16_t> public_port;
/// config for path hop selection
struct PeerSelectionConfig
{
/// in our hops what netmask will we use for unique ips for hops
/// i.e. 32 for every hop unique ip, 24 unique /24 per hop, etc
///
int unique_hop_netmask;
void define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params);
};
/// set of countrys to exclude from path building (2 char country code)
std::unordered_set<std::string> exclude_countries;
/// config for path hop selection
struct PeerSelectionConfig
{
/// in our hops what netmask will we use for unique ips for hops
/// i.e. 32 for every hop unique ip, 24 unique /24 per hop, etc
///
int unique_hop_netmask;
void
define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params);
/// set of countrys to exclude from path building (2 char country code)
std::unordered_set<std::string> exclude_countries;
/// return true if this set of router contacts is acceptable against this config
bool
check_rcs(const std::set<RemoteRC>& hops) const;
};
void define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params);
struct NetworkConfig
{
std::optional<bool> enable_profiling;
bool save_profiles;
std::set<RouterID> strict_connect;
std::string if_name;
IPRange if_addr;
/// return true if this set of router contacts is acceptable against this config
bool check_rcs(const std::set<RemoteRC>& hops) const;
};
std::optional<fs::path> keyfile;
std::string endpoint_type;
bool is_reachable = false;
std::optional<int> hops;
std::optional<int> paths;
bool allow_exit = false;
std::set<RouterID> snode_blacklist;
net::IPRangeMap<service::Address> exit_map;
net::IPRangeMap<std::string> ons_exit_map;
std::unordered_map<service::Address, service::AuthInfo> exit_auths;
std::unordered_map<std::string, service::AuthInfo> ons_exit_auths;
std::unordered_map<huint128_t, service::Address> map_addrs;
service::AuthType auth_type = service::AuthType::NONE;
service::AuthFileType auth_file_type = service::AuthFileType::HASHES;
std::optional<std::string> auth_url;
std::optional<std::string> auth_method;
std::unordered_set<service::Address> auth_whitelist;
std::unordered_set<std::string> auth_static_tokens;
std::set<fs::path> auth_files;
std::vector<llarp::dns::SRVData> srv_records;
std::optional<huint128_t> base_ipv6_addr;
std::set<IPRange> owned_ranges;
std::optional<net::TrafficPolicy> traffic_policy;
std::optional<llarp_time_t> path_alignment_timeout;
std::optional<fs::path> addr_map_persist_file;
bool enable_route_poker;
bool blackhole_routes;
void
define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params);
};
struct DnsConfig
{
bool raw;
std::vector<SockAddr> bind_addr;
std::vector<SockAddr> upstream_dns;
std::vector<fs::path> hostfiles;
std::optional<SockAddr> query_bind;
std::unordered_multimap<std::string, std::string> extra_opts;
void
define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params);
};
struct LinksConfig
{
// DEPRECATED -- use [Router]:public_addr
std::optional<std::string> public_addr;
// DEPRECATED -- use [Router]:public_port
std::optional<uint16_t> public_port;
std::optional<oxen::quic::Address> listen_addr;
bool using_user_value = false;
bool using_new_api = false;
void
define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params);
};
// TODO: remove oxenmq from this header
struct ApiConfig
{
bool enable_rpc_server = false;
std::vector<oxenmq::address> rpc_bind_addrs;
void
define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params);
};
struct LokidConfig
{
fs::path id_keyfile;
oxenmq::address rpc_addr;
bool disable_testing = true;
void
define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params);
};
struct BootstrapConfig
{
std::vector<fs::path> files;
bool seednode;
void
define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params);
};
struct LoggingConfig
{
log::Type type = log::Type::Print;
log::Level level = log::Level::off;
std::string file;
void
define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params);
};
struct Config
{
explicit Config(std::optional<fs::path> datadir = std::nullopt);
virtual ~Config() = default;
/// create generation params (virtual for unit test mock)
virtual std::unique_ptr<ConfigGenParameters>
make_gen_params() const;
RouterConfig router;
NetworkConfig network;
PeerSelectionConfig paths;
DnsConfig dns;
LinksConfig links;
ApiConfig api;
LokidConfig lokid;
BootstrapConfig bootstrap;
LoggingConfig logging;
// Initialize config definition
void
init_config(ConfigDefinition& conf, const ConfigGenParameters& params);
/// Insert config entries for backwards-compatibility (e.g. so that the config system will
/// tolerate old values that are no longer accepted)
///
/// @param conf is the config to modify
void
add_backcompat_opts(ConfigDefinition& conf);
// Load a config from the given file if the config file is not provided LoadDefault is called
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
load_string(std::string_view ini, bool isRelay = false);
std::string
generate_client_config_base();
std::string
generate_router_config_base();
void
save();
void
override(std::string section, std::string key, std::string value);
void
add_default(std::string section, std::string key, std::string value);
/// create a config with the default parameters for an embedded lokinet
static std::shared_ptr<Config>
make_embedded_config();
private:
/// Load (initialize) a default config.
///
/// This delegates to the ConfigDefinition to generate a default config,
/// as though an empty config were specified.
///
/// If using Config without the intention of loading from file (or string), this is necessary
/// in order to obtain sane defaults.
///
/// @param isRelay determines whether the config will reflect that of a relay or client
/// @param dataDir is a path representing a directory to be used as the data dir
/// @return true on success, false otherwise
bool
load_default_config(bool isRelay);
bool
load_config_data(
std::string_view ini, std::optional<fs::path> fname = std::nullopt, bool isRelay = false);
void
load_overrides(ConfigDefinition& conf) const;
std::vector<std::array<std::string, 3>> additional;
ConfigParser parser;
const fs::path data_dir;
};
void
ensure_config(fs::path dataDir, fs::path confFile, bool overwrite, bool asRouter);
struct NetworkConfig
{
std::optional<bool> enable_profiling;
bool save_profiles;
std::set<RouterID> strict_connect;
std::string if_name;
IPRange if_addr;
std::optional<fs::path> keyfile;
std::string endpoint_type;
bool is_reachable = false;
std::optional<int> hops;
std::optional<int> paths;
bool allow_exit = false;
std::set<RouterID> snode_blacklist;
net::IPRangeMap<service::Address> exit_map;
net::IPRangeMap<std::string> ons_exit_map;
std::unordered_map<service::Address, service::AuthInfo> exit_auths;
std::unordered_map<std::string, service::AuthInfo> ons_exit_auths;
std::unordered_map<huint128_t, service::Address> map_addrs;
service::AuthType auth_type = service::AuthType::NONE;
service::AuthFileType auth_file_type = service::AuthFileType::HASHES;
std::optional<std::string> auth_url;
std::optional<std::string> auth_method;
std::unordered_set<service::Address> auth_whitelist;
std::unordered_set<std::string> auth_static_tokens;
std::set<fs::path> auth_files;
std::vector<llarp::dns::SRVData> srv_records;
std::optional<huint128_t> base_ipv6_addr;
std::set<IPRange> owned_ranges;
std::optional<net::TrafficPolicy> traffic_policy;
std::optional<llarp_time_t> path_alignment_timeout;
std::optional<fs::path> addr_map_persist_file;
bool enable_route_poker;
bool blackhole_routes;
void define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params);
};
struct DnsConfig
{
bool raw;
std::vector<SockAddr> bind_addr;
std::vector<SockAddr> upstream_dns;
std::vector<fs::path> hostfiles;
std::optional<SockAddr> query_bind;
std::unordered_multimap<std::string, std::string> extra_opts;
void define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params);
};
struct LinksConfig
{
// DEPRECATED -- use [Router]:public_addr
std::optional<std::string> public_addr;
// DEPRECATED -- use [Router]:public_port
std::optional<uint16_t> public_port;
std::optional<oxen::quic::Address> listen_addr;
bool using_user_value = false;
bool using_new_api = false;
void define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params);
};
// TODO: remove oxenmq from this header
struct ApiConfig
{
bool enable_rpc_server = false;
std::vector<oxenmq::address> rpc_bind_addrs;
void define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params);
};
struct LokidConfig
{
fs::path id_keyfile;
oxenmq::address rpc_addr;
bool disable_testing = true;
void define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params);
};
struct BootstrapConfig
{
std::vector<fs::path> files;
bool seednode;
void define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params);
};
struct LoggingConfig
{
log::Type type = log::Type::Print;
log::Level level = log::Level::off;
std::string file;
void define_config_options(ConfigDefinition& conf, const ConfigGenParameters& params);
};
struct Config
{
explicit Config(std::optional<fs::path> datadir = std::nullopt);
virtual ~Config() = default;
/// create generation params (virtual for unit test mock)
virtual std::unique_ptr<ConfigGenParameters> make_gen_params() const;
RouterConfig router;
NetworkConfig network;
PeerSelectionConfig paths;
DnsConfig dns;
LinksConfig links;
ApiConfig api;
LokidConfig lokid;
BootstrapConfig bootstrap;
LoggingConfig logging;
// Initialize config definition
void init_config(ConfigDefinition& conf, const ConfigGenParameters& params);
/// Insert config entries for backwards-compatibility (e.g. so that the config system will
/// tolerate old values that are no longer accepted)
///
/// @param conf is the config to modify
void add_backcompat_opts(ConfigDefinition& conf);
// Load a config from the given file if the config file is not provided LoadDefault is
// called
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 load_string(std::string_view ini, bool isRelay = false);
std::string generate_client_config_base();
std::string generate_router_config_base();
void save();
void override(std::string section, std::string key, std::string value);
void add_default(std::string section, std::string key, std::string value);
/// create a config with the default parameters for an embedded lokinet
static std::shared_ptr<Config> make_embedded_config();
private:
/// Load (initialize) a default config.
///
/// This delegates to the ConfigDefinition to generate a default config,
/// as though an empty config were specified.
///
/// If using Config without the intention of loading from file (or string), this is
/// necessary in order to obtain sane defaults.
///
/// @param isRelay determines whether the config will reflect that of a relay or client
/// @param dataDir is a path representing a directory to be used as the data dir
/// @return true on success, false otherwise
bool load_default_config(bool isRelay);
bool load_config_data(
std::string_view ini,
std::optional<fs::path> fname = std::nullopt,
bool isRelay = false);
void load_overrides(ConfigDefinition& conf) const;
std::vector<std::array<std::string, 3>> additional;
ConfigParser parser;
const fs::path data_dir;
};
void ensure_config(fs::path dataDir, fs::path confFile, bool overwrite, bool asRouter);
} // namespace llarp

@ -8,285 +8,276 @@
namespace llarp
{
template <>
bool
OptionDefinition<bool>::from_string(const std::string& input)
{
if (input == "false" || input == "off" || input == "0" || input == "no")
return false;
if (input == "true" || input == "on" || input == "1" || input == "yes")
return true;
throw std::invalid_argument{fmt::format("{} is not a valid bool", input)};
}
ConfigDefinition&
ConfigDefinition::define_option(std::unique_ptr<OptionDefinitionBase> def)
{
using namespace config;
// If explicitly deprecated or is a {client,relay} option in a {relay,client} config then add a
// dummy, warning option instead of this one.
if (def->deprecated || (relay ? def->clientOnly : def->relay_only))
template <>
bool OptionDefinition<bool>::from_string(const std::string& input)
{
return define_option<std::string>(
def->section,
def->name,
MultiValue,
Hidden,
[deprecated = def->deprecated,
relay = relay,
opt = "[" + def->section + "]:" + def->name](std::string_view) {
LogWarn(
"*** WARNING: The config option ",
opt,
(deprecated ? " is deprecated"
: relay ? " is not valid in service node configuration files"
: " is not valid in client configuration files"),
" and has been ignored.");
});
if (input == "false" || input == "off" || input == "0" || input == "no")
return false;
if (input == "true" || input == "on" || input == "1" || input == "yes")
return true;
throw std::invalid_argument{fmt::format("{} is not a valid bool", input)};
}
auto [sectionItr, newSect] = definitions.try_emplace(def->section);
if (newSect)
section_ordering.push_back(def->section);
auto& section = sectionItr->first;
ConfigDefinition& ConfigDefinition::define_option(std::unique_ptr<OptionDefinitionBase> def)
{
using namespace config;
// If explicitly deprecated or is a {client,relay} option in a {relay,client} config then
// add a dummy, warning option instead of this one.
if (def->deprecated || (relay ? def->clientOnly : def->relay_only))
{
return define_option<std::string>(
def->section,
def->name,
MultiValue,
Hidden,
[deprecated = def->deprecated,
relay = relay,
opt = "[" + def->section + "]:" + def->name](std::string_view) {
LogWarn(
"*** WARNING: The config option ",
opt,
(deprecated ? " is deprecated"
: relay ? " is not valid in service node configuration files"
: " is not valid in client configuration files"),
" and has been ignored.");
});
}
auto [sectionItr, newSect] = definitions.try_emplace(def->section);
if (newSect)
section_ordering.push_back(def->section);
auto& section = sectionItr->first;
auto [it, added] = definitions[section].try_emplace(std::string{def->name}, std::move(def));
if (!added)
throw std::invalid_argument{
fmt::format("definition for [{}]:{} already exists", def->section, def->name)};
definition_ordering[section].push_back(it->first);
if (!it->second->comments.empty())
add_option_comments(section, it->first, std::move(it->second->comments));
return *this;
}
auto [it, added] = definitions[section].try_emplace(std::string{def->name}, std::move(def));
if (!added)
throw std::invalid_argument{
fmt::format("definition for [{}]:{} already exists", def->section, def->name)};
ConfigDefinition& ConfigDefinition::add_config_value(
std::string_view section, std::string_view name, std::string_view value)
{
// see if we have an undeclared handler to fall back to in case section or section:name is
// absent
auto undItr = undeclared_handlers.find(std::string(section));
bool haveUndeclaredHandler = (undItr != undeclared_handlers.end());
// get section, falling back to undeclared handler if needed
auto secItr = definitions.find(std::string(section));
if (secItr == definitions.end())
{
// fallback to undeclared handler if available
if (not haveUndeclaredHandler)
throw std::invalid_argument{fmt::format("unrecognized section [{}]", section)};
auto& handler = undItr->second;
handler(section, name, value);
return *this;
}
// section was valid, get definition by name
// fall back to undeclared handler if needed
auto& sectionDefinitions = secItr->second;
auto defItr = sectionDefinitions.find(std::string(name));
if (defItr != sectionDefinitions.end())
{
std::unique_ptr<OptionDefinitionBase>& definition = defItr->second;
definition->parse_value(std::string(value));
return *this;
}
if (not haveUndeclaredHandler)
throw std::invalid_argument{fmt::format("unrecognized option [{}]: {}", section, name)};
auto& handler = undItr->second;
handler(section, name, value);
return *this;
}
definition_ordering[section].push_back(it->first);
void ConfigDefinition::add_undeclared_handler(
const std::string& section, UndeclaredValueHandler handler)
{
auto itr = undeclared_handlers.find(section);
if (itr != undeclared_handlers.end())
throw std::logic_error{fmt::format("section {} already has a handler", section)};
if (!it->second->comments.empty())
add_option_comments(section, it->first, std::move(it->second->comments));
undeclared_handlers[section] = std::move(handler);
}
return *this;
}
void ConfigDefinition::remove_undeclared_handler(const std::string& section)
{
auto itr = undeclared_handlers.find(section);
if (itr != undeclared_handlers.end())
undeclared_handlers.erase(itr);
}
ConfigDefinition&
ConfigDefinition::add_config_value(
std::string_view section, std::string_view name, std::string_view value)
{
// see if we have an undeclared handler to fall back to in case section or section:name is
// absent
auto undItr = undeclared_handlers.find(std::string(section));
bool haveUndeclaredHandler = (undItr != undeclared_handlers.end());
void ConfigDefinition::validate_required_fields()
{
visit_sections([&](const std::string& section, const DefinitionMap&) {
visit_definitions(
section, [&](const std::string&, const std::unique_ptr<OptionDefinitionBase>& def) {
if (def->required and def->get_number_found() < 1)
{
throw std::invalid_argument{
fmt::format("[{}]:{} is required but missing", section, def->name)};
}
// should be handled earlier in OptionDefinition::parse_value()
assert(def->get_number_found() <= 1 or def->multi_valued);
});
});
}
// get section, falling back to undeclared handler if needed
auto secItr = definitions.find(std::string(section));
if (secItr == definitions.end())
void ConfigDefinition::accept_all_options()
{
// fallback to undeclared handler if available
if (not haveUndeclaredHandler)
throw std::invalid_argument{fmt::format("unrecognized section [{}]", section)};
auto& handler = undItr->second;
handler(section, name, value);
return *this;
visit_sections([this](const std::string& section, const DefinitionMap&) {
visit_definitions(
section, [](const std::string&, const std::unique_ptr<OptionDefinitionBase>& def) {
def->try_accept();
});
});
}
// section was valid, get definition by name
// fall back to undeclared handler if needed
auto& sectionDefinitions = secItr->second;
auto defItr = sectionDefinitions.find(std::string(name));
if (defItr != sectionDefinitions.end())
void ConfigDefinition::add_section_comments(
const std::string& section, std::vector<std::string> comments)
{
std::unique_ptr<OptionDefinitionBase>& definition = defItr->second;
definition->parse_value(std::string(value));
return *this;
auto& sectionComments = section_comments[section];
for (auto& c : comments)
sectionComments.emplace_back(std::move(c));
}
if (not haveUndeclaredHandler)
throw std::invalid_argument{fmt::format("unrecognized option [{}]: {}", section, name)};
auto& handler = undItr->second;
handler(section, name, value);
return *this;
}
void
ConfigDefinition::add_undeclared_handler(
const std::string& section, UndeclaredValueHandler handler)
{
auto itr = undeclared_handlers.find(section);
if (itr != undeclared_handlers.end())
throw std::logic_error{fmt::format("section {} already has a handler", section)};
undeclared_handlers[section] = std::move(handler);
}
void
ConfigDefinition::remove_undeclared_handler(const std::string& section)
{
auto itr = undeclared_handlers.find(section);
if (itr != undeclared_handlers.end())
undeclared_handlers.erase(itr);
}
void
ConfigDefinition::validate_required_fields()
{
visit_sections([&](const std::string& section, const DefinitionMap&) {
visit_definitions(
section, [&](const std::string&, const std::unique_ptr<OptionDefinitionBase>& def) {
if (def->required and def->get_number_found() < 1)
{
throw std::invalid_argument{
fmt::format("[{}]:{} is required but missing", section, def->name)};
}
void ConfigDefinition::add_option_comments(
const std::string& section, const std::string& name, std::vector<std::string> comments)
{
auto& defComments = definition_comments[section][name];
if (defComments.empty())
defComments = std::move(comments);
else
defComments.insert(
defComments.end(),
std::make_move_iterator(comments.begin()),
std::make_move_iterator(comments.end()));
}
// should be handled earlier in OptionDefinition::parse_value()
assert(def->get_number_found() <= 1 or def->multi_valued);
});
});
}
void
ConfigDefinition::accept_all_options()
{
visit_sections([this](const std::string& section, const DefinitionMap&) {
visit_definitions(
section, [](const std::string&, const std::unique_ptr<OptionDefinitionBase>& def) {
def->try_accept();
});
});
}
void
ConfigDefinition::add_section_comments(
const std::string& section, std::vector<std::string> comments)
{
auto& sectionComments = section_comments[section];
for (auto& c : comments)
sectionComments.emplace_back(std::move(c));
}
void
ConfigDefinition::add_option_comments(
const std::string& section, const std::string& name, std::vector<std::string> comments)
{
auto& defComments = definition_comments[section][name];
if (defComments.empty())
defComments = std::move(comments);
else
defComments.insert(
defComments.end(),
std::make_move_iterator(comments.begin()),
std::make_move_iterator(comments.end()));
}
std::string
ConfigDefinition::generate_ini_config(bool useValues)
{
std::string ini;
auto ini_append = std::back_inserter(ini);
int sectionsVisited = 0;
visit_sections([&](const std::string& section, const DefinitionMap&) {
std::string sect_str;
auto sect_append = std::back_inserter(sect_str);
visit_definitions(
section, [&](const std::string& name, const std::unique_ptr<OptionDefinitionBase>& def) {
bool has_comment = false;
// TODO: as above, this will create empty objects
// TODO: as above (but more important): this won't handle definitions with no entries
// (i.e. those handled by UndeclaredValueHandler's)
for (const std::string& comment : definition_comments[section][name])
std::string ConfigDefinition::generate_ini_config(bool useValues)
{
std::string ini;
auto ini_append = std::back_inserter(ini);
int sectionsVisited = 0;
visit_sections([&](const std::string& section, const DefinitionMap&) {
std::string sect_str;
auto sect_append = std::back_inserter(sect_str);
visit_definitions(
section,
[&](const std::string& name, const std::unique_ptr<OptionDefinitionBase>& def) {
bool has_comment = false;
// TODO: as above, this will create empty objects
// TODO: as above (but more important): this won't handle definitions with no
// entries
// (i.e. those handled by UndeclaredValueHandler's)
for (const std::string& comment : definition_comments[section][name])
{
fmt::format_to(sect_append, "\n# {}", comment);
has_comment = true;
}
if (useValues and def->get_number_found() > 0)
{
for (const auto& val : def->values_as_string())
fmt::format_to(sect_append, "\n{}={}", name, val);
*sect_append = '\n';
}
else if (not def->hidden)
{
if (auto defaults = def->default_values_as_string(); not defaults.empty())
for (const auto& val : defaults)
fmt::format_to(
sect_append, "\n{}{}={}", def->required ? "" : "#", name, val);
else
// We have no defaults so we append it as "#opt-name=" so that we show
// the option name, and make it simple to uncomment and edit to the
// desired value.
fmt::format_to(sect_append, "\n#{}=", name);
*sect_append = '\n';
}
else if (has_comment)
*sect_append = '\n';
});
if (sect_str.empty())
return; // Skip sections with no options
if (sectionsVisited > 0)
ini += "\n\n";
fmt::format_to(ini_append, "[{}]\n", section);
// TODO: this will create empty objects as a side effect of map's operator[]
// TODO: this also won't handle sections which have no definition
for (const std::string& comment : section_comments[section])
{
fmt::format_to(sect_append, "\n# {}", comment);
has_comment = true;
fmt::format_to(ini_append, "# {}\n", comment);
}
ini += "\n";
ini += sect_str;
if (useValues and def->get_number_found() > 0)
{
for (const auto& val : def->values_as_string())
fmt::format_to(sect_append, "\n{}={}", name, val);
*sect_append = '\n';
}
else if (not def->hidden)
{
if (auto defaults = def->default_values_as_string(); not defaults.empty())
for (const auto& val : defaults)
fmt::format_to(sect_append, "\n{}{}={}", def->required ? "" : "#", name, val);
else
// We have no defaults so we append it as "#opt-name=" so that we show the option
// name, and make it simple to uncomment and edit to the desired value.
fmt::format_to(sect_append, "\n#{}=", name);
*sect_append = '\n';
}
else if (has_comment)
*sect_append = '\n';
});
if (sect_str.empty())
return; // Skip sections with no options
if (sectionsVisited > 0)
ini += "\n\n";
fmt::format_to(ini_append, "[{}]\n", section);
// TODO: this will create empty objects as a side effect of map's operator[]
// TODO: this also won't handle sections which have no definition
for (const std::string& comment : section_comments[section])
{
fmt::format_to(ini_append, "# {}\n", comment);
}
ini += "\n";
ini += sect_str;
sectionsVisited++;
});
return ini;
}
const std::unique_ptr<OptionDefinitionBase>&
ConfigDefinition::lookup_definition_or_throw(
std::string_view section, std::string_view name) const
{
const auto sectionItr = definitions.find(std::string(section));
if (sectionItr == definitions.end())
throw std::invalid_argument{fmt::format("No config section [{}]", section)};
auto& sectionDefinitions = sectionItr->second;
const auto definitionItr = sectionDefinitions.find(std::string(name));
if (definitionItr == sectionDefinitions.end())
throw std::invalid_argument{
fmt::format("No config item {} within section {}", name, section)};
return definitionItr->second;
}
std::unique_ptr<OptionDefinitionBase>&
ConfigDefinition::lookup_definition_or_throw(std::string_view section, std::string_view name)
{
return const_cast<std::unique_ptr<OptionDefinitionBase>&>(
const_cast<const ConfigDefinition*>(this)->lookup_definition_or_throw(section, name));
}
void
ConfigDefinition::visit_sections(SectionVisitor visitor) const
{
for (const std::string& section : section_ordering)
sectionsVisited++;
});
return ini;
}
const std::unique_ptr<OptionDefinitionBase>& ConfigDefinition::lookup_definition_or_throw(
std::string_view section, std::string_view name) const
{
const auto itr = definitions.find(section);
assert(itr != definitions.end());
visitor(section, itr->second);
const auto sectionItr = definitions.find(std::string(section));
if (sectionItr == definitions.end())
throw std::invalid_argument{fmt::format("No config section [{}]", section)};
auto& sectionDefinitions = sectionItr->second;
const auto definitionItr = sectionDefinitions.find(std::string(name));
if (definitionItr == sectionDefinitions.end())
throw std::invalid_argument{
fmt::format("No config item {} within section {}", name, section)};
return definitionItr->second;
}
};
void
ConfigDefinition::visit_definitions(const std::string& section, DefVisitor visitor) const
{
const auto& defs = definitions.at(section);
const auto& defOrdering = definition_ordering.at(section);
for (const std::string& name : defOrdering)
std::unique_ptr<OptionDefinitionBase>& ConfigDefinition::lookup_definition_or_throw(
std::string_view section, std::string_view name)
{
const auto itr = defs.find(name);
assert(itr != defs.end());
visitor(name, itr->second);
return const_cast<std::unique_ptr<OptionDefinitionBase>&>(
const_cast<const ConfigDefinition*>(this)->lookup_definition_or_throw(section, name));
}
};
void ConfigDefinition::visit_sections(SectionVisitor visitor) const
{
for (const std::string& section : section_ordering)
{
const auto itr = definitions.find(section);
assert(itr != definitions.end());
visitor(section, itr->second);
}
};
void ConfigDefinition::visit_definitions(const std::string& section, DefVisitor visitor) const
{
const auto& defs = definitions.at(section);
const auto& defOrdering = definition_ordering.at(section);
for (const std::string& name : defOrdering)
{
const auto itr = defs.find(name);
assert(itr != defs.end());
visitor(name, itr->second);
}
};
} // namespace llarp

File diff suppressed because it is too large Load Diff

@ -10,268 +10,257 @@
namespace llarp
{
bool
ConfigParser::load_file(const fs::path& fname)
{
try
bool ConfigParser::load_file(const fs::path& fname)
{
_data = util::file_to_string(fname);
try
{
_data = util::file_to_string(fname);
}
catch (const std::exception& e)
{
return false;
}
if (_data.empty())
return false;
_filename = fname;
return parse();
}
catch (const std::exception& e)
bool ConfigParser::load_new_from_str(std::string_view str)
{
return false;
_data.resize(str.size());
std::copy(str.begin(), str.end(), _data.begin());
return parse_all();
}
if (_data.empty())
return false;
_filename = fname;
return parse();
}
bool
ConfigParser::load_new_from_str(std::string_view str)
{
_data.resize(str.size());
std::copy(str.begin(), str.end(), _data.begin());
return parse_all();
}
bool
ConfigParser::load_from_str(std::string_view str)
{
_data.resize(str.size());
std::copy(str.begin(), str.end(), _data.begin());
return parse();
}
void
ConfigParser::clear()
{
_overrides.clear();
_config.clear();
_data.clear();
}
bool ConfigParser::load_from_str(std::string_view str)
{
_data.resize(str.size());
std::copy(str.begin(), str.end(), _data.begin());
return parse();
}
static bool
whitespace(char ch)
{
return std::isspace(static_cast<unsigned char>(ch)) != 0;
}
void ConfigParser::clear()
{
_overrides.clear();
_config.clear();
_data.clear();
}
/// Differs from Parse() as ParseAll() does NOT skip comments
/// ParseAll() is only used by RPC endpoint 'config' for
/// reading new .ini files from string and writing them
bool
ConfigParser::parse_all()
{
std::list<std::string_view> lines;
static bool whitespace(char ch)
{
auto itr = _data.begin();
// split into lines
while (itr != _data.end())
{
auto beg = itr;
while (itr != _data.end() && *itr != '\n' && *itr != '\r')
++itr;
lines.emplace_back(std::addressof(*beg), std::distance(beg, itr));
if (itr == _data.end())
break;
++itr;
}
return std::isspace(static_cast<unsigned char>(ch)) != 0;
}
std::string_view sectName;
size_t lineno = 0;
for (auto line : lines)
/// Differs from Parse() as ParseAll() does NOT skip comments
/// ParseAll() is only used by RPC endpoint 'config' for
/// reading new .ini files from string and writing them
bool ConfigParser::parse_all()
{
lineno++;
// Trim whitespace
while (!line.empty() && whitespace(line.front()))
line.remove_prefix(1);
while (!line.empty() && whitespace(line.back()))
line.remove_suffix(1);
std::list<std::string_view> lines;
{
auto itr = _data.begin();
// split into lines
while (itr != _data.end())
{
auto beg = itr;
while (itr != _data.end() && *itr != '\n' && *itr != '\r')
++itr;
lines.emplace_back(std::addressof(*beg), std::distance(beg, itr));
if (itr == _data.end())
break;
++itr;
}
}
std::string_view sectName;
size_t lineno = 0;
for (auto line : lines)
{
lineno++;
// Trim whitespace
while (!line.empty() && whitespace(line.front()))
line.remove_prefix(1);
while (!line.empty() && whitespace(line.back()))
line.remove_suffix(1);
// Skip blank lines but NOT comments
if (line.empty())
continue;
// Skip blank lines but NOT comments
if (line.empty())
continue;
if (line.front() == '[' && line.back() == ']')
{
// section header
line.remove_prefix(1);
line.remove_suffix(1);
sectName = line;
}
else if (auto kvDelim = line.find('='); kvDelim != std::string_view::npos)
{
// key value pair
std::string_view k = line.substr(0, kvDelim);
std::string_view v = line.substr(kvDelim + 1);
// Trim inner whitespace
while (!k.empty() && whitespace(k.back()))
k.remove_suffix(1);
while (!v.empty() && whitespace(v.front()))
v.remove_prefix(1);
if (line.front() == '[' && line.back() == ']')
{
// section header
line.remove_prefix(1);
line.remove_suffix(1);
sectName = line;
}
else if (auto kvDelim = line.find('='); kvDelim != std::string_view::npos)
{
// key value pair
std::string_view k = line.substr(0, kvDelim);
std::string_view v = line.substr(kvDelim + 1);
// Trim inner whitespace
while (!k.empty() && whitespace(k.back()))
k.remove_suffix(1);
while (!v.empty() && whitespace(v.front()))
v.remove_prefix(1);
if (k.empty())
{
throw std::runtime_error(
fmt::format("{} invalid line ({}): '{}'", _filename, lineno, line));
if (k.empty())
{
throw std::runtime_error(
fmt::format("{} invalid line ({}): '{}'", _filename, lineno, line));
}
LogDebug(_filename, ": [", sectName, "]:", k, "=", v);
_config[std::string{sectName}].emplace(k, v);
}
else // malformed?
{
throw std::runtime_error(
fmt::format("{} invalid line ({}): '{}'", _filename, lineno, line));
}
}
LogDebug(_filename, ": [", sectName, "]:", k, "=", v);
_config[std::string{sectName}].emplace(k, v);
}
else // malformed?
{
throw std::runtime_error(
fmt::format("{} invalid line ({}): '{}'", _filename, lineno, line));
}
return true;
}
return true;
}
bool
ConfigParser::parse()
{
std::list<std::string_view> lines;
bool ConfigParser::parse()
{
auto itr = _data.begin();
// split into lines
while (itr != _data.end())
{
auto beg = itr;
while (itr != _data.end() && *itr != '\n' && *itr != '\r')
++itr;
lines.emplace_back(std::addressof(*beg), std::distance(beg, itr));
if (itr == _data.end())
break;
++itr;
}
}
std::list<std::string_view> lines;
{
auto itr = _data.begin();
// split into lines
while (itr != _data.end())
{
auto beg = itr;
while (itr != _data.end() && *itr != '\n' && *itr != '\r')
++itr;
lines.emplace_back(std::addressof(*beg), std::distance(beg, itr));
if (itr == _data.end())
break;
++itr;
}
}
std::string_view sectName;
size_t lineno = 0;
for (auto line : lines)
{
lineno++;
// Trim whitespace
while (!line.empty() && whitespace(line.front()))
line.remove_prefix(1);
while (!line.empty() && whitespace(line.back()))
line.remove_suffix(1);
std::string_view sectName;
size_t lineno = 0;
for (auto line : lines)
{
lineno++;
// Trim whitespace
while (!line.empty() && whitespace(line.front()))
line.remove_prefix(1);
while (!line.empty() && whitespace(line.back()))
line.remove_suffix(1);
// Skip blank lines
if (line.empty() or line.front() == ';' or line.front() == '#')
continue;
// Skip blank lines
if (line.empty() or line.front() == ';' or line.front() == '#')
continue;
if (line.front() == '[' && line.back() == ']')
{
// section header
line.remove_prefix(1);
line.remove_suffix(1);
sectName = line;
}
else if (auto kvDelim = line.find('='); kvDelim != std::string_view::npos)
{
// key value pair
std::string_view k = line.substr(0, kvDelim);
std::string_view v = line.substr(kvDelim + 1);
// Trim inner whitespace
while (!k.empty() && whitespace(k.back()))
k.remove_suffix(1);
while (!v.empty() && whitespace(v.front()))
v.remove_prefix(1);
if (line.front() == '[' && line.back() == ']')
{
// section header
line.remove_prefix(1);
line.remove_suffix(1);
sectName = line;
}
else if (auto kvDelim = line.find('='); kvDelim != std::string_view::npos)
{
// key value pair
std::string_view k = line.substr(0, kvDelim);
std::string_view v = line.substr(kvDelim + 1);
// Trim inner whitespace
while (!k.empty() && whitespace(k.back()))
k.remove_suffix(1);
while (!v.empty() && whitespace(v.front()))
v.remove_prefix(1);
if (k.empty())
{
throw std::runtime_error(
fmt::format("{} invalid line ({}): '{}'", _filename, lineno, line));
if (k.empty())
{
throw std::runtime_error(
fmt::format("{} invalid line ({}): '{}'", _filename, lineno, line));
}
LogDebug(_filename, ": [", sectName, "]:", k, "=", v);
_config[std::string{sectName}].emplace(k, v);
}
else // malformed?
{
throw std::runtime_error(
fmt::format("{} invalid line ({}): '{}'", _filename, lineno, line));
}
}
LogDebug(_filename, ": [", sectName, "]:", k, "=", v);
_config[std::string{sectName}].emplace(k, v);
}
else // malformed?
{
throw std::runtime_error(
fmt::format("{} invalid line ({}): '{}'", _filename, lineno, line));
}
return true;
}
return true;
}
void
ConfigParser::iter_all_sections(std::function<void(std::string_view, const SectionValues&)> visit)
{
for (const auto& item : _config)
visit(item.first, item.second);
}
bool
ConfigParser::visit_section(
const char* name, std::function<bool(const SectionValues& sect)> visit) const
{
// m_Config is effectively:
// unordered_map< string, unordered_multimap< string, string >>
// in human terms: a map of of sections
// where a section is a multimap of k:v pairs
auto itr = _config.find(name);
if (itr == _config.end())
return false;
return visit(itr->second);
}
void
ConfigParser::add_override(
fs::path fpath, std::string section, std::string key, std::string value)
{
auto& data = _overrides[fpath];
data[section].emplace(key, value);
}
void
ConfigParser::save()
{
// write overrides
for (const auto& [fname, overrides] : _overrides)
void ConfigParser::iter_all_sections(
std::function<void(std::string_view, const SectionValues&)> visit)
{
std::ofstream ofs(fname);
for (const auto& [section, values] : overrides)
{
ofs << std::endl << "[" << section << "]" << std::endl;
for (const auto& [key, value] : values)
{
ofs << key << "=" << value << std::endl;
}
}
for (const auto& item : _config)
visit(item.first, item.second);
}
_overrides.clear();
}
void
ConfigParser::save_new() const
{
if (not _overrides.empty())
bool ConfigParser::visit_section(
const char* name, std::function<bool(const SectionValues& sect)> visit) const
{
throw std::invalid_argument("Override specified when attempting new .ini save");
// m_Config is effectively:
// unordered_map< string, unordered_multimap< string, string >>
// in human terms: a map of of sections
// where a section is a multimap of k:v pairs
auto itr = _config.find(name);
if (itr == _config.end())
return false;
return visit(itr->second);
}
if (_config.empty())
void ConfigParser::add_override(
fs::path fpath, std::string section, std::string key, std::string value)
{
throw std::invalid_argument("New config not loaded when attempting new .ini save");
auto& data = _overrides[fpath];
data[section].emplace(key, value);
}
if (_filename.empty())
void ConfigParser::save()
{
throw std::invalid_argument("New config cannot be saved with filepath specified");
// write overrides
for (const auto& [fname, overrides] : _overrides)
{
std::ofstream ofs(fname);
for (const auto& [section, values] : overrides)
{
ofs << std::endl << "[" << section << "]" << std::endl;
for (const auto& [key, value] : values)
{
ofs << key << "=" << value << std::endl;
}
}
}
_overrides.clear();
}
std::ofstream ofs(_filename);
for (const auto& [section, values] : _config)
void ConfigParser::save_new() const
{
ofs << std::endl << "[" << section << "]" << std::endl;
for (const auto& [key, value] : values)
{
ofs << key << "=" << value << std::endl;
}
if (not _overrides.empty())
{
throw std::invalid_argument("Override specified when attempting new .ini save");
}
if (_config.empty())
{
throw std::invalid_argument("New config not loaded when attempting new .ini save");
}
if (_filename.empty())
{
throw std::invalid_argument("New config cannot be saved with filepath specified");
}
std::ofstream ofs(_filename);
for (const auto& [section, values] : _config)
{
ofs << std::endl << "[" << section << "]" << std::endl;
for (const auto& [key, value] : values)
{
ofs << key << "=" << value << std::endl;
}
}
}
}
} // namespace llarp

@ -11,70 +11,58 @@
namespace llarp
{
struct ConfigParser
{
using SectionValues = std::unordered_multimap<std::string, std::string>;
using ConfigMap = std::unordered_map<std::string, SectionValues>;
/// clear parser
void
clear();
/// load config file for bootserv
/// return true on success
/// return false on error
bool
load_file(const fs::path& fname);
/// load new .ini file from string (calls ParseAll() rather than Parse())
/// return true on success
/// return false on error
bool
load_new_from_str(std::string_view str);
/// load from string
/// return true on success
/// return false on error
bool
load_from_str(std::string_view str);
/// iterate all sections and thier values
void
iter_all_sections(std::function<void(std::string_view, const SectionValues&)> visit);
/// visit a section in config read only by name
/// return false if no section or value propagated from visitor
bool
visit_section(const char* name, std::function<bool(const SectionValues&)> visit) const;
/// add a config option that is appended in another file
void
add_override(fs::path file, std::string section, std::string key, std::string value);
/// save config overrides
void
save();
/// save new .ini config file to path
void
save_new() const;
void
set_filename(const fs::path& f)
struct ConfigParser
{
_filename = f;
}
using SectionValues = std::unordered_multimap<std::string, std::string>;
using ConfigMap = std::unordered_map<std::string, SectionValues>;
/// clear parser
void clear();
private:
bool
parse_all();
/// load config file for bootserv
/// return true on success
/// return false on error
bool load_file(const fs::path& fname);
bool
parse();
/// load new .ini file from string (calls ParseAll() rather than Parse())
/// return true on success
/// return false on error
bool load_new_from_str(std::string_view str);
std::string _data;
ConfigMap _config;
std::unordered_map<fs::path, ConfigMap, util::FileHash> _overrides;
fs::path _filename;
};
/// load from string
/// return true on success
/// return false on error
bool load_from_str(std::string_view str);
/// iterate all sections and thier values
void iter_all_sections(std::function<void(std::string_view, const SectionValues&)> visit);
/// visit a section in config read only by name
/// return false if no section or value propagated from visitor
bool visit_section(const char* name, std::function<bool(const SectionValues&)> visit) const;
/// add a config option that is appended in another file
void add_override(fs::path file, std::string section, std::string key, std::string value);
/// save config overrides
void save();
/// save new .ini config file to path
void save_new() const;
void set_filename(const fs::path& f)
{
_filename = f;
}
private:
bool parse_all();
bool parse();
std::string _data;
ConfigMap _config;
std::unordered_map<fs::path, ConfigMap, util::FileHash> _overrides;
fs::path _filename;
};
} // namespace llarp

@ -10,202 +10,198 @@
namespace llarp
{
KeyManager::KeyManager() : is_initialized(false), backup_keys(false)
{}
KeyManager::KeyManager() : is_initialized(false), backup_keys(false)
{}
bool
KeyManager::initialize(const llarp::Config& config, bool gen_if_absent, bool is_snode)
{
if (is_initialized)
return false;
if (not is_snode)
{
crypto::identity_keygen(identity_key);
crypto::encryption_keygen(encryption_key);
crypto::encryption_keygen(transport_key);
return true;
}
const fs::path root = config.router.data_dir;
// utility function to assign a path, using the specified config parameter if present and
// falling back to root / defaultName if not
auto deriveFile = [&](const std::string& defaultName, const std::string& option) {
if (option.empty())
{
return root / defaultName;
}
else
{
fs::path file(option);
if (not file.is_absolute())
file = root / file;
return file;
}
};
rc_path = deriveFile(our_rc_filename, config.router.rc_file);
idkey_path = deriveFile(our_identity_filename, config.router.idkey_file);
enckey_path = deriveFile(our_enc_key_filename, config.router.enckey_file);
transkey_path = deriveFile(our_transport_key_filename, config.router.transkey_file);
RemoteRC rc;
if (auto exists = rc.read(rc_path); not exists)
{
if (not gen_if_absent)
{
log::error(logcat, "Could not read RC at path {}", rc_path);
return false;
}
}
else
bool KeyManager::initialize(const llarp::Config& config, bool gen_if_absent, bool is_snode)
{
if (backup_keys = (is_snode and not rc.verify()); backup_keys)
{
auto err = "RC (path:{}) is invalid or out of date"_format(rc_path);
if (is_initialized)
return false;
if (not gen_if_absent)
if (not is_snode)
{
log::error(logcat, err);
return false;
crypto::identity_keygen(identity_key);
crypto::encryption_keygen(encryption_key);
crypto::encryption_keygen(transport_key);
return true;
}
log::warning(logcat, "{}; backing up and regenerating private keys...", err);
const fs::path root = config.router.data_dir;
// utility function to assign a path, using the specified config parameter if present and
// falling back to root / defaultName if not
auto deriveFile = [&](const std::string& defaultName, const std::string& option) {
if (option.empty())
{
return root / defaultName;
}
else
{
fs::path file(option);
if (not file.is_absolute())
file = root / file;
return file;
}
};
rc_path = deriveFile(our_rc_filename, config.router.rc_file);
idkey_path = deriveFile(our_identity_filename, config.router.idkey_file);
enckey_path = deriveFile(our_enc_key_filename, config.router.enckey_file);
transkey_path = deriveFile(our_transport_key_filename, config.router.transkey_file);
RemoteRC rc;
if (auto exists = rc.read(rc_path); not exists)
{
if (not gen_if_absent)
{
log::error(logcat, "Could not read RC at path {}", rc_path);
return false;
}
}
else
{
if (backup_keys = (is_snode and not rc.verify()); backup_keys)
{
auto err = "RC (path:{}) is invalid or out of date"_format(rc_path);
if (not gen_if_absent)
{
log::error(logcat, err);
return false;
}
log::warning(logcat, "{}; backing up and regenerating private keys...", err);
if (not copy_backup_keyfiles())
{
log::error(logcat, "Failed to copy-backup key files");
return false;
}
}
}
if (not copy_backup_keyfiles())
// load encryption key
auto enckey_gen = [](llarp::SecretKey& key) { llarp::crypto::encryption_keygen(key); };
if (not keygen(enckey_path, encryption_key, enckey_gen))
{
log::error(logcat, "Failed to copy-backup key files");
return false;
log::critical(
logcat, "KeyManager::keygen failed to generate encryption key line:{}", __LINE__);
return false;
}
}
}
// load encryption key
auto enckey_gen = [](llarp::SecretKey& key) { llarp::crypto::encryption_keygen(key); };
if (not keygen(enckey_path, encryption_key, enckey_gen))
{
log::critical(
logcat, "KeyManager::keygen failed to generate encryption key line:{}", __LINE__);
return false;
}
// TODO: transport key (currently done in LinkLayer)
auto transkey_gen = [](llarp::SecretKey& key) {
key.Zero();
crypto::encryption_keygen(key);
};
// TODO: transport key (currently done in LinkLayer)
auto transkey_gen = [](llarp::SecretKey& key) {
key.Zero();
crypto::encryption_keygen(key);
};
if (not keygen(transkey_path, transport_key, transkey_gen))
{
log::critical(
logcat, "KeyManager::keygen failed to generate transport key line:{}", __LINE__);
return false;
}
if (not keygen(transkey_path, transport_key, transkey_gen))
{
log::critical(
logcat, "KeyManager::keygen failed to generate transport key line:{}", __LINE__);
return false;
}
if (not config.router.is_relay)
{
// load identity key or create if needed
auto idkey_gen = [](llarp::SecretKey& key) {
// TODO: handle generating from service node seed
llarp::crypto::identity_keygen(key);
};
if (not keygen(idkey_path, identity_key, idkey_gen))
{
log::critical(
logcat, "KeyManager::keygen failed to generate identity key line:{}", __LINE__);
return false;
}
}
if (not config.router.is_relay)
{
// load identity key or create if needed
auto idkey_gen = [](llarp::SecretKey& key) {
// TODO: handle generating from service node seed
llarp::crypto::identity_keygen(key);
};
if (not keygen(idkey_path, identity_key, idkey_gen))
{
log::critical(
logcat, "KeyManager::keygen failed to generate identity key line:{}", __LINE__);
return false;
}
is_initialized = true;
return true;
}
is_initialized = true;
return true;
}
bool
KeyManager::copy_backup_keyfile(const fs::path& filepath)
{
auto findFreeBackupFilename = [](const fs::path& filepath) {
for (int i = 0; i < 9; i++)
{
std::string ext("." + std::to_string(i) + ".bak");
fs::path newPath = filepath;
newPath += ext;
if (not fs::exists(newPath))
return newPath;
}
return fs::path();
};
std::error_code ec;
bool exists = fs::exists(filepath, ec);
if (ec)
bool KeyManager::copy_backup_keyfile(const fs::path& filepath)
{
LogError("Could not determine status of file ", filepath, ": ", ec.message());
return false;
}
auto findFreeBackupFilename = [](const fs::path& filepath) {
for (int i = 0; i < 9; i++)
{
std::string ext("." + std::to_string(i) + ".bak");
fs::path newPath = filepath;
newPath += ext;
if (not fs::exists(newPath))
return newPath;
}
return fs::path();
};
std::error_code ec;
bool exists = fs::exists(filepath, ec);
if (ec)
{
LogError("Could not determine status of file ", filepath, ": ", ec.message());
return false;
}
if (not exists)
{
LogInfo("File ", filepath, " doesn't exist; no backup needed");
return true;
}
if (not exists)
{
LogInfo("File ", filepath, " doesn't exist; no backup needed");
return true;
}
fs::path newFilepath = findFreeBackupFilename(filepath);
if (newFilepath.empty())
{
LogWarn("Could not find an appropriate backup filename for", filepath);
return false;
}
fs::path newFilepath = findFreeBackupFilename(filepath);
if (newFilepath.empty())
{
LogWarn("Could not find an appropriate backup filename for", filepath);
return false;
}
LogInfo("Backing up (moving) key file ", filepath, " to ", newFilepath, "...");
LogInfo("Backing up (moving) key file ", filepath, " to ", newFilepath, "...");
fs::rename(filepath, newFilepath, ec);
if (ec)
{
LogError("Failed to move key file ", ec.message());
return false;
fs::rename(filepath, newFilepath, ec);
if (ec)
{
LogError("Failed to move key file ", ec.message());
return false;
}
return true;
}
return true;
}
bool KeyManager::copy_backup_keyfiles() const
{
std::vector<fs::path> files = {rc_path, idkey_path, enckey_path, transkey_path};
bool
KeyManager::copy_backup_keyfiles() const
{
std::vector<fs::path> files = {rc_path, idkey_path, enckey_path, transkey_path};
for (auto& filepath : files)
{
if (not copy_backup_keyfile(filepath))
return false;
}
for (auto& filepath : files)
{
if (not copy_backup_keyfile(filepath))
return false;
return true;
}
return true;
}
bool
KeyManager::keygen(
fs::path path, llarp::SecretKey& key, std::function<void(llarp::SecretKey& key)> keygen)
{
if (not fs::exists(path))
bool KeyManager::keygen(
fs::path path, llarp::SecretKey& key, std::function<void(llarp::SecretKey& key)> keygen)
{
LogInfo("Generating new key", path);
keygen(key);
if (!key.SaveToFile(path))
{
LogError("Failed to save new key");
return false;
}
if (not fs::exists(path))
{
LogInfo("Generating new key", path);
keygen(key);
if (!key.SaveToFile(path))
{
LogError("Failed to save new key");
return false;
}
}
LogDebug("Loading key from file ", path);
return key.LoadFromFile(path);
}
LogDebug("Loading key from file ", path);
return key.LoadFromFile(path);
}
} // namespace llarp

@ -9,81 +9,75 @@
namespace llarp
{
/// KeyManager manages the cryptographic keys stored on disk for the local
/// node. This includes private keys as well as the self-signed router contact
/// file (e.g. "self.signed").
///
/// Keys are either read from disk if they exist and are valid (see below) or
/// are generated and written to disk.
///
/// In addition, the KeyManager detects when the keys obsolete (e.g. as a
/// result of a software upgrade) and backs up existing keys before writing
/// out new ones.
struct KeyManager
{
/// Utility function to backup a file by moving it. Attempts to find a new
/// filename based on the original that doesn't exist, then moves it. The
/// pattern used is originalFile.N.bak where N is the lowest integer
/// matching a filename that doesn't exist.
/// KeyManager manages the cryptographic keys stored on disk for the local
/// node. This includes private keys as well as the self-signed router contact
/// file (e.g. "self.signed").
///
/// @param filepath is the name of the original file to backup.
/// @return true if the file could be moved or didn't exist, false otherwise
static bool
copy_backup_keyfile(const fs::path& filepath);
/// Keys are either read from disk if they exist and are valid (see below) or
/// are generated and written to disk.
///
/// In addition, the KeyManager detects when the keys obsolete (e.g. as a
/// result of a software upgrade) and backs up existing keys before writing
/// out new ones.
struct KeyManager
{
/// Utility function to backup a file by moving it. Attempts to find a new
/// filename based on the original that doesn't exist, then moves it. The
/// pattern used is originalFile.N.bak where N is the lowest integer
/// matching a filename that doesn't exist.
///
/// @param filepath is the name of the original file to backup.
/// @return true if the file could be moved or didn't exist, false otherwise
static bool copy_backup_keyfile(const fs::path& filepath);
/// Constructor
KeyManager();
/// Constructor
KeyManager();
/// Initializes keys using the provided config, loading from disk. Must be called
/// prior to obtaining any keys and blocks on I/O
///
/// @param config should be a prepared config object
/// @param genIfAbsent determines whether or not we will create files if they
/// do not exist.
/// @param isSNode
/// @return true on success, false otherwise
bool
initialize(const llarp::Config& config, bool genIfAbsent, bool isSNode);
/// Initializes keys using the provided config, loading from disk. Must be called
/// prior to obtaining any keys and blocks on I/O
///
/// @param config should be a prepared config object
/// @param genIfAbsent determines whether or not we will create files if they
/// do not exist.
/// @param isSNode
/// @return true on success, false otherwise
bool initialize(const llarp::Config& config, bool genIfAbsent, bool isSNode);
/// Obtain the self-signed RouterContact
///
/// @param rc (out) will be modified to contian the RouterContact
/// @return true on success, false otherwise
bool
gen_rc(llarp::RouterContact& rc) const;
/// Obtain the self-signed RouterContact
///
/// @param rc (out) will be modified to contian the RouterContact
/// @return true on success, false otherwise
bool gen_rc(llarp::RouterContact& rc) const;
/// Return whether or not we need to backup keys as we load them
bool
needs_backup() const
{
return backup_keys;
}
/// Return whether or not we need to backup keys as we load them
bool needs_backup() const
{
return backup_keys;
}
llarp::SecretKey identity_key;
llarp::SecretKey encryption_key;
llarp::SecretKey transport_key;
llarp::SecretKey identity_key;
llarp::SecretKey encryption_key;
llarp::SecretKey transport_key;
fs::path rc_path;
fs::path idkey_path;
fs::path enckey_path;
fs::path transkey_path;
fs::path rc_path;
fs::path idkey_path;
fs::path enckey_path;
fs::path transkey_path;
private:
std::atomic_bool is_initialized;
std::atomic_bool backup_keys;
private:
std::atomic_bool is_initialized;
std::atomic_bool backup_keys;
/// Backup each key file (by copying, e.g. foo -> foo.bak)
bool
copy_backup_keyfiles() const;
/// Backup each key file (by copying, e.g. foo -> foo.bak)
bool copy_backup_keyfiles() const;
/// Load the key at a given filepath or create it
///
/// @param keygen is a function that will generate the key if needed
static bool
keygen(
fs::path filepath,
llarp::SecretKey& key,
std::function<void(llarp::SecretKey& key)> keygen);
};
/// Load the key at a given filepath or create it
///
/// @param keygen is a function that will generate the key if needed
static bool keygen(
fs::path filepath,
llarp::SecretKey& key,
std::function<void(llarp::SecretKey& key)> keygen);
};
} // namespace llarp

@ -9,153 +9,147 @@ using std::chrono::steady_clock;
namespace llarp::consensus
{
using fseconds = std::chrono::duration<float, std::chrono::seconds::period>;
using fminutes = std::chrono::duration<float, std::chrono::minutes::period>;
static void
check_incoming_tests_impl(
std::string_view name,
const time_point_t& now,
const time_point_t& startup,
detail::incoming_test_state& incoming)
{
const auto elapsed = now - std::max(startup, incoming.last_test);
bool failing = elapsed > reachability_testing::MAX_TIME_WITHOUT_PING;
bool whine = failing != incoming.was_failing
|| (failing && now - incoming.last_whine > reachability_testing::WHINING_INTERVAL);
incoming.was_failing = failing;
if (whine)
using fseconds = std::chrono::duration<float, std::chrono::seconds::period>;
using fminutes = std::chrono::duration<float, std::chrono::minutes::period>;
static void check_incoming_tests_impl(
std::string_view name,
const time_point_t& now,
const time_point_t& startup,
detail::incoming_test_state& incoming)
{
incoming.last_whine = now;
if (!failing)
{
LogInfo(name, " ping received; port is likely reachable again");
}
else
{
if (incoming.last_test.time_since_epoch() == 0s)
{
LogWarn("Have NEVER received ", name, " pings!");
}
else
const auto elapsed = now - std::max(startup, incoming.last_test);
bool failing = elapsed > reachability_testing::MAX_TIME_WITHOUT_PING;
bool whine = failing != incoming.was_failing
|| (failing && now - incoming.last_whine > reachability_testing::WHINING_INTERVAL);
incoming.was_failing = failing;
if (whine)
{
LogWarn(
"Have not received ",
name,
" pings for a long time: ",
fminutes{elapsed}.count(),
" minutes");
incoming.last_whine = now;
if (!failing)
{
LogInfo(name, " ping received; port is likely reachable again");
}
else
{
if (incoming.last_test.time_since_epoch() == 0s)
{
LogWarn("Have NEVER received ", name, " pings!");
}
else
{
LogWarn(
"Have not received ",
name,
" pings for a long time: ",
fminutes{elapsed}.count(),
" minutes");
}
LogWarn(
"Please check your ",
name,
" port. Not being reachable "
"over ",
name,
" may result in a deregistration!");
}
}
LogWarn(
"Please check your ",
name,
" port. Not being reachable "
"over ",
name,
" may result in a deregistration!");
}
}
}
void
reachability_testing::check_incoming_tests(const time_point_t& now)
{
check_incoming_tests_impl("lokinet", now, startup, last);
}
void
reachability_testing::incoming_ping(const time_point_t& now)
{
last.last_test = now;
}
std::optional<RouterID>
reachability_testing::next_random(Router* router, const time_point_t& now, bool requeue)
{
if (next_general_test > now)
return std::nullopt;
next_general_test = now
+ std::chrono::duration_cast<time_point_t::duration>(
fseconds(TESTING_INTERVAL(llarp::csrng)));
// Pull the next element off the queue, but skip ourself, any that are no longer registered, and
// any that are currently known to be failing (those are queued for testing separately).
auto local_pk = router->local_rid();
while (!testing_queue.empty())
void reachability_testing::check_incoming_tests(const time_point_t& now)
{
check_incoming_tests_impl("lokinet", now, startup, last);
}
void reachability_testing::incoming_ping(const time_point_t& now)
{
auto& pk = testing_queue.back();
std::optional<RouterID> sn;
last.last_test = now;
}
if (pk != local_pk && !failing.count(pk))
sn = pk;
std::optional<RouterID> reachability_testing::next_random(
Router* router, const time_point_t& now, bool requeue)
{
if (next_general_test > now)
return std::nullopt;
next_general_test = now
+ std::chrono::duration_cast<time_point_t::duration>(
fseconds(TESTING_INTERVAL(llarp::csrng)));
testing_queue.pop_back();
// Pull the next element off the queue, but skip ourself, any that are no longer registered,
// and any that are currently known to be failing (those are queued for testing separately).
auto local_pk = router->local_rid();
if (sn)
return sn;
}
while (!testing_queue.empty())
{
auto& pk = testing_queue.back();
std::optional<RouterID> sn;
if (pk != local_pk && !failing.count(pk))
sn = pk;
if (!requeue)
return std::nullopt;
testing_queue.pop_back();
// FIXME: when a *new* node comes online we need to inject it into a random position in the SN
// list with probability (L/N) [L = current list size, N = potential list size]
//
if (sn)
return sn;
}
// We exhausted the queue so repopulate it and try again
if (!requeue)
return std::nullopt;
testing_queue.clear();
const auto& all = router->get_whitelist();
// FIXME: when a *new* node comes online we need to inject it into a random position in the
// SN list with probability (L/N) [L = current list size, N = potential list size]
//
testing_queue.insert(testing_queue.begin(), all.begin(), all.end());
// We exhausted the queue so repopulate it and try again
std::shuffle(testing_queue.begin(), testing_queue.end(), llarp::csrng);
testing_queue.clear();
const auto& all = router->get_whitelist();
// Recurse with the rebuilt list, but don't let it try rebuilding again
return next_random(router, now, false);
}
testing_queue.insert(testing_queue.begin(), all.begin(), all.end());
std::shuffle(testing_queue.begin(), testing_queue.end(), llarp::csrng);
// Recurse with the rebuilt list, but don't let it try rebuilding again
return next_random(router, now, false);
}
std::vector<std::pair<RouterID, int>> reachability_testing::get_failing(const time_point_t& now)
{
// Our failing_queue puts the oldest retest times at the top, so pop them off into our
// result until the top node should be retested sometime in the future
std::vector<std::pair<RouterID, int>> result;
while (result.size() < MAX_RETESTS_PER_TICK && !failing_queue.empty())
{
auto& [pk, retest_time, failures] = failing_queue.top();
if (retest_time > now)
break;
if (failing.count(pk))
result.emplace_back(pk, failures);
failing_queue.pop();
}
return result;
}
void reachability_testing::add_failing_node(const RouterID& pk, int previous_failures)
{
using namespace std::chrono;
if (previous_failures < 0)
previous_failures = 0;
auto next_test_in = duration_cast<time_point_t::duration>(
previous_failures * TESTING_BACKOFF + fseconds{TESTING_INTERVAL(llarp::csrng)});
if (next_test_in > TESTING_BACKOFF_MAX)
next_test_in = TESTING_BACKOFF_MAX;
failing.insert(pk);
failing_queue.emplace(pk, steady_clock::now() + next_test_in, previous_failures + 1);
}
std::vector<std::pair<RouterID, int>>
reachability_testing::get_failing(const time_point_t& now)
{
// Our failing_queue puts the oldest retest times at the top, so pop them off into our result
// until the top node should be retested sometime in the future
std::vector<std::pair<RouterID, int>> result;
while (result.size() < MAX_RETESTS_PER_TICK && !failing_queue.empty())
void reachability_testing::remove_node_from_failing(const RouterID& pk)
{
auto& [pk, retest_time, failures] = failing_queue.top();
if (retest_time > now)
break;
if (failing.count(pk))
result.emplace_back(pk, failures);
failing_queue.pop();
failing.erase(pk);
}
return result;
}
void
reachability_testing::add_failing_node(const RouterID& pk, int previous_failures)
{
using namespace std::chrono;
if (previous_failures < 0)
previous_failures = 0;
auto next_test_in = duration_cast<time_point_t::duration>(
previous_failures * TESTING_BACKOFF + fseconds{TESTING_INTERVAL(llarp::csrng)});
if (next_test_in > TESTING_BACKOFF_MAX)
next_test_in = TESTING_BACKOFF_MAX;
failing.insert(pk);
failing_queue.emplace(pk, steady_clock::now() + next_test_in, previous_failures + 1);
}
void
reachability_testing::remove_node_from_failing(const RouterID& pk)
{
failing.erase(pk);
}
} // namespace llarp::consensus

@ -12,136 +12,134 @@
namespace llarp
{
struct Router;
struct Router;
}
namespace llarp::consensus
{
namespace detail
{
using clock_t = std::chrono::steady_clock;
using time_point_t = std::chrono::time_point<clock_t>;
// Returns std::greater on the std::get<N>(v)th element value.
template <typename T, size_t N>
struct nth_greater
namespace detail
{
constexpr bool
operator()(const T& lhs, const T& rhs) const
{
return std::greater<std::tuple_element_t<N, T>>{}(std::get<N>(lhs), std::get<N>(rhs));
}
};
struct incoming_test_state
using clock_t = std::chrono::steady_clock;
using time_point_t = std::chrono::time_point<clock_t>;
// Returns std::greater on the std::get<N>(v)th element value.
template <typename T, size_t N>
struct nth_greater
{
constexpr bool operator()(const T& lhs, const T& rhs) const
{
return std::greater<std::tuple_element_t<N, T>>{}(
std::get<N>(lhs), std::get<N>(rhs));
}
};
struct incoming_test_state
{
time_point_t last_test{};
time_point_t last_whine{};
bool was_failing = false;
};
} // namespace detail
using time_point_t = detail::time_point_t;
using clock_t = detail::clock_t;
// How often we tick the timer to check whether we need to do any tests.
constexpr auto REACHABILITY_TESTING_TIMER_INTERVAL = 50ms;
class reachability_testing
{
time_point_t last_test{};
time_point_t last_whine{};
bool was_failing = false;
public:
// Distribution for the seconds between node tests: we throw in some randomness to avoid
// potential clustering of tests. (Note that there is some granularity here as the test
// timer only runs every REACHABILITY_TESTING_TIMER_INTERVAL).
std::normal_distribution<float> TESTING_INTERVAL{10.0, 3.0};
// The linear backoff after each consecutive test failure before we re-test. Specifically
// we schedule the next re-test for (TESTING_BACKOFF*previous_failures) +
// TESTING_INTERVAL(rng).
inline static constexpr auto TESTING_BACKOFF = 10s;
// The upper bound for the re-test interval.
inline static constexpr auto TESTING_BACKOFF_MAX = 2min;
// The maximum number of nodes that we will re-test at once (i.e. per
// TESTING_TIMING_INTERVAL); mainly intended to throttle ourselves if, for instance, our own
// connectivity loss makes us accumulate tons of nodes to test all at once. (Despite the
// random intervals, this can happen if we also get decommissioned during which we can't
// test at all but still have lots of failing nodes we want to test right away when we get
// recommissioned).
inline static constexpr int MAX_RETESTS_PER_TICK = 4;
// Maximum time without a ping before we start whining about it.
//
// We have a probability of about 0.368* of *not* getting pinged within a ping interval
// (10s), and so the probability of not getting a ping for 2 minutes (i.e. 12 test spans)
// just because we haven't been selected is extremely small (0.0000061). It also coincides
// nicely with blockchain time (i.e. two minutes) and our max testing backoff.
//
// * = approx value of ((n-1)/n)^n for non-tiny values of n
inline static constexpr auto MAX_TIME_WITHOUT_PING = 2min;
// How often we whine in the logs about being unreachable
inline static constexpr auto WHINING_INTERVAL = 2min;
private:
// Queue of pubkeys of service nodes to test; we pop off the back of this until the queue
// empties then we refill it with a shuffled list of all pubkeys then pull off of it until
// it is empty again, etc.
std::vector<RouterID> testing_queue;
// The next time for a general test
time_point_t next_general_test = time_point_t::min();
// When we started, so that we know not to hold off on whining about no pings for a while.
const time_point_t startup = clock_t::now();
// Pubkeys, next test times, and sequential failure counts of service nodes that are
// currently in "failed" status along with the last time they failed; we retest them first
// after 10s then back off linearly by an additional 10s up to a max testing interval of
// 2m30s, until we get a successful response.
using FailingPK = std::tuple<RouterID, time_point_t, int>;
std::priority_queue<FailingPK, std::vector<FailingPK>, detail::nth_greater<FailingPK, 1>>
failing_queue;
std::unordered_set<RouterID> failing;
// Track the last time *this node* was tested by other network nodes; used to detect and
// warn about possible network issues.
detail::incoming_test_state last;
public:
// If it is time to perform another random test, this returns the next node to test from the
// testing queue and returns it, also updating the timer for the next test. If it is not
// yet time, or if the queue is empty and cannot current be replenished, returns
// std::nullopt. If the queue empties then this builds a new one by shuffling current
// public keys in the swarm's "all nodes" then starts using the new queue for this an
// subsequent calls.
//
// `requeue` is mainly for internal use: if false it avoids rebuilding the queue if we run
// out (and instead just return nullopt).
std::optional<RouterID> next_random(
Router* router, const time_point_t& now = clock_t::now(), bool requeue = true);
// Removes and returns up to MAX_RETESTS_PER_TICK nodes that are due to be tested (i.e.
// next-testing-time <= now). Returns [snrecord, #previous-failures] for each.
std::vector<std::pair<RouterID, int>> get_failing(const time_point_t& now = clock_t::now());
// Adds a bad node pubkey to the failing list, to be re-tested soon (with a backoff
// depending on `failures`; see TESTING_BACKOFF). `previous_failures` should be the number
// of previous failures *before* this one, i.e. 0 for a random general test; or the failure
// count returned by `get_failing` for repeated failures.
void add_failing_node(const RouterID& pk, int previous_failures = 0);
/// removes the public key from the failing set
void remove_node_from_failing(const RouterID& pk);
// Called when this router receives an incomming session
void incoming_ping(const time_point_t& now = clock_t::now());
// Check whether we received incoming pings recently
void check_incoming_tests(const time_point_t& now = clock_t::now());
};
} // namespace detail
using time_point_t = detail::time_point_t;
using clock_t = detail::clock_t;
// How often we tick the timer to check whether we need to do any tests.
constexpr auto REACHABILITY_TESTING_TIMER_INTERVAL = 50ms;
class reachability_testing
{
public:
// Distribution for the seconds between node tests: we throw in some randomness to avoid
// potential clustering of tests. (Note that there is some granularity here as the test timer
// only runs every REACHABILITY_TESTING_TIMER_INTERVAL).
std::normal_distribution<float> TESTING_INTERVAL{10.0, 3.0};
// The linear backoff after each consecutive test failure before we re-test. Specifically we
// schedule the next re-test for (TESTING_BACKOFF*previous_failures) + TESTING_INTERVAL(rng).
inline static constexpr auto TESTING_BACKOFF = 10s;
// The upper bound for the re-test interval.
inline static constexpr auto TESTING_BACKOFF_MAX = 2min;
// The maximum number of nodes that we will re-test at once (i.e. per TESTING_TIMING_INTERVAL);
// mainly intended to throttle ourselves if, for instance, our own connectivity loss makes us
// accumulate tons of nodes to test all at once. (Despite the random intervals, this can happen
// if we also get decommissioned during which we can't test at all but still have lots of
// failing nodes we want to test right away when we get recommissioned).
inline static constexpr int MAX_RETESTS_PER_TICK = 4;
// Maximum time without a ping before we start whining about it.
//
// We have a probability of about 0.368* of *not* getting pinged within a ping interval (10s),
// and so the probability of not getting a ping for 2 minutes (i.e. 12 test spans) just because
// we haven't been selected is extremely small (0.0000061). It also coincides nicely with
// blockchain time (i.e. two minutes) and our max testing backoff.
//
// * = approx value of ((n-1)/n)^n for non-tiny values of n
inline static constexpr auto MAX_TIME_WITHOUT_PING = 2min;
// How often we whine in the logs about being unreachable
inline static constexpr auto WHINING_INTERVAL = 2min;
private:
// Queue of pubkeys of service nodes to test; we pop off the back of this until the queue
// empties then we refill it with a shuffled list of all pubkeys then pull off of it until it is
// empty again, etc.
std::vector<RouterID> testing_queue;
// The next time for a general test
time_point_t next_general_test = time_point_t::min();
// When we started, so that we know not to hold off on whining about no pings for a while.
const time_point_t startup = clock_t::now();
// Pubkeys, next test times, and sequential failure counts of service nodes that are currently
// in "failed" status along with the last time they failed; we retest them first after 10s then
// back off linearly by an additional 10s up to a max testing interval of 2m30s, until we get a
// successful response.
using FailingPK = std::tuple<RouterID, time_point_t, int>;
std::priority_queue<FailingPK, std::vector<FailingPK>, detail::nth_greater<FailingPK, 1>>
failing_queue;
std::unordered_set<RouterID> failing;
// Track the last time *this node* was tested by other network nodes; used to detect and warn
// about possible network issues.
detail::incoming_test_state last;
public:
// If it is time to perform another random test, this returns the next node to test from the
// testing queue and returns it, also updating the timer for the next test. If it is not yet
// time, or if the queue is empty and cannot current be replenished, returns std::nullopt. If
// the queue empties then this builds a new one by shuffling current public keys in the swarm's
// "all nodes" then starts using the new queue for this an subsequent calls.
//
// `requeue` is mainly for internal use: if false it avoids rebuilding the queue if we run
// out (and instead just return nullopt).
std::optional<RouterID>
next_random(Router* router, const time_point_t& now = clock_t::now(), bool requeue = true);
// Removes and returns up to MAX_RETESTS_PER_TICK nodes that are due to be tested (i.e.
// next-testing-time <= now). Returns [snrecord, #previous-failures] for each.
std::vector<std::pair<RouterID, int>>
get_failing(const time_point_t& now = clock_t::now());
// Adds a bad node pubkey to the failing list, to be re-tested soon (with a backoff depending on
// `failures`; see TESTING_BACKOFF). `previous_failures` should be the number of previous
// failures *before* this one, i.e. 0 for a random general test; or the failure count returned
// by `get_failing` for repeated failures.
void
add_failing_node(const RouterID& pk, int previous_failures = 0);
/// removes the public key from the failing set
void
remove_node_from_failing(const RouterID& pk);
// Called when this router receives an incomming session
void
incoming_ping(const time_point_t& now = clock_t::now());
// Check whether we received incoming pings recently
void
check_incoming_tests(const time_point_t& now = clock_t::now());
};
} // namespace llarp::consensus

@ -4,12 +4,12 @@
namespace llarp::apple
{
/// Localhost port on macOS where we proxy DNS requests *through* the tunnel, because without
/// calling into special snowflake Apple network APIs an extension's network connections all go
/// around the tunnel, even when the tunnel is (supposedly) the default route.
inline constexpr std::uint16_t dns_trampoline_port = 1053;
/// Localhost port on macOS where we proxy DNS requests *through* the tunnel, because without
/// calling into special snowflake Apple network APIs an extension's network connections all go
/// around the tunnel, even when the tunnel is (supposedly) the default route.
inline constexpr std::uint16_t dns_trampoline_port = 1053;
/// We query the above trampoline from unbound with this fixed source port (so that the trampoline
/// is simplified by not having to track different ports for different requests).
inline constexpr std::uint16_t dns_trampoline_source_port = 1054;
/// We query the above trampoline from unbound with this fixed source port (so that the
/// trampoline is simplified by not having to track different ports for different requests).
inline constexpr std::uint16_t dns_trampoline_source_port = 1054;
} // namespace llarp::apple

@ -12,50 +12,46 @@
namespace llarp
{
constexpr auto our_rc_filename = "self.signed";
constexpr auto our_identity_filename = "identity.key";
constexpr auto our_enc_key_filename = "encryption.key";
constexpr auto our_transport_key_filename = "transport.key";
constexpr auto our_rc_filename = "self.signed";
constexpr auto our_identity_filename = "identity.key";
constexpr auto our_enc_key_filename = "encryption.key";
constexpr auto our_transport_key_filename = "transport.key";
constexpr auto nodedb_dirname = "nodedb";
constexpr auto nodedb_dirname = "nodedb";
inline fs::path
GetDefaultDataDir()
{
if constexpr (not platform::is_windows)
inline fs::path GetDefaultDataDir()
{
fs::path datadir{"/var/lib/lokinet"};
#ifndef _WIN32
if (auto uid = geteuid())
{
if (auto* pw = getpwuid(uid))
if constexpr (not platform::is_windows)
{
datadir = fs::path{pw->pw_dir} / ".lokinet";
}
}
fs::path datadir{"/var/lib/lokinet"};
#ifndef _WIN32
if (auto uid = geteuid())
{
if (auto* pw = getpwuid(uid))
{
datadir = fs::path{pw->pw_dir} / ".lokinet";
}
}
#endif
return datadir;
return datadir;
}
else
return "C:\\ProgramData\\Lokinet";
}
inline fs::path GetDefaultConfigFilename()
{
return "lokinet.ini";
}
inline fs::path GetDefaultConfigPath()
{
return GetDefaultDataDir() / GetDefaultConfigFilename();
}
inline fs::path GetDefaultBootstrap()
{
return GetDefaultDataDir() / "bootstrap.signed";
}
else
return "C:\\ProgramData\\Lokinet";
}
inline fs::path
GetDefaultConfigFilename()
{
return "lokinet.ini";
}
inline fs::path
GetDefaultConfigPath()
{
return GetDefaultDataDir() / GetDefaultConfigFilename();
}
inline fs::path
GetDefaultBootstrap()
{
return GetDefaultDataDir() / "bootstrap.signed";
}
} // namespace llarp

@ -11,5 +11,5 @@ static constexpr auto LinkLayerConnectTimeout = 5s;
namespace llarp::constants
{
static constexpr auto DefaultInboundIWPPort = uint16_t{1090};
static constexpr auto DefaultInboundIWPPort = uint16_t{1090};
}

@ -2,6 +2,6 @@
namespace llarp::constants
{
constexpr auto udp_header_bytes = 8;
constexpr auto ip_header_min_bytes = 20;
constexpr auto udp_header_bytes = 8;
constexpr auto ip_header_min_bytes = 20;
} // namespace llarp::constants

@ -8,35 +8,36 @@
namespace llarp::path
{
/// maximum path length
constexpr std::size_t MAX_LEN = 8;
/// default path length
constexpr std::size_t DEFAULT_LEN = 4;
/// pad messages to the nearest this many bytes
constexpr std::size_t PAD_SIZE = 128;
/// default path lifetime in ms
constexpr std::chrono::milliseconds DEFAULT_LIFETIME = 20min;
/// minimum intro lifetime we will advertise
constexpr std::chrono::milliseconds MIN_INTRO_LIFETIME = DEFAULT_LIFETIME / 2;
/// number of slices of path lifetime to spread intros out via
constexpr auto INTRO_SPREAD_SLICES = 4;
/// spacing frequency at which we try to build paths for introductions
constexpr std::chrono::milliseconds INTRO_PATH_SPREAD = DEFAULT_LIFETIME / INTRO_SPREAD_SLICES;
/// how long away from expiration in millseconds do we consider an intro to become stale
constexpr std::chrono::milliseconds INTRO_STALE_THRESHOLD = DEFAULT_LIFETIME - INTRO_PATH_SPREAD;
/// Minimum paths to keep around for intros; mainly used at startup (the
/// spread, above, should be able to maintain more than this number of paths
/// normally once things are going).
constexpr std::size_t MIN_INTRO_PATHS = 4;
/// after this many ms a path build times out
constexpr auto BUILD_TIMEOUT = 10s;
/// maximum path length
constexpr std::size_t MAX_LEN = 8;
/// default path length
constexpr std::size_t DEFAULT_LEN = 4;
/// pad messages to the nearest this many bytes
constexpr std::size_t PAD_SIZE = 128;
/// default path lifetime in ms
constexpr std::chrono::milliseconds DEFAULT_LIFETIME = 20min;
/// minimum intro lifetime we will advertise
constexpr std::chrono::milliseconds MIN_INTRO_LIFETIME = DEFAULT_LIFETIME / 2;
/// number of slices of path lifetime to spread intros out via
constexpr auto INTRO_SPREAD_SLICES = 4;
/// spacing frequency at which we try to build paths for introductions
constexpr std::chrono::milliseconds INTRO_PATH_SPREAD = DEFAULT_LIFETIME / INTRO_SPREAD_SLICES;
/// how long away from expiration in millseconds do we consider an intro to become stale
constexpr std::chrono::milliseconds INTRO_STALE_THRESHOLD =
DEFAULT_LIFETIME - INTRO_PATH_SPREAD;
/// Minimum paths to keep around for intros; mainly used at startup (the
/// spread, above, should be able to maintain more than this number of paths
/// normally once things are going).
constexpr std::size_t MIN_INTRO_PATHS = 4;
/// after this many ms a path build times out
constexpr auto BUILD_TIMEOUT = 10s;
/// measure latency every this interval ms
constexpr auto LATENCY_INTERVAL = 20s;
/// if a path is inactive for this amount of time it's dead
constexpr auto ALIVE_TIMEOUT = LATENCY_INTERVAL * 1.5;
/// measure latency every this interval ms
constexpr auto LATENCY_INTERVAL = 20s;
/// if a path is inactive for this amount of time it's dead
constexpr auto ALIVE_TIMEOUT = LATENCY_INTERVAL * 1.5;
/// how big transit hop traffic queues are
constexpr std::size_t TRANSIT_HOP_QUEUE_SIZE = 256;
/// how big transit hop traffic queues are
constexpr std::size_t TRANSIT_HOP_QUEUE_SIZE = 256;
} // namespace llarp::path

@ -3,98 +3,98 @@
/// namespace for platform feature detection constexprs
namespace llarp::platform
{
/// are we linux ?
inline constexpr bool is_linux =
/// are we linux ?
inline constexpr bool is_linux =
#ifdef __linux__
true
true
#else
false
false
#endif
;
;
/// building with systemd enabled ?
inline constexpr bool with_systemd =
/// building with systemd enabled ?
inline constexpr bool with_systemd =
#ifdef WITH_SYSTEMD
true
true
#else
false
false
#endif
;
;
/// are we freebsd ?
inline constexpr bool is_freebsd =
/// are we freebsd ?
inline constexpr bool is_freebsd =
#ifdef __FreeBSD__
true
true
#else
false
false
#endif
;
;
/// are we windows ?
inline constexpr bool is_windows =
/// are we windows ?
inline constexpr bool is_windows =
#ifdef _WIN32
true
true
#else
false
false
#endif
;
;
/// are we an apple platform ?
inline constexpr bool is_apple =
/// are we an apple platform ?
inline constexpr bool is_apple =
#ifdef __APPLE__
true
true
#else
false
false
#endif
;
;
/// are we building as an apple system extension
inline constexpr bool is_apple_sysex =
/// are we building as an apple system extension
inline constexpr bool is_apple_sysex =
#ifdef MACOS_SYSTEM_EXTENSION
true
true
#else
false
false
#endif
;
;
/// are we an android platform ?
inline constexpr bool is_android =
/// are we an android platform ?
inline constexpr bool is_android =
#ifdef ANDROID
true
true
#else
false
false
#endif
;
;
/// are we an iphone ?
inline constexpr bool is_iphone =
/// are we an iphone ?
inline constexpr bool is_iphone =
#ifdef IOS
true
true
#else
false
false
#endif
;
;
/// are we running with pybind simulation mode enabled?
inline constexpr bool is_simulation =
/// are we running with pybind simulation mode enabled?
inline constexpr bool is_simulation =
#ifdef LOKINET_HIVE
true
true
#else
false
false
#endif
;
/// do we have systemd support ?
// on cross compiles sometimes weird permutations of target and host make this value not correct,
// this ensures it always is
inline constexpr bool has_systemd = is_linux and with_systemd and not(is_android or is_windows);
;
/// do we have systemd support ?
// on cross compiles sometimes weird permutations of target and host make this value not
// correct, this ensures it always is
inline constexpr bool has_systemd = is_linux and with_systemd and not(is_android or is_windows);
/// are we using macos ?
inline constexpr bool is_macos = is_apple and not is_iphone;
/// are we using macos ?
inline constexpr bool is_macos = is_apple and not is_iphone;
/// are we a mobile phone ?
inline constexpr bool is_mobile = is_android or is_iphone;
/// are we a mobile phone ?
inline constexpr bool is_mobile = is_android or is_iphone;
/// does this platform support native ipv6 ?
// TODO: make windows support ipv6
inline constexpr bool supports_ipv6 = not is_windows;
/// does this platform support native ipv6 ?
// TODO: make windows support ipv6
inline constexpr bool supports_ipv6 = not is_windows;
} // namespace llarp::platform

@ -2,8 +2,8 @@
namespace llarp::constants
{
/// current network wide protocol version
// TODO: enum class
constexpr auto proto_version = 0;
/// current network wide protocol version
// TODO: enum class
constexpr auto proto_version = 0;
} // namespace llarp::constants

@ -4,6 +4,6 @@
namespace llarp
{
using namespace std::literals;
using namespace std::literals;
} // namespace llarp

@ -5,11 +5,11 @@
namespace llarp
{
// Given a full lokinet version of: lokinet-1.2.3-abc these are:
extern const std::array<uint8_t, 3> LOKINET_VERSION;
extern const char* const LOKINET_VERSION_TAG;
extern const char* const LOKINET_VERSION_FULL;
// Given a full lokinet version of: lokinet-1.2.3-abc these are:
extern const std::array<uint8_t, 3> LOKINET_VERSION;
extern const char* const LOKINET_VERSION_TAG;
extern const char* const LOKINET_VERSION_FULL;
extern const char* const LOKINET_DEFAULT_NETID;
extern const char* const LOKINET_TESTNET_NETID;
extern const char* const LOKINET_DEFAULT_NETID;
extern const char* const LOKINET_TESTNET_NETID;
} // namespace llarp

@ -20,194 +20,178 @@
namespace llarp
{
bool
Context::CallSafe(std::function<void(void)> f)
{
if (!loop)
return false;
loop->call_soon(std::move(f));
return true;
}
void
Context::Configure(std::shared_ptr<Config> conf)
{
if (nullptr != config.get())
throw std::runtime_error("Config already exists");
config = std::move(conf);
}
bool
Context::IsUp() const
{
return router && router->IsRunning();
}
bool
Context::LooksAlive() const
{
return router && router->LooksAlive();
}
void
Context::Setup(const RuntimeOptions& opts)
{
/// Call one of the Configure() methods before calling Setup()
if (not config)
throw std::runtime_error("Cannot call Setup() on context without a Config");
if (opts.showBanner)
llarp::LogInfo(fmt::format("{}", llarp::LOKINET_VERSION_FULL));
if (!loop)
{
auto jobQueueSize = std::max(event_loop_queue_size, config->router.job_que_size);
loop = EventLoop::create(jobQueueSize);
}
router = makeRouter(loop);
nodedb = makeNodeDB();
if (!router->Configure(config, opts.isSNode, nodedb))
throw std::runtime_error("Failed to configure router");
}
std::shared_ptr<NodeDB>
Context::makeNodeDB()
{
return std::make_shared<NodeDB>(
nodedb_dirname,
[r = router.get()](auto call) { r->queue_disk_io(std::move(call)); },
router.get());
}
std::shared_ptr<Router>
Context::makeRouter(const EventLoop_ptr& loop)
{
return std::make_shared<Router>(loop, makeVPNPlatform());
}
std::shared_ptr<vpn::Platform>
Context::makeVPNPlatform()
{
auto plat = vpn::MakeNativePlatform(this);
if (plat == nullptr)
throw std::runtime_error("vpn platform not supported");
return plat;
}
int
Context::Run(const RuntimeOptions&)
{
if (router == nullptr)
{
// we are not set up so we should die
llarp::LogError("cannot run non configured context");
return 1;
}
if (not router->Run())
return 2;
// run net io thread
llarp::LogInfo("running mainloop");
loop->run();
if (closeWaiter)
{
closeWaiter->set_value();
}
Close();
return 0;
}
void
Context::CloseAsync()
{
/// already closing
if (IsStopping())
return;
loop->call([this]() { HandleSignal(SIGTERM); });
closeWaiter = std::make_unique<std::promise<void>>();
}
bool
Context::IsStopping() const
{
return closeWaiter.operator bool();
}
void
Context::Wait()
{
if (closeWaiter)
bool Context::CallSafe(std::function<void(void)> f)
{
closeWaiter->get_future().wait();
closeWaiter.reset();
if (!loop)
return false;
loop->call_soon(std::move(f));
return true;
}
}
void
Context::HandleSignal(int sig)
{
llarp::log::debug(logcat, "Handling signal {}", sig);
if (sig == SIGINT || sig == SIGTERM)
void Context::Configure(std::shared_ptr<Config> conf)
{
SigINT();
if (nullptr != config.get())
throw std::runtime_error("Config already exists");
config = std::move(conf);
}
#ifndef _WIN32
if (sig == SIGUSR1)
bool Context::IsUp() const
{
if (router and not router->is_service_node())
{
LogInfo("SIGUSR1: resetting network state");
router->Thaw();
}
return router && router->IsRunning();
}
if (sig == SIGHUP)
bool Context::LooksAlive() const
{
Reload();
return router && router->LooksAlive();
}
void Context::Setup(const RuntimeOptions& opts)
{
/// Call one of the Configure() methods before calling Setup()
if (not config)
throw std::runtime_error("Cannot call Setup() on context without a Config");
if (opts.showBanner)
llarp::LogInfo(fmt::format("{}", llarp::LOKINET_VERSION_FULL));
if (!loop)
{
auto jobQueueSize = std::max(event_loop_queue_size, config->router.job_que_size);
loop = EventLoop::create(jobQueueSize);
}
router = makeRouter(loop);
nodedb = makeNodeDB();
if (!router->Configure(config, opts.isSNode, nodedb))
throw std::runtime_error("Failed to configure router");
}
std::shared_ptr<NodeDB> Context::makeNodeDB()
{
return std::make_shared<NodeDB>(
nodedb_dirname,
[r = router.get()](auto call) { r->queue_disk_io(std::move(call)); },
router.get());
}
std::shared_ptr<Router> Context::makeRouter(const EventLoop_ptr& loop)
{
return std::make_shared<Router>(loop, makeVPNPlatform());
}
std::shared_ptr<vpn::Platform> Context::makeVPNPlatform()
{
auto plat = vpn::MakeNativePlatform(this);
if (plat == nullptr)
throw std::runtime_error("vpn platform not supported");
return plat;
}
int Context::Run(const RuntimeOptions&)
{
if (router == nullptr)
{
// we are not set up so we should die
llarp::LogError("cannot run non configured context");
return 1;
}
if (not router->Run())
return 2;
// run net io thread
llarp::LogInfo("running mainloop");
loop->run();
if (closeWaiter)
{
closeWaiter->set_value();
}
Close();
return 0;
}
void Context::CloseAsync()
{
/// already closing
if (IsStopping())
return;
loop->call([this]() { HandleSignal(SIGTERM); });
closeWaiter = std::make_unique<std::promise<void>>();
}
bool Context::IsStopping() const
{
return closeWaiter.operator bool();
}
void Context::Wait()
{
if (closeWaiter)
{
closeWaiter->get_future().wait();
closeWaiter.reset();
}
}
void Context::HandleSignal(int sig)
{
llarp::log::debug(logcat, "Handling signal {}", sig);
if (sig == SIGINT || sig == SIGTERM)
{
SigINT();
}
#ifndef _WIN32
if (sig == SIGUSR1)
{
if (router and not router->is_service_node())
{
LogInfo("SIGUSR1: resetting network state");
router->Thaw();
}
}
if (sig == SIGHUP)
{
Reload();
}
#endif
}
}
void
Context::Reload()
{}
void Context::Reload()
{}
void
Context::SigINT()
{
if (router)
void Context::SigINT()
{
llarp::log::error(logcat, "Handling SIGINT");
/// async stop router on sigint
router->Stop();
if (router)
{
llarp::log::error(logcat, "Handling SIGINT");
/// async stop router on sigint
router->Stop();
}
}
}
void
Context::Close()
{
llarp::LogDebug("free config");
config.reset();
void Context::Close()
{
llarp::LogDebug("free config");
config.reset();
llarp::LogDebug("free nodedb");
nodedb.reset();
llarp::LogDebug("free nodedb");
nodedb.reset();
llarp::LogDebug("free router");
router.reset();
llarp::LogDebug("free router");
router.reset();
llarp::LogDebug("free loop");
loop.reset();
}
llarp::LogDebug("free loop");
loop.reset();
}
Context::Context()
{
// service_manager is a global and context isnt
llarp::sys::service_manager->give_context(this);
}
Context::Context()
{
// service_manager is a global and context isnt
llarp::sys::service_manager->give_context(this);
}
} // namespace llarp

File diff suppressed because it is too large Load Diff

@ -10,153 +10,120 @@
namespace llarp
{
/*
TODO:
- make uint8_t pointers const where needed
*/
namespace crypto
{
/// decrypt cipherText given the key generated from name
std::optional<AlignedBuffer<32>>
maybe_decrypt_name(std::string_view ciphertext, SymmNonce nonce, std::string_view name);
/// xchacha symmetric cipher
bool
xchacha20(uint8_t*, size_t size, const SharedSecret&, const SymmNonce&);
bool
xchacha20(uint8_t*, size_t size, const uint8_t*, const uint8_t*);
SymmNonce
onion(
unsigned char* buf,
size_t size,
const SharedSecret& k,
const SymmNonce& nonce,
const SymmNonce& xor_factor);
/// path dh creator's side
bool
dh_client(SharedSecret&, const PubKey&, const SecretKey&, const SymmNonce&);
/// path dh relay side
bool
dh_server(SharedSecret&, const PubKey&, const SecretKey&, const SymmNonce&);
bool
dh_server(
uint8_t* shared_secret,
const uint8_t* other_pk,
const uint8_t* local_pk,
const uint8_t* nonce);
/// blake2b 256 bit
bool
shorthash(ShortHash&, uint8_t*, size_t size);
/// blake2s 256 bit hmac
bool
hmac(uint8_t*, uint8_t*, size_t, const SharedSecret&);
/// ed25519 sign
bool
sign(Signature&, const SecretKey&, uint8_t* buf, size_t size);
/// ed25519 sign, using pointers
bool
sign(uint8_t* sig, uint8_t* sk, uint8_t* buf, size_t size);
bool
sign(uint8_t* sig, const SecretKey& sk, ustring_view buf);
/// ed25519 sign (custom with derived keys)
bool
sign(Signature&, const PrivateKey&, uint8_t* buf, size_t size);
/// ed25519 verify
bool
verify(const PubKey&, ustring_view, ustring_view);
bool
verify(const PubKey&, uint8_t*, size_t, const Signature&);
bool verify(ustring_view, ustring_view, ustring_view);
bool
verify(uint8_t*, uint8_t*, size_t, uint8_t*);
/// derive sub keys for public keys. hash is really only intended for
/// testing ands key_n if given.
bool
derive_subkey(
PubKey& derived,
const PubKey& root,
uint64_t key_n,
const AlignedBuffer<32>* hash = nullptr);
/// derive sub keys for private keys. hash is really only intended for
/// testing ands key_n if given.
bool
derive_subkey_private(
PrivateKey& derived,
const SecretKey& root,
uint64_t key_n,
const AlignedBuffer<32>* hash = nullptr);
/// randomize buffer
void
randomize(uint8_t* buf, size_t len);
/// randomizer memory
void
randbytes(byte_t*, size_t);
/// generate signing keypair
void
identity_keygen(SecretKey&);
/// generate encryption keypair
void
encryption_keygen(SecretKey&);
/// generate post quantum encrytion key
void
pqe_keygen(PQKeyPair&);
/// post quantum decrypt (buffer, sharedkey_dst, sec)
bool
pqe_decrypt(const PQCipherBlock&, SharedSecret&, const byte_t*);
/// post quantum encrypt (buffer, sharedkey_dst, pub)
bool
pqe_encrypt(PQCipherBlock&, SharedSecret&, const PQPubKey&);
bool
check_identity_privkey(const SecretKey&);
bool
check_passwd_hash(std::string pwhash, std::string challenge);
}; // namespace crypto
/// return random 64bit unsigned interger
uint64_t
randint();
const byte_t*
seckey_to_pubkey(const SecretKey& secret);
const byte_t*
pq_keypair_to_pubkey(const PQKeyPair& keypair);
const byte_t*
pq_keypair_to_seckey(const PQKeyPair& keypair);
/// rng type that uses llarp::randint(), which is cryptographically secure
struct CSRNG
{
using result_type = uint64_t;
static constexpr uint64_t
min()
{
return std::numeric_limits<uint64_t>::min();
}
/*
TODO:
- make uint8_t pointers const where needed
*/
static constexpr uint64_t
max()
namespace crypto
{
return std::numeric_limits<uint64_t>::max();
}
uint64_t
operator()()
/// decrypt cipherText given the key generated from name
std::optional<AlignedBuffer<32>> maybe_decrypt_name(
std::string_view ciphertext, SymmNonce nonce, std::string_view name);
/// xchacha symmetric cipher
bool xchacha20(uint8_t*, size_t size, const SharedSecret&, const SymmNonce&);
bool xchacha20(uint8_t*, size_t size, const uint8_t*, const uint8_t*);
SymmNonce onion(
unsigned char* buf,
size_t size,
const SharedSecret& k,
const SymmNonce& nonce,
const SymmNonce& xor_factor);
/// path dh creator's side
bool dh_client(SharedSecret&, const PubKey&, const SecretKey&, const SymmNonce&);
/// path dh relay side
bool dh_server(SharedSecret&, const PubKey&, const SecretKey&, const SymmNonce&);
bool dh_server(
uint8_t* shared_secret,
const uint8_t* other_pk,
const uint8_t* local_pk,
const uint8_t* nonce);
/// blake2b 256 bit
bool shorthash(ShortHash&, uint8_t*, size_t size);
/// blake2s 256 bit hmac
bool hmac(uint8_t*, uint8_t*, size_t, const SharedSecret&);
/// ed25519 sign
bool sign(Signature&, const SecretKey&, uint8_t* buf, size_t size);
/// ed25519 sign, using pointers
bool sign(uint8_t* sig, uint8_t* sk, uint8_t* buf, size_t size);
bool sign(uint8_t* sig, const SecretKey& sk, ustring_view buf);
/// ed25519 sign (custom with derived keys)
bool sign(Signature&, const PrivateKey&, uint8_t* buf, size_t size);
/// ed25519 verify
bool verify(const PubKey&, ustring_view, ustring_view);
bool verify(const PubKey&, uint8_t*, size_t, const Signature&);
bool verify(ustring_view, ustring_view, ustring_view);
bool verify(uint8_t*, uint8_t*, size_t, uint8_t*);
/// derive sub keys for public keys. hash is really only intended for
/// testing ands key_n if given.
bool derive_subkey(
PubKey& derived,
const PubKey& root,
uint64_t key_n,
const AlignedBuffer<32>* hash = nullptr);
/// derive sub keys for private keys. hash is really only intended for
/// testing ands key_n if given.
bool derive_subkey_private(
PrivateKey& derived,
const SecretKey& root,
uint64_t key_n,
const AlignedBuffer<32>* hash = nullptr);
/// randomize buffer
void randomize(uint8_t* buf, size_t len);
/// randomizer memory
void randbytes(byte_t*, size_t);
/// generate signing keypair
void identity_keygen(SecretKey&);
/// generate encryption keypair
void encryption_keygen(SecretKey&);
/// generate post quantum encrytion key
void pqe_keygen(PQKeyPair&);
/// post quantum decrypt (buffer, sharedkey_dst, sec)
bool pqe_decrypt(const PQCipherBlock&, SharedSecret&, const byte_t*);
/// post quantum encrypt (buffer, sharedkey_dst, pub)
bool pqe_encrypt(PQCipherBlock&, SharedSecret&, const PQPubKey&);
bool check_identity_privkey(const SecretKey&);
bool check_passwd_hash(std::string pwhash, std::string challenge);
}; // namespace crypto
/// return random 64bit unsigned interger
uint64_t randint();
const byte_t* seckey_to_pubkey(const SecretKey& secret);
const byte_t* pq_keypair_to_pubkey(const PQKeyPair& keypair);
const byte_t* pq_keypair_to_seckey(const PQKeyPair& keypair);
/// rng type that uses llarp::randint(), which is cryptographically secure
struct CSRNG
{
return llarp::randint();
}
};
using result_type = uint64_t;
static constexpr uint64_t min()
{
return std::numeric_limits<uint64_t>::min();
}
static constexpr uint64_t max()
{
return std::numeric_limits<uint64_t>::max();
}
uint64_t operator()()
{
return llarp::randint();
}
};
extern CSRNG csrng;
extern CSRNG csrng;
} // namespace llarp

@ -11,156 +11,141 @@
namespace llarp
{
/// encrypted buffer base type
template <size_t bufsz = MAX_LINK_MSG_SIZE>
struct Encrypted
{
Encrypted(Encrypted&& other)
{
_sz = std::move(other._sz);
_buf = std::move(other._buf);
UpdateBuffer();
}
Encrypted(const Encrypted& other) : Encrypted(other.data(), other.size())
{
UpdateBuffer();
}
Encrypted()
{
Clear();
}
void
Clear()
{
_sz = 0;
UpdateBuffer();
}
Encrypted(const byte_t* buf, size_t sz)
{
if (sz <= bufsz)
{
_sz = sz;
if (buf)
memcpy(_buf.data(), buf, sz);
else
_buf.Zero();
}
else
_sz = 0;
UpdateBuffer();
}
Encrypted(size_t sz) : Encrypted(nullptr, sz)
{}
bool
BEncode(llarp_buffer_t* buf) const
{
return bencode_write_bytestring(buf, data(), _sz);
}
bool
operator==(const Encrypted& other) const
{
return _sz == other._sz && memcmp(data(), other.data(), _sz) == 0;
}
bool
operator!=(const Encrypted& other) const
{
return !(*this == other);
}
Encrypted&
operator=(const Encrypted& other)
{
return Encrypted::operator=(llarp_buffer_t(other));
}
Encrypted&
operator=(const llarp_buffer_t& buf)
{
if (buf.sz <= _buf.size())
{
_sz = buf.sz;
memcpy(_buf.data(), buf.base, _sz);
}
UpdateBuffer();
return *this;
}
void
Fill(byte_t fill)
{
std::fill(_buf.begin(), _buf.begin() + _sz, fill);
}
void
Randomize()
{
if (_sz)
randombytes(_buf.data(), _sz);
}
bool
BDecode(llarp_buffer_t* buf)
{
llarp_buffer_t strbuf;
if (!bencode_read_string(buf, &strbuf))
return false;
if (strbuf.sz > sizeof(_buf))
return false;
_sz = strbuf.sz;
if (_sz)
memcpy(_buf.data(), strbuf.base, _sz);
UpdateBuffer();
return true;
}
llarp_buffer_t*
Buffer()
{
return &m_Buffer;
}
size_t
size()
{
return _sz;
}
size_t
size() const
{
return _sz;
}
byte_t*
data()
{
return _buf.data();
}
const byte_t*
data() const
{
return _buf.data();
}
protected:
void
UpdateBuffer()
{
m_Buffer.base = _buf.data();
m_Buffer.cur = _buf.data();
m_Buffer.sz = _sz;
}
AlignedBuffer<bufsz> _buf;
size_t _sz;
llarp_buffer_t m_Buffer;
}; // namespace llarp
/// encrypted buffer base type
template <size_t bufsz = MAX_LINK_MSG_SIZE>
struct Encrypted
{
Encrypted(Encrypted&& other)
{
_sz = std::move(other._sz);
_buf = std::move(other._buf);
UpdateBuffer();
}
Encrypted(const Encrypted& other) : Encrypted(other.data(), other.size())
{
UpdateBuffer();
}
Encrypted()
{
Clear();
}
void Clear()
{
_sz = 0;
UpdateBuffer();
}
Encrypted(const byte_t* buf, size_t sz)
{
if (sz <= bufsz)
{
_sz = sz;
if (buf)
memcpy(_buf.data(), buf, sz);
else
_buf.Zero();
}
else
_sz = 0;
UpdateBuffer();
}
Encrypted(size_t sz) : Encrypted(nullptr, sz)
{}
bool BEncode(llarp_buffer_t* buf) const
{
return bencode_write_bytestring(buf, data(), _sz);
}
bool operator==(const Encrypted& other) const
{
return _sz == other._sz && memcmp(data(), other.data(), _sz) == 0;
}
bool operator!=(const Encrypted& other) const
{
return !(*this == other);
}
Encrypted& operator=(const Encrypted& other)
{
return Encrypted::operator=(llarp_buffer_t(other));
}
Encrypted& operator=(const llarp_buffer_t& buf)
{
if (buf.sz <= _buf.size())
{
_sz = buf.sz;
memcpy(_buf.data(), buf.base, _sz);
}
UpdateBuffer();
return *this;
}
void Fill(byte_t fill)
{
std::fill(_buf.begin(), _buf.begin() + _sz, fill);
}
void Randomize()
{
if (_sz)
randombytes(_buf.data(), _sz);
}
bool BDecode(llarp_buffer_t* buf)
{
llarp_buffer_t strbuf;
if (!bencode_read_string(buf, &strbuf))
return false;
if (strbuf.sz > sizeof(_buf))
return false;
_sz = strbuf.sz;
if (_sz)
memcpy(_buf.data(), strbuf.base, _sz);
UpdateBuffer();
return true;
}
llarp_buffer_t* Buffer()
{
return &m_Buffer;
}
size_t size()
{
return _sz;
}
size_t size() const
{
return _sz;
}
byte_t* data()
{
return _buf.data();
}
const byte_t* data() const
{
return _buf.data();
}
protected:
void UpdateBuffer()
{
m_Buffer.base = _buf.data();
m_Buffer.cur = _buf.data();
m_Buffer.sz = _sz;
}
AlignedBuffer<bufsz> _buf;
size_t _sz;
llarp_buffer_t m_Buffer;
}; // namespace llarp
} // namespace llarp

@ -10,114 +10,104 @@
namespace llarp
{
bool
PubKey::FromString(const std::string& str)
{
if (str.size() != 2 * size())
return false;
oxenc::from_hex(str.begin(), str.end(), begin());
return true;
}
PubKey
PubKey::from_string(const std::string& s)
{
PubKey p;
oxenc::from_hex(s.begin(), s.end(), p.begin());
return p;
}
std::string
PubKey::ToString() const
{
return oxenc::to_hex(begin(), end());
}
PubKey&
PubKey::operator=(const byte_t* ptr)
{
std::copy(ptr, ptr + SIZE, begin());
return *this;
}
bool
operator==(const PubKey& lhs, const PubKey& rhs)
{
return lhs.as_array() == rhs.as_array();
}
bool
SecretKey::LoadFromFile(const fs::path& fname)
{
size_t sz;
std::array<byte_t, 128> tmp;
try
bool PubKey::FromString(const std::string& str)
{
sz = util::file_to_buffer(fname, tmp.data(), tmp.size());
if (str.size() != 2 * size())
return false;
oxenc::from_hex(str.begin(), str.end(), begin());
return true;
}
catch (const std::exception&)
PubKey PubKey::from_string(const std::string& s)
{
PubKey p;
oxenc::from_hex(s.begin(), s.end(), p.begin());
return p;
}
std::string PubKey::ToString() const
{
return oxenc::to_hex(begin(), end());
}
PubKey& PubKey::operator=(const byte_t* ptr)
{
std::copy(ptr, ptr + SIZE, begin());
return *this;
}
bool operator==(const PubKey& lhs, const PubKey& rhs)
{
return lhs.as_array() == rhs.as_array();
}
bool SecretKey::LoadFromFile(const fs::path& fname)
{
return false;
size_t sz;
std::array<byte_t, 128> tmp;
try
{
sz = util::file_to_buffer(fname, tmp.data(), tmp.size());
}
catch (const std::exception&)
{
return false;
}
if (sz == size())
{
// is raw buffer
std::copy_n(tmp.begin(), sz, begin());
return true;
}
llarp_buffer_t buf(tmp);
return BDecode(&buf);
}
if (sz == size())
bool SecretKey::Recalculate()
{
// is raw buffer
std::copy_n(tmp.begin(), sz, begin());
return true;
PrivateKey key;
PubKey pubkey;
if (!toPrivate(key) || !key.toPublic(pubkey))
return false;
std::memcpy(data() + 32, pubkey.data(), 32);
return true;
}
llarp_buffer_t buf(tmp);
return BDecode(&buf);
}
bool
SecretKey::Recalculate()
{
PrivateKey key;
PubKey pubkey;
if (!toPrivate(key) || !key.toPublic(pubkey))
return false;
std::memcpy(data() + 32, pubkey.data(), 32);
return true;
}
bool
SecretKey::toPrivate(PrivateKey& key) const
{
// Ed25519 calculates a 512-bit hash from the seed; the first half (clamped)
// is the private key; the second half is the hash that gets used in
// signing.
unsigned char h[crypto_hash_sha512_BYTES];
if (crypto_hash_sha512(h, data(), 32) < 0)
return false;
h[0] &= 248;
h[31] &= 63;
h[31] |= 64;
std::memcpy(key.data(), h, 64);
return true;
}
bool
PrivateKey::toPublic(PubKey& pubkey) const
{
return crypto_scalarmult_ed25519_base_noclamp(pubkey.data(), data()) != -1;
}
bool
SecretKey::SaveToFile(const fs::path& fname) const
{
auto bte = bt_encode();
try
bool SecretKey::toPrivate(PrivateKey& key) const
{
util::buffer_to_file(fname, bte);
// Ed25519 calculates a 512-bit hash from the seed; the first half (clamped)
// is the private key; the second half is the hash that gets used in
// signing.
unsigned char h[crypto_hash_sha512_BYTES];
if (crypto_hash_sha512(h, data(), 32) < 0)
return false;
h[0] &= 248;
h[31] &= 63;
h[31] |= 64;
std::memcpy(key.data(), h, 64);
return true;
}
catch (const std::exception& e)
bool PrivateKey::toPublic(PubKey& pubkey) const
{
return false;
return crypto_scalarmult_ed25519_base_noclamp(pubkey.data(), data()) != -1;
}
return true;
}
bool SecretKey::SaveToFile(const fs::path& fname) const
{
auto bte = bt_encode();
try
{
util::buffer_to_file(fname, bte);
}
catch (const std::exception& e)
{
return false;
}
return true;
}
} // namespace llarp

@ -11,168 +11,154 @@
namespace llarp
{
using SharedSecret = AlignedBuffer<SHAREDKEYSIZE>;
using KeyExchangeNonce = AlignedBuffer<32>;
using SharedSecret = AlignedBuffer<SHAREDKEYSIZE>;
using KeyExchangeNonce = AlignedBuffer<32>;
struct RouterID;
struct RouterID;
struct PubKey : public AlignedBuffer<PUBKEYSIZE>
{
PubKey() = default;
explicit PubKey(const byte_t* ptr) : AlignedBuffer<SIZE>(ptr)
{}
explicit PubKey(const std::array<byte_t, SIZE>& data) : AlignedBuffer<SIZE>(data)
{}
explicit PubKey(const AlignedBuffer<SIZE>& other) : AlignedBuffer<SIZE>(other)
{}
std::string
ToString() const;
// FIXME: this is deceptively named: it should be "from_hex" since that's what it does.
bool
FromString(const std::string& str);
struct PubKey : public AlignedBuffer<PUBKEYSIZE>
{
PubKey() = default;
// FIXME: this is deceptively named: it should be "from_hex" since that's what it does.
static PubKey
from_string(const std::string& s);
explicit PubKey(const byte_t* ptr) : AlignedBuffer<SIZE>(ptr)
{}
PubKey&
operator=(const byte_t* ptr);
};
explicit PubKey(const std::array<byte_t, SIZE>& data) : AlignedBuffer<SIZE>(data)
{}
bool
operator==(const PubKey& lhs, const PubKey& rhs);
explicit PubKey(const AlignedBuffer<SIZE>& other) : AlignedBuffer<SIZE>(other)
{}
struct PrivateKey;
std::string ToString() const;
/// Stores a sodium "secret key" value, which is actually the seed
/// concatenated with the public key. Note that the seed is *not* the private
/// key value itself, but rather the seed from which it can be calculated.
struct SecretKey final : public AlignedBuffer<SECKEYSIZE>
{
SecretKey() = default;
// FIXME: this is deceptively named: it should be "from_hex" since that's what it does.
bool FromString(const std::string& str);
explicit SecretKey(const byte_t* ptr) : AlignedBuffer<SECKEYSIZE>(ptr)
{}
// FIXME: this is deceptively named: it should be "from_hex" since that's what it does.
static PubKey from_string(const std::string& s);
// The full data
explicit SecretKey(const AlignedBuffer<SECKEYSIZE>& seed) : AlignedBuffer<SECKEYSIZE>(seed)
{}
PubKey& operator=(const byte_t* ptr);
};
// Just the seed, we recalculate the pubkey
explicit SecretKey(const AlignedBuffer<32>& seed)
{
std::copy(seed.begin(), seed.end(), begin());
Recalculate();
}
bool operator==(const PubKey& lhs, const PubKey& rhs);
/// recalculate public component
bool
Recalculate();
struct PrivateKey;
std::string_view
ToString() const
/// Stores a sodium "secret key" value, which is actually the seed
/// concatenated with the public key. Note that the seed is *not* the private
/// key value itself, but rather the seed from which it can be calculated.
struct SecretKey final : public AlignedBuffer<SECKEYSIZE>
{
return "[secretkey]";
}
PubKey
toPublic() const
SecretKey() = default;
explicit SecretKey(const byte_t* ptr) : AlignedBuffer<SECKEYSIZE>(ptr)
{}
// The full data
explicit SecretKey(const AlignedBuffer<SECKEYSIZE>& seed) : AlignedBuffer<SECKEYSIZE>(seed)
{}
// Just the seed, we recalculate the pubkey
explicit SecretKey(const AlignedBuffer<32>& seed)
{
std::copy(seed.begin(), seed.end(), begin());
Recalculate();
}
/// recalculate public component
bool Recalculate();
std::string_view ToString() const
{
return "[secretkey]";
}
PubKey toPublic() const
{
return PubKey(data() + 32);
}
/// Computes the private key from the secret key (which is actually the
/// seed)
bool toPrivate(PrivateKey& key) const;
bool LoadFromFile(const fs::path& fname);
bool SaveToFile(const fs::path& fname) const;
};
/// PrivateKey is similar to SecretKey except that it only stores the private
/// key value and a hash, unlike SecretKey which stores the seed from which
/// the private key and hash value are generated. This is primarily intended
/// for use with derived keys, where we can derive the private key but not the
/// seed.
struct PrivateKey final : public AlignedBuffer<64>
{
return PubKey(data() + 32);
}
/// Computes the private key from the secret key (which is actually the
/// seed)
bool
toPrivate(PrivateKey& key) const;
bool
LoadFromFile(const fs::path& fname);
bool
SaveToFile(const fs::path& fname) const;
};
/// PrivateKey is similar to SecretKey except that it only stores the private
/// key value and a hash, unlike SecretKey which stores the seed from which
/// the private key and hash value are generated. This is primarily intended
/// for use with derived keys, where we can derive the private key but not the
/// seed.
struct PrivateKey final : public AlignedBuffer<64>
{
PrivateKey() = default;
explicit PrivateKey(const byte_t* ptr) : AlignedBuffer<64>(ptr)
{}
explicit PrivateKey(const AlignedBuffer<64>& key_and_hash) : AlignedBuffer<64>(key_and_hash)
{}
/// Returns a pointer to the beginning of the 32-byte hash which is used for
/// pseudorandomness when signing with this private key.
const byte_t*
signingHash() const
PrivateKey() = default;
explicit PrivateKey(const byte_t* ptr) : AlignedBuffer<64>(ptr)
{}
explicit PrivateKey(const AlignedBuffer<64>& key_and_hash) : AlignedBuffer<64>(key_and_hash)
{}
/// Returns a pointer to the beginning of the 32-byte hash which is used for
/// pseudorandomness when signing with this private key.
const byte_t* signingHash() const
{
return data() + 32;
}
/// Returns a pointer to the beginning of the 32-byte hash which is used for
/// pseudorandomness when signing with this private key.
byte_t* signingHash()
{
return data() + 32;
}
std::string_view ToString() const
{
return "[privatekey]";
}
/// Computes the public key
bool toPublic(PubKey& pubkey) const;
};
template <>
constexpr inline bool IsToStringFormattable<PubKey> = true;
template <>
constexpr inline bool IsToStringFormattable<SecretKey> = true;
template <>
constexpr inline bool IsToStringFormattable<PrivateKey> = true;
using ShortHash = AlignedBuffer<SHORTHASHSIZE>;
using LongHash = AlignedBuffer<HASHSIZE>;
struct Signature final : public AlignedBuffer<SIGSIZE>
{
return data() + 32;
}
//
};
/// Returns a pointer to the beginning of the 32-byte hash which is used for
/// pseudorandomness when signing with this private key.
byte_t*
signingHash()
{
return data() + 32;
}
using TunnelNonce = AlignedBuffer<TUNNONCESIZE>;
using SymmNonce = AlignedBuffer<NONCESIZE>;
using SymmKey = AlignedBuffer<32>; // not used
std::string_view
ToString() const
{
return "[privatekey]";
}
/// Computes the public key
bool
toPublic(PubKey& pubkey) const;
};
template <>
constexpr inline bool IsToStringFormattable<PubKey> = true;
template <>
constexpr inline bool IsToStringFormattable<SecretKey> = true;
template <>
constexpr inline bool IsToStringFormattable<PrivateKey> = true;
using ShortHash = AlignedBuffer<SHORTHASHSIZE>;
using LongHash = AlignedBuffer<HASHSIZE>;
struct Signature final : public AlignedBuffer<SIGSIZE>
{
//
};
using TunnelNonce = AlignedBuffer<TUNNONCESIZE>;
using SymmNonce = AlignedBuffer<NONCESIZE>;
using SymmKey = AlignedBuffer<32>; // not used
using PQCipherBlock = AlignedBuffer<PQ_CIPHERTEXTSIZE + 1>;
using PQPubKey = AlignedBuffer<PQ_PUBKEYSIZE>;
using PQKeyPair = AlignedBuffer<PQ_KEYPAIRSIZE>;
/// PKE(result, publickey, secretkey, nonce)
using path_dh_func = bool (*)(SharedSecret&, const PubKey&, const SecretKey&, const TunnelNonce&);
/// SH(result, body)
using shorthash_func = bool (*)(ShortHash&, const llarp_buffer_t&);
using PQCipherBlock = AlignedBuffer<PQ_CIPHERTEXTSIZE + 1>;
using PQPubKey = AlignedBuffer<PQ_PUBKEYSIZE>;
using PQKeyPair = AlignedBuffer<PQ_KEYPAIRSIZE>;
/// PKE(result, publickey, secretkey, nonce)
using path_dh_func =
bool (*)(SharedSecret&, const PubKey&, const SecretKey&, const TunnelNonce&);
/// SH(result, body)
using shorthash_func = bool (*)(ShortHash&, const llarp_buffer_t&);
} // namespace llarp
namespace std
{
template <>
struct hash<llarp::PubKey> : hash<llarp::AlignedBuffer<llarp::PubKey::SIZE>>
{};
template <>
struct hash<llarp::PubKey> : hash<llarp::AlignedBuffer<llarp::PubKey::SIZE>>
{};
}; // namespace std

@ -11,219 +11,206 @@
namespace llarp::dht
{
template <typename Val_t>
struct Bucket
{
using BucketStorage_t = std::map<Key_t, Val_t, XorMetric>;
using Random_t = std::function<uint64_t()>;
template <typename Val_t>
struct Bucket
{
using BucketStorage_t = std::map<Key_t, Val_t, XorMetric>;
using Random_t = std::function<uint64_t()>;
Bucket(const Key_t& us, Random_t r) : nodes(XorMetric(us)), random(std::move(r))
{}
Bucket(const Key_t& us, Random_t r) : nodes(XorMetric(us)), random(std::move(r))
{}
util::StatusObject
ExtractStatus() const
{
util::StatusObject obj{};
for (const auto& item : nodes)
{
obj[item.first.ToString()] = item.second.ExtractStatus();
}
return obj;
}
size_t
size() const
{
return nodes.size();
}
util::StatusObject ExtractStatus() const
{
util::StatusObject obj{};
for (const auto& item : nodes)
{
obj[item.first.ToString()] = item.second.ExtractStatus();
}
return obj;
}
struct SetIntersector
{
bool
operator()(const typename BucketStorage_t::value_type& lhs, const Key_t& rhs)
{
return lhs.first < rhs;
}
bool
operator()(const Key_t& lhs, const typename BucketStorage_t::value_type& rhs)
{
return lhs < rhs.first;
}
};
size_t size() const
{
return nodes.size();
}
bool
GetRandomNodeExcluding(Key_t& result, const std::set<Key_t>& exclude) const
{
std::vector<typename BucketStorage_t::value_type> candidates;
std::set_difference(
nodes.begin(),
nodes.end(),
exclude.begin(),
exclude.end(),
std::back_inserter(candidates),
SetIntersector());
if (candidates.empty())
{
return false;
}
result = candidates[random() % candidates.size()].first;
return true;
}
bool
FindClosest(const Key_t& target, Key_t& result) const
{
Key_t mindist;
mindist.Fill(0xff);
for (const auto& item : nodes)
{
auto curDist = item.first ^ target;
if (curDist < mindist)
struct SetIntersector
{
bool operator()(const typename BucketStorage_t::value_type& lhs, const Key_t& rhs)
{
return lhs.first < rhs;
}
bool operator()(const Key_t& lhs, const typename BucketStorage_t::value_type& rhs)
{
return lhs < rhs.first;
}
};
bool GetRandomNodeExcluding(Key_t& result, const std::set<Key_t>& exclude) const
{
mindist = curDist;
result = item.first;
std::vector<typename BucketStorage_t::value_type> candidates;
std::set_difference(
nodes.begin(),
nodes.end(),
exclude.begin(),
exclude.end(),
std::back_inserter(candidates),
SetIntersector());
if (candidates.empty())
{
return false;
}
result = candidates[random() % candidates.size()].first;
return true;
}
}
return nodes.size() > 0;
}
bool
GetManyRandom(std::set<Key_t>& result, size_t N) const
{
if (nodes.size() < N || nodes.empty())
{
llarp::LogWarn("Not enough dht nodes, have ", nodes.size(), " want ", N);
return false;
}
if (nodes.size() == N)
{
std::transform(
nodes.begin(), nodes.end(), std::inserter(result, result.end()), [](const auto& a) {
return a.first;
});
return true;
}
size_t expecting = N;
size_t sz = nodes.size();
while (N)
{
auto itr = nodes.begin();
std::advance(itr, random() % sz);
if (result.insert(itr->first).second)
bool FindClosest(const Key_t& target, Key_t& result) const
{
--N;
Key_t mindist;
mindist.Fill(0xff);
for (const auto& item : nodes)
{
auto curDist = item.first ^ target;
if (curDist < mindist)
{
mindist = curDist;
result = item.first;
}
}
return nodes.size() > 0;
}
}
return result.size() == expecting;
}
bool
FindCloseExcluding(const Key_t& target, Key_t& result, const std::set<Key_t>& exclude) const
{
Key_t maxdist;
maxdist.Fill(0xff);
Key_t mindist;
mindist.Fill(0xff);
for (const auto& item : nodes)
{
if (exclude.count(item.first))
bool GetManyRandom(std::set<Key_t>& result, size_t N) const
{
continue;
if (nodes.size() < N || nodes.empty())
{
llarp::LogWarn("Not enough dht nodes, have ", nodes.size(), " want ", N);
return false;
}
if (nodes.size() == N)
{
std::transform(
nodes.begin(),
nodes.end(),
std::inserter(result, result.end()),
[](const auto& a) { return a.first; });
return true;
}
size_t expecting = N;
size_t sz = nodes.size();
while (N)
{
auto itr = nodes.begin();
std::advance(itr, random() % sz);
if (result.insert(itr->first).second)
{
--N;
}
}
return result.size() == expecting;
}
auto curDist = item.first ^ target;
if (curDist < mindist)
bool FindCloseExcluding(
const Key_t& target, Key_t& result, const std::set<Key_t>& exclude) const
{
mindist = curDist;
result = item.first;
Key_t maxdist;
maxdist.Fill(0xff);
Key_t mindist;
mindist.Fill(0xff);
for (const auto& item : nodes)
{
if (exclude.count(item.first))
{
continue;
}
auto curDist = item.first ^ target;
if (curDist < mindist)
{
mindist = curDist;
result = item.first;
}
}
return mindist < maxdist;
}
}
return mindist < maxdist;
}
bool
GetManyNearExcluding(
const Key_t& target,
std::set<Key_t>& result,
size_t N,
const std::set<Key_t>& exclude) const
{
std::set<Key_t> s(exclude.begin(), exclude.end());
Key_t peer;
while (N--)
{
if (!FindCloseExcluding(target, peer, s))
bool GetManyNearExcluding(
const Key_t& target,
std::set<Key_t>& result,
size_t N,
const std::set<Key_t>& exclude) const
{
return false;
std::set<Key_t> s(exclude.begin(), exclude.end());
Key_t peer;
while (N--)
{
if (!FindCloseExcluding(target, peer, s))
{
return false;
}
s.insert(peer);
result.insert(peer);
}
return true;
}
s.insert(peer);
result.insert(peer);
}
return true;
}
void
PutNode(const Val_t& val)
{
auto itr = nodes.find(val.ID);
if (itr == nodes.end() || itr->second < val)
{
nodes[val.ID] = val;
}
}
void
DelNode(const Key_t& key)
{
auto itr = nodes.find(key);
if (itr != nodes.end())
{
nodes.erase(itr);
}
}
bool
HasNode(const Key_t& key) const
{
return nodes.find(key) != nodes.end();
}
// remove all nodes who's key matches a predicate
template <typename Predicate>
void
RemoveIf(Predicate pred)
{
auto itr = nodes.begin();
while (itr != nodes.end())
{
if (pred(itr->first))
itr = nodes.erase(itr);
else
++itr;
}
}
template <typename Visit_t>
void
ForEachNode(Visit_t visit)
{
for (const auto& item : nodes)
{
visit(item.second);
}
}
void
Clear()
{
nodes.clear();
}
void PutNode(const Val_t& val)
{
auto itr = nodes.find(val.ID);
if (itr == nodes.end() || itr->second < val)
{
nodes[val.ID] = val;
}
}
void DelNode(const Key_t& key)
{
auto itr = nodes.find(key);
if (itr != nodes.end())
{
nodes.erase(itr);
}
}
BucketStorage_t nodes;
Random_t random;
};
bool HasNode(const Key_t& key) const
{
return nodes.find(key) != nodes.end();
}
// remove all nodes who's key matches a predicate
template <typename Predicate>
void RemoveIf(Predicate pred)
{
auto itr = nodes.begin();
while (itr != nodes.end())
{
if (pred(itr->first))
itr = nodes.erase(itr);
else
++itr;
}
}
template <typename Visit_t>
void ForEachNode(Visit_t visit)
{
for (const auto& item : nodes)
{
visit(item.second);
}
}
void Clear()
{
nodes.clear();
}
BucketStorage_t nodes;
Random_t random;
};
} // namespace llarp::dht

@ -6,23 +6,21 @@
namespace llarp::dht
{
struct XorMetric
{
const Key_t us;
struct XorMetric
{
const Key_t us;
XorMetric(const Key_t& ourKey) : us(ourKey)
{}
XorMetric(const Key_t& ourKey) : us(ourKey)
{}
bool
operator()(const Key_t& left, const Key_t& right) const
{
return (us ^ left) < (us ^ right);
}
bool operator()(const Key_t& left, const Key_t& right) const
{
return (us ^ left) < (us ^ right);
}
bool
operator()(const RouterContact& left, const RouterContact& right) const
{
return (left.router_id() ^ us) < (right.router_id() ^ us);
}
};
bool operator()(const RouterContact& left, const RouterContact& right) const
{
return (left.router_id() ^ us) < (right.router_id() ^ us);
}
};
} // namespace llarp::dht

@ -8,67 +8,59 @@
namespace llarp::dht
{
struct Key_t : public AlignedBuffer<32>
{
explicit Key_t(const byte_t* buf) : AlignedBuffer<SIZE>(buf)
{}
struct Key_t : public AlignedBuffer<32>
{
explicit Key_t(const byte_t* buf) : AlignedBuffer<SIZE>(buf)
{}
explicit Key_t(const std::array<byte_t, SIZE>& data) : AlignedBuffer<SIZE>(data)
{}
explicit Key_t(const std::array<byte_t, SIZE>& data) : AlignedBuffer<SIZE>(data)
{}
explicit Key_t(const AlignedBuffer<SIZE>& data) : AlignedBuffer<SIZE>(data)
{}
explicit Key_t(const AlignedBuffer<SIZE>& data) : AlignedBuffer<SIZE>(data)
{}
Key_t() : AlignedBuffer<SIZE>()
{}
Key_t() : AlignedBuffer<SIZE>()
{}
/// get snode address string
std::string
SNode() const
{
const RouterID rid{as_array()};
return rid.ToString();
}
/// get snode address string
std::string SNode() const
{
const RouterID rid{as_array()};
return rid.ToString();
}
util::StatusObject
ExtractStatus() const;
util::StatusObject ExtractStatus() const;
std::string
ToString() const
{
return SNode();
}
std::string ToString() const
{
return SNode();
}
Key_t
operator^(const Key_t& other) const
{
Key_t dist;
std::transform(begin(), end(), other.begin(), dist.begin(), std::bit_xor<byte_t>());
return dist;
}
Key_t operator^(const Key_t& other) const
{
Key_t dist;
std::transform(begin(), end(), other.begin(), dist.begin(), std::bit_xor<byte_t>());
return dist;
}
bool
operator==(const Key_t& other) const
{
return as_array() == other.as_array();
}
bool operator==(const Key_t& other) const
{
return as_array() == other.as_array();
}
bool
operator!=(const Key_t& other) const
{
return as_array() != other.as_array();
}
bool operator!=(const Key_t& other) const
{
return as_array() != other.as_array();
}
bool
operator<(const Key_t& other) const
{
return as_array() < other.as_array();
}
bool operator<(const Key_t& other) const
{
return as_array() < other.as_array();
}
bool
operator>(const Key_t& other) const
{
return as_array() > other.as_array();
}
};
bool operator>(const Key_t& other) const
{
return as_array() > other.as_array();
}
};
} // namespace llarp::dht

@ -9,58 +9,54 @@
namespace llarp::dht
{
struct RCNode
{
RouterContact rc;
Key_t ID;
RCNode()
{
ID.Zero();
}
RCNode(const RouterContact& other) : rc(other), ID(other.router_id())
{}
util::StatusObject
ExtractStatus() const
{
return rc.extract_status();
}
bool
operator<(const RCNode& other) const
struct RCNode
{
return rc.timestamp() < other.rc.timestamp();
}
};
RouterContact rc;
Key_t ID;
struct ISNode
{
service::EncryptedIntroSet introset;
RCNode()
{
ID.Zero();
}
Key_t ID;
RCNode(const RouterContact& other) : rc(other), ID(other.router_id())
{}
ISNode()
{
ID.Zero();
}
util::StatusObject ExtractStatus() const
{
return rc.extract_status();
}
ISNode(service::EncryptedIntroSet other) : introset(std::move(other))
{
ID = Key_t(introset.derivedSigningKey.as_array());
}
util::StatusObject
ExtractStatus() const
{
return introset.ExtractStatus();
}
bool operator<(const RCNode& other) const
{
return rc.timestamp() < other.rc.timestamp();
}
};
bool
operator<(const ISNode& other) const
struct ISNode
{
return introset.signedAt < other.introset.signedAt;
}
};
service::EncryptedIntroSet introset;
Key_t ID;
ISNode()
{
ID.Zero();
}
ISNode(service::EncryptedIntroSet other) : introset(std::move(other))
{
ID = Key_t(introset.derivedSigningKey.as_array());
}
util::StatusObject ExtractStatus() const
{
return introset.ExtractStatus();
}
bool operator<(const ISNode& other) const
{
return introset.signedAt < other.introset.signedAt;
}
};
} // namespace llarp::dht

@ -6,24 +6,24 @@
namespace llarp::dns
{
constexpr uint16_t qTypeSRV = 33;
constexpr uint16_t qTypeAAAA = 28;
constexpr uint16_t qTypeTXT = 16;
constexpr uint16_t qTypeMX = 15;
constexpr uint16_t qTypePTR = 12;
constexpr uint16_t qTypeCNAME = 5;
constexpr uint16_t qTypeNS = 2;
constexpr uint16_t qTypeA = 1;
constexpr uint16_t qTypeSRV = 33;
constexpr uint16_t qTypeAAAA = 28;
constexpr uint16_t qTypeTXT = 16;
constexpr uint16_t qTypeMX = 15;
constexpr uint16_t qTypePTR = 12;
constexpr uint16_t qTypeCNAME = 5;
constexpr uint16_t qTypeNS = 2;
constexpr uint16_t qTypeA = 1;
constexpr uint16_t qClassIN = 1;
constexpr uint16_t qClassIN = 1;
constexpr uint16_t flags_QR = (1 << 15);
constexpr uint16_t flags_AA = (1 << 10);
constexpr uint16_t flags_TC = (1 << 9);
constexpr uint16_t flags_RD = (1 << 8);
constexpr uint16_t flags_RA = (1 << 7);
constexpr uint16_t flags_RCODENameError = (3);
constexpr uint16_t flags_RCODEServFail = (2);
constexpr uint16_t flags_RCODENoError = (0);
constexpr uint16_t flags_QR = (1 << 15);
constexpr uint16_t flags_AA = (1 << 10);
constexpr uint16_t flags_TC = (1 << 9);
constexpr uint16_t flags_RD = (1 << 8);
constexpr uint16_t flags_RA = (1 << 7);
constexpr uint16_t flags_RCODENameError = (3);
constexpr uint16_t flags_RCODEServFail = (2);
constexpr uint16_t flags_RCODENoError = (0);
} // namespace llarp::dns

@ -12,421 +12,402 @@
namespace llarp::dns
{
static auto logcat = log::Cat("dns");
bool
MessageHeader::Encode(llarp_buffer_t* buf) const
{
if (!buf->put_uint16(id))
return false;
if (!buf->put_uint16(fields))
return false;
if (!buf->put_uint16(qd_count))
return false;
if (!buf->put_uint16(an_count))
return false;
if (!buf->put_uint16(ns_count))
return false;
return buf->put_uint16(ar_count);
}
bool
MessageHeader::Decode(llarp_buffer_t* buf)
{
if (!buf->read_uint16(id))
return false;
if (!buf->read_uint16(fields))
return false;
if (!buf->read_uint16(qd_count))
return false;
if (!buf->read_uint16(an_count))
return false;
if (!buf->read_uint16(ns_count))
return false;
if (!buf->read_uint16(ar_count))
return false;
return true;
}
util::StatusObject
MessageHeader::ToJSON() const
{
return util::StatusObject{};
}
Message::Message(Message&& other)
: hdr_id(std::move(other.hdr_id))
, hdr_fields(std::move(other.hdr_fields))
, questions(std::move(other.questions))
, answers(std::move(other.answers))
, authorities(std::move(other.authorities))
, additional(std::move(other.additional))
{}
Message::Message(const Message& other)
: hdr_id(other.hdr_id)
, hdr_fields(other.hdr_fields)
, questions(other.questions)
, answers(other.answers)
, authorities(other.authorities)
, additional(other.additional)
{}
Message::Message(const MessageHeader& hdr) : hdr_id(hdr.id), hdr_fields(hdr.fields)
{
questions.resize(size_t(hdr.qd_count));
answers.resize(size_t(hdr.an_count));
authorities.resize(size_t(hdr.ns_count));
additional.resize(size_t(hdr.ar_count));
}
Message::Message(const Question& question) : hdr_id{0}, hdr_fields{}
{
questions.emplace_back(question);
}
bool
Message::Encode(llarp_buffer_t* buf) const
{
MessageHeader hdr;
hdr.id = hdr_id;
hdr.fields = hdr_fields;
hdr.qd_count = questions.size();
hdr.an_count = answers.size();
hdr.ns_count = 0;
hdr.ar_count = 0;
if (!hdr.Encode(buf))
return false;
for (const auto& question : questions)
if (!question.Encode(buf))
return false;
for (const auto& answer : answers)
if (!answer.Encode(buf))
return false;
return true;
}
bool
Message::Decode(llarp_buffer_t* buf)
{
for (auto& qd : questions)
static auto logcat = log::Cat("dns");
bool MessageHeader::Encode(llarp_buffer_t* buf) const
{
if (!qd.Decode(buf))
{
log::error(logcat, "failed to decode question");
return false;
}
log::debug(logcat, "question: {}", qd);
if (!buf->put_uint16(id))
return false;
if (!buf->put_uint16(fields))
return false;
if (!buf->put_uint16(qd_count))
return false;
if (!buf->put_uint16(an_count))
return false;
if (!buf->put_uint16(ns_count))
return false;
return buf->put_uint16(ar_count);
}
for (auto& an : answers)
bool MessageHeader::Decode(llarp_buffer_t* buf)
{
if (not an.Decode(buf))
{
log::debug(logcat, "failed to decode answer");
return false;
}
if (!buf->read_uint16(id))
return false;
if (!buf->read_uint16(fields))
return false;
if (!buf->read_uint16(qd_count))
return false;
if (!buf->read_uint16(an_count))
return false;
if (!buf->read_uint16(ns_count))
return false;
if (!buf->read_uint16(ar_count))
return false;
return true;
}
return true;
}
util::StatusObject
Message::ToJSON() const
{
std::vector<util::StatusObject> ques;
std::vector<util::StatusObject> ans;
for (const auto& q : questions)
util::StatusObject MessageHeader::ToJSON() const
{
ques.push_back(q.ToJSON());
return util::StatusObject{};
}
for (const auto& a : answers)
Message::Message(Message&& other)
: hdr_id(std::move(other.hdr_id)),
hdr_fields(std::move(other.hdr_fields)),
questions(std::move(other.questions)),
answers(std::move(other.answers)),
authorities(std::move(other.authorities)),
additional(std::move(other.additional))
{}
Message::Message(const Message& other)
: hdr_id(other.hdr_id),
hdr_fields(other.hdr_fields),
questions(other.questions),
answers(other.answers),
authorities(other.authorities),
additional(other.additional)
{}
Message::Message(const MessageHeader& hdr) : hdr_id(hdr.id), hdr_fields(hdr.fields)
{
ans.push_back(a.ToJSON());
questions.resize(size_t(hdr.qd_count));
answers.resize(size_t(hdr.an_count));
authorities.resize(size_t(hdr.ns_count));
additional.resize(size_t(hdr.ar_count));
}
return util::StatusObject{{"questions", ques}, {"answers", ans}};
}
OwnedBuffer
Message::ToBuffer() const
{
std::array<byte_t, 1500> tmp;
llarp_buffer_t buf{tmp};
if (not Encode(&buf))
throw std::runtime_error("cannot encode dns message");
return OwnedBuffer::copy_used(buf);
}
void
Message::AddServFail(RR_TTL_t)
{
if (questions.size())
Message::Message(const Question& question) : hdr_id{0}, hdr_fields{}
{
hdr_fields |= flags_RCODEServFail;
// authorative response with recursion available
hdr_fields |= flags_QR | flags_AA | flags_RA;
// don't allow recursion on this request
hdr_fields &= ~flags_RD;
questions.emplace_back(question);
}
}
static constexpr uint16_t
reply_flags(uint16_t setbits)
{
return setbits | flags_QR | flags_AA | flags_RA;
}
void
Message::AddINReply(llarp::huint128_t ip, bool isV6, RR_TTL_t ttl)
{
if (questions.size())
bool Message::Encode(llarp_buffer_t* buf) const
{
hdr_fields = reply_flags(hdr_fields);
ResourceRecord rec;
rec.rr_name = questions[0].qname;
rec.rr_class = qClassIN;
rec.ttl = ttl;
if (isV6)
{
rec.rr_type = qTypeAAAA;
ip.ToV6(rec.rData);
}
else
{
const auto addr = net::TruncateV6(ip);
rec.rr_type = qTypeA;
rec.rData.resize(4);
oxenc::write_host_as_big(addr.h, rec.rData.data());
}
answers.emplace_back(std::move(rec));
MessageHeader hdr;
hdr.id = hdr_id;
hdr.fields = hdr_fields;
hdr.qd_count = questions.size();
hdr.an_count = answers.size();
hdr.ns_count = 0;
hdr.ar_count = 0;
if (!hdr.Encode(buf))
return false;
for (const auto& question : questions)
if (!question.Encode(buf))
return false;
for (const auto& answer : answers)
if (!answer.Encode(buf))
return false;
return true;
}
}
void
Message::AddAReply(std::string name, RR_TTL_t ttl)
{
if (questions.size())
bool Message::Decode(llarp_buffer_t* buf)
{
hdr_fields = reply_flags(hdr_fields);
const auto& question = questions[0];
answers.emplace_back();
auto& rec = answers.back();
rec.rr_name = question.qname;
rec.rr_type = question.qtype;
rec.rr_class = qClassIN;
rec.ttl = ttl;
std::array<byte_t, 512> tmp = {{0}};
llarp_buffer_t buf(tmp);
if (EncodeNameTo(&buf, name))
{
buf.sz = buf.cur - buf.base;
rec.rData.resize(buf.sz);
memcpy(rec.rData.data(), buf.base, buf.sz);
}
for (auto& qd : questions)
{
if (!qd.Decode(buf))
{
log::error(logcat, "failed to decode question");
return false;
}
log::debug(logcat, "question: {}", qd);
}
for (auto& an : answers)
{
if (not an.Decode(buf))
{
log::debug(logcat, "failed to decode answer");
return false;
}
}
return true;
}
}
void
Message::AddNSReply(std::string name, RR_TTL_t ttl)
{
if (not questions.empty())
util::StatusObject Message::ToJSON() const
{
hdr_fields = reply_flags(hdr_fields);
const auto& question = questions[0];
answers.emplace_back();
auto& rec = answers.back();
rec.rr_name = question.qname;
rec.rr_type = qTypeNS;
rec.rr_class = qClassIN;
rec.ttl = ttl;
std::array<byte_t, 512> tmp = {{0}};
llarp_buffer_t buf(tmp);
if (EncodeNameTo(&buf, name))
{
buf.sz = buf.cur - buf.base;
rec.rData.resize(buf.sz);
memcpy(rec.rData.data(), buf.base, buf.sz);
}
std::vector<util::StatusObject> ques;
std::vector<util::StatusObject> ans;
for (const auto& q : questions)
{
ques.push_back(q.ToJSON());
}
for (const auto& a : answers)
{
ans.push_back(a.ToJSON());
}
return util::StatusObject{{"questions", ques}, {"answers", ans}};
}
}
void
Message::AddCNAMEReply(std::string name, RR_TTL_t ttl)
{
if (questions.size())
OwnedBuffer Message::ToBuffer() const
{
hdr_fields = reply_flags(hdr_fields);
const auto& question = questions[0];
answers.emplace_back();
auto& rec = answers.back();
rec.rr_name = question.qname;
rec.rr_type = qTypeCNAME;
rec.rr_class = qClassIN;
rec.ttl = ttl;
std::array<byte_t, 512> tmp = {{0}};
llarp_buffer_t buf(tmp);
if (EncodeNameTo(&buf, name))
{
buf.sz = buf.cur - buf.base;
rec.rData.resize(buf.sz);
memcpy(rec.rData.data(), buf.base, buf.sz);
}
std::array<byte_t, 1500> tmp;
llarp_buffer_t buf{tmp};
if (not Encode(&buf))
throw std::runtime_error("cannot encode dns message");
return OwnedBuffer::copy_used(buf);
}
}
void
Message::AddMXReply(std::string name, uint16_t priority, RR_TTL_t ttl)
{
if (questions.size())
void Message::AddServFail(RR_TTL_t)
{
hdr_fields = reply_flags(hdr_fields);
const auto& question = questions[0];
answers.emplace_back();
auto& rec = answers.back();
rec.rr_name = question.qname;
rec.rr_type = qTypeMX;
rec.rr_class = qClassIN;
rec.ttl = ttl;
std::array<byte_t, 512> tmp = {{0}};
llarp_buffer_t buf(tmp);
buf.put_uint16(priority);
if (EncodeNameTo(&buf, name))
{
buf.sz = buf.cur - buf.base;
rec.rData.resize(buf.sz);
memcpy(rec.rData.data(), buf.base, buf.sz);
}
if (questions.size())
{
hdr_fields |= flags_RCODEServFail;
// authorative response with recursion available
hdr_fields |= flags_QR | flags_AA | flags_RA;
// don't allow recursion on this request
hdr_fields &= ~flags_RD;
}
}
static constexpr uint16_t reply_flags(uint16_t setbits)
{
return setbits | flags_QR | flags_AA | flags_RA;
}
void Message::AddINReply(llarp::huint128_t ip, bool isV6, RR_TTL_t ttl)
{
if (questions.size())
{
hdr_fields = reply_flags(hdr_fields);
ResourceRecord rec;
rec.rr_name = questions[0].qname;
rec.rr_class = qClassIN;
rec.ttl = ttl;
if (isV6)
{
rec.rr_type = qTypeAAAA;
ip.ToV6(rec.rData);
}
else
{
const auto addr = net::TruncateV6(ip);
rec.rr_type = qTypeA;
rec.rData.resize(4);
oxenc::write_host_as_big(addr.h, rec.rData.data());
}
answers.emplace_back(std::move(rec));
}
}
}
void
Message::AddSRVReply(std::vector<SRVData> records, RR_TTL_t ttl)
{
hdr_fields = reply_flags(hdr_fields);
void Message::AddAReply(std::string name, RR_TTL_t ttl)
{
if (questions.size())
{
hdr_fields = reply_flags(hdr_fields);
const auto& question = questions[0];
answers.emplace_back();
auto& rec = answers.back();
rec.rr_name = question.qname;
rec.rr_type = question.qtype;
rec.rr_class = qClassIN;
rec.ttl = ttl;
std::array<byte_t, 512> tmp = {{0}};
llarp_buffer_t buf(tmp);
if (EncodeNameTo(&buf, name))
{
buf.sz = buf.cur - buf.base;
rec.rData.resize(buf.sz);
memcpy(rec.rData.data(), buf.base, buf.sz);
}
}
}
void Message::AddNSReply(std::string name, RR_TTL_t ttl)
{
if (not questions.empty())
{
hdr_fields = reply_flags(hdr_fields);
const auto& question = questions[0];
answers.emplace_back();
auto& rec = answers.back();
rec.rr_name = question.qname;
rec.rr_type = qTypeNS;
rec.rr_class = qClassIN;
rec.ttl = ttl;
std::array<byte_t, 512> tmp = {{0}};
llarp_buffer_t buf(tmp);
if (EncodeNameTo(&buf, name))
{
buf.sz = buf.cur - buf.base;
rec.rData.resize(buf.sz);
memcpy(rec.rData.data(), buf.base, buf.sz);
}
}
}
void Message::AddCNAMEReply(std::string name, RR_TTL_t ttl)
{
if (questions.size())
{
hdr_fields = reply_flags(hdr_fields);
const auto& question = questions[0];
answers.emplace_back();
auto& rec = answers.back();
rec.rr_name = question.qname;
rec.rr_type = qTypeCNAME;
rec.rr_class = qClassIN;
rec.ttl = ttl;
std::array<byte_t, 512> tmp = {{0}};
llarp_buffer_t buf(tmp);
if (EncodeNameTo(&buf, name))
{
buf.sz = buf.cur - buf.base;
rec.rData.resize(buf.sz);
memcpy(rec.rData.data(), buf.base, buf.sz);
}
}
}
void Message::AddMXReply(std::string name, uint16_t priority, RR_TTL_t ttl)
{
if (questions.size())
{
hdr_fields = reply_flags(hdr_fields);
const auto& question = questions[0];
answers.emplace_back();
auto& rec = answers.back();
rec.rr_name = question.qname;
rec.rr_type = qTypeMX;
rec.rr_class = qClassIN;
rec.ttl = ttl;
std::array<byte_t, 512> tmp = {{0}};
llarp_buffer_t buf(tmp);
buf.put_uint16(priority);
if (EncodeNameTo(&buf, name))
{
buf.sz = buf.cur - buf.base;
rec.rData.resize(buf.sz);
memcpy(rec.rData.data(), buf.base, buf.sz);
}
}
}
const auto& question = questions[0];
void Message::AddSRVReply(std::vector<SRVData> records, RR_TTL_t ttl)
{
hdr_fields = reply_flags(hdr_fields);
const auto& question = questions[0];
for (const auto& srv : records)
{
if (not srv.IsValid())
{
AddNXReply();
return;
}
answers.emplace_back();
auto& rec = answers.back();
rec.rr_name = question.qname;
rec.rr_type = qTypeSRV;
rec.rr_class = qClassIN;
rec.ttl = ttl;
std::array<byte_t, 512> tmp = {{0}};
llarp_buffer_t buf(tmp);
buf.put_uint16(srv.priority);
buf.put_uint16(srv.weight);
buf.put_uint16(srv.port);
std::string target;
if (srv.target == "")
{
// get location of second dot (after service.proto) in qname
size_t pos = question.qname.find(".");
pos = question.qname.find(".", pos + 1);
target = question.qname.substr(pos + 1);
}
else
{
target = srv.target;
}
if (not EncodeNameTo(&buf, target))
{
AddNXReply();
return;
}
buf.sz = buf.cur - buf.base;
rec.rData.resize(buf.sz);
memcpy(rec.rData.data(), buf.base, buf.sz);
}
}
void Message::AddTXTReply(std::string str, RR_TTL_t ttl)
{
auto& rec = answers.emplace_back();
rec.rr_name = questions[0].qname;
rec.rr_class = qClassIN;
rec.rr_type = qTypeTXT;
rec.ttl = ttl;
std::array<byte_t, 1024> tmp{};
llarp_buffer_t buf(tmp);
while (not str.empty())
{
const auto left = std::min(str.size(), size_t{256});
const auto sub = str.substr(0, left);
uint8_t byte = left;
*buf.cur = byte;
buf.cur++;
if (not buf.write(sub.begin(), sub.end()))
throw std::length_error("text record too big");
str = str.substr(left);
}
buf.sz = buf.cur - buf.base;
rec.rData.resize(buf.sz);
std::copy_n(buf.base, buf.sz, rec.rData.data());
}
for (const auto& srv : records)
void Message::AddNXReply(RR_TTL_t)
{
if (not srv.IsValid())
{
AddNXReply();
return;
}
answers.emplace_back();
auto& rec = answers.back();
rec.rr_name = question.qname;
rec.rr_type = qTypeSRV;
rec.rr_class = qClassIN;
rec.ttl = ttl;
std::array<byte_t, 512> tmp = {{0}};
llarp_buffer_t buf(tmp);
buf.put_uint16(srv.priority);
buf.put_uint16(srv.weight);
buf.put_uint16(srv.port);
std::string target;
if (srv.target == "")
{
// get location of second dot (after service.proto) in qname
size_t pos = question.qname.find(".");
pos = question.qname.find(".", pos + 1);
target = question.qname.substr(pos + 1);
}
else
{
target = srv.target;
}
if (not EncodeNameTo(&buf, target))
{
AddNXReply();
return;
}
buf.sz = buf.cur - buf.base;
rec.rData.resize(buf.sz);
memcpy(rec.rData.data(), buf.base, buf.sz);
if (questions.size())
{
answers.clear();
authorities.clear();
additional.clear();
// authorative response with recursion available
hdr_fields = reply_flags(hdr_fields);
// don't allow recursion on this request
hdr_fields &= ~flags_RD;
hdr_fields |= flags_RCODENameError;
}
}
}
void
Message::AddTXTReply(std::string str, RR_TTL_t ttl)
{
auto& rec = answers.emplace_back();
rec.rr_name = questions[0].qname;
rec.rr_class = qClassIN;
rec.rr_type = qTypeTXT;
rec.ttl = ttl;
std::array<byte_t, 1024> tmp{};
llarp_buffer_t buf(tmp);
while (not str.empty())
std::string Message::ToString() const
{
const auto left = std::min(str.size(), size_t{256});
const auto sub = str.substr(0, left);
uint8_t byte = left;
*buf.cur = byte;
buf.cur++;
if (not buf.write(sub.begin(), sub.end()))
throw std::length_error("text record too big");
str = str.substr(left);
return fmt::format(
"[DNSMessage id={:x} fields={:x} questions={{{}}} answers={{{}}} authorities={{{}}} "
"additional={{{}}}]",
hdr_id,
hdr_fields,
fmt::format("{}", fmt::join(questions, ",")),
fmt::format("{}", fmt::join(answers, ",")),
fmt::format("{}", fmt::join(authorities, ",")),
fmt::format("{}", fmt::join(additional, ",")));
}
buf.sz = buf.cur - buf.base;
rec.rData.resize(buf.sz);
std::copy_n(buf.base, buf.sz, rec.rData.data());
}
void
Message::AddNXReply(RR_TTL_t)
{
if (questions.size())
std::optional<Message> MaybeParseDNSMessage(llarp_buffer_t buf)
{
answers.clear();
authorities.clear();
additional.clear();
// authorative response with recursion available
hdr_fields = reply_flags(hdr_fields);
// don't allow recursion on this request
hdr_fields &= ~flags_RD;
hdr_fields |= flags_RCODENameError;
MessageHeader hdr{};
if (not hdr.Decode(&buf))
return std::nullopt;
Message msg{hdr};
if (not msg.Decode(&buf))
return std::nullopt;
return msg;
}
}
std::string
Message::ToString() const
{
return fmt::format(
"[DNSMessage id={:x} fields={:x} questions={{{}}} answers={{{}}} authorities={{{}}} "
"additional={{{}}}]",
hdr_id,
hdr_fields,
fmt::format("{}", fmt::join(questions, ",")),
fmt::format("{}", fmt::join(answers, ",")),
fmt::format("{}", fmt::join(authorities, ",")),
fmt::format("{}", fmt::join(additional, ",")));
}
std::optional<Message>
MaybeParseDNSMessage(llarp_buffer_t buf)
{
MessageHeader hdr{};
if (not hdr.Decode(&buf))
return std::nullopt;
Message msg{hdr};
if (not msg.Decode(&buf))
return std::nullopt;
return msg;
}
} // namespace llarp::dns

@ -6,108 +6,89 @@
namespace llarp
{
namespace dns
{
struct SRVData;
namespace dns
{
struct SRVData;
using MsgID_t = uint16_t;
using Fields_t = uint16_t;
using Count_t = uint16_t;
using MsgID_t = uint16_t;
using Fields_t = uint16_t;
using Count_t = uint16_t;
struct MessageHeader : public Serialize
{
static constexpr size_t Size = 12;
struct MessageHeader : public Serialize
{
static constexpr size_t Size = 12;
MessageHeader() = default;
MessageHeader() = default;
MsgID_t id;
Fields_t fields;
Count_t qd_count;
Count_t an_count;
Count_t ns_count;
Count_t ar_count;
MsgID_t id;
Fields_t fields;
Count_t qd_count;
Count_t an_count;
Count_t ns_count;
Count_t ar_count;
bool
Encode(llarp_buffer_t* buf) const override;
bool Encode(llarp_buffer_t* buf) const override;
bool
Decode(llarp_buffer_t* buf) override;
bool Decode(llarp_buffer_t* buf) override;
util::StatusObject
ToJSON() const override;
util::StatusObject ToJSON() const override;
bool
operator==(const MessageHeader& other) const
{
return id == other.id && fields == other.fields && qd_count == other.qd_count
&& an_count == other.an_count && ns_count == other.ns_count
&& ar_count == other.ar_count;
}
};
bool operator==(const MessageHeader& other) const
{
return id == other.id && fields == other.fields && qd_count == other.qd_count
&& an_count == other.an_count && ns_count == other.ns_count
&& ar_count == other.ar_count;
}
};
struct Message : public Serialize
{
explicit Message(const MessageHeader& hdr);
explicit Message(const Question& question);
struct Message : public Serialize
{
explicit Message(const MessageHeader& hdr);
explicit Message(const Question& question);
Message(Message&& other);
Message(const Message& other);
Message(Message&& other);
Message(const Message& other);
util::StatusObject
ToJSON() const override;
util::StatusObject ToJSON() const override;
void
AddNXReply(RR_TTL_t ttl = 1);
void AddNXReply(RR_TTL_t ttl = 1);
void
AddServFail(RR_TTL_t ttl = 30);
void AddServFail(RR_TTL_t ttl = 30);
void
AddMXReply(std::string name, uint16_t priority, RR_TTL_t ttl = 1);
void AddMXReply(std::string name, uint16_t priority, RR_TTL_t ttl = 1);
void
AddCNAMEReply(std::string name, RR_TTL_t ttl = 1);
void AddCNAMEReply(std::string name, RR_TTL_t ttl = 1);
void
AddINReply(llarp::huint128_t addr, bool isV6, RR_TTL_t ttl = 1);
void AddINReply(llarp::huint128_t addr, bool isV6, RR_TTL_t ttl = 1);
void
AddAReply(std::string name, RR_TTL_t ttl = 1);
void AddAReply(std::string name, RR_TTL_t ttl = 1);
void
AddSRVReply(std::vector<SRVData> records, RR_TTL_t ttl = 1);
void AddSRVReply(std::vector<SRVData> records, RR_TTL_t ttl = 1);
void
AddNSReply(std::string name, RR_TTL_t ttl = 1);
void AddNSReply(std::string name, RR_TTL_t ttl = 1);
void
AddTXTReply(std::string value, RR_TTL_t ttl = 1);
void AddTXTReply(std::string value, RR_TTL_t ttl = 1);
bool
Encode(llarp_buffer_t* buf) const override;
bool Encode(llarp_buffer_t* buf) const override;
bool
Decode(llarp_buffer_t* buf) override;
bool Decode(llarp_buffer_t* buf) override;
// Wrapper around Encode that encodes into a new buffer and returns it
[[nodiscard]] OwnedBuffer
ToBuffer() const;
// Wrapper around Encode that encodes into a new buffer and returns it
[[nodiscard]] OwnedBuffer ToBuffer() const;
std::string
ToString() const;
std::string ToString() const;
MsgID_t hdr_id;
Fields_t hdr_fields;
std::vector<Question> questions;
std::vector<ResourceRecord> answers;
std::vector<ResourceRecord> authorities;
std::vector<ResourceRecord> additional;
};
MsgID_t hdr_id;
Fields_t hdr_fields;
std::vector<Question> questions;
std::vector<ResourceRecord> answers;
std::vector<ResourceRecord> authorities;
std::vector<ResourceRecord> additional;
};
std::optional<Message>
MaybeParseDNSMessage(llarp_buffer_t buf);
} // namespace dns
std::optional<Message> MaybeParseDNSMessage(llarp_buffer_t buf);
} // namespace dns
template <>
constexpr inline bool IsToStringFormattable<llarp::dns::Message> = true;
template <>
constexpr inline bool IsToStringFormattable<llarp::dns::Message> = true;
} // namespace llarp

@ -8,131 +8,127 @@
namespace llarp::dns
{
std::optional<std::string>
DecodeName(llarp_buffer_t* buf, bool trimTrailingDot)
{
if (buf->size_left() < 1)
return std::nullopt;
auto result = std::make_optional<std::string>();
auto& name = *result;
size_t l;
do
std::optional<std::string> DecodeName(llarp_buffer_t* buf, bool trimTrailingDot)
{
l = *buf->cur;
buf->cur++;
if (l)
{
if (buf->size_left() < l)
return std::nullopt;
if (buf->size_left() < 1)
return std::nullopt;
auto result = std::make_optional<std::string>();
auto& name = *result;
size_t l;
do
{
l = *buf->cur;
buf->cur++;
if (l)
{
if (buf->size_left() < l)
return std::nullopt;
name.append((const char*)buf->cur, l);
name += '.';
}
buf->cur = buf->cur + l;
} while (l);
/// trim off last dot
if (trimTrailingDot)
name.pop_back();
return result;
}
bool
EncodeNameTo(llarp_buffer_t* buf, std::string_view name)
{
if (name.size() && name.back() == '.')
name.remove_suffix(1);
for (auto part : llarp::split(name, "."))
{
size_t l = part.length();
if (l > 63)
return false;
*(buf->cur) = l;
buf->cur++;
if (buf->size_left() < l)
return false;
if (l)
{
std::memcpy(buf->cur, part.data(), l);
buf->cur += l;
}
else
break;
name.append((const char*)buf->cur, l);
name += '.';
}
buf->cur = buf->cur + l;
} while (l);
/// trim off last dot
if (trimTrailingDot)
name.pop_back();
return result;
}
*buf->cur = 0;
buf->cur++;
return true;
}
std::optional<huint128_t>
DecodePTR(std::string_view name)
{
bool isV6 = false;
auto pos = name.find(".in-addr.arpa");
if (pos == std::string::npos)
{
pos = name.find(".ip6.arpa");
isV6 = true;
}
if (pos == std::string::npos)
return std::nullopt;
name = name.substr(0, pos + 1);
const auto numdots = std::count(name.begin(), name.end(), '.');
if (numdots == 4 && !isV6)
bool EncodeNameTo(llarp_buffer_t* buf, std::string_view name)
{
std::array<uint8_t, 4> q;
for (int i = 3; i >= 0; i--)
{
pos = name.find('.');
if (!llarp::parse_int(name.substr(0, pos), q[i]))
return std::nullopt;
name.remove_prefix(pos + 1);
}
return net::ExpandV4(llarp::ipaddr_ipv4_bits(q[0], q[1], q[2], q[3]));
if (name.size() && name.back() == '.')
name.remove_suffix(1);
for (auto part : llarp::split(name, "."))
{
size_t l = part.length();
if (l > 63)
return false;
*(buf->cur) = l;
buf->cur++;
if (buf->size_left() < l)
return false;
if (l)
{
std::memcpy(buf->cur, part.data(), l);
buf->cur += l;
}
else
break;
}
*buf->cur = 0;
buf->cur++;
return true;
}
if (numdots == 32 && name.size() == 64 && isV6)
std::optional<huint128_t> DecodePTR(std::string_view name)
{
// We're going to convert from nybbles a.b.c.d.e.f.0.1.2.3.[...] into hex string
// "badcfe1032...", then decode the hex string to bytes.
std::array<char, 32> in;
auto in_pos = in.data();
for (size_t i = 0; i < 64; i += 4)
{
if (not(oxenc::is_hex_digit(name[i]) and name[i + 1] == '.'
and oxenc::is_hex_digit(name[i + 2]) and name[i + 3] == '.'))
return std::nullopt;
bool isV6 = false;
auto pos = name.find(".in-addr.arpa");
if (pos == std::string::npos)
{
pos = name.find(".ip6.arpa");
isV6 = true;
}
if (pos == std::string::npos)
return std::nullopt;
name = name.substr(0, pos + 1);
const auto numdots = std::count(name.begin(), name.end(), '.');
if (numdots == 4 && !isV6)
{
std::array<uint8_t, 4> q;
for (int i = 3; i >= 0; i--)
{
pos = name.find('.');
if (!llarp::parse_int(name.substr(0, pos), q[i]))
return std::nullopt;
name.remove_prefix(pos + 1);
}
return net::ExpandV4(llarp::ipaddr_ipv4_bits(q[0], q[1], q[2], q[3]));
}
if (numdots == 32 && name.size() == 64 && isV6)
{
// We're going to convert from nybbles a.b.c.d.e.f.0.1.2.3.[...] into hex string
// "badcfe1032...", then decode the hex string to bytes.
std::array<char, 32> in;
auto in_pos = in.data();
for (size_t i = 0; i < 64; i += 4)
{
if (not(oxenc::is_hex_digit(name[i]) and name[i + 1] == '.'
and oxenc::is_hex_digit(name[i + 2]) and name[i + 3] == '.'))
return std::nullopt;
// Flip the nybbles because the smallest one is first
*in_pos++ = name[i + 2];
*in_pos++ = name[i];
}
assert(in_pos == in.data() + in.size());
huint128_t ip;
static_assert(in.size() == 2 * sizeof(ip.h));
// our string right now is the little endian representation, so load it as such on little
// endian, or in reverse on big endian.
if constexpr (oxenc::little_endian)
oxenc::from_hex(in.begin(), in.end(), reinterpret_cast<uint8_t*>(&ip.h));
else
oxenc::from_hex(in.rbegin(), in.rend(), reinterpret_cast<uint8_t*>(&ip.h));
// Flip the nybbles because the smallest one is first
*in_pos++ = name[i + 2];
*in_pos++ = name[i];
}
assert(in_pos == in.data() + in.size());
huint128_t ip;
static_assert(in.size() == 2 * sizeof(ip.h));
// our string right now is the little endian representation, so load it as such on
// little endian, or in reverse on big endian.
if constexpr (oxenc::little_endian)
oxenc::from_hex(in.begin(), in.end(), reinterpret_cast<uint8_t*>(&ip.h));
else
oxenc::from_hex(in.rbegin(), in.rend(), reinterpret_cast<uint8_t*>(&ip.h));
return ip;
return ip;
}
return std::nullopt;
}
return std::nullopt;
}
bool
NameIsReserved(std::string_view name)
{
const std::vector<std::string_view> reserved_names = {
".snode.loki"sv, ".loki.loki"sv, ".snode.loki."sv, ".loki.loki."sv};
for (const auto& reserved : reserved_names)
bool NameIsReserved(std::string_view name)
{
if (ends_with(name, reserved)) // subdomain foo.loki.loki
return true;
if (name == reserved.substr(1)) // loki.loki itself
return true;
const std::vector<std::string_view> reserved_names = {
".snode.loki"sv, ".loki.loki"sv, ".snode.loki."sv, ".loki.loki."sv};
for (const auto& reserved : reserved_names)
{
if (ends_with(name, reserved)) // subdomain foo.loki.loki
return true;
if (name == reserved.substr(1)) // loki.loki itself
return true;
}
return false;
}
return false;
}
} // namespace llarp::dns

@ -8,18 +8,14 @@
namespace llarp::dns
{
/// decode name from buffer; return nullopt on failure
std::optional<std::string>
DecodeName(llarp_buffer_t* buf, bool trimTrailingDot = false);
/// decode name from buffer; return nullopt on failure
std::optional<std::string> DecodeName(llarp_buffer_t* buf, bool trimTrailingDot = false);
/// encode name to buffer
bool
EncodeNameTo(llarp_buffer_t* buf, std::string_view name);
/// encode name to buffer
bool EncodeNameTo(llarp_buffer_t* buf, std::string_view name);
std::optional<huint128_t>
DecodePTR(std::string_view name);
std::optional<huint128_t> DecodePTR(std::string_view name);
bool
NameIsReserved(std::string_view name);
bool NameIsReserved(std::string_view name);
} // namespace llarp::dns

@ -12,10 +12,9 @@ using namespace std::literals;
namespace llarp::dns::nm
{
void
Platform::set_resolver(unsigned int, llarp::SockAddr, bool)
{
// todo: implement me eventually
}
void Platform::set_resolver(unsigned int, llarp::SockAddr, bool)
{
// todo: implement me eventually
}
} // namespace llarp::dns::nm
#endif

@ -8,17 +8,16 @@
namespace llarp::dns
{
namespace nm
{
// a dns platform that sets dns via network manager
class Platform : public I_Platform
namespace nm
{
public:
virtual ~Platform() = default;
// a dns platform that sets dns via network manager
class Platform : public I_Platform
{
public:
virtual ~Platform() = default;
void
set_resolver(unsigned int index, llarp::SockAddr dns, bool global) override;
};
}; // namespace nm
using NM_Platform_t = std::conditional_t<false, nm::Platform, Null_Platform>;
void set_resolver(unsigned int index, llarp::SockAddr dns, bool global) override;
};
}; // namespace nm
using NM_Platform_t = std::conditional_t<false, nm::Platform, Null_Platform>;
} // namespace llarp::dns

@ -2,31 +2,29 @@
namespace llarp::dns
{
void
Multi_Platform::add_impl(std::unique_ptr<I_Platform> impl)
{
m_Impls.emplace_back(std::move(impl));
}
void Multi_Platform::add_impl(std::unique_ptr<I_Platform> impl)
{
m_Impls.emplace_back(std::move(impl));
}
void
Multi_Platform::set_resolver(unsigned int index, llarp::SockAddr dns, bool global)
{
if (m_Impls.empty())
return;
size_t fails{0};
for (const auto& ptr : m_Impls)
void Multi_Platform::set_resolver(unsigned int index, llarp::SockAddr dns, bool global)
{
try
{
ptr->set_resolver(index, dns, global);
}
catch (std::exception& ex)
{
log::warning(log::Cat("dns"), "{}", ex.what());
fails++;
}
if (m_Impls.empty())
return;
size_t fails{0};
for (const auto& ptr : m_Impls)
{
try
{
ptr->set_resolver(index, dns, global);
}
catch (std::exception& ex)
{
log::warning(log::Cat("dns"), "{}", ex.what());
fails++;
}
}
if (fails == m_Impls.size())
throw std::runtime_error{"tried all ways to set resolver and failed"};
}
if (fails == m_Impls.size())
throw std::runtime_error{"tried all ways to set resolver and failed"};
}
} // namespace llarp::dns

@ -12,49 +12,44 @@
namespace llarp::dns
{
/// sets dns settings in a platform dependant way
class I_Platform
{
public:
virtual ~I_Platform() = default;
/// Attempts to set lokinet as the DNS server.
/// throws if unsupported or fails.
///
///
/// \param if_index -- the interface index to which we add the DNS servers, this can be gotten
/// from the interface name e.g. lokitun0 (Typically tun_endpoint.GetIfName().) and then put
/// through if_nametoindex().
/// \param dns -- the listening address of the lokinet DNS server
/// \param global -- whether to set up lokinet for all DNS queries (true) or just .loki & .snode
/// addresses (false).
virtual void
set_resolver(unsigned int if_index, llarp::SockAddr dns, bool global) = 0;
};
/// a dns platform does silently does nothing, successfully
class Null_Platform : public I_Platform
{
public:
~Null_Platform() override = default;
void
set_resolver(unsigned int, llarp::SockAddr, bool) override
{}
};
/// a collection of dns platforms that are tried in order when setting dns
class Multi_Platform : public I_Platform
{
std::vector<std::unique_ptr<I_Platform>> m_Impls;
public:
~Multi_Platform() override = default;
/// add a platform to be owned
void
add_impl(std::unique_ptr<I_Platform> impl);
/// try all owned platforms to set the resolver, throws if none of them work
void
set_resolver(unsigned int if_index, llarp::SockAddr dns, bool global) override;
};
/// sets dns settings in a platform dependant way
class I_Platform
{
public:
virtual ~I_Platform() = default;
/// Attempts to set lokinet as the DNS server.
/// throws if unsupported or fails.
///
///
/// \param if_index -- the interface index to which we add the DNS servers, this can be
/// gotten from the interface name e.g. lokitun0 (Typically tun_endpoint.GetIfName().) and
/// then put through if_nametoindex(). \param dns -- the listening address of the lokinet
/// DNS server \param global -- whether to set up lokinet for all DNS queries (true) or just
/// .loki & .snode addresses (false).
virtual void set_resolver(unsigned int if_index, llarp::SockAddr dns, bool global) = 0;
};
/// a dns platform does silently does nothing, successfully
class Null_Platform : public I_Platform
{
public:
~Null_Platform() override = default;
void set_resolver(unsigned int, llarp::SockAddr, bool) override
{}
};
/// a collection of dns platforms that are tried in order when setting dns
class Multi_Platform : public I_Platform
{
std::vector<std::unique_ptr<I_Platform>> m_Impls;
public:
~Multi_Platform() override = default;
/// add a platform to be owned
void add_impl(std::unique_ptr<I_Platform> impl);
/// try all owned platforms to set the resolver, throws if none of them work
void set_resolver(unsigned int if_index, llarp::SockAddr dns, bool global) override;
};
} // namespace llarp::dns

@ -6,121 +6,111 @@
namespace llarp::dns
{
static auto logcat = log::Cat("dns");
Question::Question(Question&& other)
: qname(std::move(other.qname))
, qtype(std::move(other.qtype))
, qclass(std::move(other.qclass))
{}
Question::Question(const Question& other)
: qname(other.qname), qtype(other.qtype), qclass(other.qclass)
{}
Question::Question(std::string name, QType_t type)
: qname{std::move(name)}, qtype{type}, qclass{qClassIN}
{
if (qname.empty())
throw std::invalid_argument{"qname cannot be empty"};
}
bool
Question::Encode(llarp_buffer_t* buf) const
{
if (!EncodeNameTo(buf, qname))
return false;
if (!buf->put_uint16(qtype))
return false;
return buf->put_uint16(qclass);
}
bool
Question::Decode(llarp_buffer_t* buf)
{
if (auto name = DecodeName(buf))
qname = *std::move(name);
else
static auto logcat = log::Cat("dns");
Question::Question(Question&& other)
: qname(std::move(other.qname)),
qtype(std::move(other.qtype)),
qclass(std::move(other.qclass))
{}
Question::Question(const Question& other)
: qname(other.qname), qtype(other.qtype), qclass(other.qclass)
{}
Question::Question(std::string name, QType_t type)
: qname{std::move(name)}, qtype{type}, qclass{qClassIN}
{
log::error(logcat, "failed to decode name");
return false;
if (qname.empty())
throw std::invalid_argument{"qname cannot be empty"};
}
if (!buf->read_uint16(qtype))
bool Question::Encode(llarp_buffer_t* buf) const
{
if (!EncodeNameTo(buf, qname))
return false;
if (!buf->put_uint16(qtype))
return false;
return buf->put_uint16(qclass);
}
bool Question::Decode(llarp_buffer_t* buf)
{
if (auto name = DecodeName(buf))
qname = *std::move(name);
else
{
log::error(logcat, "failed to decode name");
return false;
}
if (!buf->read_uint16(qtype))
{
log::error(logcat, "failed to decode type");
return false;
}
if (!buf->read_uint16(qclass))
{
log::error(logcat, "failed to decode class");
return false;
}
return true;
}
util::StatusObject Question::ToJSON() const
{
return util::StatusObject{{"qname", qname}, {"qtype", qtype}, {"qclass", qclass}};
}
bool Question::IsName(const std::string& other) const
{
// does other have a . at the end?
if (other.find_last_of('.') == (other.size() - 1))
return other == qname;
// no, add it and retry
return IsName(other + ".");
}
bool Question::IsLocalhost() const
{
return (qname == "localhost.loki." or llarp::ends_with(qname, ".localhost.loki."));
}
bool Question::HasSubdomains() const
{
const auto parts = split(qname, ".", true);
return parts.size() >= 3;
}
std::string Question::Subdomains() const
{
log::error(logcat, "failed to decode type");
return false;
if (qname.size() < 2)
return "";
size_t pos;
pos = qname.rfind('.', qname.size() - 2);
if (pos == std::string::npos or pos == 0)
return "";
pos = qname.rfind('.', pos - 1);
if (pos == std::string::npos or pos == 0)
return "";
return qname.substr(0, pos);
}
if (!buf->read_uint16(qclass))
std::string Question::Name() const
{
return qname.substr(0, qname.find_last_of('.'));
}
bool Question::HasTLD(const std::string& tld) const
{
return qname.find(tld) != std::string::npos
&& qname.rfind(tld) == (qname.size() - tld.size()) - 1;
}
std::string Question::ToString() const
{
log::error(logcat, "failed to decode class");
return false;
return fmt::format("[DNSQuestion qname={} qtype={:x} qclass={:x}]", qname, qtype, qclass);
}
return true;
}
util::StatusObject
Question::ToJSON() const
{
return util::StatusObject{{"qname", qname}, {"qtype", qtype}, {"qclass", qclass}};
}
bool
Question::IsName(const std::string& other) const
{
// does other have a . at the end?
if (other.find_last_of('.') == (other.size() - 1))
return other == qname;
// no, add it and retry
return IsName(other + ".");
}
bool
Question::IsLocalhost() const
{
return (qname == "localhost.loki." or llarp::ends_with(qname, ".localhost.loki."));
}
bool
Question::HasSubdomains() const
{
const auto parts = split(qname, ".", true);
return parts.size() >= 3;
}
std::string
Question::Subdomains() const
{
if (qname.size() < 2)
return "";
size_t pos;
pos = qname.rfind('.', qname.size() - 2);
if (pos == std::string::npos or pos == 0)
return "";
pos = qname.rfind('.', pos - 1);
if (pos == std::string::npos or pos == 0)
return "";
return qname.substr(0, pos);
}
std::string
Question::Name() const
{
return qname.substr(0, qname.find_last_of('.'));
}
bool
Question::HasTLD(const std::string& tld) const
{
return qname.find(tld) != std::string::npos
&& qname.rfind(tld) == (qname.size() - tld.size()) - 1;
}
std::string
Question::ToString() const
{
return fmt::format("[DNSQuestion qname={} qtype={:x} qclass={:x}]", qname, qtype, qclass);
}
} // namespace llarp::dns

@ -7,63 +7,52 @@
namespace llarp::dns
{
using QType_t = uint16_t;
using QClass_t = uint16_t;
using QType_t = uint16_t;
using QClass_t = uint16_t;
struct Question : public Serialize
{
Question() = default;
struct Question : public Serialize
{
Question() = default;
explicit Question(std::string name, QType_t type);
explicit Question(std::string name, QType_t type);
Question(Question&& other);
Question(const Question& other);
bool
Encode(llarp_buffer_t* buf) const override;
Question(Question&& other);
Question(const Question& other);
bool Encode(llarp_buffer_t* buf) const override;
bool
Decode(llarp_buffer_t* buf) override;
bool Decode(llarp_buffer_t* buf) override;
std::string
ToString() const;
std::string ToString() const;
bool
operator==(const Question& other) const
{
return qname == other.qname && qtype == other.qtype && qclass == other.qclass;
}
bool operator==(const Question& other) const
{
return qname == other.qname && qtype == other.qtype && qclass == other.qclass;
}
std::string qname;
QType_t qtype;
QClass_t qclass;
std::string qname;
QType_t qtype;
QClass_t qclass;
/// determine if we match a name
bool
IsName(const std::string& other) const;
/// determine if we match a name
bool IsName(const std::string& other) const;
/// is the name [something.]localhost.loki. ?
bool
IsLocalhost() const;
/// is the name [something.]localhost.loki. ?
bool IsLocalhost() const;
/// return true if we have subdomains in ths question
bool
HasSubdomains() const;
/// return true if we have subdomains in ths question
bool HasSubdomains() const;
/// get subdomain(s), if any, from qname
std::string
Subdomains() const;
/// get subdomain(s), if any, from qname
std::string Subdomains() const;
/// return qname with no trailing .
std::string
Name() const;
/// return qname with no trailing .
std::string Name() const;
/// determine if we are using this TLD
bool
HasTLD(const std::string& tld) const;
/// determine if we are using this TLD
bool HasTLD(const std::string& tld) const;
util::StatusObject
ToJSON() const override;
};
util::StatusObject ToJSON() const override;
};
} // namespace llarp::dns
template <>

@ -4,113 +4,112 @@
namespace llarp::dns
{
static auto logcat = log::Cat("dns");
static auto logcat = log::Cat("dns");
ResourceRecord::ResourceRecord(const ResourceRecord& other)
: rr_name(other.rr_name)
, rr_type(other.rr_type)
, rr_class(other.rr_class)
, ttl(other.ttl)
, rData(other.rData)
{}
ResourceRecord::ResourceRecord(const ResourceRecord& other)
: rr_name(other.rr_name),
rr_type(other.rr_type),
rr_class(other.rr_class),
ttl(other.ttl),
rData(other.rData)
{}
ResourceRecord::ResourceRecord(ResourceRecord&& other)
: rr_name(std::move(other.rr_name))
, rr_type(std::move(other.rr_type))
, rr_class(std::move(other.rr_class))
, ttl(std::move(other.ttl))
, rData(std::move(other.rData))
{}
ResourceRecord::ResourceRecord(ResourceRecord&& other)
: rr_name(std::move(other.rr_name)),
rr_type(std::move(other.rr_type)),
rr_class(std::move(other.rr_class)),
ttl(std::move(other.ttl)),
rData(std::move(other.rData))
{}
ResourceRecord::ResourceRecord(std::string name, RRType_t type, RR_RData_t data)
: rr_name{std::move(name)}, rr_type{type}, rr_class{qClassIN}, ttl{1}, rData{std::move(data)}
{}
ResourceRecord::ResourceRecord(std::string name, RRType_t type, RR_RData_t data)
: rr_name{std::move(name)},
rr_type{type},
rr_class{qClassIN},
ttl{1},
rData{std::move(data)}
{}
bool
ResourceRecord::Encode(llarp_buffer_t* buf) const
{
if (not EncodeNameTo(buf, rr_name))
return false;
if (!buf->put_uint16(rr_type))
bool ResourceRecord::Encode(llarp_buffer_t* buf) const
{
return false;
if (not EncodeNameTo(buf, rr_name))
return false;
if (!buf->put_uint16(rr_type))
{
return false;
}
if (!buf->put_uint16(rr_class))
{
return false;
}
if (!buf->put_uint32(ttl))
{
return false;
}
if (!EncodeRData(buf, rData))
{
return false;
}
return true;
}
if (!buf->put_uint16(rr_class))
{
return false;
}
if (!buf->put_uint32(ttl))
{
return false;
}
if (!EncodeRData(buf, rData))
{
return false;
}
return true;
}
bool
ResourceRecord::Decode(llarp_buffer_t* buf)
{
uint16_t discard;
if (!buf->read_uint16(discard))
return false;
if (!buf->read_uint16(rr_type))
bool ResourceRecord::Decode(llarp_buffer_t* buf)
{
log::debug(logcat, "failed to decode rr type");
return false;
uint16_t discard;
if (!buf->read_uint16(discard))
return false;
if (!buf->read_uint16(rr_type))
{
log::debug(logcat, "failed to decode rr type");
return false;
}
if (!buf->read_uint16(rr_class))
{
log::debug(logcat, "failed to decode rr class");
return false;
}
if (!buf->read_uint32(ttl))
{
log::debug(logcat, "failed to decode ttl");
return false;
}
if (!DecodeRData(buf, rData))
{
log::debug(logcat, "failed to decode rr rdata {}", *this);
return false;
}
return true;
}
if (!buf->read_uint16(rr_class))
util::StatusObject ResourceRecord::ToJSON() const
{
log::debug(logcat, "failed to decode rr class");
return false;
return util::StatusObject{
{"name", rr_name},
{"type", rr_type},
{"class", rr_class},
{"ttl", ttl},
{"rdata", std::string{reinterpret_cast<const char*>(rData.data()), rData.size()}}};
}
if (!buf->read_uint32(ttl))
std::string ResourceRecord::ToString() const
{
log::debug(logcat, "failed to decode ttl");
return false;
return fmt::format(
"[RR name={} type={} class={} ttl={} rdata-size={}]",
rr_name,
rr_type,
rr_class,
ttl,
rData.size());
}
if (!DecodeRData(buf, rData))
bool ResourceRecord::HasCNameForTLD(const std::string& tld) const
{
log::debug(logcat, "failed to decode rr rdata {}", *this);
return false;
if (rr_type != qTypeCNAME)
return false;
llarp_buffer_t buf(rData);
if (auto name = DecodeName(&buf))
return name->rfind(tld) == name->size() - tld.size() - 1;
return false;
}
return true;
}
util::StatusObject
ResourceRecord::ToJSON() const
{
return util::StatusObject{
{"name", rr_name},
{"type", rr_type},
{"class", rr_class},
{"ttl", ttl},
{"rdata", std::string{reinterpret_cast<const char*>(rData.data()), rData.size()}}};
}
std::string
ResourceRecord::ToString() const
{
return fmt::format(
"[RR name={} type={} class={} ttl={} rdata-size={}]",
rr_name,
rr_type,
rr_class,
ttl,
rData.size());
}
bool
ResourceRecord::HasCNameForTLD(const std::string& tld) const
{
if (rr_type != qTypeCNAME)
return false;
llarp_buffer_t buf(rData);
if (auto name = DecodeName(&buf))
return name->rfind(tld) == name->size() - tld.size() - 1;
return false;
}
} // namespace llarp::dns

@ -10,40 +10,35 @@
namespace llarp::dns
{
using RRClass_t = uint16_t;
using RRType_t = uint16_t;
using RR_RData_t = std::vector<byte_t>;
using RR_TTL_t = uint32_t;
using RRClass_t = uint16_t;
using RRType_t = uint16_t;
using RR_RData_t = std::vector<byte_t>;
using RR_TTL_t = uint32_t;
struct ResourceRecord : public Serialize
{
ResourceRecord() = default;
ResourceRecord(const ResourceRecord& other);
ResourceRecord(ResourceRecord&& other);
struct ResourceRecord : public Serialize
{
ResourceRecord() = default;
ResourceRecord(const ResourceRecord& other);
ResourceRecord(ResourceRecord&& other);
explicit ResourceRecord(std::string name, RRType_t type, RR_RData_t rdata);
explicit ResourceRecord(std::string name, RRType_t type, RR_RData_t rdata);
bool
Encode(llarp_buffer_t* buf) const override;
bool Encode(llarp_buffer_t* buf) const override;
bool
Decode(llarp_buffer_t* buf) override;
bool Decode(llarp_buffer_t* buf) override;
util::StatusObject
ToJSON() const override;
util::StatusObject ToJSON() const override;
std::string
ToString() const;
std::string ToString() const;
bool
HasCNameForTLD(const std::string& tld) const;
bool HasCNameForTLD(const std::string& tld) const;
std::string rr_name;
RRType_t rr_type;
RRClass_t rr_class;
RR_TTL_t ttl;
RR_RData_t rData;
};
std::string rr_name;
RRType_t rr_type;
RRClass_t rr_class;
RR_TTL_t ttl;
RR_RData_t rData;
};
} // namespace llarp::dns
template <>

@ -12,117 +12,116 @@ using namespace std::literals;
namespace llarp::dns::sd
{
void
Platform::set_resolver(unsigned int if_ndx, llarp::SockAddr dns, bool global)
{
linux::DBUS _dbus{
"org.freedesktop.resolve1",
"/org/freedesktop/resolve1",
"org.freedesktop.resolve1.Manager"};
// This passing address by bytes and using two separate calls for ipv4/ipv6 is gross, but
// the alternative is to build up a bunch of crap with va_args, which is slightly more
// gross.
const bool isStandardDNSPort = dns.getPort() == 53;
if (dns.isIPv6())
void Platform::set_resolver(unsigned int if_ndx, llarp::SockAddr dns, bool global)
{
auto ipv6 = dns.getIPv6();
static_assert(sizeof(ipv6) == 16);
auto* a = reinterpret_cast<const uint8_t*>(&ipv6);
if (isStandardDNSPort)
{
_dbus(
"SetLinkDNS",
"ia(iay)",
(int32_t)if_ndx,
(int)1, // number of "iayqs"s we are passing
(int32_t)AF_INET6, // network address type
(int)16, // network addr byte size
// clang-format off
linux::DBUS _dbus{
"org.freedesktop.resolve1",
"/org/freedesktop/resolve1",
"org.freedesktop.resolve1.Manager"};
// This passing address by bytes and using two separate calls for ipv4/ipv6 is gross, but
// the alternative is to build up a bunch of crap with va_args, which is slightly more
// gross.
const bool isStandardDNSPort = dns.getPort() == 53;
if (dns.isIPv6())
{
auto ipv6 = dns.getIPv6();
static_assert(sizeof(ipv6) == 16);
auto* a = reinterpret_cast<const uint8_t*>(&ipv6);
if (isStandardDNSPort)
{
_dbus(
"SetLinkDNS",
"ia(iay)",
(int32_t)if_ndx,
(int)1, // number of "iayqs"s we are passing
(int32_t)AF_INET6, // network address type
(int)16, // network addr byte size
// clang-format off
a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7],
a[8], a[9], a[10], a[11], a[12], a[13], a[14], a[15] // yuck
// clang-format on
);
}
else
{
_dbus(
"SetLinkDNSEx",
"ia(iayqs)",
(int32_t)if_ndx,
(int)1, // number of "iayqs"s we are passing
(int32_t)AF_INET6, // network address type
(int)16, // network addr byte size
// clang-format off
// clang-format on
);
}
else
{
_dbus(
"SetLinkDNSEx",
"ia(iayqs)",
(int32_t)if_ndx,
(int)1, // number of "iayqs"s we are passing
(int32_t)AF_INET6, // network address type
(int)16, // network addr byte size
// clang-format off
a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7],
a[8], a[9], a[10], a[11], a[12], a[13], a[14], a[15], // yuck
// clang-format on
(uint16_t)dns.getPort(),
nullptr // dns server name (for TLS SNI which we don't care about)
);
}
}
else
{
auto ipv4 = dns.getIPv4();
static_assert(sizeof(ipv4) == 4);
auto* a = reinterpret_cast<const uint8_t*>(&ipv4);
if (isStandardDNSPort)
{
_dbus(
"SetLinkDNS",
"ia(iay)",
(int32_t)if_ndx,
(int)1, // number of "iayqs"s we are passing
(int32_t)AF_INET, // network address type
(int)4, // network addr byte size
// clang-format off
// clang-format on
(uint16_t)dns.getPort(),
nullptr // dns server name (for TLS SNI which we don't care about)
);
}
}
else
{
auto ipv4 = dns.getIPv4();
static_assert(sizeof(ipv4) == 4);
auto* a = reinterpret_cast<const uint8_t*>(&ipv4);
if (isStandardDNSPort)
{
_dbus(
"SetLinkDNS",
"ia(iay)",
(int32_t)if_ndx,
(int)1, // number of "iayqs"s we are passing
(int32_t)AF_INET, // network address type
(int)4, // network addr byte size
// clang-format off
a[0], a[1], a[2], a[3] // yuck
// clang-format on
);
}
else
{
_dbus(
"SetLinkDNSEx",
"ia(iayqs)",
(int32_t)if_ndx,
(int)1, // number of "iayqs"s we are passing
(int32_t)AF_INET, // network address type
(int)4, // network addr byte size
// clang-format off
// clang-format on
);
}
else
{
_dbus(
"SetLinkDNSEx",
"ia(iayqs)",
(int32_t)if_ndx,
(int)1, // number of "iayqs"s we are passing
(int32_t)AF_INET, // network address type
(int)4, // network addr byte size
// clang-format off
a[0], a[1], a[2], a[3], // yuck
// clang-format on
(uint16_t)dns.getPort(),
nullptr // dns server name (for TLS SNI which we don't care about)
);
}
}
// clang-format on
(uint16_t)dns.getPort(),
nullptr // dns server name (for TLS SNI which we don't care about)
);
}
}
if (global)
// Setting "." as a routing domain gives this DNS server higher priority in resolution
// compared to dns servers that are set without a domain (e.g. the default for a
// DHCP-configured DNS server)
_dbus(
"SetLinkDomains",
"ia(sb)",
(int32_t)if_ndx,
(int)1, // array size
"." // global DNS root
);
else
// Only resolve .loki and .snode through lokinet (so you keep using your local DNS
// server for everything else, which is nicer than forcing everything though lokinet's
// upstream DNS).
_dbus(
"SetLinkDomains",
"ia(sb)",
(int32_t)if_ndx,
(int)2, // array size
"loki", // domain
(int)1, // routing domain = true
"snode", // domain
(int)1 // routing domain = true
);
}
if (global)
// Setting "." as a routing domain gives this DNS server higher priority in resolution
// compared to dns servers that are set without a domain (e.g. the default for a
// DHCP-configured DNS server)
_dbus(
"SetLinkDomains",
"ia(sb)",
(int32_t)if_ndx,
(int)1, // array size
"." // global DNS root
);
else
// Only resolve .loki and .snode through lokinet (so you keep using your local DNS
// server for everything else, which is nicer than forcing everything though lokinet's
// upstream DNS).
_dbus(
"SetLinkDomains",
"ia(sb)",
(int32_t)if_ndx,
(int)2, // array size
"loki", // domain
(int)1, // routing domain = true
"snode", // domain
(int)1 // routing domain = true
);
}
} // namespace llarp::dns::sd
#endif

@ -7,18 +7,17 @@
namespace llarp::dns
{
namespace sd
{
/// a dns platform that sets dns via systemd resolved
class Platform : public I_Platform
namespace sd
{
public:
virtual ~Platform() = default;
/// a dns platform that sets dns via systemd resolved
class Platform : public I_Platform
{
public:
virtual ~Platform() = default;
void
set_resolver(unsigned int if_index, llarp::SockAddr dns, bool global) override;
};
} // namespace sd
using SD_Platform_t =
std::conditional_t<llarp::platform::has_systemd, sd::Platform, Null_Platform>;
void set_resolver(unsigned int if_index, llarp::SockAddr dns, bool global) override;
};
} // namespace sd
using SD_Platform_t =
std::conditional_t<llarp::platform::has_systemd, sd::Platform, Null_Platform>;
} // namespace llarp::dns

@ -2,39 +2,37 @@
namespace llarp::dns
{
Serialize::~Serialize() = default;
Serialize::~Serialize() = default;
bool
EncodeRData(llarp_buffer_t* buf, const std::vector<byte_t>& v)
{
if (v.size() > 65536)
return false;
uint16_t len = v.size();
if (!buf->put_uint16(len))
return false;
if (buf->size_left() < len)
return false;
memcpy(buf->cur, v.data(), len);
buf->cur += len;
return true;
}
bool EncodeRData(llarp_buffer_t* buf, const std::vector<byte_t>& v)
{
if (v.size() > 65536)
return false;
uint16_t len = v.size();
if (!buf->put_uint16(len))
return false;
if (buf->size_left() < len)
return false;
memcpy(buf->cur, v.data(), len);
buf->cur += len;
return true;
}
bool
DecodeRData(llarp_buffer_t* buf, std::vector<byte_t>& v)
{
uint16_t len;
if (!buf->read_uint16(len))
return false;
size_t left = buf->size_left();
if (left < len)
return false;
v.resize(size_t(len));
if (len)
bool DecodeRData(llarp_buffer_t* buf, std::vector<byte_t>& v)
{
memcpy(v.data(), buf->cur, len);
buf->cur += len;
uint16_t len;
if (!buf->read_uint16(len))
return false;
size_t left = buf->size_left();
if (left < len)
return false;
v.resize(size_t(len));
if (len)
{
memcpy(v.data(), buf->cur, len);
buf->cur += len;
}
return true;
}
return true;
}
} // namespace llarp::dns

@ -7,28 +7,23 @@
namespace llarp::dns
{
/// base type for serializable dns entities
struct Serialize
{
virtual ~Serialize() = 0;
/// base type for serializable dns entities
struct Serialize
{
virtual ~Serialize() = 0;
/// encode entity to buffer
virtual bool
Encode(llarp_buffer_t* buf) const = 0;
/// encode entity to buffer
virtual bool Encode(llarp_buffer_t* buf) const = 0;
/// decode entity from buffer
virtual bool
Decode(llarp_buffer_t* buf) = 0;
/// decode entity from buffer
virtual bool Decode(llarp_buffer_t* buf) = 0;
/// convert this whatever into json
virtual util::StatusObject
ToJSON() const = 0;
};
/// convert this whatever into json
virtual util::StatusObject ToJSON() const = 0;
};
bool
EncodeRData(llarp_buffer_t* buf, const std::vector<byte_t>& rdata);
bool EncodeRData(llarp_buffer_t* buf, const std::vector<byte_t>& rdata);
bool
DecodeRData(llarp_buffer_t* buf, std::vector<byte_t>& rdata);
bool DecodeRData(llarp_buffer_t* buf, std::vector<byte_t>& rdata);
} // namespace llarp::dns

File diff suppressed because it is too large Load Diff

@ -13,281 +13,247 @@
namespace llarp::dns
{
/// a job handling 1 dns query
class QueryJob_Base
{
protected:
/// the original dns query
Message m_Query;
/// a job handling 1 dns query
class QueryJob_Base
{
protected:
/// the original dns query
Message m_Query;
/// True if we've sent a reply (including via a call to cancel)
std::atomic_flag m_Done = ATOMIC_FLAG_INIT;
/// True if we've sent a reply (including via a call to cancel)
std::atomic_flag m_Done = ATOMIC_FLAG_INIT;
public:
explicit QueryJob_Base(Message query) : m_Query{std::move(query)}
{}
public:
explicit QueryJob_Base(Message query) : m_Query{std::move(query)}
{}
virtual ~QueryJob_Base() = default;
virtual ~QueryJob_Base() = default;
Message&
Underlying()
{
return m_Query;
}
Message& Underlying()
{
return m_Query;
}
const Message&
Underlying() const
{
return m_Query;
}
/// cancel this operation and inform anyone who cares
void
Cancel();
/// send a raw buffer back to the querier
virtual void
SendReply(llarp::OwnedBuffer replyBuf) = 0;
};
class PacketSource_Base
{
public:
virtual ~PacketSource_Base() = default;
/// return true if traffic with source and dest addresses would cause a
/// loop in resolution and thus should not be sent to query handlers
virtual bool
WouldLoop(const SockAddr& to, const SockAddr& from) const = 0;
/// send packet with src and dst address containing buf on this packet source
virtual void
SendTo(const SockAddr& to, const SockAddr& from, OwnedBuffer buf) const = 0;
/// stop reading packets and end operation
virtual void
Stop() = 0;
/// returns the sockaddr we are bound on if applicable
virtual std::optional<SockAddr>
BoundOn() const = 0;
};
/// a packet source which will override the sendto function of an wrapped packet source to
/// construct a raw ip packet as a reply
class PacketSource_Wrapper : public PacketSource_Base
{
std::weak_ptr<PacketSource_Base> m_Wrapped;
std::function<void(net::IPPacket)> m_WritePacket;
public:
explicit PacketSource_Wrapper(
std::weak_ptr<PacketSource_Base> wrapped, std::function<void(net::IPPacket)> write_packet)
: m_Wrapped{wrapped}, m_WritePacket{write_packet}
{}
bool
WouldLoop(const SockAddr& to, const SockAddr& from) const override
{
if (auto ptr = m_Wrapped.lock())
return ptr->WouldLoop(to, from);
return true;
}
const Message& Underlying() const
{
return m_Query;
}
void
SendTo(const SockAddr& to, const SockAddr& from, OwnedBuffer buf) const override
{
m_WritePacket(net::IPPacket::make_udp(from, to, std::move(buf)));
}
/// cancel this operation and inform anyone who cares
void Cancel();
/// send a raw buffer back to the querier
virtual void SendReply(llarp::OwnedBuffer replyBuf) = 0;
};
/// stop reading packets and end operation
void
Stop() override
class PacketSource_Base
{
if (auto ptr = m_Wrapped.lock())
ptr->Stop();
}
public:
virtual ~PacketSource_Base() = default;
/// return true if traffic with source and dest addresses would cause a
/// loop in resolution and thus should not be sent to query handlers
virtual bool WouldLoop(const SockAddr& to, const SockAddr& from) const = 0;
/// returns the sockaddr we are bound on if applicable
std::optional<SockAddr>
BoundOn() const override
/// send packet with src and dst address containing buf on this packet source
virtual void SendTo(const SockAddr& to, const SockAddr& from, OwnedBuffer buf) const = 0;
/// stop reading packets and end operation
virtual void Stop() = 0;
/// returns the sockaddr we are bound on if applicable
virtual std::optional<SockAddr> BoundOn() const = 0;
};
/// a packet source which will override the sendto function of an wrapped packet source to
/// construct a raw ip packet as a reply
class PacketSource_Wrapper : public PacketSource_Base
{
if (auto ptr = m_Wrapped.lock())
return ptr->BoundOn();
return std::nullopt;
}
};
/// non complex implementation of QueryJob_Base for use in things that
/// only ever called on the mainloop thread
class QueryJob : public QueryJob_Base, std::enable_shared_from_this<QueryJob>
{
std::shared_ptr<PacketSource_Base> src;
const SockAddr resolver;
const SockAddr asker;
public:
explicit QueryJob(
std::shared_ptr<PacketSource_Base> source,
const Message& query,
const SockAddr& to_,
const SockAddr& from_)
: QueryJob_Base{query}, src{source}, resolver{to_}, asker{from_}
{}
void
SendReply(llarp::OwnedBuffer replyBuf) override
std::weak_ptr<PacketSource_Base> m_Wrapped;
std::function<void(net::IPPacket)> m_WritePacket;
public:
explicit PacketSource_Wrapper(
std::weak_ptr<PacketSource_Base> wrapped,
std::function<void(net::IPPacket)> write_packet)
: m_Wrapped{wrapped}, m_WritePacket{write_packet}
{}
bool WouldLoop(const SockAddr& to, const SockAddr& from) const override
{
if (auto ptr = m_Wrapped.lock())
return ptr->WouldLoop(to, from);
return true;
}
void SendTo(const SockAddr& to, const SockAddr& from, OwnedBuffer buf) const override
{
m_WritePacket(net::IPPacket::make_udp(from, to, std::move(buf)));
}
/// stop reading packets and end operation
void Stop() override
{
if (auto ptr = m_Wrapped.lock())
ptr->Stop();
}
/// returns the sockaddr we are bound on if applicable
std::optional<SockAddr> BoundOn() const override
{
if (auto ptr = m_Wrapped.lock())
return ptr->BoundOn();
return std::nullopt;
}
};
/// non complex implementation of QueryJob_Base for use in things that
/// only ever called on the mainloop thread
class QueryJob : public QueryJob_Base, std::enable_shared_from_this<QueryJob>
{
src->SendTo(asker, resolver, std::move(replyBuf));
}
};
/// handler of dns query hooking
/// intercepts dns for internal processing
class Resolver_Base
{
protected:
/// return the sorting order for this resolver
/// lower means it will be tried first
virtual int
Rank() const = 0;
public:
virtual ~Resolver_Base() = default;
/// less than via rank
bool
operator<(const Resolver_Base& other) const
std::shared_ptr<PacketSource_Base> src;
const SockAddr resolver;
const SockAddr asker;
public:
explicit QueryJob(
std::shared_ptr<PacketSource_Base> source,
const Message& query,
const SockAddr& to_,
const SockAddr& from_)
: QueryJob_Base{query}, src{source}, resolver{to_}, asker{from_}
{}
void SendReply(llarp::OwnedBuffer replyBuf) override
{
src->SendTo(asker, resolver, std::move(replyBuf));
}
};
/// handler of dns query hooking
/// intercepts dns for internal processing
class Resolver_Base
{
return Rank() < other.Rank();
}
/// greater than via rank
bool
operator>(const Resolver_Base& other) const
protected:
/// return the sorting order for this resolver
/// lower means it will be tried first
virtual int Rank() const = 0;
public:
virtual ~Resolver_Base() = default;
/// less than via rank
bool operator<(const Resolver_Base& other) const
{
return Rank() < other.Rank();
}
/// greater than via rank
bool operator>(const Resolver_Base& other) const
{
return Rank() > other.Rank();
}
/// get local socket address that queries are sent from
virtual std::optional<SockAddr> GetLocalAddr() const
{
return std::nullopt;
}
/// get printable name
virtual std::string_view ResolverName() const = 0;
/// reset the resolver state, optionally replace upstream info with new info. The default
/// base implementation does nothing.
virtual void ResetResolver(
[[maybe_unused]] std::optional<std::vector<SockAddr>> replace_upstream = std::nullopt)
{}
/// cancel all pending requests and cease further operation. Default operation is a no-op.
virtual void Down()
{}
/// attempt to handle a dns message
/// returns true if we consumed this query and it should not be processed again
virtual bool MaybeHookDNS(
std::shared_ptr<PacketSource_Base> source,
const Message& query,
const SockAddr& to,
const SockAddr& from) = 0;
};
// Base class for DNS proxy
class Server : public std::enable_shared_from_this<Server>
{
return Rank() > other.Rank();
}
protected:
/// add a packet source to this server, does share ownership
void AddPacketSource(std::shared_ptr<PacketSource_Base> resolver);
/// add a resolver to this packet handler, does share ownership
void AddResolver(std::shared_ptr<Resolver_Base> resolver);
/// get local socket address that queries are sent from
virtual std::optional<SockAddr>
GetLocalAddr() const
{
return std::nullopt;
}
/// get printable name
virtual std::string_view
ResolverName() const = 0;
/// reset the resolver state, optionally replace upstream info with new info. The default base
/// implementation does nothing.
virtual void
ResetResolver(
[[maybe_unused]] std::optional<std::vector<SockAddr>> replace_upstream = std::nullopt)
{}
/// cancel all pending requests and cease further operation. Default operation is a no-op.
virtual void
Down()
{}
/// attempt to handle a dns message
/// returns true if we consumed this query and it should not be processed again
virtual bool
MaybeHookDNS(
std::shared_ptr<PacketSource_Base> source,
const Message& query,
const SockAddr& to,
const SockAddr& from) = 0;
};
// Base class for DNS proxy
class Server : public std::enable_shared_from_this<Server>
{
protected:
/// add a packet source to this server, does share ownership
void
AddPacketSource(std::shared_ptr<PacketSource_Base> resolver);
/// add a resolver to this packet handler, does share ownership
void
AddResolver(std::shared_ptr<Resolver_Base> resolver);
/// create the platform dependant dns stuff
virtual std::shared_ptr<I_Platform>
CreatePlatform() const;
public:
virtual ~Server() = default;
explicit Server(EventLoop_ptr loop, llarp::DnsConfig conf, unsigned int netif_index);
/// returns all sockaddr we have from all of our PacketSources
std::vector<SockAddr>
BoundPacketSourceAddrs() const;
/// returns the first sockaddr we have on our packet sources if we have one
std::optional<SockAddr>
FirstBoundPacketSourceAddr() const;
/// add a resolver to this packet handler, does not share ownership
void
AddResolver(std::weak_ptr<Resolver_Base> resolver);
/// add a packet source to this server, does not share ownership
void
AddPacketSource(std::weak_ptr<PacketSource_Base> resolver);
/// create a packet source bound on bindaddr but does not add it
virtual std::shared_ptr<PacketSource_Base>
MakePacketSourceOn(const SockAddr& bindaddr, const llarp::DnsConfig& conf);
/// sets up all internal binds and such and begins operation
virtual void
Start();
/// stops all operation
virtual void
Stop();
/// reset the internal state
virtual void
Reset();
/// create the default resolver for out config
virtual std::shared_ptr<Resolver_Base>
MakeDefaultResolver();
std::vector<std::weak_ptr<Resolver_Base>>
GetAllResolvers() const;
/// feed a packet buffer from a packet source.
/// returns true if we decided to process the packet and consumed it
/// returns false if we dont want to process the packet
bool
MaybeHandlePacket(
std::shared_ptr<PacketSource_Base> pktsource,
const SockAddr& resolver,
const SockAddr& from,
llarp::OwnedBuffer buf);
/// set which dns mode we are in.
/// true for intercepting all queries. false for just .loki and .snode
void
SetDNSMode(bool all_queries);
protected:
EventLoop_ptr m_Loop;
llarp::DnsConfig m_Config;
std::shared_ptr<I_Platform> m_Platform;
private:
const unsigned int m_NetIfIndex;
std::set<std::shared_ptr<Resolver_Base>, ComparePtr<std::shared_ptr<Resolver_Base>>>
m_OwnedResolvers;
std::set<std::weak_ptr<Resolver_Base>, CompareWeakPtr<Resolver_Base>> m_Resolvers;
std::vector<std::weak_ptr<PacketSource_Base>> m_PacketSources;
std::vector<std::shared_ptr<PacketSource_Base>> m_OwnedPacketSources;
};
/// create the platform dependant dns stuff
virtual std::shared_ptr<I_Platform> CreatePlatform() const;
public:
virtual ~Server() = default;
explicit Server(EventLoop_ptr loop, llarp::DnsConfig conf, unsigned int netif_index);
/// returns all sockaddr we have from all of our PacketSources
std::vector<SockAddr> BoundPacketSourceAddrs() const;
/// returns the first sockaddr we have on our packet sources if we have one
std::optional<SockAddr> FirstBoundPacketSourceAddr() const;
/// add a resolver to this packet handler, does not share ownership
void AddResolver(std::weak_ptr<Resolver_Base> resolver);
/// add a packet source to this server, does not share ownership
void AddPacketSource(std::weak_ptr<PacketSource_Base> resolver);
/// create a packet source bound on bindaddr but does not add it
virtual std::shared_ptr<PacketSource_Base> MakePacketSourceOn(
const SockAddr& bindaddr, const llarp::DnsConfig& conf);
/// sets up all internal binds and such and begins operation
virtual void Start();
/// stops all operation
virtual void Stop();
/// reset the internal state
virtual void Reset();
/// create the default resolver for out config
virtual std::shared_ptr<Resolver_Base> MakeDefaultResolver();
std::vector<std::weak_ptr<Resolver_Base>> GetAllResolvers() const;
/// feed a packet buffer from a packet source.
/// returns true if we decided to process the packet and consumed it
/// returns false if we dont want to process the packet
bool MaybeHandlePacket(
std::shared_ptr<PacketSource_Base> pktsource,
const SockAddr& resolver,
const SockAddr& from,
llarp::OwnedBuffer buf);
/// set which dns mode we are in.
/// true for intercepting all queries. false for just .loki and .snode
void SetDNSMode(bool all_queries);
protected:
EventLoop_ptr m_Loop;
llarp::DnsConfig m_Config;
std::shared_ptr<I_Platform> m_Platform;
private:
const unsigned int m_NetIfIndex;
std::set<std::shared_ptr<Resolver_Base>, ComparePtr<std::shared_ptr<Resolver_Base>>>
m_OwnedResolvers;
std::set<std::weak_ptr<Resolver_Base>, CompareWeakPtr<Resolver_Base>> m_Resolvers;
std::vector<std::weak_ptr<PacketSource_Base>> m_PacketSources;
std::vector<std::shared_ptr<PacketSource_Base>> m_OwnedPacketSources;
};
} // namespace llarp::dns

@ -8,137 +8,132 @@
namespace llarp::dns
{
static auto logcat = log::Cat("dns");
static auto logcat = log::Cat("dns");
bool
SRVData::IsValid() const
{
// if target is of first two forms outlined above
if (target == "." or target.size() == 0)
bool SRVData::IsValid() const
{
return true;
// if target is of first two forms outlined above
if (target == "." or target.size() == 0)
{
return true;
}
// check target size is not absurd
if (target.size() > TARGET_MAX_SIZE)
{
log::warning(logcat, "SRVData target larger than max size ({})", TARGET_MAX_SIZE);
return false;
}
// does target end in .loki?
size_t pos = target.find(".loki");
if (pos != std::string::npos && pos == (target.size() - 5))
{
return true;
}
// does target end in .snode?
pos = target.find(".snode");
if (pos != std::string::npos && pos == (target.size() - 6))
{
return true;
}
// if we're here, target is invalid
log::warning(logcat, "SRVData invalid");
return false;
}
// check target size is not absurd
if (target.size() > TARGET_MAX_SIZE)
SRVTuple SRVData::toTuple() const
{
log::warning(logcat, "SRVData target larger than max size ({})", TARGET_MAX_SIZE);
return false;
return std::make_tuple(service_proto, priority, weight, port, target);
}
// does target end in .loki?
size_t pos = target.find(".loki");
if (pos != std::string::npos && pos == (target.size() - 5))
SRVData SRVData::fromTuple(SRVTuple tuple)
{
return true;
}
// does target end in .snode?
pos = target.find(".snode");
if (pos != std::string::npos && pos == (target.size() - 6))
{
return true;
}
// if we're here, target is invalid
log::warning(logcat, "SRVData invalid");
return false;
}
SRVTuple
SRVData::toTuple() const
{
return std::make_tuple(service_proto, priority, weight, port, target);
}
SRVData
SRVData::fromTuple(SRVTuple tuple)
{
SRVData s;
SRVData s;
std::tie(s.service_proto, s.priority, s.weight, s.port, s.target) = std::move(tuple);
std::tie(s.service_proto, s.priority, s.weight, s.port, s.target) = std::move(tuple);
return s;
}
bool
SRVData::fromString(std::string_view srvString)
{
log::debug(logcat, "SRVData::fromString(\"{}\")", srvString);
// split on spaces, discard trailing empty strings
auto splits = split(srvString, " ", false);
if (splits.size() != 5 && splits.size() != 4)
{
log::warning(logcat, "SRV record should have either 4 or 5 space-separated parts");
return false;
return s;
}
service_proto = splits[0];
if (not parse_int(splits[1], priority))
bool SRVData::fromString(std::string_view srvString)
{
log::warning(logcat, "SRV record failed to parse \"{}\" as uint16_t (priority)", splits[1]);
return false;
log::debug(logcat, "SRVData::fromString(\"{}\")", srvString);
// split on spaces, discard trailing empty strings
auto splits = split(srvString, " ", false);
if (splits.size() != 5 && splits.size() != 4)
{
log::warning(logcat, "SRV record should have either 4 or 5 space-separated parts");
return false;
}
service_proto = splits[0];
if (not parse_int(splits[1], priority))
{
log::warning(
logcat, "SRV record failed to parse \"{}\" as uint16_t (priority)", splits[1]);
return false;
}
if (not parse_int(splits[2], weight))
{
log::warning(
logcat, "SRV record failed to parse \"{}\" as uint16_t (weight)", splits[2]);
return false;
}
if (not parse_int(splits[3], port))
{
log::warning(logcat, "SRV record failed to parse \"{}\" as uint16_t (port)", splits[3]);
return false;
}
if (splits.size() == 5)
target = splits[4];
else
target = "";
return IsValid();
}
if (not parse_int(splits[2], weight))
std::string SRVData::bt_encode() const
{
log::warning(logcat, "SRV record failed to parse \"{}\" as uint16_t (weight)", splits[2]);
return false;
return oxenc::bt_serialize(toTuple());
}
if (not parse_int(splits[3], port))
bool SRVData::BDecode(llarp_buffer_t* buf)
{
log::warning(logcat, "SRV record failed to parse \"{}\" as uint16_t (port)", splits[3]);
return false;
byte_t* begin = buf->cur;
if (not bencode_discard(buf))
return false;
byte_t* end = buf->cur;
std::string_view srvString{
reinterpret_cast<char*>(begin), static_cast<std::size_t>(end - begin)};
try
{
SRVTuple tuple{};
oxenc::bt_deserialize(srvString, tuple);
*this = fromTuple(std::move(tuple));
return IsValid();
}
catch (const oxenc::bt_deserialize_invalid&)
{
return false;
};
}
if (splits.size() == 5)
target = splits[4];
else
target = "";
return IsValid();
}
std::string
SRVData::bt_encode() const
{
return oxenc::bt_serialize(toTuple());
}
bool
SRVData::BDecode(llarp_buffer_t* buf)
{
byte_t* begin = buf->cur;
if (not bencode_discard(buf))
return false;
byte_t* end = buf->cur;
std::string_view srvString{
reinterpret_cast<char*>(begin), static_cast<std::size_t>(end - begin)};
try
util::StatusObject SRVData::ExtractStatus() const
{
SRVTuple tuple{};
oxenc::bt_deserialize(srvString, tuple);
*this = fromTuple(std::move(tuple));
return IsValid();
return util::StatusObject{
{"proto", service_proto},
{"priority", priority},
{"weight", weight},
{"port", port},
{"target", target}};
}
catch (const oxenc::bt_deserialize_invalid&)
{
return false;
};
}
util::StatusObject
SRVData::ExtractStatus() const
{
return util::StatusObject{
{"proto", service_proto},
{"priority", priority},
{"weight", weight},
{"port", port},
{"target", target}};
}
} // namespace llarp::dns

@ -11,99 +11,89 @@
namespace llarp::dns
{
using SRVTuple = std::tuple<std::string, uint16_t, uint16_t, uint16_t, std::string>;
using SRVTuple = std::tuple<std::string, uint16_t, uint16_t, uint16_t, std::string>;
struct SRVData
{
static constexpr size_t TARGET_MAX_SIZE = 200;
std::string service_proto; // service and protocol may as well be together
uint16_t priority;
uint16_t weight;
uint16_t port;
// target string for the SRV record to point to
// options:
// empty - refer to query name
// dot - authoritative "no such service available"
// any other .loki or .snode - target is that .loki or .snode
std::string target;
// do some basic validation on the target string
// note: this is not a conclusive, regex solution,
// but rather some sanity/safety checks
bool
IsValid() const;
SRVTuple
toTuple() const;
auto
toTupleRef() const
{
return std::tie(service_proto, priority, weight, port, target);
}
/// so we can put SRVData in a std::set
bool
operator<(const SRVData& other) const
{
return toTupleRef() < other.toTupleRef();
}
bool
operator==(const SRVData& other) const
struct SRVData
{
return toTupleRef() == other.toTupleRef();
}
std::string
bt_encode() const;
bool
BDecode(llarp_buffer_t*);
util::StatusObject
ExtractStatus() const;
static SRVData
fromTuple(SRVTuple tuple);
/* bind-like formatted string for SRV records in config file
*
* format:
* srv=service.proto priority weight port target
*
* exactly one space character between parts.
*
* target can be empty, in which case the space after port should
* be omitted. if this is the case, the target is
* interpreted as the .loki or .snode of the current context.
*
* if target is not empty, it must be either
* - simply a full stop (dot/period) OR
* - a name within the .loki or .snode subdomains. a target
* specified in this manner must not end with a full stop.
*/
bool
fromString(std::string_view srvString);
};
static constexpr size_t TARGET_MAX_SIZE = 200;
std::string service_proto; // service and protocol may as well be together
uint16_t priority;
uint16_t weight;
uint16_t port;
// target string for the SRV record to point to
// options:
// empty - refer to query name
// dot - authoritative "no such service available"
// any other .loki or .snode - target is that .loki or .snode
std::string target;
// do some basic validation on the target string
// note: this is not a conclusive, regex solution,
// but rather some sanity/safety checks
bool IsValid() const;
SRVTuple toTuple() const;
auto toTupleRef() const
{
return std::tie(service_proto, priority, weight, port, target);
}
/// so we can put SRVData in a std::set
bool operator<(const SRVData& other) const
{
return toTupleRef() < other.toTupleRef();
}
bool operator==(const SRVData& other) const
{
return toTupleRef() == other.toTupleRef();
}
std::string bt_encode() const;
bool BDecode(llarp_buffer_t*);
util::StatusObject ExtractStatus() const;
static SRVData fromTuple(SRVTuple tuple);
/* bind-like formatted string for SRV records in config file
*
* format:
* srv=service.proto priority weight port target
*
* exactly one space character between parts.
*
* target can be empty, in which case the space after port should
* be omitted. if this is the case, the target is
* interpreted as the .loki or .snode of the current context.
*
* if target is not empty, it must be either
* - simply a full stop (dot/period) OR
* - a name within the .loki or .snode subdomains. a target
* specified in this manner must not end with a full stop.
*/
bool fromString(std::string_view srvString);
};
} // namespace llarp::dns
namespace std
{
template <>
struct hash<llarp::dns::SRVData>
{
size_t
operator()(const llarp::dns::SRVData& data) const
template <>
struct hash<llarp::dns::SRVData>
{
const std::hash<std::string> h_str{};
const std::hash<uint16_t> h_port{};
return h_str(data.service_proto) ^ (h_str(data.target) << 3) ^ (h_port(data.priority) << 5)
^ (h_port(data.weight) << 7) ^ (h_port(data.port) << 9);
}
};
size_t operator()(const llarp::dns::SRVData& data) const
{
const std::hash<std::string> h_str{};
const std::hash<uint16_t> h_port{};
return h_str(data.service_proto) ^ (h_str(data.target) << 3)
^ (h_port(data.priority) << 5) ^ (h_port(data.weight) << 7)
^ (h_port(data.port) << 9);
}
};
} // namespace std

@ -6,14 +6,12 @@ struct llarp_buffer_t;
namespace llarp::dns
{
using name_t = std::string;
using name_t = std::string;
/// decode name from buffer
bool
decode_name(llarp_buffer_t* buf, name_t& name);
/// decode name from buffer
bool decode_name(llarp_buffer_t* buf, name_t& name);
/// encode name to buffer
bool
encode_name(llarp_buffer_t* buf, const name_t& name);
/// encode name to buffer
bool encode_name(llarp_buffer_t* buf, const name_t& name);
} // namespace llarp::dns

@ -4,31 +4,28 @@
namespace llarp
{
void
EndpointBase::PutSRVRecord(dns::SRVData srv)
{
if (auto result = m_SRVRecords.insert(std::move(srv)); result.second)
void EndpointBase::PutSRVRecord(dns::SRVData srv)
{
SRVRecordsChanged();
if (auto result = m_SRVRecords.insert(std::move(srv)); result.second)
{
SRVRecordsChanged();
}
}
}
bool
EndpointBase::DelSRVRecordIf(std::function<bool(const dns::SRVData&)> filter)
{
if (util::erase_if(m_SRVRecords, filter) > 0)
bool EndpointBase::DelSRVRecordIf(std::function<bool(const dns::SRVData&)> filter)
{
SRVRecordsChanged();
return true;
if (util::erase_if(m_SRVRecords, filter) > 0)
{
SRVRecordsChanged();
return true;
}
return false;
}
return false;
}
std::set<dns::SRVData>
EndpointBase::SRVRecords() const
{
std::set<dns::SRVData> set;
set.insert(m_SRVRecords.begin(), m_SRVRecords.end());
return set;
}
std::set<dns::SRVData> EndpointBase::SRVRecords() const
{
std::set<dns::SRVData> set;
set.insert(m_SRVRecords.begin(), m_SRVRecords.end());
return set;
}
} // namespace llarp

@ -22,127 +22,112 @@
namespace llarp
{
namespace dns
{
class Server;
}
// TODO: add forward declaration of TunnelManager
// namespace link
// {
// class TunneLManager;
// }
class EndpointBase
{
std::unordered_set<dns::SRVData> m_SRVRecords;
public:
virtual ~EndpointBase() = default;
using AddressVariant_t = std::variant<service::Address, RouterID>;
struct SendStat
{
/// how many routing messages we sent to them
uint64_t messagesSend;
/// how many routing messages we got from them
uint64_t messagesRecv;
/// how many convos have we had to this guy total?
size_t numTotalConvos;
/// current estimated rtt
Duration_t estimatedRTT;
/// last time point when we sent a message to them
Duration_t lastSendAt;
/// last time point when we got a message from them
Duration_t lastRecvAt;
};
/// info about a quic mapping
struct QUICMappingInfo
namespace dns
{
/// srv data if it was provided
std::optional<dns::SRVData> srv;
/// address we are bound on
SockAddr localAddr;
/// the remote's lns name if we have one
std::optional<std::string> remoteName;
/// the remote's address
AddressVariant_t remoteAddr;
/// the remote's port we are connecting to
uint16_t remotePort;
};
class Server;
}
/// add an srv record to this endpoint's descriptor
void
PutSRVRecord(dns::SRVData srv);
// TODO: add forward declaration of TunnelManager
// namespace link
// {
// class TunneLManager;
// }
/// get dns serverr if we have on on this endpoint
virtual std::shared_ptr<dns::Server>
DNS() const
class EndpointBase
{
return nullptr;
std::unordered_set<dns::SRVData> m_SRVRecords;
public:
virtual ~EndpointBase() = default;
using AddressVariant_t = std::variant<service::Address, RouterID>;
struct SendStat
{
/// how many routing messages we sent to them
uint64_t messagesSend;
/// how many routing messages we got from them
uint64_t messagesRecv;
/// how many convos have we had to this guy total?
size_t numTotalConvos;
/// current estimated rtt
Duration_t estimatedRTT;
/// last time point when we sent a message to them
Duration_t lastSendAt;
/// last time point when we got a message from them
Duration_t lastRecvAt;
};
/// info about a quic mapping
struct QUICMappingInfo
{
/// srv data if it was provided
std::optional<dns::SRVData> srv;
/// address we are bound on
SockAddr localAddr;
/// the remote's lns name if we have one
std::optional<std::string> remoteName;
/// the remote's address
AddressVariant_t remoteAddr;
/// the remote's port we are connecting to
uint16_t remotePort;
};
/// add an srv record to this endpoint's descriptor
void PutSRVRecord(dns::SRVData srv);
/// get dns serverr if we have on on this endpoint
virtual std::shared_ptr<dns::Server> DNS() const
{
return nullptr;
};
/// called when srv data changes in some way
virtual void SRVRecordsChanged() = 0;
/// remove srv records from this endpoint that match a filter
/// for each srv record call it with filter, remove if filter returns true
/// return if we removed any srv records
bool DelSRVRecordIf(std::function<bool(const dns::SRVData&)> filter);
/// get copy of all srv records
std::set<dns::SRVData> SRVRecords() const;
/// get statistics about how much traffic we sent and recv'd to a remote endpoint
virtual std::optional<SendStat> GetStatFor(AddressVariant_t remote) const = 0;
/// list all remote endpoint addresses we have that are mapped
virtual std::unordered_set<AddressVariant_t> AllRemoteEndpoints() const = 0;
/// get our local address
virtual AddressVariant_t LocalAddress() const = 0;
virtual link::TunnelManager* GetQUICTunnel() = 0;
virtual std::optional<AddressVariant_t> GetEndpointWithConvoTag(
service::ConvoTag tag) const = 0;
virtual std::optional<service::ConvoTag> GetBestConvoTagFor(
AddressVariant_t addr) const = 0;
virtual bool EnsurePathTo(
AddressVariant_t addr,
std::function<void(std::optional<service::ConvoTag>)> hook,
llarp_time_t timeout) = 0;
virtual void lookup_name(std::string name, std::function<void(std::string, bool)> func) = 0;
virtual const EventLoop_ptr& Loop() = 0;
virtual bool send_to(service::ConvoTag tag, std::string payload) = 0;
/// lookup srv records async
virtual void LookupServiceAsync(
std::string name,
std::string service,
std::function<void(std::vector<dns::SRVData>)> resultHandler) = 0;
virtual void MarkAddressOutbound(service::Address remote) = 0;
};
/// called when srv data changes in some way
virtual void
SRVRecordsChanged() = 0;
/// remove srv records from this endpoint that match a filter
/// for each srv record call it with filter, remove if filter returns true
/// return if we removed any srv records
bool
DelSRVRecordIf(std::function<bool(const dns::SRVData&)> filter);
/// get copy of all srv records
std::set<dns::SRVData>
SRVRecords() const;
/// get statistics about how much traffic we sent and recv'd to a remote endpoint
virtual std::optional<SendStat>
GetStatFor(AddressVariant_t remote) const = 0;
/// list all remote endpoint addresses we have that are mapped
virtual std::unordered_set<AddressVariant_t>
AllRemoteEndpoints() const = 0;
/// get our local address
virtual AddressVariant_t
LocalAddress() const = 0;
virtual link::TunnelManager*
GetQUICTunnel() = 0;
virtual std::optional<AddressVariant_t>
GetEndpointWithConvoTag(service::ConvoTag tag) const = 0;
virtual std::optional<service::ConvoTag>
GetBestConvoTagFor(AddressVariant_t addr) const = 0;
virtual bool
EnsurePathTo(
AddressVariant_t addr,
std::function<void(std::optional<service::ConvoTag>)> hook,
llarp_time_t timeout) = 0;
virtual void
lookup_name(std::string name, std::function<void(std::string, bool)> func) = 0;
virtual const EventLoop_ptr&
Loop() = 0;
virtual bool
send_to(service::ConvoTag tag, std::string payload) = 0;
/// lookup srv records async
virtual void
LookupServiceAsync(
std::string name,
std::string service,
std::function<void(std::vector<dns::SRVData>)> resultHandler) = 0;
virtual void
MarkAddressOutbound(service::Address remote) = 0;
};
} // namespace llarp

@ -8,16 +8,14 @@
namespace llarp
{
EventLoop_ptr
EventLoop::create(size_t queueLength)
{
return std::make_shared<llarp::uv::Loop>(queueLength);
}
EventLoop_ptr EventLoop::create(size_t queueLength)
{
return std::make_shared<llarp::uv::Loop>(queueLength);
}
const net::Platform*
EventLoop::Net_ptr() const
{
return net::Platform::Default_ptr();
}
const net::Platform* EventLoop::Net_ptr() const
{
return net::Platform::Default_ptr();
}
} // namespace llarp

@ -17,269 +17,256 @@ using oxen::log::slns::source_location;
namespace uvw
{
class Loop;
class Loop;
}
namespace llarp
{
constexpr std::size_t event_loop_queue_size = 1024;
struct SockAddr;
struct UDPHandle;
static auto loop_cat = llarp::log::Cat("ev-loop");
template <typename... T>
void
loop_trace_log(
const log::logger_ptr& cat_logger,
[[maybe_unused]] const source_location& location,
[[maybe_unused]] fmt::format_string<T...> fmt,
[[maybe_unused]] T&&... args)
{
if (cat_logger)
cat_logger->log(
log::detail::spdlog_sloc(location), log::Level::trace, fmt, std::forward<T>(args)...);
}
namespace vpn
{
class NetworkInterface;
}
namespace net
{
class Platform;
struct IPPacket;
} // namespace net
/// distinct event loop waker upper; used to idempotently schedule a task on the next event loop
///
/// Created via EventLoop::make_waker(...).
class EventLoopWakeup
{
public:
/// Destructor: remove the task from the event loop task. (Note that destruction here only
/// initiates removal of the task from the underlying event loop: it is *possible* for the
/// callback to fire again if already triggered depending on the underlying implementation).
virtual ~EventLoopWakeup() = default;
/// trigger this task to run on the next event loop iteration; does nothing if already
/// triggered.
virtual void
Trigger() = 0;
};
/// holds a repeated task on the event loop; the task is removed on destruction
class EventLoopRepeater
{
public:
// Destructor: if the task has been started then it is removed from the event loop. Note
// that it is possible for a task to fire *after* destruction of this container;
// destruction only initiates removal of the periodic task.
virtual ~EventLoopRepeater() = default;
// Starts the repeater to call `task` every `every` period.
virtual void
start(llarp_time_t every, std::function<void()> task) = 0;
};
// this (nearly!) abstract base class
// is overriden for each platform
class EventLoop
{
public:
// Runs the event loop. This does not return until sometime after `stop()` is called (and so
// typically you want to run this in its own thread).
virtual void
run() = 0;
virtual bool
running() const = 0;
// Returns a current steady clock time value representing the current time with event loop tick
// granularity. That is, the value is typically only updated at the beginning of an event loop
// tick.
virtual llarp_time_t
time_now() const = 0;
// Calls a function/lambda/etc. If invoked from within the event loop itself this calls the
// given lambda immediately; otherwise it passes it to `call_soon()` to be queued to run at the
// next event loop iteration.
template <typename Callable>
void
call(Callable&& f)
constexpr std::size_t event_loop_queue_size = 1024;
struct SockAddr;
struct UDPHandle;
static auto loop_cat = llarp::log::Cat("ev-loop");
template <typename... T>
void loop_trace_log(
const log::logger_ptr& cat_logger,
[[maybe_unused]] const source_location& location,
[[maybe_unused]] fmt::format_string<T...> fmt,
[[maybe_unused]] T&&... args)
{
if (inEventLoop())
{
f();
wakeup();
}
else
call_soon(std::forward<Callable>(f));
if (cat_logger)
cat_logger->log(
log::detail::spdlog_sloc(location),
log::Level::trace,
fmt,
std::forward<T>(args)...);
}
// Queues a function to be called on the next event loop cycle and triggers it to be called as
// soon as possible; can be called from any thread. Note that, unlike `call()`, this queues the
// job even if called from the event loop thread itself and so you *usually* want to use
// `call()` instead.
virtual void
call_soon(std::function<void(void)> f) = 0;
// Adds a timer to the event loop to invoke the given callback after a delay.
virtual void
call_later(llarp_time_t delay_ms, std::function<void(void)> callback) = 0;
// Created a repeated timer that fires ever `repeat` time unit. Lifetime of the event
// is tied to `owner`: callbacks will be invoked so long as `owner` remains alive, but
// the first time it repeats after `owner` has been destroyed the internal timer object will
// be destroyed and no more callbacks will be invoked.
//
// Intended to be used as:
//
// loop->call_every(100ms, weak_from_this(), [this] { some_func(); });
//
// Alternative, when shared_from_this isn't available for a type, you can use a local member
// shared_ptr (or even create a simple one, for more fine-grained control) to tie the lifetime:
//
// m_keepalive = std::make_shared<int>(42);
// loop->call_every(100ms, m_keepalive, [this] { some_func(); });
//
template <typename Callable> // Templated so that the compiler can inline the call
void
call_every(llarp_time_t repeat, std::weak_ptr<void> owner, Callable f)
namespace vpn
{
auto repeater = make_repeater();
auto& r = *repeater; // reference *before* we pass ownership into the lambda below
r.start(
repeat,
[repeater = std::move(repeater), owner = std::move(owner), f = std::move(f)]() mutable {
if (auto ptr = owner.lock())
f();
else
repeater.reset(); // Trigger timer removal on tied object destruction (we should be
// the only thing holding the repeater; ideally it would be a
// unique_ptr, but std::function says nuh-uh).
});
class NetworkInterface;
}
/// Calls a function and synchronously obtains its return value. If called from within the
/// event loop, the function is called and returned immediately, otherwise a promise/future
/// is used with `call_soon` to block until the event loop comes around and calls the
/// function.
template <typename Callable, typename Ret = decltype(std::declval<Callable>()())>
Ret
call_get(Callable&& f, source_location src = source_location::current())
namespace net
{
class Platform;
struct IPPacket;
} // namespace net
/// distinct event loop waker upper; used to idempotently schedule a task on the next event loop
///
/// Created via EventLoop::make_waker(...).
class EventLoopWakeup
{
public:
/// Destructor: remove the task from the event loop task. (Note that destruction here only
/// initiates removal of the task from the underlying event loop: it is *possible* for the
/// callback to fire again if already triggered depending on the underlying implementation).
virtual ~EventLoopWakeup() = default;
/// trigger this task to run on the next event loop iteration; does nothing if already
/// triggered.
virtual void Trigger() = 0;
};
/// holds a repeated task on the event loop; the task is removed on destruction
class EventLoopRepeater
{
public:
// Destructor: if the task has been started then it is removed from the event loop. Note
// that it is possible for a task to fire *after* destruction of this container;
// destruction only initiates removal of the periodic task.
virtual ~EventLoopRepeater() = default;
// Starts the repeater to call `task` every `every` period.
virtual void start(llarp_time_t every, std::function<void()> task) = 0;
};
// this (nearly!) abstract base class
// is overriden for each platform
class EventLoop
{
if (inEventLoop())
{
loop_trace_log(loop_cat, src, "Event loop calling `{}`", src.function_name());
return f();
}
std::promise<Ret> prom;
auto fut = prom.get_future();
call_soon([&f, &prom] {
try
public:
// Runs the event loop. This does not return until sometime after `stop()` is called (and so
// typically you want to run this in its own thread).
virtual void run() = 0;
virtual bool running() const = 0;
// Returns a current steady clock time value representing the current time with event loop
// tick granularity. That is, the value is typically only updated at the beginning of an
// event loop tick.
virtual llarp_time_t time_now() const = 0;
// Calls a function/lambda/etc. If invoked from within the event loop itself this calls the
// given lambda immediately; otherwise it passes it to `call_soon()` to be queued to run at
// the next event loop iteration.
template <typename Callable>
void call(Callable&& f)
{
prom.set_value(f());
if (inEventLoop())
{
f();
wakeup();
}
else
call_soon(std::forward<Callable>(f));
}
catch (...)
// Queues a function to be called on the next event loop cycle and triggers it to be called
// as soon as possible; can be called from any thread. Note that, unlike `call()`, this
// queues the job even if called from the event loop thread itself and so you *usually* want
// to use `call()` instead.
virtual void call_soon(std::function<void(void)> f) = 0;
// Adds a timer to the event loop to invoke the given callback after a delay.
virtual void call_later(llarp_time_t delay_ms, std::function<void(void)> callback) = 0;
// Created a repeated timer that fires ever `repeat` time unit. Lifetime of the event
// is tied to `owner`: callbacks will be invoked so long as `owner` remains alive, but
// the first time it repeats after `owner` has been destroyed the internal timer object will
// be destroyed and no more callbacks will be invoked.
//
// Intended to be used as:
//
// loop->call_every(100ms, weak_from_this(), [this] { some_func(); });
//
// Alternative, when shared_from_this isn't available for a type, you can use a local member
// shared_ptr (or even create a simple one, for more fine-grained control) to tie the
// lifetime:
//
// m_keepalive = std::make_shared<int>(42);
// loop->call_every(100ms, m_keepalive, [this] { some_func(); });
//
template <typename Callable> // Templated so that the compiler can inline the call
void call_every(llarp_time_t repeat, std::weak_ptr<void> owner, Callable f)
{
prom.set_exception(std::current_exception());
auto repeater = make_repeater();
auto& r = *repeater; // reference *before* we pass ownership into the lambda below
r.start(
repeat,
[repeater = std::move(repeater),
owner = std::move(owner),
f = std::move(f)]() mutable {
if (auto ptr = owner.lock())
f();
else
repeater
.reset(); // Trigger timer removal on tied object destruction (we
// should be the only thing holding the repeater; ideally it
// would be a unique_ptr, but std::function says nuh-uh).
});
}
});
return fut.get();
}
// Wraps a lambda with a lambda that triggers it to be called via loop->call()
// when invoked. E.g.:
//
// auto x = loop->make_caller([] (int a) { std::cerr << a; });
// x(42);
// x(99);
//
// will schedule two calls of the inner lambda (with different arguments) in the event loop.
// Arguments are forwarded to the inner lambda (allowing moving arguments into it).
template <typename Callable>
auto
make_caller(Callable f)
{
return [this, f = std::move(f)](auto&&... args) {
if (inEventLoop())
return f(std::forward<decltype(args)>(args)...);
// This shared pointer in a pain in the ass but needed because this lambda is going into a
// std::function that only accepts copyable lambdas. I *want* to simply capture:
// args=std::make_tuple(std::forward<decltype(args)>(args)...) but that fails if any given
// arguments aren't copyable (because of std::function). Dammit.
auto args_tuple_ptr = std::make_shared<std::tuple<std::decay_t<decltype(args)>...>>(
std::forward<decltype(args)>(args)...);
call_soon([f, args = std::move(args_tuple_ptr)]() mutable {
// Moving away the tuple args here is okay because this lambda will only be invoked once
std::apply(f, std::move(*args));
});
};
}
/// Calls a function and synchronously obtains its return value. If called from within the
/// event loop, the function is called and returned immediately, otherwise a promise/future
/// is used with `call_soon` to block until the event loop comes around and calls the
/// function.
template <typename Callable, typename Ret = decltype(std::declval<Callable>()())>
Ret call_get(Callable&& f, source_location src = source_location::current())
{
if (inEventLoop())
{
loop_trace_log(loop_cat, src, "Event loop calling `{}`", src.function_name());
return f();
}
std::promise<Ret> prom;
auto fut = prom.get_future();
call_soon([&f, &prom] {
try
{
prom.set_value(f());
}
catch (...)
{
prom.set_exception(std::current_exception());
}
});
return fut.get();
}
virtual bool
add_network_interface(
std::shared_ptr<vpn::NetworkInterface> netif,
std::function<void(net::IPPacket)> packetHandler) = 0;
// Wraps a lambda with a lambda that triggers it to be called via loop->call()
// when invoked. E.g.:
//
// auto x = loop->make_caller([] (int a) { std::cerr << a; });
// x(42);
// x(99);
//
// will schedule two calls of the inner lambda (with different arguments) in the event loop.
// Arguments are forwarded to the inner lambda (allowing moving arguments into it).
template <typename Callable>
auto make_caller(Callable f)
{
return [this, f = std::move(f)](auto&&... args) {
if (inEventLoop())
return f(std::forward<decltype(args)>(args)...);
// This shared pointer in a pain in the ass but needed because this lambda is going
// into a std::function that only accepts copyable lambdas. I *want* to simply
// capture: args=std::make_tuple(std::forward<decltype(args)>(args)...) but that
// fails if any given arguments aren't copyable (because of std::function). Dammit.
auto args_tuple_ptr = std::make_shared<std::tuple<std::decay_t<decltype(args)>...>>(
std::forward<decltype(args)>(args)...);
call_soon([f, args = std::move(args_tuple_ptr)]() mutable {
// Moving away the tuple args here is okay because this lambda will only be
// invoked once
std::apply(f, std::move(*args));
});
};
}
virtual bool
add_ticker(std::function<void(void)> ticker) = 0;
virtual bool add_network_interface(
std::shared_ptr<vpn::NetworkInterface> netif,
std::function<void(net::IPPacket)> packetHandler) = 0;
virtual void
stop() = 0;
virtual bool add_ticker(std::function<void(void)> ticker) = 0;
virtual ~EventLoop() = default;
virtual void stop() = 0;
virtual const net::Platform*
Net_ptr() const;
virtual ~EventLoop() = default;
using UDPReceiveFunc = std::function<void(UDPHandle&, SockAddr src, llarp::OwnedBuffer buf)>;
virtual const net::Platform* Net_ptr() const;
// Constructs a UDP socket that can be used for sending and/or receiving
virtual std::shared_ptr<UDPHandle>
make_udp(UDPReceiveFunc on_recv) = 0;
using UDPReceiveFunc =
std::function<void(UDPHandle&, SockAddr src, llarp::OwnedBuffer buf)>;
/// Make a thread-safe event loop waker (an "async" in libuv terminology) on this event loop;
/// you can call `->Trigger()` on the returned shared pointer to fire the callback at the next
/// available event loop iteration. (Multiple Trigger calls invoked before the call is actually
/// made are coalesced into one call).
virtual std::shared_ptr<EventLoopWakeup>
make_waker(std::function<void()> callback) = 0;
// Constructs a UDP socket that can be used for sending and/or receiving
virtual std::shared_ptr<UDPHandle> make_udp(UDPReceiveFunc on_recv) = 0;
// Initializes a new repeated task object. Note that the task is not actually added to the event
// loop until you call start() on the returned object. Typically invoked via call_every.
virtual std::shared_ptr<EventLoopRepeater>
make_repeater() = 0;
/// Make a thread-safe event loop waker (an "async" in libuv terminology) on this event
/// loop; you can call `->Trigger()` on the returned shared pointer to fire the callback at
/// the next available event loop iteration. (Multiple Trigger calls invoked before the
/// call is actually made are coalesced into one call).
virtual std::shared_ptr<EventLoopWakeup> make_waker(std::function<void()> callback) = 0;
// Constructs and initializes a new default (libuv) event loop
static std::shared_ptr<EventLoop>
create(size_t queueLength = event_loop_queue_size);
// Initializes a new repeated task object. Note that the task is not actually added to the
// event loop until you call start() on the returned object. Typically invoked via
// call_every.
virtual std::shared_ptr<EventLoopRepeater> make_repeater() = 0;
// Returns true if called from within the event loop thread, false otherwise.
virtual bool
inEventLoop() const = 0;
// Constructs and initializes a new default (libuv) event loop
static std::shared_ptr<EventLoop> create(size_t queueLength = event_loop_queue_size);
// Returns the uvw::Loop *if* this event loop is backed by a uvw event loop (i.e. the default),
// nullptr otherwise. (This base class default always returns nullptr).
virtual std::shared_ptr<uvw::Loop>
MaybeGetUVWLoop()
{
return nullptr;
}
// Returns true if called from within the event loop thread, false otherwise.
virtual bool inEventLoop() const = 0;
// Returns the uvw::Loop *if* this event loop is backed by a uvw event loop (i.e. the
// default), nullptr otherwise. (This base class default always returns nullptr).
virtual std::shared_ptr<uvw::Loop> MaybeGetUVWLoop()
{
return nullptr;
}
// Triggers an event loop wakeup; use when something has been done that requires the event loop
// to wake up (e.g. adding to queues). This is called implicitly by call() and call_soon().
// Idempotent and thread-safe.
virtual void
wakeup() = 0;
};
// Triggers an event loop wakeup; use when something has been done that requires the event
// loop to wake up (e.g. adding to queues). This is called implicitly by call() and
// call_soon(). Idempotent and thread-safe.
virtual void wakeup() = 0;
};
using EventLoop_ptr = std::shared_ptr<EventLoop>;
using EventLoop_ptr = std::shared_ptr<EventLoop>;
} // namespace llarp

@ -12,373 +12,347 @@
namespace llarp::uv
{
std::shared_ptr<uvw::Loop>
Loop::MaybeGetUVWLoop()
{
return m_Impl;
}
class UVWakeup final : public EventLoopWakeup
{
std::shared_ptr<uvw::AsyncHandle> async;
public:
UVWakeup(uvw::Loop& loop, std::function<void()> callback)
: async{loop.resource<uvw::AsyncHandle>()}
std::shared_ptr<uvw::Loop> Loop::MaybeGetUVWLoop()
{
async->on<uvw::AsyncEvent>([f = std::move(callback)](auto&, auto&) { f(); });
return m_Impl;
}
void
Trigger() override
class UVWakeup final : public EventLoopWakeup
{
async->send();
}
~UVWakeup() override
std::shared_ptr<uvw::AsyncHandle> async;
public:
UVWakeup(uvw::Loop& loop, std::function<void()> callback)
: async{loop.resource<uvw::AsyncHandle>()}
{
async->on<uvw::AsyncEvent>([f = std::move(callback)](auto&, auto&) { f(); });
}
void Trigger() override
{
async->send();
}
~UVWakeup() override
{
async->close();
}
};
class UVRepeater final : public EventLoopRepeater
{
async->close();
}
};
std::shared_ptr<uvw::TimerHandle> timer;
class UVRepeater final : public EventLoopRepeater
{
std::shared_ptr<uvw::TimerHandle> timer;
public:
UVRepeater(uvw::Loop& loop) : timer{loop.resource<uvw::TimerHandle>()}
{}
public:
UVRepeater(uvw::Loop& loop) : timer{loop.resource<uvw::TimerHandle>()}
{}
void start(llarp_time_t every, std::function<void()> task) override
{
timer->start(every, every);
timer->on<uvw::TimerEvent>([task = std::move(task)](auto&, auto&) { task(); });
}
void
start(llarp_time_t every, std::function<void()> task) override
{
timer->start(every, every);
timer->on<uvw::TimerEvent>([task = std::move(task)](auto&, auto&) { task(); });
}
~UVRepeater() override
{
timer->stop();
}
};
~UVRepeater() override
struct UDPHandle final : llarp::UDPHandle
{
timer->stop();
}
};
UDPHandle(uvw::Loop& loop, ReceiveFunc rf);
struct UDPHandle final : llarp::UDPHandle
{
UDPHandle(uvw::Loop& loop, ReceiveFunc rf);
bool listen(const SockAddr& addr) override;
bool
listen(const SockAddr& addr) override;
bool send(const SockAddr& dest, const llarp_buffer_t& buf) override;
bool
send(const SockAddr& dest, const llarp_buffer_t& buf) override;
std::optional<SockAddr>
LocalAddr() const override
{
if (auto addr = handle->sock<uvw::IPv4>(); not addr.ip.empty())
return SockAddr{addr.ip, huint16_t{static_cast<uint16_t>(addr.port)}};
if (auto addr = handle->sock<uvw::IPv6>(); not addr.ip.empty())
return SockAddr{addr.ip, huint16_t{static_cast<uint16_t>(addr.port)}};
return std::nullopt;
}
std::optional<SockAddr> LocalAddr() const override
{
if (auto addr = handle->sock<uvw::IPv4>(); not addr.ip.empty())
return SockAddr{addr.ip, huint16_t{static_cast<uint16_t>(addr.port)}};
if (auto addr = handle->sock<uvw::IPv6>(); not addr.ip.empty())
return SockAddr{addr.ip, huint16_t{static_cast<uint16_t>(addr.port)}};
return std::nullopt;
}
std::optional<int>
file_descriptor() override
{
std::optional<int> file_descriptor() override
{
#ifndef _WIN32
if (int fd = handle->fd(); fd >= 0)
return fd;
if (int fd = handle->fd(); fd >= 0)
return fd;
#endif
return std::nullopt;
}
return std::nullopt;
}
void
close() override;
void close() override;
~UDPHandle() override;
~UDPHandle() override;
private:
std::shared_ptr<uvw::UDPHandle> handle;
private:
std::shared_ptr<uvw::UDPHandle> handle;
void
reset_handle(uvw::Loop& loop);
};
void reset_handle(uvw::Loop& loop);
};
void
Loop::FlushLogic()
{
llarp::LogTrace("Loop::FlushLogic() start");
while (not m_LogicCalls.empty())
void Loop::FlushLogic()
{
auto f = m_LogicCalls.popFront();
f();
llarp::LogTrace("Loop::FlushLogic() start");
while (not m_LogicCalls.empty())
{
auto f = m_LogicCalls.popFront();
f();
}
llarp::LogTrace("Loop::FlushLogic() end");
}
llarp::LogTrace("Loop::FlushLogic() end");
}
void
Loop::tick_event_loop()
{
llarp::LogTrace("ticking event loop.");
FlushLogic();
}
void Loop::tick_event_loop()
{
llarp::LogTrace("ticking event loop.");
FlushLogic();
}
Loop::Loop(size_t queue_size) : llarp::EventLoop{}, m_LogicCalls{queue_size}
{
if (!(m_Impl = uvw::Loop::create()))
throw std::runtime_error{"Failed to construct libuv loop"};
Loop::Loop(size_t queue_size) : llarp::EventLoop{}, m_LogicCalls{queue_size}
{
if (!(m_Impl = uvw::Loop::create()))
throw std::runtime_error{"Failed to construct libuv loop"};
#ifdef LOKINET_DEBUG
last_time = 0;
loop_run_count = 0;
last_time = 0;
loop_run_count = 0;
#endif
#ifndef _WIN32
signal(SIGPIPE, SIG_IGN);
signal(SIGPIPE, SIG_IGN);
#endif
m_Run.store(true);
m_nextID.store(0);
if (!(m_WakeUp = m_Impl->resource<uvw::AsyncHandle>()))
throw std::runtime_error{"Failed to create libuv async"};
m_WakeUp->on<uvw::AsyncEvent>([this](const auto&, auto&) { tick_event_loop(); });
}
bool
Loop::running() const
{
return m_Run.load();
}
void
Loop::run()
{
llarp::LogTrace("Loop::run_loop()");
m_EventLoopThreadID = std::this_thread::get_id();
m_Impl->run();
m_Impl->close();
m_Impl.reset();
llarp::LogInfo("we have stopped");
}
void
Loop::wakeup()
{
m_WakeUp->send();
}
std::shared_ptr<llarp::UDPHandle>
Loop::make_udp(UDPReceiveFunc on_recv)
{
return std::static_pointer_cast<llarp::UDPHandle>(
std::make_shared<llarp::uv::UDPHandle>(*m_Impl, std::move(on_recv)));
}
static void
setup_oneshot_timer(uvw::Loop& loop, llarp_time_t delay, std::function<void()> callback)
{
auto timer = loop.resource<uvw::TimerHandle>();
timer->on<uvw::TimerEvent>([f = std::move(callback)](const auto&, auto& timer) {
f();
timer.stop();
timer.close();
});
timer->start(delay, 0ms);
}
void
Loop::call_later(llarp_time_t delay_ms, std::function<void(void)> callback)
{
llarp::LogTrace("Loop::call_after_delay()");
m_Run.store(true);
m_nextID.store(0);
if (!(m_WakeUp = m_Impl->resource<uvw::AsyncHandle>()))
throw std::runtime_error{"Failed to create libuv async"};
m_WakeUp->on<uvw::AsyncEvent>([this](const auto&, auto&) { tick_event_loop(); });
}
bool Loop::running() const
{
return m_Run.load();
}
void Loop::run()
{
llarp::LogTrace("Loop::run_loop()");
m_EventLoopThreadID = std::this_thread::get_id();
m_Impl->run();
m_Impl->close();
m_Impl.reset();
llarp::LogInfo("we have stopped");
}
void Loop::wakeup()
{
m_WakeUp->send();
}
std::shared_ptr<llarp::UDPHandle> Loop::make_udp(UDPReceiveFunc on_recv)
{
return std::static_pointer_cast<llarp::UDPHandle>(
std::make_shared<llarp::uv::UDPHandle>(*m_Impl, std::move(on_recv)));
}
static void setup_oneshot_timer(
uvw::Loop& loop, llarp_time_t delay, std::function<void()> callback)
{
auto timer = loop.resource<uvw::TimerHandle>();
timer->on<uvw::TimerEvent>([f = std::move(callback)](const auto&, auto& timer) {
f();
timer.stop();
timer.close();
});
timer->start(delay, 0ms);
}
void Loop::call_later(llarp_time_t delay_ms, std::function<void(void)> callback)
{
llarp::LogTrace("Loop::call_after_delay()");
#ifdef TESTNET_SPEED
delay_ms *= TESTNET_SPEED;
delay_ms *= TESTNET_SPEED;
#endif
if (inEventLoop())
setup_oneshot_timer(*m_Impl, delay_ms, std::move(callback));
else
{
call_soon([this, f = std::move(callback), target_time = time_now() + delay_ms] {
// Recalculate delay because it may have taken some time to get ourselves into the logic
// thread
auto updated_delay = target_time - time_now();
if (updated_delay <= 0ms)
f(); // Timer already expired!
if (inEventLoop())
setup_oneshot_timer(*m_Impl, delay_ms, std::move(callback));
else
setup_oneshot_timer(*m_Impl, updated_delay, std::move(f));
});
{
call_soon([this, f = std::move(callback), target_time = time_now() + delay_ms] {
// Recalculate delay because it may have taken some time to get ourselves into the
// logic thread
auto updated_delay = target_time - time_now();
if (updated_delay <= 0ms)
f(); // Timer already expired!
else
setup_oneshot_timer(*m_Impl, updated_delay, std::move(f));
});
}
}
void Loop::stop()
{
if (m_Run)
{
if (not inEventLoop())
return call_soon([this] { stop(); });
llarp::LogInfo("stopping event loop");
m_Impl->walk([](auto&& handle) {
if constexpr (!std::is_pointer_v<std::remove_reference_t<decltype(handle)>>)
handle.close();
});
llarp::LogDebug("Closed all handles, stopping the loop");
m_Impl->stop();
m_Run.store(false);
}
}
}
void
Loop::stop()
{
if (m_Run)
bool Loop::add_ticker(std::function<void(void)> func)
{
if (not inEventLoop())
return call_soon([this] { stop(); });
llarp::LogInfo("stopping event loop");
m_Impl->walk([](auto&& handle) {
if constexpr (!std::is_pointer_v<std::remove_reference_t<decltype(handle)>>)
handle.close();
});
llarp::LogDebug("Closed all handles, stopping the loop");
m_Impl->stop();
m_Run.store(false);
auto check = m_Impl->resource<uvw::CheckHandle>();
check->on<uvw::CheckEvent>([f = std::move(func)](auto&, auto&) { f(); });
check->start();
return true;
}
}
bool
Loop::add_ticker(std::function<void(void)> func)
{
auto check = m_Impl->resource<uvw::CheckHandle>();
check->on<uvw::CheckEvent>([f = std::move(func)](auto&, auto&) { f(); });
check->start();
return true;
}
bool
Loop::add_network_interface(
std::shared_ptr<llarp::vpn::NetworkInterface> netif,
std::function<void(llarp::net::IPPacket)> handler)
{
bool Loop::add_network_interface(
std::shared_ptr<llarp::vpn::NetworkInterface> netif,
std::function<void(llarp::net::IPPacket)> handler)
{
#ifdef __linux__
using event_t = uvw::PollEvent;
auto handle = m_Impl->resource<uvw::PollHandle>(netif->PollFD());
using event_t = uvw::PollEvent;
auto handle = m_Impl->resource<uvw::PollHandle>(netif->PollFD());
#else
// we use a uv_prepare_t because it fires before blocking for new io events unconditionally
// we want to match what linux does, using a uv_check_t does not suffice as the order of
// operations is not what we need.
using event_t = uvw::PrepareEvent;
auto handle = m_Impl->resource<uvw::PrepareHandle>();
// we use a uv_prepare_t because it fires before blocking for new io events unconditionally
// we want to match what linux does, using a uv_check_t does not suffice as the order of
// operations is not what we need.
using event_t = uvw::PrepareEvent;
auto handle = m_Impl->resource<uvw::PrepareHandle>();
#endif
if (!handle)
return false;
handle->on<event_t>([netif = std::move(netif), handler = std::move(handler)](
const event_t&, [[maybe_unused]] auto& handle) {
for (auto pkt = netif->ReadNextPacket(); true; pkt = netif->ReadNextPacket())
{
if (pkt.empty())
return;
if (handler)
handler(std::move(pkt));
// on windows/apple, vpn packet io does not happen as an io action that wakes up the event
// loop thus, we must manually wake up the event loop when we get a packet on our interface.
// on linux/android this is a nop
netif->MaybeWakeUpperLayers();
}
});
if (!handle)
return false;
handle->on<event_t>([netif = std::move(netif), handler = std::move(handler)](
const event_t&, [[maybe_unused]] auto& handle) {
for (auto pkt = netif->ReadNextPacket(); true; pkt = netif->ReadNextPacket())
{
if (pkt.empty())
return;
if (handler)
handler(std::move(pkt));
// on windows/apple, vpn packet io does not happen as an io action that wakes up the
// event loop thus, we must manually wake up the event loop when we get a packet on
// our interface. on linux/android this is a nop
netif->MaybeWakeUpperLayers();
}
});
#ifdef __linux__
handle->start(uvw::PollHandle::Event::READABLE);
handle->start(uvw::PollHandle::Event::READABLE);
#else
handle->start();
handle->start();
#endif
return true;
}
return true;
}
void Loop::call_soon(std::function<void(void)> f)
{
if (not m_EventLoopThreadID.has_value())
{
m_LogicCalls.tryPushBack(f);
m_WakeUp->send();
return;
}
if (inEventLoop() and m_LogicCalls.full())
{
FlushLogic();
}
m_LogicCalls.pushBack(f);
m_WakeUp->send();
}
// Sets `handle` to a new uvw UDP handle, first initiating a close and then disowning the handle
// if already set, allocating the resource, and setting the receive event on it.
void UDPHandle::reset_handle(uvw::Loop& loop)
{
if (handle)
handle->close();
handle = loop.resource<uvw::UDPHandle>();
handle->on<uvw::UDPDataEvent>([this](auto& event, auto& /*handle*/) {
on_recv(
*this,
SockAddr{event.sender.ip, huint16_t{static_cast<uint16_t>(event.sender.port)}},
OwnedBuffer{std::move(event.data), event.length});
});
}
llarp::uv::UDPHandle::UDPHandle(uvw::Loop& loop, ReceiveFunc rf)
: llarp::UDPHandle{std::move(rf)}
{
reset_handle(loop);
}
bool UDPHandle::listen(const SockAddr& addr)
{
if (handle->active())
reset_handle(handle->loop());
auto err = handle->on<uvw::ErrorEvent>([addr](auto& event, auto&) {
throw llarp::util::bind_socket_error{
fmt::format("failed to bind udp socket on {}: {}", addr, event.what())};
});
handle->bind(*static_cast<const sockaddr*>(addr));
handle->recv();
handle->erase(err);
return true;
}
bool UDPHandle::send(const SockAddr& to, const llarp_buffer_t& buf)
{
return handle->trySend(
*static_cast<const sockaddr*>(to),
const_cast<char*>(reinterpret_cast<const char*>(buf.base)),
buf.sz)
>= 0;
}
void UDPHandle::close()
{
handle->close();
handle.reset();
}
UDPHandle::~UDPHandle()
{
close();
}
std::shared_ptr<llarp::EventLoopWakeup> Loop::make_waker(std::function<void()> callback)
{
return std::static_pointer_cast<llarp::EventLoopWakeup>(
std::make_shared<UVWakeup>(*m_Impl, std::move(callback)));
}
void
Loop::call_soon(std::function<void(void)> f)
{
if (not m_EventLoopThreadID.has_value())
std::shared_ptr<EventLoopRepeater> Loop::make_repeater()
{
m_LogicCalls.tryPushBack(f);
m_WakeUp->send();
return;
return std::static_pointer_cast<EventLoopRepeater>(std::make_shared<UVRepeater>(*m_Impl));
}
if (inEventLoop() and m_LogicCalls.full())
bool Loop::inEventLoop() const
{
FlushLogic();
if (m_EventLoopThreadID)
return *m_EventLoopThreadID == std::this_thread::get_id();
// assume we are in it because we haven't started up yet
return true;
}
m_LogicCalls.pushBack(f);
m_WakeUp->send();
}
// Sets `handle` to a new uvw UDP handle, first initiating a close and then disowning the handle
// if already set, allocating the resource, and setting the receive event on it.
void
UDPHandle::reset_handle(uvw::Loop& loop)
{
if (handle)
handle->close();
handle = loop.resource<uvw::UDPHandle>();
handle->on<uvw::UDPDataEvent>([this](auto& event, auto& /*handle*/) {
on_recv(
*this,
SockAddr{event.sender.ip, huint16_t{static_cast<uint16_t>(event.sender.port)}},
OwnedBuffer{std::move(event.data), event.length});
});
}
llarp::uv::UDPHandle::UDPHandle(uvw::Loop& loop, ReceiveFunc rf) : llarp::UDPHandle{std::move(rf)}
{
reset_handle(loop);
}
bool
UDPHandle::listen(const SockAddr& addr)
{
if (handle->active())
reset_handle(handle->loop());
auto err = handle->on<uvw::ErrorEvent>([addr](auto& event, auto&) {
throw llarp::util::bind_socket_error{
fmt::format("failed to bind udp socket on {}: {}", addr, event.what())};
});
handle->bind(*static_cast<const sockaddr*>(addr));
handle->recv();
handle->erase(err);
return true;
}
bool
UDPHandle::send(const SockAddr& to, const llarp_buffer_t& buf)
{
return handle->trySend(
*static_cast<const sockaddr*>(to),
const_cast<char*>(reinterpret_cast<const char*>(buf.base)),
buf.sz)
>= 0;
}
void
UDPHandle::close()
{
handle->close();
handle.reset();
}
UDPHandle::~UDPHandle()
{
close();
}
std::shared_ptr<llarp::EventLoopWakeup>
Loop::make_waker(std::function<void()> callback)
{
return std::static_pointer_cast<llarp::EventLoopWakeup>(
std::make_shared<UVWakeup>(*m_Impl, std::move(callback)));
}
std::shared_ptr<EventLoopRepeater>
Loop::make_repeater()
{
return std::static_pointer_cast<EventLoopRepeater>(std::make_shared<UVRepeater>(*m_Impl));
}
bool
Loop::inEventLoop() const
{
if (m_EventLoopThreadID)
return *m_EventLoopThreadID == std::this_thread::get_id();
// assume we are in it because we haven't started up yet
return true;
}
} // namespace llarp::uv

@ -12,88 +12,72 @@
namespace llarp::uv
{
class UVWakeup;
class UVRepeater;
class UVWakeup;
class UVRepeater;
class Loop : public llarp::EventLoop
{
public:
using Callback = std::function<void()>;
class Loop : public llarp::EventLoop
{
public:
using Callback = std::function<void()>;
Loop(size_t queue_size);
Loop(size_t queue_size);
virtual void
run() override;
virtual void run() override;
bool
running() const override;
bool running() const override;
llarp_time_t
time_now() const override
{
return m_Impl->now();
}
llarp_time_t time_now() const override
{
return m_Impl->now();
}
void
call_later(llarp_time_t delay_ms, std::function<void(void)> callback) override;
void call_later(llarp_time_t delay_ms, std::function<void(void)> callback) override;
void
tick_event_loop();
void tick_event_loop();
void
stop() override;
void stop() override;
bool
add_ticker(std::function<void(void)> ticker) override;
bool add_ticker(std::function<void(void)> ticker) override;
bool
add_network_interface(
std::shared_ptr<llarp::vpn::NetworkInterface> netif,
std::function<void(llarp::net::IPPacket)> handler) override;
bool add_network_interface(
std::shared_ptr<llarp::vpn::NetworkInterface> netif,
std::function<void(llarp::net::IPPacket)> handler) override;
void
call_soon(std::function<void(void)> f) override;
void call_soon(std::function<void(void)> f) override;
std::shared_ptr<llarp::EventLoopWakeup>
make_waker(std::function<void()> callback) override;
std::shared_ptr<llarp::EventLoopWakeup> make_waker(std::function<void()> callback) override;
std::shared_ptr<EventLoopRepeater>
make_repeater() override;
std::shared_ptr<EventLoopRepeater> make_repeater() override;
virtual std::shared_ptr<llarp::UDPHandle>
make_udp(UDPReceiveFunc on_recv) override;
virtual std::shared_ptr<llarp::UDPHandle> make_udp(UDPReceiveFunc on_recv) override;
void
FlushLogic();
void FlushLogic();
std::shared_ptr<uvw::Loop>
MaybeGetUVWLoop() override;
std::shared_ptr<uvw::Loop> MaybeGetUVWLoop() override;
bool
inEventLoop() const override;
bool inEventLoop() const override;
protected:
std::shared_ptr<uvw::Loop> m_Impl;
std::optional<std::thread::id> m_EventLoopThreadID;
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)>>;
AtomicQueue_t m_LogicCalls;
private:
std::shared_ptr<uvw::AsyncHandle> m_WakeUp;
std::atomic<bool> m_Run;
using AtomicQueue_t = llarp::thread::Queue<std::function<void(void)>>;
AtomicQueue_t m_LogicCalls;
#ifdef LOKINET_DEBUG
uint64_t last_time;
uint64_t loop_run_count;
uint64_t last_time;
uint64_t loop_run_count;
#endif
std::atomic<uint32_t> m_nextID;
std::atomic<uint32_t> m_nextID;
std::map<uint32_t, Callback> m_pendingCalls;
std::map<uint32_t, Callback> m_pendingCalls;
std::unordered_map<int, std::shared_ptr<uvw::PollHandle>> m_Polls;
std::unordered_map<int, std::shared_ptr<uvw::PollHandle>> m_Polls;
void
wakeup() override;
};
void wakeup() override;
};
} // namespace llarp::uv

@ -3,51 +3,46 @@
namespace llarp
{
// Base type for UDP handling; constructed via EventLoop::make_udp().
struct UDPHandle
{
using ReceiveFunc = EventLoop::UDPReceiveFunc;
// Starts listening for incoming UDP packets on the given address. Returns true on success,
// false if the address could not be bound. If you send without calling this first then the
// socket will bind to a random high port on 0.0.0.0 (the "all addresses" address).
virtual bool
listen(const SockAddr& addr) = 0;
// Sends a packet to the given recipient, immediately. Returns true if the send succeeded,
// false it could not be performed (either because of error, or because it would have blocked).
// If listen hasn't been called then a random IP/port will be used.
virtual bool
send(const SockAddr& dest, const llarp_buffer_t& buf) = 0;
// Closes the listening UDP socket (if opened); this is typically called (automatically) during
// destruction. Does nothing if the UDP socket is already closed.
virtual void
close() = 0;
// Returns the file descriptor of the socket, if available. This generally exists only after
// listen() has been called, and never exists on Windows.
virtual std::optional<int>
file_descriptor()
// Base type for UDP handling; constructed via EventLoop::make_udp().
struct UDPHandle
{
return std::nullopt;
}
/// returns the local address we are bound on
virtual std::optional<SockAddr>
LocalAddr() const = 0;
// Base class destructor
virtual ~UDPHandle() = default;
protected:
explicit UDPHandle(ReceiveFunc on_recv) : on_recv{std::move(on_recv)}
{
// It makes no sense at all to use this with a null receive function:
assert(this->on_recv);
}
// Callback to invoke when data is received
ReceiveFunc on_recv;
};
using ReceiveFunc = EventLoop::UDPReceiveFunc;
// Starts listening for incoming UDP packets on the given address. Returns true on success,
// false if the address could not be bound. If you send without calling this first then the
// socket will bind to a random high port on 0.0.0.0 (the "all addresses" address).
virtual bool listen(const SockAddr& addr) = 0;
// Sends a packet to the given recipient, immediately. Returns true if the send succeeded,
// false it could not be performed (either because of error, or because it would have
// blocked). If listen hasn't been called then a random IP/port will be used.
virtual bool send(const SockAddr& dest, const llarp_buffer_t& buf) = 0;
// Closes the listening UDP socket (if opened); this is typically called (automatically)
// during destruction. Does nothing if the UDP socket is already closed.
virtual void close() = 0;
// Returns the file descriptor of the socket, if available. This generally exists only
// after listen() has been called, and never exists on Windows.
virtual std::optional<int> file_descriptor()
{
return std::nullopt;
}
/// returns the local address we are bound on
virtual std::optional<SockAddr> LocalAddr() const = 0;
// Base class destructor
virtual ~UDPHandle() = default;
protected:
explicit UDPHandle(ReceiveFunc on_recv) : on_recv{std::move(on_recv)}
{
// It makes no sense at all to use this with a null receive function:
assert(this->on_recv);
}
// Callback to invoke when data is received
ReceiveFunc on_recv;
};
} // namespace llarp

@ -5,121 +5,113 @@
namespace llarp::exit
{
Context::Context(Router* r) : router(r)
{}
Context::~Context() = default;
Context::Context(Router* r) : router(r)
{}
Context::~Context() = default;
void
Context::Tick(llarp_time_t now)
{
void Context::Tick(llarp_time_t now)
{
auto itr = _exits.begin();
while (itr != _exits.end())
{
itr->second->Tick(now);
++itr;
}
{
auto itr = _exits.begin();
while (itr != _exits.end())
{
itr->second->Tick(now);
++itr;
}
}
{
auto itr = _closed.begin();
while (itr != _closed.end())
{
if ((*itr)->ShouldRemove())
itr = _closed.erase(itr);
else
++itr;
}
}
}
{
auto itr = _closed.begin();
while (itr != _closed.end())
{
if ((*itr)->ShouldRemove())
itr = _closed.erase(itr);
else
++itr;
}
}
}
void
Context::stop()
{
auto itr = _exits.begin();
while (itr != _exits.end())
void Context::stop()
{
itr->second->Stop();
_closed.emplace_back(std::move(itr->second));
itr = _exits.erase(itr);
auto itr = _exits.begin();
while (itr != _exits.end())
{
itr->second->Stop();
_closed.emplace_back(std::move(itr->second));
itr = _exits.erase(itr);
}
}
}
util::StatusObject
Context::ExtractStatus() const
{
util::StatusObject obj{};
auto itr = _exits.begin();
while (itr != _exits.end())
util::StatusObject Context::ExtractStatus() const
{
obj[itr->first] = itr->second->ExtractStatus();
++itr;
util::StatusObject obj{};
auto itr = _exits.begin();
while (itr != _exits.end())
{
obj[itr->first] = itr->second->ExtractStatus();
++itr;
}
return obj;
}
return obj;
}
void
Context::calculate_exit_traffic(TrafficStats& stats)
{
auto itr = _exits.begin();
while (itr != _exits.end())
void Context::calculate_exit_traffic(TrafficStats& stats)
{
itr->second->CalculateTrafficStats(stats);
++itr;
auto itr = _exits.begin();
while (itr != _exits.end())
{
itr->second->CalculateTrafficStats(stats);
++itr;
}
}
}
exit::Endpoint*
Context::find_endpoint_for_path(const PathID_t& path) const
{
auto itr = _exits.begin();
while (itr != _exits.end())
exit::Endpoint* Context::find_endpoint_for_path(const PathID_t& path) const
{
auto ep = itr->second->FindEndpointByPath(path);
if (ep)
return ep;
++itr;
auto itr = _exits.begin();
while (itr != _exits.end())
{
auto ep = itr->second->FindEndpointByPath(path);
if (ep)
return ep;
++itr;
}
return nullptr;
}
return nullptr;
}
bool
Context::obtain_new_exit(const PubKey& pk, const PathID_t& path, bool permitInternet)
{
auto itr = _exits.begin();
while (itr != _exits.end())
bool Context::obtain_new_exit(const PubKey& pk, const PathID_t& path, bool permitInternet)
{
if (itr->second->AllocateNewExit(pk, path, permitInternet))
return true;
++itr;
auto itr = _exits.begin();
while (itr != _exits.end())
{
if (itr->second->AllocateNewExit(pk, path, permitInternet))
return true;
++itr;
}
return false;
}
return false;
}
std::shared_ptr<handlers::ExitEndpoint>
Context::get_exit_endpoint(std::string name) const
{
if (auto itr = _exits.find(name); itr != _exits.end())
std::shared_ptr<handlers::ExitEndpoint> Context::get_exit_endpoint(std::string name) const
{
return itr->second;
if (auto itr = _exits.find(name); itr != _exits.end())
{
return itr->second;
}
return nullptr;
}
return nullptr;
}
void
Context::add_exit_endpoint(
const std::string& name, const NetworkConfig& networkConfig, const DnsConfig& dnsConfig)
{
if (_exits.find(name) != _exits.end())
throw std::invalid_argument{fmt::format("An exit with name {} already exists", name)};
void Context::add_exit_endpoint(
const std::string& name, const NetworkConfig& networkConfig, const DnsConfig& dnsConfig)
{
if (_exits.find(name) != _exits.end())
throw std::invalid_argument{fmt::format("An exit with name {} already exists", name)};
auto endpoint = std::make_unique<handlers::ExitEndpoint>(name, router);
endpoint->Configure(networkConfig, dnsConfig);
auto endpoint = std::make_unique<handlers::ExitEndpoint>(name, router);
endpoint->Configure(networkConfig, dnsConfig);
// add endpoint
if (!endpoint->Start())
throw std::runtime_error{fmt::format("Failed to start endpoint {}", name)};
// add endpoint
if (!endpoint->Start())
throw std::runtime_error{fmt::format("Failed to start endpoint {}", name)};
_exits.emplace(name, std::move(endpoint));
}
_exits.emplace(name, std::move(endpoint));
}
} // namespace llarp::exit

@ -8,47 +8,40 @@
namespace llarp::exit
{
/// owner of all the exit endpoints
struct Context
{
Context(Router* r);
~Context();
/// owner of all the exit endpoints
struct Context
{
Context(Router* r);
~Context();
void
Tick(llarp_time_t now);
void Tick(llarp_time_t now);
void
clear_all_endpoints();
void clear_all_endpoints();
util::StatusObject
ExtractStatus() const;
util::StatusObject ExtractStatus() const;
/// send close to all exit sessions and remove all sessions
void
stop();
/// send close to all exit sessions and remove all sessions
void stop();
void
add_exit_endpoint(
const std::string& name, const NetworkConfig& networkConfig, const DnsConfig& dnsConfig);
void add_exit_endpoint(
const std::string& name,
const NetworkConfig& networkConfig,
const DnsConfig& dnsConfig);
bool
obtain_new_exit(const PubKey& remote, const PathID_t& path, bool permitInternet);
bool obtain_new_exit(const PubKey& remote, const PathID_t& path, bool permitInternet);
exit::Endpoint*
find_endpoint_for_path(const PathID_t& path) const;
exit::Endpoint* find_endpoint_for_path(const PathID_t& path) const;
/// calculate (pk, tx, rx) for all exit traffic
using TrafficStats = std::unordered_map<PubKey, std::pair<uint64_t, uint64_t>>;
/// calculate (pk, tx, rx) for all exit traffic
using TrafficStats = std::unordered_map<PubKey, std::pair<uint64_t, uint64_t>>;
void
calculate_exit_traffic(TrafficStats& stats);
void calculate_exit_traffic(TrafficStats& stats);
std::shared_ptr<handlers::ExitEndpoint>
get_exit_endpoint(std::string name) const;
std::shared_ptr<handlers::ExitEndpoint> get_exit_endpoint(std::string name) const;
private:
Router* router;
std::unordered_map<std::string, std::shared_ptr<handlers::ExitEndpoint>> _exits;
std::list<std::shared_ptr<handlers::ExitEndpoint>> _closed;
};
private:
Router* router;
std::unordered_map<std::string, std::shared_ptr<handlers::ExitEndpoint>> _exits;
std::list<std::shared_ptr<handlers::ExitEndpoint>> _closed;
};
} // namespace llarp::exit

@ -6,239 +6,230 @@
namespace llarp::exit
{
Endpoint::Endpoint(
const llarp::PubKey& remoteIdent,
const std::shared_ptr<llarp::path::AbstractHopHandler>& beginPath,
bool rewriteIP,
huint128_t ip,
llarp::handlers::ExitEndpoint* parent)
: createdAt{parent->Now()}
, parent{parent}
, remote_signkey{remoteIdent}
, current_path{beginPath}
, IP{ip}
, rewrite_source{rewriteIP}
{
last_active = parent->Now();
}
Endpoint::~Endpoint()
{
if (current_path)
parent->DelEndpointInfo(current_path->RXID());
}
void
Endpoint::Close()
{
parent->RemoveExit(this);
}
util::StatusObject
Endpoint::ExtractStatus() const
{
auto now = parent->Now();
util::StatusObject obj{
{"identity", remote_signkey.ToString()},
{"ip", IP.ToString()},
{"txRate", tx_rate},
{"rxRate", rx_rate},
{"createdAt", to_json(createdAt)},
{"exiting", !rewrite_source},
{"looksDead", LooksDead(now)},
{"expiresSoon", ExpiresSoon(now)},
{"expired", IsExpired(now)}};
return obj;
}
bool
Endpoint::UpdateLocalPath(const llarp::PathID_t& nextPath)
{
if (!parent->UpdateEndpointPath(remote_signkey, nextPath))
return false;
const RouterID us{parent->GetRouter()->pubkey()};
// TODO: is this getting a Path or a TransitHop?
// current_path = parent->GetRouter()->path_context().GetByUpstream(us, nextPath);
return true;
}
void
Endpoint::Tick(llarp_time_t now)
{
(void)now;
rx_rate = 0;
tx_rate = 0;
}
bool
Endpoint::IsExpired(llarp_time_t now) const
{
auto path = GetCurrentPath();
if (path)
Endpoint::Endpoint(
const llarp::PubKey& remoteIdent,
const std::shared_ptr<llarp::path::AbstractHopHandler>& beginPath,
bool rewriteIP,
huint128_t ip,
llarp::handlers::ExitEndpoint* parent)
: createdAt{parent->Now()},
parent{parent},
remote_signkey{remoteIdent},
current_path{beginPath},
IP{ip},
rewrite_source{rewriteIP}
{
return path->Expired(now);
last_active = parent->Now();
}
// if we don't have an underlying path we are considered expired
return true;
}
bool
Endpoint::ExpiresSoon(llarp_time_t now, llarp_time_t dlt) const
{
if (current_path)
return current_path->ExpiresSoon(now, dlt);
return true;
}
bool
Endpoint::LooksDead(llarp_time_t now, llarp_time_t timeout) const
{
if (ExpiresSoon(now, timeout))
return true;
auto path = GetCurrentPath();
if (not path)
return true;
auto lastPing = path->LastRemoteActivityAt();
if (lastPing == 0s || (now > lastPing && now - lastPing > timeout))
return now > last_active && now - last_active > timeout;
else if (lastPing > 0s) // NOLINT
return now > lastPing && now - lastPing > timeout;
return lastPing > 0s;
}
/* bool
Endpoint::QueueOutboundTraffic(
PathID_t path, std::vector<byte_t> buf, uint64_t counter, service::ProtocolType t)
Endpoint::~Endpoint()
{
const service::ConvoTag tag{path.as_array()};
if (current_path)
parent->DelEndpointInfo(current_path->RXID());
}
// current_path->send_path_control_message(std::string method, std::string body,
std::function<void (oxen::quic::message)> func)
void Endpoint::Close()
{
parent->RemoveExit(this);
}
if (t == service::ProtocolType::QUIC)
util::StatusObject Endpoint::ExtractStatus() const
{
auto now = parent->Now();
util::StatusObject obj{
{"identity", remote_signkey.ToString()},
{"ip", IP.ToString()},
{"txRate", tx_rate},
{"rxRate", rx_rate},
{"createdAt", to_json(createdAt)},
{"exiting", !rewrite_source},
{"looksDead", LooksDead(now)},
{"expiresSoon", ExpiresSoon(now)},
{"expired", IsExpired(now)}};
return obj;
}
bool Endpoint::UpdateLocalPath(const llarp::PathID_t& nextPath)
{
if (!parent->UpdateEndpointPath(remote_signkey, nextPath))
return false;
const RouterID us{parent->GetRouter()->pubkey()};
// TODO: is this getting a Path or a TransitHop?
// current_path = parent->GetRouter()->path_context().GetByUpstream(us, nextPath);
return true;
}
void Endpoint::Tick(llarp_time_t now)
{
(void)now;
rx_rate = 0;
tx_rate = 0;
}
bool Endpoint::IsExpired(llarp_time_t now) const
{
auto path = GetCurrentPath();
if (path)
{
return path->Expired(now);
}
// if we don't have an underlying path we are considered expired
return true;
}
bool Endpoint::ExpiresSoon(llarp_time_t now, llarp_time_t dlt) const
{
if (current_path)
return current_path->ExpiresSoon(now, dlt);
return true;
}
bool Endpoint::LooksDead(llarp_time_t now, llarp_time_t timeout) const
{
if (ExpiresSoon(now, timeout))
return true;
auto path = GetCurrentPath();
if (not path)
return true;
auto lastPing = path->LastRemoteActivityAt();
if (lastPing == 0s || (now > lastPing && now - lastPing > timeout))
return now > last_active && now - last_active > timeout;
else if (lastPing > 0s) // NOLINT
return now > lastPing && now - lastPing > timeout;
return lastPing > 0s;
}
/* bool
Endpoint::QueueOutboundTraffic(
PathID_t path, std::vector<byte_t> buf, uint64_t counter, service::ProtocolType t)
{
auto quic = parent->GetQUICTunnel();
if (not quic)
const service::ConvoTag tag{path.as_array()};
// current_path->send_path_control_message(std::string method, std::string body,
std::function<void (oxen::quic::message)> func)
if (t == service::ProtocolType::QUIC)
{
auto quic = parent->GetQUICTunnel();
if (not quic)
return false;
tx_rate += buf.size();
quic->receive_packet(tag, std::move(buf));
last_active = parent->Now();
return true;
}
// queue overflow
if (m_UpstreamQueue.size() > MaxUpstreamQueueSize)
return false;
llarp::net::IPPacket pkt{std::move(buf)};
if (pkt.empty())
return false;
if (pkt.IsV6() && parent->SupportsV6())
{
huint128_t dst;
if (rewrite_source)
dst = parent->GetIfAddr();
else
dst = pkt.dstv6();
pkt.UpdateIPv6Address(IP, dst);
}
else if (pkt.IsV4() && !parent->SupportsV6())
{
huint32_t dst;
if (rewrite_source)
dst = net::TruncateV6(parent->GetIfAddr());
else
dst = pkt.dstv4();
pkt.UpdateIPv4Address(xhtonl(net::TruncateV6(IP)), xhtonl(dst));
}
else
{
return false;
tx_rate += buf.size();
quic->receive_packet(tag, std::move(buf));
}
tx_rate += pkt.size();
m_UpstreamQueue.emplace(std::move(pkt), counter);
last_active = parent->Now();
return true;
}
// queue overflow
if (m_UpstreamQueue.size() > MaxUpstreamQueueSize)
return false;
} */
llarp::net::IPPacket pkt{std::move(buf)};
if (pkt.empty())
return false;
bool Endpoint::QueueInboundTraffic(std::vector<byte_t>, service::ProtocolType)
{
// TODO: this will go away with removing flush
// if (type != service::ProtocolType::QUIC)
// {
// llarp::net::IPPacket pkt{std::move(buf)};
// if (pkt.empty())
// return false;
// huint128_t src;
// if (m_RewriteSource)
// src = m_Parent->GetIfAddr();
// else
// src = pkt.srcv6();
// if (pkt.IsV6())
// pkt.UpdateIPv6Address(src, m_IP);
// else
// pkt.UpdateIPv4Address(xhtonl(net::TruncateV6(src)), xhtonl(net::TruncateV6(m_IP)));
// buf = pkt.steal();
// }
// const uint8_t queue_idx = buf.size() / llarp::routing::EXIT_PAD_SIZE;
// if (m_DownstreamQueues.find(queue_idx) == m_DownstreamQueues.end())
// m_DownstreamQueues.emplace(queue_idx, InboundTrafficQueue_t{});
// auto& queue = m_DownstreamQueues.at(queue_idx);
// if (queue.size() == 0)
// {
// queue.emplace_back();
// queue.back().protocol = type;
// return queue.back().PutBuffer(std::move(buf), m_Counter++);
// }
// auto& msg = queue.back();
// if (msg.Size() + buf.size() > llarp::routing::EXIT_PAD_SIZE)
// {
// queue.emplace_back();
// queue.back().protocol = type;
// return queue.back().PutBuffer(std::move(buf), m_Counter++);
// }
// msg.protocol = type;
// return msg.PutBuffer(std::move(buf), m_Counter++);
return true;
}
if (pkt.IsV6() && parent->SupportsV6())
{
huint128_t dst;
if (rewrite_source)
dst = parent->GetIfAddr();
else
dst = pkt.dstv6();
pkt.UpdateIPv6Address(IP, dst);
}
else if (pkt.IsV4() && !parent->SupportsV6())
{
huint32_t dst;
if (rewrite_source)
dst = net::TruncateV6(parent->GetIfAddr());
else
dst = pkt.dstv4();
pkt.UpdateIPv4Address(xhtonl(net::TruncateV6(IP)), xhtonl(dst));
}
else
{
return false;
}
tx_rate += pkt.size();
m_UpstreamQueue.emplace(std::move(pkt), counter);
last_active = parent->Now();
return true;
} */
bool
Endpoint::QueueInboundTraffic(std::vector<byte_t>, service::ProtocolType)
{
// TODO: this will go away with removing flush
// if (type != service::ProtocolType::QUIC)
// {
// llarp::net::IPPacket pkt{std::move(buf)};
// if (pkt.empty())
// return false;
// huint128_t src;
// if (m_RewriteSource)
// src = m_Parent->GetIfAddr();
// else
// src = pkt.srcv6();
// if (pkt.IsV6())
// pkt.UpdateIPv6Address(src, m_IP);
// else
// pkt.UpdateIPv4Address(xhtonl(net::TruncateV6(src)), xhtonl(net::TruncateV6(m_IP)));
// buf = pkt.steal();
// }
// const uint8_t queue_idx = buf.size() / llarp::routing::EXIT_PAD_SIZE;
// if (m_DownstreamQueues.find(queue_idx) == m_DownstreamQueues.end())
// m_DownstreamQueues.emplace(queue_idx, InboundTrafficQueue_t{});
// auto& queue = m_DownstreamQueues.at(queue_idx);
// if (queue.size() == 0)
// {
// queue.emplace_back();
// queue.back().protocol = type;
// return queue.back().PutBuffer(std::move(buf), m_Counter++);
// }
// auto& msg = queue.back();
// if (msg.Size() + buf.size() > llarp::routing::EXIT_PAD_SIZE)
// {
// queue.emplace_back();
// queue.back().protocol = type;
// return queue.back().PutBuffer(std::move(buf), m_Counter++);
// }
// msg.protocol = type;
// return msg.PutBuffer(std::move(buf), m_Counter++);
return true;
}
bool
Endpoint::Flush()
{
// flush upstream queue
// while (m_UpstreamQueue.size())
// {
// parent->QueueOutboundTraffic(const_cast<net::IPPacket&>(m_UpstreamQueue.top().pkt).steal());
// m_UpstreamQueue.pop();
// }
// flush downstream queue
auto path = GetCurrentPath();
bool sent = path != nullptr;
// if (path)
// {
// for (auto& item : m_DownstreamQueues)
// {
// auto& queue = item.second;
// while (queue.size())
// {
// auto& msg = queue.front();
// msg.sequence_number = path->NextSeqNo();
// if (path->SendRoutingMessage(msg, m_Parent->GetRouter()))
// {
// m_RxRate += msg.Size();
// sent = true;
// }
// queue.pop_front();
// }
// }
// }
// for (auto& item : m_DownstreamQueues)
// item.second.clear();
return sent;
}
bool Endpoint::Flush()
{
// flush upstream queue
// while (m_UpstreamQueue.size())
// {
// parent->QueueOutboundTraffic(const_cast<net::IPPacket&>(m_UpstreamQueue.top().pkt).steal());
// m_UpstreamQueue.pop();
// }
// flush downstream queue
auto path = GetCurrentPath();
bool sent = path != nullptr;
// if (path)
// {
// for (auto& item : m_DownstreamQueues)
// {
// auto& queue = item.second;
// while (queue.size())
// {
// auto& msg = queue.front();
// msg.sequence_number = path->NextSeqNo();
// if (path->SendRoutingMessage(msg, m_Parent->GetRouter()))
// {
// m_RxRate += msg.Size();
// sent = true;
// }
// queue.pop_front();
// }
// }
// }
// for (auto& item : m_DownstreamQueues)
// item.second.clear();
return sent;
}
} // namespace llarp::exit

@ -10,116 +10,102 @@
namespace llarp
{
namespace handlers
{
// forward declare
struct ExitEndpoint;
} // namespace handlers
namespace exit
{
/// persistant exit state for 1 identity on the exit node
struct Endpoint
namespace handlers
{
static constexpr size_t MaxUpstreamQueueSize = 256;
explicit Endpoint(
const llarp::PubKey& remoteIdent,
const std::shared_ptr<llarp::path::AbstractHopHandler>& path,
bool rewriteIP,
huint128_t ip,
llarp::handlers::ExitEndpoint* parent);
~Endpoint();
/// close ourselves
void
Close();
/// implement istateful
util::StatusObject
ExtractStatus() const;
/// return true if we are expired right now
bool
IsExpired(llarp_time_t now) const;
bool
ExpiresSoon(llarp_time_t now, llarp_time_t dlt = 5s) const;
/// return true if this endpoint looks dead right now
bool
LooksDead(llarp_time_t now, llarp_time_t timeout = 10s) const;
/// tick ourself, reset tx/rx rates
void
Tick(llarp_time_t now);
/// queue traffic from service node / internet to be transmitted
bool
QueueInboundTraffic(std::vector<byte_t> data, service::ProtocolType t);
/// flush inbound and outbound traffic queues
bool
Flush();
/// queue outbound traffic
/// does ip rewrite here
// bool
// QueueOutboundTraffic(
// PathID_t txid, std::vector<byte_t> data, uint64_t counter, service::ProtocolType t);
/// update local path id and cascade information to parent
/// return true if success
bool
UpdateLocalPath(const llarp::PathID_t& nextPath);
std::shared_ptr<llarp::path::AbstractHopHandler>
GetCurrentPath() const
{
return current_path;
}
const llarp::PubKey&
PubKey() const
{
return remote_signkey;
}
RouterID
router_id() const
{
return remote_signkey.data();
}
uint64_t
TxRate() const
{
return tx_rate;
}
uint64_t
RxRate() const
{
return rx_rate;
}
huint128_t
LocalIP() const
{
return IP;
}
const llarp_time_t createdAt;
private:
llarp::handlers::ExitEndpoint* parent;
llarp::PubKey remote_signkey;
std::shared_ptr<llarp::path::AbstractHopHandler> current_path;
llarp::huint128_t IP;
uint64_t tx_rate, rx_rate;
llarp_time_t last_active;
bool rewrite_source;
};
} // namespace exit
// forward declare
struct ExitEndpoint;
} // namespace handlers
namespace exit
{
/// persistant exit state for 1 identity on the exit node
struct Endpoint
{
static constexpr size_t MaxUpstreamQueueSize = 256;
explicit Endpoint(
const llarp::PubKey& remoteIdent,
const std::shared_ptr<llarp::path::AbstractHopHandler>& path,
bool rewriteIP,
huint128_t ip,
llarp::handlers::ExitEndpoint* parent);
~Endpoint();
/// close ourselves
void Close();
/// implement istateful
util::StatusObject ExtractStatus() const;
/// return true if we are expired right now
bool IsExpired(llarp_time_t now) const;
bool ExpiresSoon(llarp_time_t now, llarp_time_t dlt = 5s) const;
/// return true if this endpoint looks dead right now
bool LooksDead(llarp_time_t now, llarp_time_t timeout = 10s) const;
/// tick ourself, reset tx/rx rates
void Tick(llarp_time_t now);
/// queue traffic from service node / internet to be transmitted
bool QueueInboundTraffic(std::vector<byte_t> data, service::ProtocolType t);
/// flush inbound and outbound traffic queues
bool Flush();
/// queue outbound traffic
/// does ip rewrite here
// bool
// QueueOutboundTraffic(
// PathID_t txid, std::vector<byte_t> data, uint64_t counter, service::ProtocolType
// t);
/// update local path id and cascade information to parent
/// return true if success
bool UpdateLocalPath(const llarp::PathID_t& nextPath);
std::shared_ptr<llarp::path::AbstractHopHandler> GetCurrentPath() const
{
return current_path;
}
const llarp::PubKey& PubKey() const
{
return remote_signkey;
}
RouterID router_id() const
{
return remote_signkey.data();
}
uint64_t TxRate() const
{
return tx_rate;
}
uint64_t RxRate() const
{
return rx_rate;
}
huint128_t LocalIP() const
{
return IP;
}
const llarp_time_t createdAt;
private:
llarp::handlers::ExitEndpoint* parent;
llarp::PubKey remote_signkey;
std::shared_ptr<llarp::path::AbstractHopHandler> current_path;
llarp::huint128_t IP;
uint64_t tx_rate, rx_rate;
llarp_time_t last_active;
bool rewrite_source;
};
} // namespace exit
} // namespace llarp

@ -2,35 +2,33 @@
namespace llarp::exit
{
void
Policy::bt_encode(oxenc::bt_dict_producer& btdp) const
{
try
void Policy::bt_encode(oxenc::bt_dict_producer& btdp) const
{
btdp.append("a", proto);
btdp.append("b", port);
btdp.append("d", drop);
btdp.append("v", version);
try
{
btdp.append("a", proto);
btdp.append("b", port);
btdp.append("d", drop);
btdp.append("v", version);
}
catch (...)
{
log::critical(policy_cat, "Error: exit Policy failed to bt encode contents!");
}
}
catch (...)
bool Policy::decode_key(const llarp_buffer_t& k, llarp_buffer_t* buf)
{
log::critical(policy_cat, "Error: exit Policy failed to bt encode contents!");
bool read = false;
if (!BEncodeMaybeReadDictInt("a", proto, read, k, buf))
return false;
if (!BEncodeMaybeReadDictInt("b", port, read, k, buf))
return false;
if (!BEncodeMaybeReadDictInt("d", drop, read, k, buf))
return false;
if (!BEncodeMaybeReadDictInt("v", version, read, k, buf))
return false;
return read;
}
}
bool
Policy::decode_key(const llarp_buffer_t& k, llarp_buffer_t* buf)
{
bool read = false;
if (!BEncodeMaybeReadDictInt("a", proto, read, k, buf))
return false;
if (!BEncodeMaybeReadDictInt("b", port, read, k, buf))
return false;
if (!BEncodeMaybeReadDictInt("d", drop, read, k, buf))
return false;
if (!BEncodeMaybeReadDictInt("v", version, read, k, buf))
return false;
return read;
}
} // namespace llarp::exit

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save