Tomáš Pecka | f10b930 | 2021-02-23 19:02:02 +0100 | [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 | |
Tomáš Pecka | 0972938 | 2021-03-08 19:36:50 +0100 | [diff] [blame] | 8 | #include <arpa/inet.h> |
Tomáš Pecka | f910893 | 2021-06-01 10:19:36 +0200 | [diff] [blame] | 9 | #include <filesystem> |
Tomáš Pecka | f10b930 | 2021-02-23 19:02:02 +0100 | [diff] [blame] | 10 | #include <linux/if_arp.h> |
| 11 | #include <linux/netdevice.h> |
| 12 | #include "IETFInterfaces.h" |
| 13 | #include "Rtnetlink.h" |
| 14 | #include "utils/log.h" |
| 15 | #include "utils/sysrepo.h" |
| 16 | |
| 17 | using namespace std::string_literals; |
| 18 | |
| 19 | namespace { |
| 20 | |
Tomáš Pecka | b29f36b | 2021-03-31 20:02:06 +0200 | [diff] [blame] | 21 | /** @brief Computes the length of the const C-string (array of const char) *including* the terminating zero |
| 22 | * |
| 23 | * Credits: https://dbj.org/cpp-zero-time-strlen-and-strnlen/ |
| 24 | */ |
| 25 | template <size_t N> |
| 26 | inline constexpr size_t arrlen(const char (&)[N]) noexcept |
| 27 | { |
| 28 | return N; |
| 29 | } |
| 30 | |
Tomáš Pecka | f10b930 | 2021-02-23 19:02:02 +0100 | [diff] [blame] | 31 | const auto CZECHLIGHT_NETWORK_MODULE_NAME = "czechlight-network"s; |
| 32 | const auto IETF_IP_MODULE_NAME = "ietf-ip"s; |
| 33 | const auto IETF_INTERFACES_MODULE_NAME = "ietf-interfaces"s; |
Tomáš Pecka | 3611c0e | 2021-04-14 09:02:14 +0200 | [diff] [blame] | 34 | const auto IETF_ROUTING_MODULE_NAME = "ietf-routing"s; |
| 35 | const auto IETF_IPV4_UNICAST_ROUTING_MODULE_NAME = "ietf-ipv4-unicast-routing"; |
| 36 | const auto IETF_IPV6_UNICAST_ROUTING_MODULE_NAME = "ietf-ipv6-unicast-routing"; |
Tomáš Pecka | f10b930 | 2021-02-23 19:02:02 +0100 | [diff] [blame] | 37 | const auto IETF_INTERFACES = "/"s + IETF_INTERFACES_MODULE_NAME + ":interfaces"s; |
| 38 | |
| 39 | const auto PHYS_ADDR_BUF_SIZE = 6 * 2 /* 2 chars per 6 bytes in the address */ + 5 /* delimiters (':') between bytes */ + 1 /* \0 */; |
Tomáš Pecka | b29f36b | 2021-03-31 20:02:06 +0200 | [diff] [blame] | 40 | const auto IPV6ADDRSTRLEN_WITH_PREFIX = INET6_ADDRSTRLEN + 1 + 3 /* plus slash and max three-digits prefix */; |
Tomáš Pecka | f10b930 | 2021-02-23 19:02:02 +0100 | [diff] [blame] | 41 | |
| 42 | std::string operStatusToString(uint8_t operStatus, velia::Log log) |
| 43 | { |
| 44 | // unfortunately we can't use libnl's rtnl_link_operstate2str, because it creates different strings than the YANG model expects |
| 45 | switch (operStatus) { |
| 46 | case IF_OPER_UP: |
| 47 | return "up"; |
| 48 | case IF_OPER_DOWN: |
| 49 | return "down"; |
| 50 | case IF_OPER_TESTING: |
| 51 | return "testing"; |
| 52 | case IF_OPER_DORMANT: |
| 53 | return "dormant"; |
| 54 | case IF_OPER_NOTPRESENT: |
| 55 | return "not-present"; |
| 56 | case IF_OPER_LOWERLAYERDOWN: |
| 57 | return "lower-layer-down"; |
| 58 | case IF_OPER_UNKNOWN: |
| 59 | return "unknown"; |
| 60 | default: |
| 61 | log->warn("Encountered unknown operational status {}, using 'unknown'", operStatus); |
| 62 | return "unknown"; |
| 63 | } |
| 64 | } |
| 65 | |
| 66 | std::string arpTypeToString(unsigned int arptype, velia::Log log) |
| 67 | { |
| 68 | switch (arptype) { |
| 69 | case ARPHRD_ETHER: |
| 70 | return "iana-if-type:ethernetCsmacd"; |
| 71 | case ARPHRD_LOOPBACK: |
| 72 | return "iana-if-type:softwareLoopback"; |
| 73 | case ARPHRD_SIT: |
| 74 | return "iana-if-type:sixToFour"; |
| 75 | default: |
| 76 | log->warn("Encountered unknown interface type {}, using 'iana-if-type:other'", arptype); |
| 77 | return "iana-if-type:other"; |
| 78 | } |
| 79 | } |
| 80 | |
| 81 | std::string nlActionToString(int action) |
| 82 | { |
| 83 | switch (action) { |
| 84 | case NL_ACT_NEW: |
| 85 | return "NEW"; |
| 86 | case NL_ACT_DEL: |
| 87 | return "DEL"; |
| 88 | case NL_ACT_CHANGE: |
| 89 | return "CHANGE"; |
| 90 | case NL_ACT_UNSPEC: |
| 91 | return "UNSPEC"; |
| 92 | case NL_ACT_GET: |
| 93 | return "GET"; |
| 94 | case NL_ACT_SET: |
| 95 | return "SET"; |
| 96 | default: |
| 97 | return "<unknown action>"; |
| 98 | } |
| 99 | } |
| 100 | |
Tomáš Pecka | 0972938 | 2021-03-08 19:36:50 +0100 | [diff] [blame] | 101 | std::string binaddrToString(void* binaddr, int addrFamily) |
| 102 | { |
| 103 | // any IPv4 address fits into a buffer allocated for an IPv6 address |
| 104 | static_assert(INET6_ADDRSTRLEN >= INET_ADDRSTRLEN); |
Václav Kubernát | e407f74 | 2021-05-18 10:47:13 +0200 | [diff] [blame] | 105 | std::array<char, INET6_ADDRSTRLEN> buf{}; |
Tomáš Pecka | 0972938 | 2021-03-08 19:36:50 +0100 | [diff] [blame] | 106 | |
| 107 | if (const char* res = inet_ntop(addrFamily, binaddr, buf.data(), buf.size()); res != nullptr) { |
| 108 | return res; |
| 109 | } else { |
| 110 | throw std::system_error {errno, std::generic_category(), "inet_ntop"}; |
| 111 | } |
| 112 | } |
| 113 | |
| 114 | std::string getIPVersion(int addrFamily) |
| 115 | { |
| 116 | switch (addrFamily) { |
| 117 | case AF_INET: |
| 118 | return "ipv4"; |
| 119 | case AF_INET6: |
| 120 | return "ipv6"; |
| 121 | default: |
| 122 | throw std::runtime_error("Unexpected address family " + std::to_string(addrFamily)); |
| 123 | } |
| 124 | } |
| 125 | |
Tomáš Pecka | 3663aae | 2021-03-10 13:46:31 +0100 | [diff] [blame] | 126 | /** @brief Returns YANG structure for ietf-ip:ipv(4|6)/neighbours. Set requestedAddrFamily to required ip version (AF_INET for ipv4 or AF_INET6 for ipv6). */ |
| 127 | std::map<std::string, std::string> collectNeighboursIP(std::shared_ptr<velia::system::Rtnetlink> rtnetlink, int requestedAddrFamily, velia::Log log) |
| 128 | { |
| 129 | std::map<std::string, std::string> values; |
| 130 | |
| 131 | for (const auto& [neigh, link] : rtnetlink->getNeighbours()) { |
| 132 | if (rtnl_neigh_get_state(neigh.get()) == NUD_NOARP) { |
| 133 | continue; |
| 134 | } |
| 135 | |
| 136 | auto linkName = rtnl_link_get_name(link.get()); |
| 137 | |
| 138 | auto ipAddr = rtnl_neigh_get_dst(neigh.get()); |
| 139 | auto ipAddrFamily = nl_addr_get_family(ipAddr); |
| 140 | |
| 141 | if (ipAddrFamily != requestedAddrFamily) { |
| 142 | continue; |
| 143 | } |
| 144 | |
| 145 | auto ipAddress = binaddrToString(nl_addr_get_binary_addr(ipAddr), ipAddrFamily); |
| 146 | |
| 147 | auto llAddr = rtnl_neigh_get_lladdr(neigh.get()); |
| 148 | std::array<char, PHYS_ADDR_BUF_SIZE> llAddrBuf {}; |
| 149 | if (auto llAddress = nl_addr2str(llAddr, llAddrBuf.data(), llAddrBuf.size()); llAddress != "none"s) { |
| 150 | values[IETF_INTERFACES + "/interface[name='" + linkName + "']/ietf-ip:" + getIPVersion(ipAddrFamily) + "/neighbor[ip='" + ipAddress + "']/link-layer-address"] = llAddress; |
| 151 | } else { |
| 152 | log->warn("Neighbor '{}' on link '{}' returned link layer address 'none'", ipAddress, linkName); |
| 153 | } |
| 154 | } |
| 155 | |
| 156 | return values; |
| 157 | } |
Tomáš Pecka | f910893 | 2021-06-01 10:19:36 +0200 | [diff] [blame] | 158 | |
| 159 | /** @brief Determine if link is a bridge |
| 160 | * |
| 161 | * This is done via sysfs query because rtnl_link_is_bridge doesn't always work. When bridge ports are being added/removed, kernel issues a rtnetlink message |
| 162 | * RTM_NEWLINK/RTM_DELLINK which is not a complete message. It is just an information that a bridge port changed. The rtnl_link object created by libnl from |
| 163 | * that message is not fully instantiated and rtnl_link_is_bridge function considers it a bridge. |
| 164 | * |
| 165 | * See git log for details and references. |
| 166 | */ |
| 167 | bool isBridge(rtnl_link* link) |
| 168 | { |
| 169 | return std::filesystem::exists("/sys/class/net/"s + rtnl_link_get_name(link) + "/bridge"); |
| 170 | } |
Tomáš Pecka | f10b930 | 2021-02-23 19:02:02 +0100 | [diff] [blame] | 171 | } |
| 172 | |
| 173 | namespace velia::system { |
| 174 | |
| 175 | IETFInterfaces::IETFInterfaces(std::shared_ptr<::sysrepo::Session> srSess) |
| 176 | : m_srSession(std::move(srSess)) |
Tomáš Pecka | 70e5456 | 2021-03-10 12:39:03 +0100 | [diff] [blame] | 177 | , m_srSubscribe(std::make_shared<::sysrepo::Subscribe>(m_srSession)) |
Tomáš Pecka | f10b930 | 2021-02-23 19:02:02 +0100 | [diff] [blame] | 178 | , m_log(spdlog::get("system")) |
Tomáš Pecka | 0972938 | 2021-03-08 19:36:50 +0100 | [diff] [blame] | 179 | , m_rtnetlink(std::make_shared<Rtnetlink>( |
| 180 | [this](rtnl_link* link, int action) { onLinkUpdate(link, action); }, |
Tomáš Pecka | b29f36b | 2021-03-31 20:02:06 +0200 | [diff] [blame] | 181 | [this](rtnl_addr* addr, int action) { onAddrUpdate(addr, action); }, |
| 182 | [this](rtnl_route* addr, int action) { onRouteUpdate(addr, action); })) |
Tomáš Pecka | f10b930 | 2021-02-23 19:02:02 +0100 | [diff] [blame] | 183 | { |
| 184 | utils::ensureModuleImplemented(m_srSession, IETF_INTERFACES_MODULE_NAME, "2018-02-20"); |
| 185 | utils::ensureModuleImplemented(m_srSession, IETF_IP_MODULE_NAME, "2018-02-22"); |
Tomáš Pecka | 3611c0e | 2021-04-14 09:02:14 +0200 | [diff] [blame] | 186 | utils::ensureModuleImplemented(m_srSession, IETF_ROUTING_MODULE_NAME, "2018-03-13"); |
| 187 | utils::ensureModuleImplemented(m_srSession, IETF_IPV4_UNICAST_ROUTING_MODULE_NAME, "2018-03-13"); |
| 188 | utils::ensureModuleImplemented(m_srSession, IETF_IPV6_UNICAST_ROUTING_MODULE_NAME, "2018-03-13"); |
Tomáš Pecka | f10b930 | 2021-02-23 19:02:02 +0100 | [diff] [blame] | 189 | utils::ensureModuleImplemented(m_srSession, CZECHLIGHT_NETWORK_MODULE_NAME, "2021-02-22"); |
Tomáš Pecka | 3d3cf61 | 2021-03-31 19:51:31 +0200 | [diff] [blame] | 190 | |
| 191 | m_rtnetlink->invokeInitialCallbacks(); |
Tomáš Pecka | b29f36b | 2021-03-31 20:02:06 +0200 | [diff] [blame] | 192 | // TODO: Implement /ietf-routing:routing/interfaces and /ietf-routing:routing/router-id |
Tomáš Pecka | 70e5456 | 2021-03-10 12:39:03 +0100 | [diff] [blame] | 193 | |
| 194 | m_srSubscribe->oper_get_items_subscribe( |
| 195 | IETF_INTERFACES_MODULE_NAME.c_str(), [this](auto session, auto, auto, auto, auto, auto& parent) { |
| 196 | std::map<std::string, std::string> values; |
| 197 | for (const auto& link : m_rtnetlink->getLinks()) { |
| 198 | const auto yangPrefix = IETF_INTERFACES + "/interface[name='" + rtnl_link_get_name(link.get()) + "']/statistics"; |
| 199 | |
| 200 | values[yangPrefix + "/in-octets"] = std::to_string(rtnl_link_get_stat(link.get(), RTNL_LINK_RX_BYTES)); |
| 201 | values[yangPrefix + "/out-octets"] = std::to_string(rtnl_link_get_stat(link.get(), RTNL_LINK_TX_BYTES)); |
| 202 | values[yangPrefix + "/in-discards"] = std::to_string(rtnl_link_get_stat(link.get(), RTNL_LINK_RX_DROPPED)); |
| 203 | values[yangPrefix + "/out-discards"] = std::to_string(rtnl_link_get_stat(link.get(), RTNL_LINK_TX_DROPPED)); |
| 204 | values[yangPrefix + "/in-errors"] = std::to_string(rtnl_link_get_stat(link.get(), RTNL_LINK_RX_ERRORS)); |
| 205 | values[yangPrefix + "/out-errors"] = std::to_string(rtnl_link_get_stat(link.get(), RTNL_LINK_TX_ERRORS)); |
| 206 | } |
| 207 | |
| 208 | utils::valuesToYang(values, {}, session, parent); |
| 209 | return SR_ERR_OK; |
| 210 | }, |
| 211 | (IETF_INTERFACES + "/interface/statistics").c_str()); |
Tomáš Pecka | 3663aae | 2021-03-10 13:46:31 +0100 | [diff] [blame] | 212 | |
| 213 | m_srSubscribe->oper_get_items_subscribe( |
| 214 | IETF_INTERFACES_MODULE_NAME.c_str(), [this](auto session, auto, auto, auto, auto, auto& parent) { |
| 215 | utils::valuesToYang(collectNeighboursIP(m_rtnetlink, AF_INET, m_log), {}, session, parent); |
| 216 | return SR_ERR_OK; |
| 217 | }, |
| 218 | (IETF_INTERFACES + "/interface/ietf-ip:ipv4/neighbor").c_str()); |
| 219 | |
| 220 | m_srSubscribe->oper_get_items_subscribe( |
| 221 | IETF_INTERFACES_MODULE_NAME.c_str(), [this](auto session, auto, auto, auto, auto, auto& parent) { |
| 222 | utils::valuesToYang(collectNeighboursIP(m_rtnetlink, AF_INET6, m_log), {}, session, parent); |
| 223 | return SR_ERR_OK; |
| 224 | }, |
| 225 | (IETF_INTERFACES + "/interface/ietf-ip:ipv6/neighbor").c_str()); |
Tomáš Pecka | f10b930 | 2021-02-23 19:02:02 +0100 | [diff] [blame] | 226 | } |
| 227 | |
| 228 | void IETFInterfaces::onLinkUpdate(rtnl_link* link, int action) |
| 229 | { |
| 230 | char* name = rtnl_link_get_name(link); |
| 231 | m_log->trace("Netlink update on link '{}', action {}", name, nlActionToString(action)); |
| 232 | |
| 233 | if (action == NL_ACT_DEL) { |
Tomáš Pecka | 53f08ee | 2021-04-28 12:38:11 +0200 | [diff] [blame] | 234 | utils::valuesPush(std::vector<utils::YANGPair>{}, {IETF_INTERFACES + "/interface[name='" + name + "']"}, m_srSession, SR_DS_OPERATIONAL); |
Tomáš Pecka | f10b930 | 2021-02-23 19:02:02 +0100 | [diff] [blame] | 235 | } else if (action == NL_ACT_CHANGE || action == NL_ACT_NEW) { |
| 236 | std::map<std::string, std::string> values; |
| 237 | std::vector<std::string> deletePaths; |
| 238 | |
Tomáš Pecka | db08413 | 2021-03-10 08:37:10 +0100 | [diff] [blame] | 239 | auto linkAddr = rtnl_link_get_addr(link); |
Tomáš Pecka | f10b930 | 2021-02-23 19:02:02 +0100 | [diff] [blame] | 240 | std::array<char, PHYS_ADDR_BUF_SIZE> buf; |
Tomáš Pecka | db08413 | 2021-03-10 08:37:10 +0100 | [diff] [blame] | 241 | if (auto physAddr = nl_addr2str(linkAddr, buf.data(), buf.size()); physAddr != "none"s && nl_addr_get_family(linkAddr) == AF_LLC) { // set physical address if the link has one |
Tomáš Pecka | f10b930 | 2021-02-23 19:02:02 +0100 | [diff] [blame] | 242 | values[IETF_INTERFACES + "/interface[name='" + name + "']/phys-address"] = physAddr; |
| 243 | } else { |
| 244 | // delete physical address from sysrepo if not provided by rtnetlink |
| 245 | // Note: During testing I have noticed that my wireless interface loses a physical address. There were several change callbacks invoked |
| 246 | // when simply bringing the interface down and up. In some of those, nl_addr2str returned "none". |
| 247 | deletePaths.push_back({IETF_INTERFACES + "/interface[name='" + name + "']/phys-address"}); |
| 248 | } |
| 249 | |
Tomáš Pecka | f910893 | 2021-06-01 10:19:36 +0200 | [diff] [blame] | 250 | values[IETF_INTERFACES + "/interface[name='" + name + "']/type"] = isBridge(link) ? "iana-if-type:bridge" : arpTypeToString(rtnl_link_get_arptype(link), m_log); |
Tomáš Pecka | f10b930 | 2021-02-23 19:02:02 +0100 | [diff] [blame] | 251 | values[IETF_INTERFACES + "/interface[name='" + name + "']/oper-status"] = operStatusToString(rtnl_link_get_operstate(link), m_log); |
| 252 | |
| 253 | utils::valuesPush(values, deletePaths, m_srSession, SR_DS_OPERATIONAL); |
| 254 | } else { |
| 255 | m_log->warn("Unhandled cache update action {} ({})", action, nlActionToString(action)); |
| 256 | } |
| 257 | } |
| 258 | |
Tomáš Pecka | 0972938 | 2021-03-08 19:36:50 +0100 | [diff] [blame] | 259 | void IETFInterfaces::onAddrUpdate(rtnl_addr* addr, int action) |
| 260 | { |
| 261 | std::unique_ptr<rtnl_link, std::function<void(rtnl_link*)>> link(rtnl_addr_get_link(addr), [](rtnl_link* obj) { nl_object_put(OBJ_CAST(obj)); }); |
| 262 | |
| 263 | auto linkName = rtnl_link_get_name(link.get()); |
| 264 | auto addrFamily = rtnl_addr_get_family(addr); |
| 265 | if (addrFamily != AF_INET && addrFamily != AF_INET6) { |
| 266 | return; |
| 267 | } |
| 268 | |
| 269 | m_log->trace("Netlink update on address of link '{}', action {}", linkName, nlActionToString(action)); |
| 270 | |
| 271 | auto nlAddr = rtnl_addr_get_local(addr); |
| 272 | std::string ipAddress = binaddrToString(nl_addr_get_binary_addr(nlAddr), addrFamily); // We don't use libnl's nl_addr2str because it appends a prefix length to the string (e.g. 192.168.0.1/24) |
| 273 | std::string ipVersion = getIPVersion(addrFamily); |
| 274 | |
| 275 | std::map<std::string, std::string> values; |
| 276 | std::vector<std::string> deletePaths; |
| 277 | const auto yangPrefix = IETF_INTERFACES + "/interface[name='" + linkName + "']/ietf-ip:" + ipVersion + "/address[ip='" + ipAddress + "']"; |
| 278 | |
| 279 | if (action == NL_ACT_DEL) { |
| 280 | deletePaths.push_back({yangPrefix}); |
| 281 | } else if (action == NL_ACT_CHANGE || action == NL_ACT_NEW) { |
| 282 | values[yangPrefix + "/prefix-length"] = std::to_string(rtnl_addr_get_prefixlen(addr)); |
| 283 | } else { |
| 284 | m_log->warn("Unhandled cache update action {} ({})", action, nlActionToString(action)); |
| 285 | } |
| 286 | |
| 287 | utils::valuesPush(values, deletePaths, m_srSession, SR_DS_OPERATIONAL); |
| 288 | } |
Tomáš Pecka | b29f36b | 2021-03-31 20:02:06 +0200 | [diff] [blame] | 289 | |
| 290 | void IETFInterfaces::onRouteUpdate(rtnl_route*, int) |
| 291 | { |
| 292 | /* NOTE: |
| 293 | * We don't know the position of the changed route in the list of routes |
| 294 | * Replace the whole subtree (and therefore fetch all routes to publish fresh data) |
| 295 | * Unfortunately, this function may be called several times during the "reconstruction" of the routing table. |
| 296 | */ |
| 297 | |
Tomáš Pecka | 088e774 | 2021-04-28 12:40:18 +0200 | [diff] [blame] | 298 | std::vector<utils::YANGPair> values; |
Tomáš Pecka | b29f36b | 2021-03-31 20:02:06 +0200 | [diff] [blame] | 299 | std::vector<std::string> deletePaths; |
| 300 | |
| 301 | auto routes = m_rtnetlink->getRoutes(); |
| 302 | auto links = m_rtnetlink->getLinks(); |
| 303 | |
| 304 | // ipv4 and ipv6 routes are in separate lists; keep a track of current index to the list so we correctly append the route to the end of the list |
| 305 | std::map<decltype(AF_INET), unsigned> routeIdx {{AF_INET, 1}, {AF_INET6, 1}}; |
| 306 | |
| 307 | for (const auto& route : routes) { |
| 308 | if (rtnl_route_get_table(route.get()) != RT_TABLE_MAIN) { |
| 309 | continue; |
| 310 | } |
| 311 | |
| 312 | if (rtnl_route_get_type(route.get()) != RTN_UNICAST) { |
| 313 | continue; |
| 314 | } |
| 315 | |
| 316 | auto family = rtnl_route_get_family(route.get()); |
| 317 | if (family != AF_INET && family != AF_INET6) { |
| 318 | continue; |
| 319 | } |
| 320 | |
| 321 | auto proto = rtnl_route_get_protocol(route.get()); |
| 322 | if (proto != RTPROT_KERNEL && proto != RTPROT_RA && proto != RTPROT_DHCP && proto != RTPROT_STATIC && proto != RTPROT_BOOT) { |
| 323 | std::array<char, arrlen("redirect")> buf; /* "redirect" is the longest value (libnl/lib/route/route_utils.c, init_proto_names) */ |
| 324 | m_log->warn("Unimplemented routing protocol {} '{}'", proto, rtnl_route_proto2str(proto, buf.data(), buf.size())); |
| 325 | continue; |
| 326 | } |
| 327 | |
| 328 | const auto ribName = family == AF_INET ? "ipv4-master"s : "ipv6-master"s; |
| 329 | const auto yangPrefix = "/ietf-routing:routing/ribs/rib[name='" + ribName + "']/routes/route["s + std::to_string(routeIdx[family]++) + "]/"; |
| 330 | const auto familyYangPrefix = family == AF_INET ? "ietf-ipv4-unicast-routing"s : "ietf-ipv6-unicast-routing"s; |
| 331 | |
| 332 | std::string destPrefix; |
| 333 | if (auto* addr = rtnl_route_get_dst(route.get()); addr != nullptr) { |
| 334 | if (nl_addr_iszero(addr)) { |
| 335 | destPrefix = family == AF_INET ? "0.0.0.0/0" : "::/0"; |
| 336 | } else { |
| 337 | std::array<char, IPV6ADDRSTRLEN_WITH_PREFIX> data; |
| 338 | destPrefix = nl_addr2str(addr, data.data(), data.size()); |
| 339 | |
| 340 | // append prefix len if nl_addr2str fails to do that (when prefix length is 32 in ipv4 or 128 in ipv6) |
| 341 | if (destPrefix.find_first_of('/') == std::string::npos) { |
| 342 | destPrefix += "/" + std::to_string(nl_addr_get_prefixlen(addr)); |
| 343 | } |
| 344 | } |
| 345 | } |
| 346 | |
Tomáš Pecka | 088e774 | 2021-04-28 12:40:18 +0200 | [diff] [blame] | 347 | values.emplace_back(yangPrefix + familyYangPrefix + ":destination-prefix", destPrefix); |
Tomáš Pecka | b29f36b | 2021-03-31 20:02:06 +0200 | [diff] [blame] | 348 | |
| 349 | auto scope = rtnl_route_get_scope(route.get()); |
| 350 | std::string protoStr; |
| 351 | switch (proto) { |
| 352 | case RTPROT_KERNEL: |
| 353 | protoStr = scope == RT_SCOPE_LINK ? "direct" : "static"; |
| 354 | break; |
| 355 | case RTPROT_STATIC: |
| 356 | case RTPROT_BOOT: |
| 357 | protoStr = "static"; |
| 358 | break; |
| 359 | case RTPROT_DHCP: |
| 360 | protoStr = "czechlight-network:dhcp"; |
| 361 | break; |
| 362 | case RTPROT_RA: |
| 363 | protoStr = "czechlight-network:ra"; |
| 364 | break; |
| 365 | default: |
| 366 | throw std::invalid_argument("Unexpected route protocol ("s + std::to_string(proto) + ")"); |
| 367 | } |
| 368 | |
Tomáš Pecka | 088e774 | 2021-04-28 12:40:18 +0200 | [diff] [blame] | 369 | values.emplace_back(yangPrefix + "source-protocol", protoStr); |
Tomáš Pecka | b29f36b | 2021-03-31 20:02:06 +0200 | [diff] [blame] | 370 | |
| 371 | const auto hops = rtnl_route_get_nnexthops(route.get()); |
| 372 | const bool multihop = hops > 1; |
| 373 | for (auto i = 0; i < hops; i++) { |
| 374 | rtnl_nexthop* nh = rtnl_route_nexthop_n(route.get(), i); |
| 375 | |
| 376 | if (nl_addr* addr = rtnl_route_nh_get_gateway(nh); addr) { |
| 377 | std::string yangKey; |
| 378 | if (!multihop) { |
| 379 | yangKey = yangPrefix + "next-hop/" + familyYangPrefix + ":next-hop-address"; |
| 380 | } else { |
| 381 | yangKey = yangPrefix + "next-hop/next-hop-list/next-hop[" + std::to_string(i + 1) + "]/" + familyYangPrefix + ":address"; |
| 382 | } |
| 383 | |
| 384 | std::array<char, IPV6ADDRSTRLEN_WITH_PREFIX> buf; |
Tomáš Pecka | 088e774 | 2021-04-28 12:40:18 +0200 | [diff] [blame] | 385 | values.emplace_back(yangKey, nl_addr2str(addr, buf.data(), buf.size())); |
Tomáš Pecka | b29f36b | 2021-03-31 20:02:06 +0200 | [diff] [blame] | 386 | } |
| 387 | |
| 388 | auto if_index = rtnl_route_nh_get_ifindex(nh); |
| 389 | if (auto linkIt = std::find_if(links.begin(), links.end(), [if_index](const Rtnetlink::nlLink& link) { return rtnl_link_get_ifindex(link.get()) == if_index; }); linkIt != links.end()) { |
| 390 | if (char* ifname = rtnl_link_get_name(linkIt->get()); ifname) { |
| 391 | std::string yangKey; |
| 392 | if (!multihop) { |
| 393 | yangKey = yangPrefix + "next-hop/outgoing-interface"; |
| 394 | } else { |
| 395 | yangKey = yangPrefix + "next-hop/next-hop-list/next-hop[" + std::to_string(i + 1) + "]/outgoing-interface"; |
| 396 | } |
| 397 | |
Tomáš Pecka | 088e774 | 2021-04-28 12:40:18 +0200 | [diff] [blame] | 398 | values.emplace_back(yangKey, rtnl_link_get_name(linkIt->get())); |
Tomáš Pecka | b29f36b | 2021-03-31 20:02:06 +0200 | [diff] [blame] | 399 | } |
| 400 | } |
| 401 | } |
| 402 | } |
| 403 | |
| 404 | utils::valuesPush(values, deletePaths, m_srSession, SR_DS_OPERATIONAL); |
| 405 | } |
Tomáš Pecka | f10b930 | 2021-02-23 19:02:02 +0100 | [diff] [blame] | 406 | } |