Changing a switch¶
Every write below is on SyncSwitch and AsyncSwitch alike. Each takes
force= and backend=, each runs over exactly one protocol, and each
reads back to confirm what it did.
The operations¶
Method |
What it does |
|---|---|
Administratively bring a port up or down. |
|
Enable or disable PoE on a port. |
|
Power-cycle a PoE port: off, wait for the port to stop delivering, on, wait for it to deliver again. |
|
Clear a latched PoE fault on a port. |
|
Set or clear a port’s label. Pass |
|
Force a port’s speed and duplex, or restore auto-negotiation. See
Configured speed is not negotiated speed — the field this write verifies against is
not |
|
Turn IEEE 802.3x flow control on or off for a port. CLI only — no web UI captured so far carries a control for it, including the one whose ports page reports the field. |
|
Set a port’s PVID (native VLAN). |
|
Create a VLAN with a name. |
|
Delete a VLAN. |
|
Set the management address, netmask and gateway. |
|
Set the switch’s host name. |
|
Turn remote logging on or off. Does not change the collector list. |
|
Install an HTTPS server certificate — over the web UI, or by FASTPATH
|
Three safety rails¶
1. ``force=True`` is required. Every disruptive write refuses without it. It is not a confirmation prompt in disguise but a keyword you have to type in the calling code, so a write cannot happen by passing the wrong variable to a read.
2. Protected ports. A port listed in the switch’s protected_ports raises
ProtectedPortError unless forced. delete_vlan extends this: before
deleting, the facade reads the VLAN’s members over the same backend and
refuses if any is protected — because two of the three write backends do not
guard VLAN deletion themselves, so without the facade-level check the backends
would not be equally safe.
3. Read-back verification. After the write lands, the value is read back. A
mismatch raises WriteVerificationError. A switch that accepts a SET and
silently discards it — which does happen, see below — is caught rather than
reported as success.
from netgear_switch import VlanMode, WriteVerificationError
try:
switch.set_vlan_membership(
4001, port=7, mode=VlanMode.TAGGED, force=True
)
except WriteVerificationError as exc:
print("the switch did not do what it said it did:", exc)
from netgear_switch import VlanMode, WriteVerificationError
try:
await switch.set_vlan_membership(
4001, port=7, mode=VlanMode.TAGGED, force=True
)
except WriteVerificationError as exc:
print("the switch did not do what it said it did:", exc)
Writes are model-specific in ways that matter¶
One method call hides genuinely different mechanisms, chosen per model from measurements, not from a MIB’s ideal semantics.
VLAN membership over SNMP has two dialects. On most models it is a
read-modify-write of the standard Q-BRIDGE dot1qVlanStaticEgressPorts and
dot1qVlanStaticUntaggedPorts bitmaps. On FASTPATH 12.x (both M4300 SKUs) it
is not: membership there is owned by the per-port switchport mode, and writes
go to Netgear’s vendor switchport table. On that firmware
dot1qVlanStaticEgressPorts is writable only while no interface on the switch
is in access mode — and since an untagged membership write is expressed as
access mode, the standard dialect would disable itself on first use. Worse,
dot1qVlanStaticUntaggedPorts returns noError and then silently discards
the write. The model’s snmp_vlan_write field selects the dialect; see
src/netgear_switch/registry.py.
Some switches need two PDUs, not one. On the S3300 (gsm7228ps), setting
a port’s egress bit makes it an untagged member as a side effect, and that
side effect beats an untagged varbind travelling in the same PDU — so a
TAGGED request silently lands as untagged. Splitting the write into two
PDUs, egress first, works. snmp_vlan_split_membership_writes turns this on
for that model only, because the GSM7252PS applies the combined PDU correctly
and its verified path is not worth disturbing.
You do not have to know any of this to call set_vlan_membership. The detail
is here because it explains why the same call can behave differently across two
switches from the same family, and why this project refuses to extrapolate
between SKUs.
Configured speed is not negotiated speed¶
set_port_speed writes the port’s configuration; PortStatus
reports both that and the result, in two separate fields:
speed_config— aPortSpeed: what the port is set to (show port’s “Physical Mode” column). Answers even while the link is down.speed_mbps/full_duplex— what the link actually negotiated (“Physical Status”).Nonewhile the link is down, because a down port has negotiated nothing.
They are separate because they genuinely disagree, and disagree hardest exactly
where this library operates. A port forced to 100 Mbit/s with no cable in it
reports speed_config=PortSpeed.forced(100, full_duplex=True) and
speed_mbps=None. set_port_speed verifies itself against the first;
verifying against the second would mean a write to any link-down port could
never be confirmed.
from netgear_switch import PortSpeed
switch.set_port_speed(8, PortSpeed.forced(100, full_duplex=True), force=True)
switch.set_port_speed(8, PortSpeed.auto(), force=True) # and back
The two backends disagree about a forced 1000, and both are right.
Over the CLI, asking for one raises CliCommandError before
anything is sent. 1000BASE-T requires auto-negotiation, and the FASTPATH
grammar encodes that by leaving 1000 out of its forced speed command
entirely while keeping it among speed auto’s advertised rates.
Over the GoAhead web UI (GS728TPP) it is accepted, because that page’s own Speed control offers “1000M Full Duplex” — and the live switch really does run its four SFP uplinks that way. Fibre 1000BASE-X carries no such requirement. Harmonising the two into a single rule would have made one of them wrong about real hardware, so each backend answers for its own device.
Which other rates are available differs the same way, and for the same reason — the PHY, not the firmware:
CLI: no rate table is kept at all. A 1G copper port offered 10/100/10G while a 10GBASE-T port on another model offered 100/10G, so the library sends what you ask for and surfaces the switch’s own
% Invalid inputasCliCommandError.HTTP: the page publishes its choices as a dropdown, so the library validates against that captured list (10/100 half or full, 1000 full, Auto) and refuses anything else with
UnsupportedCapabilityError.
Certificates¶
Two mechanisms, and the right one depends on the model:
# Web-UI multipart upload (S3300 / gsm7228ps, GS728TPP):
switch.upload_certificate(cert_pem, key_pem, force=True)
# FASTPATH copy scp:// (M4300, GSM7252PS):
switch.upload_certificate_scp(
scp_source="user@stage.example",
scp_password="...",
remote_dir="/srv/certs",
chain=True,
)
# Web-UI multipart upload (S3300 / gsm7228ps, GS728TPP):
await switch.upload_certificate(cert_pem, key_pem, force=True)
# FASTPATH copy scp:// (M4300, GSM7252PS):
await switch.upload_certificate_scp(
scp_source="user@stage.example",
scp_password="...",
remote_dir="/srv/certs",
chain=True,
)
Calling upload_certificate on a model whose mechanism is SCP raises
NotImplementedError — deliberately, not UnsupportedCapabilityError — naming
the mechanism and pointing at the other method. The hardware can do it; that
backend cannot.
The SCP flow is not live-verified: it is grounded in working prior art and
tested end-to-end against the mock, but a real run needs a staging SCP server
that CI does not have. It disables HTTPS, copies the server certificate (and
optionally the root chain), re-enables HTTPS to load it, and saves the
configuration. The switch is not rebooted. You must stage the PEM files on
the SCP source yourself: the switch pulls
<host-with-dots-as-dashes>-server.pem from remote_dir.
Management IP¶
set_mgmt_ip is implemented and mock-verified on every backend that
has it. The address you are talking to changes mid-operation, so the connection
issuing the write is dropped by definition. This project has deliberately never
applied it to a live switch.
From the command line¶
Every disruptive ngsw subcommand carries the same three gates:
ngsw --switch core port 7 down --dry-run # print, send nothing
ngsw --switch core port 7 down # prompt for confirmation
ngsw --switch core port 7 down --yes # skip the prompt
ngsw --switch core vlan set 100 7 tagged --force
--dry-run describes the operation at facade granularity — method, arguments
and host — rather than re-encoding the SNMP SET or the HTTP form, so what it
prints cannot drift from what would be sent.
Testing writes against real hardware¶
This project follows these rules for its own live verification. They are worth adopting.
Record the exact prior state, restore it, and prove the restore by re-reading. Not “I think it was enabled”.
Use throwaway VLAN ids — this project reserves 4001–4008 — never a production VLAN.
Touch only a port that is link-down and has no description. Never a described production port.
Never save the configuration. No
write memory: a mistake should not survive a reboot.Never change a management IP on a switch you need to keep talking to.
Why a write fails¶
When a write is refused, the cause is almost never the hardware being flaky. Check, in order:
Credentials. An SNMP agent silently drops an unauthorised request, so a wrong write community is indistinguishable from an unreachable host. One switch in this fleet has no
privatecommunity at all — its read-write community ispublic, and “the agent is dead” turned out to be a wrong credential.A prerequisite setting. Some writes are gated by other state: on FASTPATH 12.x, a port’s switchport mode governs whether membership is writable at all.
Ordering. Two varbinds in one PDU are not the same as two PDUs, as the S3300 shows.
The value’s type, encoding or field width. A
PortListbitmap must be the width the device uses — 79, 131 and 45 bytes on three switches here, none of them derivable from the port count.
Only after all of that, with captured device output as proof, is a limitation real — and it must name the firmware version it applies to. See Design principles.