blob: 421fd85c9c93b136d6a048b774e69469cb9bf8b0 [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
54 try {
55 auto enabled = getValueAsString(getSubtree(linkEntry, xpath.c_str()));
56 return enabled == "true"s;
57 } catch (const std::runtime_error&) { // leaf and the presence container missing
58 return false;
59 }
60}
Tomáš Pecka39bbb512021-05-23 22:07:22 +020061}
62
63namespace velia::system {
64
65IETFInterfacesConfig::IETFInterfacesConfig(std::shared_ptr<::sysrepo::Session> srSess, std::filesystem::path configDirectory, std::vector<std::string> managedLinks, reload_cb_t reloadCallback)
66 : m_log(spdlog::get("system"))
67 , m_reloadCb(std::move(reloadCallback))
68 , m_configDirectory(std::move(configDirectory))
69 , m_managedLinks(std::move(managedLinks))
70 , m_srSession(std::move(srSess))
71 , m_srSubscribe(std::make_shared<::sysrepo::Subscribe>(m_srSession))
72{
73 utils::ensureModuleImplemented(m_srSession, IETF_INTERFACES_MODULE_NAME, "2018-02-20");
74 utils::ensureModuleImplemented(m_srSession, IETF_IP_MODULE_NAME, "2018-02-22");
75 utils::ensureModuleImplemented(m_srSession, IETF_ROUTING_MODULE_NAME, "2018-03-13");
76 utils::ensureModuleImplemented(m_srSession, IETF_IPV4_UNICAST_ROUTING_MODULE_NAME, "2018-03-13");
77 utils::ensureModuleImplemented(m_srSession, IETF_IPV6_UNICAST_ROUTING_MODULE_NAME, "2018-03-13");
78 utils::ensureModuleImplemented(m_srSession, CZECHLIGHT_NETWORK_MODULE_NAME, "2021-02-22");
79
80 m_srSubscribe->module_change_subscribe(
81 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);
82}
83
84int IETFInterfacesConfig::moduleChange(std::shared_ptr<::sysrepo::Session> session) const
85{
86 std::map<std::string, std::string> networkConfigFiles;
87
88 if (auto data = session->get_data("/ietf-interfaces:interfaces/interface"); data) {
89 auto linkEntries = data->find_path("/ietf-interfaces:interfaces/interface");
90 for (const auto& linkEntry : linkEntries->data()) {
91 std::map<std::string, std::vector<std::string>> configValues;
92
93 auto linkName = getValueAsString(getSubtree(linkEntry, "name"));
94
95 if (auto set = linkEntry->find_path("description"); set->number() != 0) {
96 configValues["Network"].push_back("Description="s + getValueAsString(set->data().front()));
97 }
98
Tomáš Pecka85f77752021-06-03 10:09:58 +020099 // if addresses present, generate them...
100 for (const auto& ipProto : {"ipv4", "ipv6"}) {
101 // ...but only if the protocol is enabled
102 if (!protocolEnabled(linkEntry, ipProto)) {
103 continue;
104 }
105
106 const auto IPAddressListXPath = "ietf-ip:"s + ipProto + "/ietf-ip:address";
107 const auto addresses = linkEntry->find_path(IPAddressListXPath.c_str());
108
109 for (const auto& ipEntry : addresses->data()) {
110 auto ipAddress = getValueAsString(getSubtree(ipEntry, "ip"));
111 auto prefixLen = getValueAsString(getSubtree(ipEntry, "prefix-length"));
112
113 spdlog::get("system")->trace("Link {}: address {}/{} configured", linkName, ipAddress, prefixLen);
114 configValues["Network"].push_back("Address="s + ipAddress + "/" + prefixLen);
115 }
116 }
117
118 // 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 +0200119 // disable this behaviour when IPv6 is disabled or when link enslaved
120 bool isSlave = false;
121
122 if (auto set = linkEntry->find_path("czechlight-network:bridge"); set->number() > 0) {
123 configValues["Network"].push_back("Bridge="s + getValueAsString(set->data().front()));
124 isSlave = true;
125 }
126
127 if (!protocolEnabled(linkEntry, "ipv6") && !isSlave) {
Tomáš Pecka85f77752021-06-03 10:09:58 +0200128 configValues["Network"].push_back("LinkLocalAddressing=no");
129 }
130
131 configValues["Network"].push_back("DHCP=no"); // temporarily disabled
Tomáš Pecka39bbb512021-05-23 22:07:22 +0200132 configValues["Network"].push_back("LLDP=true");
133 configValues["Network"].push_back("EmitLLDP=nearest-bridge");
134
135 networkConfigFiles[linkName] = generateNetworkConfigFile(linkName, configValues);
136 }
137 }
138
139 auto changedLinks = updateNetworkFiles(networkConfigFiles, m_configDirectory);
140 m_reloadCb(changedLinks);
141 return SR_ERR_OK;
142}
143
144std::vector<std::string> IETFInterfacesConfig::updateNetworkFiles(const std::map<std::string, std::string>& networkConfig, const std::filesystem::path& configDir) const
145{
146 std::vector<std::string> changedLinks;
147
148 for (const auto& link : m_managedLinks) {
149 const auto targetFile = configDir / (link + ".network");
150 const bool fileExists = std::filesystem::exists(targetFile);
151 const bool updateExists = networkConfig.contains(link);
152
153 // no configuration for link present and the file doesn't even exist -> keep default configuration
154 if (!fileExists && !updateExists) {
155 continue;
156 }
157
158 // configuration for link present, file exists, but it has the same content as the new one -> keep current configuration, no need to rewrite
159 if (fileExists && updateExists && velia::utils::readFileToString(targetFile) == networkConfig.at(link)) {
160 continue;
161 }
162
163 if (updateExists) {
164 velia::utils::safeWriteFile(targetFile, networkConfig.at(link));
165 } else { // configuration removed
166 std::filesystem::remove(targetFile);
167 }
168
169 changedLinks.push_back(link);
170 }
171
172 return changedLinks;
173}
174
175}