system: get LLDP neighbors from JSON output of networkctl
Old way of getting the LLDP neighbours list was through patched systemd.
We patched the systemd in a way that the functions working with LLDP
files in /run/systemd/netif/lldp were made public and thus we could use
them to parse the data [1]. That required to use patched systemd (for
br2-external and for CI test runs). This was not easily maintainable
with new systemd versions.
New attempt is parsing the output of `networkctl lldp --json=pretty`
which is available with the new patch [2].
This also asks for a redesign of tests as we currently have no way of
'injecting' LLDP neighbours into systemd-networkd unless we implement
our own varlink server providing the LLDP data (see the patch).
As we are parsing only the output of `networkctl lldp`, and we do not
rely on any systemd function (even for tests), there is no need to
compile patched systemd. That will also speedup the CI runs, yay.
[1] https://github.com/systemd/systemd/pull/16744
[2] https://github.com/systemd/systemd/pull/20333
Change-Id: Ib880fef50e8d44ca257b582e7d15027ffc5194c9
diff --git a/src/system/LLDP.cpp b/src/system/LLDP.cpp
index 3300123..70bc980 100644
--- a/src/system/LLDP.cpp
+++ b/src/system/LLDP.cpp
@@ -5,30 +5,30 @@
*
*/
#include <netinet/ether.h>
+#include <nlohmann/json.hpp>
#include <spdlog/spdlog.h>
#include "LLDP.h"
+#include "system_vars.h"
+#include "utils/exec.h"
#include "utils/log.h"
namespace velia::system {
namespace {
-static const std::string systemdNetworkdDbusInterface = "org.freedesktop.network1.Manager";
-static const sdbus::ObjectPath systemdNetworkdDbusManagerObjectPath = "/org/freedesktop/network1";
-
/** @brief LLDP capabilities identifiers ordered by their appearence in YANG schema 'czechlight-lldp' */
-const char* SYSTEM_CAPABILITIES[] = {
- "other",
- "repeater",
- "bridge",
- "wlan-access-point",
- "router",
- "telephone",
- "docsis-cable-device",
- "station-only",
- "cvlan-component",
- "svlan-component",
- "two-port-mac-relay",
+std::map<char, std::string> SYSTEM_CAPABILITIES = {
+ {'o', "other"},
+ {'p', "repeater"},
+ {'b', "bridge"},
+ {'w', "wlan-access-point"},
+ {'r', "router"},
+ {'t', "telephone"},
+ {'d', "docsis-cable-device"},
+ {'a', "station-only"},
+ {'c', "cvlan-component"},
+ {'s', "svlan-component"},
+ {'m', "two-port-mac-relay"},
};
/** @brief Converts systemd's capabilities bitset to YANG's (named) bits.
@@ -41,95 +41,27 @@
* Systemd and our YANG model czechlight-lldp define the bits in the same order so this function does not have to care
* about it.
*/
-std::string toBitsYANG(uint16_t bits)
+std::string toBitsYANG(const std::string& caps)
{
std::string res;
- unsigned idx = 0;
- while (bits) {
- if (bits % 2) {
+ for (const auto& [bit, capability] : SYSTEM_CAPABILITIES) {
+ if (std::find(caps.begin(), caps.end(), bit) != caps.end()) {
if (!res.empty()) {
res += " ";
}
- res += SYSTEM_CAPABILITIES[idx];
- }
- bits /= 2;
- idx += 1;
+ res += capability;
+ }
}
return res;
}
-
-/** @brief sd_lldp_neighbor requires deletion by invoking sd_lldp_neighbor_unrefp */
-struct sd_lldp_neighbor_deleter {
- void operator()(sd_lldp_neighbor* e) const
- {
- sd_lldp_neighbor_unrefp(&e);
- }
-};
-using sd_lldp_neighbor_managed = std::unique_ptr<sd_lldp_neighbor, sd_lldp_neighbor_deleter>;
-
-/* @brief Reads a LLDP neighbour entry from systemd's binary LLDP files.
-*
-* Inspired systemd's networkctl code.
-*/
-sd_lldp_neighbor_managed nextNeighbor(std::ifstream& ifs)
-{
- size_t size;
-
- // read neighbor size
- /* Systemd allows the LLDP frame to be at most 4 KiB long. The comment in networkctl.c states that
- * "each LLDP packet is at most MTU size, but let's allow up to 4KiB just in case".
- * This comment may be misleading a bit because Ethernet Jumbo Frames can be up to 9000 B long.
- * However, LLDP frames should still be at most 1500 B long.
- * (see https://www.cisco.com/c/en/us/td/docs/routers/ncs4000/software/configure/guide/configurationguide/configurationguide_chapter_0111011.pdf)
- */
- {
- uint64_t rawSz; // get neighbour size in bytes
- ifs.read(reinterpret_cast<char*>(&rawSz), sizeof(rawSz));
- size = le64toh(rawSz);
-
- if (size_t rd = ifs.gcount(); (rd == 0 && ifs.eof()) || rd != sizeof(rawSz) || size >= 4096) {
- return nullptr;
- }
- }
-
- std::vector<uint8_t> raw;
- raw.resize(size);
-
- ifs.read(reinterpret_cast<char*>(raw.data()), size);
- if (static_cast<size_t>(ifs.gcount()) != size) { // typecast is safe here (see std::streamsize)
- return nullptr;
- }
-
- // let systemd parse from raw
- sd_lldp_neighbor* tmp = nullptr;
- if (sd_lldp_neighbor_from_raw(&tmp, raw.data(), size) < 0) {
- return nullptr;
- }
-
- return sd_lldp_neighbor_managed(tmp);
}
-/* @brief Lists links using networkd dbus interface and returns them as a list of pairs <link_id, link_name>. */
-auto listLinks(sdbus::IProxy* networkdManagerProxy)
-{
- std::vector<sdbus::Struct<int, std::string, sdbus::ObjectPath>> links;
- std::vector<std::pair<int, std::string>> res; // we only want to return pairs (linkId, linkName), we do not need dbus object path
-
- networkdManagerProxy->callMethod("ListLinks").onInterface(systemdNetworkdDbusInterface).storeResultsTo(links);
-
- 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)); });
- return res;
-}
-
-}
-
-LLDPDataProvider::LLDPDataProvider(std::filesystem::path dataDirectory, sdbus::IConnection& dbusConnection, const std::string& dbusNetworkdBus)
+LLDPDataProvider::LLDPDataProvider(std::function<std::string()> dataCallback)
: m_log(spdlog::get("system"))
- , m_dataDirectory(std::move(dataDirectory))
- , m_networkdDbusProxy(sdbus::createProxy(dbusConnection, dbusNetworkdBus, systemdNetworkdDbusManagerObjectPath))
+ , m_dataCallback(std::move(dataCallback))
{
}
@@ -137,46 +69,28 @@
{
std::vector<NeighborEntry> res;
- for (const auto& [linkId, linkName] : listLinks(m_networkdDbusProxy.get())) {
- m_log->debug("LLDP: Collecting neighbours on '{}' (id {})", linkName, linkId);
+ auto json = nlohmann::json::parse(m_dataCallback());
- // open lldp datafile
- std::filesystem::path lldpFilename = m_dataDirectory / std::to_string(linkId);
- std::ifstream ifs(lldpFilename, std::ios::binary);
-
- if (!ifs.is_open()) {
- // TODO: As of now, we are querying systemd-networkd for *all* links, not just those that have LLDP enabled.
- // TODO: Create a patch for systemd that queries *only* links with LLDP enabled and change severity of this debug log to warning/error.
- m_log->debug(" failed to open ({})", lldpFilename);
- continue;
- }
-
- while (auto n = nextNeighbor(ifs)) {
+ for (const auto& [linkName, neighbors] : json.items()) {
+ for (const auto& n_ : neighbors) {
+ [[maybe_unused]] const auto& parameters = n_["neighbor"];
NeighborEntry ne;
ne.m_portId = linkName;
- if (const char* system_name = nullptr; sd_lldp_neighbor_get_system_name(n.get(), &system_name) >= 0) {
- ne.m_properties["remoteSysName"] = system_name;
+ if (auto it = parameters.find("chassisId"); it != parameters.end()) {
+ ne.m_properties["remoteChassisId"] = *it;
}
- if (const char* port_id = nullptr; sd_lldp_neighbor_get_port_id_as_string(n.get(), &port_id) >= 0) {
- ne.m_properties["remotePortId"] = port_id;
+ if (auto it = parameters.find("portId"); it != parameters.end()) {
+ ne.m_properties["remotePortId"] = *it;
}
- if (const char* chassis_id = nullptr; sd_lldp_neighbor_get_chassis_id_as_string(n.get(), &chassis_id) >= 0) {
- ne.m_properties["remoteChassisId"] = chassis_id;
+ if (auto it = parameters.find("systemName"); it != parameters.end()) {
+ ne.m_properties["remoteSysName"] = *it;
}
- if (ether_addr* addr = nullptr; sd_lldp_neighbor_get_destination_address(n.get(), addr) >= 0) {
- ne.m_properties["remoteMgmtAddress"] = ether_ntoa(addr);
+ if (auto it = parameters.find("enabledCapabilities"); it != parameters.end()) {
+ ne.m_properties["systemCapabilitiesEnabled"] = toBitsYANG(*it);
}
- if (uint16_t cap = 0; sd_lldp_neighbor_get_system_capabilities(n.get(), &cap) >= 0) {
- ne.m_properties["systemCapabilitiesSupported"] = toBitsYANG(cap);
- }
-
- if (uint16_t cap = 0; sd_lldp_neighbor_get_enabled_capabilities(n.get(), &cap) >= 0) {
- ne.m_properties["systemCapabilitiesEnabled"] = toBitsYANG(cap);
- }
-
- m_log->trace(" found neighbor {}", ne);
+ m_log->trace("Found LLDP neighbor {}", ne);
res.push_back(ne);
}
}
@@ -196,7 +110,7 @@
os << it->first << ": " << it->second;
}
- return os << "}";
+ return os << "})";
}
}
diff --git a/src/system/LLDP.h b/src/system/LLDP.h
index 35c0ce0..b1503fb 100644
--- a/src/system/LLDP.h
+++ b/src/system/LLDP.h
@@ -7,13 +7,10 @@
#pragma once
-#include <filesystem>
-#include <fstream>
+#include <functional>
#include <map>
-#include <sdbus-c++/sdbus-c++.h>
#include <spdlog/fmt/ostr.h> // allow spdlog to use operator<<(ostream, NeighborEntry)
#include <string>
-#include <systemd/sd-lldp.h>
#include <vector>
#include "utils/log-fwd.h"
@@ -27,13 +24,12 @@
class LLDPDataProvider {
public:
- LLDPDataProvider(std::filesystem::path dataDirectory, sdbus::IConnection& dbusConnection, const std::string& dbusNetworkdBus);
+ explicit LLDPDataProvider(std::function<std::string()> dataCallback);
std::vector<NeighborEntry> getNeighbors() const;
private:
velia::Log m_log;
- std::filesystem::path m_dataDirectory;
- std::unique_ptr<sdbus::IProxy> m_networkdDbusProxy;
+ std::function<std::string()> m_dataCallback;
};
}