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 "';
+ }
+
+ $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;
+