# SNMP — barox Virtual Switch Simulator

## Agent

- Protocol: **SNMPv2c** only (no v1, no v3 in this version).
- Transport: one UDP socket per virtual switch, bound to that switch's
  `management_ip:snmp_port` (§5/§16). Default port on a fresh switch is
  `161` (privileged — the `barox-snmp.service` unit grants
  `CAP_NET_BIND_SERVICE` so it can bind without running as root); the
  seeded demo switch uses the unprivileged dev port `1161`.
- Communities: per-switch `snmp_community_ro` (default `public`) and
  `snmp_community_rw` (default `private`), editable in the Web UI under a
  switch's **SNMP** tab (Administrator role). GET/GETNEXT/GETBULK accept
  either community; SET requires the RW community. Wrong community = the
  agent silently drops the packet (matches common real-world agent
  behavior; no community-guessing oracle).
- Operations implemented: `GET`, `GETNEXT`, `GETBULK`, `SET`.
- The BER/DER codec (`app/Snmp/Ber.php`), PDU framing (`app/Snmp/Pdu.php`)
  and MIB parser (`app/Snmp/MibParser.php`) are hand-written — no
  net-snmp/php-snmp bindings, no Composer packages (§16-17).

## Testing

```bash
# Walk everything the demo switch exposes (dev port 1161):
snmpwalk -v2c -c public -p 1161 127.0.0.1

# A single scalar:
snmpget -v2c -c public -p 1161 127.0.0.1 1.3.6.1.2.1.1.1.0    # sysDescr.0

# Bulk-walk the standard interface table:
snmpbulkwalk -v2c -c public -p 1161 127.0.0.1 1.3.6.1.2.1.2.2.1.2   # ifDescr

# SNMP SET: disable port 7 admin-state (RW community required)
snmpset -v2c -c private -p 1161 127.0.0.1 1.3.6.1.2.1.2.2.1.7.7 i 2
#   -> immediately visible in the Web UI's Ports tab and via REST API (§59 MVP proof)
```

`tests/test-snmp.sh` automates a similar sequence (GET/walk/bulkwalk/SET,
including verifying the SET is rejected with the RO community).

For a production switch bound to `161`, either run the commands above as
root/with `sudo`, or bind the switch to `127.0.0.1`/a routable IP that
already has appropriate firewall rules; either way the daemon itself never
needs to run as root (`CAP_NET_BIND_SERVICE`, see
`systemd/barox-snmp.service`).

## Supported OIDs

### SNMPv2-MIB / IF-MIB (hand-maintained subset, §18 Phase 1)

| OID | Name | Access |
|---|---|---|
| 1.3.6.1.2.1.1.1.0 | sysDescr | read-only |
| 1.3.6.1.2.1.1.2.0 | sysObjectID | read-only |
| 1.3.6.1.2.1.1.3.0 | sysUpTime | read-only |
| 1.3.6.1.2.1.1.4.0 | sysContact | read-write |
| 1.3.6.1.2.1.1.5.0 | sysName | read-write |
| 1.3.6.1.2.1.1.6.0 | sysLocation | read-write |
| 1.3.6.1.2.1.2.1.0 | ifNumber | read-only |
| 1.3.6.1.2.1.2.2.1.1.\<port\> | ifIndex | read-only |
| 1.3.6.1.2.1.2.2.1.2.\<port\> | ifDescr | read-only |
| 1.3.6.1.2.1.2.2.1.3.\<port\> | ifType (ethernetCsmacd=6) | read-only |
| 1.3.6.1.2.1.2.2.1.4.\<port\> | ifMtu | read-only |
| 1.3.6.1.2.1.2.2.1.5.\<port\> | ifSpeed | read-only |
| 1.3.6.1.2.1.2.2.1.6.\<port\> | ifPhysAddress | read-only |
| 1.3.6.1.2.1.2.2.1.7.\<port\> | **ifAdminStatus** (1=up, 2=down) | **read-write** |
| 1.3.6.1.2.1.2.2.1.8.\<port\> | ifOperStatus (1=up, 2=down) | read-only |
| 1.3.6.1.2.1.2.2.1.9.\<port\> | ifLastChange | read-only |
| 1.3.6.1.2.1.2.2.1.10.\<port\> | ifInOctets | read-only |
| 1.3.6.1.2.1.2.2.1.14.\<port\> | ifInErrors | read-only |
| 1.3.6.1.2.1.2.2.1.16.\<port\> | ifOutOctets | read-only |
| 1.3.6.1.2.1.2.2.1.20.\<port\> | ifOutErrors | read-only |

`ifAdminStatus` is the canonical way to admin up/down a port over SNMP and
is the one used in the MVP acceptance test (§59 #11).

### barox private MIB (enterprise OID `1.3.6.1.4.1.47647.1585.44`)

Full per-OID table (2457 accessible objects, all 3308 nodes resolved) is in
[`MIB_SUPPORT.md`](MIB_SUPPORT.md), auto-generated from the MIB file. 30 of
them are wired to live simulator state ("Implemented"); the private MIB
covers a huge surface (VLAN/STP/QoS/TSN/RADIUS/…) that is out of scope for
a management-plane simulator and is listed as "Planned" — those OIDs simply
don't appear in a walk (never answered with a fabricated value).

**Implemented** (read-write where noted):

- System info (`rylgsp3828SystemInfo*`, 16 scalars): model name, description,
  location*, contact*, system name*, date, uptime, bootloader/firmware/
  hardware/mechanical version, serial, host MAC, fan speed, CPU load,
  production date. (`*` = read-write)
- Port configuration (`rylgsp3828PortConf*`, per port): Link (ro),
  SpeedCurrent (ro), **SpeedMode (rw** — 0=disabled, 2=auto,
  3/4=10M half/full, 5/6=100M half/full, 7=1G full, 10=10G full),
  FlowControlEnable (rw), Description (rw).
- PoE status (`rylgsp3828PoeStatus*`, per PoE-capable port, all read-only):
  PDclass, PowerRequested, PowerAllocated, PowerUsed, CurrentUsed, Priority,
  PortStatus.
- PoE configuration (`rylgsp3828PoeConfigurationPort*`, per PoE-capable
  port): **PoEMode (rw**, 0=disable/1=enable), **Priority (rw**, 0=low/
  1=high/2=critical).

## Traps (§19)

SNMPv2c traps (`TrapV2`), sent by `workers/trap-worker.php` (decoupled from
the request that raised the underlying event, §36). Configure targets per
switch in the Web UI ("Trap Targets" tab) or `trap_targets` table.

- Link/system events use the **standard** SNMPv2-MIB trap OIDs:
  `coldStart` (1.3.6.1.6.3.1.1.5.1), `warmStart` (…5.2),
  `linkDown` (…5.3), `linkUp` (…5.4).
- Everything else (PoE, SFP, DDMI, cable, scenarios, …) uses the
  severity-graded `NOTIFICATION-TYPE`s **actually defined in the private
  MIB** (`rylgsp3828Emergency` … `rylgsp3828Debug` under
  `rylgsp3828TrapEvent`), carrying the human-readable event message in
  `rylgsp3828Information` — no invented private trap OIDs.

Use the **Send Test Trap** button on a switch's Trap Targets tab, or catch
it with:

```bash
sudo snmptrapd -f -Lo -c /dev/null   # or your NMS's trap receiver
```
