mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-22 01:13:00 +02:00
test: Verify LLDP admin status
This commit is contained in:
@@ -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
@@ -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--target -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>host:mgmt--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--target -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>host:data--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"
|
||||
|
||||
|
||||
@@ -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
|
||||
]
|
||||
Reference in New Issue
Block a user