blob: e8307a3e519d53a978eda88a7ff89519228b181a [file] [log] [blame]
Tomáš Pecka39bbb512021-05-23 22:07:22 +02001/*
2 * Copyright (C) 2021 CESNET, https://photonics.cesnet.cz/
3 *
4 * Written by Tomáš Pecka <tomas.pecka@cesnet.cz>
5 *
6 */
7
8#include <numeric>
9#include "IETFInterfacesConfig.h"
10#include "utils/io.h"
11#include "utils/libyang.h"
12#include "utils/log.h"
13#include "utils/sysrepo.h"
14
15using namespace std::string_literals;
16
17namespace {
18
19const auto CZECHLIGHT_NETWORK_MODULE_NAME = "czechlight-network"s;
20const auto IETF_IP_MODULE_NAME = "ietf-ip"s;
21const auto IETF_INTERFACES_MODULE_NAME = "ietf-interfaces"s;
22const auto IETF_ROUTING_MODULE_NAME = "ietf-routing"s;
23const auto IETF_IPV4_UNICAST_ROUTING_MODULE_NAME = "ietf-ipv4-unicast-routing";
24const auto IETF_IPV6_UNICAST_ROUTING_MODULE_NAME = "ietf-ipv6-unicast-routing";
25const auto IETF_INTERFACES = "/"s + IETF_INTERFACES_MODULE_NAME + ":interfaces"s;
26
27std::string generateNetworkConfigFile(const std::string& linkName, const std::map<std::string, std::vector<std::string>>& values)
28{
29 std::ostringstream oss;
30
31 oss << "[Match]" << std::endl;
32 oss << "Name=" << linkName << std::endl;
33
34 for (const auto& [sectionName, values] : values) {
35 oss << std::endl
36 << '[' << sectionName << ']' << std::endl;
37
38 for (const auto& confValue : values) {
39 oss << confValue << std::endl;
40 }
41 }
42
43 return oss.str();
44}
45
Tomáš Pecka85f77752021-06-03 10:09:58 +020046/** @brief Checks if protocol is enabled.
47 *
48 * 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.
49 */
50bool protocolEnabled(const std::shared_ptr<libyang::Data_Node>& linkEntry, const std::string& proto)
51{
52 const auto xpath = "ietf-ip:" + proto + "/enabled";
53
Tomáš Peckafd90efb2021-10-07 10:40:44 +020054 if (auto node = velia::utils::getUniqueSubtree(linkEntry, xpath.c_str())) {
55 return velia::utils::getValueAsString(node.value()) == "true"s;
Tomáš Pecka85f77752021-06-03 10:09:58 +020056 }
Tomáš Peckafd90efb2021-10-07 10:40:44 +020057
58 return false;
Tomáš Pecka85f77752021-06-03 10:09:58 +020059}
Tomáš Pecka39bbb512021-05-23 22:07:22 +020060}
61
62namespace velia::system {
63
64IETFInterfacesConfig::IETFInterfacesConfig(std::shared_ptr<::sysrepo::Session> srSess, std::filesystem::path configDirectory, std::vector<std::string> managedLinks, reload_cb_t reloadCallback)
65 : m_log(spdlog::get("system"))
66 , m_reloadCb(std::move(reloadCallback))
67 , m_configDirectory(std::move(configDirectory))
68 , m_managedLinks(std::move(managedLinks))
69 , m_srSession(std::move(srSess))
70 , m_srSubscribe(std::make_shared<::sysrepo::Subscribe>(m_srSession))
71{
72 utils::ensureModuleImplemented(m_srSession, IETF_INTERFACES_MODULE_NAME, "2018-02-20");
73 utils::ensureModuleImplemented(m_srSession, IETF_IP_MODULE_NAME, "2018-02-22");
74 utils::ensureModuleImplemented(m_srSession, IETF_ROUTING_MODULE_NAME, "2018-03-13");
75 utils::ensureModuleImplemented(m_srSession, IETF_IPV4_UNICAST_ROUTING_MODULE_NAME, "2018-03-13");
76 utils::ensureModuleImplemented(m_srSession, IETF_IPV6_UNICAST_ROUTING_MODULE_NAME, "2018-03-13");
77 utils::ensureModuleImplemented(m_srSession, CZECHLIGHT_NETWORK_MODULE_NAME, "2021-02-22");
78
79 m_srSubscribe->module_change_subscribe(
80 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);
81}
82
83int IETFInterfacesConfig::moduleChange(std::shared_ptr<::sysrepo::Session> session) const
84{
85 std::map<std::string, std::string> networkConfigFiles;
86
Tomáš Pecka2041a672021-10-07 13:44:29 +020087 if (auto data = session->get_data("/ietf-interfaces:interfaces/interface")) {
Tomáš Pecka39bbb512021-05-23 22:07:22 +020088 auto linkEntries = data->find_path("/ietf-interfaces:interfaces/interface");
89 for (const auto& linkEntry : linkEntries->data()) {
90 std::map<std::string, std::vector<std::string>> configValues;
91
Tomáš Peckafd90efb2021-10-07 10:40:44 +020092 auto linkName = utils::getValueAsString(utils::getUniqueSubtree(linkEntry, "name").value());
Tomáš Pecka39bbb512021-05-23 22:07:22 +020093
Tomáš Pecka2041a672021-10-07 13:44:29 +020094 if (auto node = utils::getUniqueSubtree(linkEntry, "description")) {
95 configValues["Network"].push_back("Description="s + utils::getValueAsString(node.value()));
Tomáš Pecka39bbb512021-05-23 22:07:22 +020096 }
97
Tomáš Pecka85f77752021-06-03 10:09:58 +020098 // if addresses present, generate them...
99 for (const auto& ipProto : {"ipv4", "ipv6"}) {
100 // ...but only if the protocol is enabled
101 if (!protocolEnabled(linkEntry, ipProto)) {
102 continue;
103 }
104
105 const auto IPAddressListXPath = "ietf-ip:"s + ipProto + "/ietf-ip:address";
106 const auto addresses = linkEntry->find_path(IPAddressListXPath.c_str());
107
108 for (const auto& ipEntry : addresses->data()) {
Tomáš Peckafd90efb2021-10-07 10:40:44 +0200109 auto ipAddress = utils::getValueAsString(utils::getUniqueSubtree(ipEntry, "ip").value());
110 auto prefixLen = utils::getValueAsString(utils::getUniqueSubtree(ipEntry, "prefix-length").value());
Tomáš Pecka85f77752021-06-03 10:09:58 +0200111
112 spdlog::get("system")->trace("Link {}: address {}/{} configured", linkName, ipAddress, prefixLen);
113 configValues["Network"].push_back("Address="s + ipAddress + "/" + prefixLen);
114 }
115 }
116
117 // systemd-networkd auto-generates IPv6 link-layer addresses https://www.freedesktop.org/software/systemd/man/systemd.network.html#LinkLocalAddressing=
Tomáš Peckac2929a62021-06-05 17:21:03 +0200118 // disable this behaviour when IPv6 is disabled or when link enslaved
119 bool isSlave = false;
120
Tomáš Pecka2041a672021-10-07 13:44:29 +0200121 if (auto node = utils::getUniqueSubtree(linkEntry, "czechlight-network:bridge")) {
122 configValues["Network"].push_back("Bridge="s + utils::getValueAsString(node.value()));
Tomáš Peckac2929a62021-06-05 17:21:03 +0200123 isSlave = true;
124 }
125
126 if (!protocolEnabled(linkEntry, "ipv6") && !isSlave) {
Tomáš Pecka85f77752021-06-03 10:09:58 +0200127 configValues["Network"].push_back("LinkLocalAddressing=no");
128 }
129
Tomáš Peckabd43a942021-08-04 10:12:49 +0200130 // network autoconfiguration
131 if (auto node = utils::getUniqueSubtree(linkEntry, "ietf-ip:ipv6/ietf-ip:autoconf/ietf-ip:create-global-addresses"); protocolEnabled(linkEntry, "ipv6") && utils::getValueAsString(node.value()) == "true"s) {
132 configValues["Network"].push_back("IPv6AcceptRA=true");
133 } else {
134 configValues["Network"].push_back("IPv6AcceptRA=false");
135 }
136
137 if (auto node = utils::getUniqueSubtree(linkEntry, "ietf-ip:ipv4/czechlight-network:dhcp-client"); protocolEnabled(linkEntry, "ipv4") && utils::getValueAsString(node.value()) == "true"s) {
138 configValues["Network"].push_back("DHCP=ipv4");
139 } else {
140 configValues["Network"].push_back("DHCP=no");
141 }
142
Tomáš Pecka39bbb512021-05-23 22:07:22 +0200143 configValues["Network"].push_back("LLDP=true");
144 configValues["Network"].push_back("EmitLLDP=nearest-bridge");
145
146 networkConfigFiles[linkName] = generateNetworkConfigFile(linkName, configValues);
147 }
148 }
149
150 auto changedLinks = updateNetworkFiles(networkConfigFiles, m_configDirectory);
151 m_reloadCb(changedLinks);
152 return SR_ERR_OK;
153}
154
155std::vector<std::string> IETFInterfacesConfig::updateNetworkFiles(const std::map<std::string, std::string>& networkConfig, const std::filesystem::path& configDir) const
156{
157 std::vector<std::string> changedLinks;
158
159 for (const auto& link : m_managedLinks) {
160 const auto targetFile = configDir / (link + ".network");
161 const bool fileExists = std::filesystem::exists(targetFile);
162 const bool updateExists = networkConfig.contains(link);
163
164 // no configuration for link present and the file doesn't even exist -> keep default configuration
165 if (!fileExists && !updateExists) {
166 continue;
167 }
168
169 // configuration for link present, file exists, but it has the same content as the new one -> keep current configuration, no need to rewrite
170 if (fileExists && updateExists && velia::utils::readFileToString(targetFile) == networkConfig.at(link)) {
171 continue;
172 }
173
174 if (updateExists) {
175 velia::utils::safeWriteFile(targetFile, networkConfig.at(link));
176 } else { // configuration removed
177 std::filesystem::remove(targetFile);
178 }
179
180 changedLinks.push_back(link);
181 }
182
183 return changedLinks;
184}
185
186}