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 | f10b930 | 2021-02-23 19:02:02 +0100 | [diff] [blame] | 9 | #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 | |
| 16 | using namespace std::string_literals; |
| 17 | |
| 18 | namespace { |
| 19 | |
| 20 | const auto CZECHLIGHT_NETWORK_MODULE_NAME = "czechlight-network"s; |
| 21 | const auto IETF_IP_MODULE_NAME = "ietf-ip"s; |
| 22 | const auto IETF_INTERFACES_MODULE_NAME = "ietf-interfaces"s; |
| 23 | const auto IETF_INTERFACES = "/"s + IETF_INTERFACES_MODULE_NAME + ":interfaces"s; |
| 24 | |
| 25 | const auto PHYS_ADDR_BUF_SIZE = 6 * 2 /* 2 chars per 6 bytes in the address */ + 5 /* delimiters (':') between bytes */ + 1 /* \0 */; |
| 26 | |
| 27 | std::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 | |
| 51 | std::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 | |
| 66 | std::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áš Pecka | 0972938 | 2021-03-08 19:36:50 +0100 | [diff] [blame] | 86 | std::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 | |
| 99 | std::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áš Pecka | f10b930 | 2021-02-23 19:02:02 +0100 | [diff] [blame] | 111 | } |
| 112 | |
| 113 | namespace velia::system { |
| 114 | |
| 115 | IETFInterfaces::IETFInterfaces(std::shared_ptr<::sysrepo::Session> srSess) |
| 116 | : m_srSession(std::move(srSess)) |
| 117 | , m_log(spdlog::get("system")) |
Tomáš Pecka | 0972938 | 2021-03-08 19:36:50 +0100 | [diff] [blame] | 118 | , 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áš Pecka | f10b930 | 2021-02-23 19:02:02 +0100 | [diff] [blame] | 121 | { |
| 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 | |
| 127 | void 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áš Pecka | c9a57c3 | 2021-04-06 20:37:36 +0200 | [diff] [blame^] | 133 | utils::valuesPush({}, {IETF_INTERFACES + "/interface[name='" + name + "']"}, m_srSession, SR_DS_OPERATIONAL); |
Tomáš Pecka | f10b930 | 2021-02-23 19:02:02 +0100 | [diff] [blame] | 134 | } 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áš Pecka | db08413 | 2021-03-10 08:37:10 +0100 | [diff] [blame] | 138 | auto linkAddr = rtnl_link_get_addr(link); |
Tomáš Pecka | f10b930 | 2021-02-23 19:02:02 +0100 | [diff] [blame] | 139 | std::array<char, PHYS_ADDR_BUF_SIZE> buf; |
Tomáš Pecka | db08413 | 2021-03-10 08:37:10 +0100 | [diff] [blame] | 140 | 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] | 141 | 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áš Pecka | 0972938 | 2021-03-08 19:36:50 +0100 | [diff] [blame] | 158 | void 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áš Pecka | f10b930 | 2021-02-23 19:02:02 +0100 | [diff] [blame] | 188 | } |