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,183 @@
1
+ """WiFi-domain data models."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+
7
+
8
+ @dataclass
9
+ class WifiDfsState:
10
+ """DFS state of a radio.
11
+
12
+ A radio is in CAC for ~60s after tuning to a DFS channel; during CAC it
13
+ cannot transmit. Channels on which radar was recently detected enter the
14
+ Non-Occupancy List for ~30 minutes and are unavailable until the NOL ages out.
15
+ """
16
+
17
+ is_in_cac: bool
18
+ cac_remaining_seconds: int | None # None when not in CAC
19
+ nol_channels: list[int] = field(default_factory=list[int])
20
+
21
+
22
+ @dataclass
23
+ class WifiCaptiveConfig:
24
+ """Per-BSS captive-portal state."""
25
+
26
+ enabled: bool
27
+ redirect_url: str | None
28
+
29
+
30
+ @dataclass
31
+ class WifiBssConfig:
32
+ """Configuration of a single BSS, as returned by WifiBss read methods.
33
+
34
+ *passphrase* is intentionally absent — write-only across the contract.
35
+ """
36
+
37
+ name: str # stable logical handle
38
+ band: str
39
+ ssid: str # broadcast SSID string
40
+ bssid: str # MAC address assigned to this BSS
41
+ enabled: bool
42
+ broadcast_enabled: bool
43
+ security_mode: str
44
+ radius_server_name: str | None # references RadiusClient registry; None when not Enterprise
45
+ mfp: str # "off" | "optional" | "required"
46
+ vlan_id: int | None
47
+ max_clients: int | None
48
+ dtim_period: int
49
+ captive_portal: WifiCaptiveConfig
50
+
51
+
52
+ @dataclass
53
+ class WifiStation:
54
+ """An associated station's identity, capabilities, and current stats.
55
+
56
+ Stats are point-in-time snapshots; cumulative counters (bytes, packets,
57
+ retries) are since the start of the current association.
58
+ """
59
+
60
+ mac: str # canonical: lowercase colon-separated
61
+ bss_name: str # logical BSS handle the station is associated to
62
+ band: str # "2.4GHz" / "5GHz" / "6GHz"
63
+ ip_address: str | None # station's IP if known to the AP (e.g. via DHCP snooping)
64
+ associated_since: float # Unix timestamp
65
+ rssi_dbm: int
66
+ snr_db: int | None # None if the driver doesn't report SNR
67
+ tx_rate_mbps: float # last known PHY rate AP -> station
68
+ rx_rate_mbps: float # last known PHY rate station -> AP
69
+ tx_bytes: int
70
+ rx_bytes: int
71
+ tx_packets: int
72
+ rx_packets: int
73
+ tx_retries: int
74
+ capability_flags: list[str] = field(default_factory=list[str])
75
+ # capability_flags examples: ["HT", "VHT", "HE"], or ["EHT", "MLO"] for Wi-Fi 7
76
+
77
+
78
+ @dataclass
79
+ class WifiAcl:
80
+ """Per-BSS MAC access-control list state."""
81
+
82
+ bss_name: str
83
+ mode: str # "disabled" | "allow" | "deny"
84
+ # MACs, canonical lowercase colon-separated
85
+ entries: list[str] = field(default_factory=list[str])
86
+
87
+
88
+ @dataclass
89
+ class WifiNeighbor:
90
+ """A neighbour BSS observed by an off-channel scan."""
91
+
92
+ bssid: str # MAC, canonical lowercase colon-separated
93
+ ssid: str # may be empty for hidden SSIDs
94
+ band: str # the band the scanner observed it on
95
+ channel: int # operating channel of the neighbour
96
+ rssi_dbm: int
97
+ security_mode: str # best-effort identification
98
+ last_seen: float # Unix timestamp of the last beacon/probe-response
99
+
100
+
101
+ @dataclass
102
+ class WifiChannelUtilization:
103
+ """Per-radio channel-utilization breakdown.
104
+
105
+ All fields are 0-100. ``busy_pct`` is always populated; the
106
+ component splits (tx/rx/interference) are populated only on drivers
107
+ that report them separately.
108
+ """
109
+
110
+ band: str
111
+ busy_pct: int # total channel occupancy
112
+ tx_pct: int | None # own transmissions
113
+ rx_pct: int | None # all reception (own BSS + neighbours)
114
+ interference_pct: int | None # non-WiFi interference, where the driver can distinguish
115
+
116
+
117
+ @dataclass
118
+ class WifiRadioStats:
119
+ """Cumulative per-radio TX/RX/retry counters."""
120
+
121
+ band: str
122
+ tx_bytes: int
123
+ rx_bytes: int
124
+ tx_packets: int
125
+ rx_packets: int
126
+ tx_retries: int # retransmitted frames
127
+ tx_failed: int # frames the driver gave up on (max retries exceeded)
128
+
129
+
130
+ @dataclass
131
+ class WifiTransitionConfig:
132
+ """Per-BSS k/v/r configuration snapshot."""
133
+
134
+ bss_name: str
135
+ rrm_enabled: bool # 802.11k
136
+ btm_enabled: bool # 802.11v
137
+ ft_enabled: bool # 802.11r
138
+ # 802.11r over-the-DS (True) vs over-the-air (False); meaningful only when ft_enabled
139
+ ft_over_ds: bool
140
+
141
+
142
+ @dataclass
143
+ class WifiMeshLink:
144
+ """A wireless backhaul link between mesh agents."""
145
+
146
+ band: str # "2.4GHz" / "5GHz" / "6GHz"
147
+ channel: int
148
+ rssi_dbm: int # signal strength on the link
149
+ capacity_mbps: float # estimated PHY-rate capacity in Mbps
150
+
151
+
152
+ @dataclass
153
+ class WifiMeshStatus:
154
+ """A mesh participant's local status snapshot."""
155
+
156
+ # "controller" | "agent" | "controller-and-agent" | "uncommissioned"
157
+ role: str
158
+ enabled: bool
159
+ parent_mac: str | None # MAC of this agent's parent; None for the controller / root
160
+ hop_count: int # 0 for the controller; N for an agent N hops away
161
+ backhaul_link: WifiMeshLink | None # None when no uplink (root) or mesh disabled
162
+
163
+
164
+ @dataclass
165
+ class WifiMeshNode:
166
+ """Identity and position of a mesh agent in the topology."""
167
+
168
+ mac: str # canonical lowercase colon-separated
169
+ role: str # "controller" | "agent" | "controller-and-agent"
170
+ parent_mac: str | None # None for the controller / root
171
+ hop_count: int
172
+
173
+
174
+ @dataclass
175
+ class WifiMeshTopology:
176
+ """The mesh as known to the querying participant.
177
+
178
+ Controllers populate the full agent list; pure agents populate
179
+ only the parent + any peers they directly observe.
180
+ """
181
+
182
+ controller_mac: str
183
+ agents: list[WifiMeshNode] = field(default_factory=list[WifiMeshNode])
@@ -0,0 +1,20 @@
1
+ """Multicast client template.
2
+
3
+ Defines the abstract contract for sending multicast group membership reports
4
+ from a test client device.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Protocol, runtime_checkable
10
+
11
+ from testprotocols.models.multicast import MulticastGroupRecord
12
+
13
+
14
+ @runtime_checkable
15
+ class MulticastClient(Protocol):
16
+ """Abstract contract for multicast client operations."""
17
+
18
+ def send_mldv2_report(self, mcast_group_record: MulticastGroupRecord, count: int) -> None:
19
+ """Send *count* MLDv2 membership report packets for the given group records."""
20
+ ...
testprotocols/nat.py ADDED
@@ -0,0 +1,87 @@
1
+ """Firewall / NAT template.
2
+
3
+ Defines the abstract contract for low-level NAT primitives — SNAT,
4
+ DNAT, and 1:1 / static NAT. Rules are identified by a stable logical
5
+ *name* and expressed as transport-agnostic ``NatRule`` records.
6
+
7
+ In scope: rule lifecycle (add / remove / list / get / flush), enable
8
+ toggles, and per-rule packet/byte counters.
9
+
10
+ Out of scope: high-level / named port-forwarding entries (see the
11
+ port-mapping surface on ``firewall.Firewall``), packet-filter rules
12
+ (see ``packet_filter`` or the rule-administration surface inherited
13
+ by ``firewall.Firewall``), and zone-level masquerade (a per-zone flag
14
+ on ``firewall_zones``).
15
+
16
+ NAT and packet-filter rules are kept in separate templates because a
17
+ device may legitimately compose one without the other (e.g. a transit
18
+ router does NAT only; a host firewall does packet-filter only).
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ from typing import Protocol, runtime_checkable
24
+
25
+ from testprotocols.models.firewall import NatRule
26
+
27
+
28
+ @runtime_checkable
29
+ class Nat(Protocol):
30
+ """Abstract contract for low-level NAT primitives."""
31
+
32
+ # --- Rule lifecycle ---
33
+
34
+ def add_nat_rule(self, rule: NatRule) -> None:
35
+ """Install a NAT rule.
36
+
37
+ Validates that *rule.mode* is one of ``"snat"``, ``"dnat"``,
38
+ ``"1to1"`` and that the per-mode field invariants hold (see
39
+ ``NatRule`` docstring). Raises ValueError on a duplicate
40
+ ``rule.name``, on an unknown mode, or on mode/field
41
+ inconsistency (e.g. *translated_src* set with ``mode="dnat"``).
42
+ """
43
+ ...
44
+
45
+ def remove_nat_rule(self, name: str) -> None:
46
+ """Remove the NAT rule identified by *name*.
47
+
48
+ Raises KeyError if no rule with that name exists.
49
+ """
50
+ ...
51
+
52
+ def list_nat_rules(self, mode: str | None = None) -> list[NatRule]:
53
+ """Return installed NAT rules, optionally filtered by *mode*.
54
+
55
+ *mode* is one of ``None`` (all), ``"snat"``, ``"dnat"``,
56
+ ``"1to1"``. Raises ValueError if *mode* is set but not one of
57
+ the recognized values.
58
+ """
59
+ ...
60
+
61
+ def get_nat_rule(self, name: str) -> NatRule:
62
+ """Return the NAT rule identified by *name*.
63
+
64
+ Raises KeyError if no rule with that name exists.
65
+ """
66
+ ...
67
+
68
+ def set_nat_rule_enabled(self, name: str, enabled: bool) -> None:
69
+ """Enable or disable an existing NAT rule without removing it.
70
+
71
+ Raises KeyError if no rule with that name exists.
72
+ """
73
+ ...
74
+
75
+ def flush_nat_rules(self) -> None:
76
+ """Remove every NAT rule on this device."""
77
+ ...
78
+
79
+ # --- Counters ---
80
+
81
+ def get_nat_rule_counters(self, name: str) -> tuple[int, int]:
82
+ """Return ``(packets, bytes)`` matched by the rule since it was added.
83
+
84
+ Raises KeyError if no rule with that name exists.
85
+ Drivers without per-rule counter support raise NotImplementedError.
86
+ """
87
+ ...
@@ -0,0 +1,42 @@
1
+ """Traffic / NetemController template.
2
+
3
+ Defines the abstract contract for network emulation (netem) impairment
4
+ control on device interfaces.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Any, Protocol, runtime_checkable
10
+
11
+ from testprotocols.models.impairment import ImpairmentProfile
12
+
13
+
14
+ @runtime_checkable
15
+ class NetemController(Protocol):
16
+ """Abstract contract for netem-based network impairment control."""
17
+
18
+ def set_impairment_profile(self, profile: ImpairmentProfile | dict[str, Any]) -> None:
19
+ """Apply *profile* as the default impairment on all managed interfaces."""
20
+ ...
21
+
22
+ def set_interface_profile(
23
+ self, interface: str, profile: ImpairmentProfile | dict[str, Any]
24
+ ) -> None:
25
+ """Apply *profile* as the impairment on a specific *interface*."""
26
+ ...
27
+
28
+ def get_interface_profile(self, interface: str) -> ImpairmentProfile:
29
+ """Return the current impairment profile for *interface*."""
30
+ ...
31
+
32
+ def get_interface_profiles(self) -> dict[str, ImpairmentProfile]:
33
+ """Return a mapping of interface names to their current impairment profiles."""
34
+ ...
35
+
36
+ def clear(self) -> None:
37
+ """Remove all active impairments from all managed interfaces."""
38
+ ...
39
+
40
+ def inject_transient(self, event: str, duration_ms: int, **kwargs: float | int) -> None:
41
+ """Inject a transient impairment *event* lasting *duration_ms* milliseconds."""
42
+ ...
@@ -0,0 +1,32 @@
1
+ """Network endpoint template.
2
+
3
+ Defines the abstract contract for a *logical* network endpoint's identity
4
+ — the address a peer would use to reach a device's named role (the CPE's
5
+ WAN side, a server's data-plane interface, etc.). Distinct from
6
+ ``IpInterface``, which is the per-physical-interface query surface:
7
+ ``NetworkEndpoint`` is what the test wants ("give me the WAN address"),
8
+ ``IpInterface`` is one of the things a driver might use under the hood
9
+ to satisfy it.
10
+
11
+ Drivers attach one ``NetworkEndpoint`` per logical role they expose, so
12
+ the device shape remains a clean aggregation of capability namespaces
13
+ rather than accreting role-specific accessor methods.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from typing import Protocol, runtime_checkable
19
+
20
+
21
+ @runtime_checkable
22
+ class NetworkEndpoint(Protocol):
23
+ """Abstract contract for a logical network endpoint's identity."""
24
+
25
+ def get_ipv4_addr(self) -> str:
26
+ """Return the IPv4 address for this logical endpoint.
27
+
28
+ The resolution strategy is driver-internal: it may query a live
29
+ interface, read inventory metadata, lift a value from a TR-069
30
+ data model, etc. Callers see only the address.
31
+ """
32
+ ...
@@ -0,0 +1,27 @@
1
+ """Network reachability probe template.
2
+
3
+ Defines the abstract contract for L3/L4 reachability checks initiated from
4
+ a host toward an external target. Distinct from ``HttpClient`` (which is
5
+ app-layer): a ``NetworkProbe`` verifies whether the SYN/ACK handshake
6
+ completes, not whether an HTTP responder is configured at the far end.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from typing import Protocol, runtime_checkable
12
+
13
+
14
+ @runtime_checkable
15
+ class NetworkProbe(Protocol):
16
+ """Abstract contract for L3/L4 reachability probes."""
17
+
18
+ def tcp_can_connect(self, host: str, port: int, timeout: int = 5) -> bool:
19
+ """Attempt a TCP connection to *host*:*port* and return reachability.
20
+
21
+ Returns ``True`` if the three-way handshake completes within
22
+ *timeout* seconds, ``False`` otherwise (connection refused, no
23
+ route, filtered, timed out). The probe is read-only — no data is
24
+ sent on the connection — so it is safe to call against arbitrary
25
+ targets without side effects on the destination.
26
+ """
27
+ ...
@@ -0,0 +1,27 @@
1
+ """Nmap / Scanner template.
2
+
3
+ Defines the abstract contract for network scanning operations using nmap.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from typing import Any, Protocol, runtime_checkable
9
+
10
+
11
+ @runtime_checkable
12
+ class NmapScanner(Protocol):
13
+ """Abstract contract for nmap network scanning operations."""
14
+
15
+ def nmap(
16
+ self,
17
+ ipaddr: str,
18
+ ip_type: str,
19
+ port: str | int | None = None,
20
+ protocol: str | None = None,
21
+ max_retries: int | None = None,
22
+ min_rate: int | None = None,
23
+ opts: str | None = None,
24
+ timeout: int = 30,
25
+ ) -> dict[str, Any]:
26
+ """Run an nmap scan against *ipaddr* and return the parsed results."""
27
+ ...
@@ -0,0 +1,26 @@
1
+ """NTP / Client template.
2
+
3
+ Defines the abstract contract for NTP client operations including
4
+ date retrieval, date setting, and time synchronisation.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Protocol, runtime_checkable
10
+
11
+
12
+ @runtime_checkable
13
+ class NtpClient(Protocol):
14
+ """Abstract contract for NTP client operations."""
15
+
16
+ def get_date(self) -> str | None:
17
+ """Return the current date/time string from the device."""
18
+ ...
19
+
20
+ def set_date(self, opt: str, date_string: str) -> bool:
21
+ """Set the device date/time using *opt* and *date_string*."""
22
+ ...
23
+
24
+ def execute_time_sync(self, time_server: str) -> str:
25
+ """Synchronise device time against *time_server* and return status."""
26
+ ...
@@ -0,0 +1,25 @@
1
+ """NTP-server configuration — the time-sync sibling of syslog_config.
2
+
3
+ Small and generic. Distinct from the operational ``ntp_client`` (get/set/sync
4
+ time): this is server-list config. A cloud-managed product that sets time
5
+ itself (timezone-only) raises unsupported-capability.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Protocol, runtime_checkable
11
+
12
+ from testprotocols.models.switch import NtpServer
13
+
14
+
15
+ @runtime_checkable
16
+ class NtpConfig(Protocol):
17
+ """Abstract contract for NTP-server configuration."""
18
+
19
+ def set_ntp_servers(self, servers: list[NtpServer]) -> None:
20
+ """Replace the NTP-server list."""
21
+ ...
22
+
23
+ def get_ntp_servers(self) -> list[NtpServer]:
24
+ """Return the configured NTP servers."""
25
+ ...
testprotocols/ospf.py ADDED
@@ -0,0 +1,24 @@
1
+ """OSPF dynamic-routing configuration (whole-config replace)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Protocol, runtime_checkable
6
+
7
+ from testprotocols.models.switch_routing import OspfConfig
8
+
9
+
10
+ @runtime_checkable
11
+ class Ospf(Protocol):
12
+ """Abstract contract for OSPF configuration.
13
+
14
+ A product that runs OSPF only on a gateway (not the L3 switch) raises
15
+ unsupported-capability.
16
+ """
17
+
18
+ def set_ospf_config(self, config: OspfConfig) -> None:
19
+ """Replace the OSPF configuration."""
20
+ ...
21
+
22
+ def get_ospf_config(self) -> OspfConfig:
23
+ """Return the OSPF configuration."""
24
+ ...
@@ -0,0 +1,144 @@
1
+ """Firewall / PacketFilter template.
2
+
3
+ Defines the abstract contract for stateless and stateful packet-filtering
4
+ rules on a device's INPUT, OUTPUT, and FORWARD paths. Rules are addressed
5
+ by a stable logical *name* and expressed as transport-agnostic
6
+ ``FirewallRule`` records — drivers translate into iptables, nftables,
7
+ pf, or vendor CLI as appropriate.
8
+
9
+ In scope: per-chain rule administration (add / remove / list / get /
10
+ flush), chain default-policy control, and per-rule packet/byte counters.
11
+
12
+ Out of scope: NAT (see ``nat``), high-level port forwarding (see the
13
+ ``Firewall`` Protocol in ``firewall``, which extends ``PacketFilter``
14
+ with port-mapping methods), conntrack inspection (see ``conntrack``),
15
+ zone-based policy (see ``firewall_zones``), and L7 application-aware
16
+ classification (see ``l7_firewall`` for managed appliances).
17
+
18
+ The IPv4 / IPv6 split is not a contract dimension — each rule's address
19
+ family is inferred from its CIDR fields. Drivers that maintain separate
20
+ v4/v6 tables internally do so transparently.
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ from typing import Protocol, runtime_checkable
26
+
27
+ from testprotocols.models.firewall import FirewallRule
28
+
29
+
30
+ @runtime_checkable
31
+ class PacketFilter(Protocol):
32
+ """Abstract contract for stateless / stateful packet filtering."""
33
+
34
+ # --- Rule lifecycle ---
35
+
36
+ def add_rule(
37
+ self,
38
+ chain: str,
39
+ rule: FirewallRule,
40
+ position: int | None = None,
41
+ ) -> None:
42
+ """Insert *rule* into *chain* at *position*.
43
+
44
+ *chain* is one of ``"INPUT"``, ``"OUTPUT"``, ``"FORWARD"``.
45
+ *position* is 1-based: ``1`` inserts at the top, ``None`` appends
46
+ at the end.
47
+
48
+ Raises ValueError if *chain* is unknown, if a rule named
49
+ ``rule.name`` already exists in *chain*, or if *position* is
50
+ less than 1.
51
+ """
52
+ ...
53
+
54
+ def remove_rule(self, chain: str, name: str) -> None:
55
+ """Remove the rule identified by *name* from *chain*.
56
+
57
+ Raises ValueError if *chain* is unknown.
58
+ Raises KeyError if no rule with that name exists in *chain*.
59
+ """
60
+ ...
61
+
62
+ def list_rules(self, chain: str) -> list[FirewallRule]:
63
+ """Return all rules currently installed in *chain*, in evaluation order.
64
+
65
+ Raises ValueError if *chain* is unknown.
66
+ """
67
+ ...
68
+
69
+ def get_rule(self, chain: str, name: str) -> FirewallRule:
70
+ """Return the rule identified by *name* in *chain*.
71
+
72
+ Raises ValueError if *chain* is unknown.
73
+ Raises KeyError if no rule with that name exists in *chain*.
74
+ """
75
+ ...
76
+
77
+ def flush_chain(self, chain: str) -> None:
78
+ """Remove every rule from *chain*. Default policy is unchanged.
79
+
80
+ Raises ValueError if *chain* is unknown.
81
+ """
82
+ ...
83
+
84
+ # --- Default policy ---
85
+
86
+ def set_default_policy(self, chain: str, policy: str) -> None:
87
+ """Set the default action for traffic on *chain* that matches no rule.
88
+
89
+ *policy* is one of ``"accept"``, ``"drop"``, ``"reject"``.
90
+ Raises ValueError if *chain* is unknown or *policy* is not
91
+ a valid value.
92
+ """
93
+ ...
94
+
95
+ def get_default_policy(self, chain: str) -> str:
96
+ """Return the current default policy of *chain*.
97
+
98
+ Raises ValueError if *chain* is unknown.
99
+ """
100
+ ...
101
+
102
+ # --- Counters ---
103
+
104
+ def get_rule_counters(self, chain: str, name: str) -> tuple[int, int]:
105
+ """Return ``(packets, bytes)`` matched by the rule since it was added.
106
+
107
+ Raises ValueError if *chain* is unknown.
108
+ Raises KeyError if no rule with that name exists in *chain*.
109
+ Drivers without per-rule counter support raise NotImplementedError.
110
+ """
111
+ ...
112
+
113
+
114
+ @runtime_checkable
115
+ class PacketFilterWhiteBox(PacketFilter, Protocol):
116
+ """White-box extension of PacketFilter for raw kernel-level introspection.
117
+
118
+ Linux drivers that can shell into the box satisfy this extension by
119
+ capturing the underlying iptables / nftables ruleset. Vendor-RTOS or
120
+ locked-down devices typically satisfy only the base ``PacketFilter``
121
+ Protocol; tests requiring kernel-level rule verification should pin
122
+ against ``PacketFilterWhiteBox`` and accept the collection-skip on
123
+ drivers that don't satisfy it (per the ``@white_box`` scenario tag rule).
124
+ """
125
+
126
+ def get_kernel_iptables_dump(self) -> str:
127
+ """Return the raw ``iptables-save`` output (legacy iptables backend).
128
+
129
+ For drivers running on a Linux kernel with the legacy iptables
130
+ backend. Format is the standard iptables-save serialisation. Tests
131
+ parse this to verify rules landed at the kernel level rather than
132
+ only in the driver's intermediate state.
133
+ """
134
+ ...
135
+
136
+ def get_nftables_ruleset(self) -> str:
137
+ """Return the raw ``nft list ruleset`` output (nftables backend).
138
+
139
+ For drivers running on a Linux kernel with the nftables backend.
140
+ Format is the standard nftables ruleset serialisation. Returns
141
+ the entire ruleset across all tables and families; tests grep
142
+ for the rules they care about.
143
+ """
144
+ ...
@@ -0,0 +1,39 @@
1
+ """Pcap / Capture template.
2
+
3
+ Defines the abstract contract for packet capture operations using
4
+ tcpdump and tshark.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Any, Protocol, runtime_checkable
10
+
11
+
12
+ @runtime_checkable
13
+ class PcapCapture(Protocol):
14
+ """Abstract contract for packet capture operations."""
15
+
16
+ def start_tcpdump(
17
+ self,
18
+ interface: str,
19
+ port: str | None,
20
+ output_file: str = "pkt_capture.pcap",
21
+ filters: dict[str, Any] | None = None,
22
+ additional_filters: str | None = "",
23
+ ) -> str:
24
+ """Start a tcpdump capture on *interface* and return the process identifier."""
25
+ ...
26
+
27
+ def stop_tcpdump(self, process_id: str) -> None:
28
+ """Stop the tcpdump process identified by *process_id*."""
29
+ ...
30
+
31
+ def tshark_read_pcap(
32
+ self,
33
+ fname: str,
34
+ additional_args: str | None = None,
35
+ timeout: int = 30,
36
+ rm_pcap: bool = False,
37
+ ) -> str:
38
+ """Read and parse pcap file *fname* using tshark and return the output."""
39
+ ...