test: Verify LLDP admin status

This commit is contained in:
Ahmed Karic
2025-02-12 10:41:46 +01:00
parent 14fede3e56
commit af1f173497
9 changed files with 216 additions and 9 deletions
+1
View File
@@ -9,6 +9,7 @@ include::mdns_allow_deny/Readme.adoc[]
include::lldp_enable_disable/Readme.adoc[]
include::lldp_admin_status/Readme.adoc[]
include::ssh_server_config/Readme.adoc[]
@@ -2,6 +2,8 @@
- name: lldp_enable_disable
case: lldp_enable_disable/test.py
- name: lldp_admin_status
case: lldp_admin_status/test.py
- name: mdns_enable_disable
case: mdns_enable_disable/test.py
@@ -0,0 +1 @@
lldp_admin_status.adoc
@@ -0,0 +1,27 @@
=== LLDP admin status
==== Description
Verify that LLDP admin status is set properly by lldpd
==== Topology
ifdef::topdoc[]
image::{topdoc}../../test/case/infix_services/lldp_admin_status/topology.svg[LLDP admin status topology]
endif::topdoc[]
ifndef::topdoc[]
ifdef::testgroup[]
image::lldp_admin_status/topology.svg[LLDP admin status topology]
endif::testgroup[]
ifndef::testgroup[]
image::topology.svg[LLDP admin status topology]
endif::testgroup[]
endif::topdoc[]
==== Test sequence
. Set up topology and attach to target DUT
. Enable target interface and enable LLDP
. Verify admin-status: 'rx-only'
. Verify admin-status: 'tx-only'
. Verify admin-status: 'disabled'
. Verify admin-status: 'tx-and-rx'
<<<
+98
View File
@@ -0,0 +1,98 @@
#!/usr/bin/env python3
"""LLDP admin status
Verify that LLDP admin status is set properly by lldpd
"""
import time
import infamy
import infamy.lldp as lldp
from scapy.all import Ether, sendp
from scapy.contrib.lldp import (
LLDPDU, LLDPDUChassisID, LLDPDUPortID, LLDPDUTimeToLive, LLDPDUEndOfLLDPDU
)
def capture_traffic(iface, sec):
with infamy.IsolatedMacVlan(iface) as netns:
sniffer = infamy.Sniffer(netns, "ether proto 0x88cc")
with sniffer:
print("Capturing network traffic ...")
time.sleep(sec)
return sniffer.output()
def send_lldp_packet(iface, chassis_id, chassis_id_subtype, ttl=3):
eth = Ether(dst="01:80:c2:00:00:0e", type=0x88cc)
lldpdu = eth / LLDPDU()
lldpdu /= LLDPDUChassisID(subtype=chassis_id_subtype, id=chassis_id)
lldpdu /= LLDPDUPortID(subtype=5, id=iface)
lldpdu /= LLDPDUTimeToLive(ttl=ttl) / LLDPDUEndOfLLDPDU()
sendp(lldpdu, iface=iface, verbose=False)
def verify_neigh_presence(test, target, port, expect_neighbor):
"""Verify neighbor (host) presence on the target system"""
neighbors = lldp.get_remote_systems_data(target, port)
if expect_neighbor and not neighbors:
print("Expected LLDP neighbor but found none.")
test.fail()
if not expect_neighbor and neighbors:
print("Unexpected LLDP neighbor found.")
test.fail()
def verify_admin_status(test, target, port, admin_status, local_capture, remote_detect):
target.put_config_dicts({
"ieee802-dot1ab-lldp": {
"lldp": {
"port": [{
"name": target["data"],
"dest-mac-address": "00-00-00-00-00-00",
"admin-status": admin_status
}]
}
}
})
rc = capture_traffic(port, 5)
if local_capture and "LLDP" not in rc.stdout:
test.fail()
if not local_capture and "LLDP" in rc.stdout:
test.fail()
send_lldp_packet(port, "Chassis ID 007", 7)
verify_neigh_presence(test, target, target["data"], remote_detect)
with infamy.Test() as test:
with test.step("Set up topology and attach to target DUT"):
env = infamy.Env()
target = env.attach("target", "mgmt")
_, hdata = env.ltop.xlate("host", "data")
with test.step("Enable target interface and enable LLDP"):
target.put_config_dicts({
"ietf-interfaces": {
"interfaces": {
"interface": [{
"name": target["data"],
"enabled": True
}]
}
},
"ieee802-dot1ab-lldp": {
"lldp": {
"enabled": True,
"message-tx-interval": 1
}
}
})
with test.step("Verify admin-status: 'rx-only'"):
verify_admin_status(test, target, hdata, "rx-only", False, True)
with test.step("Verify admin-status: 'tx-only'"):
verify_admin_status(test, target, hdata, "tx-only", True, False)
with test.step("Verify admin-status: 'disabled'"):
verify_admin_status(test, target, hdata, "disabled", False, False)
with test.step("Verify admin-status: 'tx-and-rx'"):
verify_admin_status(test, target, hdata, "tx-and-rx", True, True)
test.succeed()
@@ -0,0 +1 @@
../lldp_enable_disable/topology.dot
@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Title: 1x2 Pages: 1 -->
<svg width="424pt" height="55pt"
viewBox="0.00 0.00 424.03 55.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 51)">
<title>1x2</title>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-51 420.03,-51 420.03,4 -4,4"/>
<!-- host -->
<g id="node1" class="node">
<title>host</title>
<polygon fill="none" stroke="black" points="0,-0.5 0,-46.5 100,-46.5 100,-0.5 0,-0.5"/>
<text text-anchor="middle" x="25" y="-19.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">host</text>
<polyline fill="none" stroke="black" points="50,-0.5 50,-46.5 "/>
<text text-anchor="middle" x="75" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
<polyline fill="none" stroke="black" points="50,-23.5 100,-23.5 "/>
<text text-anchor="middle" x="75" y="-8.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data</text>
</g>
<!-- target -->
<g id="node2" class="node">
<title>target</title>
<polygon fill="none" stroke="black" points="300.03,-0.5 300.03,-46.5 416.03,-46.5 416.03,-0.5 300.03,-0.5"/>
<text text-anchor="middle" x="325.03" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
<polyline fill="none" stroke="black" points="300.03,-23.5 350.03,-23.5 "/>
<text text-anchor="middle" x="325.03" y="-8.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data</text>
<polyline fill="none" stroke="black" points="350.03,-0.5 350.03,-46.5 "/>
<text text-anchor="middle" x="383.03" y="-19.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">target</text>
</g>
<!-- host&#45;&#45;target -->
<g id="edge1" class="edge">
<title>host:mgmt&#45;&#45;target:mgmt</title>
<path fill="none" stroke="lightgrey" stroke-width="2" d="M100,-35.5C100,-35.5 300.03,-35.5 300.03,-35.5"/>
</g>
<!-- host&#45;&#45;target -->
<g id="edge2" class="edge">
<title>host:data&#45;&#45;target:data</title>
<path fill="none" stroke="black" stroke-width="2" d="M100,-11.5C100,-11.5 300.03,-11.5 300.03,-11.5"/>
<text text-anchor="middle" x="262.03" y="-14.9" font-family="DejaVu Serif, Book" font-size="12.00">10.0.0.10/24</text>
<text text-anchor="middle" x="134" y="-14.9" font-family="DejaVu Serif, Book" font-size="12.00">10.0.0.1/24</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

