blob: be9fa5b19260855e9d5b671aabbee79861b4b9d0 [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
8#include <linux/if_arp.h>
9#include <linux/netdevice.h>
10#include "IETFInterfaces.h"
11#include "Rtnetlink.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_INTERFACES = "/"s + IETF_INTERFACES_MODULE_NAME + ":interfaces"s;
23
24const auto PHYS_ADDR_BUF_SIZE = 6 * 2 /* 2 chars per 6 bytes in the address */ + 5 /* delimiters (':') between bytes */ + 1 /* \0 */;
25
26std::string operStatusToString(uint8_t operStatus, velia::Log log)
27{
28 // unfortunately we can't use libnl's rtnl_link_operstate2str, because it creates different strings than the YANG model expects
29 switch (operStatus) {
30 case IF_OPER_UP:
31 return "up";
32 case IF_OPER_DOWN:
33 return "down";
34 case IF_OPER_TESTING:
35 return "testing";
36 case IF_OPER_DORMANT:
37 return "dormant";
38 case IF_OPER_NOTPRESENT:
39 return "not-present";
40 case IF_OPER_LOWERLAYERDOWN:
41 return "lower-layer-down";
42 case IF_OPER_UNKNOWN:
43 return "unknown";
44 default:
45 log->warn("Encountered unknown operational status {}, using 'unknown'", operStatus);
46 return "unknown";
47 }
48}
49
50std::string arpTypeToString(unsigned int arptype, velia::Log log)
51{
52 switch (arptype) {
53 case ARPHRD_ETHER:
54 return "iana-if-type:ethernetCsmacd";
55 case ARPHRD_LOOPBACK:
56 return "iana-if-type:softwareLoopback";
57 case ARPHRD_SIT:
58 return "iana-if-type:sixToFour";
59 default:
60 log->warn("Encountered unknown interface type {}, using 'iana-if-type:other'", arptype);
61 return "iana-if-type:other";
62 }
63}
64
65std::string nlActionToString(int action)
66{
67 switch (action) {
68 case NL_ACT_NEW:
69 return "NEW";
70 case NL_ACT_DEL:
71 return "DEL";
72 case NL_ACT_CHANGE:
73 return "CHANGE";
74 case NL_ACT_UNSPEC:
75 return "UNSPEC";
76 case NL_ACT_GET:
77 return "GET";
78 case NL_ACT_SET:
79 return "SET";
80 default:
81 return "<unknown action>";
82 }
83}
84
85}
86
87namespace velia::system {
88
89IETFInterfaces::IETFInterfaces(std::shared_ptr<::sysrepo::Session> srSess)
90 : m_srSession(std::move(srSess))
91 , m_log(spdlog::get("system"))
92 , m_rtnetlink(std::make_shared<Rtnetlink>([this](rtnl_link* link, int action) { onLinkUpdate(link, action); }))
93{
94 utils::ensureModuleImplemented(m_srSession, IETF_INTERFACES_MODULE_NAME, "2018-02-20");
95 utils::ensureModuleImplemented(m_srSession, IETF_IP_MODULE_NAME, "2018-02-22");
96 utils::ensureModuleImplemented(m_srSession, CZECHLIGHT_NETWORK_MODULE_NAME, "2021-02-22");
97}
98
99void IETFInterfaces::onLinkUpdate(rtnl_link* link, int action)
100{
101 char* name = rtnl_link_get_name(link);
102 m_log->trace("Netlink update on link '{}', action {}", name, nlActionToString(action));
103
104 if (action == NL_ACT_DEL) {
105 utils::valuesPush({}, {IETF_INTERFACES + "/interface[name='" + name + "']/"}, m_srSession, SR_DS_OPERATIONAL);
106 } else if (action == NL_ACT_CHANGE || action == NL_ACT_NEW) {
107 std::map<std::string, std::string> values;
108 std::vector<std::string> deletePaths;
109
110 std::array<char, PHYS_ADDR_BUF_SIZE> buf;
111 if (auto physAddr = nl_addr2str(rtnl_link_get_addr(link), buf.data(), buf.size()); physAddr != "none"s) { // set physical address if the link has one
112 values[IETF_INTERFACES + "/interface[name='" + name + "']/phys-address"] = physAddr;
113 } else {
114 // delete physical address from sysrepo if not provided by rtnetlink
115 // Note: During testing I have noticed that my wireless interface loses a physical address. There were several change callbacks invoked
116 // when simply bringing the interface down and up. In some of those, nl_addr2str returned "none".
117 deletePaths.push_back({IETF_INTERFACES + "/interface[name='" + name + "']/phys-address"});
118 }
119
120 values[IETF_INTERFACES + "/interface[name='" + name + "']/type"] = arpTypeToString(rtnl_link_get_arptype(link), m_log);
121 values[IETF_INTERFACES + "/interface[name='" + name + "']/oper-status"] = operStatusToString(rtnl_link_get_operstate(link), m_log);
122
123 utils::valuesPush(values, deletePaths, m_srSession, SR_DS_OPERATIONAL);
124 } else {
125 m_log->warn("Unhandled cache update action {} ({})", action, nlActionToString(action));
126 }
127}
128
129}