testprotocols 0.1.0__tar.gz → 0.3.0__tar.gz

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 (145) hide show
  1. {testprotocols-0.1.0/src/testprotocols.egg-info → testprotocols-0.3.0}/PKG-INFO +1 -1
  2. {testprotocols-0.1.0 → testprotocols-0.3.0}/pyproject.toml +1 -1
  3. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/__init__.py +0 -2
  4. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/appliance_vlans.py +15 -2
  5. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/devices/client.py +8 -0
  6. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/devices/sdwan.py +7 -1
  7. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/models/qoe.py +1 -0
  8. testprotocols-0.3.0/src/testprotocols/network_probe.py +49 -0
  9. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/qoe_browser.py +9 -1
  10. testprotocols-0.3.0/src/testprotocols/reachability_responder.py +55 -0
  11. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/sdwan_policy_manager.py +25 -3
  12. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/spanning_tree.py +1 -1
  13. {testprotocols-0.1.0 → testprotocols-0.3.0/src/testprotocols.egg-info}/PKG-INFO +1 -1
  14. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols.egg-info/SOURCES.txt +2 -1
  15. testprotocols-0.3.0/tests/test_appliance_vlans.py +33 -0
  16. {testprotocols-0.1.0 → testprotocols-0.3.0}/tests/test_device_types.py +5 -3
  17. {testprotocols-0.1.0 → testprotocols-0.3.0}/tests/test_sdwan_appliance_models.py +12 -9
  18. {testprotocols-0.1.0 → testprotocols-0.3.0}/tests/test_switch_models.py +1 -2
  19. {testprotocols-0.1.0 → testprotocols-0.3.0}/tests/test_switch_routing_models.py +10 -2
  20. {testprotocols-0.1.0 → testprotocols-0.3.0}/tests/test_wan_edge_templates.py +2 -0
  21. testprotocols-0.1.0/src/testprotocols/infra_controller.py +0 -28
  22. testprotocols-0.1.0/src/testprotocols/network_probe.py +0 -27
  23. {testprotocols-0.1.0 → testprotocols-0.3.0}/LICENSE +0 -0
  24. {testprotocols-0.1.0 → testprotocols-0.3.0}/NOTICE +0 -0
  25. {testprotocols-0.1.0 → testprotocols-0.3.0}/README.md +0 -0
  26. {testprotocols-0.1.0 → testprotocols-0.3.0}/setup.cfg +0 -0
  27. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/aftr_gateway.py +0 -0
  28. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/appliance_nat.py +0 -0
  29. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/appliance_uplinks.py +0 -0
  30. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/arp_client.py +0 -0
  31. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/bgp.py +0 -0
  32. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/conntrack.py +0 -0
  33. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/content_filtering.py +0 -0
  34. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/device_lifecycle.py +0 -0
  35. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/device_management.py +0 -0
  36. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/devices/__init__.py +0 -0
  37. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/devices/base.py +0 -0
  38. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/devices/cpe.py +0 -0
  39. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/devices/infra.py +0 -0
  40. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/devices/switch.py +0 -0
  41. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/devices/traffic.py +0 -0
  42. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/devices/voice.py +0 -0
  43. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/devices/wan.py +0 -0
  44. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/dhcp_client.py +0 -0
  45. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/dhcp_server.py +0 -0
  46. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/discovery.py +0 -0
  47. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/dns_client.py +0 -0
  48. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/file_transfer.py +0 -0
  49. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/firewall.py +0 -0
  50. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/firewall_zones.py +0 -0
  51. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/first_hop_security.py +0 -0
  52. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/gateway_redundancy.py +0 -0
  53. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/http_client.py +0 -0
  54. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/http_server.py +0 -0
  55. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/hw_console.py +0 -0
  56. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/interface_dhcp.py +0 -0
  57. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/ip_interface.py +0 -0
  58. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/ip_routing.py +0 -0
  59. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/iperf_client.py +0 -0
  60. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/iperf_generator.py +0 -0
  61. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/iperf_server.py +0 -0
  62. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/l3_firewall.py +0 -0
  63. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/l7_firewall.py +0 -0
  64. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/link_aggregation.py +0 -0
  65. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/mac_table.py +0 -0
  66. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/models/__init__.py +92 -92
  67. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/models/dhcp.py +0 -0
  68. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/models/firewall.py +0 -0
  69. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/models/impairment.py +0 -0
  70. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/models/l2_common.py +0 -0
  71. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/models/multicast.py +0 -0
  72. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/models/networking.py +0 -0
  73. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/models/packets.py +0 -0
  74. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/models/radius.py +0 -0
  75. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/models/sdwan_appliance.py +0 -0
  76. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/models/switch.py +0 -0
  77. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/models/switch_routing.py +0 -0
  78. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/models/tr069.py +0 -0
  79. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/models/traffic.py +0 -0
  80. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/models/wan_edge.py +0 -0
  81. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/models/wifi.py +0 -0
  82. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/multicast_client.py +0 -0
  83. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/nat.py +0 -0
  84. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/netem_controller.py +0 -0
  85. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/network_endpoint.py +0 -0
  86. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/nmap_scanner.py +0 -0
  87. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/ntp_client.py +0 -0
  88. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/ntp_config.py +0 -0
  89. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/ospf.py +0 -0
  90. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/packet_filter.py +0 -0
  91. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/pcap_capture.py +0 -0
  92. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/pdu_controller.py +0 -0
  93. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/port_poe.py +0 -0
  94. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/port_security.py +0 -0
  95. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/port_status.py +0 -0
  96. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/py.typed +0 -0
  97. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/radius_client.py +0 -0
  98. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/radius_server.py +0 -0
  99. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/routed_interfaces.py +0 -0
  100. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/router.py +0 -0
  101. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/routing_read.py +0 -0
  102. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/sip_phone.py +0 -0
  103. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/sip_server.py +0 -0
  104. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/site_to_site_vpn.py +0 -0
  105. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/snmp_client.py +0 -0
  106. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/static_routes.py +0 -0
  107. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/storm_control.py +0 -0
  108. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/streaming_server.py +0 -0
  109. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/switch_acl.py +0 -0
  110. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/switch_ports.py +0 -0
  111. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/switch_qos.py +0 -0
  112. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/switch_vlans.py +0 -0
  113. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/syslog_config.py +0 -0
  114. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/tftp_server.py +0 -0
  115. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/threat_prevention.py +0 -0
  116. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/tr069_client.py +0 -0
  117. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/tr069_server.py +0 -0
  118. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/traffic_shaping.py +0 -0
  119. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/upnp_client.py +0 -0
  120. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/vlan_client.py +0 -0
  121. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/wan_link_admin.py +0 -0
  122. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/wifi_bss.py +0 -0
  123. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/wifi_client.py +0 -0
  124. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/wifi_mesh.py +0 -0
  125. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/wifi_onboarding.py +0 -0
  126. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/wifi_radio.py +0 -0
  127. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/wifi_rf.py +0 -0
  128. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/wifi_stations.py +0 -0
  129. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols/wifi_transitions.py +0 -0
  130. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols.egg-info/dependency_links.txt +0 -0
  131. {testprotocols-0.1.0 → testprotocols-0.3.0}/src/testprotocols.egg-info/top_level.txt +0 -0
  132. {testprotocols-0.1.0 → testprotocols-0.3.0}/tests/test_cpe_templates.py +0 -0
  133. {testprotocols-0.1.0 → testprotocols-0.3.0}/tests/test_dhcp_templates.py +0 -0
  134. {testprotocols-0.1.0 → testprotocols-0.3.0}/tests/test_firewall_templates.py +0 -0
  135. {testprotocols-0.1.0 → testprotocols-0.3.0}/tests/test_infra_templates.py +0 -0
  136. {testprotocols-0.1.0 → testprotocols-0.3.0}/tests/test_ip_interface.py +0 -0
  137. {testprotocols-0.1.0 → testprotocols-0.3.0}/tests/test_ip_routing.py +0 -0
  138. {testprotocols-0.1.0 → testprotocols-0.3.0}/tests/test_network_tool_templates.py +0 -0
  139. {testprotocols-0.1.0 → testprotocols-0.3.0}/tests/test_package_imports.py +0 -0
  140. {testprotocols-0.1.0 → testprotocols-0.3.0}/tests/test_radius_templates.py +0 -0
  141. {testprotocols-0.1.0 → testprotocols-0.3.0}/tests/test_sdwan_appliance_templates.py +0 -0
  142. {testprotocols-0.1.0 → testprotocols-0.3.0}/tests/test_switch_templates.py +0 -0
  143. {testprotocols-0.1.0 → testprotocols-0.3.0}/tests/test_traffic_templates.py +0 -0
  144. {testprotocols-0.1.0 → testprotocols-0.3.0}/tests/test_voice_wifi_templates.py +0 -0
  145. {testprotocols-0.1.0 → testprotocols-0.3.0}/tests/test_wifi_templates.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: testprotocols
3
- Version: 0.1.0
3
+ Version: 0.3.0
4
4
  Summary: Common Test Resource Layer — capability and device protocols for telco testing.
5
5
  Author-email: Alottabits <rjvisser@alottabits.com>
6
6
  Maintainer-email: Alottabits <rjvisser@alottabits.com>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "testprotocols"
3
- version = "0.1.0"
3
+ version = "0.3.0"
4
4
  description = "Common Test Resource Layer — capability and device protocols for telco testing."
5
5
  requires-python = ">=3.12"
6
6
  dependencies = []
@@ -47,7 +47,6 @@ from testprotocols.gateway_redundancy import GatewayRedundancy
47
47
  from testprotocols.http_client import HttpClient
48
48
  from testprotocols.http_server import HttpServer
49
49
  from testprotocols.hw_console import HwConsole
50
- from testprotocols.infra_controller import InfraController
51
50
  from testprotocols.interface_dhcp import InterfaceDhcp
52
51
  from testprotocols.ip_interface import IpInterface
53
52
  from testprotocols.ip_routing import IpRouting
@@ -136,7 +135,6 @@ __all__ = [
136
135
  "HttpClient",
137
136
  "HttpServer",
138
137
  "HwConsole",
139
- "InfraController",
140
138
  "InterfaceDhcp",
141
139
  "IpInterface",
142
140
  "IpRouting",
@@ -5,8 +5,8 @@ subnet/addressing and DHCP configuration, plus observed DHCP leases. This is the
5
5
  appliance counterpart to a Linux host's interface + DHCP-server surfaces — an
6
6
  appliance configures DHCP per VLAN, not via a host daemon.
7
7
 
8
- In scope: list/get/set VLAN configuration (incl. per-VLAN DHCP), and read DHCP
9
- leases.
8
+ In scope: list/get/set/delete VLAN configuration (incl. per-VLAN DHCP), and read
9
+ DHCP leases.
10
10
 
11
11
  Out of scope: WAN uplink status (see ``appliance_uplinks``), L3 firewall
12
12
  (see ``l3_firewall``), and host-style per-interface config (see ``ip_interface``).
@@ -38,6 +38,19 @@ class ApplianceVlans(Protocol):
38
38
  """Create or replace the VLAN identified by ``config.vlan_id``."""
39
39
  ...
40
40
 
41
+ def delete_vlan(self, vlan_id: int) -> None:
42
+ """Remove the LAN VLAN identified by *vlan_id*.
43
+
44
+ Per-object remove, completing the VLAN CRUD surface (mirrors
45
+ ``StaticRoutes.remove_static_route``). On management planes that model the
46
+ LAN as one config blob, a driver implements this as read-modify-write of
47
+ that module. Deleting a missing VLAN may raise or succeed by plane;
48
+ callers needing idempotence should check first (``get_vlan`` raises
49
+ KeyError when absent). A plane that cannot remove a required / last LAN
50
+ VLAN raises unsupported-capability.
51
+ """
52
+ ...
53
+
41
54
  def get_dhcp_leases(self, vlan_id: int | None = None) -> list[DhcpLease]:
42
55
  """Return current DHCP leases, optionally filtered to one *vlan_id*.
43
56
 
@@ -24,6 +24,7 @@ from testprotocols.ntp_client import NtpClient
24
24
  from testprotocols.packet_filter import PacketFilter
25
25
  from testprotocols.pcap_capture import PcapCapture
26
26
  from testprotocols.qoe_browser import QoeBrowser
27
+ from testprotocols.reachability_responder import ReachabilityResponder
27
28
  from testprotocols.syslog_config import SyslogConfig
28
29
  from testprotocols.upnp_client import UpnpClient
29
30
  from testprotocols.vlan_client import VlanClient
@@ -124,8 +125,15 @@ class QoeMeasurementClientDevice(QoeClientDevice, Protocol):
124
125
  iperf_client: IperfClient
125
126
  iperf_server: IperfServer
126
127
  network_probe: NetworkProbe
128
+ responder: ReachabilityResponder
127
129
  pcap: PcapCapture
128
130
 
131
+ test_ip: str
132
+ """The client's test-plane address as a CIDR (e.g. ``"10.1.30.50/24"``), empty
133
+ if the client is not homed to a test segment. Vendor-neutral metadata the
134
+ driver resolves from its homing config — read it here rather than reaching into
135
+ a framework-specific config object."""
136
+
129
137
 
130
138
  register_device_type("linux_lan_client", LanClientDevice)
131
139
  register_device_type("linux_wlan_client", WlanClientDevice)
@@ -8,8 +8,8 @@ from testprotocols.appliance_nat import ApplianceNat
8
8
  from testprotocols.appliance_uplinks import ApplianceUplinks
9
9
  from testprotocols.appliance_vlans import ApplianceVlans
10
10
  from testprotocols.bgp import Bgp
11
- from testprotocols.content_filtering import ContentFiltering
12
11
  from testprotocols.conntrack import Conntrack
12
+ from testprotocols.content_filtering import ContentFiltering
13
13
  from testprotocols.devices import register_device_type
14
14
  from testprotocols.devices.base import BaseDeviceProtocol
15
15
  from testprotocols.ip_interface import IpInterface
@@ -93,5 +93,11 @@ class SdwanApplianceDevice(BaseDeviceProtocol, Protocol):
93
93
  lan: ApplianceVlans
94
94
  syslog: SyslogConfig
95
95
 
96
+ model: str
97
+ """Hardware model identifier (e.g. ``"MX250"``) — vendor-neutral metadata the
98
+ driver resolves from its management API / inventory. The coverage axis for
99
+ model-parameterised tests; read it here rather than reaching into a
100
+ framework-specific config object."""
101
+
96
102
 
97
103
  register_device_type("sdwan_appliance", SdwanApplianceDevice)
@@ -29,3 +29,4 @@ class MeasurementSpec:
29
29
  completion: str = "networkidle"
30
30
  timeout_ms: int = 30000
31
31
  duration_s: int | None = None
32
+ force_quic: bool = True
@@ -0,0 +1,49 @@
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
+ ...
28
+
29
+ def icmp_can_reach(self, host: str, count: int = 1, timeout: int = 5) -> bool:
30
+ """Attempt an ICMP echo to *host* and return reachability.
31
+
32
+ Returns ``True`` if at least one of *count* echo requests is answered
33
+ within *timeout* seconds, ``False`` otherwise (no route, filtered,
34
+ timed out). Read-only — echo requests have no side effect on the
35
+ destination.
36
+ """
37
+ ...
38
+
39
+ def udp_can_reach(self, host: str, port: int, timeout: int = 5) -> bool:
40
+ """Attempt a UDP exchange with *host*:*port* and return reachability.
41
+
42
+ UDP is connectionless and has no handshake, so a dropped datagram is
43
+ indistinguishable from a delivered one at the sender unless the far end
44
+ replies. This probe therefore reports reachability based on a **reply**
45
+ from a UDP responder at the target within *timeout* seconds — callers
46
+ that need a definitive verdict must ensure a responder is present. A
47
+ missing reply (no responder, no route, or filtered) is ``False``.
48
+ """
49
+ ...
@@ -27,8 +27,16 @@ class QoeBrowser(Protocol):
27
27
  scenario: str = "page_load",
28
28
  wait_until: str = "networkidle",
29
29
  timeout_ms: int = 30000,
30
+ force_quic: bool = True,
30
31
  ) -> QoEResult:
31
- """Measure productivity-app QoE for *url* (e.g., page-load time)."""
32
+ """Measure productivity-app QoE for *url* (e.g., page-load time).
33
+
34
+ ``force_quic=True`` (default) forces HTTP/3/QUIC, as a QoE measurement
35
+ should. Set ``force_quic=False`` for a reachability/block probe: the
36
+ navigation runs over TCP/h2 (QUIC disabled), so a non-HTTP/3 site stays
37
+ reachable and a TCP/TLS-inspecting middlebox (e.g. content filtering)
38
+ actually sees the traffic.
39
+ """
32
40
  ...
33
41
 
34
42
  def measure_streaming(
@@ -0,0 +1,55 @@
1
+ """Reachability responder template — the answerer counterpart to ``NetworkProbe``.
2
+
3
+ ``NetworkProbe`` *sends* reachability probes; a peer being probed must *answer*
4
+ them or a "blocked" verdict is indistinguishable from "no listener". This
5
+ capability stands up those answerers on the target's test interface:
6
+
7
+ * **TCP** — a listener that accepts connections (a ``tcp_can_connect`` connect
8
+ probe completes the handshake on accept).
9
+ * **UDP** — an echo responder (a content-based ``udp_can_reach`` probe sends a
10
+ token and is reachable iff it is echoed back).
11
+
12
+ ICMP needs no responder (the kernel answers echo requests). The pairing is
13
+ explicit and 1:1:
14
+
15
+ NetworkProbe.tcp_can_connect <-> ReachabilityResponder.start_tcp_responder
16
+ NetworkProbe.udp_can_reach <-> ReachabilityResponder.start_udp_responder
17
+ NetworkProbe.icmp_can_reach <-> (none — kernel)
18
+
19
+ Vendor-neutral contract; the listener mechanism (sockets / ``socat`` / a service)
20
+ lives entirely in the per-plugin impl, exactly as ``NetworkProbe`` impls do.
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ from typing import Protocol, runtime_checkable
26
+
27
+
28
+ @runtime_checkable
29
+ class ReachabilityResponder(Protocol):
30
+ """Abstract contract for standing up / tearing down reachability responders."""
31
+
32
+ def start_tcp_responder(self, port: int) -> None:
33
+ """Start a TCP responder on *port* that accepts connections.
34
+
35
+ A connect-only ``NetworkProbe.tcp_can_connect`` probe completes its
36
+ handshake against this listener, so an open path reads as reachable.
37
+ """
38
+ ...
39
+
40
+ def start_udp_responder(self, port: int) -> None:
41
+ """Start a UDP echo responder on *port*.
42
+
43
+ Reflects every datagram it receives back to the sender, so a peer's
44
+ content-based ``NetworkProbe.udp_can_reach`` probe gets its token echoed
45
+ when the path is open — making "blocked" distinguishable from
46
+ "no listener" on the connectionless protocol.
47
+ """
48
+ ...
49
+
50
+ def stop(self, port: int | None = None) -> None:
51
+ """Stop the responders on *port* (both protocols); ``None`` stops all.
52
+
53
+ No-op if none is running.
54
+ """
55
+ ...
@@ -7,9 +7,12 @@ Firewall-rule administration is **not** here — it moved to the dedicated
7
7
  ``l3_firewall`` / ``l7_firewall`` capabilities (coherent-domain split; see
8
8
  SPLITS.md). Typed path steering (``set_uplink_selection`` /
9
9
  ``get_uplink_selection`` over ordered ``UplinkSelectionRule``s; performance
10
- classes reuse ``SLAPolicy`` by name) landed 2026-06-12; ``apply_policy``
11
- remains the generic escape hatch for vendor-shaped policies beyond that
12
- surface. Application-match steering grows on evidence.
10
+ classes reuse ``SLAPolicy`` by name) landed 2026-06-12; the default
11
+ (primary) uplink pair (``get_default_uplink`` / ``set_default_uplink``)
12
+ landed 2026-07-03 the network-wide "which uplink carries default-routed
13
+ traffic" knob, distinct from per-flow steering. ``apply_policy`` remains
14
+ the generic escape hatch for vendor-shaped policies beyond that surface.
15
+ Application-match steering grows on evidence.
13
16
  """
14
17
 
15
18
  from __future__ import annotations
@@ -48,6 +51,25 @@ class SdwanPolicyManager(Protocol):
48
51
  """Return application flows observed in the last *since_s* seconds."""
49
52
  ...
50
53
 
54
+ def get_default_uplink(self) -> str:
55
+ """Return the name of the network's default (primary) uplink.
56
+
57
+ The default uplink carries all default-routed traffic that no
58
+ uplink-selection rule steers elsewhere — e.g. ``"wan1"``. Vendor
59
+ drivers map this to their primary-uplink notion.
60
+ """
61
+ ...
62
+
63
+ def set_default_uplink(self, uplink: str) -> None:
64
+ """Set the network's default (primary) uplink to *uplink*.
65
+
66
+ Changes only which uplink carries default-routed traffic; the
67
+ ordered uplink-selection rules (``set_uplink_selection``) are left
68
+ untouched. Drivers reject names the product cannot express (e.g.
69
+ an uplink the appliance does not have) rather than approximating.
70
+ """
71
+ ...
72
+
51
73
  def set_uplink_selection(self, rules: list[UplinkSelectionRule]) -> None:
52
74
  """Replace the ordered uplink-selection rule list with *rules*.
53
75
 
@@ -21,7 +21,7 @@ class SpanningTree(Protocol):
21
21
  ...
22
22
 
23
23
  def set_bridge_priority(self, priority: int) -> None:
24
- """Set the bridge priority (061440, in steps of 4096 per IEEE 802.1D)."""
24
+ """Set the bridge priority (0-61440, in steps of 4096 per IEEE 802.1D)."""
25
25
  ...
26
26
 
27
27
  def set_port_config(self, config: StpPortConfig) -> None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: testprotocols
3
- Version: 0.1.0
3
+ Version: 0.3.0
4
4
  Summary: Common Test Resource Layer — capability and device protocols for telco testing.
5
5
  Author-email: Alottabits <rjvisser@alottabits.com>
6
6
  Maintainer-email: Alottabits <rjvisser@alottabits.com>
@@ -25,7 +25,6 @@ src/testprotocols/gateway_redundancy.py
25
25
  src/testprotocols/http_client.py
26
26
  src/testprotocols/http_server.py
27
27
  src/testprotocols/hw_console.py
28
- src/testprotocols/infra_controller.py
29
28
  src/testprotocols/interface_dhcp.py
30
29
  src/testprotocols/ip_interface.py
31
30
  src/testprotocols/ip_routing.py
@@ -55,6 +54,7 @@ src/testprotocols/py.typed
55
54
  src/testprotocols/qoe_browser.py
