statd: Add LLDP operational data retrieval

Enable statd to fetch LLDP neighbor data via lldpcli for verifying
LLDP admin status. Currently, only chassis ID and chassis ID subtype
are retrieved.
This commit is contained in:
Ahmed Karic
2025-02-12 10:41:46 +01:00
parent d3bfbb57b6
commit 14fede3e56
4 changed files with 102 additions and 5 deletions
-4
View File
@@ -76,10 +76,6 @@ module infix-lldp {
deviation "/lldp:lldp/lldp:port/lldp:rx-statistics" {
deviate not-supported;
}
deviation "/lldp:lldp/lldp:port/lldp:remote-systems-data" {
deviate not-supported;
}
deviation "/lldp:lldp/lldp:port/lldp:message-fast-tx" {
deviate not-supported;
+3 -1
View File
@@ -1,6 +1,5 @@
import logging
import logging.handlers
import subprocess
import json
import sys # (built-in module)
import os
@@ -76,6 +75,9 @@ def main():
elif args.model == 'ietf-system':
from . import ietf_system
yang_data = ietf_system.operational()
elif args.model == 'ieee802-dot1ab-lldp':
from . import infix_lldp
yang_data = infix_lldp.operational()
else:
common.LOG.warning("Unsupported model %s", args.model)
sys.exit(1)
+96
View File
@@ -0,0 +1,96 @@
from .host import HOST
from collections import defaultdict
def operational():
"""Retrieve LLDP neighbor information and store in remote-systems-data under the correct port."""
# Reference: https://www.ieee802.org/1/files/public/YANGs/ieee802-types.yang
subtype_mapping = {
"component": "chassis-component",
"ifalias": "interface-alias",
"port": "port-component",
"mac": "mac-address",
"ip": "network-address",
"ifname": "interface-name",
"local": "local"
}
DEFAULT_MAC = "00-00-00-00-00-00"
port_data = defaultdict(lambda: {"remote-systems-data": [], "dest-mac-address": None})
data = HOST.run_json(["lldpcli", "show", "neighbors", "-f", "json"])
interfaces = data.get("lldp", {}).get("interface", [])
if isinstance(interfaces, dict):
interfaces = [interfaces]
for iface_entry in interfaces:
for iface_name, iface_data in iface_entry.items():
remote_index = int(iface_data.get("rid", 0))
time_mark = parse_time(iface_data.get("age"))
chassis = iface_data.get("chassis", {})
chassis_id_type, chassis_id_value = extract_chassis_id(chassis, subtype_mapping)
port_info = iface_data.get("port", {})
port_id_type = subtype_mapping.get(port_info.get("id", {}).get("type"), "unknown")
port_id_value = port_info.get("id", {}).get("value", "")
dest_mac_address = (
chassis_id_value.replace(":", "-") if chassis_id_type == "mac-address" else
port_id_value.replace(":", "-") if port_id_type == "mac-address" else
DEFAULT_MAC
)
remote_entry = {
"time-mark": time_mark,
"remote-index": remote_index,
"chassis-id-subtype": chassis_id_type,
"chassis-id": chassis_id_value,
"port-id-subtype": port_id_type,
"port-id": port_id_value
}
port_data[iface_name]["remote-systems-data"].append(remote_entry)
if port_data[iface_name]["dest-mac-address"] is None:
port_data[iface_name]["dest-mac-address"] = dest_mac_address
formatted_output = {
"ieee802-dot1ab-lldp:lldp": {
"port": [
{
"name": port_name,
"dest-mac-address": port_info["dest-mac-address"],
"remote-systems-data": port_info["remote-systems-data"]
}
for port_name, port_info in port_data.items() if port_info["remote-systems-data"]
]
}
}
return formatted_output
def extract_chassis_id(chassis_block, subtype_mapping):
if "id" in chassis_block:
id_info = chassis_block["id"]
return subtype_mapping.get(id_info.get("type"), "unknown"), id_info.get("value", "")
for _, value in chassis_block.items():
if isinstance(value, dict) and "id" in value:
id_info = value["id"]
return subtype_mapping.get(id_info.get("type"), "unknown"), id_info.get("value", "")
return "unknown", ""
def parse_time(time_str):
"""Convert LLDP time format to seconds"""
import re
if time_str:
match = re.search(r"(\d+)\s*day[s]*,\s*(\d+):(\d+):(\d+)", time_str)
if match:
days, hours, minutes, seconds = map(int, match.groups())
return days * 86400 + hours * 3600 + minutes * 60 + seconds
return 0
+3
View File
@@ -41,6 +41,7 @@
#define XPATH_ROUTING_OSPF XPATH_ROUTING_BASE "/ospf"
#define XPATH_CONTAIN_BASE "/infix-containers:containers"
#define XPATH_DHCP_SERVER_BASE "/infix-dhcp-server:dhcp-server"
#define XPATH_LLDP_BASE "/ieee802-dot1ab-lldp:lldp"
TAILQ_HEAD(sub_head, sub);
@@ -344,6 +345,8 @@ static int subscribe_to_all(struct statd *statd)
return SR_ERR_INTERNAL;
if (subscribe(statd, "ietf-system", XPATH_SYSTEM_BASE, sr_generic_cb))
return SR_ERR_INTERNAL;
if (subscribe(statd, "ieee802-dot1ab-lldp", XPATH_LLDP_BASE, sr_generic_cb))
return SR_ERR_INTERNAL;
#ifdef CONTAINERS
if (subscribe(statd, "infix-containers", XPATH_CONTAIN_BASE, sr_generic_cb))
return SR_ERR_INTERNAL;