. * * @link https://www.librenms.org * * @copyright 2021 Tony Murray * @author Tony Murray */ namespace LibreNMS\OS\Traits; use App\Models\PortStp; use App\Models\Stp; use Illuminate\Support\Collection; use LibreNMS\Util\Mac; use SnmpQuery; trait BridgeMib { public function discoverStpInstances(?string $vlan = null): Collection { $protocol = SnmpQuery::get('BRIDGE-MIB::dot1dStpProtocolSpecification.0')->value(); // 1 = unknown (mstp?), 3 = ieee8021d if ($protocol != 1 && $protocol != 3) { return new Collection; } $timeFactor = $this->stpTimeFactor ?? 0.01; // fetch STP config and store it $stp = SnmpQuery::context("$vlan", 'vlan-')->enumStrings()->get([ 'BRIDGE-MIB::dot1dBaseBridgeAddress.0', 'BRIDGE-MIB::dot1dStpProtocolSpecification.0', 'BRIDGE-MIB::dot1dStpPriority.0', 'BRIDGE-MIB::dot1dStpTimeSinceTopologyChange.0', 'BRIDGE-MIB::dot1dStpTopChanges.0', 'BRIDGE-MIB::dot1dStpDesignatedRoot.0', 'BRIDGE-MIB::dot1dStpRootCost.0', 'BRIDGE-MIB::dot1dStpRootPort.0', 'BRIDGE-MIB::dot1dStpMaxAge.0', 'BRIDGE-MIB::dot1dStpHelloTime.0', 'BRIDGE-MIB::dot1dStpHoldTime.0', 'BRIDGE-MIB::dot1dStpForwardDelay.0', 'BRIDGE-MIB::dot1dStpBridgeMaxAge.0', 'BRIDGE-MIB::dot1dStpBridgeHelloTime.0', 'BRIDGE-MIB::dot1dStpBridgeForwardDelay.0', ])->values(); if (empty($stp)) { return new Collection; } $bridge = Mac::parseBridge($stp['BRIDGE-MIB::dot1dBaseBridgeAddress.0'] ?? ''); $bridgeMac = $bridge->hex(); $drBridge = Mac::parseBridge($stp['BRIDGE-MIB::dot1dStpDesignatedRoot.0'] ?? ''); \Log::info(sprintf('VLAN: %s Bridge: %s DR: %s', $vlan ?: 1, $bridge->readable(), $drBridge->readable())); $instance = new \App\Models\Stp([ 'vlan' => $vlan, 'rootBridge' => $bridgeMac == $drBridge->hex() ? 1 : 0, 'bridgeAddress' => $bridgeMac, 'protocolSpecification' => $stp['BRIDGE-MIB::dot1dStpProtocolSpecification.0'] ?? 'unknown', 'priority' => $stp['BRIDGE-MIB::dot1dStpPriority.0'] ?? 0, 'timeSinceTopologyChange' => substr($stp['BRIDGE-MIB::dot1dStpTimeSinceTopologyChange.0'] ?? '', 0, -2) ?: 0, 'topChanges' => $stp['BRIDGE-MIB::dot1dStpTopChanges.0'] ?? 0, 'designatedRoot' => $drBridge->hex(), 'rootCost' => $stp['BRIDGE-MIB::dot1dStpRootCost.0'] ?? 0, 'rootPort' => $stp['BRIDGE-MIB::dot1dStpRootPort.0'] ?? 0, 'maxAge' => ($stp['BRIDGE-MIB::dot1dStpMaxAge.0'] ?? 0) * $timeFactor, 'helloTime' => ($stp['BRIDGE-MIB::dot1dStpHelloTime.0'] ?? 0) * $timeFactor, 'holdTime' => ($stp['BRIDGE-MIB::dot1dStpHoldTime.0'] ?? 0) * $timeFactor, 'forwardDelay' => ($stp['BRIDGE-MIB::dot1dStpForwardDelay.0'] ?? 0) * $timeFactor, 'bridgeMaxAge' => ($stp['BRIDGE-MIB::dot1dStpBridgeMaxAge.0'] ?? 0) * $timeFactor, 'bridgeHelloTime' => ($stp['BRIDGE-MIB::dot1dStpBridgeHelloTime.0'] ?? 0) * $timeFactor, 'bridgeForwardDelay' => ($stp['BRIDGE-MIB::dot1dStpBridgeForwardDelay.0'] ?? 0) * $timeFactor, ]); return (new Collection())->push($instance); } public function discoverStpPorts(Collection $stpInstances): Collection { $ports = new Collection; foreach ($stpInstances as $instance) { $vlan_ports = SnmpQuery::context("$instance->vlan", 'vlan-') ->enumStrings()->walk('BRIDGE-MIB::dot1dStpPortTable') ->mapTable(function ($data, $port) use ($instance) { return new PortStp([ 'vlan' => $instance->vlan, 'port_id' => $this->basePortToId($port), 'port_index' => $port, 'priority' => $data['BRIDGE-MIB::dot1dStpPortPriority'] ?? 0, 'state' => $data['BRIDGE-MIB::dot1dStpPortState'] ?? 'unknown', 'enable' => $data['BRIDGE-MIB::dot1dStpPortEnable'] ?? 'unknown', 'pathCost' => $data['BRIDGE-MIB::dot1dStpPortPathCost32'] ?? $data['BRIDGE-MIB::dot1dStpPortPathCost'] ?? 0, 'designatedRoot' => Mac::parseBridge($data['BRIDGE-MIB::dot1dStpPortDesignatedRoot'] ?? '')->hex(), 'designatedCost' => $data['BRIDGE-MIB::dot1dStpPortDesignatedCost'] ?? 0, 'designatedBridge' => Mac::parseBridge($data['BRIDGE-MIB::dot1dStpPortDesignatedBridge'] ?? '')->hex(), 'designatedPort' => $this->designatedPort($data['BRIDGE-MIB::dot1dStpPortDesignatedPort'] ?? ''), 'forwardTransitions' => $data['BRIDGE-MIB::dot1dStpPortForwardTransitions'] ?? 0, ]); })->filter(function (PortStp $port) { if ($port->enable === 'disabled') { d_echo("$port->port_index ($port->vlan) disabled skipping\n"); return false; } if ($port->state === 'disabled') { d_echo("$port->port_index ($port->vlan) state disabled skipping\n"); return false; } if (! $port->port_id) { d_echo("$port->port_index ($port->vlan) port not found skipping\n"); return false; } d_echo("Discovered STP port $port->port_index ($port->vlan): $port->port_id"); return true; }); $ports = $ports->merge($vlan_ports); } return $ports; } public function pollStpInstances(Collection $stpInstances): Collection { return $stpInstances->each(function (Stp $instance) { $data = SnmpQuery::context("$instance->vlan", 'vlan-')->enumStrings()->get([ 'BRIDGE-MIB::dot1dStpTimeSinceTopologyChange.0', 'BRIDGE-MIB::dot1dStpTopChanges.0', 'BRIDGE-MIB::dot1dStpDesignatedRoot.0', ])->values(); $instance->timeSinceTopologyChange = substr($data['BRIDGE-MIB::dot1dStpTimeSinceTopologyChange.0'] ?? '', 0, -2) ?: 0; $instance->topChanges = $data['BRIDGE-MIB::dot1dStpTopChanges.0'] ?? 0; $instance->designatedRoot = Mac::parseBridge($data['BRIDGE-MIB::dot1dStpDesignatedRoot.0'] ?? '')->hex(); $instance->rootBridge = $instance->bridgeAddress == $instance->designatedRoot; // dr might have changed }); } public function pollStpPorts(Collection $stpPorts): Collection { foreach ($stpPorts->groupBy('vlan') as $vlan => $vlan_ports) { $vlan_ports = $vlan_ports->keyBy('port_index'); $oids = $vlan_ports->keys()->sort()->reduce(function ($carry, $base_port) { $carry[] = 'BRIDGE-MIB::dot1dStpPortState.' . $base_port; $carry[] = 'BRIDGE-MIB::dot1dStpPortEnable.' . $base_port; $carry[] = 'BRIDGE-MIB::dot1dStpPortDesignatedRoot.' . $base_port; $carry[] = 'BRIDGE-MIB::dot1dStpPortDesignatedBridge.' . $base_port; return $carry; }, []); SnmpQuery::context("$vlan", 'vlan-')->enumStrings()->get($oids) ->mapTable(function ($data, $base_port) use ($vlan, $vlan_ports) { $port = $vlan_ports->get($base_port); $port->vlan = $vlan; $port->state = $data['BRIDGE-MIB::dot1dStpPortState'] ?? 'unknown'; $port->enable = $data['BRIDGE-MIB::dot1dStpPortEnable'] ?? 'unknown'; $port->designatedRoot = Mac::parseBridge($data['BRIDGE-MIB::dot1dStpPortDesignatedRoot'] ?? '')->hex(); $port->designatedBridge = Mac::parseBridge($data['BRIDGE-MIB::dot1dStpPortDesignatedBridge'] ?? '')->hex(); return $port; }); } return $stpPorts; } private function designatedPort(string $dp): int { if (preg_match('/-(\d+)/', $dp, $matches)) { // Syntax with "priority" dash "portID" like so : 32768-54, both in decimal return (int) $matches[1]; } // Port saved in format priority+port (ieee 802.1d-1998: clause 8.5.5.1) $dp = substr($dp, -2); //discard the first octet (priority part) return (int) hexdec($dp); } }