blob: 1b05861480cf303b056f2bd04a4415a4330f247e [file] [log] [blame]
Tomáš Peckaf10b9302021-02-23 19:02:02 +01001/*
2 * Copyright (C) 2021 CESNET, https://photonics.cesnet.cz/
3 *
4 * Written by Tomáš Pecka <tomas.pecka@cesnet.cz>
5 *
6 */
7
Tomáš Pecka09729382021-03-08 19:36:50 +01008#include <arpa/inet.h>
Tomáš Peckaf10b9302021-02-23 19:02:02 +01009#include <linux/if_arp.h>
10#include <linux/netdevice.h>
11#include "IETFInterfaces.h"
12#include "Rtnetlink.h"
13#include "utils/log.h"
14#include "utils/sysrepo.h"
15
16using namespace std::string_literals;
17
18namespace {
19
20const auto CZECHLIGHT_NETWORK_MODULE_NAME = "czechlight-network"s;
21const auto IETF_IP_MODULE_NAME = "ietf-ip"s;
22const auto IETF_INTERFACES_MODULE_NAME = "ietf-interfaces"s;
23const auto IETF_INTERFACES = "/"s + IETF_INTERFACES_MODULE_NAME + ":interfaces"s;
24
25const auto PHYS_ADDR_BUF_SIZE = 6 * 2 /* 2 chars per 6 bytes in the address */ + 5 /* delimiters (':') between bytes */ + 1 /* \0 */;
26
27std::string operStatusToString(uint8_t operStatus, velia::Log log)
28{
29 // unfortunately we can't use libnl's rtnl_link_operstate2str, because it creates different strings than the YANG model expects
30 switch (operStatus) {
31 case IF_OPER_UP:
32 return "up";
33 case IF_OPER_DOWN:
34 return "down";
35 case IF_OPER_TESTING:
36 return "testing";
37 case IF_OPER_DORMANT:
38 return "dormant";
39 case IF_OPER_NOTPRESENT:
40 return "not-present";
41 case IF_OPER_LOWERLAYERDOWN:
42 return "lower-layer-down";
43 case IF_OPER_UNKNOWN:
44 return "unknown";
45 default:
46 log->warn("Encountered unknown operational status {}, using 'unknown'", operStatus);
47 return "unknown";
48 }
49}
50
51std::string arpTypeToString(unsigned int arptype, velia::Log log)
52{
53 switch (arptype) {
54 case ARPHRD_ETHER:
55 return "iana-if-type:ethernetCsmacd";
56 case ARPHRD_LOOPBACK:
57 return "iana-if-type:softwareLoopback";
58 case ARPHRD_SIT:
59 return "iana-if-type:sixToFour";
60 default:
61 log->warn("Encountered unknown interface type {}, using 'iana-if-type:other'", arptype);
62 return "iana-if-type:other";
63 }
64}
65
66std::string nlActionToString(int action)
67{
68 switch (action) {
69 case NL_ACT_NEW:
70 return "NEW";
71 case NL_ACT_DEL:
72 return "DEL";
73 case NL_ACT_CHANGE:
74 return "CHANGE";
75 case NL_ACT_UNSPEC:
76 return "UNSPEC";
77 case NL_ACT_GET:
78 return "GET";
79 case NL_ACT_SET:
80 return "SET";
81 default:
82 return "<unknown action>";
83 }
84}
85
Tomáš Pecka09729382021-03-08 19:36:50 +010086std::string binaddrToString(void* binaddr, int addrFamily)
87{
88 // any IPv4 address fits into a buffer allocated for an IPv6 address
89 static_assert(INET6_ADDRSTRLEN >= INET_ADDRSTRLEN);
90 std::array<char, INET6_ADDRSTRLEN> buf;
91
92 if (const char* res = inet_ntop(addrFamily, binaddr, buf.data(), buf.size()); res != nullptr) {
93 return res;
94 } else {
95 throw std::system_error {errno, std::generic_category(), "inet_ntop"};
96 }
97}
98
99std::string getIPVersion(int addrFamily)
100{
101 switch (addrFamily) {
102 case AF_INET:
103 return "ipv4";
104 case AF_INET6:
105 return "ipv6";
106 default:
107 throw std::runtime_error("Unexpected address family " + std::to_string(addrFamily));
108 }
109}
110
Tomáš Peckaf10b9302021-02-23 19:02:02 +0100111}
112
113namespace velia::system {
114
115IETFInterfaces::IETFInterfaces(std::shared_ptr<::sysrepo::Session> srSess)
116 : m_srSession(std::move(srSess))
117 , m_log(spdlog::get("system"))
Tomáš Pecka09729382021-03-08 19:36:50 +0100118 , m_rtnetlink(std::make_shared<Rtnetlink>(
119 [this](rtnl_link* link, int action) { onLinkUpdate(link, action); },
120 [this](rtnl_addr* addr, int action) { onAddrUpdate(addr, action); }))
Tomáš Peckaf10b9302021-02-23 19:02:02 +0100121{
122 utils::ensureModuleImplemented(m_srSession, IETF_INTERFACES_MODULE_NAME, "2018-02-20");
123 utils::ensureModuleImplemented(m_srSession, IETF_IP_MODULE_NAME, "2018-02-22");
124 utils::ensureModuleImplemented(m_srSession, CZECHLIGHT_NETWORK_MODULE_NAME, "2021-02-22");
125}
126
127void IETFInterfaces::onLinkUpdate(rtnl_link* link, int action)
128{
129 char* name = rtnl_link_get_name(link);
130 m_log->trace("Netlink update on link '{}', action {}", name, nlActionToString(action));
131
132 if (action == NL_ACT_DEL) {
Tomáš Peckac9a57c32021-04-06 20:37:36 +0200133 utils::valuesPush({}, {IETF_INTERFACES + "/interface[name='" + name + "']"}, m_srSession, SR_DS_OPERATIONAL);
Tomáš Peckaf10b9302021-02-23 19:02:02 +0100134 } else if (action == NL_ACT_CHANGE || action == NL_ACT_NEW) {
135 std::map<std::string, std::string> values;
136 std::vector<std::string> deletePaths;
137
Tomáš Peckadb084132021-03-10 08:37:10 +0100138 auto linkAddr = rtnl_link_get_addr(link);
Tomáš Peckaf10b9302021-02-23 19:02:02 +0100139 std::array<char, PHYS_ADDR_BUF_SIZE> buf;
Tomáš Peckadb084132021-03-10 08:37:10 +0100140 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áš Peckaf10b9302021-02-23 19:02:02 +0100141 values[IETF_INTERFACES + "/interface[name='" + name + "']/phys-address"] = physAddr;
142 } else {
143 // delete physical address from sysrepo if not provided by rtnetlink
144 // Note: During testing I have noticed that my wireless interface loses a physical address. There were several change callbacks invoked
145 // when simply bringing the interface down and up. In some of those, nl_addr2str returned "none".
146 deletePaths.push_back({IETF_INTERFACES + "/interface[name='" + name + "']/phys-address"});
147 }
148
149 values[IETF_INTERFACES + "/interface[name='" + name + "']/type"] = arpTypeToString(rtnl_link_get_arptype(link), m_log);
150 values[IETF_INTERFACES + "/interface[name='" + name + "']/oper-status"] = operStatusToString(rtnl_link_get_operstate(link), m_log);
151
152 utils::valuesPush(values, deletePaths, m_srSession, SR_DS_OPERATIONAL);
153 } else {
154 m_log->warn("Unhandled cache update action {} ({})", action, nlActionToString(action));
155 }
156}
157
Tomáš Pecka09729382021-03-08 19:36:50 +0100158void IETFInterfaces::onAddrUpdate(rtnl_addr* addr, int action)
159{
160 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)); });
161
162 auto linkName = rtnl_link_get_name(link.get());
163 auto addrFamily = rtnl_addr_get_family(addr);
164 if (addrFamily != AF_INET && addrFamily != AF_INET6) {
165 return;
166 }
167
168 m_log->trace("Netlink update on address of link '{}', action {}", linkName, nlActionToString(action));
169
170 auto nlAddr = rtnl_addr_get_local(addr);
171 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)
172 std::string ipVersion = getIPVersion(addrFamily);
173
174 std::map<std::string, std::string> values;
175 std::vector<std::string> deletePaths;
176 const auto yangPrefix = IETF_INTERFACES + "/interface[name='" + linkName + "']/ietf-ip:" + ipVersion + "/address[ip='" + ipAddress + "']";
177
178 if (action == NL_ACT_DEL) {
179 deletePaths.push_back({yangPrefix});
180 } else if (action == NL_ACT_CHANGE || action == NL_ACT_NEW) {
181 values[yangPrefix + "/prefix-length"] = std::to_string(rtnl_addr_get_prefixlen(addr));
182 } else {
183 m_log->warn("Unhandled cache update action {} ({})", action, nlActionToString(action));
184 }
185
186 utils::valuesPush(values, deletePaths, m_srSession, SR_DS_OPERATIONAL);
187}
Tomáš Peckaf10b9302021-02-23 19:02:02 +0100188}