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.
Files changed (119) hide show
  1. testprotocols/__init__.py +217 -0
  2. testprotocols/aftr_gateway.py +22 -0
  3. testprotocols/appliance_nat.py +52 -0
  4. testprotocols/appliance_uplinks.py +32 -0
  5. testprotocols/appliance_vlans.py +50 -0
  6. testprotocols/arp_client.py +26 -0
  7. testprotocols/bgp.py +55 -0
  8. testprotocols/conntrack.py +147 -0
  9. testprotocols/content_filtering.py +47 -0
  10. testprotocols/device_lifecycle.py +49 -0
  11. testprotocols/device_management.py +50 -0
  12. testprotocols/devices/__init__.py +46 -0
  13. testprotocols/devices/base.py +40 -0
  14. testprotocols/devices/client.py +133 -0
  15. testprotocols/devices/cpe.py +66 -0
  16. testprotocols/devices/infra.py +62 -0
  17. testprotocols/devices/sdwan.py +97 -0
  18. testprotocols/devices/switch.py +115 -0
  19. testprotocols/devices/traffic.py +53 -0
  20. testprotocols/devices/voice.py +69 -0
  21. testprotocols/devices/wan.py +60 -0
  22. testprotocols/dhcp_client.py +30 -0
  23. testprotocols/dhcp_server.py +23 -0
  24. testprotocols/discovery.py +20 -0
  25. testprotocols/dns_client.py +23 -0
  26. testprotocols/file_transfer.py +22 -0
  27. testprotocols/firewall.py +121 -0
  28. testprotocols/firewall_zones.py +133 -0
  29. testprotocols/first_hop_security.py +52 -0
  30. testprotocols/gateway_redundancy.py +29 -0
  31. testprotocols/http_client.py +36 -0
  32. testprotocols/http_server.py +22 -0
  33. testprotocols/hw_console.py +48 -0
  34. testprotocols/infra_controller.py +28 -0
  35. testprotocols/interface_dhcp.py +30 -0
  36. testprotocols/ip_interface.py +62 -0
  37. testprotocols/ip_routing.py +57 -0
  38. testprotocols/iperf_client.py +47 -0
  39. testprotocols/iperf_generator.py +42 -0
  40. testprotocols/iperf_server.py +41 -0
  41. testprotocols/l3_firewall.py +74 -0
  42. testprotocols/l7_firewall.py +32 -0
  43. testprotocols/link_aggregation.py +24 -0
  44. testprotocols/mac_table.py +20 -0
  45. testprotocols/models/__init__.py +304 -0
  46. testprotocols/models/dhcp.py +28 -0
  47. testprotocols/models/firewall.py +197 -0
  48. testprotocols/models/impairment.py +18 -0
  49. testprotocols/models/l2_common.py +53 -0
  50. testprotocols/models/multicast.py +22 -0
  51. testprotocols/models/networking.py +50 -0
  52. testprotocols/models/packets.py +21 -0
  53. testprotocols/models/qoe.py +31 -0
  54. testprotocols/models/radius.py +63 -0
  55. testprotocols/models/sdwan_appliance.py +637 -0
  56. testprotocols/models/switch.py +297 -0
  57. testprotocols/models/switch_routing.py +122 -0
  58. testprotocols/models/tr069.py +35 -0
  59. testprotocols/models/traffic.py +29 -0
  60. testprotocols/models/wan_edge.py +116 -0
  61. testprotocols/models/wifi.py +183 -0
  62. testprotocols/multicast_client.py +20 -0
  63. testprotocols/nat.py +87 -0
  64. testprotocols/netem_controller.py +42 -0
  65. testprotocols/network_endpoint.py +32 -0
  66. testprotocols/network_probe.py +27 -0
  67. testprotocols/nmap_scanner.py +27 -0
  68. testprotocols/ntp_client.py +26 -0
  69. testprotocols/ntp_config.py +25 -0
  70. testprotocols/ospf.py +24 -0
  71. testprotocols/packet_filter.py +144 -0
  72. testprotocols/pcap_capture.py +39 -0
  73. testprotocols/pdu_controller.py +26 -0
  74. testprotocols/port_poe.py +25 -0
  75. testprotocols/port_security.py +25 -0
  76. testprotocols/port_status.py +23 -0
  77. testprotocols/py.typed +0 -0
  78. testprotocols/qoe_browser.py +62 -0
  79. testprotocols/radius_client.py +78 -0
  80. testprotocols/radius_server.py +130 -0
  81. testprotocols/routed_interfaces.py +29 -0
  82. testprotocols/router.py +53 -0
  83. testprotocols/routing_read.py +22 -0
  84. testprotocols/sdwan_policy_manager.py +64 -0
  85. testprotocols/sip_phone.py +230 -0
  86. testprotocols/sip_server.py +205 -0
  87. testprotocols/site_to_site_vpn.py +61 -0
  88. testprotocols/snmp_client.py +17 -0
  89. testprotocols/spanning_tree.py +37 -0
  90. testprotocols/static_routes.py +47 -0
  91. testprotocols/storm_control.py +24 -0
  92. testprotocols/streaming_server.py +32 -0
  93. testprotocols/switch_acl.py +29 -0
  94. testprotocols/switch_ports.py +28 -0
  95. testprotocols/switch_qos.py +33 -0
  96. testprotocols/switch_vlans.py +34 -0
  97. testprotocols/syslog_config.py +31 -0
  98. testprotocols/tftp_server.py +22 -0
  99. testprotocols/threat_prevention.py +60 -0
  100. testprotocols/tr069_client.py +47 -0
  101. testprotocols/tr069_server.py +151 -0
  102. testprotocols/traffic_shaping.py +54 -0
  103. testprotocols/upnp_client.py +37 -0
  104. testprotocols/vlan_client.py +22 -0
  105. testprotocols/wan_link_admin.py +34 -0
  106. testprotocols/wifi_bss.py +197 -0
  107. testprotocols/wifi_client.py +72 -0
  108. testprotocols/wifi_mesh.py +259 -0
  109. testprotocols/wifi_onboarding.py +105 -0
  110. testprotocols/wifi_radio.py +153 -0
  111. testprotocols/wifi_rf.py +78 -0
  112. testprotocols/wifi_stations.py +59 -0
  113. testprotocols/wifi_transitions.py +112 -0
  114. testprotocols-0.1.0.dist-info/METADATA +29 -0
  115. testprotocols-0.1.0.dist-info/RECORD +119 -0
  116. testprotocols-0.1.0.dist-info/WHEEL +5 -0
  117. testprotocols-0.1.0.dist-info/licenses/LICENSE +201 -0
  118. testprotocols-0.1.0.dist-info/licenses/NOTICE +11 -0
  119. 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
+ ...