From 566782d846a546e674c8f78760268310eb24b46e Mon Sep 17 00:00:00 2001 From: Martin Weishart <41727377+maweit@users.noreply.github.com> Date: Thu, 27 Feb 2025 13:17:34 +0100 Subject: [PATCH] LLDP: enable configuration on the fly `make_lldp_manager` is implemented as a lambda and receives a list of host interfaces at creation of the handler. If i.e. the IP address of an interface changes the lldp-manager has to destroyed and recreated. By re-reading the `host_interfaces` from the `model.settings` this can be avoided and `configure_interfaces` can be called instead. --- lldp_handler.cpp | 90 ++++++++++++++++++++++++++++++++++++++++++++++++ lldp_handler.h | 26 ++++++++++++++ lldp_manager.cpp | 48 ++++++++++++++++++++++++++ 3 files changed, 164 insertions(+) create mode 100644 lldp_handler.cpp create mode 100644 lldp_handler.h create mode 100644 lldp_manager.cpp diff --git a/lldp_handler.cpp b/lldp_handler.cpp new file mode 100644 index 000000000..8535358df --- /dev/null +++ b/lldp_handler.cpp @@ -0,0 +1,90 @@ +#include "nmos/lldp_handler.h" + +#include "cpprest/host_utils.h" +#include "cpprest/json.h" +#include "nmos/json_fields.h" +#include "nmos/model.h" +#include "nmos/node_interfaces.h" +#include "nmos/slog.h" + +namespace nmos +{ + namespace experimental + { + // make an LLDP handler to update the node interfaces JSON data with attached_network_device details + lldp::lldp_handler make_lldp_handler(nmos::model& model, slog::base_gate& gate) + { + return [&model, &gate](const std::string& interface_id, const lldp::lldp_data_unit& lldpdu) + { + const auto host_interfaces = nmos::get_host_interfaces(model.settings); + const auto interfaces = nmos::experimental::node_interfaces(host_interfaces); + + auto interface = interfaces.find(utility::s2us(interface_id)); + if (interfaces.end() == interface) + { + slog::log(gate, SLOG_FLF) << "LLDP received - no matching network interface " << interface_id << " to update"; + return; + } + + const auto& interface_name = interface->second.name; + + using web::json::value_of; + + auto lock = model.write_lock(); + auto& resources = model.node_resources; + + auto resource = nmos::find_self_resource(resources); + if (resources.end() != resource) + { + auto& json_interfaces = nmos::fields::interfaces(resource->data); + + const auto json_interface = std::find_if(json_interfaces.begin(), json_interfaces.end(), [&](const web::json::value& nv) + { + return nmos::fields::name(nv) == interface_name; + }); + + if (json_interfaces.end() == json_interface) + { + slog::log(gate, SLOG_FLF) << "LLDP received - no matching network interface " << interface_id << " to update"; + return; + } + + const auto attached_network_device = value_of({ + { nmos::fields::chassis_id, utility::s2us(lldp::parse_chassis_id(lldpdu.chassis_id)) }, + { nmos::fields::port_id, utility::s2us(lldp::parse_port_id(lldpdu.port_id)) } + }); + + // has the attached_network_device info changed? + if (nmos::fields::attached_network_device(*json_interface) != attached_network_device) + { + slog::log(gate, SLOG_FLF) << "LLDP received - updating attached network device info for network interface " << interface->second.name; + + nmos::modify_resource(resources, resource->id, [&interface_name, &attached_network_device](nmos::resource& resource) + { + resource.data[nmos::fields::version] = web::json::value::string(nmos::make_version()); + + auto& json_interfaces = nmos::fields::interfaces(resource.data); + + const auto json_interface = std::find_if(json_interfaces.begin(), json_interfaces.end(), [&](const web::json::value& nv) + { + return nmos::fields::name(nv) == interface_name; + }); + + if (json_interfaces.end() != json_interface) + { + (*json_interface)[nmos::fields::attached_network_device] = attached_network_device; + } + }); + + slog::log(gate, SLOG_FLF) << "Notifying attached network device info updated"; + model.notify(); + } + } + else + { + slog::log(gate, SLOG_FLF) << "Self resource not found!"; + } + }; + } + } +} diff --git a/lldp_handler.h b/lldp_handler.h new file mode 100644 index 000000000..90ba30a06 --- /dev/null +++ b/lldp_handler.h @@ -0,0 +1,26 @@ +#ifndef NMOS_LLDP_HANDLER_H +#define NMOS_LLDP_HANDLER_H + +#include +#include "cpprest/details/basic_types.h" +#include "lldp/lldp.h" // forward declaration of lldp::lldp_handler + +namespace slog +{ + class base_gate; +} + +namespace nmos +{ + struct model; + + struct node_interface; + + namespace experimental + { + // make an LLDP handler to update the node interfaces JSON data with attached_network_device details + lldp::lldp_handler make_lldp_handler(nmos::model& model, slog::base_gate& gate); + } +} + +#endif diff --git a/lldp_manager.cpp b/lldp_manager.cpp new file mode 100644 index 000000000..0722ddbc4 --- /dev/null +++ b/lldp_manager.cpp @@ -0,0 +1,48 @@ +#ifdef HAVE_LLDP +#include "nmos/lldp_manager.h" + +#include "lldp/lldp_manager.h" +#include "cpprest/json.h" +#include "nmos/json_fields.h" +#include "nmos/lldp_handler.h" +#include "nmos/model.h" +#include "nmos/node_interfaces.h" +#include "nmos/slog.h" + +namespace nmos +{ + namespace experimental + { + // make an LLDP manager configured to receive LLDP frames on the specified interfaces + // and update the node interfaces JSON data with attached_network_device details + // and optionally, transmit LLDP frames for these interfaces + lldp::lldp_manager make_lldp_manager(nmos::model& model, const std::map& interfaces, bool transmit, slog::base_gate& gate) + { + // use default configuration for now + lldp::lldp_manager manager(gate); + + const auto status = transmit ? lldp::management_status::transmit_receive : lldp::management_status::receive; + + manager.set_handler(make_lldp_handler(model, gate)); + + for (const auto& interface : interfaces) + { + // data with default TTL + const auto data = lldp::normal_data_unit( + lldp::make_chassis_id(utility::us2s(interface.second.chassis_id)), + lldp::make_port_id(utility::us2s(interface.second.port_id)), + { utility::us2s(interface.second.name) }, + utility::us2s(nmos::get_host_name(model.settings)) + ); + + auto configure = manager.configure_interface(utility::us2s(interface.first), status, data); + // current configure_interface implementation is synchronous, but just to make clear... + // for now, wait for it to complete + configure.wait(); + } + + return manager; + } + } +} +#endif