mirror of
https://github.com/oxen-io/lokinet
synced 2023-12-14 06:53:00 +01:00
initial config overriding on runtime
This commit is contained in:
parent
e3bfe76f98
commit
5391e6a66a
10 changed files with 259 additions and 127 deletions
|
@ -66,6 +66,10 @@ namespace llarp
|
|||
void
|
||||
Configure(Config conf);
|
||||
|
||||
/// handle SIGHUP
|
||||
void
|
||||
Reload();
|
||||
|
||||
bool
|
||||
IsUp() const;
|
||||
|
||||
|
@ -92,7 +96,7 @@ namespace llarp
|
|||
makeRouter(llarp_ev_loop_ptr __netloop, std::shared_ptr<Logic> logic);
|
||||
|
||||
protected:
|
||||
std::unique_ptr<Config> config = nullptr;
|
||||
std::shared_ptr<Config> config = nullptr;
|
||||
|
||||
private:
|
||||
void
|
||||
|
|
|
@ -547,6 +547,18 @@ namespace llarp
|
|||
"logging", "file", false, DefaultLogFile, AssignmentAcceptor(m_logFile));
|
||||
}
|
||||
|
||||
void
|
||||
Config::Save() const
|
||||
{
|
||||
m_Parser.Save();
|
||||
}
|
||||
|
||||
void
|
||||
Config::Override(std::string section, std::string key, std::string value)
|
||||
{
|
||||
m_Parser.AddOverride(std::move(section), std::move(key), std::move(value));
|
||||
}
|
||||
|
||||
bool
|
||||
Config::Load(const fs::path fname, bool isRelay, fs::path defaultDataDir)
|
||||
{
|
||||
|
@ -559,14 +571,13 @@ namespace llarp
|
|||
ConfigDefinition conf;
|
||||
initializeConfig(conf, params);
|
||||
addBackwardsCompatibleConfigOptions(conf);
|
||||
|
||||
ConfigParser parser;
|
||||
if (!parser.LoadFile(fname))
|
||||
m_Parser.Clear();
|
||||
if (!m_Parser.LoadFile(fname))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
parser.IterAll([&](std::string_view section, const SectionValues_t& values) {
|
||||
m_Parser.IterAll([&](std::string_view section, const SectionValues_t& values) {
|
||||
for (const auto& pair : values)
|
||||
{
|
||||
conf.addConfigValue(section, pair.first, pair.second);
|
||||
|
|
|
@ -224,6 +224,15 @@ namespace llarp
|
|||
|
||||
std::string
|
||||
generateBaseRouterConfig(fs::path defaultDataDir);
|
||||
|
||||
void
|
||||
Save() const;
|
||||
|
||||
void
|
||||
Override(std::string section, std::string key, std::string value);
|
||||
|
||||
private:
|
||||
ConfigParser m_Parser;
|
||||
};
|
||||
|
||||
void
|
||||
|
|
|
@ -40,6 +40,7 @@ namespace llarp
|
|||
void
|
||||
ConfigParser::Clear()
|
||||
{
|
||||
m_Overrides.clear();
|
||||
m_Config.clear();
|
||||
m_Data.clear();
|
||||
}
|
||||
|
@ -162,4 +163,32 @@ namespace llarp
|
|||
return false;
|
||||
return visit(itr->second);
|
||||
}
|
||||
|
||||
void
|
||||
ConfigParser::AddOverride(std::string section, std::string key, std::string value)
|
||||
{
|
||||
m_Overrides[section].emplace(key, value);
|
||||
}
|
||||
|
||||
void
|
||||
ConfigParser::Save() const
|
||||
{
|
||||
// if we have no overrides keep the config the same on disk
|
||||
if (m_Overrides.empty())
|
||||
return;
|
||||
std::ofstream ofs(m_FileName);
|
||||
// write existing config data
|
||||
ofs.write(m_Data.data(), m_Data.size());
|
||||
// write overrides
|
||||
ofs << std::endl << std::endl << "# overrides" << std::endl;
|
||||
for (const auto& [section, values] : m_Overrides)
|
||||
{
|
||||
ofs << std::endl << "[" << section << "]" << std::endl;
|
||||
for (const auto& [key, value] : values)
|
||||
{
|
||||
ofs << key << "=" << value << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace llarp
|
||||
|
|
|
@ -40,12 +40,21 @@ namespace llarp
|
|||
bool
|
||||
VisitSection(const char* name, std::function<bool(const SectionValues_t&)> visit) const;
|
||||
|
||||
/// add a config option that is appended at the end of the config buffer with no comments
|
||||
void
|
||||
AddOverride(std::string section, std::string key, std::string value);
|
||||
|
||||
/// save config and any overrides to the file it was loaded from
|
||||
void
|
||||
Save() const;
|
||||
|
||||
private:
|
||||
bool
|
||||
Parse();
|
||||
|
||||
std::vector<char> m_Data;
|
||||
Config_impl_t m_Config;
|
||||
Config_impl_t m_Overrides;
|
||||
fs::path m_FileName;
|
||||
};
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ namespace llarp
|
|||
if (nullptr != config.get())
|
||||
throw std::runtime_error("Config already exists");
|
||||
|
||||
config = std::make_unique<Config>(std::move(conf));
|
||||
config = std::make_shared<Config>(std::move(conf));
|
||||
|
||||
logic = std::make_shared<Logic>();
|
||||
|
||||
|
@ -85,7 +85,7 @@ namespace llarp
|
|||
nodedb = std::make_unique<llarp_nodedb>(
|
||||
nodedb_dir, [r = router.get()](auto call) { r->QueueDiskIO(std::move(call)); });
|
||||
|
||||
if (!router->Configure(*config.get(), opts.isRouter, nodedb.get()))
|
||||
if (!router->Configure(config, opts.isRouter, nodedb.get()))
|
||||
throw std::runtime_error("Failed to configure router");
|
||||
|
||||
// must be done after router is made so we can use its disk io worker
|
||||
|
@ -157,8 +157,17 @@ namespace llarp
|
|||
{
|
||||
SigINT();
|
||||
}
|
||||
// TODO: Hot reloading would be kewl
|
||||
// (it used to exist here, but wasn't maintained)
|
||||
#ifndef _WIN32
|
||||
if (sig == SIGHUP)
|
||||
{
|
||||
Reload();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
Context::Reload()
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -182,7 +191,7 @@ namespace llarp
|
|||
Context::Close()
|
||||
{
|
||||
llarp::LogDebug("free config");
|
||||
config.release();
|
||||
config.reset();
|
||||
|
||||
llarp::LogDebug("free nodedb");
|
||||
nodedb.release();
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#ifndef LLARP_ABSTRACT_ROUTER_HPP
|
||||
#define LLARP_ABSTRACT_ROUTER_HPP
|
||||
|
||||
#include <config/config.hpp>
|
||||
#include <config/key_manager.hpp>
|
||||
#include <memory>
|
||||
#include <util/types.hpp>
|
||||
|
@ -134,6 +135,12 @@ namespace llarp
|
|||
/// call function in disk io thread
|
||||
virtual void QueueDiskIO(std::function<void(void)>) = 0;
|
||||
|
||||
virtual std::shared_ptr<Config>
|
||||
GetConfig() const
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
virtual service::Context&
|
||||
hiddenServiceContext() = 0;
|
||||
|
||||
|
@ -159,7 +166,7 @@ namespace llarp
|
|||
Sign(Signature& sig, const llarp_buffer_t& buf) const = 0;
|
||||
|
||||
virtual bool
|
||||
Configure(const Config& conf, bool isRouter, llarp_nodedb* nodedb) = 0;
|
||||
Configure(std::shared_ptr<Config> conf, bool isRouter, llarp_nodedb* nodedb) = 0;
|
||||
|
||||
virtual bool
|
||||
IsServiceNode() const = 0;
|
||||
|
|
|
@ -262,8 +262,10 @@ namespace llarp
|
|||
}
|
||||
|
||||
bool
|
||||
Router::Configure(const Config& conf, bool isRouter, llarp_nodedb* nodedb)
|
||||
Router::Configure(std::shared_ptr<Config> c, bool isRouter, llarp_nodedb* nodedb)
|
||||
{
|
||||
m_Config = c;
|
||||
auto& conf = *m_Config;
|
||||
whitelistRouters = conf.lokid.whitelistRouters;
|
||||
if (whitelistRouters)
|
||||
lokidRPCAddr = lokimq::address(conf.lokid.lokidRPCAddr);
|
||||
|
|
|
@ -353,7 +353,7 @@ namespace llarp
|
|||
Close();
|
||||
|
||||
bool
|
||||
Configure(const Config& conf, bool isRouter, llarp_nodedb* nodedb = nullptr) override;
|
||||
Configure(std::shared_ptr<Config> conf, bool isRouter, llarp_nodedb* nodedb = nullptr) override;
|
||||
|
||||
bool
|
||||
StartRpcServer() override;
|
||||
|
@ -499,6 +499,14 @@ namespace llarp
|
|||
void
|
||||
AfterStopIssued();
|
||||
|
||||
std::shared_ptr<Config> m_Config;
|
||||
|
||||
std::shared_ptr<Config>
|
||||
GetConfig() const override
|
||||
{
|
||||
return m_Config;
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic<bool> _stopping;
|
||||
std::atomic<bool> _running;
|
||||
|
|
|
@ -110,131 +110,175 @@ namespace llarp::rpc
|
|||
auto ftr = result.get_future();
|
||||
msg.send_reply(CreateJSONResponse(ftr.get()));
|
||||
})
|
||||
.add_request_command("exit", [&](lokimq::Message& msg) {
|
||||
.add_request_command(
|
||||
"exit",
|
||||
[&](lokimq::Message& msg) {
|
||||
HandleJSONRequest(msg, [r = m_Router](nlohmann::json obj, ReplyFunction_t reply) {
|
||||
if (r->IsServiceNode())
|
||||
{
|
||||
reply(CreateJSONError("not supported"));
|
||||
return;
|
||||
}
|
||||
std::optional<service::Address> exit;
|
||||
IPRange range;
|
||||
bool map = true;
|
||||
const auto exit_itr = obj.find("exit");
|
||||
if (exit_itr != obj.end())
|
||||
{
|
||||
service::Address addr;
|
||||
if (not addr.FromString(exit_itr->get<std::string>()))
|
||||
{
|
||||
reply(CreateJSONError("invalid exit address"));
|
||||
return;
|
||||
}
|
||||
exit = addr;
|
||||
}
|
||||
|
||||
const auto unmap_itr = obj.find("unmap");
|
||||
if (unmap_itr != obj.end() and unmap_itr->get<bool>())
|
||||
{
|
||||
map = false;
|
||||
}
|
||||
|
||||
const auto range_itr = obj.find("range");
|
||||
if (range_itr == obj.end())
|
||||
{
|
||||
range.FromString("0.0.0.0/0");
|
||||
}
|
||||
else if (not range.FromString(range_itr->get<std::string>()))
|
||||
{
|
||||
reply(CreateJSONError("invalid ip range"));
|
||||
return;
|
||||
}
|
||||
std::optional<std::string> token;
|
||||
const auto token_itr = obj.find("token");
|
||||
if (token_itr != obj.end())
|
||||
{
|
||||
token = token_itr->get<std::string>();
|
||||
}
|
||||
|
||||
std::string endpoint = "default";
|
||||
const auto endpoint_itr = obj.find("endpoint");
|
||||
if (endpoint_itr != obj.end())
|
||||
{
|
||||
endpoint = endpoint_itr->get<std::string>();
|
||||
}
|
||||
LogicCall(r->logic(), [map, exit, range, token, endpoint, r, reply]() {
|
||||
auto ep = r->hiddenServiceContext().GetEndpointByName(endpoint);
|
||||
if (ep == nullptr)
|
||||
{
|
||||
reply(CreateJSONError("no endpoint with name " + endpoint));
|
||||
return;
|
||||
}
|
||||
if (map and exit.has_value())
|
||||
{
|
||||
const auto gateways = net::GetGatewaysNotOnInterface(ep->GetIfName());
|
||||
if (gateways.empty())
|
||||
{
|
||||
reply(CreateJSONError("no gateway found"));
|
||||
return;
|
||||
}
|
||||
ep->MapExitRange(range, *exit);
|
||||
if (token.has_value())
|
||||
{
|
||||
ep->SetAuthInfoForEndpoint(*exit, service::AuthInfo{*token});
|
||||
}
|
||||
ep->EnsurePathToService(
|
||||
*exit,
|
||||
[r, gateway = gateways[0], reply, ep](auto, service::OutboundContext* ctx) {
|
||||
if (ctx == nullptr)
|
||||
{
|
||||
reply(CreateJSONError("could not find exit"));
|
||||
return;
|
||||
}
|
||||
std::vector<std::string> firsthops;
|
||||
r->ForEachPeer(
|
||||
[&firsthops](const auto* link, bool) {
|
||||
firsthops.emplace_back(link->GetRemoteEndpoint().toHost());
|
||||
},
|
||||
false);
|
||||
for (const auto& hop : firsthops)
|
||||
{
|
||||
net::AddRoute(hop, gateway);
|
||||
}
|
||||
net::AddDefaultRouteViaInterface(ep->GetIfName());
|
||||
r->SetDownHook([firsthops, gateway]() {
|
||||
for (const auto& hop : firsthops)
|
||||
{
|
||||
net::DelRoute(hop, gateway);
|
||||
}
|
||||
});
|
||||
reply(CreateJSONResponse("OK"));
|
||||
},
|
||||
5s);
|
||||
return;
|
||||
}
|
||||
else if (map and not exit.has_value())
|
||||
{
|
||||
reply(CreateJSONError("no exit address provided"));
|
||||
return;
|
||||
}
|
||||
else if (not map)
|
||||
{
|
||||
const auto gateways = net::GetGatewaysNotOnInterface(ep->GetIfName());
|
||||
if (gateways.empty())
|
||||
{
|
||||
reply(CreateJSONError("no gateway found"));
|
||||
return;
|
||||
}
|
||||
net::DelDefaultRouteViaInterface(ep->GetIfName());
|
||||
r->ForEachPeer(
|
||||
[gateway = gateways[0]](const auto* link, bool) {
|
||||
net::DelRoute(link->GetRemoteEndpoint().toHost(), gateway);
|
||||
},
|
||||
false);
|
||||
|
||||
ep->UnmapExitRange(range);
|
||||
}
|
||||
reply(CreateJSONResponse("OK"));
|
||||
});
|
||||
});
|
||||
})
|
||||
.add_request_command("config", [&](lokimq::Message& msg) {
|
||||
HandleJSONRequest(msg, [r = m_Router](nlohmann::json obj, ReplyFunction_t reply) {
|
||||
if (r->IsServiceNode())
|
||||
{
|
||||
reply(CreateJSONError("not supported"));
|
||||
return;
|
||||
}
|
||||
std::optional<service::Address> exit;
|
||||
IPRange range;
|
||||
bool map = true;
|
||||
const auto exit_itr = obj.find("exit");
|
||||
if (exit_itr != obj.end())
|
||||
{
|
||||
service::Address addr;
|
||||
if (not addr.FromString(exit_itr->get<std::string>()))
|
||||
const auto itr = obj.find("override");
|
||||
if (itr != obj.end())
|
||||
{
|
||||
reply(CreateJSONError("invalid exit address"));
|
||||
return;
|
||||
}
|
||||
exit = addr;
|
||||
}
|
||||
|
||||
const auto unmap_itr = obj.find("unmap");
|
||||
if (unmap_itr != obj.end() and unmap_itr->get<bool>())
|
||||
{
|
||||
map = false;
|
||||
}
|
||||
|
||||
const auto range_itr = obj.find("range");
|
||||
if (range_itr == obj.end())
|
||||
{
|
||||
range.FromString("0.0.0.0/0");
|
||||
}
|
||||
else if (not range.FromString(range_itr->get<std::string>()))
|
||||
{
|
||||
reply(CreateJSONError("invalid ip range"));
|
||||
return;
|
||||
}
|
||||
std::optional<std::string> token;
|
||||
const auto token_itr = obj.find("token");
|
||||
if (token_itr != obj.end())
|
||||
{
|
||||
token = token_itr->get<std::string>();
|
||||
}
|
||||
|
||||
std::string endpoint = "default";
|
||||
const auto endpoint_itr = obj.find("endpoint");
|
||||
if (endpoint_itr != obj.end())
|
||||
{
|
||||
endpoint = endpoint_itr->get<std::string>();
|
||||
}
|
||||
LogicCall(r->logic(), [map, exit, range, token, endpoint, r, reply]() {
|
||||
auto ep = r->hiddenServiceContext().GetEndpointByName(endpoint);
|
||||
if (ep == nullptr)
|
||||
{
|
||||
reply(CreateJSONError("no endpoint with name " + endpoint));
|
||||
return;
|
||||
}
|
||||
if (map and exit.has_value())
|
||||
{
|
||||
const auto gateways = net::GetGatewaysNotOnInterface(ep->GetIfName());
|
||||
if (gateways.empty())
|
||||
if (not itr->is_object())
|
||||
{
|
||||
reply(CreateJSONError("no gateway found"));
|
||||
reply(CreateJSONError(stringify("override is not an object")));
|
||||
return;
|
||||
}
|
||||
ep->MapExitRange(range, *exit);
|
||||
if (token.has_value())
|
||||
for (const auto& [section, value] : itr->items())
|
||||
{
|
||||
ep->SetAuthInfoForEndpoint(*exit, service::AuthInfo{*token});
|
||||
if (not value.is_object())
|
||||
{
|
||||
reply(CreateJSONError(
|
||||
stringify("failed to set [", section, "] section is not an object")));
|
||||
return;
|
||||
}
|
||||
for (const auto& [key, value] : value.items())
|
||||
{
|
||||
if (not value.is_string())
|
||||
{
|
||||
reply(CreateJSONError(stringify(
|
||||
"failed to set [", section, "]:", key, " value is not a string")));
|
||||
return;
|
||||
}
|
||||
r->GetConfig()->Override(section, key, value.get<std::string>());
|
||||
}
|
||||
}
|
||||
ep->EnsurePathToService(
|
||||
*exit,
|
||||
[r, gateway = gateways[0], reply, ep](auto, service::OutboundContext* ctx) {
|
||||
if (ctx == nullptr)
|
||||
{
|
||||
reply(CreateJSONError("could not find exit"));
|
||||
return;
|
||||
}
|
||||
std::vector<std::string> firsthops;
|
||||
r->ForEachPeer(
|
||||
[&firsthops](const auto* link, bool) {
|
||||
firsthops.emplace_back(link->GetRemoteEndpoint().toHost());
|
||||
},
|
||||
false);
|
||||
for (const auto& hop : firsthops)
|
||||
{
|
||||
net::AddRoute(hop, gateway);
|
||||
}
|
||||
net::AddDefaultRouteViaInterface(ep->GetIfName());
|
||||
r->SetDownHook([firsthops, gateway]() {
|
||||
for (const auto& hop : firsthops)
|
||||
{
|
||||
net::DelRoute(hop, gateway);
|
||||
}
|
||||
});
|
||||
reply(CreateJSONResponse("OK"));
|
||||
},
|
||||
5s);
|
||||
return;
|
||||
}
|
||||
else if (map and not exit.has_value())
|
||||
}
|
||||
{
|
||||
const auto itr = obj.find("reload");
|
||||
if (itr != obj.end() and itr->get<bool>())
|
||||
{
|
||||
reply(CreateJSONError("no exit address provided"));
|
||||
return;
|
||||
r->QueueDiskIO([conf = r->GetConfig()]() { conf->Save(); });
|
||||
}
|
||||
else if (not map)
|
||||
{
|
||||
const auto gateways = net::GetGatewaysNotOnInterface(ep->GetIfName());
|
||||
if (gateways.empty())
|
||||
{
|
||||
reply(CreateJSONError("no gateway found"));
|
||||
return;
|
||||
}
|
||||
net::DelDefaultRouteViaInterface(ep->GetIfName());
|
||||
r->ForEachPeer(
|
||||
[gateway = gateways[0]](const auto* link, bool) {
|
||||
net::DelRoute(link->GetRemoteEndpoint().toHost(), gateway);
|
||||
},
|
||||
false);
|
||||
|
||||
ep->UnmapExitRange(range);
|
||||
}
|
||||
reply(CreateJSONResponse("OK"));
|
||||
});
|
||||
}
|
||||
reply(CreateJSONResponse("OK"));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue