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,637 @@
|
|
|
1
|
+
"""Data models for the vendor-neutral SD-WAN **appliance** capabilities.
|
|
2
|
+
|
|
3
|
+
These back the managed-appliance capability protocols composed by
|
|
4
|
+
``devices.sdwan.SdwanApplianceDevice`` (distinct from the Linux digital twin's
|
|
5
|
+
``SdwanRouterDevice``).
|
|
6
|
+
|
|
7
|
+
**Vendor neutrality is part of the contract.** Field value-vocabularies are
|
|
8
|
+
*normalized* and owned here as ``StrEnum`` types: members are plain strings (so
|
|
9
|
+
serialization to a vendor's REST/JSON API is trivial), constructing from a value
|
|
10
|
+
validates it (``RuleAction("x")`` raises ``ValueError``), and the types give
|
|
11
|
+
static checking at every driver/test call site. A testbed plugin maps its
|
|
12
|
+
product's representation to/from these neutral values; no vendor identifier, raw
|
|
13
|
+
payload, or vendor-specific vocabulary ever appears in this module.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from dataclasses import dataclass, field
|
|
19
|
+
from enum import StrEnum
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class RuleAction(StrEnum):
|
|
23
|
+
"""Action a firewall rule takes on a match."""
|
|
24
|
+
|
|
25
|
+
ALLOW = "allow"
|
|
26
|
+
DENY = "deny"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class RuleProtocol(StrEnum):
|
|
30
|
+
"""Transport a rule matches. ``ANY`` leaves the protocol unconstrained."""
|
|
31
|
+
|
|
32
|
+
TCP = "tcp"
|
|
33
|
+
UDP = "udp"
|
|
34
|
+
ICMP = "icmp"
|
|
35
|
+
ICMP6 = "icmp6"
|
|
36
|
+
ANY = "any"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class L3Rule:
|
|
41
|
+
"""A single ordered L3 firewall rule — 5-tuple match plus an action.
|
|
42
|
+
|
|
43
|
+
A managed appliance evaluates its L3 policy as a flat, ordered list of these
|
|
44
|
+
(not as netfilter INPUT/OUTPUT/FORWARD chains). The CIDR and port fields take
|
|
45
|
+
``"any"`` when unconstrained; ports may be a single port, a range
|
|
46
|
+
(``"8000-8100"``), or a comma list — always a string so the contract stays
|
|
47
|
+
transport- and vendor-agnostic.
|
|
48
|
+
|
|
49
|
+
``syslog_enabled`` is per-rule intent. Products whose firewall logging is
|
|
50
|
+
only list- or segment-scoped approximate it in the driver (enable scoped
|
|
51
|
+
logging when any rule requests it) — an accepted, documented approximation,
|
|
52
|
+
not a contract violation.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
action: RuleAction
|
|
56
|
+
protocol: RuleProtocol = RuleProtocol.ANY
|
|
57
|
+
src_cidr: str = "any"
|
|
58
|
+
src_port: str = "any"
|
|
59
|
+
dst_cidr: str = "any"
|
|
60
|
+
dst_port: str = "any"
|
|
61
|
+
comment: str = ""
|
|
62
|
+
syslog_enabled: bool = False
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class L7MatchType(StrEnum):
|
|
66
|
+
"""How an L7 (application-aware) firewall rule selects traffic."""
|
|
67
|
+
|
|
68
|
+
APPLICATION = "application"
|
|
69
|
+
APPLICATION_CATEGORY = "application_category"
|
|
70
|
+
HOST = "host"
|
|
71
|
+
PORT = "port"
|
|
72
|
+
IP_RANGE = "ip_range"
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclass
|
|
76
|
+
class L7Rule:
|
|
77
|
+
"""A single application-aware (L7) firewall rule.
|
|
78
|
+
|
|
79
|
+
``match_type`` selects the dimension; ``value`` carries the matched item.
|
|
80
|
+
For ``HOST`` / ``PORT`` / ``IP_RANGE`` the value is a free string. For
|
|
81
|
+
``APPLICATION_CATEGORY`` the value is an ``ApplicationCategory`` member. For
|
|
82
|
+
``APPLICATION`` (an individual app) the value is a vendor-mapped string — a
|
|
83
|
+
normalized ``Application`` registry is not seeded yet (grow on evidence). The
|
|
84
|
+
driver maps the value to its product's identifier in all cases.
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
action: RuleAction
|
|
88
|
+
match_type: L7MatchType
|
|
89
|
+
value: str
|
|
90
|
+
comment: str = ""
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class ContentCategory(StrEnum):
|
|
94
|
+
"""Normalized URL / content-filtering categories owned by commons.
|
|
95
|
+
|
|
96
|
+
A balanced **standard** set drawn from the common-denominator categories
|
|
97
|
+
across managed SD-WAN appliances' URL-filter taxonomies — broad enough to
|
|
98
|
+
cover the likely need without mirroring any one vendor's full list. The
|
|
99
|
+
plugin maps each to its product's category id; add members on evidence.
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
ADULT = "adult"
|
|
103
|
+
ADVERTISING = "advertising"
|
|
104
|
+
ALCOHOL_AND_TOBACCO = "alcohol_and_tobacco"
|
|
105
|
+
BUSINESS = "business"
|
|
106
|
+
DATING = "dating"
|
|
107
|
+
DRUGS = "drugs"
|
|
108
|
+
EDUCATION = "education"
|
|
109
|
+
FILE_SHARING = "file_sharing"
|
|
110
|
+
FINANCE = "finance"
|
|
111
|
+
GAMBLING = "gambling"
|
|
112
|
+
GAMES = "games"
|
|
113
|
+
GOVERNMENT = "government"
|
|
114
|
+
HACKING = "hacking"
|
|
115
|
+
HEALTH = "health"
|
|
116
|
+
ILLEGAL_CONTENT = "illegal_content"
|
|
117
|
+
JOB_SEARCH = "job_search"
|
|
118
|
+
MALWARE_SITES = "malware_sites"
|
|
119
|
+
NEWS = "news"
|
|
120
|
+
PEER_TO_PEER = "peer_to_peer"
|
|
121
|
+
PHISHING = "phishing"
|
|
122
|
+
RELIGION = "religion"
|
|
123
|
+
SEARCH_ENGINES = "search_engines"
|
|
124
|
+
SHOPPING = "shopping"
|
|
125
|
+
SOCIAL_NETWORKING = "social_networking"
|
|
126
|
+
SPORTS = "sports"
|
|
127
|
+
STREAMING_MEDIA = "streaming_media"
|
|
128
|
+
TRAVEL = "travel"
|
|
129
|
+
VIOLENCE = "violence"
|
|
130
|
+
WEAPONS = "weapons"
|
|
131
|
+
WEB_BASED_EMAIL = "web_based_email"
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class ApplicationCategory(StrEnum):
|
|
135
|
+
"""Normalized application categories for L7 (application-aware) policy.
|
|
136
|
+
|
|
137
|
+
A balanced **standard** set drawn from the common-denominator app-control
|
|
138
|
+
categories across managed SD-WAN appliances — the dimensions a test is
|
|
139
|
+
likely to steer or block on. The plugin maps each to its product's
|
|
140
|
+
application-category id; add members on evidence. (Individual application
|
|
141
|
+
identifiers — a far larger, more divergent catalog — are deliberately not
|
|
142
|
+
seeded here; add an ``Application`` registry if/when a test needs one.)
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
ADVERTISING = "advertising"
|
|
146
|
+
BUSINESS_AND_PRODUCTIVITY = "business_and_productivity"
|
|
147
|
+
CLOUD_SERVICES = "cloud_services"
|
|
148
|
+
COLLABORATION = "collaboration"
|
|
149
|
+
DATABASE = "database"
|
|
150
|
+
EMAIL = "email"
|
|
151
|
+
FILE_SHARING = "file_sharing"
|
|
152
|
+
GAMING = "gaming"
|
|
153
|
+
INSTANT_MESSAGING = "instant_messaging"
|
|
154
|
+
MUSIC_STREAMING = "music_streaming"
|
|
155
|
+
NETWORK_SERVICES = "network_services"
|
|
156
|
+
NEWS = "news"
|
|
157
|
+
PEER_TO_PEER = "peer_to_peer"
|
|
158
|
+
REMOTE_ACCESS = "remote_access"
|
|
159
|
+
SOCIAL_NETWORKING = "social_networking"
|
|
160
|
+
SOFTWARE_UPDATES = "software_updates"
|
|
161
|
+
SPORTS = "sports"
|
|
162
|
+
VIDEO_STREAMING = "video_streaming"
|
|
163
|
+
VOIP_AND_VIDEO_CONFERENCING = "voip_and_video_conferencing"
|
|
164
|
+
VPN_AND_PROXY = "vpn_and_proxy"
|
|
165
|
+
WEB_FILE_TRANSFER = "web_file_transfer"
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
# --- Traffic shaping ---
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class ShapingPriority(StrEnum):
|
|
172
|
+
"""Relative scheduling priority a shaping rule assigns to matched traffic."""
|
|
173
|
+
|
|
174
|
+
LOW = "low"
|
|
175
|
+
NORMAL = "normal"
|
|
176
|
+
HIGH = "high"
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
@dataclass
|
|
180
|
+
class ShapingRule:
|
|
181
|
+
"""A traffic-shaping rule — match a traffic class, then limit / mark / prioritize.
|
|
182
|
+
|
|
183
|
+
``match_type`` / ``value`` reuse the L7 match vocabulary (by application,
|
|
184
|
+
application category, host, port, or IP range). ``bandwidth_limit_kbps``
|
|
185
|
+
caps the class (``None`` = uncapped); ``dscp_tag`` applies a DSCP marking
|
|
186
|
+
(``None`` = leave unmarked); ``priority`` sets relative scheduling.
|
|
187
|
+
"""
|
|
188
|
+
|
|
189
|
+
name: str
|
|
190
|
+
match_type: L7MatchType
|
|
191
|
+
value: str
|
|
192
|
+
bandwidth_limit_kbps: int | None = None
|
|
193
|
+
dscp_tag: int | None = None
|
|
194
|
+
priority: ShapingPriority = ShapingPriority.NORMAL
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
# --- NAT (1:1 / 1:Many / port-forwarding) ---
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
@dataclass
|
|
201
|
+
class NatInboundAllow:
|
|
202
|
+
"""An inbound allowance attached to a 1:1 NAT mapping."""
|
|
203
|
+
|
|
204
|
+
protocol: RuleProtocol = RuleProtocol.ANY
|
|
205
|
+
ports: str = "any"
|
|
206
|
+
allowed_remote_cidrs: list[str] = field(default_factory=lambda: ["any"])
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
@dataclass
|
|
210
|
+
class PortForwardRule:
|
|
211
|
+
"""A port-forwarding (DNAT) rule — public port → internal host:port."""
|
|
212
|
+
|
|
213
|
+
name: str
|
|
214
|
+
protocol: RuleProtocol
|
|
215
|
+
public_port: str
|
|
216
|
+
lan_ip: str
|
|
217
|
+
local_port: str
|
|
218
|
+
uplink: str = "any"
|
|
219
|
+
allowed_remote_cidrs: list[str] = field(default_factory=lambda: ["any"])
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
@dataclass
|
|
223
|
+
class OneToOneNatRule:
|
|
224
|
+
"""A 1:1 NAT mapping between a public IP and an internal IP."""
|
|
225
|
+
|
|
226
|
+
name: str
|
|
227
|
+
public_ip: str
|
|
228
|
+
lan_ip: str
|
|
229
|
+
uplink: str = "any"
|
|
230
|
+
allowed_inbound: list[NatInboundAllow] = field(default_factory=list)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
@dataclass
|
|
234
|
+
class OneToManyNatRule:
|
|
235
|
+
"""A 1:many (PAT) mapping — one public IP, many port-based forwards."""
|
|
236
|
+
|
|
237
|
+
public_ip: str
|
|
238
|
+
uplink: str = "any"
|
|
239
|
+
port_forwards: list[PortForwardRule] = field(default_factory=list)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
# --- WAN uplinks ---
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
class UplinkState(StrEnum):
|
|
246
|
+
"""Operational state of a WAN uplink.
|
|
247
|
+
|
|
248
|
+
``DEGRADED`` covers vendor states reporting a link that is forwarding but
|
|
249
|
+
impaired (unstable / lossy / connecting) — normalized here so drivers do
|
|
250
|
+
not collapse such states into ``UP``.
|
|
251
|
+
"""
|
|
252
|
+
|
|
253
|
+
UP = "up"
|
|
254
|
+
DEGRADED = "degraded"
|
|
255
|
+
DOWN = "down"
|
|
256
|
+
STANDBY = "standby"
|
|
257
|
+
NOT_CONNECTED = "not_connected"
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
@dataclass
|
|
261
|
+
class UplinkStatus:
|
|
262
|
+
"""Current status of a single WAN uplink (read-only observation)."""
|
|
263
|
+
|
|
264
|
+
name: str
|
|
265
|
+
state: UplinkState
|
|
266
|
+
ip: str = ""
|
|
267
|
+
gateway: str = ""
|
|
268
|
+
public_ip: str = ""
|
|
269
|
+
primary_dns: str = ""
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
# --- Syslog destinations ---
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
class SyslogRole(StrEnum):
|
|
276
|
+
"""Category of log a syslog destination receives."""
|
|
277
|
+
|
|
278
|
+
EVENT_LOG = "event_log"
|
|
279
|
+
FLOWS = "flows"
|
|
280
|
+
SECURITY = "security"
|
|
281
|
+
URLS = "urls"
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
@dataclass
|
|
285
|
+
class SyslogServer:
|
|
286
|
+
"""A syslog destination and the log roles it receives."""
|
|
287
|
+
|
|
288
|
+
host: str
|
|
289
|
+
port: int = 514
|
|
290
|
+
roles: list[SyslogRole] = field(default_factory=list)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
# --- Threat prevention (IDS / IPS + malware) ---
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
class IntrusionMode(StrEnum):
|
|
297
|
+
"""IDS/IPS operating mode."""
|
|
298
|
+
|
|
299
|
+
DISABLED = "disabled"
|
|
300
|
+
DETECTION = "detection"
|
|
301
|
+
PREVENTION = "prevention"
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
class IntrusionSensitivity(StrEnum):
|
|
305
|
+
"""Normalized IPS ruleset sensitivity (vendor ruleset names map onto this)."""
|
|
306
|
+
|
|
307
|
+
LOW = "low"
|
|
308
|
+
MEDIUM = "medium"
|
|
309
|
+
HIGH = "high"
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
class MalwareMode(StrEnum):
|
|
313
|
+
"""Anti-malware operating mode."""
|
|
314
|
+
|
|
315
|
+
DISABLED = "disabled"
|
|
316
|
+
ENABLED = "enabled"
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
class SecurityAction(StrEnum):
|
|
320
|
+
"""What the appliance did about a security event."""
|
|
321
|
+
|
|
322
|
+
ALLOWED = "allowed"
|
|
323
|
+
BLOCKED = "blocked"
|
|
324
|
+
DETECTED = "detected"
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
class ThreatCategory(StrEnum):
|
|
328
|
+
"""Normalized class of a security event."""
|
|
329
|
+
|
|
330
|
+
MALWARE = "malware"
|
|
331
|
+
INTRUSION = "intrusion"
|
|
332
|
+
EXPLOIT = "exploit"
|
|
333
|
+
SCAN = "scan"
|
|
334
|
+
BOTNET = "botnet"
|
|
335
|
+
PHISHING = "phishing"
|
|
336
|
+
POLICY_VIOLATION = "policy_violation"
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
@dataclass
|
|
340
|
+
class IntrusionConfig:
|
|
341
|
+
"""IDS/IPS configuration state."""
|
|
342
|
+
|
|
343
|
+
mode: IntrusionMode
|
|
344
|
+
sensitivity: IntrusionSensitivity | None = None
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
@dataclass
|
|
348
|
+
class MalwareConfig:
|
|
349
|
+
"""Anti-malware configuration state."""
|
|
350
|
+
|
|
351
|
+
mode: MalwareMode
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
@dataclass
|
|
355
|
+
class SecurityEvent:
|
|
356
|
+
"""A normalized security event (the deferred-API-augmentation surface).
|
|
357
|
+
|
|
358
|
+
Carries only normalized fields for portable assertions — vendor signature
|
|
359
|
+
ids and raw payloads are deliberately not modelled. ``ts`` is an ISO-8601
|
|
360
|
+
UTC timestamp string.
|
|
361
|
+
"""
|
|
362
|
+
|
|
363
|
+
ts: str
|
|
364
|
+
src_ip: str
|
|
365
|
+
dst_ip: str
|
|
366
|
+
protocol: RuleProtocol
|
|
367
|
+
action: SecurityAction
|
|
368
|
+
category: ThreatCategory
|
|
369
|
+
description: str = ""
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
# --- LAN VLANs + DHCP ---
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
class DhcpMode(StrEnum):
|
|
376
|
+
"""How the appliance handles DHCP on a VLAN."""
|
|
377
|
+
|
|
378
|
+
SERVER = "server"
|
|
379
|
+
RELAY = "relay"
|
|
380
|
+
DISABLED = "disabled"
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
class DhcpOptionType(StrEnum):
|
|
384
|
+
"""Value type of a DHCP option."""
|
|
385
|
+
|
|
386
|
+
TEXT = "text"
|
|
387
|
+
IP = "ip"
|
|
388
|
+
INTEGER = "integer"
|
|
389
|
+
HEX = "hex"
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
@dataclass
|
|
393
|
+
class DhcpOption:
|
|
394
|
+
"""A custom DHCP option served on a VLAN."""
|
|
395
|
+
|
|
396
|
+
code: int
|
|
397
|
+
type: DhcpOptionType
|
|
398
|
+
value: str
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
@dataclass
|
|
402
|
+
class DhcpReservation:
|
|
403
|
+
"""A fixed IP assignment for a known MAC."""
|
|
404
|
+
|
|
405
|
+
mac: str
|
|
406
|
+
ip: str
|
|
407
|
+
name: str = ""
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
@dataclass
|
|
411
|
+
class VlanConfig:
|
|
412
|
+
"""A LAN VLAN and its DHCP configuration.
|
|
413
|
+
|
|
414
|
+
``dhcp_lease_seconds`` normalizes lease time to seconds (vendors express it
|
|
415
|
+
variously). ``dns_servers`` empty means "use the appliance / upstream
|
|
416
|
+
default". Reserved ranges are ``(start_ip, end_ip)`` pairs excluded from
|
|
417
|
+
the dynamic pool.
|
|
418
|
+
"""
|
|
419
|
+
|
|
420
|
+
vlan_id: int
|
|
421
|
+
name: str
|
|
422
|
+
subnet: str
|
|
423
|
+
appliance_ip: str
|
|
424
|
+
dhcp_mode: DhcpMode = DhcpMode.SERVER
|
|
425
|
+
dhcp_lease_seconds: int = 86400
|
|
426
|
+
dns_servers: list[str] = field(default_factory=list)
|
|
427
|
+
dhcp_options: list[DhcpOption] = field(default_factory=list)
|
|
428
|
+
reservations: list[DhcpReservation] = field(default_factory=list)
|
|
429
|
+
reserved_ranges: list[tuple[str, str]] = field(default_factory=list)
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
@dataclass
|
|
433
|
+
class DhcpLease:
|
|
434
|
+
"""An observed DHCP lease (read-only)."""
|
|
435
|
+
|
|
436
|
+
mac: str
|
|
437
|
+
ip: str
|
|
438
|
+
hostname: str = ""
|
|
439
|
+
vlan_id: int = 0
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
# --- Site-to-site VPN overlay ---
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
class VpnRole(StrEnum):
|
|
446
|
+
"""Role a device plays in the site-to-site VPN overlay."""
|
|
447
|
+
|
|
448
|
+
DISABLED = "disabled"
|
|
449
|
+
HUB = "hub"
|
|
450
|
+
SPOKE = "spoke"
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
class VpnPeerState(StrEnum):
|
|
454
|
+
"""Reachability of a site-to-site VPN peer."""
|
|
455
|
+
|
|
456
|
+
REACHABLE = "reachable"
|
|
457
|
+
UNREACHABLE = "unreachable"
|
|
458
|
+
UNKNOWN = "unknown"
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
@dataclass
|
|
462
|
+
class VpnHub:
|
|
463
|
+
"""A hub a spoke connects to.
|
|
464
|
+
|
|
465
|
+
``name`` is the testbed-level hub identifier; the plugin maps it to the
|
|
466
|
+
vendor's id. ``use_default_route`` points the spoke's default route into
|
|
467
|
+
the overlay via this hub.
|
|
468
|
+
"""
|
|
469
|
+
|
|
470
|
+
name: str
|
|
471
|
+
use_default_route: bool = False
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
@dataclass
|
|
475
|
+
class VpnSubnet:
|
|
476
|
+
"""A local subnet and whether it participates in the overlay."""
|
|
477
|
+
|
|
478
|
+
subnet: str
|
|
479
|
+
advertise: bool = True
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
@dataclass
|
|
483
|
+
class SiteToSiteVpnConfig:
|
|
484
|
+
"""Complete overlay-participation config — read and replaced whole.
|
|
485
|
+
|
|
486
|
+
``hubs`` is only meaningful for ``VpnRole.SPOKE`` and is ordered by
|
|
487
|
+
priority. ``subnets`` lists the local subnets and whether each is
|
|
488
|
+
advertised into the overlay.
|
|
489
|
+
"""
|
|
490
|
+
|
|
491
|
+
role: VpnRole
|
|
492
|
+
hubs: list[VpnHub] = field(default_factory=list)
|
|
493
|
+
subnets: list[VpnSubnet] = field(default_factory=list)
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
@dataclass
|
|
497
|
+
class VpnPeerStatus:
|
|
498
|
+
"""Observed status of one site-to-site VPN peer (read-only).
|
|
499
|
+
|
|
500
|
+
``name`` is the peer's testbed-level site name (normalized; the plugin
|
|
501
|
+
maps the vendor's peer identifier). ``uplink`` names the local uplink
|
|
502
|
+
carrying the tunnel when the product reports it, else ``""``.
|
|
503
|
+
"""
|
|
504
|
+
|
|
505
|
+
name: str
|
|
506
|
+
state: VpnPeerState
|
|
507
|
+
uplink: str = ""
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
# --- Path steering (uplink selection) ---
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
class SteeringScope(StrEnum):
|
|
514
|
+
"""Traffic domain an uplink-selection rule steers.
|
|
515
|
+
|
|
516
|
+
Deliberately no ``ANY`` member — the test author states the intent, and
|
|
517
|
+
products with split steering surfaces need it to route the write.
|
|
518
|
+
"""
|
|
519
|
+
|
|
520
|
+
INTERNET = "internet"
|
|
521
|
+
OVERLAY = "overlay"
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
@dataclass
|
|
525
|
+
class FlowMatch:
|
|
526
|
+
"""5-tuple traffic match for steering rules (match only — no action).
|
|
527
|
+
|
|
528
|
+
Field semantics mirror ``L3Rule``'s match half: ``"any"`` when
|
|
529
|
+
unconstrained; ports may be a single port, a range (``"8000-8100"``),
|
|
530
|
+
or a comma list.
|
|
531
|
+
"""
|
|
532
|
+
|
|
533
|
+
protocol: RuleProtocol = RuleProtocol.ANY
|
|
534
|
+
src_cidr: str = "any"
|
|
535
|
+
src_port: str = "any"
|
|
536
|
+
dst_cidr: str = "any"
|
|
537
|
+
dst_port: str = "any"
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
@dataclass
|
|
541
|
+
class UplinkSelectionRule:
|
|
542
|
+
"""One ordered uplink-steering rule.
|
|
543
|
+
|
|
544
|
+
With ``performance_class`` set (the *name* of an ``SLAPolicy`` configured
|
|
545
|
+
via ``configure_sla_policy``), traffic matching ``match`` is steered to
|
|
546
|
+
``preferred_uplink`` while the class is met and fails over when it is
|
|
547
|
+
breached. With ``performance_class=None`` the preference is static —
|
|
548
|
+
failover occurs only on uplink loss.
|
|
549
|
+
"""
|
|
550
|
+
|
|
551
|
+
name: str
|
|
552
|
+
scope: SteeringScope
|
|
553
|
+
match: FlowMatch
|
|
554
|
+
preferred_uplink: str
|
|
555
|
+
performance_class: str | None = None
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
# --- Static routes ---
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
@dataclass
|
|
562
|
+
class StaticRoute:
|
|
563
|
+
"""A testbed-owned static route.
|
|
564
|
+
|
|
565
|
+
``name`` is the per-entry CRUD handle (``remove_static_route(name)``).
|
|
566
|
+
Products whose API keys routes by sequence number or opaque id carry the
|
|
567
|
+
name in their description/comment field or a driver-side mapping — a
|
|
568
|
+
driver concern, not a contract one. ``next_hop`` is a next-hop IP
|
|
569
|
+
address; interface-bound next hops, metrics/administrative distance, and
|
|
570
|
+
per-route advertise flags grow on evidence.
|
|
571
|
+
"""
|
|
572
|
+
|
|
573
|
+
name: str
|
|
574
|
+
destination_cidr: str
|
|
575
|
+
next_hop: str
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
# --- BGP ---
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
class BgpSessionState(StrEnum):
|
|
582
|
+
"""BGP FSM state of a neighbor session.
|
|
583
|
+
|
|
584
|
+
The RFC 4271 state vocabulary — protocol-standard, not vendor-specific,
|
|
585
|
+
so the full set is seeded (the grow-on-evidence rule applies to vendor
|
|
586
|
+
taxonomies, not to standardized protocol states). ``UNKNOWN`` absorbs
|
|
587
|
+
vendor representations that do not map to an FSM state.
|
|
588
|
+
"""
|
|
589
|
+
|
|
590
|
+
IDLE = "idle"
|
|
591
|
+
CONNECT = "connect"
|
|
592
|
+
ACTIVE = "active"
|
|
593
|
+
OPEN_SENT = "open_sent"
|
|
594
|
+
OPEN_CONFIRM = "open_confirm"
|
|
595
|
+
ESTABLISHED = "established"
|
|
596
|
+
UNKNOWN = "unknown"
|
|
597
|
+
|
|
598
|
+
|
|
599
|
+
@dataclass
|
|
600
|
+
class BgpNeighbor:
|
|
601
|
+
"""A configured BGP neighbor (minimal — timers/auth/multihop grow on
|
|
602
|
+
evidence)."""
|
|
603
|
+
|
|
604
|
+
peer_ip: str
|
|
605
|
+
remote_as: int
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
@dataclass
|
|
609
|
+
class BgpConfig:
|
|
610
|
+
"""Complete BGP configuration — read and replaced whole.
|
|
611
|
+
|
|
612
|
+
``enabled`` / ``as_number`` / ``neighbors`` are semantically coupled,
|
|
613
|
+
and most reviewed management planes expose BGP as one object, so the
|
|
614
|
+
surface is whole-config replace (idempotent), not per-neighbor CRUD.
|
|
615
|
+
``advertised_networks`` lists CIDRs announced to peers; products that
|
|
616
|
+
auto-advertise their overlay subnets and offer no per-network control
|
|
617
|
+
raise unsupported-capability when it is non-empty.
|
|
618
|
+
"""
|
|
619
|
+
|
|
620
|
+
enabled: bool
|
|
621
|
+
as_number: int
|
|
622
|
+
neighbors: list[BgpNeighbor] = field(default_factory=list)
|
|
623
|
+
advertised_networks: list[str] = field(default_factory=list)
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
@dataclass
|
|
627
|
+
class BgpPeerStatus:
|
|
628
|
+
"""Observed status of one BGP neighbor session (read-only).
|
|
629
|
+
|
|
630
|
+
``prefixes_received`` is ``None`` when the product does not report a
|
|
631
|
+
count.
|
|
632
|
+
"""
|
|
633
|
+
|
|
634
|
+
peer_ip: str
|
|
635
|
+
remote_as: int
|
|
636
|
+
state: BgpSessionState
|
|
637
|
+
prefixes_received: int | None = None
|