56
55
  src/testprotocols/radius_client.py
57
56
  src/testprotocols/radius_server.py
57
+ src/testprotocols/reachability_responder.py
58
58
  src/testprotocols/routed_interfaces.py
59
59
  src/testprotocols/router.py
60
60
  src/testprotocols/routing_read.py
@@ -119,6 +119,7 @@ src/testprotocols/models/tr069.py
119
119
  src/testprotocols/models/traffic.py
120
120
  src/testprotocols/models/wan_edge.py
121
121
  src/testprotocols/models/wifi.py
122
+ tests/test_appliance_vlans.py
122
123
  tests/test_cpe_templates.py
123
124
  tests/test_device_types.py
124
125
  tests/test_dhcp_templates.py
@@ -0,0 +1,33 @@
1
+ """Protocol-conformance tests for ApplianceVlans (runtime_checkable structural check)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from testprotocols.appliance_vlans import ApplianceVlans
6
+
7
+
8
+ class _Complete:
9
+ """Has every ApplianceVlans member, including delete_vlan."""
10
+
11
+ def list_vlans(self): ...
12
+ def get_vlan(self, vlan_id): ...
13
+ def set_vlan(self, config): ...
14
+ def delete_vlan(self, vlan_id): ...
15
+ def get_dhcp_leases(self, vlan_id=None): ...
16
+
17
+
18
+ class _NoDelete:
19
+ """Missing delete_vlan — must NOT satisfy the protocol once delete_vlan is declared."""
20
+
21
+ def list_vlans(self): ...
22
+ def get_vlan(self, vlan_id): ...
23
+ def set_vlan(self, config): ...
24
+ def get_dhcp_leases(self, vlan_id=None): ...
25
+
26
+
27
+ def test_complete_impl_satisfies_protocol():
28
+ assert isinstance(_Complete(), ApplianceVlans)
29
+
30
+
31
+ def test_delete_vlan_is_required_by_protocol():
32
+ # runtime_checkable isinstance requires every declared member be present.
33
+ assert not isinstance(_NoDelete(), ApplianceVlans)
@@ -10,7 +10,6 @@ inventory-string -> Protocol binding and the per-archetype capability set
10
10
  from __future__ import annotations
11
11
 
12
12
  import pytest
13
-
14
13
  from testprotocols.devices import DeviceTypeSpec, all_device_types, get_device_type
15
14
  from testprotocols.devices.base import BaseDeviceProtocol
16
15
  from testprotocols.devices.client import (
@@ -22,12 +21,12 @@ from testprotocols.devices.client import (
22
21
  from testprotocols.devices.cpe import CpeDevice
23
22
  from testprotocols.devices.infra import AcsDevice, ProvisionerDevice, TftpDevice
24
23
  from testprotocols.devices.sdwan import SdwanApplianceDevice, SdwanRouterDevice
24
+ from testprotocols.devices.switch import L2Switch, L3Switch, L3SwitchRouted
25
25
  from testprotocols.devices.traffic import (
26
26
  IperfTrafficGeneratorDevice,
27
27
  TrafficControllerDevice,
28
28
  )
29
29
  from testprotocols.devices.voice import SipPhoneDevice, SipServerDevice
30
- from testprotocols.devices.switch import L2Switch, L3Switch, L3SwitchRouted
31
30
  from testprotocols.devices.wan import WanServerDevice
32
31
 
33
32
  _ALL_ARCHETYPES = (
@@ -320,6 +319,7 @@ def test_qoe_measurement_client_aggregates_expected_capabilities() -> None:
320
319
  "iperf_client",
321
320
  "iperf_server",
322
321
  "network_probe",
322
+ "responder",
323
323
  "pcap",
324
324
  "syslog",
325
325
  }
@@ -454,7 +454,9 @@ def test_cpe_full_firewall_surface() -> None:
454
454
 
455
455
  def test_devices_without_firewall() -> None:
456
456
  """These archetypes must not pull in any of the firewall capabilities."""
457
- firewall_attrs = ("packet_filter", "firewall", "nat", "port_forwarding", "conntrack", "firewall_zones")
457
+ firewall_attrs = (
458
+ "packet_filter", "firewall", "nat", "port_forwarding", "conntrack", "firewall_zones"
459
+ )
458
460
  for archetype in (
459
461
  TftpDevice,
460
462
  WlanClientDevice,
@@ -10,7 +10,6 @@ from __future__ import annotations
10
10
  from enum import StrEnum
11
11
 
12
12
  import pytest
13
-
14
13
  from testprotocols.models.sdwan_appliance import (
15
14
  ApplicationCategory,
16
15
  BgpConfig,
@@ -18,20 +17,20 @@ from testprotocols.models.sdwan_appliance import (
18
17
  BgpPeerStatus,
19
18
  BgpSessionState,
20
19
  ContentCategory,
21
- FlowMatch,
22
- L3Rule,
23
- L7MatchType,
24
- L7Rule,
25
- RuleAction,
26
- RuleProtocol,
27
20
  DhcpLease,
28
21
  DhcpMode,
29
22
  DhcpOption,
30
23
  DhcpOptionType,
24
+ FlowMatch,
31
25
  IntrusionConfig,
32
26
  IntrusionMode,
33
27
  IntrusionSensitivity,
28
+ L3Rule,
29
+ L7MatchType,
30
+ L7Rule,
34
31
  MalwareMode,
32
+ RuleAction,
33
+ RuleProtocol,
35
34
  SecurityAction,
36
35
  SecurityEvent,
37
36
  ShapingPriority,
@@ -126,7 +125,8 @@ def test_shaping_rule_uses_normalized_vocabulary() -> None:
126
125
  priority=ShapingPriority.LOW,
127
126
  )
128
127
  assert rule.priority == "low"
129
- assert ShapingRule(name="x", match_type=L7MatchType.HOST, value="1.2.3.4").priority is ShapingPriority.NORMAL
128
+ default = ShapingRule(name="x", match_type=L7MatchType.HOST, value="1.2.3.4")
129
+ assert default.priority is ShapingPriority.NORMAL
130
130
 
131
131
 
132
132
  def test_uplink_state_and_status() -> None:
@@ -144,7 +144,10 @@ def test_syslog_role_and_server() -> None:
144
144
 
145
145
 
146
146
  def test_threat_prevention_vocabularies() -> None:
147
- for enum_cls in (IntrusionMode, IntrusionSensitivity, MalwareMode, SecurityAction, ThreatCategory):
147
+ vocabularies = (
148
+ IntrusionMode, IntrusionSensitivity, MalwareMode, SecurityAction, ThreatCategory
149
+ )
150
+ for enum_cls in vocabularies:
148
151
  assert issubclass(enum_cls, StrEnum)
149
152
  assert {m.value for m in IntrusionMode} == {"disabled", "detection", "prevention"}
150
153
  assert {s.value for s in IntrusionSensitivity} == {"low", "medium", "high"}
@@ -66,6 +66,7 @@ def test_switch_records() -> None:
66
66
  AccessPolicy,
67
67
  AccessPolicyType,
68
68
  FhsBinding,
69
+ LinkAggregationGroup,
69
70
  LinkState,
70
71
  LldpNeighbor,
71
72
  NtpServer,
@@ -74,12 +75,10 @@ def test_switch_records() -> None:
74
75
  PortStatusEntry,
75
76
  QosRule,
76
77
  StormControlConfig,
77
- StormControlType,
78
78
  StpPortConfig,
79
79
  SwitchAclRule,
80
80
  SwitchPort,
81
81
  VlanDef,
82
- LinkAggregationGroup,
83
82
  )
84
83
 
85
84
  p = SwitchPort(name="1", mode="access") # type: ignore[arg-type]
@@ -43,7 +43,13 @@ def test_switch_routing_records() -> None:
43
43
  RoutedInterface,
44
44
  )
45
45
 
46
- ri = RoutedInterface(name="vlan10", mode=InterfaceMode.SVI, ip_address="10.0.10.1", subnet="10.0.10.0/24", vlan_id=10)
46
+ ri = RoutedInterface(
47
+ name="vlan10",
48
+ mode=InterfaceMode.SVI,
49
+ ip_address="10.0.10.1",
50
+ subnet="10.0.10.0/24",
51
+ vlan_id=10,
52
+ )
47
53
  assert ri.vlan_id == 10
48
54
  d = InterfaceDhcpConfig(interface="vlan10")
49
55
  assert d.mode == "disabled" and d.relay_targets == []
@@ -51,5 +57,7 @@ def test_switch_routing_records() -> None:
51
57
  assert o.version == "v2" and o.interfaces == []
52
58
  oi = OspfInterfaceSettings(interface="vlan10", area="0.0.0.0")
53
59
  assert oi.passive is False
54
- g = RedundancyGroup(group_id=1, virtual_ip="10.0.10.254", role=RedundancyRole.PRIMARY, interface="vlan10")
60
+ g = RedundancyGroup(
61
+ group_id=1, virtual_ip="10.0.10.254", role=RedundancyRole.PRIMARY, interface="vlan10"
62
+ )
55
63
  assert g.virtual_ip == "10.0.10.254"
@@ -60,6 +60,8 @@ PROTOCOLS = [
60
60
  "get_application_flows",
61
61
  "set_uplink_selection",
62
62
  "get_uplink_selection",
63
+ "get_default_uplink",
64
+ "set_default_uplink",
63
65
  },
64
66
  ),
65
67
  (
@@ -1,28 +0,0 @@
1
- """Infra-controller capability template.
2
-
3
- Abstract contract for environment/fabric control delegated by the test
4
- orchestrator to a hypervisor (vSphere). It reads and (for re-homing) moves a
5
- VM NIC's port-group, and asserts a named port-group is present. It does NOT
6
- create fabric — the lab carries the test VLANs already.
7
- """
8
-
9
- from __future__ import annotations
10
-
11
- from typing import Protocol, runtime_checkable
12
-
13
-
14
- @runtime_checkable
15
- class InfraController(Protocol):
16
- """Read / presence-check / move a VM NIC's port-group."""
17
-
18
- def get_nic_portgroup(self, vm_name: str, nic_index: int) -> str:
19
- """Return the portgroup name backing NIC *nic_index* on *vm_name*."""
20
- ...
21
-
22
- def ensure_port_group(self, name: str) -> None:
23
- """Assert a portgroup named *name* exists (raise if absent). No create."""
24
- ...
25
-
26
- def set_nic_portgroup(self, vm_name: str, nic_index: int, portgroup: str) -> None:
27
- """Repoint NIC *nic_index* on *vm_name* onto the named *portgroup*."""
28
- ...
@@ -1,27 +0,0 @@
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
- ...
File without changes
File without changes
File without changes
File without changes