blob: 330012379b3dda50af105563f4de6f605fda990d [file] [log] [blame]
Tomáš Pecka7acf3922021-08-10 11:16:57 +02001/*
2 * Copyright (C) 2020 CESNET, https://photonics.cesnet.cz/
3 *
4 * Written by Tomáš Pecka <tomas.pecka@fit.cvut.cz>
5 *
6 */
7#include <netinet/ether.h>
8#include <spdlog/spdlog.h>
9#include "LLDP.h"
10#include "utils/log.h"
11
12namespace velia::system {
13
Tomáš Pecka36427a12021-08-10 11:27:56 +020014namespace {
Tomáš Pecka7acf3922021-08-10 11:16:57 +020015
16static const std::string systemdNetworkdDbusInterface = "org.freedesktop.network1.Manager";
17static const sdbus::ObjectPath systemdNetworkdDbusManagerObjectPath = "/org/freedesktop/network1";
18
19/** @brief LLDP capabilities identifiers ordered by their appearence in YANG schema 'czechlight-lldp' */
20const char* SYSTEM_CAPABILITIES[] = {
21 "other",
22 "repeater",
23 "bridge",
24 "wlan-access-point",
25 "router",
26 "telephone",
27 "docsis-cable-device",
28 "station-only",
29 "cvlan-component",
30 "svlan-component",
31 "two-port-mac-relay",
32};
33
34/** @brief Converts systemd's capabilities bitset to YANG's (named) bits.
35 *
36 * Apparently, libyang's parser requires the bits to be specified as string of names separated by whitespace.
37 * See libyang's src/parser.c (function lyp_parse_value, switch-case LY_TYPE_BITS) and tests/test_sec9_7.c
38 *
39 * The names of individual bits should appear in the order they are defined in the YANG schema. At least that is how
40 * I understand libyang's comment 'identifiers appear ordered by their position' in src/parser.c.
41 * Systemd and our YANG model czechlight-lldp define the bits in the same order so this function does not have to care
42 * about it.
43 */
44std::string toBitsYANG(uint16_t bits)
45{
46 std::string res;
47
48 unsigned idx = 0;
49 while (bits) {
50 if (bits % 2) {
51 if (!res.empty()) {
52 res += " ";
53 }
54 res += SYSTEM_CAPABILITIES[idx];
55 }
56
57 bits /= 2;
58 idx += 1;
59 }
60
61 return res;
62}
63
64/** @brief sd_lldp_neighbor requires deletion by invoking sd_lldp_neighbor_unrefp */
65struct sd_lldp_neighbor_deleter {
66 void operator()(sd_lldp_neighbor* e) const
67 {
68 sd_lldp_neighbor_unrefp(&e);
69 }
70};
71using sd_lldp_neighbor_managed = std::unique_ptr<sd_lldp_neighbor, sd_lldp_neighbor_deleter>;
72
73/* @brief Reads a LLDP neighbour entry from systemd's binary LLDP files.
74*
75* Inspired systemd's networkctl code.
76*/
77sd_lldp_neighbor_managed nextNeighbor(std::ifstream& ifs)
78{
79 size_t size;
80
81 // read neighbor size
82 /* Systemd allows the LLDP frame to be at most 4 KiB long. The comment in networkctl.c states that
83 * "each LLDP packet is at most MTU size, but let's allow up to 4KiB just in case".
84 * This comment may be misleading a bit because Ethernet Jumbo Frames can be up to 9000 B long.
85 * However, LLDP frames should still be at most 1500 B long.
86 * (see https://www.cisco.com/c/en/us/td/docs/routers/ncs4000/software/configure/guide/configurationguide/configurationguide_chapter_0111011.pdf)
87 */
88 {
89 uint64_t rawSz; // get neighbour size in bytes
90 ifs.read(reinterpret_cast<char*>(&rawSz), sizeof(rawSz));
91 size = le64toh(rawSz);
92
93 if (size_t rd = ifs.gcount(); (rd == 0 && ifs.eof()) || rd != sizeof(rawSz) || size >= 4096) {
94 return nullptr;
95 }
96 }
97
98 std::vector<uint8_t> raw;
99 raw.resize(size);
100
101 ifs.read(reinterpret_cast<char*>(raw.data()), size);
102 if (static_cast<size_t>(ifs.gcount()) != size) { // typecast is safe here (see std::streamsize)
103 return nullptr;
104 }
105
106 // let systemd parse from raw
107 sd_lldp_neighbor* tmp = nullptr;
108 if (sd_lldp_neighbor_from_raw(&tmp, raw.data(), size) < 0) {
109 return nullptr;
110 }
111
112 return sd_lldp_neighbor_managed(tmp);
113}
114
115/* @brief Lists links using networkd dbus interface and returns them as a list of pairs <link_id, link_name>. */
116auto listLinks(sdbus::IProxy* networkdManagerProxy)
117{
118 std::vector<sdbus::Struct<int, std::string, sdbus::ObjectPath>> links;
119 std::vector<std::pair<int, std::string>> res; // we only want to return pairs (linkId, linkName), we do not need dbus object path
120
Tomáš Pecka36427a12021-08-10 11:27:56 +0200121 networkdManagerProxy->callMethod("ListLinks").onInterface(systemdNetworkdDbusInterface).storeResultsTo(links);
Tomáš Pecka7acf3922021-08-10 11:16:57 +0200122
123 std::transform(links.begin(), links.end(), std::back_inserter(res), [](const auto& e) { return std::make_pair(std::get<0>(e), std::get<1>(e)); });
124 return res;
125}
126
Tomáš Pecka36427a12021-08-10 11:27:56 +0200127}
Tomáš Pecka7acf3922021-08-10 11:16:57 +0200128
129LLDPDataProvider::LLDPDataProvider(std::filesystem::path dataDirectory, sdbus::IConnection& dbusConnection, const std::string& dbusNetworkdBus)
130 : m_log(spdlog::get("system"))
131 , m_dataDirectory(std::move(dataDirectory))
Tomáš Pecka36427a12021-08-10 11:27:56 +0200132 , m_networkdDbusProxy(sdbus::createProxy(dbusConnection, dbusNetworkdBus, systemdNetworkdDbusManagerObjectPath))
Tomáš Pecka7acf3922021-08-10 11:16:57 +0200133{
134}
135
136std::vector<NeighborEntry> LLDPDataProvider::getNeighbors() const
137{
138 std::vector<NeighborEntry> res;
139
Tomáš Pecka36427a12021-08-10 11:27:56 +0200140 for (const auto& [linkId, linkName] : listLinks(m_networkdDbusProxy.get())) {
Tomáš Pecka7acf3922021-08-10 11:16:57 +0200141 m_log->debug("LLDP: Collecting neighbours on '{}' (id {})", linkName, linkId);
142
143 // open lldp datafile
144 std::filesystem::path lldpFilename = m_dataDirectory / std::to_string(linkId);
145 std::ifstream ifs(lldpFilename, std::ios::binary);
146
147 if (!ifs.is_open()) {
148 // TODO: As of now, we are querying systemd-networkd for *all* links, not just those that have LLDP enabled.
149 // TODO: Create a patch for systemd that queries *only* links with LLDP enabled and change severity of this debug log to warning/error.
150 m_log->debug(" failed to open ({})", lldpFilename);
151 continue;
152 }
153
Tomáš Pecka36427a12021-08-10 11:27:56 +0200154 while (auto n = nextNeighbor(ifs)) {
Tomáš Pecka7acf3922021-08-10 11:16:57 +0200155 NeighborEntry ne;
156 ne.m_portId = linkName;
157
158 if (const char* system_name = nullptr; sd_lldp_neighbor_get_system_name(n.get(), &system_name) >= 0) {
159 ne.m_properties["remoteSysName"] = system_name;
160 }
161 if (const char* port_id = nullptr; sd_lldp_neighbor_get_port_id_as_string(n.get(), &port_id) >= 0) {
162 ne.m_properties["remotePortId"] = port_id;
163 }
164 if (const char* chassis_id = nullptr; sd_lldp_neighbor_get_chassis_id_as_string(n.get(), &chassis_id) >= 0) {
165 ne.m_properties["remoteChassisId"] = chassis_id;
166 }
167 if (ether_addr* addr = nullptr; sd_lldp_neighbor_get_destination_address(n.get(), addr) >= 0) {
168 ne.m_properties["remoteMgmtAddress"] = ether_ntoa(addr);
169 }
170
171 if (uint16_t cap = 0; sd_lldp_neighbor_get_system_capabilities(n.get(), &cap) >= 0) {
Tomáš Pecka36427a12021-08-10 11:27:56 +0200172 ne.m_properties["systemCapabilitiesSupported"] = toBitsYANG(cap);
Tomáš Pecka7acf3922021-08-10 11:16:57 +0200173 }
174
175 if (uint16_t cap = 0; sd_lldp_neighbor_get_enabled_capabilities(n.get(), &cap) >= 0) {
Tomáš Pecka36427a12021-08-10 11:27:56 +0200176 ne.m_properties["systemCapabilitiesEnabled"] = toBitsYANG(cap);
Tomáš Pecka7acf3922021-08-10 11:16:57 +0200177 }
178
179 m_log->trace(" found neighbor {}", ne);
180 res.push_back(ne);
181 }
182 }
183
184 return res;
185}
186
187std::ostream& operator<<(std::ostream& os, const NeighborEntry& entry)
188{
189 os << "NeighborEntry(" << entry.m_portId << ": {";
190
191 for (auto it = entry.m_properties.begin(); it != entry.m_properties.end(); ++it) {
192 if (it != entry.m_properties.begin()) {
193 os << ", ";
194 }
195
196 os << it->first << ": " << it->second;
197 }
198
199 return os << "}";
200}
201
202}