blob: 421fd85c9c93b136d6a048b774e69469cb9bf8b0 [file] [log] [blame]
/*
* Copyright (C) 2021 CESNET, https://photonics.cesnet.cz/
*
* Written by Tomáš Pecka <tomas.pecka@cesnet.cz>
*
*/
#include <numeric>
#include "IETFInterfacesConfig.h"
#include "utils/io.h"
#include "utils/libyang.h"
#include "utils/log.h"
#include "utils/sysrepo.h"
using namespace std::string_literals;
namespace {
const auto CZECHLIGHT_NETWORK_MODULE_NAME = "czechlight-network"s;
const auto IETF_IP_MODULE_NAME = "ietf-ip"s;
const auto IETF_INTERFACES_MODULE_NAME = "ietf-interfaces"s;
const auto IETF_ROUTING_MODULE_NAME = "ietf-routing"s;
const auto IETF_IPV4_UNICAST_ROUTING_MODULE_NAME = "ietf-ipv4-unicast-routing";
const auto IETF_IPV6_UNICAST_ROUTING_MODULE_NAME = "ietf-ipv6-unicast-routing";
const auto IETF_INTERFACES = "/"s + IETF_INTERFACES_MODULE_NAME + ":interfaces"s;
std::string generateNetworkConfigFile(const std::string& linkName, const std::map<std::string, std::vector<std::string>>& values)
{
std::ostringstream oss;
oss << "[Match]" << std::endl;
oss << "Name=" << linkName << std::endl;
for (const auto& [sectionName, values] : values) {
oss << std::endl
<< '[' << sectionName << ']' << std::endl;
for (const auto& confValue : values) {
oss << confValue << std::endl;
}
}
return oss.str();
}
/** @brief Checks if protocol is enabled.
*
* If the ietf-ip:ipv{4,6} presence container is present, takes value of leaf 'enabled' (which is always there). If the container is not present (and so the 'enabled' leaf is not there as well), then the protocol is disabled.
*/
bool protocolEnabled(const std::shared_ptr<libyang::Data_Node>& linkEntry, const std::string& proto)
{
const auto xpath = "ietf-ip:" + proto + "/enabled";
try {
auto enabled = getValueAsString(getSubtree(linkEntry, xpath.c_str()));
return enabled == "true"s;
} catch (const std::runtime_error&) { // leaf and the presence container missing
return false;
}
}
}
namespace velia::system {
IETFInterfacesConfig::IETFInterfacesConfig(std::shared_ptr<::sysrepo::Session> srSess, std::filesystem::path configDirectory, std::vector<std::string> managedLinks, reload_cb_t reloadCallback)
: m_log(spdlog::get("system"))
, m_reloadCb(std::move(reloadCallback))
, m_configDirectory(std::move(configDirectory))
, m_managedLinks(std::move(managedLinks))
, m_srSession(std::move(srSess))
, m_srSubscribe(std::make_shared<::sysrepo::Subscribe>(m_srSession))
{
utils::ensureModuleImplemented(m_srSession, IETF_INTERFACES_MODULE_NAME, "2018-02-20");
utils::ensureModuleImplemented(m_srSession, IETF_IP_MODULE_NAME, "2018-02-22");
utils::ensureModuleImplemented(m_srSession, IETF_ROUTING_MODULE_NAME, "2018-03-13");
utils::ensureModuleImplemented(m_srSession, IETF_IPV4_UNICAST_ROUTING_MODULE_NAME, "2018-03-13");
utils::ensureModuleImplemented(m_srSession, IETF_IPV6_UNICAST_ROUTING_MODULE_NAME, "2018-03-13");
utils::ensureModuleImplemented(m_srSession, CZECHLIGHT_NETWORK_MODULE_NAME, "2021-02-22");
m_srSubscribe->module_change_subscribe(
IETF_INTERFACES_MODULE_NAME.c_str(), [this](auto session, auto, auto, auto, auto) { return moduleChange(session); }, IETF_INTERFACES.c_str(), 0, SR_SUBSCR_DONE_ONLY);
}
int IETFInterfacesConfig::moduleChange(std::shared_ptr<::sysrepo::Session> session) const
{
std::map<std::string, std::string> networkConfigFiles;
if (auto data = session->get_data("/ietf-interfaces:interfaces/interface"); data) {
auto linkEntries = data->find_path("/ietf-interfaces:interfaces/interface");
for (const auto& linkEntry : linkEntries->data()) {
std::map<std::string, std::vector<std::string>> configValues;
auto linkName = getValueAsString(getSubtree(linkEntry, "name"));
if (auto set = linkEntry->find_path("description"); set->number() != 0) {
configValues["Network"].push_back("Description="s + getValueAsString(set->data().front()));
}
// if addresses present, generate them...
for (const auto& ipProto : {"ipv4", "ipv6"}) {
// ...but only if the protocol is enabled
if (!protocolEnabled(linkEntry, ipProto)) {
continue;
}
const auto IPAddressListXPath = "ietf-ip:"s + ipProto + "/ietf-ip:address";
const auto addresses = linkEntry->find_path(IPAddressListXPath.c_str());
for (const auto& ipEntry : addresses->data()) {
auto ipAddress = getValueAsString(getSubtree(ipEntry, "ip"));
auto prefixLen = getValueAsString(getSubtree(ipEntry, "prefix-length"));
spdlog::get("system")->trace("Link {}: address {}/{} configured", linkName, ipAddress, prefixLen);
configValues["Network"].push_back("Address="s + ipAddress + "/" + prefixLen);
}
}
// systemd-networkd auto-generates IPv6 link-layer addresses https://www.freedesktop.org/software/systemd/man/systemd.network.html#LinkLocalAddressing=
// disable this behaviour when IPv6 is disabled or when link enslaved
bool isSlave = false;
if (auto set = linkEntry->find_path("czechlight-network:bridge"); set->number() > 0) {
configValues["Network"].push_back("Bridge="s + getValueAsString(set->data().front()));
isSlave = true;
}
if (!protocolEnabled(linkEntry, "ipv6") && !isSlave) {
configValues["Network"].push_back("LinkLocalAddressing=no");
}
configValues["Network"].push_back("DHCP=no"); // temporarily disabled
configValues["Network"].push_back("LLDP=true");
configValues["Network"].push_back("EmitLLDP=nearest-bridge");
networkConfigFiles[linkName] = generateNetworkConfigFile(linkName, configValues);
}
}
auto changedLinks = updateNetworkFiles(networkConfigFiles, m_configDirectory);
m_reloadCb(changedLinks);
return SR_ERR_OK;
}
std::vector<std::string> IETFInterfacesConfig::updateNetworkFiles(const std::map<std::string, std::string>& networkConfig, const std::filesystem::path& configDir) const
{
std::vector<std::string> changedLinks;
for (const auto& link : m_managedLinks) {
const auto targetFile = configDir / (link + ".network");
const bool fileExists = std::filesystem::exists(targetFile);
const bool updateExists = networkConfig.contains(link);
// no configuration for link present and the file doesn't even exist -> keep default configuration
if (!fileExists && !updateExists) {
continue;
}
// configuration for link present, file exists, but it has the same content as the new one -> keep current configuration, no need to rewrite
if (fileExists && updateExists && velia::utils::readFileToString(targetFile) == networkConfig.at(link)) {
continue;
}
if (updateExists) {
velia::utils::safeWriteFile(targetFile, networkConfig.at(link));
} else { // configuration removed
std::filesystem::remove(targetFile);
}
changedLinks.push_back(link);
}
return changedLinks;
}
}