Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add basic STP/RSTP support #2690

Merged
merged 5 commits into from Jan 10, 2016
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
29 changes: 29 additions & 0 deletions html/includes/print-stp.inc.php
@@ -0,0 +1,29 @@
<?php

$stp_raw = dbFetchRow('SELECT * FROM `stp` WHERE `device_id` = ?', array($device['device_id']));
$stp = array (
'Root bridge' => ($stp_raw['rootBridge'] == 1) ? 'Yes' : 'No',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to visually align the => in this section.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Paulgear, thank you for hint!

'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 "
<tr>
<td width=280 class=list-large>$key</td>
<td class=box-desc>$stp[$key]</td>
</tr>
";
}
8 changes: 8 additions & 0 deletions html/pages/device.inc.php
Expand Up @@ -229,6 +229,14 @@
</a>
</li>');

if (@dbFetchCell("SELECT COUNT(stp_id) FROM stp WHERE device_id = '".$device['device_id']."'") > '0') {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of count just select 1 - will take some load away from the sql if the table gets too populated

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should do not count all the id entries, because of "WHERE device_id =" statement in the query.
As I know the COUNT function is called after WHERE, but you can correct me if I'm wrong (I'm not the SQL guru)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well it boils down to:
Does stp have more than 1 entry?

If so, the count is more excessive than just a boolean check whether something is there or not.

On 8 January 2016 10:52:39 CET, Vitali Kari notifications@github.com wrote:

@@ -229,6 +229,14 @@

');

  •    if (@dbFetchCell("SELECT COUNT(stp_id) FROM stp WHERE
    
    device_id = '".$device['device_id']."'") > '0') {

this should do not count all the id entries, because of "WHERE
device_id =" statement in the query.
As I know the COUNT function is called after WHERE, but you can correct
me if I'm wrong (I'm not the SQL guru)


Reply to this email directly or view it on GitHub:
https://github.com/librenms/librenms/pull/2690/files#r49172512

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that we would have more then one STP/RSTP instance per device, maybe in VRF.
I can change this to boolean to save some ms.

echo '<li class="'.$select['stp'].'">
<a href="'.generate_device_url($device, array('tab' => 'stp')).'">
<img src="images/16/chart_organisation.png" align="absmiddle" border="0" /> STP
</a>
</li>';
}

if (@dbFetchCell("SELECT COUNT(*) FROM `packages` WHERE device_id = '".$device['device_id']."'") > '0') {
echo '<li class="'.$select['packages'].'">
<a href="'.generate_device_url($device, array('tab' => 'packages')).'">
Expand Down
50 changes: 50 additions & 0 deletions html/pages/device/stp.inc.php
@@ -0,0 +1,50 @@
<?php

$link_array = array(
'page' => 'device',
'device' => $device['device_id'],
'tab' => 'stp',
);

print_optionbar_start();

echo "<span style='font-weight: bold;'>STP</span> &#187; ";

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 "<span class='pagemenu-selected'>";
}

echo generate_link($text, $link_array, array('view' => $option));
if ($vars['view'] == $option) {
echo '</span>';
}

$sep = ' | ';
}

unset($sep);

print_optionbar_end();

echo '<table border="0" cellspacing="0" cellpadding="5" width="100%">';

$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 '</table>';

$pagetitle[] = 'STP';
2 changes: 2 additions & 0 deletions includes/defaults.inc.php
Expand Up @@ -695,6 +695,7 @@ function set_debug($debug) {
$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.
Expand Down Expand Up @@ -726,6 +727,7 @@ function set_debug($debug) {
$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;
Expand Down
118 changes: 118 additions & 0 deletions includes/discovery/stp.inc.php
@@ -0,0 +1,118 @@
<?php
/*
* LibreNMS
*
* Copyright (c) 2015 Vitali Kari <vitali.kari@gmail.com>
*
* 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 (?<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";
127 changes: 127 additions & 0 deletions includes/polling/stp.inc.php
@@ -0,0 +1,127 @@
<?php
/*
* LibreNMS
*
* Copyright (c) 2015 Vitali Kari <vitali.kari@gmail.com>
*
* 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 (?<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";
27 changes: 27 additions & 0 deletions 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;