@@ -12,14 +12,7 @@ with infamy.Test() as test:
with test.step("Set up topology and attach to target DUT"):
env = infamy.Env()
target = env.attach("target", "mgmt")
lldp_link = env.ltop.get_link("host", "target", flt=lambda e: "ieee-mc" in e.get("requires", "").split())
if not lldp_link:
print("Skipping test: No link providing ieee-mc found in the topology.")
test.skip()
log_hport, _ = lldp_link
_, phy_hport = env.ltop.xlate("host", log_hport)
_, hdata = env.ltop.xlate("host", "data")
with test.step("Enable target interface and disable LLDP"):
target.put_config_dicts({
@@ -48,7 +41,7 @@ with infamy.Test() as test:
def verify(enabled, sec):
"""Verify lldp traffic, or no traffic if lldp is disabled."""
with infamy.IsolatedMacVlan(phy_hport) as netns:
with infamy.IsolatedMacVlan(hdata) as netns:
snif = infamy.Sniffer(netns, "ether proto 0x88cc")
act = "enabling" if enabled else "disabling"
+40
View File
@@ -0,0 +1,40 @@
"""
Fetch LLDP local system data from remote device.
"""
reverse_subtype_mapping = {
"chassis-component": 1,
"interface-alias": 2,
"port-component": 3,
"mac-address": 4,
"network-address": 5,
"interface-name": 6,
"local": 7 # 'local' maps to 7 as per LLDP spec
}
def get_remote_systems_data(target, port):
"""Fetch the full remote-systems-data list for a specific port"""
content = target.get_data("/ieee802-dot1ab-lldp:lldp")
if not content:
return []
for port_entry in content.get("lldp", {}).get("port", []):
if port_entry.get("name") == port:
return port_entry.get("remote-systems-data", [])
return []
def get_chassis_ids(target, port):
"""Fetch all LLDP chassis IDs for neighbors on a specific port"""
neighbors = get_remote_systems_data(target, port)
return [neighbor.get("chassis-id") for neighbor in neighbors if "chassis-id" in neighbor]
def get_chassis_ids_subtype(target, port):
"""Fetch all LLDP chassis ID subtypes for neighbors on a specific port and convert them to numbers."""
neighbors = get_remote_systems_data(target, port)
return [
reverse_subtype_mapping.get(neighbor.get("chassis-id-subtype"), 0) # Default to 0 if unknown
for neighbor in neighbors if "chassis-id-subtype" in neighbor
]