Merge pull request #1039 from kernelkit/verify-ieee-group-fwd
Verify ieee group fwd
@@ -12,6 +12,10 @@ All notable changes to the project are documented in this file.
|
||||
- Upgrade netopeer2 (NETCONF) to 2.4.1
|
||||
- Add documentation on Infix upgrading and downgrading, issue #1009
|
||||
- Add HDMI and USB support for iMX8MP-evk
|
||||
- Enforced strict format for LLDP destination MAC address:
|
||||
- Only accepts colon-separated format: `01:80:C2:00:00:0E`
|
||||
- Add `show lldp` command to show discovered neighbors per interface.
|
||||
- Add configuration support for per-interface LLDP administrative status
|
||||
|
||||
### Fixes
|
||||
- Fix containers with multiple mounts
|
||||
@@ -128,6 +132,7 @@ All notable changes to the project are documented in this file.
|
||||
- Add support for GRE/GRETAP tunnels
|
||||
- Add support for STP/RSTP on bridges
|
||||
- Add support for VXLAN tunnels
|
||||
- Add support for configuring global LLDP `message-tx-interval`
|
||||
|
||||
### Fixes
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ admin@infix-c0-ff-ee:~$
|
||||
|
||||
## LLDP
|
||||
|
||||
Infix supports LLDP (IEEE 802.1AB). For a device with factory default
|
||||
Infix supports LLDP (IEEE 802.1AB). For a device with factory default
|
||||
settings, the link-local IPv6 address can be read from the Management
|
||||
Address TLV using *tcpdump* or other sniffing tools[^1]:
|
||||
|
||||
@@ -131,6 +131,10 @@ tcpdump: listening on tap0, link-type EN10MB (Ethernet), snapshot length 262144
|
||||
linux-pc:#
|
||||
```
|
||||
|
||||
The following capabilities are available via NETCONF/RESTCONF or the Infix CLI.
|
||||
|
||||
### LLDP Enable/Disable
|
||||
|
||||
The LLDP service can be disabled using the following commands.
|
||||
|
||||
```
|
||||
@@ -140,6 +144,59 @@ admin@infix-c0-ff-ee:/config/> leave
|
||||
admin@infix-c0-ff-ee:/>
|
||||
```
|
||||
|
||||
To reenable it from the CLI config mode:
|
||||
|
||||
```
|
||||
admin@test-00-01-00:/config/> set lldp enabled
|
||||
admin@test-00-01-00:/config/> leave
|
||||
```
|
||||
|
||||
### LLDP Message Transmission Interval
|
||||
|
||||
By default, LLDP uses a `message-tx-interval` of 30 seconds, as defined
|
||||
by the IEEE standard. Infix allows this value to be customized.
|
||||
To change it using the CLI:
|
||||
|
||||
```
|
||||
admin@test-00-01-00:/config/> set lldp message-tx-interval 1
|
||||
admin@test-00-01-00:/config/> leave
|
||||
```
|
||||
|
||||
### LLDP Administrative Status per Interface
|
||||
|
||||
Infix supports configuring the LLDP administrative status on a per-port
|
||||
basis. The default mode is `tx-and-rx`, but the following options are
|
||||
also supported:
|
||||
|
||||
- `rx-only` – Receive LLDP packets only
|
||||
- `tx-only` – Transmit LLDP packets only
|
||||
- `disabled` – Disable LLDP on the interface
|
||||
|
||||
Example configuration:
|
||||
|
||||
```
|
||||
admin@test-00-01-00:/config/> set lldp port e8 dest-mac-address 01:80:C2:00:00:0E admin-status disabled
|
||||
admin@test-00-01-00:/config/> set lldp port e5 dest-mac-address 01:80:C2:00:00:0E admin-status rx-only
|
||||
admin@test-00-01-00:/config/> set lldp port e6 dest-mac-address 01:80:C2:00:00:0E admin-status tx-only
|
||||
admin@test-00-01-00:/config/> leave
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> The destination MAC address must be the standard LLDP multicast
|
||||
> address: `01:80:C2:00:00:0E`.
|
||||
|
||||
### Displaying LLDP Neighbor Information
|
||||
|
||||
In CLI mode, Infix also provides a convenient `show lldp` command to
|
||||
list LLDP neighbors detected on each interface:
|
||||
|
||||
```
|
||||
admin@test-00-01-00:/> show lldp
|
||||
INTERFACE REM-IDX TIME CHASSIS-ID PORT-ID
|
||||
e5 1 902 00:a0:85:00:04:01 00:a0:85:00:04:07
|
||||
e6 3 897 00:a0:85:00:03:01 00:a0:85:00:03:07
|
||||
e8 2 901 00:a0:85:00:02:01 00:a0:85:00:02:05
|
||||
```
|
||||
|
||||
## mDNS-SD
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ MODULES=(
|
||||
"infix-if-type@2025-02-12.yang"
|
||||
"infix-routing@2024-11-27.yang"
|
||||
"ieee802-dot1ab-lldp@2022-03-15.yang"
|
||||
"infix-lldp@2025-01-08.yang"
|
||||
"infix-lldp@2025-05-05.yang"
|
||||
"infix-dhcp-common@2025-01-29.yang"
|
||||
"infix-dhcp-client@2025-01-29.yang"
|
||||
"infix-dhcp-server@2025-01-29.yang"
|
||||
|
||||
@@ -11,6 +11,13 @@ module infix-lldp {
|
||||
contact "kernelkit@googlegroups.com";
|
||||
description "Infix augments and deviations to ieee-dot1ab-lldp.";
|
||||
|
||||
revision 2025-05-05 {
|
||||
description
|
||||
"Restrict 'dest-mac-address' to only allow the LLDP multicast MAC
|
||||
address (01:80:C2:00:00:0E).";
|
||||
reference "internal";
|
||||
}
|
||||
|
||||
revision 2025-01-08 {
|
||||
description "Enable std. /lldp:lldp/message-tx-interval";
|
||||
}
|
||||
@@ -32,6 +39,22 @@ module infix-lldp {
|
||||
}
|
||||
}
|
||||
|
||||
deviation "/lldp:lldp/lldp:port/lldp:dest-mac-address" {
|
||||
deviate replace {
|
||||
type string {
|
||||
pattern "([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deviation "/lldp:lldp/lldp:port/lldp:dest-mac-address" {
|
||||
deviate add {
|
||||
must "translate(., 'abcdef', 'ABCDEF') = '01:80:C2:00:00:0E'" {
|
||||
error-message "Only the LLDP Multicast MAC (01:80:C2:00:00:0E) is allowed.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deviation "/lldp:lldp/lldp:message-fast-tx" {
|
||||
deviate not-supported;
|
||||
}
|
||||
|
||||
@@ -373,6 +373,12 @@
|
||||
</ACTION>
|
||||
</COMMAND>
|
||||
|
||||
<COMMAND name="lldp" help="Show LLDP neighbors">
|
||||
<ACTION sym="script">
|
||||
show lldp
|
||||
</ACTION>
|
||||
</COMMAND>
|
||||
|
||||
<COMMAND name="hardware" help="Show hardware information">
|
||||
<ACTION sym="script" in="tty" out="tty" interrupt="true">
|
||||
show hardware |pager
|
||||
@@ -471,22 +477,22 @@
|
||||
<ACTION sym="script">sysinfo</ACTION>
|
||||
</COMMAND>
|
||||
-->
|
||||
<COMMAND name="version" help="Show software version">
|
||||
<ACTION sym="script" in="tty" interrupt="true">cat /etc/version</ACTION>
|
||||
</COMMAND>
|
||||
|
||||
<COMMAND name="factory-config" help="Show factory-config">
|
||||
<ACTION sym="script" in="tty" out="tty" interrupt="true">jq -C . /etc/factory-config.cfg |pager</ACTION>
|
||||
</COMMAND>
|
||||
|
||||
<COMMAND name="running-config" help="Show running-config">
|
||||
<ACTION sym="script" in="tty" out="tty" interrupt="true">sysrepocfg -X -f json | jq -C . |pager</ACTION>
|
||||
</COMMAND>
|
||||
|
||||
<COMMAND name="startup-config" help="Show startup-config">
|
||||
<ACTION sym="script" in="tty" out="tty" interrupt="true">jq -C . /cfg/startup-config.cfg |pager</ACTION>
|
||||
</COMMAND>
|
||||
<COMMAND name="version" help="Show software version">
|
||||
<ACTION sym="script" in="tty" interrupt="true">cat /etc/version</ACTION>
|
||||
</COMMAND>
|
||||
|
||||
<COMMAND name="factory-config" help="Show factory-config">
|
||||
<ACTION sym="script" in="tty" out="tty" interrupt="true">jq -C . /etc/factory-config.cfg |pager</ACTION>
|
||||
</COMMAND>
|
||||
|
||||
<COMMAND name="running-config" help="Show running-config">
|
||||
<ACTION sym="script" in="tty" out="tty" interrupt="true">sysrepocfg -X -f json | jq -C . |pager</ACTION>
|
||||
</COMMAND>
|
||||
|
||||
<COMMAND name="startup-config" help="Show startup-config">
|
||||
<ACTION sym="script" in="tty" out="tty" interrupt="true">jq -C . /cfg/startup-config.cfg |pager</ACTION>
|
||||
</COMMAND>
|
||||
|
||||
<COMMAND name="factory-reset" help="Restore the system to factory default state">
|
||||
<ACTION sym="script" in="tty" out="tty" interrupt="true">
|
||||
/bin/yorn "This will restore the device to factory defaults"
|
||||
@@ -581,6 +587,7 @@
|
||||
</ACTION>
|
||||
</COMMAND>
|
||||
|
||||
</COMMAND>
|
||||
</VIEW>
|
||||
|
||||
<VIEW name="config">
|
||||
|
||||
@@ -154,6 +154,15 @@ def routes(args: List[str]):
|
||||
return
|
||||
cli_pretty(data, "show-routing-table", "-i", ip_version)
|
||||
|
||||
def lldp(args: List[str]):
|
||||
data = run_sysrepocfg("/ieee802-dot1ab-lldp:lldp")
|
||||
if not data:
|
||||
print("No lldp data retrieved.")
|
||||
return
|
||||
if RAW_OUTPUT:
|
||||
print(json.dumps(data, indent=2))
|
||||
return
|
||||
cli_pretty(data, "show-lldp")
|
||||
|
||||
def execute_command(command: str, args: List[str]):
|
||||
command_mapping = {
|
||||
@@ -162,6 +171,7 @@ def execute_command(command: str, args: List[str]):
|
||||
'interface': interface,
|
||||
'ntp': ntp,
|
||||
'routes': routes,
|
||||
'lldp': lldp,
|
||||
'software' : software,
|
||||
'stp': stp,
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ class PadMdb:
|
||||
group = 20
|
||||
ports = 45
|
||||
|
||||
|
||||
class PadStpPort:
|
||||
port = 12
|
||||
id = 7
|
||||
@@ -30,6 +31,7 @@ class PadStpPort:
|
||||
|
||||
total = 12 + 7 + 12 + 12 + 6 + 31
|
||||
|
||||
|
||||
class PadRoute:
|
||||
dest = 30
|
||||
pref = 8
|
||||
@@ -57,6 +59,7 @@ class PadSoftware:
|
||||
state = 10
|
||||
version = 23
|
||||
|
||||
|
||||
class PadDhcpServer:
|
||||
ip = 17
|
||||
mac = 19
|
||||
@@ -64,11 +67,13 @@ class PadDhcpServer:
|
||||
cid = 19
|
||||
exp = 10
|
||||
|
||||
|
||||
class PadUsbPort:
|
||||
title = 30
|
||||
name = 20
|
||||
state = 10
|
||||
|
||||
|
||||
class PadNtpSource:
|
||||
address = 16
|
||||
mode = 13
|
||||
@@ -76,6 +81,7 @@ class PadNtpSource:
|
||||
stratum = 11
|
||||
poll = 14
|
||||
|
||||
|
||||
class Decore():
|
||||
@staticmethod
|
||||
def decorate(sgr, txt, restore="0"):
|
||||
@@ -109,11 +115,21 @@ class Decore():
|
||||
def gray_bg(txt):
|
||||
return Decore.decorate("100", txt)
|
||||
|
||||
|
||||
class PadLldp:
|
||||
interface = 16
|
||||
rem_idx = 10
|
||||
time = 12
|
||||
chassis_id = 20
|
||||
port_id = 20
|
||||
|
||||
|
||||
def datetime_now():
|
||||
if UNIT_TEST:
|
||||
return datetime(2023, 1, 1, 12, 0, 0, tzinfo=timezone.utc)
|
||||
return datetime.now(timezone.utc)
|
||||
|
||||
|
||||
def get_json_data(default, indata, *args):
|
||||
data = indata
|
||||
for arg in args:
|
||||
@@ -124,12 +140,14 @@ def get_json_data(default, indata, *args):
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def remove_yang_prefix(key):
|
||||
parts = key.split(":", 1)
|
||||
if len(parts) > 1:
|
||||
return parts[1]
|
||||
return key
|
||||
|
||||
|
||||
class Date(datetime):
|
||||
def _pretty_delta(delta):
|
||||
assert(delta.total_seconds() > 0)
|
||||
@@ -179,7 +197,6 @@ class Date(datetime):
|
||||
return cls.strptime(f"{date}+{tz}", "%Y-%m-%dT%H:%M:%S%z")
|
||||
|
||||
|
||||
|
||||
class Route:
|
||||
def __init__(self, data, ip):
|
||||
self.data = data
|
||||
@@ -321,6 +338,7 @@ class Software:
|
||||
print(f"SHA-256 : {self.hash}")
|
||||
print(f"Installed : {self.date}")
|
||||
|
||||
|
||||
class USBport:
|
||||
def __init__(self, data):
|
||||
self.data = data
|
||||
@@ -333,6 +351,7 @@ class USBport:
|
||||
row += f"{self.state:<{PadUsbPort.state}}"
|
||||
print(row)
|
||||
|
||||
|
||||
class STPBridgeID:
|
||||
def __init__(self, id):
|
||||
self.id = id
|
||||
@@ -345,6 +364,7 @@ class STPBridgeID:
|
||||
)
|
||||
return f"{prio:1x}.{sysid:03x}.{addr}"
|
||||
|
||||
|
||||
class STPPortID:
|
||||
def __init__(self, id):
|
||||
self.id = id
|
||||
@@ -356,6 +376,7 @@ class STPPortID:
|
||||
)
|
||||
return f"{prio:1x}.{pid:03x}"
|
||||
|
||||
|
||||
class DhcpServer:
|
||||
def __init__(self, data):
|
||||
self.data = data
|
||||
@@ -533,6 +554,7 @@ class Iface:
|
||||
|
||||
def is_gretap(self):
|
||||
return self.data['type'] == "infix-if-type:gretap"
|
||||
|
||||
def oper(self, detail=False):
|
||||
"""Remap in brief overview to fit column widths."""
|
||||
if not detail and self.oper_status == "lower-layer-down":
|
||||
@@ -719,7 +741,7 @@ class Iface:
|
||||
lower.pr_name(pipe)
|
||||
lower.pr_proto_lag()
|
||||
|
||||
def pr_veth(self, _ifaces):
|
||||
def pr_veth(self):
|
||||
self.pr_name(pipe="")
|
||||
self.pr_proto_veth()
|
||||
self.pr_proto_ipv4()
|
||||
@@ -930,6 +952,25 @@ class Iface:
|
||||
print(f"{' port':<{20}}: {tc.get('port', 'UNKNOWN')}")
|
||||
|
||||
|
||||
class LldpNeighbor:
|
||||
def __init__(self, iface, data):
|
||||
self.interface = iface
|
||||
self.remote_index = data.get("remote-index", 0)
|
||||
self.time_mark = data.get("time-mark", 0)
|
||||
self.chassis_id = data.get("chassis-id", "unknown")
|
||||
self.port_id = data.get("port-id", "unknown")
|
||||
|
||||
def print(self):
|
||||
row = (
|
||||
f"{self.interface:<{PadLldp.interface}}"
|
||||
f"{self.remote_index:<{PadLldp.rem_idx}}"
|
||||
f"{self.time_mark:<{PadLldp.time}}"
|
||||
f"{self.chassis_id:<{PadLldp.chassis_id}}"
|
||||
f"{self.port_id:<{PadLldp.port_id}}"
|
||||
)
|
||||
print(row)
|
||||
|
||||
|
||||
def find_iface(_ifaces, name):
|
||||
for _iface in [Iface(data) for data in _ifaces]:
|
||||
if _iface.name == name:
|
||||
@@ -941,9 +982,11 @@ def find_iface(_ifaces, name):
|
||||
def version_sort(s):
|
||||
return [int(x) if x.isdigit() else x for x in re.split(r'(\d+)', s)]
|
||||
|
||||
|
||||
def ifname_sort(iface):
|
||||
return version_sort(iface["name"])
|
||||
|
||||
|
||||
def brport_sort(iface):
|
||||
brname = iface.get("infix-interfaces:bridge-port", {}).get("bridge", "")
|
||||
return version_sort(brname) + version_sort(iface["name"])
|
||||
@@ -987,7 +1030,7 @@ def pr_interface_list(json):
|
||||
continue
|
||||
|
||||
if iface.is_veth():
|
||||
iface.pr_veth(ifaces)
|
||||
iface.pr_veth()
|
||||
continue
|
||||
|
||||
if iface.is_gre():
|
||||
@@ -1057,7 +1100,6 @@ def show_bridge_mdb(json):
|
||||
iface.pr_vlans_mdb(iface.name)
|
||||
|
||||
|
||||
|
||||
def show_bridge_stp_port(ifname, brport):
|
||||
stp = brport["stp"]
|
||||
|
||||
@@ -1091,6 +1133,7 @@ def show_bridge_stp_port(ifname, brport):
|
||||
)
|
||||
print(row)
|
||||
|
||||
|
||||
def show_bridge_stp(json):
|
||||
if not json.get("ietf-interfaces:interfaces"):
|
||||
print("Error, top level \"ietf-interfaces:interface\" missing")
|
||||
@@ -1198,6 +1241,7 @@ def show_software(json, name):
|
||||
if slot.is_rootfs():
|
||||
slot.print()
|
||||
|
||||
|
||||
def show_hardware(json):
|
||||
if not json.get("ietf-hardware:hardware"):
|
||||
print(f"Error, top level \"ietf-hardware:component\" missing")
|
||||
@@ -1216,6 +1260,7 @@ def show_hardware(json):
|
||||
port = USBport(component)
|
||||
port.print()
|
||||
|
||||
|
||||
def show_ntp(json):
|
||||
if not json.get("ietf-system:system-state"):
|
||||
print("NTP client not enabled.")
|
||||
@@ -1236,6 +1281,7 @@ def show_ntp(json):
|
||||
row += f"{source['poll']:>{PadNtpSource.poll}}"
|
||||
print(row)
|
||||
|
||||
|
||||
def show_dhcp_server(json, stats):
|
||||
data = json.get("infix-dhcp-server:dhcp-server")
|
||||
if not data:
|
||||
@@ -1255,6 +1301,37 @@ def show_dhcp_server(json, stats):
|
||||
print(Decore.invert(hdr))
|
||||
server.print()
|
||||
|
||||
|
||||
def show_lldp(json):
|
||||
if not json.get("ieee802-dot1ab-lldp:lldp"):
|
||||
print("Error: No LLDP data available.")
|
||||
sys.exit(1)
|
||||
|
||||
lldp_ports = json["ieee802-dot1ab-lldp:lldp"].get("port", [])
|
||||
|
||||
if not lldp_ports:
|
||||
print("No LLDP neighbors found.")
|
||||
return
|
||||
|
||||
header = (
|
||||
f"{'INTERFACE':<{PadLldp.interface}}"
|
||||
f"{'REM-IDX':<{PadLldp.rem_idx}}"
|
||||
f"{'TIME':<{PadLldp.time}}"
|
||||
f"{'CHASSIS-ID':<{PadLldp.chassis_id}}"
|
||||
f"{'PORT-ID':<{PadLldp.port_id}}"
|
||||
)
|
||||
|
||||
print(Decore.invert(header))
|
||||
|
||||
for port_data in lldp_ports:
|
||||
port_name = port_data["name"]
|
||||
neighbors = port_data.get("remote-systems-data", [])
|
||||
|
||||
for neighbor in neighbors:
|
||||
entry = LldpNeighbor(port_name, neighbor)
|
||||
entry.print()
|
||||
|
||||
|
||||
def main():
|
||||
global UNIT_TEST
|
||||
|
||||
@@ -1272,47 +1349,51 @@ def main():
|
||||
|
||||
parser.add_argument('-t', '--test', action='store_true', help='Enable unit test mode')
|
||||
|
||||
parser_show_routing_table = subparsers.add_parser('show-routing-table', help='Show the routing table')
|
||||
parser_show_routing_table.add_argument('-i', '--ip', required=True, help='IPv4 or IPv6 address')
|
||||
subparsers.add_parser('show-boot-order', help='Show NTP sources')
|
||||
|
||||
parser_show_interfaces = subparsers.add_parser('show-interfaces', help='Show interfaces')
|
||||
parser_show_interfaces.add_argument('-n', '--name', help='Interface name')
|
||||
subparsers.add_parser('show-bridge-mdb', help='Show bridge MDB')
|
||||
|
||||
parser_show_bridge_mdb = subparsers.add_parser('show-bridge-mdb', help='Show bridge MDB')
|
||||
parser_show_bridge_stp = subparsers.add_parser('show-bridge-stp',
|
||||
help='Show spanning tree state')
|
||||
subparsers.add_parser('show-bridge-stp', help='Show spanning tree state')
|
||||
|
||||
parser_show_software = subparsers.add_parser('show-software', help='Show software versions')
|
||||
parser_show_software.add_argument('-n', '--name', help='Slotname')
|
||||
subparsers.add_parser('show-dhcp-server', help='Show DHCP server') \
|
||||
.add_argument("-s", "--stats", action="store_true", help="Show server statistics")
|
||||
|
||||
parser_show_hardware = subparsers.add_parser('show-hardware', help='Show USB ports')
|
||||
subparsers.add_parser('show-hardware', help='Show USB ports')
|
||||
|
||||
parser_show_ntp_sources = subparsers.add_parser('show-ntp', help='Show NTP sources')
|
||||
subparsers.add_parser('show-interfaces', help='Show interfaces') \
|
||||
.add_argument('-n', '--name', help='Interface name')
|
||||
|
||||
parser_show_boot_order = subparsers.add_parser('show-boot-order', help='Show NTP sources')
|
||||
subparsers.add_parser('show-lldp', help='Show LLDP neighbors')
|
||||
|
||||
parser_dhcp_srv = subparsers.add_parser('show-dhcp-server', help='Show DHCP server')
|
||||
parser_dhcp_srv.add_argument("-s", "--stats", action="store_true", help="Show server statistics")
|
||||
subparsers.add_parser('show-ntp', help='Show NTP sources')
|
||||
|
||||
subparsers.add_parser('show-routing-table', help='Show the routing table') \
|
||||
.add_argument('-i', '--ip', required=True, help='IPv4 or IPv6 address')
|
||||
|
||||
subparsers.add_parser('show-software', help='Show software versions') \
|
||||
.add_argument('-n', '--name', help='Slotname')
|
||||
|
||||
args = parser.parse_args()
|
||||
UNIT_TEST = args.test
|
||||
|
||||
if args.command == "show-interfaces":
|
||||
if args.command == "show-bridge-mdb":
|
||||
show_bridge_mdb(json_data)
|
||||
elif args.command == "show-bridge-stp":
|
||||
show_bridge_stp(json_data)
|
||||
elif args.command == "show-dhcp-server":
|
||||
show_dhcp_server(json_data, args.stats)
|
||||
elif args.command == "show-hardware":
|
||||
show_hardware(json_data)
|
||||
elif args.command == "show-interfaces":
|
||||
show_interfaces(json_data, args.name)
|
||||
elif args.command == "show-lldp":
|
||||
show_lldp(json_data)
|
||||
elif args.command == "show-ntp":
|
||||
show_ntp(json_data)
|
||||
elif args.command == "show-routing-table":
|
||||
show_routing_table(json_data, args.ip)
|
||||
elif args.command == "show-software":
|
||||
show_software(json_data, args.name)
|
||||
elif args.command == "show-bridge-mdb":
|
||||
show_bridge_mdb(json_data)
|
||||
elif args.command == "show-bridge-stp":
|
||||
show_bridge_stp(json_data)
|
||||
elif args.command == "show-hardware":
|
||||
show_hardware(json_data)
|
||||
elif args.command == "show-ntp":
|
||||
show_ntp(json_data)
|
||||
elif args.command == "show-dhcp-server":
|
||||
show_dhcp_server(json_data, args.stats)
|
||||
else:
|
||||
print(f"Error, unknown command '{args.command}'")
|
||||
sys.exit(1)
|
||||
|
||||
@@ -4,20 +4,35 @@ 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",
|
||||
# https://www.ieee802.org/1/files/public/YANGs/ieee802-types.yang
|
||||
# <chassis-component> and <port-component> subtypes are not supported in
|
||||
# https://github.com/lldpd/lldpd until v1.0.19
|
||||
chassis_id_subtype_mapping = {
|
||||
#"unhandled": "chassis-component",
|
||||
"ifalias": "interface-alias",
|
||||
"port": "port-component",
|
||||
#"unhandled": "port-component",
|
||||
"mac": "mac-address",
|
||||
"ip": "network-address",
|
||||
"ifname": "interface-name",
|
||||
"local": "local"
|
||||
}
|
||||
|
||||
DEFAULT_MAC = "00-00-00-00-00-00"
|
||||
# https://www.ieee802.org/1/files/public/YANGs/ieee802-types.yang
|
||||
# <port-component> and <agent-circuit-id> subtypes are not supported in
|
||||
# https://github.com/lldpd/lldpd until v1.0.19
|
||||
port_id_subtype_mapping = {
|
||||
"ifalias": "interface-alias",
|
||||
#"unhandled": "port-component",
|
||||
"mac": "mac-address",
|
||||
"ip": "network-address",
|
||||
"ifname": "interface-name",
|
||||
#"unhandled": "agent-circuit-id"
|
||||
"local": "local"
|
||||
}
|
||||
|
||||
port_data = defaultdict(lambda: {"remote-systems-data": [], "dest-mac-address": None})
|
||||
LLDP_MULTICAST_MAC = "01:80:C2:00:00:0E"
|
||||
|
||||
port_data = defaultdict(lambda: {"remote-systems-data": [], "dest-mac-address": LLDP_MULTICAST_MAC})
|
||||
|
||||
data = HOST.run_json(["lldpcli", "show", "neighbors", "-f", "json"])
|
||||
|
||||
@@ -32,18 +47,12 @@ def operational():
|
||||
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)
|
||||
chassis_id_type, chassis_id_value = extract_chassis_id(chassis, chassis_id_subtype_mapping)
|
||||
|
||||
port_info = iface_data.get("port", {})
|
||||
port_id_type = subtype_mapping.get(port_info.get("id", {}).get("type"), "unknown")
|
||||
port_id_type = port_id_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,
|
||||
@@ -55,9 +64,6 @@ def operational():
|
||||
|
||||
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": [
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
graph "1x3" {
|
||||
layout="neato";
|
||||
overlap="false";
|
||||
esep="+80";
|
||||
|
||||
node [shape=record, fontname="DejaVu Sans Mono, Book"];
|
||||
edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"];
|
||||
|
||||
host [
|
||||
label="host | { <mgmt> mgmt | <data1> data1 | <data2> data2 }",
|
||||
pos="0,12!",
|
||||
requires="controller",
|
||||
];
|
||||
|
||||
target [
|
||||
label="{ <mgmt> mgmt | <data1> data1 | <data2> data2 } | target",
|
||||
pos="10,12!",
|
||||
|
||||
requires="infix",
|
||||
];
|
||||
|
||||
host:mgmt -- target:mgmt [requires="mgmt", color=lightgrey]
|
||||
host:data1 -- target:data1 [color=black]
|
||||
host:data2 -- target:data2 [color=black]
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
../../../infamy/topologies/1x3.dot
|
||||
@@ -35,7 +35,7 @@
|
||||
<!-- host--target -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>host:mgmt--target:mgmt</title>
|
||||
<path fill="none" stroke="lightgrey" stroke-width="2" d="M108,-58C108,-58 308.03,-58 308.03,-58"/>
|
||||
<path fill="none" stroke="lightgray" stroke-width="2" d="M108,-58C108,-58 308.03,-58 308.03,-58"/>
|
||||
</g>
|
||||
<!-- host--target -->
|
||||
<g id="edge2" class="edge">
|
||||
|
||||
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
@@ -8,14 +8,14 @@ attached to a VLAN filtering bridge are always locally terminated.
|
||||
.---------------------------.
|
||||
| target |
|
||||
| |
|
||||
| data0.10 br0 data1.10 |
|
||||
| data1.10 br0 data2.10 |
|
||||
| \ / \ / |
|
||||
'------data0-----data1------'
|
||||
'------data1-----data2------'
|
||||
| |
|
||||
| |
|
||||
.------data0-----data1------.
|
||||
.------data1-----data2------.
|
||||
| / : \ |
|
||||
| data0.10 : data1.10 |
|
||||
| data1.10 : data2.10 |
|
||||
| : |
|
||||
| host |
|
||||
| : |
|
||||
@@ -23,7 +23,7 @@ attached to a VLAN filtering bridge are always locally terminated.
|
||||
....
|
||||
|
||||
In this setup, even though VLAN 10 is allowed to ingress and egress on
|
||||
both `data0` and `data1`, _bridging_ of packets from one to the other
|
||||
both `data1` and `data2`, _bridging_ of packets from one to the other
|
||||
must _not_ be allowed.
|
||||
|
||||
"""
|
||||
@@ -35,10 +35,10 @@ with infamy.Test() as test:
|
||||
tgt = env.attach("target", "mgmt")
|
||||
|
||||
with test.step("Configure bridge and VLAN interfaces on target"):
|
||||
_, hdata0 = env.ltop.xlate( "host", "data0")
|
||||
_, hdata1 = env.ltop.xlate( "host", "data1")
|
||||
_, ddata0 = env.ltop.xlate("target", "data0")
|
||||
_, hdata2 = env.ltop.xlate( "host", "data2")
|
||||
_, ddata1 = env.ltop.xlate("target", "data1")
|
||||
_, ddata2 = env.ltop.xlate("target", "data2")
|
||||
|
||||
tgt.put_config_dicts({
|
||||
"ietf-interfaces": {
|
||||
@@ -53,35 +53,12 @@ with infamy.Test() as test:
|
||||
"vlan": [
|
||||
{
|
||||
"vid": 1,
|
||||
"untagged": [ddata0, ddata1]
|
||||
"untagged": [ddata1, ddata2]
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": ddata0,
|
||||
"infix-interfaces:bridge-port": {
|
||||
"pvid": 1,
|
||||
"bridge": "br0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": f"{ddata0}.10",
|
||||
"type": "infix-if-type:vlan",
|
||||
"vlan": {
|
||||
"id": 10,
|
||||
"lower-layer-if": ddata0,
|
||||
},
|
||||
"ipv4": {
|
||||
"address": [
|
||||
{
|
||||
"ip": "10.10.1.2",
|
||||
"prefix-length": 24,
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": ddata1,
|
||||
"infix-interfaces:bridge-port": {
|
||||
@@ -96,6 +73,29 @@ with infamy.Test() as test:
|
||||
"id": 10,
|
||||
"lower-layer-if": ddata1,
|
||||
},
|
||||
"ipv4": {
|
||||
"address": [
|
||||
{
|
||||
"ip": "10.10.1.2",
|
||||
"prefix-length": 24,
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": ddata2,
|
||||
"infix-interfaces:bridge-port": {
|
||||
"pvid": 1,
|
||||
"bridge": "br0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": f"{ddata2}.10",
|
||||
"type": "infix-if-type:vlan",
|
||||
"vlan": {
|
||||
"id": 10,
|
||||
"lower-layer-if": ddata2,
|
||||
},
|
||||
"ipv4": {
|
||||
"address": [
|
||||
{
|
||||
@@ -110,8 +110,8 @@ with infamy.Test() as test:
|
||||
}
|
||||
})
|
||||
|
||||
with infamy.IsolatedMacVlan(hdata0) as ns0, \
|
||||
infamy.IsolatedMacVlan(hdata1) as ns1:
|
||||
with infamy.IsolatedMacVlan(hdata1) as ns0, \
|
||||
infamy.IsolatedMacVlan(hdata2) as ns1:
|
||||
|
||||
with test.step("Configure IP addresses and VLAN interfaces on host"):
|
||||
ns0.addip("10.0.1.1")
|
||||
@@ -128,17 +128,17 @@ with infamy.Test() as test:
|
||||
ip addr add 10.10.2.1/24 dev vlan10
|
||||
""")
|
||||
|
||||
with test.step("Verify that host:data0 reaches host:data1 with untagged packets"):
|
||||
with test.step("Verify that host:data1 reaches host:data2 with untagged packets"):
|
||||
ns0.must_reach("10.0.1.2")
|
||||
|
||||
with test.step("Verify that traffic on VLAN 10 from host:data0 does not reach host:data1"):
|
||||
with test.step("Verify that traffic on VLAN 10 from host:data1 does not reach host:data2"):
|
||||
infamy.parallel(lambda: ns0.runsh("timeout -s INT 5 ping -i 0.2 -b 10.10.1.255 || true"),
|
||||
lambda: ns1.must_not_receive("ip src 10.10.1.1"))
|
||||
|
||||
with test.step("Verify that host:data0 can reach target on VLAN 10"):
|
||||
with test.step("Verify that host:data1 can reach target on VLAN 10"):
|
||||
ns0.must_reach("10.10.1.2")
|
||||
|
||||
with test.step("Verify that host:data1 can reach target on VLAN 10"):
|
||||
with test.step("Verify that host:data2 can reach target on VLAN 10"):
|
||||
ns1.must_reach("10.10.2.2")
|
||||
|
||||
test.succeed()
|
||||
|
||||
@@ -14,11 +14,11 @@
|
||||
<polygon fill="none" stroke="black" points="0,-0.5 0,-69.5 108,-69.5 108,-0.5 0,-0.5"/>
|
||||
<text text-anchor="middle" x="25" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">host</text>
|
||||
<polyline fill="none" stroke="black" points="50,-0.5 50,-69.5 "/>
|
||||
<text text-anchor="middle" x="79" y="-54.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">tgt</text>
|
||||
<text text-anchor="middle" x="79" y="-54.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
|
||||
<polyline fill="none" stroke="black" points="50,-46.5 108,-46.5 "/>
|
||||
<text text-anchor="middle" x="79" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data0</text>
|
||||
<text text-anchor="middle" x="79" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data1</text>
|
||||
<polyline fill="none" stroke="black" points="50,-23.5 108,-23.5 "/>
|
||||
<text text-anchor="middle" x="79" y="-8.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data1</text>
|
||||
<text text-anchor="middle" x="79" y="-8.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data2</text>
|
||||
</g>
|
||||
<!-- target -->
|
||||
<g id="node2" class="node">
|
||||
@@ -26,25 +26,25 @@
|
||||
<polygon fill="none" stroke="black" points="308.03,-0.5 308.03,-69.5 432.03,-69.5 432.03,-0.5 308.03,-0.5"/>
|
||||
<text text-anchor="middle" x="337.03" y="-54.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
|
||||
<polyline fill="none" stroke="black" points="308.03,-46.5 366.03,-46.5 "/>
|
||||
<text text-anchor="middle" x="337.03" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data0</text>
|
||||
<text text-anchor="middle" x="337.03" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data1</text>
|
||||
<polyline fill="none" stroke="black" points="308.03,-23.5 366.03,-23.5 "/>
|
||||
<text text-anchor="middle" x="337.03" y="-8.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data1</text>
|
||||
<text text-anchor="middle" x="337.03" y="-8.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data2</text>
|
||||
<polyline fill="none" stroke="black" points="366.03,-0.5 366.03,-69.5 "/>
|
||||
<text text-anchor="middle" x="399.03" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">target</text>
|
||||
</g>
|
||||
<!-- host--target -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>host:tgt--target:mgmt</title>
|
||||
<path fill="none" stroke="cornflowerblue" stroke-width="2" d="M108,-58C108,-58 308.03,-58 308.03,-58"/>
|
||||
<title>host:mgmt--target:mgmt</title>
|
||||
<path fill="none" stroke="lightgray" stroke-width="2" d="M108,-58C108,-58 308.03,-58 308.03,-58"/>
|
||||
</g>
|
||||
<!-- host--target -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>host:data0--target:data0</title>
|
||||
<title>host:data1--target:data1</title>
|
||||
<path fill="none" stroke="black" stroke-width="2" d="M108,-35C108,-35 308.03,-35 308.03,-35"/>
|
||||
</g>
|
||||
<!-- host--target -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>host:data1--target:data1</title>
|
||||
<title>host:data2--target:data2</title>
|
||||
<path fill="none" stroke="black" stroke-width="2" d="M108,-12C108,-12 308.03,-12 308.03,-12"/>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
@@ -7,14 +7,14 @@ attached to a VLAN filtering bridge are always locally terminated.
|
||||
.---------------------------.
|
||||
| target |
|
||||
| |
|
||||
| data0.10 br0 data1.10 |
|
||||
| data1.10 br0 data2.10 |
|
||||
| \ / \ / |
|
||||
'------data0-----data1------'
|
||||
'------data1-----data2------'
|
||||
| |
|
||||
| |
|
||||
.------data0-----data1------.
|
||||
.------data1-----data2------.
|
||||
| / : \ |
|
||||
| data0.10 : data1.10 |
|
||||
| data1.10 : data2.10 |
|
||||
| : |
|
||||
| host |
|
||||
| : |
|
||||
@@ -22,7 +22,7 @@ attached to a VLAN filtering bridge are always locally terminated.
|
||||
....
|
||||
|
||||
In this setup, even though VLAN 10 is allowed to ingress and egress on
|
||||
both `data0` and `data1`, _bridging_ of packets from one to the other
|
||||
both `data1` and `data2`, _bridging_ of packets from one to the other
|
||||
must _not_ be allowed.
|
||||
|
||||
==== Topology
|
||||
@@ -41,10 +41,10 @@ endif::topdoc[]
|
||||
. Set up topology and attach to target
|
||||
. Configure bridge and VLAN interfaces on target
|
||||
. Configure IP addresses and VLAN interfaces on host
|
||||
. Verify that host:data0 reaches host:data1 with untagged packets
|
||||
. Verify that traffic on VLAN 10 from host:data0 does not reach host:data1
|
||||
. Verify that host:data0 can reach target on VLAN 10
|
||||
. Verify that host:data1 reaches host:data2 with untagged packets
|
||||
. Verify that traffic on VLAN 10 from host:data1 does not reach host:data2
|
||||
. Verify that host:data1 can reach target on VLAN 10
|
||||
. Verify that host:data2 can reach target on VLAN 10
|
||||
|
||||
|
||||
<<<
|
||||
|
||||
@@ -3,14 +3,16 @@
|
||||
|
||||
<<<
|
||||
|
||||
include::mdns_enable_disable/Readme.adoc[]
|
||||
include::mdns/mdns_enable_disable/Readme.adoc[]
|
||||
|
||||
include::mdns_allow_deny/Readme.adoc[]
|
||||
include::mdns/mdns_allow_deny/Readme.adoc[]
|
||||
|
||||
include::lldp_enable_disable/Readme.adoc[]
|
||||
include::lldp/lldp_enable_disable/Readme.adoc[]
|
||||
|
||||
include::lldp_admin_status/Readme.adoc[]
|
||||
include::lldp/lldp_admin_status/Readme.adoc[]
|
||||
|
||||
include::ssh_server_config/Readme.adoc[]
|
||||
include::lldp/lldp_ieee_group_forward/Readme.adoc[]
|
||||
|
||||
include::ssh_key_authentication/Readme.adoc[]
|
||||
include::ssh/ssh_server_config/Readme.adoc[]
|
||||
|
||||
include::ssh/ssh_key_authentication/Readme.adoc[]
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
---
|
||||
- name: lldp
|
||||
suite: lldp.yaml
|
||||
suite: lldp/lldp.yaml
|
||||
|
||||
- name: mdns
|
||||
suite: mdns.yaml
|
||||
suite: mdns/mdns.yaml
|
||||
|
||||
- name: ssh
|
||||
suite: ssh.yaml
|
||||
suite: ssh/ssh.yaml
|
||||
|
||||
@@ -4,3 +4,6 @@
|
||||
|
||||
- name: lldp_admin_status
|
||||
case: lldp_admin_status/test.py
|
||||
|
||||
- name: lldp_ieee_group_forward
|
||||
case: lldp_ieee_group_forward/test.py
|
||||
@@ -4,11 +4,11 @@ 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]
|
||||
image::{topdoc}../../test/case/infix_services/lldp/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]
|
||||
image::lldp/lldp_admin_status/topology.svg[LLDP admin status topology]
|
||||
endif::testgroup[]
|
||||
ifndef::testgroup[]
|
||||
image::topology.svg[LLDP admin status topology]
|
||||
@@ -22,7 +22,7 @@ def capture_traffic(iface, 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)
|
||||
eth = Ether(src="02:01:02:03:04:05", 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)
|
||||
@@ -45,7 +45,7 @@ def verify_admin_status(test, target, port, admin_status, local_capture, remote_
|
||||
"lldp": {
|
||||
"port": [{
|
||||
"name": target["data"],
|
||||
"dest-mac-address": "00-00-00-00-00-00",
|
||||
"dest-mac-address": "01:80:C2:00:00:0E",
|
||||
"admin-status": admin_status
|
||||
}]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
@@ -5,11 +5,11 @@ Operation and non-operation are confirmed using tcpdump.
|
||||
|
||||
==== Topology
|
||||
ifdef::topdoc[]
|
||||
image::{topdoc}../../test/case/infix_services/lldp_enable_disable/topology.svg[LLDP enable disable topology]
|
||||
image::{topdoc}../../test/case/infix_services/lldp/lldp_enable_disable/topology.svg[LLDP enable disable topology]
|
||||
endif::topdoc[]
|
||||
ifndef::topdoc[]
|
||||
ifdef::testgroup[]
|
||||
image::lldp_enable_disable/topology.svg[LLDP enable disable topology]
|
||||
image::lldp/lldp_enable_disable/topology.svg[LLDP enable disable topology]
|
||||
endif::testgroup[]
|
||||
ifndef::testgroup[]
|
||||
image::topology.svg[LLDP enable disable topology]
|
||||
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
@@ -0,0 +1 @@
|
||||
lldp_ieee_group_forward.adoc
|
||||
@@ -0,0 +1,27 @@
|
||||
=== LLDP IEEE Group Forward
|
||||
==== Description
|
||||
Verify that LLDP packets can be flooded to all ports on a bridge.
|
||||
Operation and non-operation are confirmed using tcpdump.
|
||||
|
||||
==== Topology
|
||||
ifdef::topdoc[]
|
||||
image::{topdoc}../../test/case/infix_services/lldp/lldp_ieee_group_forward/topology.svg[LLDP IEEE Group Forward topology]
|
||||
endif::topdoc[]
|
||||
ifndef::topdoc[]
|
||||
ifdef::testgroup[]
|
||||
image::lldp/lldp_ieee_group_forward/topology.svg[LLDP IEEE Group Forward topology]
|
||||
endif::testgroup[]
|
||||
ifndef::testgroup[]
|
||||
image::topology.svg[LLDP IEEE Group Forward topology]
|
||||
endif::testgroup[]
|
||||
endif::topdoc[]
|
||||
==== Test sequence
|
||||
. Set up topology and attach to target DUT
|
||||
. Configure interfaces and disable LLDP daemon
|
||||
. Verify LLDP absence on host:data2
|
||||
. Enable LLDP flooding by setting group forward
|
||||
. Verify LLDP arrival on host:data2
|
||||
|
||||
|
||||
<<<
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""LLDP IEEE Group Forward
|
||||
|
||||
Verify that LLDP packets can be flooded to all ports on a bridge.
|
||||
Operation and non-operation are confirmed using tcpdump.
|
||||
"""
|
||||
|
||||
import time
|
||||
import threading
|
||||
import infamy
|
||||
|
||||
from scapy.all import Ether, sendp
|
||||
from scapy.contrib.lldp import (
|
||||
LLDPDU, LLDPDUChassisID, LLDPDUPortID,
|
||||
LLDPDUTimeToLive, LLDPDUEndOfLLDPDU
|
||||
)
|
||||
|
||||
SNIFFING_DURATION = 3
|
||||
|
||||
LLDP_INTERVAL = 1
|
||||
LLDP_TTL = 3
|
||||
LLDP_MULTICAST_MAC = "01:80:C2:00:00:0E"
|
||||
LLDP_ETHERTYPE = 0x88cc
|
||||
|
||||
CHASSIS_ID = "Chassis-Test"
|
||||
SRC_MAC = "02:01:02:03:04:05"
|
||||
|
||||
BRIDGE = "br0"
|
||||
|
||||
def send_lldp_packet(iface):
|
||||
eth = Ether(src=SRC_MAC, dst=LLDP_MULTICAST_MAC, type=LLDP_ETHERTYPE)
|
||||
lldpdu = eth / LLDPDU()
|
||||
lldpdu /= LLDPDUChassisID(subtype=7, id=CHASSIS_ID)
|
||||
lldpdu /= LLDPDUPortID(subtype=5, id=iface)
|
||||
lldpdu /= LLDPDUTimeToLive(ttl=LLDP_TTL)
|
||||
lldpdu /= LLDPDUEndOfLLDPDU()
|
||||
sendp(lldpdu, iface=iface, verbose=False)
|
||||
|
||||
def send_lldp_packets_continuously(iface, stop_event):
|
||||
time.sleep(LLDP_INTERVAL/2)
|
||||
while not stop_event.is_set():
|
||||
send_lldp_packet(iface)
|
||||
time.sleep(LLDP_INTERVAL)
|
||||
|
||||
def capture_traffic(iface):
|
||||
with infamy.IsolatedMacVlan(iface) as netns:
|
||||
sniffer = infamy.Sniffer(netns, f"ether proto {LLDP_ETHERTYPE}")
|
||||
with sniffer:
|
||||
time.sleep(SNIFFING_DURATION)
|
||||
return sniffer.output()
|
||||
|
||||
def start_lldp_sender_thread(iface):
|
||||
stop = threading.Event()
|
||||
thread = threading.Thread(
|
||||
target=send_lldp_packets_continuously,
|
||||
args=(iface, stop)
|
||||
)
|
||||
thread.start()
|
||||
return stop, thread
|
||||
|
||||
with infamy.Test() as test:
|
||||
with test.step("Set up topology and attach to target DUT"):
|
||||
env = infamy.Env()
|
||||
target = env.attach("target", "mgmt")
|
||||
_, hdata1 = env.ltop.xlate("host", "data1")
|
||||
_, hdata2 = env.ltop.xlate("host", "data2")
|
||||
|
||||
with test.step("Configure interfaces and disable LLDP daemon"):
|
||||
target.put_config_dicts({
|
||||
"ietf-interfaces": {
|
||||
"interfaces": {
|
||||
"interface": [
|
||||
{
|
||||
"name": BRIDGE,
|
||||
"type": "infix-if-type:bridge",
|
||||
"enabled": True,
|
||||
},
|
||||
{
|
||||
"name": target["data1"],
|
||||
"enabled": True,
|
||||
"infix-interfaces:bridge-port": {
|
||||
"bridge": "br0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": target["data2"],
|
||||
"enabled": True,
|
||||
"infix-interfaces:bridge-port": {
|
||||
"bridge": "br0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"ieee802-dot1ab-lldp": {
|
||||
"lldp": {
|
||||
"enabled": False
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
stop_event, sender_thread = start_lldp_sender_thread(hdata1)
|
||||
|
||||
try:
|
||||
with test.step("Verify LLDP absence on host:data2"):
|
||||
captured_traffic = capture_traffic(hdata2)
|
||||
if "LLDP" in captured_traffic.stdout:
|
||||
test.fail()
|
||||
|
||||
finally:
|
||||
stop_event.set()
|
||||
sender_thread.join()
|
||||
|
||||
with test.step("Enable LLDP flooding by setting group forward"):
|
||||
target.put_config_dicts({
|
||||
"ietf-interfaces": {
|
||||
"interfaces": {
|
||||
"interface": [
|
||||
{
|
||||
"name": BRIDGE,
|
||||
"infix-interfaces:bridge": {
|
||||
"ieee-group-forward": [
|
||||
"lldp"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
stop_event, sender_thread = start_lldp_sender_thread(hdata1)
|
||||
|
||||
try:
|
||||
with test.step("Verify LLDP arrival on host:data2"):
|
||||
captured_traffic = capture_traffic(hdata2)
|
||||
if "LLDP" not in captured_traffic.stdout:
|
||||
test.fail()
|
||||
|
||||
finally:
|
||||
stop_event.set()
|
||||
sender_thread.join()
|
||||
|
||||
test.succeed()
|
||||
@@ -0,0 +1,24 @@
|
||||
graph "1x3" {
|
||||
layout="neato";
|
||||
overlap="false";
|
||||
esep="+80";
|
||||
|
||||
node [shape=record, fontname="DejaVu Sans Mono, Book"];
|
||||
edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"];
|
||||
|
||||
host [
|
||||
label="host | { <mgmt> mgmt | <data1> data1 | <data2> data2 }",
|
||||
pos="0,12!",
|
||||
requires="controller",
|
||||
];
|
||||
|
||||
target [
|
||||
label="{ <mgmt> mgmt | <data1> data1 | <data2> data2 } | target",
|
||||
pos="10,12!",
|
||||
requires="infix",
|
||||
];
|
||||
|
||||
host:mgmt -- target:mgmt [requires="mgmt", color="lightgray"]
|
||||
host:data1 -- target:data1 [color="black", requires="ieee-mc"]
|
||||
host:data2 -- target:data2 [color="black", requires="ieee-mc"]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
@@ -10,11 +10,11 @@ with three scenarios:
|
||||
|
||||
==== Topology
|
||||
ifdef::topdoc[]
|
||||
image::{topdoc}../../test/case/infix_services/mdns_allow_deny/topology.svg[mDNS allow/deny interfaces topology]
|
||||
image::{topdoc}../../test/case/infix_services/mdns/mdns_allow_deny/topology.svg[mDNS allow/deny interfaces topology]
|
||||
endif::topdoc[]
|
||||
ifndef::topdoc[]
|
||||
ifdef::testgroup[]
|
||||
image::mdns_allow_deny/topology.svg[mDNS allow/deny interfaces topology]
|
||||
image::mdns/mdns_allow_deny/topology.svg[mDNS allow/deny interfaces topology]
|
||||
endif::testgroup[]
|
||||
ifndef::testgroup[]
|
||||
image::topology.svg[mDNS allow/deny interfaces topology]
|
||||
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
@@ -5,11 +5,11 @@ Operation and non-operation are confirmed using tcpdump.
|
||||
|
||||
==== Topology
|
||||
ifdef::topdoc[]
|
||||
image::{topdoc}../../test/case/infix_services/mdns_enable_disable/topology.svg[mDNS enable disable topology]
|
||||
image::{topdoc}../../test/case/infix_services/mdns/mdns_enable_disable/topology.svg[mDNS enable disable topology]
|
||||
endif::topdoc[]
|
||||
ifndef::topdoc[]
|
||||
ifdef::testgroup[]
|
||||
image::mdns_enable_disable/topology.svg[mDNS enable disable topology]
|
||||
image::mdns/mdns_enable_disable/topology.svg[mDNS enable disable topology]
|
||||
endif::testgroup[]
|
||||
ifndef::testgroup[]
|
||||
image::topology.svg[mDNS enable disable topology]
|
||||
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
@@ -4,11 +4,11 @@ Verify that 'guest' user can fetch data using only the 'public' key
|
||||
|
||||
==== Topology
|
||||
ifdef::topdoc[]
|
||||
image::{topdoc}../../test/case/infix_services/ssh_key_authentication/topology.svg[Generate ssh key pair topology]
|
||||
image::{topdoc}../../test/case/infix_services/ssh/ssh_key_authentication/topology.svg[Generate ssh key pair topology]
|
||||
endif::topdoc[]
|
||||
ifndef::topdoc[]
|
||||
ifdef::testgroup[]
|
||||
image::ssh_key_authentication/topology.svg[Generate ssh key pair topology]
|
||||
image::ssh/ssh_key_authentication/topology.svg[Generate ssh key pair topology]
|
||||
endif::testgroup[]
|
||||
ifndef::testgroup[]
|
||||
image::topology.svg[Generate ssh key pair topology]
|
||||
@@ -0,0 +1 @@
|
||||
../../../../infamy/topologies/1x1.dot
|
||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
@@ -7,11 +7,11 @@ Test SSH server functionality with pre-defined key pair:
|
||||
|
||||
==== Topology
|
||||
ifdef::topdoc[]
|
||||
image::{topdoc}../../test/case/infix_services/ssh_server_config/topology.svg[SSH server configuration topology]
|
||||
image::{topdoc}../../test/case/infix_services/ssh/ssh_server_config/topology.svg[SSH server configuration topology]
|
||||
endif::topdoc[]
|
||||
ifndef::topdoc[]
|
||||
ifdef::testgroup[]
|
||||
image::ssh_server_config/topology.svg[SSH server configuration topology]
|
||||
image::ssh/ssh_server_config/topology.svg[SSH server configuration topology]
|
||||
endif::testgroup[]
|
||||
ifndef::testgroup[]
|
||||
image::topology.svg[SSH server configuration topology]
|
||||
@@ -0,0 +1 @@
|
||||
../../../../infamy/topologies/1x3.dot
|
||||
@@ -0,0 +1,51 @@
|
||||
<?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: 1x3 Pages: 1 -->
|
||||
<svg width="440pt" height="78pt"
|
||||
viewBox="0.00 0.00 440.03 78.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 74)">
|
||||
<title>1x3</title>
|
||||
<polygon fill="white" stroke="transparent" points="-4,4 -4,-74 436.03,-74 436.03,4 -4,4"/>
|
||||
<!-- host -->
|
||||
<g id="node1" class="node">
|
||||
<title>host</title>
|
||||
<polygon fill="none" stroke="black" points="0,-0.5 0,-69.5 108,-69.5 108,-0.5 0,-0.5"/>
|
||||
<text text-anchor="middle" x="25" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">host</text>
|
||||
<polyline fill="none" stroke="black" points="50,-0.5 50,-69.5 "/>
|
||||
<text text-anchor="middle" x="79" y="-54.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
|
||||
<polyline fill="none" stroke="black" points="50,-46.5 108,-46.5 "/>
|
||||
<text text-anchor="middle" x="79" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data1</text>
|
||||
<polyline fill="none" stroke="black" points="50,-23.5 108,-23.5 "/>
|
||||
<text text-anchor="middle" x="79" y="-8.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data2</text>
|
||||
</g>
|
||||
<!-- target -->
|
||||
<g id="node2" class="node">
|
||||
<title>target</title>
|
||||
<polygon fill="none" stroke="black" points="308.03,-0.5 308.03,-69.5 432.03,-69.5 432.03,-0.5 308.03,-0.5"/>
|
||||
<text text-anchor="middle" x="337.03" y="-54.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
|
||||
<polyline fill="none" stroke="black" points="308.03,-46.5 366.03,-46.5 "/>
|
||||
<text text-anchor="middle" x="337.03" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data1</text>
|
||||
<polyline fill="none" stroke="black" points="308.03,-23.5 366.03,-23.5 "/>
|
||||
<text text-anchor="middle" x="337.03" y="-8.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data2</text>
|
||||
<polyline fill="none" stroke="black" points="366.03,-0.5 366.03,-69.5 "/>
|
||||
<text text-anchor="middle" x="399.03" y="-31.3" 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="lightgray" stroke-width="2" d="M108,-58C108,-58 308.03,-58 308.03,-58"/>
|
||||
</g>
|
||||
<!-- host--target -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>host:data1--target:data1</title>
|
||||
<path fill="none" stroke="black" stroke-width="2" d="M108,-35C108,-35 308.03,-35 308.03,-35"/>
|
||||
</g>
|
||||
<!-- host--target -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>host:data2--target:data2</title>
|
||||
<path fill="none" stroke="black" stroke-width="2" d="M108,-12C108,-12 308.03,-12 308.03,-12"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
@@ -1 +0,0 @@
|
||||
../../../infamy/topologies/1x1.dot
|
||||
@@ -1,24 +0,0 @@
|
||||
graph "1x3" {
|
||||
layout="neato";
|
||||
overlap="false";
|
||||
esep="+80";
|
||||
|
||||
node [shape=record, fontname="DejaVu Sans Mono, Book"];
|
||||
edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"];
|
||||
|
||||
host [
|
||||
label="host | { <mgmt> mgmt | <data1> data1 | <data2> data2 }",
|
||||
pos="0,12!",
|
||||
requires="controller",
|
||||
];
|
||||
|
||||
target [
|
||||
label="{ <mgmt> mgmt | <data1> data1 | <data2> data2 } | target",
|
||||
pos="10,12!",
|
||||
requires="infix",
|
||||
];
|
||||
|
||||
host:mgmt -- target:mgmt [requires="mgmt", color="lightgray"]
|
||||
host:data1 -- target:data1 [color="black"]
|
||||
host:data2 -- target:data2 [color="black"]
|
||||
}
|
||||
@@ -4,22 +4,21 @@ graph "1x3" {
|
||||
esep="+80";
|
||||
|
||||
node [shape=record, fontname="DejaVu Sans Mono, Book"];
|
||||
edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"];
|
||||
edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"];
|
||||
|
||||
host [
|
||||
label="host | { <tgt> tgt | <data0> data0 | <data1> data1 }",
|
||||
pos="0,12!",
|
||||
requires="controller",
|
||||
label="host | { <mgmt> mgmt | <data1> data1 | <data2> data2 }",
|
||||
pos="0,12!",
|
||||
requires="controller",
|
||||
];
|
||||
|
||||
target [
|
||||
label="{ <mgmt> mgmt | <data0> data0 | <data1> data1 } | target",
|
||||
pos="10,12!",
|
||||
|
||||
requires="infix",
|
||||
target [
|
||||
label="{ <mgmt> mgmt | <data1> data1 | <data2> data2 } | target",
|
||||
pos="10,12!",
|
||||
requires="infix",
|
||||
];
|
||||
|
||||
host:tgt -- target:mgmt [requires="mgmt"]
|
||||
host:data0 -- target:data0 [color=black]
|
||||
host:data1 -- target:data1 [color=black]
|
||||
}
|
||||
host:mgmt -- target:mgmt [requires="mgmt", color="lightgray"]
|
||||
host:data1 -- target:data1 [color="black"]
|
||||
host:data2 -- target:data2 [color="black"]
|
||||
}
|
||||
|
||||