python-netgear-switch-library 0.0.post154__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.
- netgear_switch/__init__.py +132 -0
- netgear_switch/_dispatch.py +178 -0
- netgear_switch/_version.py +24 -0
- netgear_switch/aio_api.py +529 -0
- netgear_switch/cli/__init__.py +1 -0
- netgear_switch/cli/capture.py +131 -0
- netgear_switch/cli/context.py +39 -0
- netgear_switch/cli/format.py +201 -0
- netgear_switch/cli/main.py +484 -0
- netgear_switch/cli/resolve.py +108 -0
- netgear_switch/cli/safety.py +71 -0
- netgear_switch/config.py +184 -0
- netgear_switch/errors.py +52 -0
- netgear_switch/http_read.py +174 -0
- netgear_switch/http_write.py +420 -0
- netgear_switch/models.py +156 -0
- netgear_switch/nsdp_read.py +221 -0
- netgear_switch/nsdp_write.py +315 -0
- netgear_switch/protocols/__init__.py +1 -0
- netgear_switch/protocols/http/__init__.py +1 -0
- netgear_switch/protocols/http/crypt.py +29 -0
- netgear_switch/protocols/http/endpoints.py +165 -0
- netgear_switch/protocols/http/forms.py +77 -0
- netgear_switch/protocols/http/parse.py +238 -0
- netgear_switch/protocols/http/session.py +29 -0
- netgear_switch/protocols/nsdp/__init__.py +7 -0
- netgear_switch/protocols/nsdp/auth.py +33 -0
- netgear_switch/protocols/nsdp/client.py +67 -0
- netgear_switch/protocols/nsdp/parsers.py +209 -0
- netgear_switch/protocols/nsdp/protocol.py +201 -0
- netgear_switch/protocols/nsdp/types.py +137 -0
- netgear_switch/protocols/nsdp/write.py +98 -0
- netgear_switch/protocols/snmp/__init__.py +1 -0
- netgear_switch/protocols/snmp/client.py +88 -0
- netgear_switch/protocols/snmp/oids.py +125 -0
- netgear_switch/protocols/snmp/parse.py +777 -0
- netgear_switch/protocols/snmp/write.py +112 -0
- netgear_switch/py.typed +0 -0
- netgear_switch/registry.py +227 -0
- netgear_switch/snmp_read.py +226 -0
- netgear_switch/snmp_write.py +625 -0
- netgear_switch/sync_api.py +557 -0
- netgear_switch/transport/__init__.py +1 -0
- netgear_switch/transport/aio/__init__.py +1 -0
- netgear_switch/transport/aio/nsdp_udp.py +152 -0
- netgear_switch/transport/aio/snmp_pysnmp.py +247 -0
- netgear_switch/transport/http/__init__.py +1 -0
- netgear_switch/transport/http/client.py +217 -0
- netgear_switch/transport/sync/__init__.py +1 -0
- netgear_switch/transport/sync/nsdp_udp.py +109 -0
- netgear_switch/transport/sync/snmp_netsnmp_cli.py +257 -0
- netgear_switch/virtual/__init__.py +8 -0
- netgear_switch/virtual/faces/__init__.py +2 -0
- netgear_switch/virtual/faces/http.py +164 -0
- netgear_switch/virtual/faces/mibview.py +92 -0
- netgear_switch/virtual/faces/nsdp.py +124 -0
- netgear_switch/virtual/faces/snmp.py +412 -0
- netgear_switch/virtual/seed.py +220 -0
- netgear_switch/virtual/server.py +106 -0
- netgear_switch/virtual/state.py +615 -0
- netgear_switch/virtual/web.py +210 -0
- python_netgear_switch_library-0.0.post154.dist-info/METADATA +85 -0
- python_netgear_switch_library-0.0.post154.dist-info/RECORD +66 -0
- python_netgear_switch_library-0.0.post154.dist-info/WHEEL +4 -0
- python_netgear_switch_library-0.0.post154.dist-info/entry_points.txt +2 -0
- python_netgear_switch_library-0.0.post154.dist-info/licenses/LICENSE +202 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""Shared SNMP transport seam: row type, error, and client protocols.
|
|
2
|
+
|
|
3
|
+
Pure and I/O-free, and transport-agnostic. The net-snmp CLI (sync) and pysnmp
|
|
4
|
+
(async) transports both implement these protocols and return SnmpRow instances
|
|
5
|
+
the parsers consume. No transport-specific types appear here.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from typing import TYPE_CHECKING, Protocol
|
|
11
|
+
|
|
12
|
+
from ...errors import NetgearSwitchError
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from .write import SetVarbind
|
|
16
|
+
|
|
17
|
+
# snmp_type tokens meaning "no value present here".
|
|
18
|
+
ABSENT_TYPES: frozenset[str] = frozenset(
|
|
19
|
+
{"NOSUCHOBJECT", "NOSUCHINSTANCE", "ENDOFMIBVIEW"}
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass(frozen=True)
|
|
24
|
+
class SnmpRow:
|
|
25
|
+
"""One SNMP varbind: full numeric OID, normalized value, type token.
|
|
26
|
+
|
|
27
|
+
``value`` is a normalized Python value so the sync (net-snmp CLI) and async
|
|
28
|
+
(pysnmp) clients are interchangeable: ``int`` for integer-family types
|
|
29
|
+
(INTEGER/Gauge32/Counter32/Counter64/Timeticks-numeric), ``str`` for text,
|
|
30
|
+
OID and IP-address values, ``bytes`` for raw octet strings (Hex-STRING).
|
|
31
|
+
Both clients MUST yield equal values for the same OID (Task 16 enforces it).
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
oid: str
|
|
35
|
+
value: int | str | bytes
|
|
36
|
+
snmp_type: str
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class SnmpError(NetgearSwitchError):
|
|
40
|
+
"""An SNMP transport operation failed (timeout, connection, agent error)."""
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def full_oid(oid: str, oid_index: str) -> str:
|
|
44
|
+
"""Join an optional instance index onto a base OID as a full numeric OID.
|
|
45
|
+
|
|
46
|
+
A transport may hand back the whole numeric OID in ``oid`` with an empty
|
|
47
|
+
``oid_index``, or split the instance into ``oid_index``. Joining both and
|
|
48
|
+
stripping any leading dot is correct either way.
|
|
49
|
+
"""
|
|
50
|
+
oid = oid.lstrip(".")
|
|
51
|
+
return f"{oid}.{oid_index}" if oid_index else oid
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class SnmpClient(Protocol):
|
|
55
|
+
"""Synchronous SNMP v2c read client for a single switch."""
|
|
56
|
+
|
|
57
|
+
def get(self, oids: list[str]) -> list[SnmpRow]: ...
|
|
58
|
+
|
|
59
|
+
def walk(self, base_oid: str) -> list[SnmpRow]: ...
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class AsyncSnmpClient(Protocol):
|
|
63
|
+
"""Asynchronous SNMP v2c read client for a single switch."""
|
|
64
|
+
|
|
65
|
+
async def get(self, oids: list[str]) -> list[SnmpRow]: ...
|
|
66
|
+
|
|
67
|
+
async def walk(self, base_oid: str) -> list[SnmpRow]: ...
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class SnmpWriteClient(SnmpClient, Protocol):
|
|
71
|
+
"""Synchronous SNMP v2c read+write client for a single switch.
|
|
72
|
+
|
|
73
|
+
Extends the read client with SET. ``set_many`` is one PDU (atomic). A write
|
|
74
|
+
RW community can also read, so a single write client verifies its own
|
|
75
|
+
writes via the inherited ``get``/``walk``.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
def set(self, varbind: SetVarbind) -> None: ...
|
|
79
|
+
|
|
80
|
+
def set_many(self, varbinds: list[SetVarbind]) -> None: ...
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class AsyncSnmpWriteClient(AsyncSnmpClient, Protocol):
|
|
84
|
+
"""Asynchronous SNMP v2c read+write client for a single switch."""
|
|
85
|
+
|
|
86
|
+
async def set(self, varbind: SetVarbind) -> None: ...
|
|
87
|
+
|
|
88
|
+
async def set_many(self, varbinds: list[SetVarbind]) -> None: ...
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""SNMP OID constants and per-model vendor OID tables."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
from ...errors import UnsupportedCapabilityError
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
# Imported only for the vendor_oids() type annotation; keep it behind
|
|
12
|
+
# TYPE_CHECKING so ruff's TC rules stay clean and the pure layer stays light.
|
|
13
|
+
from ...registry import SwitchModel
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# Standard-MIB OID constants
|
|
17
|
+
# MIB-II System group scalars (Task 2 model detection): both are full,
|
|
18
|
+
# instance-qualified (".0") leaf OIDs, fetched with a plain exact-OID GET
|
|
19
|
+
# (unlike the walk-based base-OIDs below) -- see snmp_read.read_system_info.
|
|
20
|
+
SYS_DESCR = "1.3.6.1.2.1.1.1.0" # sysDescr: text incl. the model name
|
|
21
|
+
SYS_OBJECT_ID = "1.3.6.1.2.1.1.2.0" # sysObjectID: read-only signal, unused
|
|
22
|
+
# for matching (no known OID->model table exists -- see parse.py's
|
|
23
|
+
# detect_model_from_sysdescr docstring).
|
|
24
|
+
IF_ADMIN_STATUS = "1.3.6.1.2.1.2.2.1.7" # ifAdminStatus (1=up,2=down)
|
|
25
|
+
IF_OPER_STATUS = "1.3.6.1.2.1.2.2.1.8" # ifOperStatus (1=up,2=down)
|
|
26
|
+
IF_IN_ERRORS = "1.3.6.1.2.1.2.2.1.14"
|
|
27
|
+
IF_OUT_ERRORS = "1.3.6.1.2.1.2.2.1.20"
|
|
28
|
+
IF_NAME = "1.3.6.1.2.1.31.1.1.1.1"
|
|
29
|
+
IF_HC_IN_OCTETS = "1.3.6.1.2.1.31.1.1.1.6"
|
|
30
|
+
IF_HC_IN_UCAST = "1.3.6.1.2.1.31.1.1.1.7"
|
|
31
|
+
IF_HC_OUT_OCTETS = "1.3.6.1.2.1.31.1.1.1.10"
|
|
32
|
+
IF_HC_OUT_UCAST = "1.3.6.1.2.1.31.1.1.1.11"
|
|
33
|
+
IF_HIGH_SPEED = "1.3.6.1.2.1.31.1.1.1.15" # Mbps
|
|
34
|
+
IF_ALIAS = "1.3.6.1.2.1.31.1.1.1.18"
|
|
35
|
+
DOT1D_BASE_BRIDGE_ADDRESS = "1.3.6.1.2.1.17.1.1" # scalar (.0); BRIDGE-MIB base MAC
|
|
36
|
+
DOT1D_BASE_PORT_IF_INDEX = "1.3.6.1.2.1.17.1.4.1.2"
|
|
37
|
+
DOT1Q_TP_FDB_PORT = "1.3.6.1.2.1.17.7.1.2.2.1.2" # MAC table, port column ONLY
|
|
38
|
+
DOT1Q_VLAN_STATIC_NAME = "1.3.6.1.2.1.17.7.1.4.3.1.1"
|
|
39
|
+
DOT1Q_VLAN_STATIC_EGRESS = "1.3.6.1.2.1.17.7.1.4.3.1.2"
|
|
40
|
+
DOT1Q_VLAN_STATIC_UNTAGGED = "1.3.6.1.2.1.17.7.1.4.3.1.4"
|
|
41
|
+
DOT1Q_PVID = "1.3.6.1.2.1.17.7.1.4.5.1.1"
|
|
42
|
+
DOT1Q_VLAN_STATIC_ROW_STATUS = "1.3.6.1.2.1.17.7.1.4.3.1.5" # dot1qVlanStaticRowStatus
|
|
43
|
+
ROW_STATUS_CREATE_AND_GO = 4 # RowStatus createAndGo
|
|
44
|
+
ROW_STATUS_DESTROY = 6 # RowStatus destroy
|
|
45
|
+
# columns 5=chassis,7=portId,8=portDesc,9=sysName
|
|
46
|
+
LLDP_REM_TABLE = "1.0.8802.1.1.2.1.4.1"
|
|
47
|
+
# RFC3621; col3=admin, col6=detect
|
|
48
|
+
PETH_PSE_PORT_TABLE = "1.3.6.1.2.1.105.1.1.1"
|
|
49
|
+
# ipAddrTable (snmp_common.py:36 base .4.20)
|
|
50
|
+
IP_ADENT_ADDR = "1.3.6.1.2.1.4.20.1.1"
|
|
51
|
+
IP_ADENT_IFINDEX = "1.3.6.1.2.1.4.20.1.2"
|
|
52
|
+
IP_ADENT_NETMASK = "1.3.6.1.2.1.4.20.1.3"
|
|
53
|
+
IP_ROUTE_DEST = "1.3.6.1.2.1.4.21.1.1" # ipRouteDest
|
|
54
|
+
# ipRouteNextHop (gateway where dest=0.0.0.0)
|
|
55
|
+
IP_ROUTE_NEXTHOP = "1.3.6.1.2.1.4.21.1.7"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# (kind, unit, column suffix under {base}.43.1)
|
|
59
|
+
BOX_SENSOR_COLUMNS: tuple[tuple[str, str, str], ...] = (
|
|
60
|
+
("fan", "RPM", "6.1.4"),
|
|
61
|
+
("power", "W", "8.1.5"),
|
|
62
|
+
("temperature", "C", "15.1.3"),
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
DHCP_MODE_OID_SUFFIX = "99.1"
|
|
66
|
+
"""UNVERIFIED — this Netgear private OID for DHCP-vs-static management-IP mode.
|
|
67
|
+
|
|
68
|
+
This is an unconfirmed guess used only so the mock and reader agree under
|
|
69
|
+
test; it MUST be confirmed against real hardware via the capture utility
|
|
70
|
+
(Slice 7) before it is trusted. Until then get_mgmt_ip returns IpMode.UNKNOWN
|
|
71
|
+
when this OID is absent.
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclass(frozen=True)
|
|
76
|
+
class VendorOids:
|
|
77
|
+
"""Per-model Netgear vendor-specific OID table."""
|
|
78
|
+
|
|
79
|
+
base: str
|
|
80
|
+
poe_power_mw: str
|
|
81
|
+
box_fan: str
|
|
82
|
+
box_psu_power: str
|
|
83
|
+
box_temp: str
|
|
84
|
+
dhcp_mode_unverified: str
|
|
85
|
+
"""The ONE symbol every call site uses for the DHCP-mode OID. See
|
|
86
|
+
DHCP_MODE_OID_SUFFIX above — UNVERIFIED, best-effort read only. No call site
|
|
87
|
+
may hard-code a ``.99.1`` literal; they all reference this field."""
|
|
88
|
+
mgmt_write_addr_unverified: str
|
|
89
|
+
mgmt_write_netmask_unverified: str
|
|
90
|
+
mgmt_write_gateway_unverified: str
|
|
91
|
+
"""UNVERIFIED writable management-IP OIDs — placeholders pending Slice 7
|
|
92
|
+
hardware capture. They are NEVER trusted on real hardware (set_mgmt_ip is
|
|
93
|
+
force-gated and documented UNVERIFIED); they exist so the mutable mock and
|
|
94
|
+
the writer agree under test, mirroring the ``dhcp_mode_unverified``
|
|
95
|
+
precedent above. No call site may hard-code these literals."""
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def vendor_oids(model: SwitchModel) -> VendorOids:
|
|
99
|
+
"""Resolve vendor OIDs for a switch model.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
model: A SwitchModel instance with snmp_vendor_base set.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
VendorOids dataclass populated with vendor-specific OID strings.
|
|
106
|
+
|
|
107
|
+
Raises:
|
|
108
|
+
UnsupportedCapabilityError: If the model has no SNMP vendor base.
|
|
109
|
+
"""
|
|
110
|
+
base = model.snmp_vendor_base
|
|
111
|
+
if base is None:
|
|
112
|
+
raise UnsupportedCapabilityError(
|
|
113
|
+
f"model {model.key!r} has no SNMP vendor OID subtree"
|
|
114
|
+
)
|
|
115
|
+
return VendorOids(
|
|
116
|
+
base=base,
|
|
117
|
+
poe_power_mw=f"{base}.15.1.1.1.2",
|
|
118
|
+
box_fan=f"{base}.43.1.6.1.4",
|
|
119
|
+
box_psu_power=f"{base}.43.1.8.1.5",
|
|
120
|
+
box_temp=f"{base}.43.1.15.1.3",
|
|
121
|
+
dhcp_mode_unverified=f"{base}.{DHCP_MODE_OID_SUFFIX}",
|
|
122
|
+
mgmt_write_addr_unverified=f"{base}.98.1",
|
|
123
|
+
mgmt_write_netmask_unverified=f"{base}.98.2",
|
|
124
|
+
mgmt_write_gateway_unverified=f"{base}.98.3",
|
|
125
|
+
)
|