testprotocols 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- testprotocols/__init__.py +217 -0
- testprotocols/aftr_gateway.py +22 -0
- testprotocols/appliance_nat.py +52 -0
- testprotocols/appliance_uplinks.py +32 -0
- testprotocols/appliance_vlans.py +50 -0
- testprotocols/arp_client.py +26 -0
- testprotocols/bgp.py +55 -0
- testprotocols/conntrack.py +147 -0
- testprotocols/content_filtering.py +47 -0
- testprotocols/device_lifecycle.py +49 -0
- testprotocols/device_management.py +50 -0
- testprotocols/devices/__init__.py +46 -0
- testprotocols/devices/base.py +40 -0
- testprotocols/devices/client.py +133 -0
- testprotocols/devices/cpe.py +66 -0
- testprotocols/devices/infra.py +62 -0
- testprotocols/devices/sdwan.py +97 -0
- testprotocols/devices/switch.py +115 -0
- testprotocols/devices/traffic.py +53 -0
- testprotocols/devices/voice.py +69 -0
- testprotocols/devices/wan.py +60 -0
- testprotocols/dhcp_client.py +30 -0
- testprotocols/dhcp_server.py +23 -0
- testprotocols/discovery.py +20 -0
- testprotocols/dns_client.py +23 -0
- testprotocols/file_transfer.py +22 -0
- testprotocols/firewall.py +121 -0
- testprotocols/firewall_zones.py +133 -0
- testprotocols/first_hop_security.py +52 -0
- testprotocols/gateway_redundancy.py +29 -0
- testprotocols/http_client.py +36 -0
- testprotocols/http_server.py +22 -0
- testprotocols/hw_console.py +48 -0
- testprotocols/infra_controller.py +28 -0
- testprotocols/interface_dhcp.py +30 -0
- testprotocols/ip_interface.py +62 -0
- testprotocols/ip_routing.py +57 -0
- testprotocols/iperf_client.py +47 -0
- testprotocols/iperf_generator.py +42 -0
- testprotocols/iperf_server.py +41 -0
- testprotocols/l3_firewall.py +74 -0
- testprotocols/l7_firewall.py +32 -0
- testprotocols/link_aggregation.py +24 -0
- testprotocols/mac_table.py +20 -0
- testprotocols/models/__init__.py +304 -0
- testprotocols/models/dhcp.py +28 -0
- testprotocols/models/firewall.py +197 -0
- testprotocols/models/impairment.py +18 -0
- testprotocols/models/l2_common.py +53 -0
- testprotocols/models/multicast.py +22 -0
- testprotocols/models/networking.py +50 -0
- testprotocols/models/packets.py +21 -0
- testprotocols/models/qoe.py +31 -0
- testprotocols/models/radius.py +63 -0
- testprotocols/models/sdwan_appliance.py +637 -0
- testprotocols/models/switch.py +297 -0
- testprotocols/models/switch_routing.py +122 -0
- testprotocols/models/tr069.py +35 -0
- testprotocols/models/traffic.py +29 -0
- testprotocols/models/wan_edge.py +116 -0
- testprotocols/models/wifi.py +183 -0
- testprotocols/multicast_client.py +20 -0
- testprotocols/nat.py +87 -0
- testprotocols/netem_controller.py +42 -0
- testprotocols/network_endpoint.py +32 -0
- testprotocols/network_probe.py +27 -0
- testprotocols/nmap_scanner.py +27 -0
- testprotocols/ntp_client.py +26 -0
- testprotocols/ntp_config.py +25 -0
- testprotocols/ospf.py +24 -0
- testprotocols/packet_filter.py +144 -0
- testprotocols/pcap_capture.py +39 -0
- testprotocols/pdu_controller.py +26 -0
- testprotocols/port_poe.py +25 -0
- testprotocols/port_security.py +25 -0
- testprotocols/port_status.py +23 -0
- testprotocols/py.typed +0 -0
- testprotocols/qoe_browser.py +62 -0
- testprotocols/radius_client.py +78 -0
- testprotocols/radius_server.py +130 -0
- testprotocols/routed_interfaces.py +29 -0
- testprotocols/router.py +53 -0
- testprotocols/routing_read.py +22 -0
- testprotocols/sdwan_policy_manager.py +64 -0
- testprotocols/sip_phone.py +230 -0
- testprotocols/sip_server.py +205 -0
- testprotocols/site_to_site_vpn.py +61 -0
- testprotocols/snmp_client.py +17 -0
- testprotocols/spanning_tree.py +37 -0
- testprotocols/static_routes.py +47 -0
- testprotocols/storm_control.py +24 -0
- testprotocols/streaming_server.py +32 -0
- testprotocols/switch_acl.py +29 -0
- testprotocols/switch_ports.py +28 -0
- testprotocols/switch_qos.py +33 -0
- testprotocols/switch_vlans.py +34 -0
- testprotocols/syslog_config.py +31 -0
- testprotocols/tftp_server.py +22 -0
- testprotocols/threat_prevention.py +60 -0
- testprotocols/tr069_client.py +47 -0
- testprotocols/tr069_server.py +151 -0
- testprotocols/traffic_shaping.py +54 -0
- testprotocols/upnp_client.py +37 -0
- testprotocols/vlan_client.py +22 -0
- testprotocols/wan_link_admin.py +34 -0
- testprotocols/wifi_bss.py +197 -0
- testprotocols/wifi_client.py +72 -0
- testprotocols/wifi_mesh.py +259 -0
- testprotocols/wifi_onboarding.py +105 -0
- testprotocols/wifi_radio.py +153 -0
- testprotocols/wifi_rf.py +78 -0
- testprotocols/wifi_stations.py +59 -0
- testprotocols/wifi_transitions.py +112 -0
- testprotocols-0.1.0.dist-info/METADATA +29 -0
- testprotocols-0.1.0.dist-info/RECORD +119 -0
- testprotocols-0.1.0.dist-info/WHEEL +5 -0
- testprotocols-0.1.0.dist-info/licenses/LICENSE +201 -0
- testprotocols-0.1.0.dist-info/licenses/NOTICE +11 -0
- testprotocols-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Traffic device archetypes — impairment injection and load generation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Protocol, runtime_checkable
|
|
6
|
+
|
|
7
|
+
from testprotocols.devices import register_device_type
|
|
8
|
+
from testprotocols.devices.base import BaseDeviceProtocol
|
|
9
|
+
from testprotocols.ip_interface import IpInterface
|
|
10
|
+
from testprotocols.iperf_generator import IperfGenerator
|
|
11
|
+
from testprotocols.netem_controller import NetemController
|
|
12
|
+
from testprotocols.pcap_capture import PcapCapture
|
|
13
|
+
from testprotocols.syslog_config import SyslogConfig
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@runtime_checkable
|
|
17
|
+
class TrafficControllerDevice(BaseDeviceProtocol, Protocol):
|
|
18
|
+
"""Traffic controller archetype — inline impairment injector.
|
|
19
|
+
|
|
20
|
+
Sits in the data path to apply latency, loss, jitter, and rate-limit
|
|
21
|
+
impairments via netem. Being inline on the path, it is also the device that
|
|
22
|
+
**captures** traffic crossing it (pcap) — the capture role the host/appliance
|
|
23
|
+
and switch archetypes deliberately exclude as "the TrafficControllerDevice's
|
|
24
|
+
job" (SPLITS.md). Also exposes IP interface management for placement on the
|
|
25
|
+
test topology.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
netem: NetemController
|
|
29
|
+
ip_interface: IpInterface
|
|
30
|
+
pcap: PcapCapture
|
|
31
|
+
syslog: SyslogConfig
|
|
32
|
+
"""Where this host forwards its syslog stream (management-plane telemetry).
|
|
33
|
+
|
|
34
|
+
Testbed-driven: the driver auto-applies the configured collector at
|
|
35
|
+
configure time; a testbed with no collector simply leaves it unset.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@runtime_checkable
|
|
40
|
+
class IperfTrafficGeneratorDevice(BaseDeviceProtocol, Protocol):
|
|
41
|
+
"""Iperf traffic generator archetype — multi-flow load source.
|
|
42
|
+
|
|
43
|
+
Drives concurrent iperf flows (UDP / TCP, configurable rate / count) to
|
|
44
|
+
saturate or stress a path under test. IP interface management for
|
|
45
|
+
placement on the test topology.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
iperf_generator: IperfGenerator
|
|
49
|
+
ip_interface: IpInterface
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
register_device_type("linux_traffic_controller", TrafficControllerDevice)
|
|
53
|
+
register_device_type("iperf_traffic_generator", IperfTrafficGeneratorDevice)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Voice device archetypes — SIP phone and SIP server."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Protocol, runtime_checkable
|
|
6
|
+
|
|
7
|
+
from testprotocols.devices import register_device_type
|
|
8
|
+
from testprotocols.devices.base import BaseDeviceProtocol
|
|
9
|
+
from testprotocols.dhcp_client import DhcpClient
|
|
10
|
+
from testprotocols.file_transfer import FileTransfer
|
|
11
|
+
from testprotocols.ip_interface import IpInterface
|
|
12
|
+
from testprotocols.pcap_capture import PcapCapture
|
|
13
|
+
from testprotocols.sip_phone import SipPhone
|
|
14
|
+
from testprotocols.sip_server import SipServer
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@runtime_checkable
|
|
18
|
+
class SipPhoneDevice(BaseDeviceProtocol, Protocol):
|
|
19
|
+
"""SIP phone archetype — endpoint that registers with a SIP server and places calls.
|
|
20
|
+
|
|
21
|
+
Used to drive call-control scenarios (register / invite / cancel / bye) and
|
|
22
|
+
media-plane verification against a SIP server under test.
|
|
23
|
+
|
|
24
|
+
A phone has one or more SIP accounts (lines). Most testbeds run single-line
|
|
25
|
+
phones; multi-line phones expose additional entries in ``aors``.
|
|
26
|
+
|
|
27
|
+
``aors`` is the list of registered SIP URIs in RFC 3261 §6 address-of-record
|
|
28
|
+
form (``sip:user@host`` with optional ``;params``). ``host`` may be an FQDN,
|
|
29
|
+
an IPv4 literal (``10.0.0.5``), or an IPv6 bracketed literal
|
|
30
|
+
(``[2001:db8::5]``) — the driver decides which form matches its testbed's
|
|
31
|
+
address-resolution context. The list is never empty; ``aors[0]`` is the
|
|
32
|
+
primary line that single-line operations (``sip_phone.dial``,
|
|
33
|
+
``sip_phone.answer``, MWI subscriptions, …) act on.
|
|
34
|
+
|
|
35
|
+
``number`` is the user-part of ``aors[0]`` — the SIP phone's E.164 /
|
|
36
|
+
dial-string identifier for its primary line. Provided as a convenience so
|
|
37
|
+
single-line callers don't have to parse SIP URIs.
|
|
38
|
+
|
|
39
|
+
``device_name`` / ``device_type`` come from :class:`BaseDeviceProtocol`.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
sip_phone: SipPhone
|
|
43
|
+
ip_interface: IpInterface
|
|
44
|
+
dhcp_client: DhcpClient
|
|
45
|
+
number: str
|
|
46
|
+
aors: list[str]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@runtime_checkable
|
|
50
|
+
class SipServerDevice(BaseDeviceProtocol, Protocol):
|
|
51
|
+
"""SIP server archetype — registrar / proxy for SIP phones in voice tests.
|
|
52
|
+
|
|
53
|
+
Provides the server-side SIP surface plus packet capture and file transfer
|
|
54
|
+
for inspecting and exporting signalling exchanges.
|
|
55
|
+
|
|
56
|
+
``aor_domain`` is the host part that callers should embed in SIP AORs
|
|
57
|
+
targeting this server (``sip:user@<aor_domain>``). May be an FQDN, an
|
|
58
|
+
IPv4 literal, or an IPv6 bracketed literal — the driver decides which
|
|
59
|
+
form matches its testbed's address-resolution context.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
sip_server: SipServer
|
|
63
|
+
pcap: PcapCapture
|
|
64
|
+
file_transfer: FileTransfer
|
|
65
|
+
aor_domain: str
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
register_device_type("linux_sip_phone", SipPhoneDevice)
|
|
69
|
+
register_device_type("linux_sip_server", SipServerDevice)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""WAN device archetypes — upstream-side server endpoints for end-to-end tests."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Protocol, runtime_checkable
|
|
6
|
+
|
|
7
|
+
from testprotocols.conntrack import Conntrack
|
|
8
|
+
from testprotocols.devices import register_device_type
|
|
9
|
+
from testprotocols.devices.base import BaseDeviceProtocol
|
|
10
|
+
from testprotocols.dhcp_client import DhcpClient
|
|
11
|
+
from testprotocols.dns_client import DnsClient
|
|
12
|
+
from testprotocols.file_transfer import FileTransfer
|
|
13
|
+
from testprotocols.http_client import HttpClient
|
|
14
|
+
from testprotocols.http_server import HttpServer
|
|
15
|
+
from testprotocols.ip_interface import IpInterface
|
|
16
|
+
from testprotocols.ip_routing import IpRouting
|
|
17
|
+
from testprotocols.iperf_client import IperfClient
|
|
18
|
+
from testprotocols.iperf_server import IperfServer
|
|
19
|
+
from testprotocols.nat import Nat
|
|
20
|
+
from testprotocols.network_endpoint import NetworkEndpoint
|
|
21
|
+
from testprotocols.network_probe import NetworkProbe
|
|
22
|
+
from testprotocols.nmap_scanner import NmapScanner
|
|
23
|
+
from testprotocols.ntp_client import NtpClient
|
|
24
|
+
from testprotocols.packet_filter import PacketFilter
|
|
25
|
+
from testprotocols.pcap_capture import PcapCapture
|
|
26
|
+
from testprotocols.snmp_client import SnmpClient
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@runtime_checkable
|
|
30
|
+
class WanServerDevice(BaseDeviceProtocol, Protocol):
|
|
31
|
+
"""WAN server archetype — far-side host for end-to-end traffic and protocol tests.
|
|
32
|
+
|
|
33
|
+
Sits on the WAN side of the topology to act as the remote endpoint for
|
|
34
|
+
HTTP, DNS, iperf, NTP, and SNMP exchanges initiated from the LAN through a
|
|
35
|
+
CPE / SD-WAN router under test. Includes L3 (IP, routing, DHCP, NAT,
|
|
36
|
+
conntrack), packet capture, host packet-filtering, network scanning, and
|
|
37
|
+
file transfer for orchestration of test artefacts.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
ip_interface: IpInterface
|
|
41
|
+
ip_routing: IpRouting
|
|
42
|
+
dhcp_client: DhcpClient
|
|
43
|
+
http_client: HttpClient
|
|
44
|
+
http_server: HttpServer
|
|
45
|
+
dns_client: DnsClient
|
|
46
|
+
iperf_client: IperfClient
|
|
47
|
+
iperf_server: IperfServer
|
|
48
|
+
pcap: PcapCapture
|
|
49
|
+
ntp_client: NtpClient
|
|
50
|
+
nmap_scanner: NmapScanner
|
|
51
|
+
file_transfer: FileTransfer
|
|
52
|
+
snmp_client: SnmpClient
|
|
53
|
+
packet_filter: PacketFilter
|
|
54
|
+
nat: Nat
|
|
55
|
+
conntrack: Conntrack
|
|
56
|
+
network_probe: NetworkProbe
|
|
57
|
+
data_plane_endpoint: NetworkEndpoint
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
register_device_type("linux_wan_server", WanServerDevice)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""DHCP / Client template.
|
|
2
|
+
|
|
3
|
+
Defines the abstract contract for DHCP client operations including lease
|
|
4
|
+
release and renewal for both IPv4 and IPv6.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Protocol, runtime_checkable
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@runtime_checkable
|
|
13
|
+
class DhcpClient(Protocol):
|
|
14
|
+
"""Abstract contract for DHCP client operations."""
|
|
15
|
+
|
|
16
|
+
def release_dhcp(self, interface: str) -> None:
|
|
17
|
+
"""Release the DHCPv4 lease on *interface*."""
|
|
18
|
+
...
|
|
19
|
+
|
|
20
|
+
def renew_dhcp(self, interface: str) -> None:
|
|
21
|
+
"""Renew the DHCPv4 lease on *interface*."""
|
|
22
|
+
...
|
|
23
|
+
|
|
24
|
+
def release_ipv6(self, interface: str, stateless: bool = False) -> None:
|
|
25
|
+
"""Release the DHCPv6 lease on *interface*."""
|
|
26
|
+
...
|
|
27
|
+
|
|
28
|
+
def renew_ipv6(self, interface: str, stateless: bool = False) -> None:
|
|
29
|
+
"""Renew the DHCPv6 lease on *interface*."""
|
|
30
|
+
...
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""DHCP / Server template.
|
|
2
|
+
|
|
3
|
+
Defines the abstract contract for DHCP server operations such as CPE
|
|
4
|
+
provisioning.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any, Protocol, runtime_checkable
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@runtime_checkable
|
|
13
|
+
class DhcpServer(Protocol):
|
|
14
|
+
"""Abstract contract for DHCP server operations."""
|
|
15
|
+
|
|
16
|
+
def provision_cpe(
|
|
17
|
+
self,
|
|
18
|
+
cpe_mac: str,
|
|
19
|
+
dhcpv4_options: dict[str, Any],
|
|
20
|
+
dhcpv6_options: dict[str, Any],
|
|
21
|
+
) -> None:
|
|
22
|
+
"""Provision a CPE device by MAC address with the given DHCP options."""
|
|
23
|
+
...
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Read-only link-layer neighbour discovery (LLDP-normalized)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Protocol, runtime_checkable
|
|
6
|
+
|
|
7
|
+
from testprotocols.models.switch import LldpNeighbor
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@runtime_checkable
|
|
11
|
+
class Discovery(Protocol):
|
|
12
|
+
"""Abstract contract for neighbour discovery (read-only).
|
|
13
|
+
|
|
14
|
+
A product that also runs a proprietary discovery protocol maps its neighbour
|
|
15
|
+
data onto this LLDP-shaped read.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def get_neighbors(self, port: str | None = None) -> list[LldpNeighbor]:
|
|
19
|
+
"""Return discovered neighbours, optionally filtered to one *port*."""
|
|
20
|
+
...
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""DNS / Client template.
|
|
2
|
+
|
|
3
|
+
Defines the abstract contract for DNS client operations including
|
|
4
|
+
name resolution lookups.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any, Protocol, runtime_checkable
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@runtime_checkable
|
|
13
|
+
class DnsClient(Protocol):
|
|
14
|
+
"""Abstract contract for DNS client operations."""
|
|
15
|
+
|
|
16
|
+
def dns_lookup(
|
|
17
|
+
self,
|
|
18
|
+
domain_name: str,
|
|
19
|
+
record_type: str,
|
|
20
|
+
opts: str = "",
|
|
21
|
+
) -> list[dict[str, Any]]:
|
|
22
|
+
"""Perform a DNS lookup for *domain_name* and return matching records."""
|
|
23
|
+
...
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""File Transfer template.
|
|
2
|
+
|
|
3
|
+
Defines the abstract contract for file transfer operations including
|
|
4
|
+
deletion and secure copy.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Protocol, runtime_checkable
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@runtime_checkable
|
|
13
|
+
class FileTransfer(Protocol):
|
|
14
|
+
"""Abstract contract for file transfer operations."""
|
|
15
|
+
|
|
16
|
+
def delete_file(self, filename: str) -> None:
|
|
17
|
+
"""Delete *filename* from the device."""
|
|
18
|
+
...
|
|
19
|
+
|
|
20
|
+
def scp_device_file_to_local(self, local_path: str, source_path: str) -> None:
|
|
21
|
+
"""Copy *source_path* from the device to *local_path* via SCP."""
|
|
22
|
+
...
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""Firewall template — gateway-tier rule administration + port forwarding.
|
|
2
|
+
|
|
3
|
+
Extends ``PacketFilter`` with port-forwarding methods (named external→internal
|
|
4
|
+
port mappings, DMZ host). Models the OpenWrt-UCI ``firewall`` config and
|
|
5
|
+
TR-181 ``Device.Firewall.*`` subtree as one coherent gateway subsystem:
|
|
6
|
+
admins reason about packet rules and port forwards together, and an
|
|
7
|
+
``iptables-save`` dump captures both in one stream.
|
|
8
|
+
|
|
9
|
+
In scope: the full ``PacketFilter`` rule-lifecycle surface (inherited)
|
|
10
|
+
plus port-mapping lifecycle and the single DMZ-host shortcut.
|
|
11
|
+
|
|
12
|
+
Out of scope: low-level NAT primitives (see ``nat``), zone-based policy
|
|
13
|
+
(see ``firewall_zones``), conntrack inspection (see ``conntrack``), and
|
|
14
|
+
client-side UPnP-IGD rule injection (see ``upnp_client``).
|
|
15
|
+
|
|
16
|
+
Devices that have rule administration but no port forwarding (clients,
|
|
17
|
+
infra services) satisfy only the narrower ``PacketFilter`` Protocol; they
|
|
18
|
+
should not be typed against ``Firewall``.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
from typing import Protocol, runtime_checkable
|
|
24
|
+
|
|
25
|
+
from testprotocols.models.firewall import PortMapping
|
|
26
|
+
from testprotocols.packet_filter import PacketFilter
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@runtime_checkable
|
|
30
|
+
class Firewall(PacketFilter, Protocol):
|
|
31
|
+
"""Gateway-tier firewall: rule administration + port forwarding.
|
|
32
|
+
|
|
33
|
+
Liskov extension of ``PacketFilter``. Any driver satisfying ``Firewall``
|
|
34
|
+
automatically satisfies ``PacketFilter`` via Protocol inheritance.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
# --- Port-mapping lifecycle ---
|
|
38
|
+
|
|
39
|
+
def add_port_mapping(self, mapping: PortMapping) -> None:
|
|
40
|
+
"""Install a port mapping.
|
|
41
|
+
|
|
42
|
+
Raises ValueError on a duplicate ``mapping.name``, on
|
|
43
|
+
*external_port* / *internal_port* outside ``1..65535``, or on
|
|
44
|
+
*protocol* not in ``{"tcp", "udp", "tcp-udp"}``.
|
|
45
|
+
"""
|
|
46
|
+
...
|
|
47
|
+
|
|
48
|
+
def remove_port_mapping(self, name: str) -> None:
|
|
49
|
+
"""Remove the port mapping identified by *name*.
|
|
50
|
+
|
|
51
|
+
Raises KeyError if no mapping with that name exists.
|
|
52
|
+
"""
|
|
53
|
+
...
|
|
54
|
+
|
|
55
|
+
def list_port_mappings(self) -> list[PortMapping]:
|
|
56
|
+
"""Return all installed port mappings."""
|
|
57
|
+
...
|
|
58
|
+
|
|
59
|
+
def get_port_mapping(self, name: str) -> PortMapping:
|
|
60
|
+
"""Return the port mapping identified by *name*.
|
|
61
|
+
|
|
62
|
+
Raises KeyError if no mapping with that name exists.
|
|
63
|
+
"""
|
|
64
|
+
...
|
|
65
|
+
|
|
66
|
+
def set_port_mapping_enabled(self, name: str, enabled: bool) -> None:
|
|
67
|
+
"""Enable or disable an existing port mapping without removing it.
|
|
68
|
+
|
|
69
|
+
Raises KeyError if no mapping with that name exists.
|
|
70
|
+
"""
|
|
71
|
+
...
|
|
72
|
+
|
|
73
|
+
# --- DMZ host ---
|
|
74
|
+
|
|
75
|
+
def set_dmz_host(self, host: str | None) -> None:
|
|
76
|
+
"""Set the DMZ-host destination, or pass ``None`` to clear DMZ.
|
|
77
|
+
|
|
78
|
+
When set, all unsolicited inbound traffic that does not match
|
|
79
|
+
any other port mapping is forwarded to *host*.
|
|
80
|
+
|
|
81
|
+
Raises ValueError if *host* is not a parseable IP address.
|
|
82
|
+
"""
|
|
83
|
+
...
|
|
84
|
+
|
|
85
|
+
def get_dmz_host(self) -> str | None:
|
|
86
|
+
"""Return the current DMZ host, or ``None`` if no DMZ host is set."""
|
|
87
|
+
...
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@runtime_checkable
|
|
91
|
+
class FirewallWhiteBox(Firewall, Protocol):
|
|
92
|
+
"""White-box extension of Firewall for raw kernel-level introspection.
|
|
93
|
+
|
|
94
|
+
Linux drivers that can shell into the box satisfy this extension by
|
|
95
|
+
capturing the underlying iptables / nftables ruleset (which natively
|
|
96
|
+
covers both filter rules and DNAT / port-forward entries in one
|
|
97
|
+
serialisation). Vendor-RTOS or locked-down devices typically satisfy
|
|
98
|
+
only the base ``Firewall`` Protocol; tests requiring kernel-level
|
|
99
|
+
verification should pin against ``FirewallWhiteBox`` and accept the
|
|
100
|
+
collection-skip on drivers that don't satisfy it (per the
|
|
101
|
+
``@white_box`` scenario tag rule).
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
def get_kernel_iptables_dump(self) -> str:
|
|
105
|
+
"""Return the raw ``iptables-save`` output (legacy iptables backend).
|
|
106
|
+
|
|
107
|
+
For drivers running on a Linux kernel with the legacy iptables
|
|
108
|
+
backend. Format is the standard iptables-save serialisation,
|
|
109
|
+
spanning filter chains, NAT chains, and DNAT / port-forward
|
|
110
|
+
entries in one stream.
|
|
111
|
+
"""
|
|
112
|
+
...
|
|
113
|
+
|
|
114
|
+
def get_nftables_ruleset(self) -> str:
|
|
115
|
+
"""Return the raw ``nft list ruleset`` output (nftables backend).
|
|
116
|
+
|
|
117
|
+
For drivers running on a Linux kernel with the nftables backend.
|
|
118
|
+
Format is the standard nftables ruleset serialisation, spanning
|
|
119
|
+
the full ruleset across all tables and families.
|
|
120
|
+
"""
|
|
121
|
+
...
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""Firewall / Zones template.
|
|
2
|
+
|
|
3
|
+
Defines the abstract contract for zone-based firewall administration —
|
|
4
|
+
the OpenWrt / firewalld / pfSense interface-group model in which traffic
|
|
5
|
+
policy is expressed at the zone level rather than as flat chain rules.
|
|
6
|
+
|
|
7
|
+
A zone groups one or more interfaces and / or CIDR networks under a
|
|
8
|
+
shared default-input / default-forward / default-output action. Per-zone-
|
|
9
|
+
pair forwarding policy controls fall-through traffic between zones; more
|
|
10
|
+
specific rules go in the ``packet_filter`` template (or the inherited
|
|
11
|
+
rule-administration surface on ``firewall.Firewall`` for gateway
|
|
12
|
+
devices) — on a zone-aware device they target a zone instead of a
|
|
13
|
+
chain, which drivers map internally.
|
|
14
|
+
|
|
15
|
+
In scope: zone CRUD, interface / network membership, zone defaults,
|
|
16
|
+
masquerade and MSS-clamping toggles, and zone-pair forwarding policy.
|
|
17
|
+
|
|
18
|
+
Out of scope: per-flow filtering (see ``packet_filter`` / ``firewall``),
|
|
19
|
+
NAT rules (see ``nat``), and per-zone counters (deferred — not all
|
|
20
|
+
drivers expose them).
|
|
21
|
+
|
|
22
|
+
Devices on a flat-chain firewall (no zones) should not compose this
|
|
23
|
+
template; ``packet_filter`` (or ``firewall`` for gateways) alone is
|
|
24
|
+
sufficient for them.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
from typing import Protocol, runtime_checkable
|
|
30
|
+
|
|
31
|
+
from testprotocols.models.firewall import Zone, ZonePolicy
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@runtime_checkable
|
|
35
|
+
class FirewallZones(Protocol):
|
|
36
|
+
"""Abstract contract for zone-based firewall administration."""
|
|
37
|
+
|
|
38
|
+
# --- Zone lifecycle ---
|
|
39
|
+
|
|
40
|
+
def create_zone(self, zone: Zone) -> None:
|
|
41
|
+
"""Create a new zone.
|
|
42
|
+
|
|
43
|
+
Raises ValueError on a duplicate ``zone.name`` or on default
|
|
44
|
+
action values outside ``{"accept", "drop", "reject"}``.
|
|
45
|
+
"""
|
|
46
|
+
...
|
|
47
|
+
|
|
48
|
+
def delete_zone(self, name: str) -> None:
|
|
49
|
+
"""Delete the zone identified by *name*.
|
|
50
|
+
|
|
51
|
+
Raises KeyError if no zone with that name exists.
|
|
52
|
+
"""
|
|
53
|
+
...
|
|
54
|
+
|
|
55
|
+
def list_zones(self) -> list[Zone]:
|
|
56
|
+
"""Return all zones currently configured on the device."""
|
|
57
|
+
...
|
|
58
|
+
|
|
59
|
+
def get_zone(self, name: str) -> Zone:
|
|
60
|
+
"""Return the zone identified by *name*.
|
|
61
|
+
|
|
62
|
+
Raises KeyError if no zone with that name exists.
|
|
63
|
+
"""
|
|
64
|
+
...
|
|
65
|
+
|
|
66
|
+
# --- Membership ---
|
|
67
|
+
|
|
68
|
+
def add_zone_interface(self, zone_name: str, interface: str) -> None:
|
|
69
|
+
"""Add *interface* to *zone_name*. No-op if already a member.
|
|
70
|
+
|
|
71
|
+
Raises KeyError if no zone with that name exists.
|
|
72
|
+
"""
|
|
73
|
+
...
|
|
74
|
+
|
|
75
|
+
def remove_zone_interface(self, zone_name: str, interface: str) -> None:
|
|
76
|
+
"""Remove *interface* from *zone_name*. No-op if not a member.
|
|
77
|
+
|
|
78
|
+
Raises KeyError if no zone with that name exists.
|
|
79
|
+
"""
|
|
80
|
+
...
|
|
81
|
+
|
|
82
|
+
def add_zone_network(self, zone_name: str, cidr: str) -> None:
|
|
83
|
+
"""Add the *cidr* network to *zone_name*. No-op if already a member.
|
|
84
|
+
|
|
85
|
+
Raises KeyError if no zone with that name exists.
|
|
86
|
+
Raises ValueError if *cidr* is malformed.
|
|
87
|
+
"""
|
|
88
|
+
...
|
|
89
|
+
|
|
90
|
+
def remove_zone_network(self, zone_name: str, cidr: str) -> None:
|
|
91
|
+
"""Remove the *cidr* network from *zone_name*. No-op if not a member.
|
|
92
|
+
|
|
93
|
+
Raises KeyError if no zone with that name exists.
|
|
94
|
+
"""
|
|
95
|
+
...
|
|
96
|
+
|
|
97
|
+
# --- Per-zone defaults ---
|
|
98
|
+
|
|
99
|
+
def set_zone_defaults(
|
|
100
|
+
self,
|
|
101
|
+
zone_name: str,
|
|
102
|
+
*,
|
|
103
|
+
input_action: str | None = None,
|
|
104
|
+
forward_action: str | None = None,
|
|
105
|
+
output_action: str | None = None,
|
|
106
|
+
masquerade: bool | None = None,
|
|
107
|
+
mss_clamping: bool | None = None,
|
|
108
|
+
) -> None:
|
|
109
|
+
"""Update one or more default-policy fields of *zone_name*.
|
|
110
|
+
|
|
111
|
+
Only fields passed (non-None) are changed. Action values must be
|
|
112
|
+
one of ``"accept"``, ``"drop"``, ``"reject"``.
|
|
113
|
+
|
|
114
|
+
Raises KeyError if no zone with that name exists.
|
|
115
|
+
Raises ValueError on a bad action value.
|
|
116
|
+
"""
|
|
117
|
+
...
|
|
118
|
+
|
|
119
|
+
# --- Zone-pair forwarding ---
|
|
120
|
+
|
|
121
|
+
def set_forwarding(self, src_zone: str, dst_zone: str, action: str) -> None:
|
|
122
|
+
"""Set the default forwarding action between *src_zone* and *dst_zone*.
|
|
123
|
+
|
|
124
|
+
*action* is one of ``"accept"``, ``"drop"``, ``"reject"``.
|
|
125
|
+
|
|
126
|
+
Raises KeyError if either zone is unknown.
|
|
127
|
+
Raises ValueError on a bad action value.
|
|
128
|
+
"""
|
|
129
|
+
...
|
|
130
|
+
|
|
131
|
+
def list_forwarding(self) -> list[ZonePolicy]:
|
|
132
|
+
"""Return every configured zone-pair forwarding policy."""
|
|
133
|
+
...
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""First-hop security — DHCP snooping + Dynamic ARP Inspection.
|
|
2
|
+
|
|
3
|
+
Rogue-DHCP control is the DHCP-snooping *intent*: divergent vendor shapes
|
|
4
|
+
(server allow/block, port-trust snooping, IMPB) normalize onto one surface.
|
|
5
|
+
Per-port trust / rate-limit / binding-table read are optional sub-methods; a
|
|
6
|
+
driver lacking one raises unsupported-capability rather than failing the
|
|
7
|
+
baseline. IP Source Guard is a deferred optional extension (see GAPS.md).
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import Protocol, runtime_checkable
|
|
13
|
+
|
|
14
|
+
from testprotocols.models.switch import BindingSource, FhsBinding, FhsScope, FhsTrustState
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@runtime_checkable
|
|
18
|
+
class FirstHopSecurity(Protocol):
|
|
19
|
+
"""Abstract contract for DHCP snooping + Dynamic ARP Inspection."""
|
|
20
|
+
|
|
21
|
+
def set_dhcp_snooping(
|
|
22
|
+
self, scope: FhsScope, enabled: bool, vlan: int | None = None
|
|
23
|
+
) -> None:
|
|
24
|
+
"""Enable/disable DHCP snooping globally or per-VLAN. A switch-wide-only
|
|
25
|
+
product maps ``PER_VLAN`` to ``GLOBAL`` or raises unsupported-capability."""
|
|
26
|
+
...
|
|
27
|
+
|
|
28
|
+
def set_dhcp_snooping_trust(self, port: str, trust: FhsTrustState) -> None:
|
|
29
|
+
"""Set the per-port snooping trust state. Optional — unsupported-capability
|
|
30
|
+
where the product has no per-port trust (e.g. MAC allow/block only)."""
|
|
31
|
+
...
|
|
32
|
+
|
|
33
|
+
def get_dhcp_bindings(self) -> list[FhsBinding]:
|
|
34
|
+
"""Return the snooping binding table. Optional — unsupported-capability
|
|
35
|
+
where no binding table is published."""
|
|
36
|
+
...
|
|
37
|
+
|
|
38
|
+
def set_dai(
|
|
39
|
+
self,
|
|
40
|
+
scope: FhsScope,
|
|
41
|
+
enabled: bool,
|
|
42
|
+
vlan: int | None = None,
|
|
43
|
+
binding_source: BindingSource = BindingSource.DYNAMIC_SNOOPING,
|
|
44
|
+
) -> None:
|
|
45
|
+
"""Enable/disable Dynamic ARP Inspection. Products without DAI raise
|
|
46
|
+
unsupported-capability."""
|
|
47
|
+
...
|
|
48
|
+
|
|
49
|
+
def set_arp_trust(self, port: str, trust: FhsTrustState) -> None:
|
|
50
|
+
"""Set the per-port ARP-inspection trust state. Optional — unsupported-capability
|
|
51
|
+
where the product has no per-port ARP trust."""
|
|
52
|
+
...
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""First-hop gateway redundancy — virtual IP + role per group."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Protocol, runtime_checkable
|
|
6
|
+
|
|
7
|
+
from testprotocols.models.switch_routing import RedundancyGroup
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@runtime_checkable
|
|
11
|
+
class GatewayRedundancy(Protocol):
|
|
12
|
+
"""Abstract contract for first-hop redundancy.
|
|
13
|
+
|
|
14
|
+
Normalized to a virtual-IP + role group so VRRP, HSRP, and warm-spare map
|
|
15
|
+
onto one concept. A product with no switch-side FHR raises
|
|
16
|
+
unsupported-capability.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def list_groups(self) -> list[RedundancyGroup]:
|
|
20
|
+
"""Return every redundancy group."""
|
|
21
|
+
...
|
|
22
|
+
|
|
23
|
+
def get_group(self, group_id: int) -> RedundancyGroup:
|
|
24
|
+
"""Return the redundancy group *group_id*. Raises KeyError if absent."""
|
|
25
|
+
...
|
|
26
|
+
|
|
27
|
+
def set_group(self, group: RedundancyGroup) -> None:
|
|
28
|
+
"""Create or replace the redundancy group ``group.group_id``."""
|
|
29
|
+
...
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""HTTP / Client template.
|
|
2
|
+
|
|
3
|
+
Defines the abstract contract for HTTP client operations including curl
|
|
4
|
+
and GET requests.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from ipaddress import IPv4Address
|
|
10
|
+
from typing import Protocol, runtime_checkable
|
|
11
|
+
|
|
12
|
+
from testprotocols.models.networking import HTTPResult
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@runtime_checkable
|
|
16
|
+
class HttpClient(Protocol):
|
|
17
|
+
"""Abstract contract for HTTP client operations."""
|
|
18
|
+
|
|
19
|
+
def curl(
|
|
20
|
+
self,
|
|
21
|
+
url: str | IPv4Address,
|
|
22
|
+
protocol: str,
|
|
23
|
+
port: str | int | None = None,
|
|
24
|
+
options: str = "",
|
|
25
|
+
) -> bool:
|
|
26
|
+
"""Execute a curl request to *url* using *protocol*."""
|
|
27
|
+
...
|
|
28
|
+
|
|
29
|
+
def http_get(
|
|
30
|
+
self,
|
|
31
|
+
url: str,
|
|
32
|
+
timeout: int = 20,
|
|
33
|
+
options: str = "",
|
|
34
|
+
) -> HTTPResult:
|
|
35
|
+
"""Perform an HTTP GET request to *url* and return the result."""
|
|
36
|
+
...
|