Tomáš Pecka | 39bbb51 | 2021-05-23 22:07:22 +0200 | [diff] [blame] | 1 | /* |
| 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 | |
| 15 | using namespace std::string_literals; |
| 16 | |
| 17 | namespace { |
| 18 | |
| 19 | const auto CZECHLIGHT_NETWORK_MODULE_NAME = "czechlight-network"s; |
| 20 | const auto IETF_IP_MODULE_NAME = "ietf-ip"s; |
| 21 | const auto IETF_INTERFACES_MODULE_NAME = "ietf-interfaces"s; |
| 22 | const auto IETF_ROUTING_MODULE_NAME = "ietf-routing"s; |
| 23 | const auto IETF_IPV4_UNICAST_ROUTING_MODULE_NAME = "ietf-ipv4-unicast-routing"; |
| 24 | const auto IETF_IPV6_UNICAST_ROUTING_MODULE_NAME = "ietf-ipv6-unicast-routing"; |
| 25 | const auto IETF_INTERFACES = "/"s + IETF_INTERFACES_MODULE_NAME + ":interfaces"s; |
| 26 | |
| 27 | std::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áš Pecka | 85f7775 | 2021-06-03 10:09:58 +0200 | [diff] [blame] | 46 | /** @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 | */ |
| 50 | bool protocolEnabled(const std::shared_ptr<libyang::Data_Node>& linkEntry, const std::string& proto) |
| 51 | { |
| 52 | const auto xpath = "ietf-ip:" + proto + "/enabled"; |
| 53 | |
Tomáš Pecka | fd90efb | 2021-10-07 10:40:44 +0200 | [diff] [blame] | 54 | if (auto node = velia::utils::getUniqueSubtree(linkEntry, xpath.c_str())) { |
| 55 | return velia::utils::getValueAsString(node.value()) == "true"s; |
Tomáš Pecka | 85f7775 | 2021-06-03 10:09:58 +0200 | [diff] [blame] | 56 | } |
Tomáš Pecka | fd90efb | 2021-10-07 10:40:44 +0200 | [diff] [blame] | 57 | |
| 58 | return false; |
Tomáš Pecka | 85f7775 | 2021-06-03 10:09:58 +0200 | [diff] [blame] | 59 | } |
Tomáš Pecka | 39bbb51 | 2021-05-23 22:07:22 +0200 | [diff] [blame] | 60 | } |
| 61 | |
| 62 | namespace velia::system { |
| 63 | |
| 64 | IETFInterfacesConfig::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 | |
| 83 | int IETFInterfacesConfig::moduleChange(std::shared_ptr<::sysrepo::Session> session) const |
| 84 | { |
| 85 | std::map<std::string, std::string> networkConfigFiles; |
| 86 | |
Tomáš Pecka | 2041a67 | 2021-10-07 13:44:29 +0200 | [diff] [blame] | 87 | if (auto data = session->get_data("/ietf-interfaces:interfaces/interface")) { |
Tomáš Pecka | 39bbb51 | 2021-05-23 22:07:22 +0200 | [diff] [blame] | 88 | 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áš Pecka | fd90efb | 2021-10-07 10:40:44 +0200 | [diff] [blame] | 92 | auto linkName = utils::getValueAsString(utils::getUniqueSubtree(linkEntry, "name").value()); |
Tomáš Pecka | 39bbb51 | 2021-05-23 22:07:22 +0200 | [diff] [blame] | 93 | |
Tomáš Pecka | 2041a67 | 2021-10-07 13:44:29 +0200 | [diff] [blame] | 94 | if (auto node = utils::getUniqueSubtree(linkEntry, "description")) { |
| 95 | configValues["Network"].push_back("Description="s + utils::getValueAsString(node.value())); |
Tomáš Pecka | 39bbb51 | 2021-05-23 22:07:22 +0200 | [diff] [blame] | 96 | } |
| 97 | |
Tomáš Pecka | 85f7775 | 2021-06-03 10:09:58 +0200 | [diff] [blame] | 98 | // 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áš Pecka | fd90efb | 2021-10-07 10:40:44 +0200 | [diff] [blame] | 109 | auto ipAddress = utils::getValueAsString(utils::getUniqueSubtree(ipEntry, "ip").value()); |
| 110 | auto prefixLen = utils::getValueAsString(utils::getUniqueSubtree(ipEntry, "prefix-length").value()); |
Tomáš Pecka | 85f7775 | 2021-06-03 10:09:58 +0200 | [diff] [blame] | 111 | |
| 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áš Pecka | c2929a6 | 2021-06-05 17:21:03 +0200 | [diff] [blame] | 118 | // disable this behaviour when IPv6 is disabled or when link enslaved |
| 119 | bool isSlave = false; |
| 120 | |
Tomáš Pecka | 2041a67 | 2021-10-07 13:44:29 +0200 | [diff] [blame] | 121 | if (auto node = utils::getUniqueSubtree(linkEntry, "czechlight-network:bridge")) { |
| 122 | configValues["Network"].push_back("Bridge="s + utils::getValueAsString(node.value())); |
Tomáš Pecka | c2929a6 | 2021-06-05 17:21:03 +0200 | [diff] [blame] | 123 | isSlave = true; |
| 124 | } |
| 125 | |
| 126 | if (!protocolEnabled(linkEntry, "ipv6") && !isSlave) { |
Tomáš Pecka | 85f7775 | 2021-06-03 10:09:58 +0200 | [diff] [blame] | 127 | configValues["Network"].push_back("LinkLocalAddressing=no"); |
| 128 | } |
| 129 | |
Tomáš Pecka | bd43a94 | 2021-08-04 10:12:49 +0200 | [diff] [blame] | 130 | // 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áš Pecka | 39bbb51 | 2021-05-23 22:07:22 +0200 | [diff] [blame] | 143 | 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 | |
| 155 | std::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 | } |