diff --git a/html/includes/print-stp.inc.php b/html/includes/print-stp.inc.php new file mode 100644 index 0000000000..2d45ce217a --- /dev/null +++ b/html/includes/print-stp.inc.php @@ -0,0 +1,29 @@ + ($stp_raw['rootBridge'] == 1) ? 'Yes' : 'No', + 'Bridge address (MAC)' => $stp_raw['bridgeAddress'], + 'Protocol specification' => $stp_raw['protocolSpecification'], + 'Priority (0-61440)' => $stp_raw['priority'], + 'Time since topology change' => formatUptime($stp_raw['timeSinceTopologyChange']), + 'Topology changes' => $stp_raw['topChanges'], + 'Designated root (MAC)' => $stp_raw['designatedRoot'], + 'Root cost' => $stp_raw['rootCost'], + 'Root port' => $stp_raw['rootPort'], + 'Max age (s)' => $stp_raw['maxAge'], + 'Hello time (s)' => $stp_raw['helloTime'], + 'Hold time (s)' => $stp_raw['holdTime'], + 'Forward delay (s)' => $stp_raw['forwardDelay'], + 'Bridge max age (s)' => $stp_raw['bridgeMaxAge'], + 'Bridge hello time (s)' => $stp_raw['bridgeHelloTime'], + 'Bridge forward delay (s)' => $stp_raw['bridgeForwardDelay'] +); +foreach (array_keys($stp) as $key) { + echo " + + $key + $stp[$key] + + "; +} diff --git a/html/pages/device.inc.php b/html/pages/device.inc.php index 1a68f9dbf3..e7638d5799 100644 --- a/html/pages/device.inc.php +++ b/html/pages/device.inc.php @@ -229,6 +229,14 @@ if (device_permitted($vars['device']) || $check_device == $vars['device']) { '); + if (@dbFetchCell("SELECT COUNT(stp_id) FROM stp WHERE device_id = '".$device['device_id']."'") > '0') { + echo '
  • + + STP + +
  • '; + } + if (@dbFetchCell("SELECT COUNT(*) FROM `packages` WHERE device_id = '".$device['device_id']."'") > '0') { echo '
  • diff --git a/html/pages/device/stp.inc.php b/html/pages/device/stp.inc.php new file mode 100644 index 0000000000..74a803b0b4 --- /dev/null +++ b/html/pages/device/stp.inc.php @@ -0,0 +1,50 @@ + 'device', + 'device' => $device['device_id'], + 'tab' => 'stp', + ); + +print_optionbar_start(); + +echo "STP » "; + +if (!$vars['view']) { + $vars['view'] = 'basic'; +} + +$menu_options['basic'] = 'Basic'; +// $menu_options['details'] = 'Details'; +$sep = ''; +foreach ($menu_options as $option => $text) { + echo $sep; + if ($vars['view'] == $option) { + echo ""; + } + + echo generate_link($text, $link_array, array('view' => $option)); + if ($vars['view'] == $option) { + echo ''; + } + + $sep = ' | '; +} + +unset($sep); + +print_optionbar_end(); + +echo ''; + +$i = '1'; + +foreach (dbFetchRows("SELECT * FROM `stp` WHERE `device_id` = ? ORDER BY 'stp_id'", array($device['device_id'])) as $stp) { + include 'includes/print-stp.inc.php'; + + $i++; +} + +echo '
    '; + +$pagetitle[] = 'STP'; diff --git a/includes/defaults.inc.php b/includes/defaults.inc.php index 407402746f..19a2b096af 100644 --- a/includes/defaults.inc.php +++ b/includes/defaults.inc.php @@ -695,6 +695,7 @@ $config['poller_modules']['applications'] = 1; $config['poller_modules']['cisco-asa-firewall'] = 1; $config['poller_modules']['mib'] = 0; $config['poller_modules']['cisco-voice'] = 1; +$config['poller_modules']['stp'] = 1; // List of discovery modules. Need to be in this array to be // considered for execution. @@ -726,6 +727,7 @@ $config['discovery_modules']['toner'] = 1; $config['discovery_modules']['ucd-diskio'] = 1; $config['discovery_modules']['services'] = 1; $config['discovery_modules']['charge'] = 1; +$config['discovery_modules']['stp'] = 1; $config['modules_compat']['rfc1628']['liebert'] = 1; $config['modules_compat']['rfc1628']['netmanplus'] = 1; diff --git a/includes/discovery/stp.inc.php b/includes/discovery/stp.inc.php new file mode 100644 index 0000000000..c6be49f569 --- /dev/null +++ b/includes/discovery/stp.inc.php @@ -0,0 +1,118 @@ + + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. Please see LICENSE.txt at the top level of + * the source code distribution for details. + * + * Based on IEEE-802.1D-2004, (STP, RSTP) + * needs RSTP-MIB + */ + +echo "Spanning Tree: "; + +// Pre-cache existing state of STP for this device from database +$stp_db = dbFetchRow('SELECT * FROM `stp` WHERE `device_id` = ?', array($device['device_id'])); +//d_echo($stp_db); + +$stpprotocol = snmp_get($device, 'dot1dStpProtocolSpecification.0', '-Oqv', 'RSTP-MIB'); + +// FIXME I don't know what "unknown" means, perhaps MSTP? (saw it on some cisco devices) +// But we can try to retrieve data +if ($stpprotocol == 'ieee8021d' || $stpprotocol == 'unknown') { + + // set time multiplier to convert from centiseconds to seconds + // all time values are stored in databese as seconds + $tm = '0.01'; + // some vendors like PBN dont follow the 802.1D implementation and use seconds in SNMP + if ($device['os'] == 'pbn') { + preg_match('/^.* Build (?\d+)/', $device['version'], $version); + if ($version[build] <= 16607) { // Buggy version :-( + $tm = '1'; + } + } + + // read the 802.1D subtree + $stp_raw = snmpwalk_cache_oid($device, 'dot1dStp', array(), 'RSTP-MIB'); + $stp = array( + 'protocolSpecification' => $stp_raw[0]['dot1dStpProtocolSpecification'], + 'priority' => $stp_raw[0]['dot1dStpPriority'], + 'topChanges' => $stp_raw[0]['dot1dStpTopChanges'], + 'rootCost' => $stp_raw[0]['dot1dStpRootCost'], + 'rootPort' => $stp_raw[0]['dot1dStpRootPort'], + 'maxAge' => $stp_raw[0]['dot1dStpMaxAge'] * $tm, + 'helloTime' => $stp_raw[0]['dot1dStpHelloTime'] * $tm, + 'holdTime' => $stp_raw[0]['dot1dStpHoldTime'] * $tm, + 'forwardDelay' => $stp_raw[0]['dot1dStpForwardDelay'] * $tm, + 'bridgeMaxAge' => $stp_raw[0]['dot1dStpBridgeMaxAge'] * $tm, + 'bridgeHelloTime' => $stp_raw[0]['dot1dStpBridgeHelloTime'] * $tm, + 'bridgeForwardDelay' => $stp_raw[0]['dot1dStpBridgeForwardDelay'] * $tm + ); + + // set device binding + $stp['device_id'] = $device['device_id']; + + // read the 802.1D bridge address and set as MAC in database + $mac_raw = snmp_get($device, 'dot1dBaseBridgeAddress.0', '-Oqv', 'RSTP-MIB'); + + // read Time as timetics (in hundredths of a seconds) since last topology change and convert to seconds + $time_since_change = snmp_get($device, 'dot1dStpTimeSinceTopologyChange.0', '-Ovt', 'RSTP-MIB'); + if ($time_since_change > '100') { + $time_since_change = substr($time_since_change, 0, -2); // convert to seconds since change + } + else { + $time_since_change = '0'; + } + $stp['timeSinceTopologyChange'] = $time_since_change; + + // designated root is stored in format 2 octet bridge priority + MAC address, so we need to normalize it + $dr = str_replace(array(' ', ':', '-'), '', strtolower($stp_raw[0]['dot1dStpDesignatedRoot'])); + $dr = substr($dr, -12); //remove first two octets + $stp['designatedRoot'] = $dr; + + // normalize the MAC + $mac_array = explode(':', $mac_raw); + foreach($mac_array as &$octet) { + if (strlen($octet) < 2) { + $octet = "0" . $octet; // add suppressed 0 + } + } + $stp['bridgeAddress'] = implode($mac_array); + + // I'm the boss? + if ($stp['bridgeAddress'] == $stp['designatedRoot']) { + $stp['rootBridge'] = '1'; + } + else { + $stp['rootBridge'] = '0'; + } + + d_echo($stp); + + if ($stp_raw[0]['version'] == '3') { + echo "RSTP "; + } + else { + echo "STP "; + } + + if (!$stp_db['bridgeAddress'] && $stp['bridgeAddress']) { + dbInsert($stp,'stp'); + log_event('STP added, bridge address: '.$stp['bridgeAddress'], $device, 'stp'); + echo '+'; + } + + if ($stp_db['bridgeAddress'] && !$stp['bridgeAddress']) { + dbDelete('stp','device_id = ?', array($device['device_id'])); + log_event('STP removed', $device, 'stp'); + echo '-'; + } +} + +unset($stp_raw, $stp, $stp_db); +echo "\n"; diff --git a/includes/polling/stp.inc.php b/includes/polling/stp.inc.php new file mode 100644 index 0000000000..d9886a58c7 --- /dev/null +++ b/includes/polling/stp.inc.php @@ -0,0 +1,127 @@ + + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. Please see LICENSE.txt at the top level of + * the source code distribution for details. + * + * Based on IEEE-802.1D-2004, (STP, RSTP) + * needs RSTP-MIB + */ + +echo "Spanning Tree: "; + +// Pre-cache existing state of STP for this device from database +$stp_db = dbFetchRow('SELECT * FROM `stp` WHERE `device_id` = ?', array($device['device_id'])); +//d_echo($stp_db); + +$stpprotocol = snmp_get($device, 'dot1dStpProtocolSpecification.0', '-Oqv', 'RSTP-MIB'); + +// FIXME I don't know what "unknown" means, perhaps MSTP? (saw it on some cisco devices) +// But we can try to retrieve data +if ($stpprotocol == 'ieee8021d' || $stpprotocol == 'unknown') { + + // set time multiplier to convert from centiseconds to seconds + // all time values are stored in databese as seconds + $tm = '0.01'; + // some vendors like PBN dont follow the 802.1D implementation and use seconds in SNMP + if ($device['os'] == 'pbn') { + preg_match('/^.* Build (?\d+)/', $device['version'], $version); + if ($version[build] <= 16607) { // Buggy version :-( + $tm = '1'; + } + } + + // read the 802.1D subtree + $stp_raw = snmpwalk_cache_oid($device, 'dot1dStp', array(), 'RSTP-MIB'); + $stp = array( + 'protocolSpecification' => $stp_raw[0]['dot1dStpProtocolSpecification'], + 'priority' => $stp_raw[0]['dot1dStpPriority'], + 'topChanges' => $stp_raw[0]['dot1dStpTopChanges'], + 'rootCost' => $stp_raw[0]['dot1dStpRootCost'], + 'rootPort' => $stp_raw[0]['dot1dStpRootPort'], + 'maxAge' => $stp_raw[0]['dot1dStpMaxAge'] * $tm, + 'helloTime' => $stp_raw[0]['dot1dStpHelloTime'] * $tm, + 'holdTime' => $stp_raw[0]['dot1dStpHoldTime'] * $tm, + 'forwardDelay' => $stp_raw[0]['dot1dStpForwardDelay'] * $tm, + 'bridgeMaxAge' => $stp_raw[0]['dot1dStpBridgeMaxAge'] * $tm, + 'bridgeHelloTime' => $stp_raw[0]['dot1dStpBridgeHelloTime'] * $tm, + 'bridgeForwardDelay' => $stp_raw[0]['dot1dStpBridgeForwardDelay'] * $tm + ); + + // set device binding + $stp['device_id'] = $device['device_id']; + + // read the 802.1D bridge address and set as MAC in database + $mac_raw = snmp_get($device, 'dot1dBaseBridgeAddress.0', '-Oqv', 'RSTP-MIB'); + + // read Time as timetics (in hundredths of a seconds) since last topology change and convert to seconds + $time_since_change = snmp_get($device, 'dot1dStpTimeSinceTopologyChange.0', '-Ovt', 'RSTP-MIB'); + if ($time_since_change > '100') { + $time_since_change = substr($time_since_change, 0, -2); // convert to seconds since change + } + else { + $time_since_change = '0'; + } + $stp['timeSinceTopologyChange'] = $time_since_change; + + // designated root is stored in format 2 octet bridge priority + MAC address, so we need to normalize it + $dr = str_replace(array(' ', ':', '-'), '', strtolower($stp_raw[0]['dot1dStpDesignatedRoot'])); + $dr = substr($dr, -12); //remove first two octets + $stp['designatedRoot'] = $dr; + + // normalize the MAC + $mac_array = explode(':', $mac_raw); + foreach($mac_array as &$octet) { + if (strlen($octet) < 2) { + $octet = "0" . $octet; // add suppressed 0 + } + } + $stp['bridgeAddress'] = implode($mac_array); + + // I'm the boss? + if ($stp['bridgeAddress'] == $stp['designatedRoot']) { + $stp['rootBridge'] = '1'; + } + else { + $stp['rootBridge'] = '0'; + } + + d_echo($stp); + + if ($stp_db['bridgeAddress'] && $stp['bridgeAddress']) { + // Logging if designated root changed since last db update + if ($stp_db['designatedRoot'] != $stp['designatedRoot']) { + log_event('STP designated root changed: '.$stp_db['designatedRoot'].' > '.$stp['designatedRoot'], $device, 'stp'); + } + + // Logging if designated root port changed since last db update + if ($stp_db['rootPort'] != $stp['rootPort']) { + log_event('STP root port changed: '.$stp_db['rootPort'].' > '.$stp['rootPort'], $device, 'stp'); + } + + // Logging if topology changed since last db update + if ($stp_db['timeSinceTopologyChange'] > $stp['timeSinceTopologyChange']) { + // FIXME log_event should log really changing time, not polling time + // but upstream function do not care about this at the moment. + // + // saw same problem with this line librenms/includes/polling/system.inc.php + // log_event('Device rebooted after '.formatUptime($device['uptime']), $device, 'reboot', $device['uptime']); + // ToDo fix log_event() + // + //log_event('STP topology changed after: '.formatUptime($stp['timeSinceTopologyChange']), $device, 'stp', $stp['timeSinceTopologyChange']); + log_event('STP topology changed after: '.formatUptime($stp['timeSinceTopologyChange']), $device, 'stp'); + } + // Write to db + dbUpdate($stp,'stp','device_id = ?', array($device['device_id'])); + echo '.'; + } +} + +unset($stp_raw, $stp, $stp_db); +echo "\n"; diff --git a/sql-schema/085.sql b/sql-schema/085.sql new file mode 100644 index 0000000000..f5718e5bdf --- /dev/null +++ b/sql-schema/085.sql @@ -0,0 +1,27 @@ +CREATE TABLE IF NOT EXISTS `stp` ( +`stp_id` int(11) NOT NULL, + `device_id` int(11) NOT NULL, + `rootBridge` tinyint(1) NOT NULL, + `bridgeAddress` varchar(32) NOT NULL, + `protocolSpecification` varchar(16) NOT NULL, + `priority` mediumint(9) NOT NULL, + `timeSinceTopologyChange` varchar(32) NOT NULL, + `topChanges` mediumint(9) NOT NULL, + `designatedRoot` varchar(32) NOT NULL, + `rootCost` mediumint(9) NOT NULL, + `rootPort` mediumint(9) NOT NULL, + `maxAge` mediumint(9) NOT NULL, + `helloTime` mediumint(9) NOT NULL, + `holdTime` mediumint(9) NOT NULL, + `forwardDelay` mediumint(9) NOT NULL, + `bridgeMaxAge` smallint(6) NOT NULL, + `bridgeHelloTime` smallint(6) NOT NULL, + `bridgeForwardDelay` smallint(6) NOT NULL +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; + +ALTER TABLE `stp` + ADD PRIMARY KEY (`stp_id`), ADD KEY `stp_host` (`device_id`); + +ALTER TABLE `stp` +MODIFY `stp_id` int(11) NOT NULL AUTO_INCREMENT,AUTO_INCREMENT=1; +